Sage-Code Laboratory
index<--

EVE Structure

EVE is a scripting language for test automation and data transformation. We have created this project to learn how to craft an interpretor. Our intention is to make a simple language that works even if is more verbose and slower than other languages. EVE is an experimental language that will be used in test automation.

Structure

Next sections will teach you the structure of EVE scripts.

Bookmarks:

EVE script

EVE enable you to create scripts that can be compiled in memory and can be run in a loop until an finalization condition force the program to terminate. EVE scripts are never compiled into an executable form. Every time a script runs, it create a log file that can be archived and investigate later.

Kind of Scirpts

EVE include several kind of scripts that together makes a "suite" of scripts, called project. Some scripts are reusable to many projects but some are specific to a single project. Here are all the possible scripts:

Notes: One project can contain one or many "driver scripts". A driver script can "import modules". A module is a non executable script that contains utility: constants, data types, functions, and routines.

Resrtictions

Modules

Modules are source files having extension: *.eve. Module names are using lowercase letters, can also contain underscore or digits but no special characters and no Unicode strings. Longer names that use several words can be separate with underscore. The module name can be 30 characters long.

Members

Library

library is a shared folder containing reusable modules.

Aspects

An "aspect" is a script specific to a particular project. It resolve one aspect of a problem. Aspect files are executable scripts. You can call one aspect from another aspect or from the application driver.

An aspect have parameters and processing region. You can call an aspect by using keyword: "start". An aspect can fail or can pass. You can collect the aspect status to check if the aspect has pass or failed.

Drivers

driver is the application main aspect. It has the role to lead the application logical thread. When driver execution is over the application give control back to the operating system.

Notes:

Configuration

One application can load system constants from a configuration file. These are stored as "$name:value" pairs. Some system constants can be derived from environment variables using concatenation operators "&","+", "/" or "\".

A configuration file have extension .cfg. It can be used by the compiler or by the application. One application can run with different configuration files. Application documentation must contain description of configuration constants.

Sometimes a file template is provided to for copy and modify. Template file may contain comments using Bee syntax that you will learn later. Bee application will automatically parse configuration file to read values for: system constants.

Script Regions

A script file is divided into regions using keywords: {import, define, global, class, routine}. In each region you can declare one or single kind of members. Executable statements are enclosed in structures.

Syntax Pattern:

An EVE script start with optional directive: "driver", "aspect", "module". If none is specified, then we consider the file a module. Mosules do not have "main()" sub-routine.

# structure of a driver (pseudocode)
******************************************
**   Header comments: module purpose    **
******************************************
[driver | aspect | module] demo: //name of the scripot is "demo.eve"

** global region 
$sys_con = "value"; // system constant
@sys_var = {1,2,3}; // system variable
...

** import region
import
    from $path/library_name use (*);

** qualifier suppression region
alias 
    ClassName = library_name.class_name;
    ClassName = class_name{generic_parameters};
    ...

** shared constants region
constant
    TypeName NAME = value; // constant (UPPERCASE NAMES)

  
** shared variables region
variable
    TypeName x;         // default value = 0
    TypeName y = value; // specify value ≠ 0
    ...
  
** function declaration region
function 
    TypeName: name(params) => (expression);
    ...

** class declaration region
class ClassName(params) <: Superclass:
  ...
return;

** routine declaration region
routine name(params):
  ...
return;

** main routine is last
routine main(List[String]: *args):
process
   // executable region
   ...
return;

Declaration order

Order of regions by default is: {globals, imports, aliases, constants, variables, functions, classes, routines}. Routines and classes can alternate. You can define multiple regions of the same type when members depend on each other.

Globals

Globals are declared in first module region, with zero space indentation. There is no keyword "global". Global members are recognized by "sigil" $ or @. Therefore we do not need any keywords to declare globals:

Declare Globals

$identifier := value;
@identifier := value;

Once you have imported one module that have globals, you can access them without qualifiers. If names colide, the import can fail, you have to hide the global members that colide to be able to import.

System constants

System constants start with prefix "$". Usually are loaded from a configuration file (*.cfg) and do not need to be declared explicit in a constant region. They are known by EVE runtime environment and can be used in all modules.

Note:

Several system constants are provided by EVE environment:

System variables

System variables are starting with prefix "@" and are defined by standard library.

Notes:

Import region

Is used to include members from several other modules into current module:

Syntax:

** define global constant
$user_path := $root_path/relative_path

imports 
    from $user_path use (member_name,...);  // specific members
    from $user_path use (*);                // all public members
    ...

Notes:

Shared constants

Shared constants are declared in constant region, with "." prefix.

