Sage-Code Laboratory
index<--

Eve Collections

Collections are composite types represents groups of elements of the same type. In Eve collections are actually generics. Usually we declare collections using "gradual typing" technique but it is possible to use an explicit declaration.

Page bookmarks:



Ordinal

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.

Pattern:

# 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;

List

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.

Syntax:

#declare two lists
** empty list
  new list1 : ()Type;  

** type inference
  new list2 := (value1, value2, ...); 

** using constructor
  new list3 := List(value1,value2,...)

Notes:

Examples:


# 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

Examples:

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;

List operations

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:

List builder

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

List deconstruct

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.

Append to list

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;

Alter a list

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;
The list is prepared to support queue and stack. It has all the operations required. We can implement a class wrapper with the suggestive method names.

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: {+=, -=} {:=, <-, ->}.

Queue (FIFO)

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;

Stack

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;

List methods

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

join() method

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;

Collection iteration

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.

Example:


#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;

Example:

# 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;

Array

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.

Example:

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;

Inference:

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

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;

Expected Output

numbers = [0,1,2,3,4,5,6,7,8,9]

Notes:

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;

Array builder

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.

Syntax:

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;

Example:


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;

output:

 1 4 7 10
 ----------
 1 2  3  4
 2 4  6  8
 3 6  9 12
 4 8 12 16

Array deconstruct

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.

Array slicing

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.

Named slices

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;

Limited slices

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;

Anonymous slices

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;

Deconstruct with rest

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;

Matrix

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.

eve-array

2D Array

Example:

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;

Expected output:

 1  2  3  4
 5  6  7  8
 9 10 11 12
13 14 15 16

Memory model

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

Row Major Order

Example

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;

Output:

 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.

Example:

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;

Output


  11  12  13
  21  22  23
  31  32  33

  11  21  31  12  22  32  13  23  33

DataSet

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.

Empty DataSet

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;

DataSet restrictions

Mutability

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.

Example:


#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;

Union

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;

Intersection

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

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;

Using DataSet

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;

DataSet Builder

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;

Difference

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;

Intersection

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;

HashMap

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.

Syntax:


class Dic = {}(String,Object) <: HashMap;
driver test:
# Null map
  set m :Dic; -- declare empty dataset
process
  expect type(m) == HashMap;
  expect m == {};
return;

Example:


# 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;

Using HashMap

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.

Map Initialization

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 Elements

** 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;

Output:

{"Rob":"dog","Kiwi":"bird","Toto":"parot"}

Modify Elements

#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;

Output:

{}
{"dogs":1,"birds":5};

Iteration of Map items

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:

Example:


# 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;

Output:

('a':1)
('b':2)
('c':3)

Generate Elements

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.

Example:


# 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;

Output:

('a1':2)
('b1':2)
('c1':2)
('a2':4)
('b2':4)
('c2':4)
('a3':8)
('b3':8)
('c3':8)

Objects

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.

Pattern:

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;

Example:

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;

Empty object:

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.

String

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 declaration

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.

Syntax:


process
  ** 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:
  ** 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:

String comparison

Example:

# 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;

Null strings

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;

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 recommand: Dejavu Sans Mono.

See also: wikipedia ucsunicode 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 unicode_text:
  new us = "I can write Greek: {α,β,γ,δ}";
  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 necesary to use &code; convention. You can copy paste the real Unicode Symbol from character map.

Tips & Trics

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;

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 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;

String: concatenation

Strings can be concatenated using operator "+" and modifier "+=" and some other ways:

Example:

** 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;

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:
  ** 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 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:
  new str   := "Eve is a cool language. True?";
  new words := str.split(" ");
  print words; 
return;
("Eve","is","a", "cool", "language.","True?");

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.

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.

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.

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

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".

Regex operators

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.

External Reference

You can learn and build regular expressions on this website: https://regexr.com/


Read next: Control Flow