Eve Macros

Eve has two ways to write a whole program in one file. A free script is a sequence of statements: the shortest way to a small tool. An Eve macro is a hybrid script: a driver, its aspects and the declarations they share, all in one file, so a small application, also a parallel one, needs no project folder. This page shows both, side by side, and when to choose each.

One file, two purposes

A project spreads its work over many files: the driver at the root, the aspects in asp/, the modules in lib/ (see Scripts). This is the right shape for a large application, but it is heavy for a small one. A script that only needs to read a file and print a report, or a job that runs two workers in parallel, should fit in one file that you can copy, mail or run with one command. Eve has two kinds of such files:

Free scriptEve macro
Purposea small tool, a test, an experiment: statements run from top to bottoma small application: a driver with its own aspects and shared code
Line 1#!, required#! optional, or a # title
Top levelstatements (new, let, if, for, print, …)declarations only: shared declarations, aspects, one driver
Subprograms, classesnoyes, shared by the whole file
Aspectsapplies the aspects of a projectdeclares its own aspects; may also apply those of a project
Parallel groups, jobs, recovernoyes, in the process of the driver
Runeve tool.eveeve pipeline.eve: runs process main of its driver
Imported by othersnono: a macro is self-contained

Both are self-contained: nothing in them can be imported by another script, so they never become a hidden dependency. Code that several programs need belongs in a module.

Free script

A free script starts with #! and runs its statements in order, to the end of the file. It has no driver, no process and no subprograms (the full rules are on the page Scripts).


#!/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}";

Use a free script when the program is one straight sequence. When it needs a function, a class, a job with recover, or an aspect of its own, it has outgrown the free script: make it a macro.

Eve macro

An Eve macro is a hybrid script: one file that holds shared declarations, any number of aspects and exactly one driver. No keyword marks it: a file with a top-level driver and other top-level declarations is a macro. The declarations are written at column 1, in any order; by custom the shared declarations come first, then the aspects, and the driver last, so the file reads from the tools to the work.


#!/usr/bin/env eve
# score: a driver and its aspect in one file

** shared declarations: seen by the aspects and by the driver
set bonus = 5 :Integer;
new total := 0;

function twice(x: Integer) => (@result: Integer) is
  let result := x * 2;
return;

** an exclusive aspect runs alone, so it may change a shared variable
exclusive aspect collect is
  process main(points: ()Integer) is
    for p in points do
      let total += p + bonus;
    done;
  return;
end collect;

** the driver: process main is the entry point of the macro
driver score is
  process main is
    apply collect((10, 20));
    apply collect((30, 40));
    print "total: {total}";        ** total: 120
    print "double: {twice(total)}"; ** double: 240
  return;
end score;

Shared declarations

Every top-level declaration that is not an aspect or the driver is shared by the whole file: classes, functions, procedures, methods, constants (set), variables (new) and imports (from … use). This is the difference from a project, where an aspect sees no global of the driver: in a macro the driver and its aspects share what the file declares at the top. Each aspect still keeps its own scope and state, and it sees no variable of the driver or of another aspect; only the shared declarations are common.

  • The driver and the exclusive aspects use every shared declaration, the variables too: an exclusive aspect runs alone, so it changes a shared variable safely.
  • A concurrent aspect uses only what is thread safe: constants, classes, atomic variables (Atomic(:T)), channels, and the functions and procedures that the compiler proves thread safe. Anything else is a compile error that names it (below).

A parallel job in one file

A macro is the natural home of a small parallel job. The driver starts its concurrent aspects in a parallel group (see Concurrency); a shared class carries the results back. An output is declared before the group, so it is still there after done.


#!/usr/bin/env eve
# pipeline: two workers in parallel, one file

class Result = {name :String, count :Integer} <: Object is
  constructor(name = "" :String, count = 0 :Integer) => (@self) is
    let self := Object();
    let self.name := name;
    let self.count := count;
  return;
end Result;

concurrent aspect job_worker_1 is
  process main(@res: Result) is
    ** internal sequential job logic
    let res := Result("worker 1", 10);
  return;
end job_worker_1;

concurrent aspect job_worker_2 is
  process main(@res: Result) is
    ** internal sequential job logic
    let res := Result("worker 2", 20);
  return;
end job_worker_2;

# driver executing parallel tasks
driver pipeline_driver is
  process main is
    new r1 := Result();
    new r2 := Result();
    p_exec: parallel
    do
      start job_worker_1(@r1);
      start job_worker_2(@r2);
    done p_exec;
    print "{r1.name}: {r1.count}, {r2.name}: {r2.count}";
  return;
end pipeline_driver;

This example is the demo pipeline_macro; every demo of the tutorial that needs aspects is written as a macro (Demos & Examples).

Concurrent aspects that must add to the same total use a shared atomic variable. Reading it gives a value that depends on the timing of the tasks, so the function that reads it is stochastic and its name ends with !:


# counter: eight tasks add to one shared atomic total
new count = 0 :Atomic(:Integer);

procedure add(n: Integer) is
  let count += n;
return;

function total!() => (@result: Integer) is
  let result := count;
return;

concurrent aspect adder is
  process main(times: Integer) is
    for i in (1..times) do
      add(1);
    done;
  return;
end adder;

driver counter is
  process main is
    parallel
    do
      for i in (1..8) do
        start adder(1000);
      done;
    done;
    print "total: {total!()}";   ** total: 8000, at every run
  return;
end counter;

Rules

  • A macro has exactly one driver; it runs like a driver and its process main takes the command-line arguments.
  • Line 1 may be #!, so the file runs as a command on Unix (./pipeline.eve), or a # title. A #! file without a driver is a free script.
  • Only declarations at the top level: a statement outside a declaration is an error.
  • The aspects of a macro are private: no other driver can apply or start them. A macro exports nothing and can not be imported.
  • An aspect named in apply or start is searched first in the macro, then in asp/ and at the project root. An aspect of the macro with the name of an aspect file of the project is an error, so the reader never wonders which one runs.
  • Apart from the shared declarations, the aspects follow the rules of Aspects and of the parallel groups.

Compile errors

MistakeMessage
two drivers in one filea macro has only one driver
a statement at the top levela statement outside a declaration in a macro
aspects or shared declarations, no driver (no #!)a macro needs a driver
a concurrent aspect uses a plain shared variablethe concurrent aspect 'adder' uses 'count', which is not thread safe
a concurrent aspect calls a subprogram that changes one… calls 'bump', which is not thread safe

Which one to choose

A program usually grows in three steps, and each step keeps the code of the one before:

  1. Free script: a few statements to try an idea or to do a small job.
  2. Eve macro: when the script needs functions, classes, error recovery or aspects, move the statements into process main of a driver and write the rest as shared declarations and aspects, in the same file.
  3. Project: when the file grows too long, or other programs need its aspects or its code, move the aspects to asp/ and the shared code to a module in lib/; the driver stays as it is.

Read next: Modules