Next topics describe overal structure of Eve applications.
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.bee
| |-- aspect2.bee
| ...
|-- server
| |-- aspect1.bee
| |--- aspect2.bee
| ...
|-- library
| |-- module1
| | |-- module_script1.bee
| | |-- module_script2.bee
| | ...
| |-- module2
| ...
|
|-- documents
| |-- readme.md
| |-- index.html
| ...
|-- agent.eve
|-- server.eve
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:
Notes: One project can contain one or many drivers. Each driver is an independend script. A driver has a single executable process. Eve virtual machine can run a single driver at a time but several aspects in the same time. A driver can "import" modules and can "start" several aspects in parallel.
A script file is divided into regions using keywords: {import, alias, global, process}. Regions members are using indentation, like Python. A region ends when next region begins. Eve try to be flat as much as possible. Therefore these regions are right side aligned.
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 script can have parameters, defined after script name in a list, but a module do not have parameters.
+--------------------------------------------
Header comments: explain the module purpose
These comments can be used on top of module
---------------------------------------------+
[driver | aspect | module] name(parameters):
** set system variables
set $sys_con = value;
...
** import region
import
from $path/library_name use (*);
** qualifier suppression region
alias
AliasName1 = library_name.MemberName;
AliasName2 = ClassName[parameters];
...
** define user types
class
NewType = {} <: Type;
...
** define functions using Lambda expression
global
set expression_name = (params) => (expression);
...
** global & shared states
global
set local = value1: user_tupe;
...
----------------------------------------------------
** main process, or initialization region
[process | init]
new my_var = value; -- private variable
...
[finalize | release]
** release locked resources & log reports
...
return;
-----------------------------------------------------
/* notes: can be used at the end of the script.
usually the notes use C like, block comments.
*/
Note: Any script has one or more global regions. In this regions, you can set system states and define global states. In declaration region you can also define: classes, methods, functions & routines.
All variables defined in global scope are static. You must use keyword "set" to define them and assign initial value. Static variables are created on the heap and have a permanent nature.
Shared states use prefix "$". These are called system variables. Can be loaded from a configuration file (*.cfg). Eve modules and drivers cana define, initialize or reset values for shared states.
Shared states can be defined in the first region after ":", after indentation. No need to use global keyword for this region. You need to use global if you declare some globals later before the process or init region.
Several system variables are provided by Eve environment:
System objects are instantiated at startup and are part of core library. These contain runtime information that you can use for debuging. System objects do not need module qualifier. You can access them directly once the module is imported.
Constants have at least first letter capitalized. The best practice is to use all capital letters for constants. The next symbols in a constant name can be numbers or underscore but no special or Unicode character.
** gobal scope
global
set $PI = 3.14 :Float; -- shared constant
set $var = 0 :Integer; -- shared variable
Global states are static variable defined in global scope . You can access clobal states in functions and methods without qualifier.
Global states can be constant or variables. Constats use capital letters while variables are using lowercase letters. First letter is important, other letters can be numbers, lowercase or uppdercase letters or undrscore.
driver test():
** private class
class Person = {name:String, age:Integer} <: Object;
** create states using type inference
global
set E = 2.52 :Real; -- Euler's number
set x = 0 :Integer; -- private state
** create a global object of type Person.
process
new me := Person("Elucian", "56");
return;
The import is used to include public members from modules:
driver script_name:
** define global states
set $user_path := root_path/relative_path;
import
from $user_path use (module_name,...); -- specific modules
from $user_path use (*); -- find all modules
...
alias
** create alias for some of the hiden members
set new_name := module_name.member_name;
process
...
return;
A library is a set of reusable modules. A library can be installed in EVE environment or can be project specific. EVE machine is using a special system variable to search for a library: $EVE_LIB. If the library is not found the module can't be imported and the script fails.
Note: Library name is the name of the subfolder. Module is the name of the last folder in a path. By convention you can create itermediate subfolders but only the last folder is relevant and it must be unique in a library.
A driver represents the application main script. It has the role to lead the application main process. When driver execution is over the application give control back to the EVE virtual machine. If no other process is running, the machine shuts-down after it clean-up the memory.
An "aspect" is a script specific to a particular project. It resolve a problem or address a specific concern. Aspect files are executable. One aspect can execute other aspects, however it can't be recursive.
An aspect can receive parameters. One aspect has a declaration region and an executable region. You can resolve an aspect by using keyword: "apply" or "start". An aspect must handle it's own errors. If errors are not handled, the program panic and stop execution.
aspect aspect_name(parameter_list):
** declare variables
...
process
** executable region
...
recover
** handle all errors
...
return;
Parameters are defined in round brackets () separated by comma. Each parameter must have type and name. Using parameters require several conventions:
Mandatory parameters do not have initial values but only type. Optional parameters have initial value that is assign using operator "=" with explicit :type, or ":=" with type inference. Not both.
(name:value)
pairs;Vararg parameters
One aspect can receive multiple arguments of the same type into a single collection parameter. This can be a List, DataSet or HashMap, depending on declaration.
# print all arguments
aspect test(*args: ()String):
** list all arguments
process
cycle:
new arg :String;
for arg in args loop
print arg;
repeat;
return;
One script can have one single process. Every process has a local context. In this context you can define variables, and instantiate objects but you can't define routines, classes, methods or functions. Process local members can't be shared but can be send as input/output parameters to other subprograms.
To avoid overuse of global variables you must use input/output parameters. We prefix output parameters using symbol "@". Output parameters require a variable as argument, otherwise you will not be able to capture the output value.
#demo output parameters
aspect output_params:
** private subroutine
routine add(p1 = 0, p2 = 1: Integer, @op: Integer):
let op := p1 + p2;
return;
process
new result: Integer;
** inpur/output argument require a variable
call add(1,2, op:result);
print result; -- expected value 3
** negative test, will fail
call add(1,2,4); -- error, "out" parameter require a variable
return;
Eve modules are abstract concepts. One module exist in a folder. A module consist of several scripts with extension ".eve". Modules can be combined together in large projects. Some modules are common for many projects. These are called system modules or library modules.
modules ...
An Eve module begins with "module" keyword. Module name is the same as the last folder in a library path. A module can have an initialization region. This is executed when the module is imported for the first time.
+-----------------------------------------
module name is the folder name, so that
one module can have more then one scripts.
-----------------------------------------+
module module_name:
** shared states
set $system_variable: Type;
...
** import region
import
from $path/library_name use (*);
** declare members
...
init
** setup intial states
let $system_variable := value;
...
release
** verify states & log
$error.log();
...
return;
Module names are using lowercase letters, can 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.
Module scripts A module can be a single script file but usually represents several files. The main script has the same name as the module name. In the main script you can define ony the public members. In secondary scripts, you can define only the private members.
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 run only a one driver script at a time. In service mode you can run multiple drivers. Each driver is independent and can't communicate with other drivers.
Configuration: Eve services using a general configuration file: eve.cfg. This file contains default settings for EVE machine. System variables are shared. A copy of EVE system variables is send to every new driver instance.
A driver can define default settings using "set". Configuration setings are loaded over the default values and have priority. In a process, you can temporarly change the settings using let. These changes do not propagate back into the condifuration file.
Memory allocation: EVE service is in charge of allocating memory for each process. There is no shared memory between processes. When a process is terminated the memory is cleaned up of garbage and the machine continue to run until all processes are finished.
To execute a driver there are 2 methods:
When a driver is loaded, all it's components are compiled in memory, then the process is executed. If a driver do not have a "process", it can be manually executed from eve console, one routine at a time.
~/eve -c file.cfg
eve:> load ~/path/driver_name.eve -c file.cfg -m 2048GB
eve:> debug ~/path/driver_name.eve -c file.cfg
Eve REPL can be used to load or debug a driver manually, using special commands. The command options are not yet fully designed. We will implement these basic commands:
command | description |
---|---|
load | compile a driver and execute the main process |
debug | compile a driver but does not execute main process |
begin | start the driver main process step by step execution |
enter | this is actually the enter key. execute next step |
display value for a global variable, it can't print locals | |
resume | continue running until a "halt" statement is enconter |
report | create a debugging report about system state |
stop | stop the driver execution but do not clean memory |
clear | stop the driver and clean the memory |
setup | load configuration file and set "$" variables |
quit | stop EVE machine and exit from REPL |
You can use "halt" inside the script to stop a script and debug the script later. "halt" is working only when debug mode is active. Statement "halt" can be conditioned with "if" to create a debugging break-point.
One aspect is executed from driver or from another aspect. You can not execute an aspect from itself. Recursive aspects are not supported. The compiler will detect a recursive aspect and will fail at runtime.
You can debug an aspect just like a driver. You can load an aspect, execute the aspect step by step and create a report. Aspects are independent processes. However you can't debug aspects in parallel.
Aspects can be run in serial mode one after another. Let's consider we have 3 aspects: {one, two, three}. We can execute each aspect using keyword: "apply". This will intrerupt the driver process and will execute aspec process one by one then return control to driver and continue the main process:
driver test_apply:
# synchronous call
process
** apply each aspect
apply [folder/]aspect_one(arguments);
apply [folder/]aspect_two(arguments);
apply [folder/]aspect_three(arguments);
...
return;
Note: You do not have to import an aspect into a driver but you must specify the relative path of the aspect. If the aspect is in a subfolder, you must include folder name. If the aspect is in the same location as the driver, you do not need a folder name.
Aspects can be resolved in parallel mode using keyword "start".
# asynchronous call demo
driver async_demo:
process
** enqueue aspects to be resolved
start [folder/]aspect_one(arguments);
start [folder/]aspect_two(arguments);
start [folder/]aspect_three(arguments);
** wait for all aspects to finish
yield;
return;
One script can start an aspect with different parameters.
** resolve several aspects
driver async_loop:
process
** enqueue same aspect 4 times
cycle
new i: Integer;
for i in (1..4) loop
start demo(i);
repeat;
yield;
return;
The driver or aspect can import modules. After import, all public elements of modules can be used on demand. The parent process is controling the execution. Modules are not directly executable.
Modules are "singleton" once a module is loaded in memory, it will not load a second time for same process. Eve is a multi-session system. Module states are bound the the current process.
When a eve is executed, it can be started with parameter: -x or --exclusive. This is a signal that only one process can connect or use resources. In this mode "start" is not starting a new process but works like "apply" in serial mode. In this mode you can alter a database structure.
Program can be early terminated be done using: "over" or "panic". This is a way to release all locked resources and terminate the application. Program can end with an error code using "panic N" statement, otherwise it will automatically return 1 with panic and 0 with "over". Actually "panic 0" is equivalent to "over".
When drive is terminated, it depends what is heppening next. When was started from a console in debug mode the module remains in memory, parsed and ready for debugging. The memory can be investigated using commands. When driver is executed in regular mode, the memory is erased and the EVE machine stop running.
Next code sequence is terminated after 100 iterations:
# terminate a program with: over
driver test_over:
process
cycle:
new i = 0 :Integer;
loop
write "."; wait 10;
over if i > 10;
let i += 1;
repeat;
return;
EVE virtual machine can load system environment variables 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. One application can run with different configuration file. Application documentation must contain demoduleion of all variables required by the application.
A good practice is to provide a configuration template . The template file may contain comments and $key = value pairs. Values can be numeric, string, symbol, date, time or data literals: [],(),{}. Eve application will automatically parse configuration file to read values for the system variables.
In debug mode you can modify the configuration and reload using "setup" command. REPL must be in "ready" state when you reload settings otherwise you will get an error.
Read next: Data Types