Example

constants
    Double.PI = 3.14; // shared constant

Notes:

Shared variables

Shared variables are declared in variable region:

Example:

#declare variables
variables
    Double pi = 3.14; //  shared variable

Note:

Functions

A function is a relation between some input values and output values. A function may or may not make a computation to establish the output. In EVE functions are pure and first class values.

Pattern:

Functions are declared in script regions that start with keyword: "function". A function can be public or private, local or global. Let's analyze the syntax:

#declare functions
functions 
    TypeName name(parameters) => (expression);

Notes:

Function call The call for a function is using name of the function and round brackets () for arguments. The brackets are mandatory even if there are no arguments, otherwise the function is not executed.

Pattern:

#demo function call
functions 
    TypeName: function_name(Type : param = value,...) => (expression);
  
routine main:
    TypeName result; //not initialized (require store)    
process    
    ** call with no arguments:
    result := function_name();
    
    ** call with arguments mapped by position
    result := function_name(value, ...);
    
    ** call using parameter names and pair-up operator ":"
    result := function_name(parameter:value ...);
return;

Note: Argument value can be anything that translate to a value of expected type:

formal parameters

function call arguments

There is a difference between the parameter and the argument. The parameter is a local variable in the function scope while arguments are values assigned to these parameters with a function call. Arguments can be literals, constants or variables.

Example:

** main routine  
routine main:
    Integer sum(Integer a, b) => (a + b); // local function
process  
    print sum(1,2);  // 3
    print sum(2,4);  // 6
return;

Routines

A routine is a named block of code that can be executed multiple times. A script can have one or more routines. Driver and aspects, must have one special routine that is called "main". Driver main routine can accept a list of text parameters. Aspect main routines can have other type of parameters inlcuding output parameters.

Syntax Pattern:

Routine with result:

routine name(Type parameter, Type .result):
    ** declaration region
    ...
process
    ** executable region
    if condition then 
       exit;
    end if;
    ...
    result := value; //result parameter
return [result];

Notes:

Routine name A routine is extending the language with domain specific algorithms. It must have suggestive names so that other developers can understand its purpose. The routines are doing something, therefore the best names for methods are verbs.

Parameters Formal parameters are defined in round brackets () separated by comma. Each parameter must have type and name. Using parameters require several conventions to resolve many requirements. General syntax for parameter name is:

Syntax:

Mandatory parameters do not have initial values.

Notes:

  1. One routine can receive one or more parameters,
  2. Parameters having initial values are optional,
  3. Values used for parameters in routine call are called arguments,
  4. You can assign arguments by position using a list of values,
  5. You can assign arguments ny name using name:value pairs;

Syntax:

Optional parameters have initial value.

Variable list of arguments

One routine can receive multiple arguments of the same type separated by comma into a list.

Example:

# print all arguments
routine test(List[String] *args) 
process
  print args;
return;

** call routine with variable number of arguments
apply test ('a','b','c'); // use 3 arguments
apply test ('a','b');     // use 2 arguments

** you can use operator "*" to _spread_ collection elements
local
    Set[Integer]: argument;
process
    store argument := {1, 2, 3};
    ** transfer arguments by position using spreading operator (*)
    test (*argument);  
return;  

Routine context

Every routine has a local context. In this context a routine can define variables, functions and objects but not other routines nor clases. These members are private, can not be accessed from outside of the routine.

Routine call Routines can be used in call statements. A routine call can be done using "apply" routine name followed by arguments. For one single argument, or no argument the parentheses are not required.

#these are all valid routine calls
apply routine_name; //call routine without arguments
apply routine_name argument_value;   //call with single argument
apply routine_name (value, value, ...);//call with a list of arguments
apply routine_name (param:value,...);  //call with a argument by name
apply routine_name (value, value, param:value); //mix position with names

Routine termination A routine end with keyword return; When routine is terminated, program execution will continue with the next statement after the routine call. Keyword exit can terminate a routine early. If the result do not have initial value and routine terminate early the result may be null.

Example:

** routine call demos
routine test(Integer a):
process
    ** print is a system routine
    print a; 
return;

routine main(List[String]: *args):
    ** number of arguments received:
    Integer c = args.length;
process
    ** verify condition and exit
    exit if c == 0;  // early interruption  
    apply test(c);   // routine call
return;

Side Effects

A routine can have side-effects:

using side-effects

Next routine: "add_numbers" has 2 side effects:

** local variables
variable
    Integer test; 
    Integer p1;   
    Integer p2;   

routine add_numbers():
process
    **side effects  
    test := p1 + p2; //  first side-effect
    print test;      //  second side-effect
