Skip to content

Instantly share code, notes, and snippets.

@voodooattack
Last active September 17, 2018 21:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save voodooattack/ccb1d18112720a8de5be660dbb80541c to your computer and use it in GitHub Desktop.
Save voodooattack/ccb1d18112720a8de5be660dbb80541c to your computer and use it in GitHub Desktop.
When: A proposition for a new, event-based programming language based on JavaScript.

when - event-based programming

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.

Program state

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 loop

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.

State history

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!

when keyword

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();
}

Structure of a program

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.

Sidenote

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment