diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-03-02 19:08:48 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2020-03-02 19:09:56 +0000 |
| commit | c26b90317cb67ddcf71237e1d2f5fad87a5a3191 (patch) | |
| tree | 3577416ff058cbd5e955c33693684a2b78c0c364 | |
| parent | find IO via Result tree, not Registry (diff) | |
| download | alive-c26b90317cb67ddcf71237e1d2f5fad87a5a3191.tar.gz alive-c26b90317cb67ddcf71237e1d2f5fad87a5a3191.zip | |
document more interfaces
| -rw-r--r-- | README.md | 13 | ||||
| -rw-r--r-- | core/base.moon | 225 | ||||
| -rw-r--r-- | core/cell.moon | 4 | ||||
| -rw-r--r-- | core/init.moon | 8 | ||||
| -rw-r--r-- | lib/debug.moon | 2 | ||||
| -rw-r--r-- | lib/gui.moon | 76 | ||||
| -rw-r--r-- | lib/logic.moon | 2 | ||||
| -rw-r--r-- | lib/math.moon | 2 | ||||
| -rw-r--r-- | lib/midi/core.moon | 2 | ||||
| -rw-r--r-- | lib/midi/init.moon | 2 | ||||
| -rw-r--r-- | lib/midi/launchctl.moon | 3 | ||||
| -rw-r--r-- | lib/osc.moon | 2 | ||||
| -rw-r--r-- | lib/pilot.moon | 2 | ||||
| -rw-r--r-- | lib/random.moon | 2 | ||||
| -rw-r--r-- | lib/sc.moon | 2 | ||||
| -rw-r--r-- | lib/string.moon | 2 | ||||
| -rw-r--r-- | lib/time.moon | 2 | ||||
| -rw-r--r-- | lib/util.moon | 3 | ||||
| -rw-r--r-- | spec/core/scope_spec.moon | 3 | ||||
| -rw-r--r-- | spec/core/value_spec.moon | 3 |
20 files changed, 172 insertions, 188 deletions
@@ -12,25 +12,16 @@ text-based language and works without special editor support. - [luafilesystem][lfs]: `luarocks install luafilesystem` - [LPeg][lpeg]: `luarocks install lpeg` - [osc][osc]: `luarocks install osc` -- [socket][socket]: `luarocks install luasocket` (not required in love2d) -- [system][system]: `luarocks install luasystem` (not required in love2d) +- [socket][socket]: `luarocks install luasocket` +- [system][system]: `luarocks install luasystem` ## running -headless / standalone: - $ moon init.moon <session.alv> -or in [LÖVE][love2d] (make sure to install the required modules for lua5.1): - - $ love . <session.alv> - -running in LÖVE adds the additional `gui` module. See [`lib/gui.moon`](lib/gui.moon). - [moonscript]: https://moonscript.org/ [lfs]: https://keplerproject.github.io/luafilesystem/ [lpeg]: http://www.inf.puc-rio.br/~roberto/lpeg/ [osc]: https://github.com/lubyk/osc [system]: https://github.com/o-lim/luasystem [socket]: http://w3.impa.br/~diego/software/luasocket/ -[love2d]: https://love2d.org/ diff --git a/core/base.moon b/core/base.moon index 41ddd4c..8bd6271 100644 --- a/core/base.moon +++ b/core/base.moon @@ -1,58 +1,29 @@ -- base definitions for extensions import Value, Result from require 'core.value' +import match from require 'core.pattern' unpack or= table.unpack -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}" - - merge: (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 - --- ColdInput scheduling policy +-- an incoming side-effect adapter, polled by the main event loop to pump +-- events into the dataflow graph. -- --- never marked dirty -class ColdInput extends Input - dirty: => false - --- ValueInput scheduling policy +-- subclasses must implement this interface: -- --- during setup, only marked dirty if old and new stream differ in value -class ValueInput extends Input - merge: (old) => @dirty_setup = not old or @stream != old.stream - finish_setup: => @dirty_setup = false - dirty: => @dirty_setup or @stream\dirty! - --- EventInput scheduling policy +-- :new() - construct a new instance -- --- only marked dirty if the input stream itself is dirty -class EventInput extends Input - --- IOInput scheduling policy +-- 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. -- --- lifts streams of IO objects to events -class IOInput extends Input - impure: true - dirty: => @stream\unwrap!\dirty! - class IO -- called in the main event loop tick: => @@ -62,20 +33,55 @@ class IO -- a persistent expression Operator -- --- accepts Const or Stream inputs and produces a Stream output +-- 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) => - @impulses = {} - if type @out = Value type, init - -- (re)-initialize this Op with the given inputs - -- after this method finishes, :tick(true) is called once, after which - -- @impulses and @out have to be set and may not change until :setup() - -- is called again. + -- 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_merge = (old, cur) -> + do_setup = (old, cur) -> for k, cur_val in pairs cur old_val = old and old[k] @@ -85,16 +91,20 @@ class Op if cur_plain and old_plain -- both are tables, recurse - do_merge old_val, cur_val + do_setup old_val, cur_val elseif not (cur_plain or old_plain) - -- both are streams (or nil), merge them - cur_val\merge old_val + -- both are streams (or nil), setup them + cur_val\setup old_val (inputs) => old_inputs = @inputs @inputs = inputs - do_merge old_inputs, @inputs + do_setup old_inputs, @inputs + + tick: => + destroy: => +-- utilities -- iterate over the (potentially nested) inputs table all_inputs: do do_yield = (table) -> @@ -106,15 +116,6 @@ class Op => coroutine.wrap -> do_yield @inputs - -- called once per frame if any inputs or impulses are dirty, and once - -- immediately after :setup(). 'first' will be true in the latter case. - -- Should update @out. - tick: (first) => - - -- called once when the Op is destroyed - destroy: => - --- utilities unwrap_all: do do_unwrap = (value) -> if value.__class @@ -139,19 +140,17 @@ class Op __tostring: => "<op: #{@@__name}>" __inherited: (cls) => cls.__base.__tostring = @__tostring --- a builtin / special form / cell-evaluation strategy +-- a builtin / special form / cell-evaluation strategy. -- --- responsible for quoting/evaluating subexpressions, --- instantiating and patching Ops, --- updating the current Scope, --- etc. +-- 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 --- AST interface -- * eval args -- * perform scope effects -- * patch nested exprs @@ -202,23 +201,97 @@ class Action __tostring: => "<#{@@__name} #{@head}>" __inherited: (cls) => cls.__base.__tostring = @__tostring --- a ALV function definition +-- 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 - -- scoe: the lexical scope the function was defined in (closure) + -- 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! + { - :ValueInput, :EventInput, :IOInput, :ColdInput :IO :Op :Action :FnDef + + :ValueInput, :EventInput, :IOInput, :ColdInput + + -- redundant exports, to keep anything an extension might need in one import + :Value, :Result + :match } diff --git a/core/cell.moon b/core/cell.moon index c052d46..724b74e 100644 --- a/core/cell.moon +++ b/core/cell.moon @@ -23,8 +23,8 @@ class Cell Action\eval_cell scope, @tag, head, @tail! - -- quoting a Cell recursively quotes children, but preserves identity this - -- means that a quoted Cell may only be 'used' once. use :clone() otherwise. + -- quoting a Cell recursively quotes children, but preserves identity. This + -- means that a quoted Cell may only be 'used' once. Use :clone() otherwise. quote: (scope) => children = [child\quote scope for child in *@children] Cell @tag, children, @white diff --git a/core/init.moon b/core/init.moon index 6a8c5d4..73f63f9 100644 --- a/core/init.moon +++ b/core/init.moon @@ -1,9 +1,5 @@ L or= setmetatable {}, __index: => -> -import Op, IO, Action, FnDef, EventInput, ValueInput, IOInput, ColdInput - from require 'core.base' -import match from require 'core.pattern' - import Value, Result, load_ from require 'core.value' import Scope from require 'core.scope' load_! @@ -19,10 +15,6 @@ globals = Scope.from_table require 'core.builtin' { :Value, :Result :Cell, :RootCell - - :Op, :IO, :Action, :FnDef - :EventInput, :ValueInput, :IOInput, :ColdInput, - :match :Scope :Registry, :Tag diff --git a/lib/debug.moon b/lib/debug.moon index e098800..1ce56cc 100644 --- a/lib/debug.moon +++ b/lib/debug.moon @@ -1,4 +1,4 @@ -import Op, ValueInput, EventInput, match from require 'core' +import Op, ValueInput, EventInput, match from require 'core.base' class out extends Op @doc: "(out [name-str?] value) - log value to the console" diff --git a/lib/gui.moon b/lib/gui.moon deleted file mode 100644 index 474de5c..0000000 --- a/lib/gui.moon +++ /dev/null @@ -1,76 +0,0 @@ -assert love, "this module only works from within love2d!" -{ graphics: lg, keyboard: lk } = love - -import Op from require 'core' -import Copilot from require 'copilot' -import Logger from require 'logger' - -class out extends Op - @doc: "(out name-str value) - show the output - -display value as a bar" - - setup: (name, @chld) => - @@instances[@name] = nil if @name - @name = name\const!\unwrap! - @@instances[@name] = @ - - update: (dt) => - @value = @chld\unwrap! - - destroy: => - @@instances[@name] = nil - - MARGIN = 16 - WIDTH = 24 - HEIGHT = 120 - @instances: {} - @draw_all: -> - outs = [name for name in pairs @@instances] - table.sort outs - - x = MARGIN - lg.setColor 1, 1, 1 - for name in *outs - value = @@instances[name].value * HEIGHT - lg.rectangle 'line', x, MARGIN, WIDTH, HEIGHT - lg.rectangle 'fill', x, MARGIN + HEIGHT - value, WIDTH, value - lg.print name, x, MARGIN + HEIGHT + MARGIN - x += WIDTH + MARGIN - -class key extends Op - @doc: "(key name-str) - gate from keypress" - - setup: (@key) => - assert @key - @out = Stream 'bool', false - @out - - update: (dt) => - @out\set lk.isDown @key\unwrap! - -arguments, k = {} -for a in *arg - if match = a\match '^%-%-(.*)' - k = match - arguments[k] = true - elseif k - arguments[k] = a - k = nil - else - table.insert arguments, a - -Logger.init arguments.log - -copilot = Copilot arguments[#arguments] - -love.update = (dt) -> - copilot\update dt - -love.draw = -> - out.draw_all! - -{ - :out - :key -} diff --git a/lib/logic.moon b/lib/logic.moon index b4c6cf7..adff5ee 100644 --- a/lib/logic.moon +++ b/lib/logic.moon @@ -1,4 +1,4 @@ -import Op, ValueInput, match from require 'core' +import Op, ValueInput, match from require 'core.base' all_same = (first, list) -> for v in *list diff --git a/lib/math.moon b/lib/math.moon index 4d01741..b8b4361 100644 --- a/lib/math.moon +++ b/lib/math.moon @@ -1,4 +1,4 @@ -import Op, Value, ValueInput, match from require 'core' +import Op, Value, ValueInput, match from require 'core.base' unpack or= table.unpack class ReduceOp extends Op diff --git a/lib/midi/core.moon b/lib/midi/core.moon index 156feb2..627aa2e 100644 --- a/lib/midi/core.moon +++ b/lib/midi/core.moon @@ -1,4 +1,4 @@ -import IO, Op, Registry, ValueInput, match from require 'core' +import IO, Op, Registry, ValueInput, match from require 'core.base' import RtMidiIn, RtMidiOut, RtMidi from require 'luartmidi' import band, bor, lshift, rshift from require 'bit32' diff --git a/lib/midi/init.moon b/lib/midi/init.moon index daf0088..01af84f 100644 --- a/lib/midi/init.moon +++ b/lib/midi/init.moon @@ -1,4 +1,4 @@ -import Value, Op, ValueInput, IOInput, match from require 'core' +import Value, Op, ValueInput, IOInput, match from require 'core.base' import input, output, inout, apply_range from require 'lib.midi.core' class gate extends Op diff --git a/lib/midi/launchctl.moon b/lib/midi/launchctl.moon index 1acafdc..495743b 100644 --- a/lib/midi/launchctl.moon +++ b/lib/midi/launchctl.moon @@ -1,4 +1,5 @@ -import Value, Op, ValueInput, EventInput, IOInput, match from require 'core' +import Value, Op, ValueInput, EventInput, IOInput, match + from require 'core.base' import apply_range from require 'lib.midi.core' import bor, lshift from require 'bit32' diff --git a/lib/osc.moon b/lib/osc.moon index e73a982..c459a66 100644 --- a/lib/osc.moon +++ b/lib/osc.moon @@ -1,4 +1,4 @@ -import Op, ValueInput, EventInput, ColdInput, match from require 'core' +import Op, ValueInput, EventInput, ColdInput, match from require 'core.base' import pack from require 'osc' import dns, udp from require 'socket' diff --git a/lib/pilot.moon b/lib/pilot.moon index a442b77..7251f55 100644 --- a/lib/pilot.moon +++ b/lib/pilot.moon @@ -1,4 +1,4 @@ -import Op, EventInput, ValueInput, ColdInput, match from require 'core' +import Op, EventInput, ValueInput, ColdInput, match from require 'core.base' import udp from require 'socket' conn = udp! diff --git a/lib/random.moon b/lib/random.moon index 9e5e62b..88fdb89 100644 --- a/lib/random.moon +++ b/lib/random.moon @@ -1,4 +1,4 @@ -import Op, Value, ValueInput, EventInput, match from require 'core' +import Op, Value, ValueInput, EventInput, match from require 'core.base' apply_range = (range, val) -> if range\type! == 'str' diff --git a/lib/sc.moon b/lib/sc.moon index b30e30d..bae252a 100644 --- a/lib/sc.moon +++ b/lib/sc.moon @@ -1,4 +1,4 @@ -import Op, EventInput, ColdInput, match from require 'core' +import Op, EventInput, ColdInput, match from require 'core.base' import pack from require 'osc' import dns, udp from require 'socket' diff --git a/lib/string.moon b/lib/string.moon index 8c10c5d..2753381 100644 --- a/lib/string.moon +++ b/lib/string.moon @@ -1,4 +1,4 @@ -import Op, ValueInput from require 'core' +import Op, ValueInput from require 'core.base' class str extends Op @doc: "(str v1 [v2]...) diff --git a/lib/time.moon b/lib/time.moon index d44013e..5823d80 100644 --- a/lib/time.moon +++ b/lib/time.moon @@ -1,5 +1,5 @@ import Registry, Value, IO, Op, ValueInput, IOInput, match - from require 'core' + from require 'core.base' import monotime from require 'system' class Clock extends IO diff --git a/lib/util.moon b/lib/util.moon index 6ada851..65bab65 100644 --- a/lib/util.moon +++ b/lib/util.moon @@ -1,4 +1,5 @@ -import Op, Value, ValueInput, EventInput, ColdInput, match from require 'core' +import Op, Value, ValueInput, EventInput, ColdInput, match + from require 'core.base' all_same = (list) -> for v in *list[2,] diff --git a/spec/core/scope_spec.moon b/spec/core/scope_spec.moon index 563f243..6d7d754 100644 --- a/spec/core/scope_spec.moon +++ b/spec/core/scope_spec.moon @@ -1,4 +1,5 @@ -import Scope, Value, Result, Op from require 'core' +import Scope, Value, Result from require 'core' +import Op from require 'core.base' import Logger from require 'logger' Logger.init 'silent' diff --git a/spec/core/value_spec.moon b/spec/core/value_spec.moon index cda7123..4e56c31 100644 --- a/spec/core/value_spec.moon +++ b/spec/core/value_spec.moon @@ -1,4 +1,5 @@ -import Value, Result, Op, Action, Scope from require 'core' +import Value, Result, Scope from require 'core' +import Op, Action from require 'core.base' import Logger from require 'logger' Logger.init 'silent' |
