Eve Strings

Strings are immutable collections of symbols. Text is the mutable type for building large documents. Regular expressions search inside strings. This page continues Collections.

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.

FormInsertsExample
{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: "&alpha;". 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: i integer, f fixed real, e real with exponent, x hexadecimal, b binary. The width W follows the code and .D sets 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: 3i4 is three columns of width 4;
  • boolean code: tf True/False (default), yn Yes/No, 01 1/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