aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2020-02-19 21:17:46 +0000
committers-ol <s-ol@users.noreply.github.com>2020-02-19 21:17:53 +0000
commite62ef99244bb3aa906810a5cfd53c0dac68b0f37 (patch)
treee7379e2add7014e7124f678f4a7a8edc699fe914
parentmajor refactoring: Const, Stream + ResultNode (diff)
downloadalive-e62ef99244bb3aa906810a5cfd53c0dac68b0f37.tar.gz
alive-e62ef99244bb3aa906810a5cfd53c0dac68b0f37.zip
merge Const and Stream into Value; Dataflow logic
lib not fully ported / functonial
-rw-r--r--copilot.moon14
-rw-r--r--core/base.moon37
-rw-r--r--core/builtin.moon36
-rw-r--r--core/cell.moon10
-rw-r--r--core/init.moon6
-rw-r--r--core/invoke.moon13
-rw-r--r--core/parsing.moon10
-rw-r--r--core/registry.moon58
-rw-r--r--core/scope.moon17
-rw-r--r--core/value.moon154
-rw-r--r--init.moon23
-rw-r--r--lib/debug.moon9
-rw-r--r--lib/midi/core.moon147
-rw-r--r--lib/midi/init.moon75
-rw-r--r--lib/midi/launchctl.moon59
-rw-r--r--lib/osc.moon29
-rw-r--r--lib/time.moon139
-rw-r--r--lib/util.moon114
-rw-r--r--spec/cell_spec.moon18
-rw-r--r--spec/const_spec.moon122
-rw-r--r--spec/parsing_spec.moon30
-rw-r--r--spec/scope_spec.moon48
-rw-r--r--spec/value_spec.moon112
-rw-r--r--test.alv19
24 files changed, 714 insertions, 585 deletions
diff --git a/copilot.moon b/copilot.moon
index 78848e5..a66c570 100644
--- a/copilot.moon
+++ b/copilot.moon
@@ -29,20 +29,21 @@ class Copilot
return
scope = Scope ast, globals
- ok, err = pcall (@registry\wrap ast\eval), scope, @registry
+ ok, err = pcall ast\eval, scope, @registry
if not ok
L\error "error evaluating: #{err}"
return
@root = err
+ ast
- spit @file, ast\stringify!
-
- update: (dt) =>
+ tick: =>
@poll!
if @root
- @root\update dt
+ ok, err = pcall @registry\wrap_tick @root\tick
+ if not ok
+ L\error err
tb = (msg) -> debug.traceback msg, 2
poll: =>
@@ -52,7 +53,8 @@ class Copilot
if @last_modification < modification
L\log "#{@file} changed at #{modification}"
- L\push @\patch
+ ast = L\push @registry\wrap_eval @\patch
+ spit @file, ast\stringify!
@last_modification = os.time!
:Copilot
diff --git a/core/base.moon b/core/base.moon
index 1b1be5d..60ee460 100644
--- a/core/base.moon
+++ b/core/base.moon
@@ -1,21 +1,47 @@
-- base definitions for extensions
+import Value from require 'core.value'
+
+unpack or= table.unpack
-- a persistent expression Operator
--
-- accepts Const or Stream inputs and produces a Stream output
class Op
- update: (dt) =>
+ 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.
+ setup: (@inputs) =>
- -- set @out to a Value (Const or Stream)
- setup: (...) =>
+ -- 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_inputs: =>
+ unpack [input! for input in *@inputs]
+
+ assert_types: (...) =>
+ num = select '#', ...
+ assert #@inputs >= num, "argument count mismatch"
+ 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,
@@ -51,7 +77,7 @@ class Action
-- 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 ResultNode
+ -- register the action with the tag, evaluate it and return the Result
@eval_cell: (scope, tag, head, tail) =>
last = tag\last!
compatible = last and
@@ -93,6 +119,7 @@ class FnDef
"(fn (#{table.concat [p\stringify! for p in *@params], ' '}) ...)"
{
+ :Dispatcher
:Op
:Action
:FnDef
diff --git a/core/builtin.moon b/core/builtin.moon
index 71beac3..6abcbd9 100644
--- a/core/builtin.moon
+++ b/core/builtin.moon
@@ -1,6 +1,6 @@
-- builtin special forms
import Action, FnDef from require 'core.base'
-import ResultNode, Value, Const from require 'core.value'
+import Result, Value from require 'core.value'
import Cell from require 'core.cell'
import Scope from require 'core.scope'
@@ -13,7 +13,7 @@ prints the docstring for sym in the console"
assert #tail == 1, "'doc' takes exactly one parameter"
def = L\push tail[1]\eval, scope
- with ResultNode children: { def }
+ with Result children: { def }
def = def.value\const!\unwrap!
L\print "(doc #{tail[1]\stringify!}):\n#{def.doc}\n"
@@ -35,9 +35,9 @@ updates all val-exprs."
name = (name\quote scope)\unwrap 'sym'
with val_expr\eval scope
- scope\set name, .value
+ scope\set name, \make_ref!
- ResultNode :children
+ Result :children
class use extends Action
@doc: "(use scope1 [scope2]...) - merge scopes into parent scope
@@ -49,10 +49,10 @@ all scopes have to be eval-time constants."
L\trace "evaling #{@}"
for child in *tail
result = L\push child\eval, scope
- value = result\value_only!\const!
+ value = result\const!
scope\use value\unwrap 'scope', "'use' only works on scopes"
- ResultNode!
+ Result!
class require_ extends Action
@doc: "(require name-str) - require a module
@@ -65,10 +65,10 @@ name-str has to be an eval-time constant."
assert #tail == 1, "'require' takes exactly one parameter"
result = L\push tail[1]\eval, scope
- name = result\value_only!\const!
+ name = result\const!
L\trace @, "loading module #{name}"
- ResultNode value: Value.wrap require "lib.#{name\unwrap 'str'}"
+ Result value: Value.wrap require "lib.#{name\unwrap 'str'}"
class import_ extends Action
@doc: "(import sym1 [sym2]...) - require and define modules
@@ -81,9 +81,9 @@ requires modules sym1, sym2, ... and defines them as sym1, sym2, ... in the curr
for child in *tail
name = (child\quote scope)\unwrap 'sym'
- scope\set name, Value.wrap require "lib.#{name}"
+ scope\set name, Result value: Value.wrap require "lib.#{name}"
- ResultNode!
+ Result!
class import_star extends Action
@doc: "(import* sym1 [sym2]...) - require and use modules
@@ -99,7 +99,7 @@ requires modules sym1, sym2, ... and merges them into the current scope"
name = (child\quote scope)\unwrap 'sym'
scope\use (Value.wrap require "lib.#{name}")\unwrap 'scope'
- ResultNode!
+ Result!
class fn extends Action
@doc: "(fn (p1 [p2]...) body-expr) - declare a (lambda) function
@@ -117,7 +117,7 @@ the symbols p1, p2, ... will resolve to the arguments passed to the function."
param\quote scope
body = body\quote scope
- ResultNode value: Value.wrap FnDef param_symbols, body, scope
+ Result value: Value.wrap FnDef param_symbols, body, scope
class defn extends Action
@doc: "(defn name-sym (p1 [p2]...) body-expr) - define a function
@@ -138,8 +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, Value.wrap fn
- ResultNode!
+ scope\set name, Result value: Value.wrap fn
+ Result!
class do_expr extends Action
@doc: "(do expr1 [expr2]...) - update multiple expressions
@@ -148,7 +148,7 @@ evaluates and continously updates expr1, expr2, ...
the last expression's value is returned."
eval: (scope, tail) =>
- ResultNode children: [expr\eval scope for expr in *tail]
+ Result 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
@@ -164,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\value_only!\const!\unwrap!
+ xif = xif\const!\unwrap!
if xif
xthen\eval scope
@@ -189,8 +189,8 @@ class trace extends Action
import: import_
'import*': import_star
- true: Const.bool true
- false: Const.bool false
+ true: Value.bool true
+ false: Value.bool false
:fn, :defn
'do': do_expr
diff --git a/core/cell.moon b/core/cell.moon
index 642e1a6..0cb8e9f 100644
--- a/core/cell.moon
+++ b/core/cell.moon
@@ -1,5 +1,5 @@
-- ALV Cell type
-import Const from require 'core.value'
+import Value from require 'core.value'
import op_invoke, fn_invoke from require 'core.invoke'
import Tag from require 'core.tag'
@@ -11,7 +11,8 @@ class Cell
-- white: optional sequence of whitespace segments ([0 .. #@children])
new: (@tag=Tag.blank!, @children, @white) =>
if not @white
- @white = ['' for i=1,#@children+1]
+ @white = ['' for i=1,#@children]
+ @white[0] = ''
assert #@white == #@children, "mismatched whitespace length"
@@ -22,8 +23,7 @@ class Cell
-- AST interface
eval: (scope) =>
- head_result = @head!\eval scope
- head = head_result.value\const!
+ head = (@head!\eval scope)\const!
Action = switch head.type
when 'opdef'
-- scope\get 'op-invoke'
@@ -89,7 +89,7 @@ class Cell
--
-- evaluates with an implicit 'do' in the head
class RootCell extends Cell
- head: => Const.sym 'do'
+ head: => Value.sym 'do'
tail: => @children
stringify: =>
diff --git a/core/init.moon b/core/init.moon
index b885747..a309722 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 Stream, Const, load_ from require 'core.value'
+import Value, Result, load_ from require 'core.value'
import Scope from require 'core.scope'
load_!
@@ -15,7 +15,7 @@ import cell, program from require 'core.parsing'
globals = Scope.from_table require 'core.builtin'
{
- :Stream, :Const
+ :Value, :Result
:Cell, :RootCell
:Op, :Action, :FnDef
:Scope
@@ -30,5 +30,5 @@ globals = Scope.from_table require 'core.builtin'
ast = assert (cell\match str), "failed to parse: #{str}"
result = ast\eval scope
- result\value_only!
+ result\const!
}
diff --git a/core/invoke.moon b/core/invoke.moon
index 5a6c420..a3932f8 100644
--- a/core/invoke.moon
+++ b/core/invoke.moon
@@ -1,4 +1,4 @@
-import ResultNode, Value from require 'core.value'
+import Result, Value from require 'core.value'
import Action from require 'core.base'
import Scope from require 'core.scope'
@@ -8,16 +8,17 @@ class op_invoke extends Action
@op\destroy! if @op
- def = head\const!\unwrap 'opdef', "cant op-invoke #{@head}"
+ def = head\unwrap 'opdef', "cant op-invoke #{@head}"
@head, @op = head, def!
true
eval: (scope, tail) =>
children = L\push -> [L\push expr\eval, scope for expr in *tail]
- value = @op\setup unpack [child.value for child in *children]
+ @op\setup [child.value for child in *children]
+ @op\tick true
- ResultNode :children, :value, op: @op
+ Result :children, value: @op.out, op: @op
class fn_invoke extends Action
-- @TODO:
@@ -32,7 +33,7 @@ class fn_invoke extends Action
true
eval: (outer_scope, tail) =>
- { :params, :body, :scope } = @head\const!\unwrap 'fndef', "cant fn-invoke #{@head}"
+ { :params, :body, :scope } = @head\unwrap 'fndef', "cant fn-invoke #{@head}"
assert #params == #tail, "argument count mismatch in #{@head}"
@@ -47,7 +48,7 @@ class fn_invoke extends Action
result = body\eval fn_scope
table.insert children, result
- ResultNode :children, value: result.value
+ Result :children, value: result.value
{
:op_invoke, :fn_invoke
diff --git a/core/parsing.moon b/core/parsing.moon
index 96d8125..03ef85c 100644
--- a/core/parsing.moon
+++ b/core/parsing.moon
@@ -1,4 +1,4 @@
-import Const from require 'core.value'
+import Value 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'
@@ -16,15 +16,15 @@ mspace = (comment + wc)^0 / 1 -- optional whitespace
-- atoms
digit = R '09'
first = (R 'az', 'AZ') + S '-_+*/.!?='
-sym = first * (first + digit)^0 / Const\parse 'sym'
+sym = first * (first + digit)^0 / Value\parse 'sym'
-strd = '"' * (C ((P '\\"') + (P '\\\\') + (1 - P '"'))^0) * '"' / Const\parse 'str', '\"'
-strq = "'" * (C ((P "\\'") + (P '\\\\') + (1 - P "'"))^0) * "'" / Const\parse 'str', '\''
+strd = '"' * (C ((P '\\"') + (P '\\\\') + (1 - P '"'))^0) * '"' / Value\parse 'str', '\"'
+strq = "'" * (C ((P "\\'") + (P '\\\\') + (1 - P "'"))^0) * "'" / Value\parse 'str', '\''
str = strd + strq
int = digit^1
float = (digit^1 * '.' * digit^0) + (digit^0 * '.' * digit^1)
-num = (float + int) / Const\parse 'num'
+num = (float + int) / Value\parse 'num'
atom = num + sym + str
diff --git a/core/registry.moon b/core/registry.moon
index 4dd5bbb..e8272fa 100644
--- a/core/registry.moon
+++ b/core/registry.moon
@@ -1,7 +1,12 @@
+import Value from require 'core.value'
+
class Registry
new: () =>
@map = {}
+ @tick = 0
+ @kr = Value.bool true
+
-- methods for Tag
last: (index) => @last_map[index]
@@ -15,37 +20,44 @@ class Registry
L\trace "reg: init pending to #{expr}"
table.insert @pending, { :tag, :expr }
- active: ->
- assert Registry.active_registry, "no active Registry!"
+ active: -> assert Registry.active_registry, "no active Registry!"
-- public methods
- wrap: (fn) =>
- (...) ->
- @prepare!
- with fn ...
- @finalize!
-
- prepare: =>
- assert not @prev, "already have a previous registry? #{@prev}"
- @prev, @@active_registry = @@active_registry, @
+ wrap_eval: (fn) => (...) ->
+ @grab!
@last_map, @map, @pending = @map, {}, {}
- finalize: =>
- for tag, val in pairs @last_map
- if not @map[tag]
- val\destroy!
+ with fn ...
+ for tag, val in pairs @last_map
+ if not @map[tag]
+ val\destroy!
- for { :tag, :expr } in *@pending
- -- tag was solved by another pending registration
- -- (e.g. first [A] is solved, then [5.A] is solved)
- continue if tag\index!
+ for { :tag, :expr } in *@pending
+ -- tag was solved by another pending registration
+ -- (e.g. first [A] is solved, then [5.A] is solved)
+ continue if tag\index!
- next_tag = @next_tag!
- L\trace "assigned new tag #{next_tag} to #{tag} #{expr}"
- tag\set next_tag
- @map[tag\index!] = expr
+ next_tag = @next_tag!
+ L\trace "assigned new tag #{next_tag} to #{tag} #{expr}"
+ tag\set next_tag
+ @map[tag\index!] = expr
+
+ @release!
+
+ wrap_tick: (fn) => (...) ->
+ @grab!
+ @tick += 1
+ @kr\set true
+
+ with fn ...
+ @release!
+
+ grab: =>
+ assert not @prev, "already have a previous registry? #{@prev}"
+ @prev, @@active_registry = @@active_registry, @
+ release: =>
assert @ == @@active_registry, "not the active registry!"
@@active_registry, @prev = @prev, nil
diff --git a/core/scope.moon b/core/scope.moon
index aaa38f2..f795f97 100644
--- a/core/scope.moon
+++ b/core/scope.moon
@@ -1,12 +1,16 @@
-import Value from require 'core.value'
+import Result, Value from require 'core.value'
class Scope
new: (@node, @parent) =>
@values = {}
- set_raw: (key, val) => @values[key] = Value.wrap val, key
+ set_raw: (key, val) =>
+ value = Value.wrap val, key
+ @values[key] = Result :value
+
set: (key, val) =>
L\trace "setting #{key} = #{val} in #{@}"
+ assert val.__class == Result, "expected #{key}=#{val} to be Result"
@values[key] = val
get: (key, prefix='') =>
@@ -20,9 +24,9 @@ class Scope
if not start
return @parent and L\push -> @parent\get key
- scope = @get start
- assert scope and scope.type == 'scope', "cant find '#{prefix}#{start}' for '#{prefix}#{key}'"
- scope\unwrap!\get rest, "#{prefix}#{start}/"
+ child = @get start
+ assert child and child.value.type == 'scope', "#{start} is not a scope (looking for #{key})"
+ child.value\unwrap!\get rest, "#{prefix}#{start}/"
use: (other) =>
L\trace "using defs from #{other} in #{@}"
@@ -31,7 +35,8 @@ class Scope
from_table: (tbl) ->
with Scope!
- .values = { k, Value.wrap v, k for k,v in pairs tbl }
+ for k, v in pairs tbl
+ \set_raw k, v
__tostring: =>
buf = "<Scope"
diff --git a/core/value.moon b/core/value.moon
index f3f9796..986c846 100644
--- a/core/value.moon
+++ b/core/value.moon
@@ -1,9 +1,9 @@
-- ALV Value types
-import Op, Action, FnDef from require 'core.base'
-
-local Scope
+local Scope, Registry, Op, Action, FnDef
load_ = ->
import Scope from require 'core.scope'
+ import Registry from require 'core.registry'
+ import Op, Action, FnDef from require 'core.base'
ancestor = (klass) ->
assert klass, "cant find the ancestor of nil"
@@ -16,26 +16,73 @@ ancestor = (klass) ->
-- - a Value
-- - an Op (to update)
-- - children (results of subexpressions that were evaluated)
+-- - cached list of all Dispatchers affecting all Ops in the subtree
--
--- ResultNodes form a tree that controls execution order and message passing
+-- Results form a tree that controls execution order and message passing
-- between Ops.
-class ResultNode
+class Result
-- params: table with optional keys op, value, children
new: (params={}) =>
- @op = params.op
@value = params.value
+ @op = params.op
@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"
+ @all_impulses = {}
+ for child in *@children
+ for d in pairs child.all_impulses
+ @all_impulses[d] = true
+
+ if @op
+ assert @op.impulses, "#{@op} not set up correctly (impulses)"
+ for d in *@op.impulses
+ @all_impulses[d] = true
+
+ -- asserts value-constness and returns the value
+ const: (msg) =>
+ assert not (next @all_impulses), msg or "eval-time const expected"
@value
- update: (dt) =>
+ -- create a value-copy of this result that has the same impulses but without
+ -- affecting the original's update logic
+ make_ref: =>
+ with Result value: @value
+ .all_impulses = @all_impulses
+
+ -- in depth-first order, tick all Ops who have dirty Stream inputs or impulses
+ --
+ -- short-circuits if there are no dirty Streams in the entire subtree
+ tick: =>
+ any_dirty = false
+ for stream in pairs @all_impulses
+ if stream\dirty!
+ any_dirty = true
+ break
+
+ -- early-out if no streams are dirty in this whole subtree
+ return unless any_dirty
+
for child in *@children
- child\update dt
- @op\update dt if @op
+ child\tick!
+
+ if @op
+ -- we have to check self_dirty here, because streams from child
+ -- expressions might have changed
+ self_dirty = false
+ for stream in *@op.impulses
+ if stream\dirty!
+ self_dirty = true
+ break
+ for stream in *@op.inputs
+ if stream\dirty!
+ self_dirty = true
+ break
+
+ L\trace "#{op} is #{if self_dirty then 'dirty' else 'clean'}"
+ return unless self_dirty
+
+ @op\tick!
+-- static
__tostring: =>
buf = "<result=#{@value}"
buf ..= " #{@op}" if @op
@@ -43,19 +90,18 @@ class ResultNode
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) =>
+ new: (@type, @value, @raw) =>
+ @updated = 0
- -- asserts value-constness
- -- returns self (for chaining)
- const: => error 'not a constant'
+ dirty: => @updated == Registry.active!.tick
+
+ set: (@value) => @updated = Registry.active!.tick
-- unwrap to the Lua type
-- asserts @type == type, msg if given
@@ -63,14 +109,30 @@ class Value
assert type == @type, msg or "#{@} is not a #{type}" if type
@value
+-- AST interface
+ eval: (scope) =>
+ switch @type
+ when 'num', 'str'
+ Result value: @
+ when 'sym'
+ assert (scope\get @value), "undefined reference to symbol '#{@value}'"
+ else
+ error "cannot evaluate #{@}"
+
+ quote: => @
+
+ stringify: => assert @raw, "stringifying Value that wasn't parsed"
+
+ clone: (prefix) => @
+ -- in case of doubt:
+ -- clone: (prefix) => Value @type, @value, @raw
+
-- static
__tostring: =>
value = if 'table' == (type @value) and rawget @value, '__base' then @value.__name else @value
"<#{@@__name} #{@type}: #{value}>"
+ __call: (...) => @unwrap ...
__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)') ->
@@ -96,62 +158,26 @@ class Value
error "#{name}: cannot wrap '#{val.__class.__name}' instance"
else
-- plain table
- return Const 'scope', Scope.from_table val
+ return Value '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
+ Value typ, val
- 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
+ @num: (num) -> Value 'num', num, tostring num
+ @str: (str) -> Value 'str', str, "'#{str}'"
+ @sym: (sym) -> Value 'sym', sym, sym
+ @bool: (bool) -> Value 'bool', bool, tostring bool
{
- :ResultNode
+ :Result
:Value
- :Stream, :Const
:load_
}
diff --git a/init.moon b/init.moon
index d4f7e85..8bb80e6 100644
--- a/init.moon
+++ b/init.moon
@@ -1,7 +1,7 @@
-- run from CLI
-import monotime, sleep from require 'system'
import Logger from require 'logger'
import Copilot from require 'copilot'
+import sleep from require 'system'
arguments, key = {}
for a in *arg
@@ -16,25 +16,8 @@ for a in *arg
Logger.init arguments.log
-delta = do
- period = 1 / 60
-
- local last
- ->
- if last
- target, current = (last + period), monotime!
- if current > target
- L\warn 'Frame Skipped!'
- else
- sleep target - current
-
- time = monotime!
- with time - (last or time)
- last = time
-
copilot = Copilot arguments[1]
while true
- dt = delta!
-
- copilot\update dt
+ copilot\tick!
+ sleep 1 / 1000
diff --git a/lib/debug.moon b/lib/debug.moon
index f642c2e..08a7f78 100644
--- a/lib/debug.moon
+++ b/lib/debug.moon
@@ -3,10 +3,13 @@ import Op from require 'core'
class out extends Op
@doc: "(out name-str value) - log value to the console"
- setup: (@name, @value) =>
+ setup: (params) =>
+ super params
+ assert @inputs[2], "need a value"
+ @assert_types 'str', @inputs[2].type
- update: (dt) =>
- L\print "#{@name\unwrap 'str'}", @value\unwrap!
+ tick: =>
+ L\print @unwrap_inputs!
{
:out
diff --git a/lib/midi/core.moon b/lib/midi/core.moon
index 891de0a..7f661ed 100644
--- a/lib/midi/core.moon
+++ b/lib/midi/core.moon
@@ -1,5 +1,6 @@
import RtMidiIn, RtMidiOut, RtMidi from require 'luartmidi'
import band, bor, lshift, rshift from require 'bit32'
+import Op, Registry from require 'core'
MIDI = {
[0x9]: 'note-on'
@@ -15,75 +16,101 @@ MIDI = {
rMIDI = {v,k for k,v in pairs MIDI}
-class Input
- new: (name) =>
- @input = RtMidiIn RtMidi.Api.UNIX_JACK
-
+find_port = (Klass, name) ->
+ with Klass RtMidi.Api.UNIX_JACK
id = nil
- for port=1,@input\getportcount!
- if name == @input\getportname port
+ for port=1, \getportcount!
+ if name == \getportname port
id = port
break
- @input\openport id
+ \openport id
+
+class MidiPort
+ new: (@inp, @out) =>
- @listeners = {}
+ dirty: =>
+ @updated == Registry.active!.tick
tick: =>
- while true
- delta, bytes = @input\getmessage!
- break unless delta
-
- { status, a, b } = bytes
- chan = band status, 0xf
- status = MIDI[rshift status, 4]
- @dispatch status, chan, a, b
-
- dispatch: (status, chan, a, b) =>
- L\trace "dispatching MIDI event #{status} CH#{chan} #{a} #{b}"
- for mask, handler in pairs @listeners
- match = true
- match and= status == mask.status if mask.status
- match and= chan == mask.chan if mask.chan
- match and= a == mask.a if mask.a
- if match
- handler status, chan, a, b
-
- -- 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
-
-class Output
- new: (name) =>
- @output = RtMidiOut RtMidi.Api.UNIX_JACK
+ if @inp
+ @messages = while true
+ delta, bytes = @inp\getmessage!
+ break unless delta
+
+ { status, a, b } = bytes
+ chan = band status, 0xf
+ status = MIDI[rshift status, 4]
+ { :status, :chan, :a, :b }
+
+ if @messages
+ @updated = Registry.active!.tick
+
+ receive: =>
+ assert @inp, "#{@} is not an input port"
+ return unless @messages
+ coroutine.wrap ->
+ for msg in *@messages
+ coroutine.yield msg
- id = nil
- for port=1,@output\getportcount!
- if name == @output\getportname port
- id = port
- break
+ send: (status, chan, a, b) =>
+ assert @out, "#{@} is not an output port"
+ if 'string' == type 'status'
+ status = bor (lshift rMIDI[status], 4), chan
+ @out\sendmessage status, a, b
- @output\openport id
+class input extends Op
+ @doc: "(midi/input name) - create a MIDI input port"
- send: (status, chan, a, b) =>
- status = bor (lshift rMIDI[status], 4), chan
- @output\sendmessage status, a, b
+ new: =>
+ super 'midi/port'
+ @impulses = { Registry.active!.kr }
+
+ setup: (params) =>
+ super params
+ @assert_types 'str'
+
+ tick: (first) =>
+ { name } = @inputs
+ if first or name\dirty!
+ @out\set MidiPort find_port RtMidiIn, name\unwrap!
+
+ @out\unwrap!\tick!
+
+class output extends Op
+ @doc: "(midi/output name) - create a MIDI output port"
+
+ new: =>
+ super 'midi/port'
+
+ setup: (params) =>
+ super params
+ @assert_types 'str'
+
+ tick: (first) =>
+ { name } = @inputs
+ if first or name\dirty!
+ @out\set MidiPort nil, find_port RtMidiOut, name\unwrap!
+
+class inout extends Op
+ @doc: "(midi/inout inname outname) - create a bidirectional MIDI port"
+
+ new: =>
+ super 'midi/port'
+ @impulses = { Registry.active!.kr }
+
+ setup: (params) =>
+ super params
+ @assert_types 'str', 'str'
+
+ tick: (first) =>
+ { inp, out } = @inputs
-class InOut
- new: (inp, out) =>
- @inp = Input inp
- @out = Output out
+ if first or inp\dirty! or out\dirty!
+ inp, out = inp\unwrap!, out\unwrap!
+ @out\set MidiPort (find_port RtMidiIn, inp), (find_port RtMidiOut, out)
- tick: (...) => @inp\tick ...
- attach: (...) => @inp\attach ...
- detach: (...) => @inp\detach ...
- send: (...) => @out\send ...
+ @out\unwrap!\tick!
apply_range = (range, val) ->
if range.type == 'str'
@@ -100,8 +127,8 @@ apply_range = (range, val) ->
error "range has to be a string or number"
{
- :Input
- :Output
- :InOut
+ :input
+ :output
+ :inout
:apply_range
}
diff --git a/lib/midi/init.moon b/lib/midi/init.moon
index c9e2476..03f5115 100644
--- a/lib/midi/init.moon
+++ b/lib/midi/init.moon
@@ -1,34 +1,31 @@
-import Stream, Const, Op from require 'core'
-import Input, apply_range from require 'lib.midi.core'
-
-dispatch = Input 'system:midi_capture_4'
+import Value, Op from require 'core'
+import input, output, inout, apply_range from require 'lib.midi.core'
class gate extends Op
- @doc: "(midi/gate note [chan]) - gate from note-on and note-off messages"
+ @doc: "(midi/gate port note [chan]) - gate from note-on and note-off messages"
- destroy: =>
- dispatch\detach @mask if @mask
-
- setup: (note, chan) =>
- dispatch\detach @mask if @mask
+ new: =>
+ super 'bool', false
- note = note\const!\unwrap 'num'
- chan = chan and chan\const!\unwrap 'num'
- @value = false
+ setup: (params) =>
+ super params
+ @inputs[3] or= Value.num -1
+ @assert_types 'midi/port', 'num', 'num'
+ @impulses = { @inputs[1]\unwrap! }
- @mask = dispatch\attach { :chan, a: note }, (status) ->
- if status == 'note-on'
- @out\set true
- else if status == 'note-off'
- @out\set false
+ tick: =>
+ local port, note, chan
+ { port, note, chan } = [i\unwrap! for i in *@inputs]
- @out = Stream 'bool', false
- @out
-
- update: (dt) => dispatch\tick!
+ for msg in port\receive!
+ if msg.a == note and (chan == -1 or msg.chan == chan)
+ if msg.status == 'note-on'
+ @out\set true
+ elseif msg.status == 'note-off'
+ @out\set false
class cc extends Op
- @doc: "(midi/cc cc [chan [range]]) - MIDI CC to number
+ @doc: "(midi/cc port cc [chan [range]]) - MIDI CC to number
range can be one of:
- 'raw' [ 0 - 128[
@@ -37,24 +34,36 @@ range can be one of:
- 'rad' [ 0 - tau[
- (num) [ 0 - num["
- destroy: =>
- dispatch\detach @mask if @mask
+ new: =>
+ super 'num'
- setup: (cc, chan, @range=Const.str'uni') =>
+ destroy: =>
dispatch\detach @mask if @mask
- cc = cc\const!\unwrap 'num'
- chan = chan and chan\const!\unwrap 'num'
+ setup: (params) =>
+ super params
+ @inputs[3] or= Value.num -1
+ @inputs[4] or= Value.str 'uni'
+ @assert_types 'midi/port', 'num', 'num', 'str'
+ @impulses = { @inputs[1]\unwrap! }
- @mask = dispatch\attach { status: 'control-change', :chan, a: cc }, (_, _, _, val) ->
- @out\set apply_range @range, val
+ if not @out\unwrap!
+ @out\set apply_range @inputs[4], 0
- @out = Stream 'num', 0
- @out
+ tick: =>
+ local port, cc, chan
+ { port, cc, chan } = [i\unwrap! for i in *@inputs]
- update: (dt) => dispatch\tick!
+ for msg in port\receive!
+ if msg.status == 'control-change' and
+ (chan == -1 or msg.chan == chan) and
+ msg.a == cc
+ @out\set apply_range @inputs[4], msg.b
{
+ :input
+ :output
+ :inout
:gate
:cc
}
diff --git a/lib/midi/launchctl.moon b/lib/midi/launchctl.moon
index 778a724..c0d5e24 100644
--- a/lib/midi/launchctl.moon
+++ b/lib/midi/launchctl.moon
@@ -1,13 +1,12 @@
-import Stream, Const, Op from require 'core'
-import InOut, apply_range from require 'lib.midi.core'
+import Value, Op from require 'core'
+import apply_range from require 'lib.midi.core'
import bor, lshift from require 'bit32'
-launch = InOut 'system:midi_capture_4', 'system:midi_playback_4'
-
+unpack or= table.unpack
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
+ @doc: "(launctl/cc-seq port i start chan [steps [range]]) - CC-Sequencer
returns the value for the i-th step steps (buttons starting from start).
steps defaults to 8.
@@ -19,43 +18,41 @@ range can be one of:
- 'rad' [ 0 - tau[
- (num) [ 0 - num["
- destroy: =>
- launch\detach @mask if @mask
-
new: =>
+ super 'num'
@steps = {}
- @out = Stream 'num'
- setup: (@i, start, chan, steps=(Const.num 8), @range=Const.str 'uni') =>
- launch\detach @mask if @mask
+ setup: (params) =>
+ super params
- @start = start\const!\unwrap 'num'
- @chan = chan\const!\unwrap 'num'
- steps = steps\const!\unwrap 'num'
+ @inputs[5] or= Value.num 8
+ @inputs[6] or= Value.str 'uni'
+ @assert_types 'midi/port', 'num', 'num', 'num', 'num', 'str'
+ @impulses = { @inputs[1]\unwrap! }
- 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
+ if not @out\unwrap!
+ @out\set apply_range @inputs[6], 0
- change: (cc, val) =>
- i = cc - @start
- if i < #@steps
- @steps[i+1] = val
+ tick: =>
+ port, i, start, chan, steps = unpack [i\unwrap! for i in *@inputs]
+ port = @inputs[1]\unwrap!
+ _, i, start, chan, steps = unpack @inputs
- update: (dt) =>
- launch\tick!
-
- curr_i = (@i\unwrap 'num') % #@steps
+ curr_i = i\unwrap! % #@steps
+ changed = false
- @out\set apply_range @range, @steps[curr_i+1]
+ for msg in port\receive!
+ if msg.status == 'control-change' and msg.chan == chan
+ i = msg.a - start
+ if i < #@steps
+ @steps[i+1] = msg.b
+ changed = i == curr_i
+ if changed or i\dirty! or start\dirty! or chan\dirty! or steps\diry!
+ @out\set apply_range @inputs[6], @steps[curr_i+1]
class gate_seq extends Op
- @doc: "(launctl/gate-seq i start chan [steps]) - Gate-Sequencer
+ @doc: "(launctl/gate-seq port i start chan [steps]) - Gate-Sequencer
returns true or false for the i-th step steps (buttons starting from start).
steps defaults to 8."
diff --git a/lib/osc.moon b/lib/osc.moon
index fed1697..3bac3f1 100644
--- a/lib/osc.moon
+++ b/lib/osc.moon
@@ -2,22 +2,39 @@ import Stream, Op from require 'core'
import pack, unpack from require 'osc'
import dns, udp from require 'socket'
+class addr extends Op
+ @doc: "(remote host port) - UDP remote definition"
+
+ new: =>
+ super 'udp/remote'
+ @@udp or= udp!
+
+ setup: (params) =>
+ super params
+ @assert_types 'str', 'num'
+
+ tick: =>
+ host, port = @unwrap_inputs!
+ ip = dns.toip host
+ @out\set { :ip, :port }
+
class out extends Op
@doc: "(out host port path val) - send a value via OSC"
new: (...) =>
- super ...
-
@@udp or= udp!
- setup: (@host, @port, @path, @value) =>
+ setup: (params) =>
+ super params
+ assert @inputs[3], "need a value"
+ @assert_types 'udp/remote', 'str', @inputs[3].type
update: (dt) =>
- ip = dns.toip @host\unwrap 'str'
- port = @port\unwrap 'num'
- msg = pack (@path\unwrap 'str'), @value\unwrap!
+ remote, path, value = @unwrap_inputs!
+ msg = pack path, value
@@udp\sendto msg, ip, port
{
+ :addr
:out
}
diff --git a/lib/time.moon b/lib/time.moon
index 7ce8b84..c49a7d7 100644
--- a/lib/time.moon
+++ b/lib/time.moon
@@ -1,7 +1,31 @@
-import Stream, Const, Op from require 'core'
+import Registry, Value, Op from require 'core'
+import monotime from require 'system'
+
+delta = do
+ period = 1 / 60
+
+ local last
+ ->
+
+class clock extends Op
+ new: =>
+ super 'num'
+ @impulses = { Registry.active!.kr }
+ @out\set 0
+
+ setup: (params) =>
+ super params
+ @last = monotime!
+
+ tick: =>
+ time = monotime!
+ dt = time - @last
+ if dt >= 1/60
+ @out\set dt
+ @last = time
class lfo extends Op
- @doc: "(lfo freq [wave]) - low-frequency oscillator
+ @doc: "(lfo clock freq [wave]) - low-frequency oscillator
oscillates between 0 and 1 at the frequency freq.
wave selects the wave shape from the following (default sin):
@@ -10,89 +34,96 @@ wave selects the wave shape from the following (default sin):
- tri"
tau = math.pi * 2
- new: (...) =>
- super ...
- print "creating LFO"
- @out = Stream 'num'
+ new: =>
+ super '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
+ default_wave = Value.str 'sin'
+ setup: (params) =>
+ super params
- update: (dt) =>
- @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
- else error "unknown wave type"
+ @inputs[3] or= default_wave
+ @assert_types 'num', 'num', 'str'
+
+ tick: =>
+ -- if clock is dirty
+ if @inputs[1].dirty
+ dt, freq, wave = @unwrap_inputs!
+ @phase += dt * freq
+ @out\set switch wave
+ when 'sin' then .5 + .5 * math.cos @phase * tau
+ when 'saw' then @phase % 1
+ when 'tri' then math.abs (2*@phase % 2) - 1
+ else error "unknown wave type"
class ramp extends Op
- @doc: "(ramp period [max]) - sawtooth lfo
+ @doc: "(ramp clock period [max]) - sawtooth lfo
-ramps from 0 to max (default same as ramp) once every period seconds"
+ramps from 0 to max (default same as ramp) once every period seconds."
- new: (...) =>
- super ...
- @out = Stream 'num'
+ new: =>
+ super 'num'
@phase = 0
- @out
- setup: (@period, @max) =>
- assert @period, "tick requires a period value"
+ setup: (params) =>
+ super params
+ assert @inputs[1].type == 'num', "tick requires a clock value"
+ assert @inputs[2].type == 'num', "tick requires a period value"
- update: (dt) =>
- period = @period\unwrap 'num'
- max = if @max then @max\unwrap! else period
- @phase += dt / period
+ tick: =>
+ -- if clock is dirty
+ if @inputs[1].dirty
+ dt, period, max = @unwrap_inputs!
+ max or= period
+ @phase += dt / period
- if @phase >= 1
- @phase -= 1
+ if @phase >= 1
+ @phase -= 1
- @out\set @phase * max
+ @out\set @phase * max
class tick extends Op
- @doc: "(tick period) - count ticks
+ @doc: "(tick clock period) - count ticks
counts upwards by one every period seconds and returns the number of completed ticks."
- new: (...) =>
- super ...
- @out = Stream 'num'
+ new: =>
+ super 'num'
@phase = 0
- setup: (@period) =>
- assert @period, "tick requires a period value"
- @out
+ setup: (params) =>
+ @assert_types 'num', 'num'
update: (dt) =>
- @phase += dt / @period\unwrap 'num'
- @out\set math.floor @phase
+ -- if clock is dirty
+ if @inputs[1].dirty
+ dt, period = @unwrap_inputs!
+ @phase += dt / period
+
+ @out\set math.floor @phase
class every extends Op
- @doc: "(every period) - sometimes true
+ @doc: "(every clock period) - trigger every period seconds
returns true once every period seconds."
- new: (...) =>
- super ...
- @out = Stream 'bool'
+ new: =>
+ super 'bang'
@phase = 0
setup: (@period) =>
- assert @period, "every requires a period value"
- @out
+ @assert_types 'num', 'num'
update: (dt) =>
- @phase += dt / @period\unwrap 'num'
- if @phase > 1
- @phase -= 1
- @out\set true
- else
- @out\set false
+ -- if clock is dirty
+ if @inputs[1].dirty
+ dt, period = @unwrap_inputs!
+ @phase += dt / period
+
+ if @phase >= 1
+ @phase -= 1
+ @out\set true
{
+ :clock
:lfo
:ramp
:tick
diff --git a/lib/util.moon b/lib/util.moon
index 192c489..04d012f 100644
--- a/lib/util.moon
+++ b/lib/util.moon
@@ -1,4 +1,4 @@
-import Stream, Const, Op from require 'core'
+import Op from require 'core'
class switch_ extends Op
@doc: "(switch i v0 [v1 v2...]) - switch between multiple inputs
@@ -7,85 +7,91 @@ when i is true, the first value is reproduced.
when i is false, the second value is reproduced.
when i is a num, it is (floor)ed and the matching argument (starting from 0) is reproduced."
- setup: (@i, ...) =>
- @choices = { ... }
+ setup: (params) =>
+ super params
- typ = @choices[1].type
- for inp in *@choices[2,]
- assert inp.type == typ, "not all values have the same type: #{typ} != #{inp.type}"
+ { i, first } = @inputs
+ assert i.type == 'bool' or i.type == 'num', "#{@}: i has to be bool or num"
- @out = Stream typ
- @out
+ for inp in *@inputs[3,]
+ assert inp.type == first.type, "not all values have the same type: #{first.type} != #{inp.type}"
+ @out = Stream first.type
update: (dt) =>
- i = @i\unwrap!
+ i = @inputs[1]\unwrap!
active = switch i
when true
- @choices[1]
+ @inputs[2]
when false
- @choices[2]
+ @inputs[3]
else
- i = 1 + (math.floor i) % #@choices
- @choices[i]
+ i = 2 + (math.floor i) % (#@inputs - 1)
+ @inputs[i]
@out\set active and active\unwrap!
-class switch_pause extends Op
- @doc: "(switch- i v0 [v1 v2...]) - switch and pause multiple inputs
+--class switch_pause extends Op
+-- @doc: "(switch- i v0 [v1 v2...]) - switch and pause multiple inputs
+--
+--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 = @i\unwrap!
+-- active = switch i
+-- when true
+-- @choices[1]
+-- when false
+-- @choices[2]
+-- else
+-- i = 1 + (math.floor i) % #@choices
+-- @choices[i]
+--
+-- @out\set if active
+-- active\unwrap!
-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 = @i\unwrap!
- active = switch i
- when true
- @choices[1]
- when false
- @choices[2]
- else
- i = 1 + (math.floor i) % #@choices
- @choices[i]
+class edge extends Op
+ @doc: "(edge bool) - convert rising edges to bangs"
- @out\set if active
- active\unwrap!
+ new: =>
+ super 'bang'
-class edge extends Op
- setup: (@i) =>
- @last = false
- @out = Stream @i.type
- @out
+ setup: (params) =>
+ super params
+ @assert_types 'bool'
update: (dt) =>
- now = @i\unwrap!
- @out\set not @last and now
- @last = now
+ now = @params[1]\unwrap!
+ if now and not @last
+ @out\set true
+ @last = now
class keep extends Op
- @doc: "(keep value [default]) - keep the last non-nil value
+ @doc: "(keep value [init]) - keep the last non-nil value
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
+ setup: (params) =>
+ super params
+ { i, init } = @inputs
+ @out = Value i.type, default and init\unwrap!
- update: (dt) =>
- if next = @i\unwrap!
+ tick =>
+ if next = @params[1]\unwrap!
@out\set next
{
'switch': switch_
- 'switch-': switch_pause
+-- 'switch-': switch_pause
:edge
:keep
}
diff --git a/spec/cell_spec.moon b/spec/cell_spec.moon
index b31ef99..fcf9861 100644
--- a/spec/cell_spec.moon
+++ b/spec/cell_spec.moon
@@ -1,31 +1,31 @@
-import Cell, RootCell, Const, Scope, globals from require 'core'
+import Cell, RootCell, Value, Scope, globals from require 'core'
import Registry from require 'core.registry'
import Logger from require 'logger'
Logger.init 'silent'
-hello_world = Cell nil, { (Const.sym 'hello'), (Const.str 'world') }
-two_plus_two = Cell nil, { (Const.sym '+'), (Const.num 2), (Const.num 2) }
+hello_world = Cell nil, { (Value.sym 'hello'), (Value.str 'world') }
+two_plus_two = Cell nil, { (Value.sym '+'), (Value.num 2), (Value.num 2) }
describe 'Cell', ->
it 'supports quoting', ->
with hello_world\quote!
assert.is.equal Cell, .__class
- assert.is.equal (Const.sym 'hello'), \head!
- assert.is.same { Const.str 'world' }, \tail!
+ assert.is.equal (Value.sym 'hello'), \head!
+ assert.is.same { Value.str 'world' }, \tail!
with two_plus_two\quote!
assert.is.equal Cell, .__class
- assert.is.equal (Const.sym '+'), \head!
- assert.is.same { (Const.num 2), (Const.num 2) }, \tail!
+ assert.is.equal (Value.sym '+'), \head!
+ assert.is.same { (Value.num 2), (Value.num 2) }, \tail!
describe 'RootCell', ->
test 'head is always "do"', ->
cell = RootCell\parse {}
- assert.is.equal (Const.sym 'do'), cell\head!
+ assert.is.equal (Value.sym 'do'), cell\head!
cell = RootCell nil, { hello_world, two_plus_two }
- assert.is.equal (Const.sym 'do'), cell\head!
+ assert.is.equal (Value.sym 'do'), cell\head!
test 'tail is all children', ->
cell = RootCell\parse {}
diff --git a/spec/const_spec.moon b/spec/const_spec.moon
deleted file mode 100644
index 4def3a6..0000000
--- a/spec/const_spec.moon
+++ /dev/null
@@ -1,122 +0,0 @@
-import Const, Op, Action, Scope from require 'core'
-import Logger from require 'logger'
-Logger.init 'silent'
-
-class TestOp extends Op
- new: (...) => super ...
-
-class TestAction extends Action
- new: (...) =>
-
-describe 'Const', ->
- describe 'wraps', ->
- test 'numbers', ->
- got = Const.wrap 3
- assert.is.equal 'num', got.type
- assert.is.equal 3, got.value
-
- test 'strings', ->
- got = Const.wrap "im a happy string"
- assert.is.equal 'str', got.type
- assert.is.equal "im a happy string", got.value
-
- test 'Consts', ->
- pi = Const 'num', 3.14
- got = Const.wrap pi
-
- assert.is.equal pi, got
-
- test 'Opdefs', ->
- got = Const.wrap TestOp
-
- assert.is.equal 'opdef', got.type
- assert.is.equal TestOp, got.value
-
- test 'Bultins', ->
- got = Const.wrap TestAction
-
- assert.is.equal 'builtin', got.type
- assert.is.equal TestAction, got.value
-
- test 'Scopes', ->
- sub = Scope!
- got = Const.wrap sub
-
- assert.is.equal 'scope', got.type
- assert.is.equal sub, got.value
-
- test 'tables', ->
- pi = Const 'num', 3.14
- got = Const.wrap { :pi }
-
- assert.is.equal 'scope', got.type
- assert.is.equal pi, got.value\get 'pi'
-
- describe 'unwraps', ->
- test 'get!, getc!', ->
- assert.is.equal 3.14, (Const.num 3.14)\getc!
- assert.is.equal 'hi', (Const.str 'hi')\getc!
- assert.is.equal 'hi', (Const.sym 'hi')\getc!
-
- assert.is.equal 3.14, (Const.num 3.14)\get!
- assert.is.equal 'hi', (Const.str 'hi')\get!
- assert.is.equal 'hi', (Const.sym 'hi')\get!
-
- test 'with type assert', ->
- assert.is.equal 3.14, (Const.num 3.14)\getc 'num'
- assert.is.equal 'hi', (Const.str 'hi')\getc 'str'
- assert.is.equal 'hi', (Const.sym 'hi')\getc 'sym'
- assert.has_error -> (Const.num 3.14)\getc 'sym'
- assert.has_error -> (Const.str 'hi')\getc 'num'
- assert.has_error -> (Const.sym 'hi')\getc 'str'
-
- assert.is.equal 3.14, (Const.num 3.14)\get 'num'
- assert.is.equal 'hi', (Const.str 'hi')\get 'str'
- assert.is.equal 'hi', (Const.sym 'hi')\get 'sym'
- assert.has_error -> (Const.num 3.14)\get 'sym'
- assert.has_error -> (Const.str 'hi')\get 'num'
- assert.has_error -> (Const.sym 'hi')\get 'str'
-
- describe 'checks equality', ->
- test 'using the type', ->
- val = Const 'num', 3
- assert.is.equal (Const.num 3), val
- assert.not.equal (Const.str '3'), val
-
- val = Const 'str', 'hello'
- assert.is.equal (Const.str 'hello'), val
- assert.not.equal (Const.sym 'hello'), val
-
- test 'using the value', ->
- val = Const 'num', 3
- assert.is.equal (Const.num 3), val
- assert.not.equal (Const.num 4), val
-
- describe 'evaluates literal', ->
- test 'constants to themselves', ->
- assert_noop = (val) -> assert.is.equal val, val\eval!
-
- assert_noop Const.num 2
- assert_noop Const.str 'hello'
-
- test 'symbols in the scope', ->
- scope = with Scope!
- \set 'number', Const.num 3
- \set 'hello', Const.str "world"
- \set 'goodbye', Const.sym "again"
-
- assert_eval = (sym, val) ->
- const = Const.sym sym
- assert.is.equal val, const\eval scope
-
- assert_eval 'number', Const.num 3
- assert_eval 'hello', Const.str "world"
- assert_eval 'goodbye', Const.sym "again"
-
- describe 'quotes literals', ->
- test 'as themselves', ->
- assert_noop = (val) -> assert.is.equal val, val\quote!
-
- assert_noop Const.num 2
- assert_noop Const.str 'hello'
- assert_noop Const.sym 'world'
diff --git a/spec/parsing_spec.moon b/spec/parsing_spec.moon
index 2e7da7f..ddf760f 100644
--- a/spec/parsing_spec.moon
+++ b/spec/parsing_spec.moon
@@ -1,5 +1,5 @@
import space, atom, expr, explist, cell, program, comment from require 'core.parsing'
-import Const from require 'core'
+import Value from require 'core'
import Logger from require 'logger'
Logger.init 'silent'
@@ -16,48 +16,48 @@ describe 'atom parsing', ->
test 'symbols', ->
sym = verify_parse_nope atom, 'some-toast nope'
assert.is.equal 'sym', sym.type
- assert.is.equal 'some-toast', sym\getc!
+ assert.is.equal 'some-toast', sym\unwrap!
assert.is.equal 'some-toast', sym\stringify!
describe 'numbers', ->
it 'parses ints', ->
num = verify_parse_nope atom, '1234 nope'
assert.is.equal 'num', num.type
- assert.is.equal 1234, num\getc!
+ assert.is.equal 1234, num\unwrap!
assert.is.equal '1234', num\stringify!
it 'parses floats', ->
num = verify_parse_nope atom, '0.123 nope'
assert.is.equal 'num', num.type
- assert.is.equal 0.123, num\getc!
+ assert.is.equal 0.123, num\unwrap!
num = verify_parse_nope atom, '.123 nope'
assert.is.equal 'num', num.type
- assert.is.equal 0.123, num\getc!
+ assert.is.equal 0.123, num\unwrap!
num = verify_parse_nope atom, '0. nope'
assert.is.equal 'num', num.type
- assert.is.equal 0, num\getc!
+ assert.is.equal 0, num\unwrap!
describe 'strings', ->
it 'parses double-quote strings', ->
str = verify_parse_nope atom, '"help some stuff!" nope'
assert.is.equal 'str', str.type
- assert.is.equal 'help some stuff!', str\getc!
+ assert.is.equal 'help some stuff!', str\unwrap!
it 'parses single-quote strings', ->
str = verify_parse_nope atom, "'help some stuff!' nope"
assert.is.equal 'str', str.type
- assert.is.equal "help some stuff!", str\getc!
+ assert.is.equal "help some stuff!", str\unwrap!
it 'handles escapes', ->
str = verify_parse_nope atom, '"string with \\"quote\\"s and \\\\" nope'
assert.is.equal 'str', str.type
- assert.is.equal 'string with \"quote\"s and \\', str\getc!
+ assert.is.equal 'string with \"quote\"s and \\', str\unwrap!
str = verify_parse_nope atom, "'string with \\'quote\\'s and \\\\' nope"
assert.is.equal 'str', str.type
- assert.is.equal "string with \'quote\'s and \\", str\getc!
+ assert.is.equal "string with \'quote\'s and \\", str\unwrap!
describe 'Cell', ->
test 'basic parsing', ->
@@ -65,9 +65,9 @@ describe 'Cell', ->
"friend" )'
assert.is.equal 3, #node.children
- assert.is.equal (Const.num 3), node.children[1]
- assert.is.equal (Const.sym 'ok-yes'), node.children[2]
- assert.is.equal (Const.str 'friend'), node.children[3]
+ assert.is.equal (Value.num 3), node.children[1]
+ assert.is.equal (Value.sym 'ok-yes'), node.children[2]
+ assert.is.equal (Value.str 'friend'), node.children[3]
test 'tag parsing', ->
node = verify_parse cell, '([42]tagged 2)'
@@ -88,8 +88,8 @@ describe 'RootCell parsing', ->
node = verify_parse program, str
assert.is.equal 2, #node.children
- assert.is.equal (Const.num 3), node.children[1]
- assert.is.equal (Const.sym 'ok-yes'), node.children[2]
+ assert.is.equal (Value.num 3), node.children[1]
+ assert.is.equal (Value.sym 'ok-yes'), node.children[2]
it 'at the front of the string', ->
verify ' 3\tok-yes'
diff --git a/spec/scope_spec.moon b/spec/scope_spec.moon
index 9e7ab80..471bbce 100644
--- a/spec/scope_spec.moon
+++ b/spec/scope_spec.moon
@@ -1,4 +1,4 @@
-import Scope, Const, Op from require 'core'
+import Scope, Value, Op from require 'core'
import Logger from require 'logger'
Logger.init 'silent'
@@ -12,27 +12,27 @@ describe 'Scope', ->
test 'numbers', ->
scope\set_raw 'num', 3
- got = scope\get 'num'
+ got = (scope\get 'num')\const!
assert.is.equal 'num', got.type
assert.is.equal 3, got.value
test 'strings', ->
scope\set_raw 'str', "im a happy string"
- got = scope\get 'str'
+ got = (scope\get 'str')\const!
assert.is.equal 'str', got.type
assert.is.equal "im a happy string", got.value
- test 'Consts', ->
- pi = Const 'num', 3.14
+ test 'Values', ->
+ pi = Value 'num', 3.14
scope\set_raw 'pi', pi
- assert.is.equal pi, scope\get 'pi'
+ assert.is.equal pi, (scope\get 'pi')\const!
test 'Opdefs', ->
scope\set_raw 'test', TestOp
- got = scope\get 'test'
+ got = (scope\get 'test')\const!
assert.is.equal 'opdef', got.type
assert.is.equal TestOp, got.value
@@ -40,22 +40,22 @@ describe 'Scope', ->
sub = Scope!
scope\set_raw 'sub', sub
- got = scope\get 'sub'
+ got = (scope\get 'sub')\const!
assert.is.equal 'scope', got.type
assert.is.equal sub, got.value
test 'tables', ->
- pi = Const 'num', 3.14
+ pi = Value 'num', 3.14
scope\set_raw 'math', { :pi }
- got = scope\get 'math'
+ got = (scope\get 'math')\const!
assert.is.equal 'scope', got.type
assert.is.equal Scope, got.value.__class
- assert.is.equal pi, got.value\get 'pi'
- assert.is.equal pi, scope\get 'math/pi'
+ assert.is.equal pi, (got.value\get 'pi')\const!
+ assert.is.equal pi, (scope\get 'math/pi')\const!
- it 'constifies in from_table', ->
- pi = Const 'num', 3.14
+ it 'wraps Values in from_table', ->
+ pi = Value 'num', 3.14
scope = Scope.from_table {
num: 3
str: "im a happy string"
@@ -64,35 +64,35 @@ describe 'Scope', ->
test: TestOp
}
- got = scope\get 'num'
+ got = (scope\get 'num')\const!
assert.is.equal 'num', got.type
assert.is.equal 3, got.value
- got = scope\get 'str'
+ got = (scope\get 'str')\const!
assert.is.equal 'str', got.type
assert.is.equal "im a happy string", got.value
- assert.is.equal pi, scope\get 'pi'
+ assert.is.equal pi, (scope\get 'pi')\const!
- got = scope\get 'test'
+ got = (scope\get 'test')\const!
assert.is.equal 'opdef', got.type
assert.is.equal TestOp, got.value
- got = scope\get 'math'
+ got = (scope\get 'math')\const!
assert.is.equal 'scope', got.type
- assert.is.equal pi, scope\get 'math/pi'
+ assert.is.equal pi, (scope\get 'math/pi')\const!
it 'gets from nested scopes', ->
root = Scope!
a = Scope!
b = Scope!
- pi = Const 'num', 3.14
+ pi = Value 'num', 3.14
b\set_raw 'test', pi
a\set_raw 'child', b
root\set_raw 'deep', a
- assert.is.equal pi, root\get 'deep/child/test'
+ assert.is.equal pi, (root\get 'deep/child/test')\const!
describe 'inheritance', ->
root = Scope!
@@ -102,13 +102,13 @@ describe 'Scope', ->
scope = Scope nil, root
it 'allows access', ->
- got = scope\get 'inherited'
+ got = (scope\get 'inherited')\const!
assert.is.equal 'str', got.type
assert.is.equal "inherited string", got.value
it 'can keep defs', ->
scope\set_raw 'hidden', "overwritten"
- got = scope\get 'hidden'
+ got = (scope\get 'hidden')\const!
assert.is.equal 'str', got.type
assert.is.equal "overwritten", got.value
diff --git a/spec/value_spec.moon b/spec/value_spec.moon
new file mode 100644
index 0000000..bc68c5b
--- /dev/null
+++ b/spec/value_spec.moon
@@ -0,0 +1,112 @@
+import Value, Result, Op, Action, Scope from require 'core'
+import Logger from require 'logger'
+Logger.init 'silent'
+
+class TestOp extends Op
+ new: (...) => super ...
+
+class TestAction extends Action
+ new: (...) =>
+
+describe 'Value', ->
+ describe 'wraps', ->
+ test 'numbers', ->
+ got = Value.wrap 3
+ assert.is.equal 'num', got.type
+ assert.is.equal 3, got.value
+
+ test 'strings', ->
+ got = Value.wrap "im a happy string"
+ assert.is.equal 'str', got.type
+ assert.is.equal "im a happy string", got.value
+
+ test 'Values', ->
+ pi = Value 'num', 3.14
+ got = Value.wrap pi
+
+ assert.is.equal pi, got
+
+ test 'Opdefs', ->
+ got = Value.wrap TestOp
+
+ assert.is.equal 'opdef', got.type
+ assert.is.equal TestOp, got.value
+
+ test 'Bultins', ->
+ got = Value.wrap TestAction
+
+ assert.is.equal 'builtin', got.type
+ assert.is.equal TestAction, got.value
+
+ test 'Scopes', ->
+ sub = Scope!
+ got = Value.wrap sub
+
+ assert.is.equal 'scope', got.type
+ assert.is.equal sub, got.value
+
+ test 'tables', ->
+ pi = Value 'num', 3.14
+ got = Value.wrap { :pi }
+
+ assert.is.equal 'scope', got.type
+ assert.is.equal pi, (got.value\get 'pi')\const!
+
+ describe 'unwraps', ->
+ test 'unwrap!', ->
+ assert.is.equal 3.14, (Value.num 3.14)\unwrap!
+ assert.is.equal 'hi', (Value.str 'hi')\unwrap!
+ assert.is.equal 'hi', (Value.sym 'hi')\unwrap!
+
+ test 'with type assert', ->
+ assert.is.equal 3.14, (Value.num 3.14)\unwrap 'num'
+ assert.is.equal 'hi', (Value.str 'hi')\unwrap 'str'
+ assert.is.equal 'hi', (Value.sym 'hi')\unwrap 'sym'
+ assert.has_error -> (Value.num 3.14)\unwrap 'sym'
+ assert.has_error -> (Value.str 'hi')\unwrap 'num'
+ assert.has_error -> (Value.sym 'hi')\unwrap 'str'
+
+ describe 'checks equality', ->
+ test 'using the type', ->
+ val = Value 'num', 3
+ assert.is.equal (Value.num 3), val
+ assert.not.equal (Value.str '3'), val
+
+ val = Value 'str', 'hello'
+ assert.is.equal (Value.str 'hello'), val
+ assert.not.equal (Value.sym 'hello'), val
+
+ test 'using the value', ->
+ val = Value 'num', 3
+ assert.is.equal (Value.num 3), val
+ assert.not.equal (Value.num 4), val
+
+ describe 'evaluates literal', ->
+ test 'numbers to consts', ->
+ assert_noop = (val) ->
+ assert.is.equal val, val\eval!\const!
+
+ assert_noop Value.num 2
+ assert_noop Value.str 'hello'
+
+ test 'symbols in the scope', ->
+ scope = with Scope!
+ \set 'number', Result value: Value.num 3
+ \set 'hello', Result value: Value.str "world"
+ \set 'goodbye', Result value: Value.sym "again"
+
+ assert_eval = (sym, val) ->
+ const = Value.sym sym
+ assert.is.equal val, (const\eval scope)\const!
+
+ assert_eval 'number', Value.num 3
+ assert_eval 'hello', Value.str "world"
+ assert_eval 'goodbye', Value.sym "again"
+
+ describe 'quotes literals', ->
+ test 'as themselves', ->
+ assert_noop = (val) -> assert.is.equal val, val\quote!
+
+ assert_noop Value.num 2
+ assert_noop Value.str 'hello'
+ assert_noop Value.sym 'world'
diff --git a/test.alv b/test.alv
index 7d51b9b..221b7ea 100644
--- a/test.alv
+++ b/test.alv
@@ -1,16 +1,9 @@
-([1]import* math time string util)
-([2]import osc envelope midi)
+([1]import* debug time)
+([2]import midi)
-([3]defn make-lfo (type)
- ([8]fn ([5]f) ([7]lfo ([6]* f 0.5) type)))
+([3]trace "help")
-([9]def sin-lfo ([10]make-lfo 'sin'))
+([4]def device ([5]midi/input "system:midi_capture_2"))
-([11]defn send (name value)
- ([13]osc/out '127.0.0.1' 9000
- ([12].. '/param/' name '/set')
- value))
-
-([23]send 'radius' ([25]([24]envelope/ar ([14]midi/cc 0) ([15]midi/cc 1)) ([27]midi/gate 36)))
-
-([19]send 'offset' ([20]sin-lfo ([16]keep ([26]midi/cc 24 0 4))))
+([6]out 'name' ([7]midi/cc device 0))
+([8]out 'gate' ([9]midi/gate device 44))