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;
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]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
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;
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];
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
apply 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: 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;
process
** 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 == {};
return;
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
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.
# 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};
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;
process
expect type(m) == HashMap;
expect m is Null;
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
print h -- {'a':1, 'b':2, 'c':3, 'd':4};
return;
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
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 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.
process
** define Null string with default capacity
new str1 :String; -- default Null
new str2 = "": String; -- equivalent to Null
...
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
** 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)
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.
# null string demo
driver null_string:
** Null strings
set str :String;
set btr := 'ω';
process
** 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
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.
# text type has unicode support
process unicode_text:
new us = "I can write Greek: \αβγδ\.";
print us;
return;
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".
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
process
** 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;
return;
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,
line:Integer
} <: Object;
class .Error = {code: Integer, message: String} => (@self :Error):
let self.code := code;
let self.message := message;
return;
class Call = {line:String, method:String} => (@self:Call):
let self.line := line;
let self.method := method;
return;
** system variables
global
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(...);
return;
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.
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;
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;
process
let a := 1/0;
recover
print $error.message;
return;
error: numeric division by zero.
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