Sage-Code Laboratory

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

  new a := TypeName.name1; -- a=2
  new b := TypeName.name2; -- b=3
  new c := TypeName.name3; -- c=4

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;

  new a := Name1; -- a = 10
  new b := Name2; -- b = 11
  ** verify values
  expect a == 10;
  expect b == 11;


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;
  ** initialize the list with integers
  let c_list := (1,2,3);


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
  set array = [1,2,3,4,5,6,7,8,9,10]: AType;

  ** define local arrays of various capacities
  new array_name1: [10]Double;  -- 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


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;
  write "]";
  print; -- flush the buffer

Expected Output

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

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
  new vec :[?]Symbol;
  new nec :[?]Integer;

  ** arrays are empty
  print vec == []; -- True
  print nec == []; -- True

  ** extend symbol array;
  apply vec.extend(10);
  print vec; -- expect ['','','','','','','','','','']
  ** extend numeric array;
  apply nec.extend(10);
  print nec; -- expect [0,0,0,0,0,0,0,0,0,0];


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;

  new mat: Matrix; -- define matrix variable

  ** modify matrix using ":=" operator
  let mat :=[
     [1,  2, 3, 4]
    ,[5,  6, 7, 8]
    ,[9, 10,11,12]

  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
  apply mat.print;

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


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
  new mat: [3,3]Integer <: Array;
  ** initialize matrix elements
    new i   := 1;
    new x   := mat.length;
  while (i < x) loop
    let mat[i] := i;
    let i += 1;
  print mat; -- nice 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.


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;
  ** give value to all elements
    new i, j :Integer;
  for i in (1..3) loop
    for j in (1..3) loop
  ** output matrix elements
  print "#(3i4)", matrx -- 3 columns
  print "#(9i4)", matrx -- 9 columns


  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.

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.

  ** Define a Null DataSet with unknow element type
  set n_name :DataSet;

  ** Define DataSet with known element type
  set t_name : {}Integer;

DataSet restrictions


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: Any modifier "x=" can be used for altering individual elements. You can rever to all elements using "*" and modify them.


#demo for using a set
driver set_demo:

** initialized DataSet of integers
  set my_set = {0,2,3} :DataSet;
  ** append element 1
  let my_set += 1;
  expect 1 in test;

  ** remove element 2
  let my_set -= 2;
  expect 2 not in test;

  ** remove all elements
  let my_set := {};
  expect my_set == {};


Use union operator to combine two sets.

# combine two sets using "|" = (or)
driver set_union_demo:
** element set
  set first = {0,1,2,3} :DataSet;
  set second: DataSet; -- Null DataSet
  ** 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};


Intersect operator & find common elements:

# Create intersection using &
driver set_intersection_demo:
  set test: DataSet; -- Null set
  let test := {1,2,3,4} & {3,4,5};
  expect test == {3,4};


Difference operator "-", and modifier "-=", extract elements.

# Extraact severa lelements;
  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};


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;
  expect type(m) == HashMap;
  expect m is Null;
  expect m == {};


# how to initialize a table
driver table_init:
  set h: HashMap;
  ** 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
  print h -- {'a':1, 'b':2, 'c':3, 'd':4};


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; 

  ** accessing attributes
  print object_name.attribute;


Define a local object that has two attributes:

# define global object instances
  ** simple object with two fields
  new point = {x:1.5, x:1.5}: Object; 

  ** accessing attributes
  print point.x;
  print point.y;

Empty object:

Define a local Null object == {}

# define global object instances
  ** simple object with two fields
  new object = {} :Object; 

  ** create new attributes
  let object += (x:1);
  let object += (y:2);
  print object.x; -- 1
  print object.y; -- 2

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 declaration

String can be initialized with a constant literals using double quotes or triple quotes. You can scan a string symbol by symbol using "for" cycle. Each element in a string is on type Symbol.


  ** define Null string with default capacity
  new str1 :String;  -- default Null 
  new str2 = "": String; -- equivalent to Null

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.


