when
is an event-based programming language based on JavaScript, with a few key differences.
when
is not fully procedural, and execution can flow non-linearly through the source code.
A program’s state
consists of:
- Top-level/global variables, persisted as part of the state.
- Tick counter (
history.tick
): A special property that is automatically incremented with every new tick. Marks the currently executing tick.
MEMA stands for: Match, Evaluate, Mutate, and Advance.
when
implements a loop that constantly evaluates a set of rules (condition queue
). Every iteration of this loop is called a tick
, when a condition evaluates to true
, the body associated with the conditon is evaluated, appending actions
to an action queue
that is then executed against the current state
to mutate it into the next state
and advance.
The goal of the MEMA loop is to move execution forward by mutating the current state
.
A globally accessible object (history
) that represents a queue which records the program‘s state as the program advances. The state can be rewinded at any time to a previously recorded state using the history.rewind(T-n)
method.
history.rewind(2)
will cause the program to rewind by two full ticks (the tick counter will be decremented as needed).
Rewind accepts a second parameter with optional mutations to apply to variables after rewinding to the past state, history.rewind(2, { rw: true })
will rewind and set the variable rw
to true
.
State history can be erased at any time using history.clear();
.
State recording can be configured or disabled at any time:
history.limit(4); // Only record the most recent 4 states. Discards any stored older states.
history.limit(0); // No further state recording allowed. Clears current history.
console.log(history.tick) // Access the current tick counter!
Similar to the if
keyword, the when
keyword evaluates a conditional statement, and any condition placed in the trailing parenthesis is evaluated at the start of the next tick. If the condition evaluates to a truthy value, the associated code block is executed.
Here‘s an example of a when
program that executes for 10 ticks before it exits.
let x = 0;
when(true) {
/**
* This code block executes on every loop iteration, because the condition
* statement always evaluates to true.
* It will increment `x` on every tick.
*/
x++;
}
when(x >= 10) {
/**
* This code block will only be evaluated when `x` is
* greater than or equal to 10.
* It will log a message to the console and exit the program.
*/
console.log("done");
exit();
}
A program will be parsed procedurally from top to bottom, any when
statements encountered will enqueue a condition (and a the associated block) to the condition queue
.
// The code below will execute procedurally at the start of the the program.
// An import statement will immediately evaluate the imported file.
// Any `when` statements encountered in that file will be enqueued sequentially
// to the `condition queue`, in order of appearance.
import './rule';
let x = 0, y = 0;
// Defining a function works like JavaScript.
function test() {
return x * y > 0;
}
// Immediately print the current tick number, currently 0.
console.log(`tick ${history.tick}`);
// This condition will be enqueued after any `when` statement from the imported file above.
when(x) {
// the code here will not execute proceduarlly, it will only be executed if the above rule matches (`x` is truthy);
y++;
}
// This condition will be enqueued next, and so on.
when(y % 2) {
x++;
}
// This condition will evaluate the return value of `test()` on every tick!
// Note that `test()` will not be called immediately,
// as the supplied expression will only be evaluated at the start of
// the next tick.
when(test()) {
x += y; // Mutate the current state
}
// this procedural code will be executed during `tick 0`,
// which consists of evaluating the input code.
x = y = 1;
// Executes every tick at the very end of coniditon evaluation.
when(true) {
// Print the current tick number and a snapshot of the current
// state to the console. (this snapshot is read-only, taken before
// any mutations occur within the current tick!)
console.log(`tick ${history.tick}:`, history.state);
if (history.tick == 10) {
// rewind on the tenth tick, restarting the whole program.
// Note that the first real tick is tick 1!
history.rewind(10);
// Nothing below the above line will be ever executed!
// Rewind returns immediately!
console.log('You should never see this!');
}
}
// End of file, parsing stops here.
//
// At this point, the MEMA loop will begin evaluating conditions in
// order of definition and executing blocks as necessary.
//
// Note that the MEMA loop begins with the tick counter set to 1.
As you may have noticed, this whole pattern/architecture is based on DNA gene expression. It is a well established pattern that we can attempt to adapt for our own use case.
In essence, DNA is collection of conditonally restricted blocks (genes) that only execute (get expressed) when a condition (activation region) evaluates to true.