Ordinal is an generic class representing a set of unique ordered symbols. Each symbol represents an integer value starting from N to capacity C. Element values start with 1 or a specific number and grow with one for each element.
# Define a Ordinal subtype
driver test_ordinal;
class TypeName = {name1:0, name2, name3} <: Ordinal;
process
new a := TypeName.name1; -- a=2
new b := TypeName.name2; -- b=3
new c := TypeName.name3; -- c=4
return;
Note: When an element name start with capital letter there is no need to use qualifiers for the individual values. By convention these values are known in the scope where the ordinal is visible, without a qualifier.
## Declare public elements in enumeration
driver constant_ordinals;
class TypeName = {Name1:10, Name2} <: Ordinal;
process
new a := Name1; -- a = 10
new b := Name2; -- b = 11
** verify values
expect a == 10;
expect b == 11;
return;
A list is a sequence of elements having a dynamic capacity. Elements are stored in the order you have create them. The order can change if you sort the list or add/remove elements. However you can't insert elements in the middle.
#declare two lists
** empty list
new list1 : ()Type;
** type inference
new list2 := (value1, value2, ...);
** using constructor
new list3 := List(value1,value2,...)
# Null list declaration (no element type)
new e_list := ();
new x_list :List;
# explicit declarations
new n_list = (1,2,3) :()Integer;
new c_list = ('a','b','b') :()Symbol;
new s_list = ("a","b","c") :()String;
new o_list = ({a:1},{b:2},{c:3}) :()Object;
list literals
Literals can be used for initialization of variables:
# initialized list with type inference
new c_list := ('a', 'b', 'c');
new n_list := (1, 2, 3);
Literals can be used in assign expressions:
# Defer initialization
driver begin_initial:
** define empty list
set c_list :List;
process
** initialize the list with integers
let c_list := (1,2,3);
return;
There are many things you can do with a list. You can add/remove elements at both ends. You can deconstruct a list and you can concatenate lists together. Most important features are implemented as built-in functions or special operators:
You can populate a list from a range using a function or expression. List builder are similar to Array builders. Also you can append in bulk new elements into an existing list.
#list builder demo
driver list_builder:
process:
** initialize list from range
new a := (a | a in ('A'..'F'));
print a;
** initialize list from another list
new b := (x^2 | x in (1, 2, 3, 4));
print b;
return;
'A' 'B' 'C' 'D' 'E' 'F' 1 4 9 16
The deconstruct is basically the inverse of a builder. You create individual variables from a list. This is useful to clone or borrow specific parts of a list.
#list deconstruct demo
driver list_deconstruct:
set a : List;
process:
** initialize list from Range
let a := (1..10);
** create new variables using clone deconstruct
new x, y, _, *rest, _ :: a;
** check new variables
expect x == 1;
expect y == 2;
** anonymous variable vanish the value
expect _ == Null;
** check the rest
print rest;
print list;
return;
(4,5,6,7,8,9) (1,2,3,4,5,6,7,8,9,10)
Note: variable "_" is special. It can consume one single element and is always Null. It can be re-declared several times in the same block. The value received is ignored.
List is using operator +> or <+ to grow. This operator represent insert or append. You can insert an element or many elements into a list at the beginning or you can add element or list of elements at the end but you can not insert elements in the middle of the list./p>
#list demo concatenation
driver list_concat:
process:
** initialized list
new a := ('a','b');
new b := ('1','3');
** concatenate two lists
new c := a <+ b;
expect c == ('a','b','1','3');
** concatenate two lists
new d := b +> b;
expect c == ('1','3','a','b');
return;
You can use "let" with operators { += -= }, to append or remove elements from beginning of list. You can remove any element from a list using "-=". All elements having specified value will be remove. To remove one single element you must use operator <-, or ->
#altering a list
driver list_alter:
set lst :()Symbol;
process:
** initialized list
let lst := ('a','b','c','b');
** create a new element at the begining of list
add lst <+ 'z';
expect lst == ('a','b','c','b','z');
** remove all "b" elements from list
del 'b' in lst;
expect lst == ('a','c','z');
** append element to end of list
add lst <+ 'x';
expect a == ('a','c','z','x');
return;
Note: There is no protection about using a List as stack or queue. Is developer responsibility to append and remove correctly using one of the operators: {+=, -=} {:=, <-, ->}.
FIFO = First in First Out
For queue, two operations are possible
#using a list as a queue
driver list_remove:
** initialized queue
set queue := ()Symbol;
process:
** enqueue one element (at beginning)
add queue +> "x";
expect queue == ('x');
** enqueue several elements
add queue +> ('z','y')
expect queue == ('z', 'y', 'x')
** create new element and dequeue (from end)
pop queue -> e;
expect queue == ('z', 'y');
expect e == 'x';
return;
LIFO = Last in Last Out
For stack, two operations are possible
#remove elements from list
driver list_remove:
** initialized stack
set stack := ('a','b','c');
process:
** append element at the end
add stack <+ 'x';
expect stack == ('a','b','c','x');
** extract element from the end
pop stack -> x;
expect stack == ('a','b','c');
expect x == 'x';
return;
A List is a class, so it has all required methods and properties to be used in algorithms and expressions. We do not yet forsee all methods bu maybe these would be useful. Here is a minimalist suggestion for some methods.
List.empty; -- true/false
List.full; -- true/false
List.capacity; -- list with capacity
The join method receive a list and convert elements into a string separated be specified character.
#call List.join() demo
function list_join:
new str := (1,2,3).join(',');
expect str == "1,2,3";
return;
split() method The split method is actually a method of a String, that create a List of strings by splitting a long string into parts. Usually you split by comma, but any symbol can be used. The separator is removed and will not be part of the resulting list.
** string split demo
process list_split:
new list: ()Integer; -- empty List
** initialize new reference for "lst"
let lst := "1,2,3".split(',');
expect lst == (1,2,3);
return;
We can use a for loop that is executed for each element belonging to a collection. All collections are iterable. To be iterable a class need to implement an iterable interface.
#iteration process
driver test_iteration:
set collection := ('a','b','c');
process:
cycle:
new element: Symbol;
for element in collection loop
write element;
write ',' if element is not collection.last;
repeat;
print;
return;
# list iteration
driver list_iteration:
set my_list: ()Symbol; -- this list is Null
process:
let my_list := ('a','b','c','d','e');
cycle:
new e: Symbol;
for e in my_list loop
write e;
if e == 'd' then
stop; -- early termination;
else
write(',');
done;
repeat;
print; -- a,b,c,d
return;
An array is a collection of elements ordered by one or more index numbers. Eve count elements in range (1..n), where n is the number of elements. Forst element is array[1]. We count starting from 1 while other languages start form 0.
Eve define Arrays using notation: [c]Type, where [c] is the capacity and Type is the type of elements. Arrays are automatically initialized. However, all elements will have default zero values or Null.
** diverse array variables
driver test_array:
set n = 2, m = 3 :Natural;
set x = y = z = 2 :Natural;
** define Array sub-types
class AType = []Integer <: Array;
** define a global array with user define type
global
set array = [1,2,3,4,5,6,7,8,9,10]: AType;
process
** define local arrays of various capacities
new array_name1: [10]Real; -- Array of 10 large numbers
new array_name3: [2^8]Symbol; -- Array of 255 Symbols
new array_name4: [n,m]Type; -- 2d matrix, capacity: 6
new array_name4: [x,y,z]Type; -- 3d matrix, capacity: 8
return;
In next example we declare an array and use index "i" to access each element of the array by position. Later we will learn smarter ways to initialize an arrays and access its elements by using a visitor pattern.
Eve Array
Lets implement previous array: numbers[] and print its elements using a cycle. For initialization we use type inference with symbol ":=". Array literal contains all the elements.
# define single dimension array
process demo_numbers:
new numbers := [0,1,2,3,4,5,6,7,8,9];
** access .numbers elements one by one
write "numbers = [";
cycle: for i in (1..10) loop
write numbers[i];
write ',' if i < 10;
repeat;
write "]";
print; -- flush the buffer
return;
numbers = [0,1,2,3,4,5,6,7,8,9]
initialize elements
Initial value for elements can be set during declaration or later:
# How to initialize all elements
process
new zum: [10]Integer;
** explicit initialization using single value
let zum[*] := 0;
expect zoom = [0,0,0,0,0,0,0,0,0,0]
** modify two special elements:
let zum[0] := 1;
let zum[#] := 10;
print zum; -- expect [1,0,0,0,0,0,0,0,0,10]
return;
Deferred initialization: We can define an empty array and initialize its elements later. Empty arrays have capacity zero until array is initialized.
** array without capacity
process
new vec :[?]Symbol;
new nec :[?]Integer;
** arrays are empty
print vec == []; -- True
print nec == []; -- True
** extend symbol array;
vec.extend(10);
print vec; -- expect ['','','','','','','','','','']
** extend numeric array;
nec.extend(10);
print nec; -- expect [0,0,0,0,0,0,0,0,0,0];
return;
The array builder is a syntax feature that enable you to populate an array with data in a single statement. An array builder can be based on one or more ranges. Before you can populate the array it must be declared.
You can declare and initialize an array in the global area using the array builder. Array total capacity must match the number of elements created by the range. A function can be used to compute the values of all elements.
** declare an Array initialized using range
set array = [min..max:ratio]: []ElementType;
** declare a new Array using a map function with range
set array = [ f(x) | x in (min..max:ratio)]: []ElementType;
** declare a new 2d array that is called matrix, row by row
set matrix = [ f(x) | x in (min..max:ration)]: [n,m]ElementType;
driver test_matrix:
set array :[4]Integer;
set matrix :[4,4]Integer;
process:
** init a vector Array using a range
let array := [ x | x in (1..10:3)];
expect array == [1,4,7,10];
print array;
print ("----------")
** init a matrix Array usinc cartesian pruduct between two ranges
let matrix := [(1..4) * (1..4)];
print matrix;
return;
1 4 7 10 ---------- 1 2 3 4 2 4 6 8 3 6 9 12 4 8 12 16
The deconstruct is basically the inverse of a builder. You create individual variables from an Array. This is useful for copy or borrow specific parts of an Array.
#array deconstruct demo
driver array_deconstruct:
set a :[]Integer;
process:
** initialize array from Range
let a := [1..10];
** create new variables using deconstruct
new x, y, *, z := a;
** check new variables
expect x == 1;
expect y == 2;
expect z == 10;
return;
Note: The spreading statement must specify what is happening with all the elements. Using * you can ignore several elements and reach the last element z. You can have only one rest operator "*" in a statement.
An array can be sliced using slice notation [n:m]. A slice is also an array and can be sliced. Slicing an array create a view over existing elements of an array. Before you can slice an array, it must be already initialized.
In next example we use slicing notation to create two named slices. One is a dynamic slice, the other is a static slice. When you modify the dynamic slice, also the base array is modified. If you modify the static slice, the base array is unaffected.
driver test_slice:
set slice :[]Symbol;
process:
new base: [20]Symbol;
let base := "this is a test array".split();
** create a dynamic slice
let slice := base[11:14];
** check slice content
expect slice[1] == 't';
expect slice[#] == 't';
expect slice == ['t','e','s','t']
** join back an array to a string
print slice.join(); -- "test"
** making a static slice;
new copy :: base[11:14];
** alter two characters in slice
let slice[1] := 'b';
let slice[2] := 'u';
** the copy is not modified
expect copy.join(); == "test"
** base array is now modified;
print base.join(); -- "this is a bust array"
return;
A limited slice is a slice that starts at a particular position and goes from that position several elements. You can calculate the last element in the slice using an expression that may depend on the last element "#".
driver test_slice:
set slice, sreve :[?]Symbol;
process:
** generate Array elements from a Range
new base := [1..10];
new x := 5;
** limited slices
let slice := base[x: x+3];
let sreve := base[5: #-3].reverse();
** check the results
expect slice == [5,6,7];
expect sreve == [7,6,5];
return;
You can use anonymous slice to modify an Array or to print a section of the array. You do not have to assign a variable to the slice. This is a shortcut, and is very useful for handling Array elements in bulk.
driver ano_slicing:
process:
new base:[10]Integer;
** using bulk assignment
let base[1:5][*] := 0;
let base[6:#][*] := 2;
** create a dynamic slice
expect base == [1,1,1,1,1,2,2,2,2,2];
** print 2 elements from the middle
print base[5:6]; -- [1,2]
return;
You can use deconstruct with rest to create a slice in the same time extract several values. By using operator ":=" you will create a slice but using "::" you will create a new Array.
# deconstruct with slice
process:
** generate Array elements from a Range
new base := [1..8];
** create a rest slice
new x,y,z, *rest := base;
** check the rest slice
expect rest == [4,5,6,7,8];
expect (x,y,z) == (1,2,3);
return;
# deconstruct with clone
process:
** generate Array elements from a Range
new base := [1..8];
** create a clone from reversed array
new a,b,c, *clone :: base.reverse();
** check the clone elements
expect clone == [5,4,3,2,1];
expect (a,b,c) == (8,7,6);
return;
A matrix is an array with 2 or more dimensions. In next diagram we have a matrix with 4 rows and 4 columns. Total 16 elements. Observe, matrix index start from [1,1] as in Mathematics.
2D Array
In next example we demonstrate a matrix literal. Eve has a default output for printing a matrix using columns. You can print a matrix on rows or columns using "print" statement.
# define a subtype of Array
driver test_matrix;
class Matrix = [4,4]Integer <: Array;
process
new mat: Matrix; -- define matrix variable
** modify matrix using ":=" operator
let mat :=[
[1, 2, 3, 4]
,[5, 6, 7, 8]
,[9, 10,11,12]
,[13,14,15,16]
];
expect mat[1,1]; -- 1 = first element
expect mat[2,1]; -- 5 = first element in second row
expect mat[3,#]; -- 12 = last element in third row
expect mat[#,1]; -- 13 = first element last row
expect mat[#,#]; -- 16 = last element
** nice output using array print method
mat.print;
return;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
In memory elements are organized linear, row by row. We say that Eve matrix are in row major order like C, in contrast to Fortran where column major order is used. It is important to use the right major order when you travers a 2d array.
Row Major Order
Memory is linear, so we fake a matrix. We can access the entire matrix with a single loop or nested loops. Next program can initialize a matrix in a flat cycle. The order of elements is automaticly established.
** initialize matrix elements
process
new mat: [3,3]Integer <: Array;
** initialize matrix elements
cycle:
new i := 1;
new x := mat.length;
while (i < x) loop
let mat[i] := i;
let i += 1;
repeat;
print mat; -- nice output
return;
1 2 3 4 5 6 7 8 9
Note: The capacity of an Array is part of data type. An array of fixed size. When you define an array with variable capacity using [?], you do not reserve any memory for its elements. This kind of Array is dynamic. It can be extended.
Nested loops can be used to access every element in the matrix. When you do this kind of loops, pay atention to order of indexes. In Eve you must use first index for outer loop and second index for inner loop. For large matrix this can improve performance. Dure to better cash management.
driver matrix_demo:
set matrx :[?, ?]Integer;
process
** give value to all elements
matrix.extend(3,3);
cycle
new i, j :Integer;
for i in (1..3) loop
for j in (1..3) loop
matrx(i,j)=i*10+j
repeat;
repeat;
** output matrix elements
print "#(3i4)", matrx -- 3 columns
print
print "#(9i4)", matrx -- 9 columns
return;
11 12 13
21 22 23
31 32 33
11 21 31 12 22 32 13 23 33
In mathematics a data set is an abstract data structure that can store certain values, in random order, but has no repeated values. Sets are fast collections for search, add update with new values. However, you have to know the element value in order to search for it.
An empty DataSet is represented like this: {} and can be assigned to a set if you wish to remove all elements of the set. A set that is not initialized is Null.
global
** Define a Null DataSet with unknow element type
set n_name :DataSet;
** Define DataSet with known element type
set t_name : {}Integer;
A set can be modified during run-time using operators. When you modify a set, keep in mind that set's are automaticly sorted by a hash key. You never know what the position of the new element is going to be. Also if you add or remove an element the position of several other elements will be shifted.
Note: You can rever to all elements using "*" and modify them using modifiers.
#demo for using a set
driver set_demo:
** initialized DataSet of integers
set my_set = {0,2,3} :DataSet;
process
** append element 1
add 1 in my_set;
expect 1 in my_set;
** remove element 2
del 2 in my_set;
expect 2 not in my_set;
** remove all elements
let my_set := {};
expect my_set == {};
return;
Use union operator to combine two sets.
# combine two sets using "||" = (two vertical bars)
driver set_union_demo:
** element set
set first = {0,1,2,3} :DataSet;
set second: DataSet; -- Null DataSet
process
** asign elements using union
let second := first || {2, 4, 8, 10};
** common value 2 is not duplicate
expect second == {0,1,2,3,4,8,10};
return;
Intersect operator & find common elements:
# Create intersection using &&
driver set_intersection_demo:
set test: DataSet; -- Null set
process
let test := {1,2,3,4} && {3,4,5};
expect test == {3,4};
return;
Difference operator "-", and modifier "-=", extract elements. With this trick you can remove one single element from a DataSet instead of using "del". Addition "+" operator and "+= modifier will not add elements. You can use && and &= that will perform union. This is to prevent the missconception that you can add elements to a set. Remember, elements in a set must be unique.
# Extraact severa lelements;
process
new test := {1,2,3,4};
new rest := test - {3,4,5};
** check remaining elements
expect (1,2) in rest;
expect (3,4) not in rest;
** use modifier -=
let test -= {1,3}
expect test == {2,4};
** use modifier &= to add elements
let test &= {1,3,5};
expect test == {1,2,2,4,5}; -- we keep elements sorted
return;
A DataSet is very useful for it's property to hold unique elements sorted. This enable deduplication of elements in a list by converting the list to a DataSet. Operator && is overloaded to accept list arguments but will return a DataSet.
# convert a list to a dataset
process:
new list := (1,2,1,2,3,1,2,3,4);
** using operator && (union)
new dset := {} && list;
expect dset == {1,2,3,4};
return;
You can create elements of a DataSet from a range using builder.
# demonstrate how to create a set from a range
process:
new dset := {1 << x | x in (2..6)};
** check the new set
expect dset == {4,8,16,32,64};
return;
You can extract elements out of a DataSet using a list, or another set.
# Extract several elements;
process:
new test := {1,2,3,4};
new rest := test - (3,4);
** check remaining elements
expect (1,2) in rest;
expect (3,4) not in rest;
** use modifier -=
let test -= (1,3)
expect test == {2,4};
return;
You can intersect 2 lists but the result will be a DataSet.
# Create intersection using &&
driver set_intersection_demo:
set test: DataSet; -- Null set
process:
let test := (1,2,3,4) && (2,3,4,5);
expect test == {2,3,4};
return;
It is called Hash due to similarity with letter H, that can be used as analog representation for a structure having two connecte columns. Collections are ordered by unique keys. This helps to find elements faster using a binary search algorithm.
class Dic = {}(String,Object) <: HashMap;
driver test:
# Null map
set m :Dic; -- declare empty dataset
process
expect type(m) == HashMap;
expect m == {};
return;
# how to initialize a table
driver table_init:
set h: HashMap;
process
** initialize table
let h := {'a':1, 'b':2};
** accessing elements
expect h['a'] == 1;
expect h['b'] == 2;
** create new elements
new h('c') := 3;
new h('d') := 4;
** optput all elements
print h -- {'a':1, 'b':2, 'c':3, 'd':4};
return;
Maps are sorted in memory by key for faster search. It is more difficult to search by value because usually is not unique and not sorted. To search by value one must create a loop and verify every element. This is called full scan and is slow so you should never use this process.
Initialize a collection later than declaration.
# local map initialization
driver map_init:
set my_map :HashMap; -- uninitialized collection
process:
** initialize my_map with values
let my_map := {1:'a',2:'b',3:'c'};
** check if a specific key is present in a map collection
expect 3 in my_map; -- will pass
** check if a sub-set of keys is present in a map collection
expect {1,3} in my_map;
return;
** create new elements
process map_append:
set animals: {}(String, String): ; -- empty collection
process:
** append several elements
let animals += {"Rob":"dog", "Kiwi":"bird"};
** create single element
new animals["Toto"] := "parot"
print animals;
return;
{"Rob":"dog","Kiwi":"bird","Toto":"parot"}
#update a map
driver map_update:
set animals : {}(String, String);
process:
** print the new collection
print animals;
** modify the collection
new animals["dogs"] := 1; -- new element
new animals["birds"] := 2; -- new element
let animals["birds"] += 3; -- alter element
** print the new collection
print animals;
return;
{} {"dogs":1,"birds":5};
Collections have common methods that enable traversal using for loop.
{List, Map, Set}
built-in:
set iteration Map and Set are iterable. We can visit all elements using for loop:
# HashMap iteration
driver map_iteration:
process:
new my_map := {'a':1,'b':2,'c':3};
cycle:
new key: Symbol;
new value: Integer;
for (key: value) in my_map loop
** print pairs (key:value)
print "(#s:#n)" ? (key, value);
repeat;
return;
('a':1) ('b':2) ('c':3)
You can generate elements for a HashMap using a collection builder. This is similar to a List builder, except you generate pair of values instead of a single value. You can use a function to calculate the value, or you can search values from another collection.
# HashMap iteration
driver map_generator:
set my_map = { ((x + y): 1 << y) | x in ('a'..'c') and y in (1..3)}: HashMap;
process:
cycle:
new key :String;
new value:Integer;
for (key: value) in my_map loop
** print pairs (key:value)
print "(#s:#n)" ? (key, value);
repeat;
return;
('a1':2) ('b1':2) ('c1':2) ('a2':4) ('b2':4) ('c2':4) ('a3':8) ('b3':8) ('c3':8)
Objects are collections of attributes of different types. The most simple object is an instance of type Object. The Object is the fundamental class in EVE, that is why it starts with uppercase.
Initialize an object by using an object literal with initialization values:
# define global object instances
driver test:
** empty object
set object_name = {attribute:value, ...}: Object;
process
** accessing attributes
print object_name.attribute;
...
return;
Define a local object that has two attributes:
# define global object instances
process
** simple object with two fields
new point = {x:1.5, x:1.5}: Object;
** accessing attributes
print point.x;
print point.y;
return;
Define a local Null object == {}
# define global object instances
process
** simple object with two fields
new object = {} :Object;
** create new attributes using &= (union modifier)
let object &= (x:1);
let object &= (y:2);
...
print object.x; -- 1
print object.y; -- 2
return;
Note: Objects and HashMap are very similar. The difference is, object attributes can have diverse types. In a HashMap all elements have the same type. Also in HashMap the key can be numeric, symbol or string. Object attributes are always strings.
In Eve, strings are collections of UTF8 code points. The capacity of string is virtual unlimited. A string can grow automaticly. Strings are immutable.
String can be initialized with a constant literals using Real quotes or triple quotes. You can scan a string symbol by symbol using "for" cycle. Each element in a string is on type Symbol.
process
** define empty string with default capacity
new str1 :String;
new str2 = "": String; -- equivalent
...
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.
# working with strings
driver test_string:
** shared mutable strings
set str := "First value";
set ref := "First value";
process
** 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;
Note:
# compare strings
driver compare_strings:
expect ('a' == 'a'); -- true (same value)
expect ("this" == "this"); -- true (same value)
expect (" this" != 'this '); -- trailing spaces are significant
return;
We can test if a string is Null using "is Null" expression. In Eve "" is empty string but Null string is not "" that is empty, A Null string is declared but uninitialized. That is equivalent with Null object.
# null string & symbol demo
driver null_string:
set str :String; -- Null string
set emp := ""; -- empty string
set btr :Symbol; -- Null symbol
process
** null string
expect str is Null;
expect emp is not Null;
** empty string
expect emp is not Null;
expect emp == "";
expect emp.length() == 0;
** symbol follow the same rule
expect btr is Null;
let btr == '';
expect btr is not Null;
return;
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 recommand: 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.
# text type has unicode support
process unicode_text:
new us = "I can write Greek: {α,β,γ,δ}";
print us;
return;
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 necesary to use &code; convention. You can copy paste the real Unicode Symbol from character map.
Sytrings 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 spages to one space.
# create list from string
process
** ASCII string buffers
new str1 = "test":
new str2 = () <+ str1; -- feed the list with Symbols
** check list elements
expect str2[1] == 't';
expect str2 == ('t','e','s','t');
return;
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 Symbol or a String pattern.
# string generators
process:
** 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;
Strings can be concatenated using operator "+" and modifier "+=" and some other ways:
** example of string concatenation
driver string_concat:
process:
** 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"};
** string format operator ? with #n placeholders for numbers
print "these numbers #n, #n, #n are prime" ? (1,3,5);
return;
symbols & numbers
You can concatenate a Symbol (character) with number. There is an implicit coercion that will transform the number into a string before concatenation. However, to concatenate two numbers you must explicitly join the numbers together using a function join() that will produce a string.
# numeric concatenation
process:
** implicit conversion
new s := 'a' + 1;
expect s == "a1"; -- is a string
** explicit conversion
new c := 1 + 'a';
expect c == "1a"; -- is a string
** explicit join list elements with empty Symbol
new test = (1,2,3).join('');
expect test == "123";
return;
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:
** 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;
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:
new str := "Eve is a cool language. True?";
new words := str.split(" ");
print words;
return;
("Eve","is","a", "cool", "language.","True?");
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.
A Text has an different internal representation and can be used to represent HTML or XML and other data format like CSV. Double quotes are preserved without escape sequence. Te example below demonstrate our little trick.
# declaration of a text literal
driver text_literal:
process:
** 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;
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 semicolumn. This is used by the compiler to determine the indentation.
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.
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"). Just a trick you can use to simplify your code sometimes.
Regular expressions are special string templates useful to search inside strings. We implement regular expressions a la carte except the delimiter \" must be escaped. Regular expressions are enclosed in Real quotes like strings: "/regex/g". A regular expression always start with "/" and ends with "/flags".
Eve has support for two operators: "~" and "!~". These are logic operators. Represent "is like" respective "is not like". It require a string for the left operand and a regular expression for the right operand.
# compare regular expression
process:
** search " is " in the string
new found := "this is like" ~ "/\sis\s/";
expect found; -- True
** check if string start with "not"
new not_found := "this is not like" !~ "/^not/";
expect not_found; -- True
return;
Note: Regular expressions are powerful but difficult to master. This is a language and like any other language has complex rules. You can create and test regular expressions using a tool. It is not in our scope to teach you regular expressions.
You can learn and build regular expressions on this website: https://regexr.com/
Read next: Control Flow