class Novika::Engine

Overview

An engine object is responsible for managing a continuations block.

Continuations block consists of continuation blocks.

Canonical continuation blocks themselves contain two blocks (see Engine.cont):

Engine#schedule is used to create a continuation block given a Schedulable object (usually a Form, and in rarer cases an Entry) and a stack block. It then adds the continuation block to the continuations block -- effectively scheduling it for execution on the next exhaust loop cycle.

Note that there are two other methods linked with execution and implemented by all forms: on_open, and on_parent_open. They perform whatever action the form wants rather than simply scheduling it to be performed some time in the future. Namely, on_open is invoked whenever the form at hand is itself the target of opening (aka execution, aka evaluation), and on_parent_open is invoked when a block containing the form at hand (its parent block) is the target of opening.

An engine's exhaust loop is where most of the magic happens. It is organized very much like the fetch-decode-execute cycle in CPUs.

For fetch, the engine finds the top (see Block#top) continuation block, then finds the top form on the code block, and invokes the on_parent_open method on it.

This method is analogous to decoding followed by execution. The form is free to choose how it wants to make sense of itself, given an engine. Some forms (e.g. words) end up scheduling new continuation blocks on_parent_open, making the engine go through them first.

After the cursor of the active block hits the end, Engine drops (see Block#drop) the continuation block (thereby closing the code block).

caps = CapabilityCollection.with_default.enable_all
block = Block.new(caps.block).slurp("1 2 +")
stack = Block.new

engine = Engine.new(caps)
engine.schedule(block, stack)
engine.exhaust

puts stack # [ 3 ]

# Or, shorter:

caps = CapabilityCollection.with_default.enable_all
block = Block.new(caps.block).slurp("1 2 +")

puts Engine.exhaust(caps, block) # [ 3 ]

Defined in:

novika/engine.cr

Constant Summary

C_BLOCK_AT = 0

Index of the code block in a continuation block.

C_STACK_AT = 1

Index of the stack block in a continuation block.

MAX_CONTS = 1024

Maximum amount of scheduled continuations in #conts. After passing this number, Error is raised to bring attention to such dangerous depth.

MAX_ENGINES = 1024

Maximum number of engines that can be created.

This is for safety reasons only, particularly to prevent infinite recursion in e.g. asserts which are called from Crystal rather than Novika, thereby circumventing MAX_CONTS checks. See Engine.count.

Constructors

Class Method Summary

Instance Method Summary

Constructor Detail

def self.new(capabilities : CapabilityCollection, &) #

Yields an instance of Engine.


def self.push(engine : Engine) : Engine #

Pushes engine onto the engine stack.


Class Method Detail

def self.cont(*, block, stack) #

Creates and returns a canonical continuation block.

A continuation block must include two blocks: the first is called simply the block (found at C_BLOCK_AT), and the second is called the stack block (found at C_STACK_AT).


def self.current #

Returns the current engine. Raises a BUG exception if there is no current engine.


def self.exhaust(capabilities : CapabilityCollection, schedulable, stack = nil) : Block #

Schedules schedulable and exhausts immediately. Returns the resulting stack (creates one if nil).

Useful for when you need the result of schedulable immediately.

For details see Engine#schedule.

caps = CapabilityCollection.with_default.enable_all
result = Engine.exhaust(caps, Block.new(caps.block).slurp("1 2 +"))
result.top # 3 : Novika::Decimal

def self.exhaust!(capabilities : CapabilityCollection, schedulable, stack = nil) : Block #

Schedules schedulable and exhausts immediately. Returns the resulting stack (creates one if nil).

Useful for when you need the result of schedulable immediately.

For details see Engine#schedule!.

caps = CapabilityCollection.with_default.enable_all
result = Engine.exhaust(caps, Block.new(caps.block).slurp("1 2 +"))
result.top # 3 : Novika::Decimal

def self.pop(engine : Engine) : Engine | Nil #

Pops engine from the engine stack. Raises a BUG exception (and does not pop!) if the current engine is not engine (or if it is absent).


def self.push(caps : CapabilityCollection) #

Pushes a new engine with the given capability collection caps.

Make sure that you .pop it yourself or that you know what you're doing!


def self.trackers #

Holds an array of exhaust tracker objects associated with all instances of Engine. These objects intercept forms before/after opening in Engine#exhaust. This e.g. allows frontends to analyze/track forms and/or matching blocks.


Instance Method Detail

def block #

Returns the block of the active continuation.


def capabilities : CapabilityCollection #

Returns the capability collection used by this engine.


def cont #

Returns the active continuation.


def conts : Novika::Block #

Holds the continuations block (aka continuations stack).


def conts=(conts : Novika::Block) #

Holds the continuations block (aka continuations stack).


def die(*args, **options) #

See Form#die.


def die(*args, **options, &) #

See Form#die.


def drop_until_death_handler?(avoid_prototype = nil) #

Returns the relevant death handler, or nil. Avoids handlers whose prototype is avoid_prototype.

To find the relevant death handler, the continuations block is inspected right-to-left (back-to-front); each code block is then asked to retrieve Word::DIED using Block#at?. Regardless of the result, the continuation block is then dropped.

If succeeded in retrieving Word::DIED, converts the resulting entry to block (does not distinguish between openers and pushers). Returns that block.

If all continuations were exhausted and no Hook.died had been found, returns nil.


def each_active_block(&) #

Yields active blocks, starting from the oldest active block up to the current active block.


def execute(form : Form) #

def exhaust #

Exhausts all scheduled continuations, starting from the topmost (see Block#top) continuation in #conts.


def schedule(schedulable : Schedulable, stack : Block) #

def schedule!(schedulable : Schedulable, stack : Block) #

def schedule!(other : Block) #

Main authorized point for adding continuations unsafely. Returns self.

Provides protection from continuations stack overflow.

Adding to #conts (the unauthorized way) does not protect one from continuations stack overflow, and therefore from a memory usage explosion.


def schedule!(*, block : Block, stack : Block) #

Schedules a continuation with the given block and stack.


def stack #

Returns the stack block of the active continuation.