aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2020-03-02 19:08:48 +0000
committers-ol <s-ol@users.noreply.github.com>2020-03-02 19:09:56 +0000
commitc26b90317cb67ddcf71237e1d2f5fad87a5a3191 (patch)
tree3577416ff058cbd5e955c33693684a2b78c0c364
parentfind IO via Result tree, not Registry (diff)
downloadalive-c26b90317cb67ddcf71237e1d2f5fad87a5a3191.tar.gz
alive-c26b90317cb67ddcf71237e1d2f5fad87a5a3191.zip
document more interfaces
-rw-r--r--README.md13
-rw-r--r--core/base.moon225
-rw-r--r--core/cell.moon4
-rw-r--r--core/init.moon8
-rw-r--r--lib/debug.moon2
-rw-r--r--lib/gui.moon76
-rw-r--r--lib/logic.moon2
-rw-r--r--lib/math.moon2
-rw-r--r--lib/midi/core.moon2
-rw-r--r--lib/midi/init.moon2
-rw-r--r--lib/midi/launchctl.moon3
-rw-r--r--lib/osc.moon2
-rw-r--r--lib/pilot.moon2
-rw-r--r--lib/random.moon2
-rw-r--r--lib/sc.moon2
-rw-r--r--lib/string.moon2
-rw-r--r--lib/time.moon2
-rw-r--r--lib/util.moon3
-rw-r--r--spec/core/scope_spec.moon3
-rw-r--r--spec/core/value_spec.moon3
20 files changed, 172 insertions, 188 deletions
diff --git a/README.md b/README.md
index 0cd3910..518dad9 100644
--- a/README.md
+++ b/README.md
@@ -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'