Eve Scripts

Eve is designed to quickly build a data processing app that can have jobs to be executed in order on a precise schedule. You can write a small free script, or divide a project into many scripts that run together: drivers, aspects and modules. This page shows the project, the free script and what every script has in common. The next pages explain each kind: Drivers, Aspects, Macros (a driver and its aspects in one file) and Modules.

Keywords of this phase

The keywords that phase 2, Eve Code Topology, adds to the core language (the core keywords are in Syntax). Each one is explained on the page in the last column.

KeywordMeaningExplained in
aspectheader of a script that a driver applies: aspect name isAspects
moduleheader of a library script; end name; closes itModules
fromstarts an import: from lib use (a, b);Modules
uselists the imported names of an importModules
exportlists the members a module makes public: export (Point, square);Modules
initializeinitialization region of a moduleModules
finalizefinal region of a process: runs when the process ends normally, by exit or by abortScripts
proceduredeclares a procedure: a subprogram outside a class that does work and returns no result; it is called as a statementSubprograms
applyruns an aspect and waits for it: apply report(10, @sum);Aspects

Projects

A project is a folder with a specific structure. It contains one or more applications that can run independent of each other on the same computer or a group of computers.

project structure

Next project contains two applications: server/agent and show folders where you should put your code (src+lib) and documentation (doc). This is a recommendation but not a hard rule.

$pro_home
project
  |-- agent
  |   |-- aspect1.eve
  |   |-- aspect2.eve
  |   ...
  |-- server
  |   |--  aspect1.eve
  |   |--- aspect2.eve
  |   ...
  |-- library
  |   |-- module1
  |   |     |-- module_script1.eve
  |   |     |-- module_script2.eve
  |   |     ...
  |   |-- module2
  |   ... 
  |       
  |-- documents
  |   |-- readme.md
  |   |-- index.html
  |   ...
  |-- agent.eve
  |-- server.eve

Free scripts

Not every program needs a driver. A free script is a file whose first line starts with #! (the shebang, for example #!/usr/bin/env eve). It holds sequential statements, one after another, and it needs no driver, no process, no return; and no end name;: the script ends at the end of the file. Only a file that starts with #! can be a free script; the same text without the first line is an error.


#!/usr/bin/env eve
** a free script: statements in sequence, it ends at the end of the file
new total = 0 :Integer;

for i in (1..10) do
  let total += i;
done;

print "total: {total}";

Rules

  • A free script has control statements: if, loop, for, while, and also expect, print and calls of library functions;
  • It has no declarations of subprograms: no functions, methods, classes or generators. To define them, write a driver, a module or an Eve macro;
  • It has no jobs: jobs, recover and finalize belong to processes (see Processing), so an error that nobody handles ends the script, with the exit code of table in Processing;
  • It can import modules with from … use and apply aspects;
  • It is the shortest way to a small tool, a test or an experiment, and the first step to a driver: when the script grows, move the statements into process main is … return; of a driver. A #! file that declares a driver is no longer a free script but an Eve macro, which keeps the driver, its aspects and their shared declarations in the same file.

Which kind of script?

ScriptStarts withHas a processCan be runDefines globals
free script#!noyes, eve script.eveits own variables
driverdriver name isyes, mainyesyes
aspectaspect name isyes, mainapplied by a driverno
Eve macro#! or # title, then declarationsyes, main of its one driveryes, eve macro.eveyes, shared by its driver and its aspects
modulemodule name isno, initialize and finalizeimportedno, one private state

Scripts

Eve define several kind of scripts that together makes a "project suite". Some scripts are reusable but some are specific to a single project. Here are all the possible scripts:

  • Driver, created with keyword: "driver",
  • Aspect, created with keyword: "aspect",
  • Module, created with keyword: "module",

Notes: One project can contain one or many drivers. Each driver is an independent script. A driver has a single executable process, main. A driver control the execution of aspects. An aspect is encapsulated and contains its own process, also named main. An aspect can receive parameters from the driver. An aspect or driver can "import" modules and can use classes or functions defined in these modules.

Features:

  • A driver can import one or more modules;
  • A driver applies aspects in a sequence, each one an independent piece of the work;
  • A driver or aspect define a single executable process, main;
  • An aspect or driver can import modules;
  • Only a driver defines global states; every driver has its own global scope;
  • An aspect has no public members; a module lists its public members in export (…), and they are used with the dot operator.

Regions

A driver, aspect or module has a single scope. There are no import, alias, constant or global regions: imports (from … use), aliases (def), constants (set), variables (new), classes, functions, methods and processes are declarations in the same scope, in any order. Indentation is mandatory: everything between the header and end name; is indented by exactly 2 spaces, and the compiler reports an error when the indentation is wrong. Only the header, the closing end and the comments before the header start at column 0. A process has its own regions, recover and finalize, aligned with the process keyword. The canonical skeleton of a driver is below, of an aspect in Aspects and of a module in Modules.

Driver skeleton:

An Eve script begins with one of these keywords: {"driver", "aspect", "module"}. Script name is the same as the file name without the extension. A driver or aspect has no parameter list in its header: the parameters belong to its process, process main(parameters) is. The process is indented inside the driver or aspect. A module has no process and no parameters.


