marslang rs-0.19.1

Strings and slicing#

Both single and double quotes create strings. Boolean literals are true and false (lowercase). Strings are immutable values: these methods return results without changing the original binding.

Triple double quotes make a string that spans lines, keeping its line breaks, indentation, and any " inside it; escapes such as \t still work:

mars
banner = """Marslang
  says "hello"
  to\tyou""";

The text is taken exactly as written, so the line break after an opening """ and the indentation of the lines are part of the string. Docstrings are the exception: @Decorator.docstring removes the indentation they share.

Raw strings#

An r straight before the opening quote makes a raw string, in which a backslash is only a backslash and escapes nothing. Use them for regular expressions and Windows paths:

mars
pattern = r"\d{4}-\d{2}";          // the same as "\\d{4}-\\d{2}"
folder = r"C:\Users\ada\new";       // \n is not a line break here

All three quotes work: r"...", r'...', and r"""...""", which spans lines. A raw string cannot contain its own closing quote, so a " goes in r'...' or r"""...""". In an ordinary string, a backslash before a character without an escape of its own stands for that character, so "\d" is just d, which is why patterns need raw strings.

f-strings#

An f before the opening quote puts values into the text: each {expression} is replaced by its value, written as string(value) writes it.

mars
func m{
    name = "Ada";
    age = 36;
    out(f"{name} is {age}, and {age + 1} next year");   // Ada is 36, and 37 next year
    out(f"{name.upper()} has {name.len()} letters");     // ADA has 3 letters
    out(f"{{braces}} are written twice");               // {braces} are written twice
}

Any expression works between the braces: arithmetic, calls, methods, members, and indexes. The expressions run from left to right, once each. {{ and }} are braces themselves, and escapes such as \t work in the text as in any string. All three quotes work: f"...", f'...', and f"""...""", which spans lines.

A string inside the braces needs the other kind of quote, since the first " would end the f-string: f"{scores.get('ada')}", or \" as in f"{name.replace(\"-\", \" \")}". In f"""...""", plain " works.

There is no format after a : as in other languages; format a number with std.format inside the braces: f"Total: {format.fixed(price, 2)}". An f-string cannot also be raw.

Characters#

A character is one Unicode extended grapheme cluster: a visible character can contain multiple Unicode code points. Chinese 中, Japanese あ, Arabic عَ, é, 🇨🇳, 👍🏽, and 👨‍👩‍👧‍👦 each count as one.

Length, iteration, reversal, and slicing all use this unit. They do not normalize the text. Segmentation uses the Rust unicode-segmentation crate (Unicode extended grapheme cluster rules); its Unicode version follows the crate version.

Methods#

Call Result
text[i] / text.iget(i) The character at position i, counting from 0
text.len() Number of characters
text.reverse() New string with characters in reverse order
text.slice(start,end) New string selecting positions [start,end)
text.lenslice(start,length) New string selecting length characters from start
text.split() / text.split(sep) Array of the words between runs of whitespace / of the parts between each sep; an empty sep raises RangeError
text.lines() Array of lines, splitting at \n or \r\n
text.strip() / lstrip() / rstrip() Without whitespace at both ends / the start / the end
text.strip(chars) / lstrip(chars) / rstrip(chars) Without any of the characters in chars at those ends
text.upper() / text.lower() Unicode case conversion ("Straße".upper() is "STRASSE")
text.replace(old,new) Every occurrence of old replaced; an empty old raises RangeError
text.find(part) / text.rfind(part) Position of the first / last occurrence, or -1
text.contains(part) / starts_with(part) / ends_with(part) Boolean
text.copy() The same immutable string value

Every method returns a new value; the string itself never changes, so text[0] = "x" is a compile error. Positions, searches, and splits work in whole characters: "café".find("e") is -1 when the é is e plus a combining accent, because that accent belongs to the same character. An index outside the string raises OutOfBoundsError, and one that is not a whole number TypeError. The std.strings functions do the same as these methods, for code that prefers strings.split(text, ",").

Counting from the end#

Both slicing methods accept an optional third boolean argument. It defaults to false. Write reverse=true or pass true positionally. A boolean expression is also accepted. Named reverse= must follow exactly two positional arguments and must be last. No other named arguments are supported.

With reverse=true, position zero is the last character. Selection proceeds toward the beginning, but the result keeps the original text order.

Expression Result
"ABCDE".lenslice(0,3) "ABC"
"ABCDE".lenslice(0,3,reverse=true) "CDE"
"ABCDE".lenslice(1,3,reverse=true) "BCD"
"ABCDE".slice(1,4,reverse=true) "BCD"
"ABCDE".lenslice(0,3,reverse=true).reverse() "EDC"

For slice, the end remains exclusive in the chosen direction. For a string of length N, reverse slice(start,end) selects the forward interval [N-end,N-start). Reverse lenslice(start,length) selects [N-start-length,N-start).

Bounds and errors#

Slices never silently truncate. For example, "ABC".lenslice(0,4) raises OutOfBoundsError, and "ABC".lenslice(3,0,reverse=true) returns "".

Iteration#

mars
func m{
    for (ch, "中é👨‍👩‍👧‍👦"){
        out(ch);
        out(ch.len()); // Each prints 1.
    }
}

Only the string APIs listed here are supported language contracts. Calling any other method on a string raises TypeError.