Eve Strings
String
In Eve, strings are collections of UTF8 code points. The capacity of string is virtual unlimited. A string can grow automatically. Strings are immutable. If you need a mutable string, use a collection of Runes or a Text (see below).
String declaration
String can be initialized with a constant literals using double quotes or triple quotes. You can scan a string symbol by symbol using a "for" loop. Each element in a string is on type Rune.
Syntax:
process main is
** define empty string with default capacity
new str1 :String;
new str2 = "": String; ** equivalent
...
String mutability
In Eve Strings are immutable. If you use ":=" the string object is replaced. You can concatenate strings and you can use string interpolation but every time you do, a new string is created.
Example:
# working with strings
driver test_string is
** shared immutable strings
set str := "First value";
set ref := "First value";
process main is
** check initial value
expect str == ref; ** same value
expect str eq ref; ** same value and type
expect str is not ref; ** different locations
** operator ":=" works by reference
let ref := str; ** reset ref
expect str == ref; ** same value
expect str is ref; ** same reference (old reference is garbage)
** if we recreate str, reference is reset
let str := "First value"; ** new string
expect str == ref; ** same value
expect str is not ref; ** different location
** if we modify "str" then "ref" will remain the same
** because the strings are immutable in Eve
let str += ':'; ** create a new string (concatenate ":")
expect str == "First value:"; ** modified
expect ref == "First value"; ** not modified
expect str is not ref; ** the reference was broken
return;
end test_string;
Note:
- You can create garbage in Eve if you lose references;
- You can create a collection of Runes to split() a string;
- You can join() elements of a collection to create a string;
String comparison
- Two strings are compared using relation operators: { ==, <>, <, >, >=, <= };
- Single quoted symbols can be compared to double quoted strings;
- The length of the string is the number of symbols:
- Empty string "" == '' has length 0 and is empty but not
null;
Example:
# compare strings
driver compare_strings is
expect ('a' == 'a'); ** true (same value)
expect ("this" == "this"); ** true (same value)
expect (" this" <> 'this '); ** trailing spaces are significant
return;
Null strings
A string declared without a value is the empty string "", its zero value: the object exists. Only an optional string, String?, can be null, which means that no string exists. We test it with is Null or == null.
# null string & symbol demo
driver null_string is
process main is
new str: String?; ** optional string: null
new emp: String; ** zero value: empty string
new btr: Rune?; ** optional rune: null
** null string
expect str is Null;
expect str == null;
** empty string
expect emp is not Null;
expect emp == "";
expect emp.length() == 0;
** a rune follows the same rule
expect btr == null;
let btr := ''; ** nil, the empty rune
expect btr is not Null;
return;
end null_string;
UCS Font
The string is a collection of code points using symbols from universal character set (UCS). To handle code source that contain Unicode symbols you must select a proper programming font. We recommend: Dejavu Sans Mono.
See also: wikipedia ucs, unicode characters
HTML standard is using special escape codes for each symbol. Is not the scope of this document to explain this codification. In example below we show you some Unicode symbols. Using convention &code; we can escape special UNICODE characters in double quoted string. Eve is parsing a double quoted string and replace these characters with the correct code point.
Example:
# text type has unicode support
process main is
new us = "I can write Greek: \{α,β,γ,δ\}"; ** a literal brace is escaped
print us;
return;
Output:
I can write Greek: {α,β,γ,δ}.
To edit Eve source code containing Unicode literals one must use a specific font and UTF-8 source files. The preferred font for Eve programming is "DejaVu Sans Mono". In this case it is not necessary to use &code; convention. You can copy paste the real Unicode Rune from character map. Not all Unicode characters have an HTML code, so Eve supports both forms: the &code; convention and the placeholder {U+H...} with 1 to 6 hexadecimal digits, for example "{U+03B1}" is the letter alpha.
Tips & Tricks
Strings are immutable. You can handle more efficient strings using list. For this you can create a list from string. Conversion is implicit made using "feed" operator "<+". This operator is populating the list with characters from string, preserving the spaces between words, reduce many spaces to one space.
# create list from string
process main is
** ASCII string buffers
new str1 = "test";
new str2 = () <+ str1; ** feed the list with Runes
** check list elements
expect str2[1] == 't';
expect str2 == ('t','e','s','t');
return;
Using strings:
In next examples we use type inference to create strings. The length of the string is the number of symbols. You can use operator "*" to initialize a long string with the same Rune or a String pattern.
# string generators
process main is
** examples of empty strings
new short_string := "short string";** most common form of literal
new long_line := '-' * 80; ** a long line of 80 dashes
new dash_dot := "-·" * 40; ** a long line "-·-·-·";
return;
String: concatenation
Strings can be concatenated using operator "+" and modifier "+=" and some other ways. Concatenating a string with another literal (symbol or number) gives a String:
- regular concatenation: "+"
- concatenation with modifier "+="
- string interpolation: "{name}" and "{name % format}"
- string string pipe "<+":
- collection join() method
Example:
** example of string concatenation
driver string_concat is
process main is
** set string value using different operators
new str := " this " + "string";
expect str == " this string";
** use string interpolation {str} is recognized
print "I have insert {str} here!";
** concatenate string with collection
print "digits = " + (1,2,3).join(",");
** string pipeline
print "" <+ ("this ","is ","a "," string");
** interpolation with a format: i3 is an integer of width 3
new (a, b, c) = (1, 3, 5);
print "these numbers {a % i3},{b % i3},{c % i3} are prime";
return;
end string_concat;
symbols & numbers
Two strings are concatenated with +, which is the same as <+ (append) and +> (put in front): "a" + "b", "a" <+ "b" and "a" +> "b" are all "ab". Between a string and a number, + is a compile-time type error: Eve prefers explicit over implicit. Use <+ to append a number converted to a string, and +> to put it in front. To concatenate two numbers, join them with the method join(), which produces a string.
# numeric concatenation
process main is
** append a number to a string
new s := "a" <+ 1;
expect s == "a1"; ** is a string
** put a number in front of a string
new c := 1 +> "a";
expect c == "1a"; ** is a string
** explicit join list elements with empty Rune
new test = (1,2,3).join('');
expect test == "123";
return;
String: builders
You can convert a range to a list of symbols by using operators: +> and <+. In next example we demonstrate several use-cases useful to create new strings using range notations.
# different ways to convert data into string
process main is
** Range conversion
new str1 := (0..9) +> "";
expect str1 == "0123456789";
** DataSet conversion
new str2 := "" <+ ("A".."F");
expect str2 == "ABCDEF";
** Join a set created with set builder in place
new str2 := {"0x1" + a | a in ("A".."F")}.join(',');
expect str2 == "0x1A,0x1B,0x1C,0x1D,0x1E,0x1F";
return;
String interpolation and format
The braces of a string always open a placeholder: a place where Eve inserts something while it reads the string. A placeholder holds a name or a literal.
| Form | Inserts | Example |
|---|---|---|
{name} | the value of the name, written by its type: a string as it is, a number in digits, a boolean as True or False | "n = {n}" |
{name % format} | the value, with a format for its type (below) | "{pi % f8.2}" |
{literal} | the literal, written by its type: a number, a string, a list, a symbol, a boolean | "{399}", "{"test"}", "{(1, 2, 3)}", "{True}" |
{U+H...} | one Unicode character, by its code point: it is the symbol literal U+03B1 (1 to 6 hexadecimal digits) | "{U+03B1}" is α |
\{ \} | a literal brace | "\{1, 2\}" |
A literal inside the braces is written as it is anywhere else in Eve, and its quotes are not escaped: "name: {"test"}" is the string name: test. The string ends at the first double quote that is outside the braces. The code point {U+0041} is A and {U+1F600} is an emoji: it is the way to write a character that you cannot type, or that has no HTML name. For a character that has a name, the HTML reference &name; is shorter and easier to read: "α". The old escapes \xHH and \u{H...} and the numeric references &#N; and &#xH; do not exist.
A placeholder holds a name, a literal or a simple expression: a variable, a constant, a system variable ({$HOME}), an attribute ({p.x}), or an element or a part selected with brackets ({a[1]}, {a[-1]}, {a[i]}, {m[key]}, {m["key"]}, {a[2..4]}). An index is an integer, a name, a quoted key or a range; the quotes of the key are written as they are. A simple expression has operators and calls: {n + 1}, {s.length()}, {a > 2}, {s + "!"}. A statement or an assignment is not allowed. The sign % always starts the format, so a remainder is written inside parentheses: {((n + 1) % 3)}. A brace that does not open a valid placeholder, "{1, 2}" or "{}", is a compile error: write "\{1, 2\}".
A format can follow the name after the format operator %: {name % format}. Inside a placeholder % has only this meaning (it is not the remainder, because there is no expression). The format depends on the type of the value. The number formats are adapted from Fortran; the alignment is borrowed from Python. A result is never truncated by the width: if the value is wider, the result is wider (only .max of a string cuts).
string : {name % [[fill]align][width][.max]}
number : {name % [[fill]align][flags][repeat]code[width][.digits]}
boolean: {name % [[fill]align][width][code]}
Note: a text literal """…""" and a regex literal (a string that starts with /) are raw: they have no placeholders, so their braces need no escape. The character # is used only in comments.
- align:
<left,>right,^center. The default is left for strings and booleans, right for numbers; - fill: one character written before the align, default space:
0>pads with zeros,*^centers with stars; - number code:
iinteger,ffixed real,ereal with exponent,xhexadecimal,bbinary. The widthWfollows the code and.Dsets the decimals (f8.2) or the minimum digits of an integer (i5.3); - flags:
+always writes the sign,,groups the digits by thousands; - repeat: for a collection, a count before the code puts that many elements on each row:
3i4is three columns of width 4; - boolean code:
tfTrue/False (default),ynYes/No,011/0; - .max on a string cuts it to at most that many symbols.
process main is
new pi := 3.14159;
new n := 42;
new name := "Eve";
new ok := True;
new big := 1234567;
new code := 255;
print "pi = {pi % f8.2}|";
print "n = {n % i5}|{n % 0>i5}|{n % <i5}|";
print "big = {big % ,i9}";
print "hex = {code % 0>x4}";
print "name = {name % *^9}|{name % >6}|{name % .2}|";
print "ok = {ok}, {ok % yn}, {ok % 01}";
print "alpha = {U+03B1} = α";
print "braces = \{{name}\}";
print "literals: {399} {"test"} {(1, 2, 3)} {True}";
return;
pi = 3.14|
n = 42|00042|42 |
big = 1,234,567
hex = 00ff
name = ***Eve***| Eve|Ev|
ok = True, Yes, 1
alpha = α = α
braces = {Eve}
literals: 399 test (1,2,3) True
A placeholder must be written exactly: "{U+03B1}" is a character, but "{U03B1}" is a name that does not exist, and "{}" is an error. The older forms #s, #n, #{a}, \#{a}, \s{a}, \n{a}, \b{a}, the format after a colon and the operator ? do not exist. To write a literal brace use \{ and \}.
String split
You can split a string in list of words. When you do this, you can specify a symbol or a list of symbols all considered separators. Separator is removed and not included in the string.
process main is
new str := "one two three";
new words := str.split(" ");
print words;
return;
("one","two","three")
Text Literals
Strings can contain multiple lines separated with end of line character. Large text literal can be defined on multiple lines. Eve use triple quoted strings """...""" and very special trick to parse long strings.
Text is a different type from String. A String is immutable, while a Text is mutable: it has a different internal representation, an efficient structure that allows modification (for example a rope). A Text can be used to represent HTML or XML and other data format like CSV. Double quotes are preserved without escape sequence. The example below demonstrate our little trick.
# declaration of a text literal
driver text_literal is
process main is
** multiline text literal is possible in Eve
new my_text:="""
"Opportunity is missed by most people
because it is dressed in overalls
and looks like work."
""";
print my_text;
return;
end text_literal;
Output:
"Opportunity is missed by most people because it is dressed in overalls and looks like work."
An equal number of spaces was detected by the compiler and removed. The equal number of spaces is the indentation of last tripple quote, that must be on a new line (alone) follow by semicolon. This is used by the compiler to determine the indentation.
String functions
Full inventory of functions will be design in package specification. We enumerate several function as example that we wish to have, but is not our scope yet to enumerate all possible functions.
- String.split();
- String.length();
- String.find(pattern:Regex);
- String.count(pattern:Regex);
- String.replace(pattern:Regex, cargo:String);
Note. These functions can be used with class qualifier "String" but also with data literals. For example: "test".split() is equivalent to String.split("test"). This is a general rule: a class method can be called with the class name or with an object, it is the same method.
Regular Expressions
Regular expressions are special string templates useful to search inside strings. They are enclosed in double quotes like strings: "/regex/g". A regular expression starts with / and ends with /flags.
Regex literal. Every string literal that starts with / is a regex literal: it keeps its backslashes as they are, so "/\sis\s/" means what a regular expression tool expects. In a regex literal there are no escape sequences and no placeholders: \s{2} is the regular expression "two spaces", and {2} is a count, not a placeholder. The only escape is \", the double quote. A regex literal is still a String: it can be stored in a variable, passed as an argument and concatenated with other strings. The rule is lexical: it says how the literal is read, not what the value is.
- A string that starts with
/, such as a file path or a route, is also read this way. To insert a value in it, concatenate:"/data/" + name + ".csv"; a placeholder would stay as text; - Only the literal that starts with
/keeps its backslashes. Another part of a concatenation is an ordinary string, so its backslash is written twice:"/\s" + word + "\\s/"; - On the right of
=~, a string that starts with/is used as a regular expression, also when it was built by concatenation.
Regex operators
Eve has one match operator, "=~", read "is like". It is a logic operator: it requires a string for the left operand and a regular expression for the right operand. The negation is written with not: not (s =~ "/regex/"). The same operator compares numbers with a tolerance: x =~ b +- 0.01. == is the exact match, =~ the approximate one.
# compare regular expression
process main is
** search " is " in the string
new found := "this is like" =~ "/\sis\s/";
expect found; ** True
** check that the string does not start with "not"
new not_found := not ("this is not like" =~ "/^not/");
expect not_found; ** True
** build a pattern: the first part is a regex literal, the last one an ordinary string
new word := "is";
new pattern := "/\s" + word + "\\s/";
expect "this is like" =~ pattern;
** a path starts with "/" too: concatenate to insert a value
new name := "orders";
new path := "/data/" + name + ".csv";
expect path == "/data/orders.csv";
return;
Note: You can create and test regular expressions using a tool. It is not in our scope to teach you regular expressions.
External Reference
You can learn and build regular expressions on this website: https://regexr.com/
Read next: Collections