** purpose of the driver
driver driver_name is

  ** system variables and constants
  set $sys_con = value;
  set LIMIT = 10 :Integer;

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

  ** alias: qualifier suppression
  def AliasName = library_name.MemberName;

  ** constants
  set NAME = Value :Type;

  ** classes, functions and global variables
  class Person = {name:String, age:Integer} <: Object;
  set expression_name = (params) => (expression);
  new shared = value1 :user_type;

  ** main process
  process main is
    new my_var = value :Type;  ** private to the process
    ** executable region
    ...
  recover
    ** error handling area: retry, resume or abort
    ...
  finalize
    ** release locked resources, consume the job report
    ...
  return;
end driver_name;

Members

  • a script can define members, visible in process;
  • a script can import public members from modules;
  • a driver can "set" system variables and constants using "$" sigil;

Note: All declarations sit directly under the script header, indented by 2 spaces. They belong to the global scope, which is bound to the process scope.

Note: Only a driver defines global states. In its global scope you can set constants and system variables, and define global variables, classes, functions and methods. Aspects and modules define public and private members instead (see Module Import).

The keyword "set" creates a constant; the keyword "new" creates a variable. Global constants and variables are static: they are created on the heap and have a permanent nature.

System Variables

System variables use prefix "$". These are defined and maintained by EVE virtual engine. Values for these variables can be read automatically by EVE engine at startup. Can be loaded from a configuration file (*.cfg). New variables can be defined by user for each script.

New system variables can be defined with the keyword set directly after the script header (or after the #! line), with 2-space indentation. These are constants that belong to the global scope, the same scope used by the driver.

Note:

  • System variables can be defined in a configuration file,
  • OS environment variables are visible as system variables: the variable NAME is read as $NAME,
  • System variables are usually defined in core EVE modules,
  • System variables can be defined or overwritten in program modules,

The system variables that Eve provides, with their types, are listed in one table: System Variables in the Syntax page.

Global Constants

The name of a constant is not enforced by the compiler; the tutorial only recommends it. By convention, constants use capital letters, and the next symbols in a constant name can be numbers or underscore but no special or Unicode character.

Example


** define global scope
  set PI  = 3.14 :Real;       ** global constant
  ...
  new var = 0    :Integer;    ** global variable
  ...

Notes:

  • You can create new global constants and variables;
  • Prefix "$" for system constants to avoid scope qualifier;
  • OS environment variables are visible as $NAME;
  • System constants are capitalized and also begin with "$";

Example


driver test is

** driver context constant
  set E = 2.718281828 :Real;    ** Euler's number

** driver context: class and variable
  class Person = {name:String, age:Integer} <: Object;
  new x = 0 :Integer;           ** global state
  ...

Notes:

  • Global constants are created using keyword "set", global variables using keyword "new",
  • All global states are bound to the process scope,
  • We are using operator "=" for initialization,
  • Operator ":=" executes an expression and can be used in the global scope, for example a string concatenation,
  • Constants are by convention using uppercase letters, the compiler does not check it,
  • $user_path is a shared variable defined by the user;
  • spaces in script-names are not supported you must use: "_";
  • aliases are created with "def" from public members of other modules;
  • a path is a string, built with the smart concatenation operator /

Importing Modules

The import is used to include public members from modules:

Syntax:


driver script_name is
  ** define global system variable
  set $user_path := root_path/relative_path;

  from $user_path use (module_name,...);  ** specific modules
  from $user_path use (*);                ** find all modules
  ...
  ** create alias for some of the hidden members
  def new_name = module_name.member_name;
  process main is
    ...
  return;
end script_name;

Notes:

  • A path is a string. The operator / is a "smart concatenation": between strings it joins path segments and it becomes \ on Windows;
  • An alias is created with "def Name = library_name.MemberName;". An alias of a parameterized class, like ClassName[parameters], is not supported;

Process

A process is the executable region of a script, mandatory for drivers and aspects. A driver and an aspect have the same shape: declarations, then one process, process main is … return;, and the script closes with end script_name;. main is the only process name and the entry point. An aspect is run with apply aspect_name(args);: the caller waits.

A process can receive parameters, input and output (process name(n: Integer, @total: Integer) is). A process returns no result: its outputs are the parameters marked with "@". It can end with an error instead: the exit code of panic, raise, expect or assert is captured by the driver. The arguments are passed in a list, apply reports(10, @sum);. The caller waits, so the outputs are ready on the next statement. Only the driver defines a global scope: its process reads and changes the driver globals. An aspect has its own state and no globals: it receives data by parameters. The scope of an imported module is bound to the scope of the driver or aspect that imports it.

While modules don't use the 'process' keyword, they have an initialization region that serves a similar purpose. This initialization region is executed first when a process starts. For modules imported by a driver, the initialization runs within the driver's main process. When imported by an aspect, it runs in the process of that aspect, at each apply.

Process context

Every process has a local context. In this context you can define variables, and instantiate objects but you can't define classes, methods or functions. Process local members can't be shared but can be send as input/output parameters to subprograms or aspects.

Process Termination

Process can be early terminated using: "over" or "panic". This is a way to release all locked resources and terminate the process. The statement "over" ends the process it is in with code 0. The statement "panic" is an unhandled exception: it ends the whole application with code 1, not only the process; "panic 0" is not allowed and there is no "panic N". A failed "expect" always ends with code 2, and "raise" ends with an error code greater than 0.

Example:

Next code sequence is terminated after 11 iterations:


# terminate a program with: over
driver test_over is

  process main is
    loop
      new i = 0 :Integer;
    do
      write "."; wait  10;
      over if i > 10;
      let i += 1;
    repeat;
  return;
end test_over;

Read next: Drivers