Sage-Code Laboratory

EVE Data Types

The purpose of any programming language is to manipulate data. On the lowest level, data is basicly 0 and 1 signals. EVE is a high level language so our idea of data type is more complex. EVE has a gradual-typing system that will be explaine below.

Let's describe the basic types:


Primitive Types

Class Name Description Minim Maxim
Integer Signed on 64 bit -2⁶³ 2⁶³-1
Natural Unsigned on 64 bit 0 2⁶⁴-1
Decimal Float precision number on 8 bytes 0 1.8 × 10³⁰⁸
Unicode Single Unicode symbol UTF-32 4 bytes 0 2³²-1
Ordinal Enumeration of symbols, Short 16 bit 0 2¹⁶-1
Date Calendar date 01/01/0001 01/01/9999
Time Time of the day (24h) 0 8.64e+7ms
Logic Is a Ordinal subtype having values: False = 0, True = 1 0 1
Note: Primitive types get zero initial values automatically and can not be Null. This is a major thing in EVE. No null primitive-types! Numeric values have initial value zero.

Numeric Types

In EVE we can have two categories of numbers:

Category EVE Types
Discrete Ordinal, Integer, Natural, Range
Continuous Single, Double, Decimal, Domain

Discrete numbers:

type Chars Bytes min max maximum number
Integer 20 8 -2⁶³ 2⁶³-1 ≤ 9,223,372,036,854,775,807
Natural 20 8 0 2⁶⁴-1 ≤ 18,446,744,073,709,551,615

For conversion into characters:

Continuous numbers

The type Double is represented using floating precision numbers.
Floating decimal numbers are most simply described by 3 Integers:

The numerical value of a finite number is −1ˢ × c × 2ⁿ Using this formula EVE define two floating point types.

Single: is single-precision 32-bit IEEE 754:
Double is double-precision 64-bit IEEE 754:

type Digits Bytes maximum number
Single 7 4 ≤ 3.4 × 10³⁸
Double 16 8 ≤ 1.8 × 10³⁰⁸

Precision is variable depending on the size of the number. The number of digits represents the largest number that can be converted from string format into a Double and back without loosing any digit. Think of it like a digital display from a packet calculator.

Numeric literals

Example Description
0 Integer zero
123 Integer number using symbols: {0,1,2,3,4,5,6,7,8,9}
1/2 Single number use symbols: {.,0,1,2,3,4,5,6,7,8,9}
0.5 Double number use symbols: {.,0,1,2,3,4,5,6,7,8,9}


#numeric literals
driver numeric_literals:

    Integer i; // Initial value = 0
    Natural n; // Initial value = 0
    Double  r; // Initial value = 0.00

    i := 9223372036854775807;  //  maximum
    n := 18446744073709551615; //  maximum
    r := 1/2; //  0.5

See also: scientific notation

Single Character

For representing a single character you can use one of two conventions: single quoted strings are ASCII encoded while double quoted strings are Unicode UTF-8. For single character literals we do not use a string prefix.


These symbols are represented using 172 ASCII not extended ASCII

Value NUL = ''. This is also the default value.


These symbols are unicode UTF32. That is using 32 bit Integer

Value NUL = U+0000. This is also the default value.

Composite Types

Composite data types are unions of data elements. A composite variable that is not initialized can have Null value that is similar to zero but different. Some of these types are going to be explained later. We enumerate them all here to grasp the idea.

Class Name Description
Range Discrete range of numbers equaly distanced (x..y:ratio)
Domain Continuous domain delimited by loer and upper bound [x..y]
String Single quoted string: a'...' or b'...'
Text Variable capacity string: a"..." or u"..."
List Dynamic unsorted enumeration of values or objects of same type
Map Enumeration of (key:value) pairs unique sorted by key
Set Enumeration of unique elements of the same type sorted by value
Object Base class for creation of plain simple objects
Exception Composite type derived from Object base class


A range is a notation that can create a sub-set of integer numbers.


range ::= (min..max:ratio);



Numeric ranges:

#numeric range demo
driver numeric_range:
  print (0..5);    // (0,1,2,3,4,5)
  print (0.!5);    // (0,1,2,3,4)
  print (0!.5);    // (1,2,3,4,5)
  print (0..10:2); // (0,2,4,6,8,10)

  ** test range  
  expect 0 in (0..5); 
  expect 5 in (0..5); 
  ** test limits
  expect 0 not in (0!.5);     
  expect 5 not in (0.!5);   