# working with strings
driver test_string:
  ** shared mutable strings
  set str := "First value";
  set ref := "First value";
  ** 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

  ** if we modify "str" then "ref" will appear modified
  let str <+ ':'; --  mutate string "str"
  expect str == "First value:"; --  modified
  expect ref == "First value:"; --  also modified
  expect str is ref; -- the reference is holding

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

  ** ref is pointing to a different location
  let ref  := "New value:"
  print ref;  --  New value: (modified, as expected)
  print str;  --  First value: (unmodified value)


String comparison


# compare strings
driver compare_strings:
  expect ('a' == 'a');         --  true (same value)
  expect ("this" == "this");   --  true (same value)
  expect (" this" != 'this '); --  trailing spaces are significant

Null strings

We can test if a string is Null using "is Null" expression.

# null string demo
driver null_string:
  ** Null strings
  set str :String; 
  set btr := 'ω';   
  ** null string
  expect str is Null;
  expect str == Null;
  expect str == '';
  expect str == "";

  ** initialized string
  expect btr is not Null;
  expect btr == "ω";
  expect btr == 'ω';

  ** somesing unexpected
  expect btr is not 'ω'; -- pass, different 
  expect btr is "ω"; -- fail, different objects

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.


# text type has unicode support
process unicode_text:
  new us = "I can write Greek: \αβγδ\.";
  print us;


I can write Greek: "αβγδ".

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

Tips & Trics

Due to internal representation of Symbol, it can store 4 ASCII characters. This is a side-effect, not by intention. Is just a feature that you can use as a tric to store data more compressed. We can also store two UTF16 symbols, and one UTF32. This is a curiosity of Eve.

# Single quoted string 
  ** short ASCII strings
  new str1 := 'a';
  new str2 := 'abcd';
  new str3 := '0xFF';
  new str4 := 'NULL';

  ** all 4 strings have same type
  expect type(str4) == Symbol;

  ** somesing surprising
  expect str4 == "NULL";
  expect str4 != Null;

Note: Intresting, all hex number (0x00..0xFF) can be stored as Symbol. Notice this is 4 times more that necesary. We know these values can be encoded in a single Byte.


Exception is a module that define several system variables and system objects used to handle errors. In Eve all exceptions are considered errors.

The error is a variable of type Exception.Error that is created when exception is raised. System variable $error contains several members that are fill-in by the Eve program when exception is created.


The Exception module is not yet created. We have the intention to use Eve to create this module. This is a prototype that explain the structure of our future Exception module.

** system exception type
module Exception:

class .Error: {
  code: Integer, 
  message: String, 
  module: String, 
} <: Object;

class .Error = {code: Integer, message: String} => (@self :Error):
  let self.code := code;
  let self.message := message;

class Call = {line:String, method:String} => (@self:Call):
  let self.line   := line;
  let self.method := method;

** system variables 
  set $error: Error;   -- last error

  ** system objects;
  set $stack: ()Call;  -- list of calls
  set $trace: ()Error; -- list of errors

** methods for Exceptions
method (@self:Error) .raise(...);
method (@self:Error) .expect(...);
method (@self:Error) .pass(...);
method (@self:Error) .fail(...);

Runtime Errors

Runtime errors can be system exceptions or user defined exceptions. System exceptions are predefined in the Exception package. Every system module can define system errors. User errors can be defined in project library.

Error instances are created during the program execution when program can not continue. An error can be raised by your modules or by the system modules.

Raising exception

There are two alternative statements to create user defined exceptions: "raise", "expect". These methods are static methods defined in module Exception.

** system exception
raise $ExceptionType("message");

** user-defined exception
raise (code,"message") if condition;

Exception handling

Recover: region define an "exception handling region" for a processes, functions or methods. This is a optional region that you define if your sub-program can produce exceptions.

In this region developer can decide to abort the program, print a message, resume the execution or wait and tray again. Maybe a second time will work.

Unhandled exception are automaticly propagated in the stack trace. So the caller must handle the exceptions that are possible raised by a subprogram. If not, the program will crash.


#using recover
driver recover_demo:
  set a = 0.00;
  let a := 1/0;
  print $error.message;


error: numeric division by zero.

Testing expectations

The "expect" statement can check a condition and raise an error if condition is false. Error message is default: "Unexpected error in line: N". Optional you can define a custom message.


** runtime expectations
expect condition1;
expect condition2 else "user defined message";


Read next: Control Flow