aboutsummaryrefslogtreecommitdiffstats
path: root/core/base.moon
diff options
context:
space:
mode:
Diffstat (limited to 'core/base.moon')
-rw-r--r--core/base.moon298
1 files changed, 0 insertions, 298 deletions
diff --git a/core/base.moon b/core/base.moon
deleted file mode 100644
index 7962545..0000000
--- a/core/base.moon
+++ /dev/null
@@ -1,298 +0,0 @@
--- base definitions for extensions
-import Value from require 'core.value'
-import Result from require 'core.result'
-import match from require 'core.pattern'
-
-unpack or= table.unpack
-
--- an incoming side-effect adapter, polled by the main event loop to pump
--- events into the dataflow graph.
---
--- subclasses must implement this interface:
---
--- :new() - construct a new instance
---
--- must prepare the instance for :dirty().
---
--- :tick() - poll for changes
---
--- called every frame by the event loop to update internal state.
---
--- :dirty() - whether this adapter requires processing
---
--- must return a boolean indicating whether `Op`s that refer to this instance
--- via `IOInput` should be notified (via `Op:tick()`). May be called multiple
--- multiple times. May be called before :tick() on the first frame after
--- construction.
---
-class IO
- -- called in the main event loop
- tick: =>
-
- -- whether a tree update is necessary
- dirty: =>
-
--- a persistent expression Operator
---
--- subclasses must implement this interface:
---
--- :new() - construct a new instance
---
--- the super-constructor can be used to construct a `Value` instance in @out.
---
--- :setup(inputs, scope) - parse arguments and patch self
---
--- called once every eval-cycle. `inputs` is a list of `Result`s that are the
--- argument to this op. The `inputs` have to be wrapped in `Input` instances
--- to define update behaviour. Use `match` to parse them, then delegate to
--- super to patch the `Input` instances.
---
--- :tick(setup) - handle incoming events and update @out
---
--- called once per frame if any inputs are dirty. Some `Input`s (like
--- `ValueInput`) have special behaviour immediately after :setup(). You can
--- detect this using the `setup` parameter, which is true the first time
--- :tick() is called after :setup(). :tick() is not called immediately after
--- :setup() if no `@inputs` are dirty. Update @out here.
---
--- :destroy() - called when the Op is destroyed
---
--- .out - a `Value` instance representing this Op's computed output value.
---
--- @out must be set to a `Value` instance once :setup() finishes. @out must
--- not change type, be removed or replaced outside of :new() and :setup().
--- @out should have a value assigned via :set() or the `Value` constructor
--- once :tick(true) is called. If @out's value is not initialized in :new()
--- or :setup(), the implementation must make sure :tick(true) is called at
--- least on the first eval-cycle the Op goes through, e.g. by using a
--- `ValueInput`.
---
-class Op
--- super-implementations for extensions
- -- if `type` is passed, an output stream is instantiated.
- -- if `init` is passed, the stream is initialized to that Lua value.
- -- it is okay not to use this and create the output stream in :setup() if the
- -- type is not known at this time.
- new: (type, init) =>
- if type
- @out = Value type, init
-
- -- setups previous @inputs, if any, with the new inputs, and writes them to
- -- `@inputs`. The `inputs` table can be nested with string or integer keys,
- -- but all leaf-entries must be `Input` instances. It must not contain loops
- -- or instances of other classes.
- setup: do
- do_setup = (old, cur) ->
- for k, cur_val in pairs cur
- old_val = old and old[k]
-
- -- are these inputs or nested tables?
- cur_plain = cur_val and not cur_val.__class
- old_plain = old_val and not old_val.__class
-
- if cur_plain and old_plain
- -- both are tables, recurse
- do_setup old_val, cur_val
- elseif not (cur_plain or old_plain)
- -- both are streams (or nil), setup them
- cur_val\setup old_val
-
- (inputs) =>
- old_inputs = @inputs
- @inputs = inputs
- do_setup old_inputs, @inputs
-
- tick: =>
- destroy: =>
-
--- utilities
- -- iterate over the (potentially nested) inputs table
- all_inputs: do
- do_yield = (table) ->
- for k, v in pairs table
- if v.__class
- coroutine.yield v
- else
- do_yield v
-
- => coroutine.wrap -> do_yield @inputs
-
- unwrap_all: do
- do_unwrap = (value) ->
- if value.__class
- value\unwrap!
- else
- {k, do_unwrap v for k,v in pairs value}
-
- => do_unwrap @inputs
-
- assert_types: (...) =>
- num = select '#', ...
- assert #@inputs >= num, "argument count mismatch"
- @assert_first_types ...
-
- assert_first_types: (...) =>
- num = select '#', ...
- for i = 1, num
- expect = select i, ...
- assert @inputs[i].type == expect, "expected argument #{i} of #{@} to be of type #{expect} but found #{@inputs[i]}"
-
--- static
- __tostring: => "<op: #{@@__name}>"
- __inherited: (cls) => cls.__base.__tostring = @__tostring
-
--- a builtin / special form / cell-evaluation strategy.
---
--- responsible for quoting/evaluating subexpressions, instantiating and patching
--- Ops updating the current Scope, etc. See core.builtin and core.invoke for
--- many examples.
-class Action
- -- head: the (:eval'd) head of the Cell to evaluate (a Const)
- -- tag: the Tag of the expression to evaluate
- new: (head, @tag) =>
- @patch head
-
- -- * eval args
- -- * perform scope effects
- -- * patch nested exprs
- -- * return runtime-tree value
- eval: (scope, tail) => error "not implemented"
-
- -- free resources
- destroy: =>
-
- -- update this instance for :eval() with new head
- -- if :patch() returns false, this instance is :destroy'ed and recreated
- -- instead must *not* return false when called after :new()
- -- only considered if Action types match
- patch: (head) =>
- if head == @head
- true
-
- @head = head
-
--- static
- -- find & patch the action for the expression with Tag 'tag' if it exists,
- -- and is compatible with the new Cell contents, otherwise instantiate it.
- -- register the action with the tag, evaluate it and return the Result
- @eval_cell: (scope, tag, head, tail) =>
- last = tag\last!
- compatible = last and
- (last.__class == @) and
- (last\patch head) and
- last
-
- L\trace if compatible
- "reusing #{last} for #{tag} <#{@__name} #{head}>"
- else if last
- "replacing #{last} with new #{tag} <#{@__name} #{head}>"
- else
- "initializing #{tag} <#{@__name} #{head}>"
-
- action = if compatible
- tag\keep compatible
- compatible
- else
- last\destroy! if last
- with next = @ head, tag
- tag\replace next
-
- action\eval scope, tail
-
- __tostring: => "<#{@@__name} #{@head}>"
- __inherited: (cls) => cls.__base.__tostring = @__tostring
-
--- an ALV function definition
---
--- when called, expands its body with params bound to the fn arguments
--- (see core.invoke.fn-invoke)
-class FnDef
- -- params: sequence of (:quote'd) symbols, each naming a function parameter
- -- body: (:quote'd) expression the function evaluates to
- -- scope: the lexical scope the function was defined in (closure)
- new: (@params, @body, @scope) =>
-
- __tostring: =>
- "(fn (#{table.concat [p\stringify! for p in *@params], ' '}) ...)"
-
--- an update scheduling policy for `Op`.
---
--- subclasses must implement this interface:
---
--- :new(value) - create an instance
---
--- `value` is either a `Value` or a `Result` instance and should be unwrapped.
---
--- :setup(prev) - copy state from old instance
---
--- called by `Op:setup()` with another `Input` instance or `nil` once this instance is
--- registered. Must prepare this instance for :dirty().
---
---- :dirty() - whether this input requires processing
---
--- must return a boolean indicating whether `Op`s that refer to this instance
--- should be notified (via `Op:tick()`).
---
--- :finish_setup() - leave setup state
---
--- called after the Op has completed (or skipped) its first `Op:tick()` after
--- `Op:setup()`. Must prepare this instance for dataflow operation.
---
-class Input
- new: (value) =>
- assert value, "nil passed to Input: #{value}"
- @stream = switch value.__class
- when Result
- assert value.value, "Input from result without value!"
- when Value
- value
- else
- error "Input from unknown value: #{value}"
-
- setup: (previous) =>
-
- finish_setup: =>
- dirty: => @stream\dirty!
- unwrap: => @stream\unwrap!
- type: => @stream.type
-
- __call: => @stream\unwrap!
- __tostring: => "#{@@__name}:#{@stream}"
- __inherited: (cls) =>
- cls.__base.__call = @__call
- cls.__base.__tostring = @__tostring
-
--- Never marked dirty. Use this for input streams that are only read when
--- another input fires.
-class ColdInput extends Input
- dirty: => false
-
--- Marked dirty for the setup-tick if old and new stream differ in current
--- value. This is the most common `Input` strategy. Should be used whenever a
--- value denotes state.
-class ValueInput extends Input
- setup: (old) => @dirty_setup = not old or @stream != old.stream
- finish_setup: => @dirty_setup = false
- dirty: => @dirty_setup or @stream\dirty!
-
--- Only marked dirty if the input stream itself is dirty. Should be used
--- whenever a value denotes a momentary event or impulse.
-class EventInput extends Input
-
--- Marked dirty when an IO object is dirty. Must be used for IO values.
-class IOInput extends Input
- impure: true
- dirty: => @stream\unwrap!\dirty!
-
-{
- :IO
- :Op
- :Action
- :FnDef
-
- :ValueInput, :EventInput, :IOInput, :ColdInput
-
- -- redundant exports, to keep anything an extension might need in one import
- :Value, :Result
- :match
-}