Symbol ranges:

# using symbol ranges
driver symbol_range:
  print ('0'..'5') // ('0','1','2','3','4','5')

  ** following statements should pass
  expect ('0' in ('0'..'9'));  //will pass   
  expect ('X' in ('A'..'Z'));  //will pass



Domains are similar to ranges, except they cover continuous numbers not discrete range. So you can not print an entire domain. If you try, an infitinet loop may trigger so when you print a domain it will be printed as it is defined unlike the range that is expanded.


domain ::= [min..max] <: Super_Type


# domain demo
driver domain_demo:  
    print [0..1]; // [0..1]
    ** using domain
    expect 0   in [0..1]; 
    expect 1   in [0..1]; 
    expect 0.5 in [0..1];     

    ** exclude limits
    expect 0 not in [0!.1];
    expect 1 not in [0.!1];  
    expect 0.5 in [0!!1];

Data Coercion

In computer science coercion is used to implicitly or explicitly change an entity of one data type into another of different type. This is ready to take advantage of type hierarchies and type representations. If not designed properly the coercion can be a fatal mistake. EVE is a safe language so we do only safe coercion.

Implicit coercion In EVE the arithmetic operators are polymorphic. Numeric operators can do implicit data conversion to accommodate the data types and return an accurate result. Automatic conversion is possible only when there is no risk of loosing data precision. If there is a loss of precision we can end-up with a run-time error. To prevent this EVE will implement a safe compile-time check.


#example of implicit conversion
driver implicit_coercion:

  Integer a = 2;
  Double  b = 1.5; 
  b := a;       //  this implicit cast is possible b = 2.0
  b := a + 3.5; //  add 3.5 then assign result to b = 5.5
  a := b;       //  error: can not assign Double to  Integer
  a := 1.5;     //  error: can not assign Double to  Integer

Explicit coercion Explicit coercion is a forced conversion. Can be used to convert backwards from higher data range to lower data range or from continuous numbers to discrete numbers. This however can cause a data or precision loss. Explicit coercion is using a function.

Examples of explicit coercion:

# explicit coercion in EVE
driver explicit_coercion:

  Integer a = 0;
  Double  b = 1.5;

**explicit coercion lose (0.5)
  a := floor(b);
  write  a; //will print: 1
**explicit coercion add (0.5)
  a := ceiling(b); 
  print  a; //will print: 2

**explicit coercion rounding:  
  a := round(b);
  print  a; //  will print: 2

Number to a string

#convert number to string
driver number_to_string:

  String  s; 
  Integer v = 1000;
  s := format(v); //  explicit coercion s = '1000'
  expect s == a'1000'

String to a number

This can be ready using the casting function parse(), only if the string contains a number. Otherwise the conversion fail and will rise and exception.

#string to number conversion
driver string_to_number:
  Integer  v; 
  Double   b;
  String   s = '1000';
  String   r = '200.02';
  v := parse(s); //  make v = 1000
  v := parse(r); //  make v = 200 and decimal .02 is lost
  b := parse(r); //  make b = 200.02 and decimal .02 is preserved

Note: Build-in functions that are located in EVE default library: { parse(), format(), ceiling(), floor() round()}. This module is one of the standard modules that are automatically included in any EVE program.

Type inference

This is a logical deduction of data type from constant literals.


#test type inference
driver type_inference:

** Define a list of 10 elements using type inference
global List ls = [0,1,2,3,4,5,6,7,8,9]; //  initialized list of Integer   

  print  ls.type(); //  List[Integer]
  expect ls is List[Integer];

Default types

Literals are representations of specific particular data type in source code.

Basic types Next notation use "9" to show any digit in range [0..9].

Literal Type
9 Integer
-9 Integer
0x9ABCDEF Natural
0b1010101 Binary
9.9 Double
U+0001 Word

Zero literals

Literal Type
[] List
{} Set/Map
() List
"" Text
'' String
0 Integer
0.0 Double

Collection literals

Literal Type
{a:0, b, c} Ordinal
{x:'b',y:'d'} Object
[1, 2, 3] List[Byte]
['a','b','c'] List[Symbol]
["a","b","c"] List[Text]

Type Inference

Sometimes the type is partially specified to simplify type declaration:

# gradual type declaration
    ** member type is inferred later from first member
    List a := [];

Type verification

We can verify the type using "is" operator:

# using operator "is" to check type
driver type_check:

    ** define object and initialize
    Object r = {name:"test", age:"24"};  
    ** define hash table
    Map    t = {('key1':"value1"),('ley2':"value2")}; 

    ** check variable types using introspection
    expect  is Text;
    expect r.age   is Text;
    expect t.key   is String;
    expect t.value is Text;

Printing type()

For type introspection we can use type() built-in function:

# introspection demo
driver print_type:

global  Double i = 1.5;
  expect i is Double;
  print "type of i is \s" ? type(i);

Polymorphic operators

In mathematics there are very few operators: {+, -, / , * } that can operate with any kind of numbers: negative, positive, rational or real. Operators are not bound to specific data types. Therefore these operators are called "polymorphic".

Some languages define different operators for Integers and Floating decimal numbers. For example in OCaml the operator "/" can divide Integers while "/." can divide Floating point decimal numbers. This is unexpected for a mathematician who is expecting to use one single operator for division.

In EVE, operators are mapped to functions. To design polymorphic operators we overload the function signature using type dispatch. The dispatch is using left side operand first, this is the leading operand. For unary operators there is only right side operand so this becomes the leading operand.

Logical type

In Latin the "falsus" and "verum" are translated in English to "false" and "true".

name value binary
False Logic.False 00000000 00000000
True Logic.True 00000000 00000001


Logic: name = False; // explicit initialization

Internal design

Probably best to define Logic type is Ordinal:

type Logic = { .False , .True } <: Ordinal;

Logical expressions

A logical expression is a demonstration or logical deduction having result True or False. Operator precedence is: {not, and, or, xor}. The order of operations can be controlled using operator precedence and round parentheses.

Result of logical expressions can be used into a conditional statement to make a decision. Also results of logical expressions can be stored in logical variables to be used later in other conditions.

Gradual typing

Gradual typing is a type system in which some variables may be given types and the correctness of the typing is checked at compile-time (which is static typing) and some variables may be left un-typed and eventual type errors are reported at run-time (which is dynamic typing). To declare gradual types we use a polymorphic type called variant.

Variant Types

A Variant is a polymorphic variable that can have multiple types but only one at a time:


** define variant subtype
type Variant_Name: {Type | Type | ... } <: Variant;

**  declare single variable (with initial value)
global Variant_Name v = value; 

Variant Properties

Making a null-able type

For this we use a special type Null


** define nulable variant
type Number: {Integer | Double | Null} <: Variant;

** use nulable variant
global Number: x; // default value is Null


A variant can establish its data type at runtime:

Example 1:

In next example variant we use a variant that can be Double or Integer.

#variant type demo
driver variant_type:

    Double | Integer v, x ,t;
    ** safe conversion
    t := 1 / 2;   // make t Double    
    t := 12;      // change type
    print type(t) // Integer
    ** unsafe conversion
    x := 1.5;   //  x is Double    
    v := 1; //  v is  Integer    
    v := x; //  v becomes Double     
    print type(v) // Double

Example 2:

A variant is a way to create a generic routine. For this we use variant parameters:

# variant parameter in routines
driver variant_params:

** define a subroutine that can swap two numbers
routine swap(Integer | Double x, y):
    Integer | Double i
    expect type(x) = type(y);
    i := x; //  intermediate reference
    x := y; //  first  swap
    y := x; //  second swap

    Integer y;   // default zero
    Double a, b; // default zero
    ** invert two  Integer numbers
    x := 10;
    y := 20;  
    swap(x, y);
    expect (x == 20) and (y == 10);
    ** invert two Double numbers
    a := 1.5;
    b := 2.5;
    swap(a, b);
    expect (a == 2.5) and (b == 1.5);

Calendar date

In EVE we represent calendar date.

Date storage

Date literals

When can create a date literal using 3 format functions:

Note: A reversible function is overloaded.

#overloaded function
routine main:
    Date: date := "2019/01/30" as YDM
    print date as YDM; //  2019/01/30
    print date as DMY; //  30/01/2019
    print date as MDY; //  01/30/2019

Time Duration

Time data type can be used to represent duration.

Representation Time is represented as a number on 8 bytes.


Time format is created using two reversible functions: t12() and t24()

ss: can be 0..60 seconds
xx: can be: (am/pm)


# time demo
driver time_demo:
    Time time1 = time2 = time3 = "00:00";
    time1 := "23:63" as T24;
    time2 := "23:63:59,99" as T24;
    time3 := "11:63:59pm,99ms" as T12;

Read next: Collections