diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-02-16 11:47:24 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2020-02-16 11:47:24 +0000 |
| commit | 2953f1e56b408fc26eb54fa65935505dd128ce82 (patch) | |
| tree | 8c7c513ccf03df06c4e9e2e70ba68031d2d0b7a5 | |
| parent | add ==, mod (diff) | |
| download | alive-2953f1e56b408fc26eb54fa65935505dd128ce82.tar.gz alive-2953f1e56b408fc26eb54fa65935505dd128ce82.zip | |
major refactoring: Const, Stream + ResultNode
| -rw-r--r-- | core/base.moon | 69 | ||||
| -rw-r--r-- | core/builtin.moon | 64 | ||||
| -rw-r--r-- | core/cell.moon | 28 | ||||
| -rw-r--r-- | core/const.moon | 136 | ||||
| -rw-r--r-- | core/init.moon | 8 | ||||
| -rw-r--r-- | core/invoke.moon | 50 | ||||
| -rw-r--r-- | core/parsing.moon | 15 | ||||
| -rw-r--r-- | core/registry.moon | 5 | ||||
| -rw-r--r-- | core/scope.moon | 8 | ||||
| -rw-r--r-- | core/value.moon | 157 | ||||
| -rw-r--r-- | lib/debug.moon | 6 | ||||
| -rw-r--r-- | lib/envelope.moon | 13 | ||||
| -rw-r--r-- | lib/gui.moon | 9 | ||||
| -rw-r--r-- | lib/logic.moon | 46 | ||||
| -rw-r--r-- | lib/math.moon | 66 | ||||
| -rw-r--r-- | lib/midi/core.moon | 17 | ||||
| -rw-r--r-- | lib/midi/init.moon | 44 | ||||
| -rw-r--r-- | lib/midi/launchctl.moon | 99 | ||||
| -rw-r--r-- | lib/osc.moon | 11 | ||||
| -rw-r--r-- | lib/pilot.moon | 22 | ||||
| -rw-r--r-- | lib/string.moon | 9 | ||||
| -rw-r--r-- | lib/time.moon | 57 | ||||
| -rw-r--r-- | lib/util.moon | 53 |
23 files changed, 559 insertions, 433 deletions
diff --git a/core/base.moon b/core/base.moon index dcf600a..1b1be5d 100644 --- a/core/base.moon +++ b/core/base.moon @@ -1,15 +1,13 @@ -class Op --- common - new: => - -- (...) => @setup ... +-- base definitions for extensions - get: => @value - getc: => - L\warn "stream #{@} cast to constant" - @value +-- a persistent expression Operator +-- +-- accepts Const or Stream inputs and produces a Stream output +class Op + update: (dt) => --- Value interface - update: => + -- set @out to a Value (Const or Stream) + setup: (...) => destroy: => @@ -17,11 +15,16 @@ class Op __tostring: => "<op: #{@@__name}>" __inherited: (cls) => cls.__base.__tostring = @__tostring - spawn: (Opdef, ...) -> - Opdef ... +-- a builtin / special form / cell-evaluation strategy +-- +-- responsible for quoting/evaluating subexpressions, +-- instantiating and patching Ops, +-- updating the current Scope, +-- etc. class Action --- common + -- 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 @@ -35,9 +38,9 @@ class Action -- free resources destroy: => - -- update this instance for :eavl() with new head - -- if :patch() returns false, this instance is :destroy'ed and recreated instead - -- must *not* return false when called after :new() + -- 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 @@ -46,35 +49,51 @@ class Action @head = head -- static - @get_or_create: (ActionType, head, tag) -> + -- 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 ResultNode + @eval_cell: (scope, tag, head, tail) => last = tag\last! compatible = last and - (last.__class == ActionType) and + (last.__class == @) and (last\patch head) and last L\trace if compatible - "reusing #{last} for #{tag} <#{ActionType.__name} #{head}>" + "reusing #{last} for #{tag} <#{@__name} #{head}>" else if last - "replacing #{last} with new #{tag} <#{ActionType.__name} #{head}>" + "replacing #{last} with new #{tag} <#{@__name} #{head}>" else - "initializing #{tag} <#{ActionType.__name} #{head}>" + "initializing #{tag} <#{@__name} #{head}>" - if compatible + action = if compatible tag\keep compatible compatible else last\destroy! if last - with next = ActionType head, tag + with next = @ head, tag tag\replace next + action\eval scope, tail + __tostring: => "<#{@@__name} #{@head}>" __inherited: (cls) => cls.__base.__tostring = @__tostring +-- a 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) new: (@params, @body, @scope) => __tostring: => - table.concat [p\stringify! for p in *@params], ' ' + "(fn (#{table.concat [p\stringify! for p in *@params], ' '}) ...)" -:Op, :Action, :FnDef +{ + :Op + :Action + :FnDef +} diff --git a/core/builtin.moon b/core/builtin.moon index 97f4457..71beac3 100644 --- a/core/builtin.moon +++ b/core/builtin.moon @@ -1,8 +1,8 @@ +-- builtin special forms import Action, FnDef from require 'core.base' -import Const from require 'core.const' +import ResultNode, Value, Const from require 'core.value' import Cell from require 'core.cell' import Scope from require 'core.scope' -import UpdateChildren from require 'core.invoke' class doc extends Action @doc: "(doc sym) - print documentation in console @@ -13,8 +13,9 @@ prints the docstring for sym in the console" assert #tail == 1, "'doc' takes exactly one parameter" def = L\push tail[1]\eval, scope - L\print "(doc #{tail[1]\stringify!}):\n#{def\getc!.doc}\n" - nil + with ResultNode children: { def } + def = def.value\const!\unwrap! + L\print "(doc #{tail[1]\stringify!}):\n#{def.doc}\n" class def extends Action @doc: "(def sym1 val-expr1 @@ -28,16 +29,15 @@ updates all val-exprs." assert #tail > 1, "'def' requires at least 2 arguments" assert #tail % 2 == 0, "'def' requires an even number of arguments" - values = L\push -> + children = L\push -> return for i=1,#tail,2 name, val_expr = tail[i], tail[i+1] - name = (name\quote scope)\getc 'sym' + name = (name\quote scope)\unwrap 'sym' - val = val_expr\eval scope - scope\set name, Const.wrap_ref val - val + with val_expr\eval scope + scope\set name, .value - UpdateChildren values + ResultNode :children class use extends Action @doc: "(use scope1 [scope2]...) - merge scopes into parent scope @@ -48,12 +48,11 @@ all scopes have to be eval-time constants." eval: (scope, tail) => L\trace "evaling #{@}" for child in *tail - value = L\push child\eval, scope - L\trace @, "merging #{value} into #{scope}" - assert value.type == 'scope', "'use' only works on scopes" - scope\use value\getc 'scope' + result = L\push child\eval, scope + value = result\value_only!\const! + scope\use value\unwrap 'scope', "'use' only works on scopes" - nil + ResultNode! class require_ extends Action @doc: "(require name-str) - require a module @@ -65,10 +64,11 @@ name-str has to be an eval-time constant." L\trace "evaling #{@}" assert #tail == 1, "'require' takes exactly one parameter" - name = L\push tail[1]\eval, scope + result = L\push tail[1]\eval, scope + name = result\value_only!\const! L\trace @, "loading module #{name}" - Const.wrap require "lib.#{name\getc 'str'}" + ResultNode value: Value.wrap require "lib.#{name\unwrap 'str'}" class import_ extends Action @doc: "(import sym1 [sym2]...) - require and define modules @@ -79,12 +79,11 @@ requires modules sym1, sym2, ... and defines them as sym1, sym2, ... in the curr L\trace "evaling #{@}" assert #tail > 0, "'import' requires at least one arguments" - for child in *tail - name = (child\quote scope)\getc 'sym' - scope\set name, Const.wrap require "lib.#{name}" + name = (child\quote scope)\unwrap 'sym' + scope\set name, Value.wrap require "lib.#{name}" - nil + ResultNode! class import_star extends Action @doc: "(import* sym1 [sym2]...) - require and use modules @@ -97,10 +96,10 @@ requires modules sym1, sym2, ... and merges them into the current scope" for child in *tail - name = (child\quote scope)\getc 'sym' - scope\use (Const.wrap require "lib.#{name}")\getc 'scope' + name = (child\quote scope)\unwrap 'sym' + scope\use (Value.wrap require "lib.#{name}")\unwrap 'scope' - nil + ResultNode! class fn extends Action @doc: "(fn (p1 [p2]...) body-expr) - declare a (lambda) function @@ -118,7 +117,7 @@ the symbols p1, p2, ... will resolve to the arguments passed to the function." param\quote scope body = body\quote scope - Const.wrap FnDef param_symbols, body, scope + ResultNode value: Value.wrap FnDef param_symbols, body, scope class defn extends Action @doc: "(defn name-sym (p1 [p2]...) body-expr) - define a function @@ -130,7 +129,7 @@ declares a lambda (see (doc fn)) and defines it in the current scope" assert #tail == 3, "'defn' takes exactly three arguments" { name, params, body } = tail - name = (name\quote scope)\getc 'sym' + name = (name\quote scope)\unwrap 'sym' assert params.__class == Cell, "'defn's second argument has to be an expression" param_symbols = for param in *params.children assert param.type == 'sym', "function parameter declaration has to be a symbol" @@ -139,9 +138,8 @@ declares a lambda (see (doc fn)) and defines it in the current scope" body = body\quote scope fn = FnDef param_symbols, body, scope - scope\set name, Const.wrap fn - - nil + scope\set name, Value.wrap fn + ResultNode! class do_expr extends Action @doc: "(do expr1 [expr2]...) - update multiple expressions @@ -150,7 +148,7 @@ evaluates and continously updates expr1, expr2, ... the last expression's value is returned." eval: (scope, tail) => - UpdateChildren [(expr\eval scope) or Const.empty! for expr in *tail] + ResultNode children: [expr\eval scope for expr in *tail] class if_ extends Action @doc: "(if bool then-expr [else-xpr]) - make an eval-time const choice @@ -166,7 +164,7 @@ to then-expr, otherwise it is equivalent to else-xpr if given, or nil otherwise. { xif, xthen, xelse } = tail xif = L\push xif\eval, scope - xif = xif\getc! + xif = xif\value_only!\const!\unwrap! if xif xthen\eval scope @@ -180,8 +178,8 @@ class trace extends Action L\trace "evaling #{@}" assert #tail == 1, "'trace' takes exactly one parameter" - with val = L\push tail[1]\eval, scope - L\print "trace:", val + with result = L\push tail[1]\eval, scope + L\print "trace #{tail[1]\stringify!}: #{result.value}" { :doc, :trace diff --git a/core/cell.moon b/core/cell.moon index 4aa847b..642e1a6 100644 --- a/core/cell.moon +++ b/core/cell.moon @@ -1,15 +1,19 @@ -import Const from require 'core.const' +-- ALV Cell type +import Const from require 'core.value' import op_invoke, fn_invoke from require 'core.invoke' import Tag from require 'core.tag' +-- ALV Cell type class Cell -- common - new: (@tag, @children, @white) => + -- tag: the parsed Tag + -- children: sequence of child AST Nodes + -- white: optional sequence of whitespace segments ([0 .. #@children]) + new: (@tag=Tag.blank!, @children, @white) => if not @white @white = ['' for i=1,#@children+1] - if not @tag - @tag = Tag.blank! + assert #@white == #@children, "mismatched whitespace length" head: => @children[1] tail: => [c for c in *@children[2,]] @@ -18,7 +22,8 @@ class Cell -- AST interface eval: (scope) => - head = @head!\eval scope + head_result = @head!\eval scope + head = head_result.value\const! Action = switch head.type when 'opdef' -- scope\get 'op-invoke' @@ -27,7 +32,7 @@ class Cell -- scope\get 'fn-invoke' fn_invoke when 'builtin' - head\getc! + head\unwrap! else print head for k,v in pairs head @@ -35,8 +40,7 @@ class Cell print head.__class.__name error "cannot evaluate expr with head #{head}" - action = Action\get_or_create head, @tag - action\eval scope, @tail! + Action\eval_cell scope, @tag, head, @tail! quote: (scope) => children = [child\quote scope for child in *@children] @@ -81,6 +85,9 @@ class Cell tag, children, white = parse_args ... @ tag, children, white +-- A parenthesis-less Cell (root of an ALV document) +-- +-- evaluates with an implicit 'do' in the head class RootCell extends Cell head: => Const.sym 'do' tail: => @children @@ -98,4 +105,7 @@ class RootCell extends Cell @parse: (...) => @__parent.parse @, (Tag\parse '0'), ... -:Cell, :RootCell +{ + :Cell + :RootCell +} diff --git a/core/const.moon b/core/const.moon deleted file mode 100644 index 99e4178..0000000 --- a/core/const.moon +++ /dev/null @@ -1,136 +0,0 @@ -import Op, Action, FnDef from require 'core.base' - -local Scope -load_ = -> - import Scope from require 'core.scope' - -ancestor = (klass) -> - assert klass, "cant find the ancestor of nil" - while klass.__parent - klass = klass.__parent - klass - -class Ref - new: (@original) => - - get: (...) => @original\get ... - getc: (...) => @original\get ... - - destroy: => - update: => - -class Const - types = { - sym: true - str: true - num: true - bool: true - scope: true - op: true - opdef: true - fndef: true - builtin: true - } - - new: (@type, @value, @raw) => - assert types[@type], "invalid Const type: #{@type}" - --- Value interface - get: (type) => - assert not type or type == @type, "#{@} is not a #{type}" - @value - - getc: (type) => - assert not type or type == @type, "#{@} is not a #{type}" - @value - - update: (dt) => - switch @type - when 'op' - @value\update dt - --- AST interface - eval: (scope) => - switch @type - when 'num', 'str' - @ - when 'sym' - assert (scope\get @value), "undefined reference to symbol '#{@value}'" - else - error "cannot evaluate #{@}" - - quote: => @ - - stringify: => @raw - - clone: (prefix) => @ - -- in case of doubt: - -- clone: (prefix) => Const @type, @value, @raw - --- static - __tostring: => - value = if @type == 'opdef' or @type == 'builtin' then @value.__name else @value - "<#{@type}: #{value}>" - - __eq: (other) => - other.type == @type and other.value == @value - - unescape = (str) -> str\gsub '\\([\'"\\])', '%1' - - @parse: (type, sep) => - switch type - when 'num' then (match) -> @ 'num', (tonumber match), match - when 'sym' then (match) -> @ 'sym', match, match - when 'str' then (match) -> @ 'str', (unescape match), sep .. match .. sep - - @num: (num) -> Const 'num', num, tostring num - @str: (str) -> Const 'str', str, "'#{str}'" - @sym: (sym) -> Const 'sym', sym, sym - @bool: (bool) -> Const 'bool', bool, tostring bool - @empty: -> Const 'str', '', "''" - - @wrap: (val, name='(unknown)') -> - typ = switch type val - when 'number' then 'num' - when 'string' then 'str' - when 'table' - if base = rawget val, '__base' - -- a class - switch ancestor val - when Op then 'opdef' - when Action then 'builtin' - else - error "#{name}: cannot wrap class '#{val.__name}'" - elseif val.__class - -- an instance - switch ancestor val.__class - when Op then 'op' - when Scope then 'scope' - when FnDef then 'fndef' - when Const - return val - else - error "#{name}: cannot wrap '#{val.__class.__name}' instance" - else - -- plain table - return Const 'scope', Scope.from_table val - else - error "#{name}: cannot wrap Lua type '#{type val}'" - - Const typ, val - - @wrap_ref: (val) -> - if base = rawget val, '__base' - -- a class - error "#{name}: cannot wrap_ref class '#{val.__name}'" - elseif val.__class - -- an instance - switch ancestor val.__class - when Op then Ref val - when Const then val - else - error "#{name}: cannot wrap_ref '#{val.__class.__name}' instance" - else - error "#{name}: cannot wrap_ref Lua type '#{type val}'" - -:Const, :load_ diff --git a/core/init.moon b/core/init.moon index 1d9e191..b885747 100644 --- a/core/init.moon +++ b/core/init.moon @@ -2,7 +2,7 @@ L or= setmetatable {}, __index: => -> import Op, Action, FnDef from require 'core.base' -import Const, load_ from require 'core.const' +import Stream, Const, load_ from require 'core.value' import Scope from require 'core.scope' load_! @@ -15,7 +15,8 @@ import cell, program from require 'core.parsing' globals = Scope.from_table require 'core.builtin' { - :Const, :Cell, :RootCell + :Stream, :Const + :Cell, :RootCell :Op, :Action, :FnDef :Scope @@ -28,5 +29,6 @@ globals = Scope.from_table require 'core.builtin' scope\use inject if inject ast = assert (cell\match str), "failed to parse: #{str}" - Const.wrap ast\eval scope + result = ast\eval scope + result\value_only! } diff --git a/core/invoke.moon b/core/invoke.moon index 5ec4a82..5a6c420 100644 --- a/core/invoke.moon +++ b/core/invoke.moon @@ -1,39 +1,23 @@ -import Const from require 'core.const' +import ResultNode, Value from require 'core.value' import Action from require 'core.base' import Scope from require 'core.scope' -class UpdateChildren - new: (@children) => - - update: (dt) => - for child in *@children - -- L\trace "updating #{child}" - L\push child\update, dt - - get: => @children[#@children]\get! - getc: => @children[#@children]\getc! - - __tostring: => '<forwarder>' - class op_invoke extends Action patch: (head) => return true if head == @head @op\destroy! if @op - @head = head - assert @head.type == 'opdef', "cant op-invoke #{@head}" - @op = @head\getc!! - + def = head\const!\unwrap 'opdef', "cant op-invoke #{@head}" + @head, @op = head, def! + true eval: (scope, tail) => - L\trace "evaling #{@}" - args = L\push -> [L\push expr\eval, scope for expr in *tail] + children = L\push -> [L\push expr\eval, scope for expr in *tail] + value = @op\setup unpack [child.value for child in *children] - -- Const 'op', with @op - with @op - \setup unpack args + ResultNode :children, :value, op: @op class fn_invoke extends Action -- @TODO: @@ -48,27 +32,23 @@ class fn_invoke extends Action true eval: (outer_scope, tail) => - assert @head.type == 'fndef', "cant fn-invoke #{@head}" - { :params, :body, :scope } = @head\getc! + { :params, :body, :scope } = @head\const!\unwrap 'fndef', "cant fn-invoke #{@head}" assert #params == #tail, "argument count mismatch in #{@head}" fn_scope = Scope @, scope - for i=1,#params - name = params[i]\getc! - argm = tail[i] - fn_scope\set name, L\push argm\eval, outer_scope + children = for i=1,#params + name = params[i]\unwrap 'sym' + with L\push tail[i]\eval, outer_scope + fn_scope\set name, .value body = body\clone @tag + result = body\eval fn_scope - body\eval fn_scope - -class do_expr extends Action - eval: (scope, tail) => - UpdateChildren [(expr\eval scope) or Const.empty! for expr in *tail] + table.insert children, result + ResultNode :children, value: result.value { :op_invoke, :fn_invoke - :UpdateChildren } diff --git a/core/parsing.moon b/core/parsing.moon index 58dbf62..96d8125 100644 --- a/core/parsing.moon +++ b/core/parsing.moon @@ -1,4 +1,4 @@ -import Const from require 'core.const' +import Const from require 'core.value' import Cell, RootCell from require 'core.cell' import Tag from require 'core.tag' import R, S, P, V, C, Ct from require 'lpeg' @@ -14,13 +14,14 @@ space = (wc^1 * (comment * wc^1)^0) / 1 -- required whitespace mspace = (comment + wc)^0 / 1 -- optional whitespace -- atoms -sym = ((R 'az', 'AZ') + (S '-_+*/.!?')) ^ 1 / Const\parse 'sym' +digit = R '09' +first = (R 'az', 'AZ') + S '-_+*/.!?=' +sym = first * (first + digit)^0 / Const\parse 'sym' strd = '"' * (C ((P '\\"') + (P '\\\\') + (1 - P '"'))^0) * '"' / Const\parse 'str', '\"' strq = "'" * (C ((P "\\'") + (P '\\\\') + (1 - P "'"))^0) * "'" / Const\parse 'str', '\'' str = strd + strq -digit = R '09' int = digit^1 float = (digit^1 * '.' * digit^0) + (digit^0 * '.' * digit^1) num = (float + int) / Const\parse 'num' @@ -43,7 +44,13 @@ cell = P { :expr, :explist, :cell } -program = root * -1 +parse_error = (root, rest) -> + if #rest > 0 + error "failed to parse, rest is:\n#{rest}" + + root + +program = root * (C (P 1)^0) * -1 / parse_error { :comment diff --git a/core/registry.moon b/core/registry.moon index 3fc6d55..4dd5bbb 100644 --- a/core/registry.moon +++ b/core/registry.moon @@ -41,8 +41,9 @@ class Registry -- (e.g. first [A] is solved, then [5.A] is solved) continue if tag\index! - L\trace "assigning new tag #{value} to #{tag} #{expr}" - tag\set @next_tag! + next_tag = @next_tag! + L\trace "assigned new tag #{next_tag} to #{tag} #{expr}" + tag\set next_tag @map[tag\index!] = expr assert @ == @@active_registry, "not the active registry!" diff --git a/core/scope.moon b/core/scope.moon index ed4d67d..aaa38f2 100644 --- a/core/scope.moon +++ b/core/scope.moon @@ -1,10 +1,10 @@ -import Const from require 'core.const' +import Value from require 'core.value' class Scope new: (@node, @parent) => @values = {} - set_raw: (key, val) => @values[key] = Const.wrap val, key + set_raw: (key, val) => @values[key] = Value.wrap val, key set: (key, val) => L\trace "setting #{key} = #{val} in #{@}" @values[key] = val @@ -22,7 +22,7 @@ class Scope scope = @get start assert scope and scope.type == 'scope', "cant find '#{prefix}#{start}' for '#{prefix}#{key}'" - scope\getc!\get rest, "#{prefix}#{start}/" + scope\unwrap!\get rest, "#{prefix}#{start}/" use: (other) => L\trace "using defs from #{other} in #{@}" @@ -31,7 +31,7 @@ class Scope from_table: (tbl) -> with Scope! - .values = { k, Const.wrap v, k for k,v in pairs tbl } + .values = { k, Value.wrap v, k for k,v in pairs tbl } __tostring: => buf = "<Scope" diff --git a/core/value.moon b/core/value.moon new file mode 100644 index 0000000..f3f9796 --- /dev/null +++ b/core/value.moon @@ -0,0 +1,157 @@ +-- ALV Value types +import Op, Action, FnDef from require 'core.base' + +local Scope +load_ = -> + import Scope from require 'core.scope' + +ancestor = (klass) -> + assert klass, "cant find the ancestor of nil" + while klass.__parent + klass = klass.__parent + klass + +-- Result of evaluating an expression +-- carries (all optional): +-- - a Value +-- - an Op (to update) +-- - children (results of subexpressions that were evaluated) +-- +-- ResultNodes form a tree that controls execution order and message passing +-- between Ops. +class ResultNode + -- params: table with optional keys op, value, children + new: (params={}) => + @op = params.op + @value = params.value + @children = params.children or {} + + -- unwrap value and assert that neither @op nor @children were set + value_only: (msg) => + assert not @op and #@children == 0, msg or "pure expression expected" + @value + + update: (dt) => + for child in *@children + child\update dt + @op\update dt if @op + + __tostring: => + buf = "<result=#{@value}" + buf ..= " #{@op}" if @op + buf ..= " (#{#@children} children)" if #@children > 0 + buf ..= ">" + buf + +local Const, Stream + +-- ALV Type wrapper +class Value + -- @type - type name. + -- builtin types: * literals: sym, num, bool + -- * scope, opdef, fndef, builtin + -- @value - Lua value - access through :unwrap() + new: (@type, @value) => + + -- asserts value-constness + -- returns self (for chaining) + const: => error 'not a constant' + + -- unwrap to the Lua type + -- asserts @type == type, msg if given + unwrap: (type, msg) => + assert type == @type, msg or "#{@} is not a #{type}" if type + @value + +-- static + __tostring: => + value = if 'table' == (type @value) and rawget @value, '__base' then @value.__name else @value + "<#{@@__name} #{@type}: #{value}>" + __eq: (other) => other.type == @type and other.value == @value + __inherited: (cls) => + cls.__base.__tostring = @__tostring + cls.__base.__eq = @__eq + + -- wrap a Lua type + @wrap: (val, name='(unknown)') -> + typ = switch type val + when 'number' then 'num' + when 'string' then 'str' + when 'table' + if base = rawget val, '__base' + -- a class + switch ancestor val + when Op then 'opdef' + when Action then 'builtin' + else + error "#{name}: cannot wrap class '#{val.__name}'" + elseif val.__class + -- an instance + switch ancestor val.__class + when Scope then 'scope' + when FnDef then 'fndef' + when Value + return val + else + error "#{name}: cannot wrap '#{val.__class.__name}' instance" + else + -- plain table + return Const 'scope', Scope.from_table val + else + error "#{name}: cannot wrap Lua type '#{type val}'" + + Const typ, val + +class Stream extends Value + new: (@type, @value=nil) => + + set: (@value) => + -- set dirty flag (?) + +class Const extends Value + new: (type, value, @raw) => + super type, value + +-- Value interface + const: => @ + +-- AST interface + eval: (scope) => + value = switch @type + when 'num', 'str' + @ + when 'sym' + assert (scope\get @value), "undefined reference to symbol '#{@value}'" + else + error "cannot evaluate #{@}" + + ResultNode :value + + quote: => @ + + stringify: => @raw + + clone: (prefix) => @ + -- in case of doubt: + -- clone: (prefix) => Const @type, @value, @raw + +-- static + unescape = (str) -> str\gsub '\\([\'"\\])', '%1' + + @parse: (type, sep) => + switch type + when 'num' then (match) -> @ 'num', (tonumber match), match + when 'sym' then (match) -> @ 'sym', match, match + when 'str' then (match) -> @ 'str', (unescape match), sep .. match .. sep + + @num: (num) -> Const 'num', num, tostring num + @str: (str) -> Const 'str', str, "'#{str}'" + @sym: (sym) -> Const 'sym', sym, sym + @bool: (bool) -> Const 'bool', bool, tostring bool + +{ + :ResultNode + :Value + :Stream, :Const + :load_ +} diff --git a/lib/debug.moon b/lib/debug.moon index ece365f..f642c2e 100644 --- a/lib/debug.moon +++ b/lib/debug.moon @@ -3,12 +3,10 @@ import Op from require 'core' class out extends Op @doc: "(out name-str value) - log value to the console" - setup: (name, @chld) => - @name = name\getc! + setup: (@name, @value) => update: (dt) => - @chld\update dt - L\print "@name", @chld\get! + L\print "#{@name\unwrap 'str'}", @value\unwrap! { :out diff --git a/lib/envelope.moon b/lib/envelope.moon index 8e8fafe..eb79a64 100644 --- a/lib/envelope.moon +++ b/lib/envelope.moon @@ -1,4 +1,4 @@ -import Const, Op, Scope, eval from require 'core' +import Stream, Op, Scope, eval from require 'core' class ar_core extends Op @doc: "(ar-core attack release gate) @@ -8,21 +8,18 @@ goes from 0 to 1 in attack seconds, holds while gate is on, then goes back to 0 new: (...) => super ... - @value = 0 + @out = Stream 'num', 0 setup: (@a, @r, @gate) => assert @a, "ar requires an attack value" assert @r, "ar requires a release value" assert @gate, "ar requires a gate value" + @out update: (dt) => - @a\update dt - @r\update dt - @gate\update dt + slope = if (@gate\unwrap 'bool') then (@a\unwrap! or 0.1) else -(@r\unwrap! or 0.5) - slope = if @gate\get! then (@a\get! or 0.1) else -(@r\get! or 0.5) - - @value = math.min 1, math.max 0, @value + dt / slope + @out\set math.min 1, math.max 0, @out\unwrap! + dt / slope scope = Scope.from_table { 'ar-core': ar_core } scope\set 'ar', eval '(fn (a r) (fn (gate) (ar-core a r gate)))', scope diff --git a/lib/gui.moon b/lib/gui.moon index a2fee4b..474de5c 100644 --- a/lib/gui.moon +++ b/lib/gui.moon @@ -12,12 +12,11 @@ display value as a bar" setup: (name, @chld) => @@instances[@name] = nil if @name - @name = name\getc! + @name = name\const!\unwrap! @@instances[@name] = @ update: (dt) => - @chld\update dt - @value = @chld\get! + @value = @chld\unwrap! destroy: => @@instances[@name] = nil @@ -44,9 +43,11 @@ class key extends Op setup: (@key) => assert @key + @out = Stream 'bool', false + @out update: (dt) => - @value = lk.isDown @key\get! + @out\set lk.isDown @key\unwrap! arguments, k = {} for a in *arg diff --git a/lib/logic.moon b/lib/logic.moon index 1c274dd..c476033 100644 --- a/lib/logic.moon +++ b/lib/logic.moon @@ -1,37 +1,36 @@ -import Op from require 'core' +import Stream, Op from require 'core' unpack or= table.unpack class BinOp extends Op + new: (...) => + super ... + @out = Stream 'bool' + setup: (...) => @children = { ... } assert #@children >= 2, "#{@} needs at least two parameters" - - update: (dt) => - for child in *@children - child\update dt + @out class eq extends BinOp @doc: "(eq a b [c]...) (== a b [c]...) - check for equality" update: (dt) => - super\update dt - - @value = true - val = @children[1]\get! + equal = true + val = @children[1]\unwrap! for child in *@children[2,] - @value and= val == child\get! + equal and= val == child\unwrap! + @out\set equal class and_ extends BinOp @doc: "(and a b [c]...) - AND values" update: (dt) => - super\update dt - - @value = true + value = true for child in *@children - @value and= child\get! + value and= child\unwrap! + @out\set value class or_ extends BinOp @doc: "(or a b [c]...) - OR values @@ -39,31 +38,30 @@ class or_ extends BinOp subtracts all other arguments from a" update: (dt) => - super\update dt - - @value = false + value = false for child in *@children - @value or= child\get! + value or= child\unwrap! + @out\set value class not_ extends Op @doc: "(not a) - boolean opposite" setup: (@a) => + @out = Stream 'bool' + @out update: (dt) => - @a\update dt - - @value = not @a\get! + @out\set not @a\unwrap! class bool extends Op @doc: "(bool a) - convert to bool" setup: (@a) => + @out = Stream 'bool' + @out update: (dt) => - @a\update dt - - @value = switch @a\get! + @out\set switch @a\unwrap! when false, nil, 0 false else diff --git a/lib/math.moon b/lib/math.moon index 54d3440..68dae3a 100644 --- a/lib/math.moon +++ b/lib/math.moon @@ -1,25 +1,25 @@ -import Op from require 'core' +import Stream, Op, Const from require 'core' unpack or= table.unpack class BinOp extends Op + new: (...) => + super ... + @out = Stream 'num', 0 + setup: (...) => @children = { ... } assert #@children >= 2, "#{@} needs at least two parameters" - - update: (dt) => - for child in *@children - child\update dt + @out class add extends BinOp @doc: "(+ a b [c]...) (add a b [c]...) - add values" update: (dt) => - super\update dt - - @value = 0 + value = 0 for child in *@children - @value += child\get! + value += child\unwrap 'num' + @out\set value class sub extends BinOp @doc: "(- a b [c]...) @@ -28,22 +28,20 @@ class sub extends BinOp subtracts all other arguments from a" update: (dt) => - super\update dt - - @value = @children[1]\get! + value = @children[1]\unwrap 'num' for child in *@children[2,] - @value -= child\get! + value -= child\unwrap 'num' + @out\set value class mul extends BinOp @doc: "(* a b [c]...) (mul a b [c]...) - multiply values" update: (dt) => - super\update dt - - @value = 1 + value = 1 for child in *@children - @value *= child\get! + value *= child\unwrap 'num' + @out\set value class div extends BinOp @doc: "(/ a b [c]...) @@ -52,11 +50,26 @@ class div extends BinOp divides a by all other arguments" update: (dt) => - super\update dt - - @value = @children[1]\get! + value = @children[1]\unwrap 'num' for child in *@children[2,] - @value /= child\get! + value /= child\unwrap 'num' + @out\set value + +evenodd_op = (name, remainder) -> + class k extends Op + setup: (@a, @div=Const.num 2) => + @out = Stream 'bool' + @out + + update: (dt) => + a, divi = (@a\unwrap 'num'), @div\unwrap 'num' + @out\set (a % divi) == remainder + + k.__name = name + k.doc = "(#{name} a [div]) - check for #{name} divison + +div defaults to 2" + k func_op = (name, arity, func) -> k = class extends Op @@ -65,12 +78,11 @@ func_op = (name, arity, func) -> if arity != '*' assert #@params == arity, "#{@} needs exactly #{arity} parameters" - update: (dt) => - params = for param in *@params - param\update dt - param\get! + @out = Stream 'num' + @out - @value = func unpack params + update: (dt) => + @out\set func unpack [p\unwrap 'num' for p in *@params] k.__name = name k @@ -85,6 +97,8 @@ module = { :mod, '%': mod mix: func_op 'mix', 3, (a, b, i) -> i*b + (1-i)*a + even: evenodd_op 'even', 0 + odd: evenodd_op 'odd', 1 pi: math.pi, huge: math.huge } diff --git a/lib/midi/core.moon b/lib/midi/core.moon index ea49a2c..891de0a 100644 --- a/lib/midi/core.moon +++ b/lib/midi/core.moon @@ -51,8 +51,10 @@ class Input -- register a handler -- mask is { :status, :chan, :a } (all keys optional) + -- returns mask for op convenience attach: (mask, handler) => @listeners[mask] = handler + mask detach: (mask) => @listeners[mask] = nil @@ -83,8 +85,23 @@ class InOut detach: (...) => @inp\detach ... send: (...) => @out\send ... +apply_range = (range, val) -> + if range.type == 'str' + switch range\unwrap! + when 'raw' then val + when 'uni' then val / 128 + when 'bip' then val / 64 - 1 + when 'rad' then val / 64 * math.pi + else + error "unknown range #{@range}" + elseif range.type == 'num' + val / 128 * range\unwrap! + else + error "range has to be a string or number" + { :Input :Output :InOut + :apply_range } diff --git a/lib/midi/init.moon b/lib/midi/init.moon index f522b6c..c9e2476 100644 --- a/lib/midi/init.moon +++ b/lib/midi/init.moon @@ -1,29 +1,29 @@ -import Const, Op from require 'core' -import Input from require 'lib.midi.core' +import Stream, Const, Op from require 'core' +import Input, apply_range from require 'lib.midi.core' -dispatch = Input 'system:midi_capture_6' +dispatch = Input 'system:midi_capture_4' class gate extends Op @doc: "(midi/gate note [chan]) - gate from note-on and note-off messages" - new: (...) => - super ... - destroy: => dispatch\detach @mask if @mask setup: (note, chan) => dispatch\detach @mask if @mask - note = note\getc 'num' - chan = chan and chan\getc 'num' + note = note\const!\unwrap 'num' + chan = chan and chan\const!\unwrap 'num' @value = false @mask = dispatch\attach { :chan, a: note }, (status) -> if status == 'note-on' - @value = true + @out\set true else if status == 'note-off' - @value = false + @out\set false + + @out = Stream 'bool', false + @out update: (dt) => dispatch\tick! @@ -43,26 +43,16 @@ range can be one of: setup: (cc, chan, @range=Const.str'uni') => dispatch\detach @mask if @mask - cc = cc\getc 'num' - chan = chan and chan\getc 'num' + cc = cc\const!\unwrap 'num' + chan = chan and chan\const!\unwrap 'num' - @mask = dispatch\attach { status: 'control-change', :chan, a: cc }, (_, _, _, val) -> @apply val + @mask = dispatch\attach { status: 'control-change', :chan, a: cc }, (_, _, _, val) -> + @out\set apply_range @range, val - update: (dt) => dispatch\tick! + @out = Stream 'num', 0 + @out - apply: (val) => - @value = if @range.type == 'str' - switch @range\get! - when 'raw' then val - when 'uni' then val / 128 - when 'bip' then val / 64 - 1 - when 'rad' then val / 64 * math.pi - else - error "unknown range #{@range}" - elseif @range.type == 'num' - val / 128 * @range\get! - else - error "range has to be a string or number" + update: (dt) => dispatch\tick! { :gate diff --git a/lib/midi/launchctl.moon b/lib/midi/launchctl.moon index c7d7b06..778a724 100644 --- a/lib/midi/launchctl.moon +++ b/lib/midi/launchctl.moon @@ -1,66 +1,127 @@ -import Const, Op from require 'core' -import InOut from require 'lib.midi.core' +import Stream, Const, Op from require 'core' +import InOut, apply_range from require 'lib.midi.core' import bor, lshift from require 'bit32' -launch = InOut 'system:midi_capture_6', 'system:midi_playback_6' +launch = InOut 'system:midi_capture_4', 'system:midi_playback_4' color = (r, g) -> bor 12, r, (lshift g, 4) +class cc_seq extends Op + @doc: "(launctl/cc-seq i start chan [steps [range]]) - CC-Sequencer + +returns the value for the i-th step steps (buttons starting from start). +steps defaults to 8. + +range can be one of: +- 'raw' [ 0 - 128[ +- 'uni' [ 0 - 1[ (default) +- 'bip' [-1 - 1[ +- 'rad' [ 0 - tau[ +- (num) [ 0 - num[" + + destroy: => + launch\detach @mask if @mask + + new: => + @steps = {} + @out = Stream 'num' + + setup: (@i, start, chan, steps=(Const.num 8), @range=Const.str 'uni') => + launch\detach @mask if @mask + + @start = start\const!\unwrap 'num' + @chan = chan\const!\unwrap 'num' + steps = steps\const!\unwrap 'num' + + while steps > #@steps + table.insert @steps, 0 + while steps < #@steps + table.remove @steps + + @mask = launch\attach { status: 'control-change', chan: @chan }, (_, _, cc, val) -> @change cc, val + @out + + change: (cc, val) => + i = cc - @start + if i < #@steps + @steps[i+1] = val + + update: (dt) => + launch\tick! + + curr_i = (@i\unwrap 'num') % #@steps + + @out\set apply_range @range, @steps[curr_i+1] + + class gate_seq extends Op @doc: "(launctl/gate-seq i start chan [steps]) - Gate-Sequencer -returns true or false for the i-th step steps (buttons starting from start)." +returns true or false for the i-th step steps (buttons starting from start). +steps defaults to 8." destroy: => launch\detach @mask if @mask new: => @steps = {} - @value = false + @out = Stream 'bool' setup: (@i, start, chan, steps=(Const.num 8)) => launch\detach @mask if @mask - @start = start\getc 'num' - @chan = chan\getc 'num' - steps = steps\getc 'num' + for i=1, #@steps + launch\send 'note-on', @chan, (@start+i), 0, color 0, 0 + + @start = start\const!\unwrap 'num' + @chan = chan\const!\unwrap 'num' + steps = steps\const!\unwrap 'num' while steps > #@steps table.insert @steps, false while steps < #@steps table.remove @steps - @mask = launch\attach { status: 'note-on', chan: @chan }, (_, _, note, _) -> @toggle note + for i=1, steps + @display i + + @mask = launch\attach { status: 'note-on', chan: @chan }, (_, _, note, _) -> + @toggle note + + @out toggle: (note) => i = note - @start val = @steps[i+1] if val != nil @steps[i+1] = not val - curr_i = (@i\get 'num') % #@steps - launch\send 'note-on', @chan, note, light @steps[i+1], i == curr_i + curr_i = (@i\unwrap 'num') % #@steps + @display i, i == curr_i + + display: (i, active) => + launch\send 'note-on', @chan, (@start + i), light @steps[i+1], active light = (set, active) -> set = if set then 'S' else ' ' active = if active then 'A' else ' ' color switch set .. active when ' ' then 0, 0 - when ' A' then 1, 0 - when 'S ' then 0, 1 - when 'SA' then 0, 3 + when ' A' then 1, 1 + when 'S ' then 1, 0 + when 'SA' then 3, 1 update: (dt) => launch\tick! - @i\update dt - curr_i = (@i\get 'num') % #@steps + curr_i = (@i\unwrap 'num') % #@steps prev_i = (curr_i - 1) % #@steps - launch\send 'note-on', @chan, (@start + curr_i), light @steps[curr_i+1], true - launch\send 'note-on', @chan, (@start + prev_i), light @steps[prev_i+1], false + @display curr_i, true + @display prev_i, false - @value = @steps[curr_i+1] + @out\set @steps[curr_i+1] { 'gate-seq': gate_seq + 'cc-seq': cc_seq } diff --git a/lib/osc.moon b/lib/osc.moon index dae6737..fed1697 100644 --- a/lib/osc.moon +++ b/lib/osc.moon @@ -1,4 +1,4 @@ -import Const, Op, FnDef from require 'core' +import Stream, Op from require 'core' import pack, unpack from require 'osc' import dns, udp from require 'socket' @@ -13,12 +13,9 @@ class out extends Op setup: (@host, @port, @path, @value) => update: (dt) => - for p in *{@host, @port, @path, @value} - L\push p\update, dt - - ip = dns.toip @host\get! - port = @port\get! - msg = pack @path\get!, @value\get! + ip = dns.toip @host\unwrap 'str' + port = @port\unwrap 'num' + msg = pack (@path\unwrap 'str'), @value\unwrap! @@udp\sendto msg, ip, port { diff --git a/lib/pilot.moon b/lib/pilot.moon index 1cbc070..2a7d21a 100644 --- a/lib/pilot.moon +++ b/lib/pilot.moon @@ -1,5 +1,5 @@ -import Const, Op, FnDef from require 'core' -import dns, udp from require 'socket' +import Op from require 'core' +import udp from require 'socket' conn = udp! hex = "0123456789abcdef" @@ -18,17 +18,12 @@ send = (tbl) -> class play extends Op @doc: "(play trig ch oct note [vel [len]]) - play a note when trig is live" - setup: (@trig, @ch, @oct, @note, @vel, @len) => + setup: (@trig, ...) => + @vals = { ... } update: (dt) => - vals = for c in *{@trig, @ch, @oct, @note, @vel, @len } - c\update dt - c\get! - - trig = table.remove vals, 1 - - if trig - send vals + if @trig\unwrap 'bool' + send [c\unwrap! for c in *@vals] class effect extends Op @doc: "(effect which a b) - set an effect @@ -38,10 +33,7 @@ which is one of 'DIS', 'CHO', 'REV' or 'FEE'" setup: (@which, @a, @b) => update: (dt) => - @which\update dt - @a\update dt - @b\update dt - which, a, b = @which\get!, @a\get!, @b\get! + which, a, b = (@which\unwrap 'str'), @a\unwrap!, @b\unwrap! if which and a and b send { which, a, b } { diff --git a/lib/string.moon b/lib/string.moon index 82e9702..6a9b7d7 100644 --- a/lib/string.moon +++ b/lib/string.moon @@ -1,4 +1,4 @@ -import Op from require 'core' +import Stream, Op from require 'core' class str extends Op @doc: "(str v1 [v2]...) @@ -6,12 +6,11 @@ class str extends Op setup: (...) => @children = { ... } + @out = Stream 'str' + @out update: (dt) => - for child in *@children - child\update dt - - @value = table.concat [tostring child\get! for child in *@children] + @out\set table.concat [tostring child\unwrap! for child in *@children] { :str, '..': str diff --git a/lib/time.moon b/lib/time.moon index 89f947e..7ce8b84 100644 --- a/lib/time.moon +++ b/lib/time.moon @@ -1,4 +1,4 @@ -import Const, Op from require 'core' +import Stream, Const, Op from require 'core' class lfo extends Op @doc: "(lfo freq [wave]) - low-frequency oscillator @@ -12,19 +12,19 @@ wave selects the wave shape from the following (default sin): tau = math.pi * 2 new: (...) => super ... + print "creating LFO" + @out = Stream 'num' @phase = 0 default_wave = Const 'str', 'sin' setup: (@freq, @wave=default_wave) => assert @freq, "lfo requires a frequency value" L\trace "setup #{@}, freq=#{@freq}, wave=#{@wave}" + @out update: (dt) => - @freq\update dt - @wave\update dt - - @phase += dt * @freq\get! - @value = switch @wave\get! + @phase += dt * @freq\unwrap 'num' + @out\set switch @wave\unwrap! when 'sin' then .5 + .5 * math.cos @phase * tau when 'saw' then @phase % 1 when 'tri' then math.abs (2*@phase % 2) - 1 @@ -37,45 +37,64 @@ ramps from 0 to max (default same as ramp) once every period seconds" new: (...) => super ... + @out = Stream 'num' @phase = 0 + @out setup: (@period, @max) => assert @period, "tick requires a period value" update: (dt) => - @period\update dt - max = if @max - @max\update dt - @max\get! - else - @period\get! - - @phase += dt / @period\get! + period = @period\unwrap 'num' + max = if @max then @max\unwrap! else period + @phase += dt / period if @phase >= 1 @phase -= 1 - @value = @phase * max + @out\set @phase * max class tick extends Op @doc: "(tick period) - count ticks -counts upwards every period seconds and returns the number of completed ticks." +counts upwards by one every period seconds and returns the number of completed ticks." new: (...) => super ... + @out = Stream 'num' @phase = 0 setup: (@period) => assert @period, "tick requires a period value" + @out update: (dt) => - @period\update dt + @phase += dt / @period\unwrap 'num' + @out\set math.floor @phase + +class every extends Op + @doc: "(every period) - sometimes true - @phase += dt / @period\get! - @value = math.floor @phase +returns true once every period seconds." + new: (...) => + super ... + @out = Stream 'bool' + @phase = 0 + + setup: (@period) => + assert @period, "every requires a period value" + @out + + update: (dt) => + @phase += dt / @period\unwrap 'num' + if @phase > 1 + @phase -= 1 + @out\set true + else + @out\set false { :lfo :ramp :tick + :every } diff --git a/lib/util.moon b/lib/util.moon index af5d8f8..192c489 100644 --- a/lib/util.moon +++ b/lib/util.moon @@ -1,4 +1,4 @@ -import Const, Op from require 'core' +import Stream, Const, Op from require 'core' class switch_ extends Op @doc: "(switch i v0 [v1 v2...]) - switch between multiple inputs @@ -10,14 +10,16 @@ when i is a num, it is (floor)ed and the matching argument (starting from 0) is setup: (@i, ...) => @choices = { ... } - update: (dt) => - @i\update dt - i = @i\get! + typ = @choices[1].type + for inp in *@choices[2,] + assert inp.type == typ, "not all values have the same type: #{typ} != #{inp.type}" - for choice in *@choices - choice\update dt + @out = Stream typ + @out - active = switch @i\get! + update: (dt) => + i = @i\unwrap! + active = switch i when true @choices[1] when false @@ -25,7 +27,7 @@ when i is a num, it is (floor)ed and the matching argument (starting from 0) is else i = 1 + (math.floor i) % #@choices @choices[i] - @value = active and active\get! + @out\set active and active\unwrap! class switch_pause extends Op @doc: "(switch- i v0 [v1 v2...]) - switch and pause multiple inputs @@ -35,10 +37,16 @@ like (switch ...) except that the unused inputs are paused." setup: (@i, ...) => @choices = { ... } + typ = @choices[1].type + for inp in *@choices[2,] + assert inp.type == typ, "not all values have the same type: #{typ} != #{inp.type}" + + @out = Stream typ + @out + update: (dt) => - @i\update dt - i = @i\get! - active = switch @i\get! + i = @i\unwrap! + active = switch i when true @choices[1] when false @@ -47,21 +55,18 @@ like (switch ...) except that the unused inputs are paused." i = 1 + (math.floor i) % #@choices @choices[i] - @value = if active - active\update dt - active\get! + @out\set if active + active\unwrap! class edge extends Op setup: (@i) => - @value = false @last = false + @out = Stream @i.type + @out update: (dt) => - @i\update dt - - now = @i\get! - - @value = not @last and now + now = @i\unwrap! + @out\set not @last and now @last = now class keep extends Op @@ -71,12 +76,12 @@ always reproduces the last non-nil value the input produced or default. default defaults to zero." setup: (@i, @default=Const.num 0) => + @out = Stream @i.type, @default.value + @out update: (dt) => - @i\update dt - - next = @i\get! - @value = next or @value or @default\get! + if next = @i\unwrap! + @out\set next { 'switch': switch_ |