return;

routine main:
process
    p1 := 10;    //  side effect
    p2 := 20;    //  side effect
    add_numbers; //  call routine add_numbers;
    expect (result == 30);
return;

Output Parameters

To avoid shared variables you can use input/output parameters. We mark ourput parameters using symbol "." like parameter is public. Output parameters require a variable as argument, otherwise you will not be able to capture the output value.

routine add(Integer p1 = 0, p2 = 1,  Integer .out):
process
    out := p1 + p2;
return;

routine main:
    Integer result;
process  
    ** reference argument require a variable
    apply add(1,2, result);
    print result;     //  expected value 3
    ** negative test
    apply add(1,2,4); //  error, "out" parameter require a variable
return;

Notes:

Dispatch

Dispatch is a form of subroutine selection by signature. It makes possible multiple subroutines with the same and different parameters. These kind of subroutines are overloaded. Subroutine signature include name, parameter types and result types.

Wikipedia: name mangling

Classes

Classes are composite data types. We use a classes to create multiple objects with same structure. Each object is a reference to a location in memory were the object is stored. An object is also called instance of a class.

Pattern:


class name(parameters) <: base_class:
   // definition region
   ...
create
   // constructor region
   ...
remove
   // release region
   ...
return;

Script Execution

EVE scripts are executed using a virtual machine. You can start the virtual machine as a service or as console application. In console you can start only a one script at a time. In service mode you can start multiple sessions with different startup parameters. Each session is independent and can start one single script.

Configuration:  EVE serviceis using a general configuration file: eve.cfg. This file contains information about all scripts and configuration files. After the service starts this file is parsed and each script that have an entry is start automatically formng a session. For each session, the service will create a different log file.

Memory alocation: EVE service is in charge of allocating memory for one session before the application starts. There is no shared memory between sessions. That is a session is dedicated for a single application. After application is terminated the memory is released.

To start an application there are 2 methods:

  1. Using the system command call with parameters
  2. Using console REPL commands line app and type a "start" command

Running a Driver

eve:> run path/driver_name -c file.cfg -m 2048GB

Driver Execution:

When a driver is executed, all it's components are compiled in memory, then the main subroutine is executed. If a script do not have a "main()", it can not be executed. The is the program entry point. When the "driver.main()" is finalized the program terminates and is removed from memory.

Aspect Execution:

One aspect is executed from "driver main process" or from another aspect process. You can not start an aspect from itself. Recursive or cyclic aspects are not supported. The compiler will detect a recursive aspect and will fail.

Synchronously

Aspects can be resolved in linear mode one after another.

** resolve several aspects
subroutine main:
process
    ** solve each aspect immediatly
    solve aspect_a(params);
    solve aspect_b(params);    
    solve aspect_c(params);        
    ...
return;    

ASynchronously

Aspects can be resolved in parallel mode in different processes.

** resolve several aspects
subroutine main:
process
    ** enque aspects to be resolved
    defer aspect_a(params);
    defer aspect_b(params);    
    defer aspect_c(params);        
    ** resolve all enqued aspects
    resolve;
return;    

Parallel Sessions

One aspect can resolve same problem with different parameters in parallel.

** resolve several aspects
subroutine main:
process
    ** enque same aspect 4 times
    for i in (1..4) loop
        defer aspect(f(i));        
    end loop;
    resolve;
return;    

Module Execution:

The drivers and aspects can load modules in memory. After loading, all public elements of modules can be executed on demand. Before execution the driver can interact with the user to ask for input then call routines, functions and static classes members.

Modules are "singleton" once a module is loaded in memory, it will not load a second time. Its states are unique for a session. EVE is a multi-session system. A script can be run asynchronously in parallel with itself but in different sessions.

Exclusive Mode:

When a script is executed, it can be started with parameter: -e or --exclusive. This is a signal that the script can not be started a second time in a separated session. In this mode a script can connect to a database in admin mode and can lock a file so that no other process will access the file in parallel.

Session Termination:

Script termination can be done using: "exit" or "abort". This is a way to release all locked resources and terminate the application session. Program can end with an error code using "abort N" statement, otherwise it will automaticly return 0 code for the operating system.

When script is terminated, it depends if was started from a console in debug mode or in regular mode. In debug mode the script remains in memory, parsed and ready for debuging. The memory can be investigated using commands.

Example:

Next code sequence is terminated after 100 iterations:

** fast forward demo
routine main:
    Integer i = 0;
process
    loop: 
        write ".";
        wait 100;               
        over if i == 100;
        i += 1;
    end loop;
return;

Read next: Data Types