diff options
| author | s-ol <s+removethis@s-ol.nu> | 2022-02-05 10:11:13 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2025-03-02 14:24:49 +0000 |
| commit | c484cce5ab1f6acd14ecad30adb586a6d5fa0fd1 (patch) | |
| tree | a45c702a83e150fea5b14b279e5f0cab9b65bc3e /spec/internal | |
| parent | examples: more smoothing in love example :) (diff) | |
| download | alive-c484cce5ab1f6acd14ecad30adb586a6d5fa0fd1.tar.gz alive-c484cce5ab1f6acd14ecad30adb586a6d5fa0fd1.zip | |
rearrange spec, fix for Lua 5.1
Diffstat (limited to 'spec/internal')
| -rw-r--r-- | spec/internal/cell_spec.moon | 58 | ||||
| -rw-r--r-- | spec/internal/input_spec.moon | 156 | ||||
| -rw-r--r-- | spec/internal/match_spec.moon | 307 | ||||
| -rw-r--r-- | spec/internal/parsing_spec.moon | 141 | ||||
| -rw-r--r-- | spec/internal/pureop_spec.moon | 128 | ||||
| -rw-r--r-- | spec/internal/registry_spec.moon | 8 | ||||
| -rw-r--r-- | spec/internal/result/const_spec.moon | 158 | ||||
| -rw-r--r-- | spec/internal/result/evt_spec.moon | 144 | ||||
| -rw-r--r-- | spec/internal/result/sig_spec.moon | 116 | ||||
| -rw-r--r-- | spec/internal/rtnode_spec.moon | 216 | ||||
| -rw-r--r-- | spec/internal/scope_spec.moon | 166 | ||||
| -rw-r--r-- | spec/internal/tag_spec.moon | 91 | ||||
| -rw-r--r-- | spec/internal/type_spec.moon | 175 |
13 files changed, 1864 insertions, 0 deletions
diff --git a/spec/internal/cell_spec.moon b/spec/internal/cell_spec.moon new file mode 100644 index 0000000..a8e79fe --- /dev/null +++ b/spec/internal/cell_spec.moon @@ -0,0 +1,58 @@ +import do_setup from require 'spec.test_setup' +import Cell, RootCell from require 'alv.cell' +import Constant, Scope, Tag, SimpleRegistry, globals from require 'alv' + +setup do_setup + +hello_world = Cell.parse Tag.parse('2'), { + '', (Constant.sym 'hello'), ' ', (Constant.str 'world'), '' +} +two_plus_two = Cell.parse Tag.parse('3'), { + '', (Constant.sym '+'), ' ', (Constant.num 2), ' ', (Constant.num 2), '' +} + +describe 'Cell', -> + describe 'when cloned', -> + parent = Tag.blank '1' + with hello_world\clone parent + it 'keeps children', -> + assert.is.equal Cell, .__class + assert.is.equal (Constant.sym 'hello'), \head! + assert.is.same { Constant.str 'world' }, \tail! + + it 'clones the tag', -> + assert.is.equal hello_world.tag, .tag.original + assert.is.equal parent, .tag.parent + + describe 'when evaluated', -> + it 'errors when empty', -> + cell = Cell.parse {''} + assert.has.error -> cell\eval globals + + it 'evaluates its head', -> + head = Constant.sym 'trace' + cell = Cell.parse { '', head, ' ', (Constant.sym 'true'), '' } + + s = spy.on head, 'eval' + cell\eval globals + assert.spy(s).was_called_with (match.is_ref head), (match.is_ref globals) + +describe 'RootCell', -> + test 'tag is always [0]', -> + cell = Cell.parse_root {} + assert.is.equal '[0]', cell.tag\stringify! + + test 'head is always "do"', -> + cell = Cell.parse_root {} + assert.is.equal (Constant.sym 'do'), cell\head! + + cell = RootCell nil, { hello_world, two_plus_two } + assert.is.equal (Constant.sym 'do'), cell\head! + + test 'tail is all children', -> + cell = Cell.parse_root {} + assert.is.same {}, cell\tail! + + cell = RootCell nil, { hello_world, two_plus_two } + assert.is.same { hello_world, two_plus_two }, + cell\tail! diff --git a/spec/internal/input_spec.moon b/spec/internal/input_spec.moon new file mode 100644 index 0000000..04cf16e --- /dev/null +++ b/spec/internal/input_spec.moon @@ -0,0 +1,156 @@ +import do_setup, do_teardown from require 'spec.test_setup' +import Input, T, Result from require 'alv.base' + +setup do_setup +teardown do_teardown + +basic_tests = (result, input) -> + it 'gives access to the Result', -> + assert.is.equal result, input.result + + it 'forwards :unwrap', -> + assert.is.equal result\unwrap!, input\unwrap!, nil + assert.is.equal result\unwrap!, input!, nil + + it 'gives access to the type string', -> + assert.is.equal result.type, input\type! + + it 'gives access to the metatype string', -> + assert.is.equal result.metatype, input\metatype! + +describe 'Input.cold', -> + result = T.num\mk_sig 1 + input = Input.cold result + + basic_tests result, input + + it 'is marked cold', -> + assert.is.equal 'cold', input.mode + + it 'is never dirty', -> + assert.is.false input\dirty! + result\set 2 + assert.is.false input\dirty! + + input\setup nil + assert.is.false input\dirty! + input\finish_setup! + + new_input = Input.cold T.num\mk_sig 3 + new_input\setup input + assert.is.false new_input\dirty! + new_input.result\set 4 + assert.is.false new_input\dirty! + input\finish_setup! + +describe 'Input.hot', -> + describe 'with Constant', -> + result = T.num\mk_const 1 + input = Input.hot result + + basic_tests result, input + + it 'is marked cold', -> + assert.is.equal 'cold', input.mode + + describe 'at evaltime', -> + it 'is dirty when new', -> + assert.is.false result\dirty! + + newinput = Input.hot result + newinput\setup nil + assert.is.true newinput\dirty! + newinput\finish_setup! + + it 'is dirty when different', -> + newval = T.num\mk_const 2 + + assert.is.false newval\dirty! + newinput = Input.hot newval + newinput\setup input + assert.is.true newinput\dirty! + newinput\finish_setup! + + it 'is not dirty when equal', -> + newval = T.num\mk_const 1 + + assert.is.false newval\dirty! + newinput = Input.hot newval + newinput\setup input + assert.is.false newinput\dirty! + newinput\finish_setup! + + describe 'with EvtStream', -> + result = T.num\mk_evt! + input = Input.hot result + + basic_tests result, input + + it 'is marked hot', -> + assert.is.equal 'hot', input.mode + + it 'is dirty when the EvtStream is dirty', -> + assert.is.false input\dirty! + assert.is.false result\dirty! + + input\setup nil + assert.is.false input\dirty! + input\finish_setup! + + COPILOT\next_tick! + result\set 1 + + assert.is.true input\dirty! + assert.is.true result\dirty! + + input\setup nil + assert.is.true input\dirty! + input\finish_setup! + + assert.is.true input\dirty! + assert.is.true result\dirty! + + describe 'with SigStream', -> + result = T.num\mk_sig 1 + local input + + describe 'at evaltime', -> + it 'is dirty when new', -> + assert.is.false result\dirty! + + input = Input.hot result + input\setup nil + assert.is.true input\dirty! + input\finish_setup! + + it 'is dirty when different', -> + newval = T.num\mk_sig 2 + + assert.is.false newval\dirty! + newinput = Input.hot newval + newinput\setup input + assert.is.true newinput\dirty! + newinput\finish_setup! + + it 'is not dirty when equal', -> + newval = T.num\mk_sig! + newval\set 1 + + assert.is.true newval\dirty! + newinput = Input.hot newval + newinput\setup input + assert.is.false newinput\dirty! + newinput\finish_setup! + + it 'is marked hot', -> + assert.is.equal 'hot', input.mode + + describe 'at runtime', -> + it 'is dirty when the result is dirty', -> + result\set 3 + assert.is.true result\dirty! + assert.is.true input\dirty! + + COPILOT\next_tick! + assert.is.false result\dirty! + assert.is.false input\dirty! diff --git a/spec/internal/match_spec.moon b/spec/internal/match_spec.moon new file mode 100644 index 0000000..b9d5ef6 --- /dev/null +++ b/spec/internal/match_spec.moon @@ -0,0 +1,307 @@ +import const, sig, evt, any from require 'alv.base.match' +import Op, Input from require 'alv.base' +import RTNode, T, Error from require 'alv' + +op_with_inputs = (inputs) -> + with Op! + \setup inputs if inputs + +mk_const = (type, const) -> + result = T[type]\mk_const true + RTNode :result, op: op_with_inputs { Input.hot result } + +mk_val = (type, const) -> + result = T[type]\mk_sig true + RTNode :result, op: op_with_inputs { Input.hot result } + +mk_evt = (type, const) -> + result = T[type]\mk_evt! + RTNode :result, op: op_with_inputs { Input.hot result } + +describe 'sig and evt', -> + describe 'type-less shorthand', -> + it 'matches metatype', -> + str = mk_val 'str' + num = mk_val 'num' + assert.is.equal str, sig!\match { str } + assert.is.equal num, sig!\match { num } + assert.has.error -> const!\match { str } + assert.has.error -> const!\match { num } + assert.has.error -> evt!\match { str } + assert.has.error -> evt!\match { num } + + str = mk_evt 'str' + num = mk_evt 'num' + assert.has.error -> sig!\match { str } + assert.has.error -> sig!\match { num } + assert.has.error -> const!\match { str } + assert.has.error -> const!\match { num } + assert.is.equal str, evt!\match { str } + assert.is.equal num, evt!\match { num } + + str = mk_const 'str' + num = mk_const 'num' + assert.is.equal str, sig!\match { str } + assert.is.equal num, sig!\match { num } + assert.is.equal str, const!\match { str } + assert.is.equal num, const!\match { num } + assert.has.error -> evt!\match { str } + assert.has.error -> evt!\match { num } + + it 'can recall the type', -> + value = sig!! + event = evt!! + two_equal_values = value + value + two_equal_events = event + event + + str1 = mk_val 'str' + str2 = mk_val 'str' + num = mk_val 'num' + assert.is.same { str1, str2 }, two_equal_values\match { str1, str2 } + assert.is.same { str2, str1 }, two_equal_values\match { str2, str1 } + assert.is.same { num, num }, two_equal_values\match { num, num } + assert.has.error -> two_equal_values\match { str1, num } + assert.has.error -> two_equal_values\match { num, str2 } + assert.has.error -> two_equal_events\match { str1, str2 } + + str1 = mk_evt 'str' + str2 = mk_evt 'str' + num = mk_evt 'num' + assert.is.same { str1, str2 }, two_equal_events\match { str1, str2 } + assert.is.same { str2, str1 }, two_equal_events\match { str2, str1 } + assert.is.same { num, num }, two_equal_events\match { num, num } + assert.has.error -> two_equal_events\match { str1, num } + assert.has.error -> two_equal_events\match { num, str2 } + assert.has.error -> two_equal_values\match { str1, str2 } + + it 'stringifies well', -> + assert.is.equal 'any=', tostring const! + assert.is.equal 'any!', tostring evt! + assert.is.equal 'any~', tostring sig! + + describe 'typed shorthand', -> + it 'matches by metatype', -> + str = mk_val 'str' + num = mk_val 'num' + assert.is.equal str, sig.str\match { str } + assert.is.equal num, sig.num\match { num } + assert.has.error -> const.str\match { str } + assert.has.error -> const.num\match { num } + assert.has.error -> evt.str\match { str } + assert.has.error -> evt.num\match { num } + + str = mk_evt 'str' + num = mk_evt 'num' + assert.has.error -> sig.str\match { str } + assert.has.error -> sig.num\match { num } + assert.has.error -> const.str\match { str } + assert.has.error -> const.num\match { num } + assert.is.equal str, evt.str\match { str } + assert.is.equal num, evt.num\match { num } + + str = mk_const 'str' + num = mk_const 'num' + assert.is.equal str, sig.str\match { str } + assert.is.equal num, sig.num\match { num } + assert.is.equal str, const.str\match { str } + assert.is.equal num, const.num\match { num } + assert.has.error -> evt.str\match { str } + assert.has.error -> evt.num\match { num } + + it 'matches by type', -> + str = mk_const 'str' + num = mk_const 'num' + assert.is.equal str, sig.str\match { str } + assert.is.equal num, sig.num\match { num } + assert.is.equal str, const.str\match { str } + assert.is.equal num, const.num\match { num } + assert.has.error -> sig.num\match { str } + assert.has.error -> sig.str\match { num } + + str = mk_val 'str' + num = mk_val 'num' + assert.is.equal str, sig.str\match { str } + assert.is.equal num, sig.num\match { num } + assert.has.error -> const.num\match { str } + assert.has.error -> const.str\match { num } + assert.has.error -> sig.num\match { str } + assert.has.error -> sig.str\match { num } + + str = mk_evt 'str' + num = mk_evt 'num' + assert.is.equal str, evt.str\match { str } + assert.is.equal num, evt.num\match { num } + assert.has.error -> const.num\match { str } + assert.has.error -> const.str\match { num } + assert.has.error -> evt.num\match { str } + assert.has.error -> evt.str\match { num } + + it 'stringifies well', -> + assert.is.equal 'str!', tostring evt.str + assert.is.equal 'num!', tostring evt.num + assert.is.equal 'str~', tostring sig.str + assert.is.equal 'num~', tostring sig.num + assert.is.equal 'str=', tostring const.str + assert.is.equal 'num=', tostring const.num + +describe 'choice', -> + str = mk_val 'str' + num = mk_val 'num' + bool = mk_val 'bool' + choice = sig.str / sig.num + + it 'matches either type', -> + assert.is.equal str, choice\match { str } + assert.is.equal num, choice\match { num } + assert.has.error -> choice\match { bool } + + it 'can recall the choice', -> + same = choice! + assert.is.equal num, same\match { num } + + same = same + same + assert.is.same { str, str }, same\match { str, str } + assert.is.same { num, num }, same\match { num, num } + assert.has.error -> same\match { str, num } + assert.has.error -> same\match { num, str } + assert.has.error -> same\match { bool, bool } + + it 'makes inner types recall', -> + same = any!! + same = same + same + assert.is.same { str, str }, same\match { str, str } + assert.is.same { num, num }, same\match { num, num } + assert.is.same { bool, bool }, same\match { bool, bool } + assert.has.error -> same\match { str, num } + assert.has.error -> same\match { num, str } + + it 'stringifies well', -> + assert.is.equal '(str~ | num~)', tostring choice + +describe 'sequence', -> + str = mk_val 'str' + num = mk_val 'num' + bool = mk_evt 'bool' + seq = sig.str + sig.num + evt.bool + + it 'matches all types in order', -> + assert.is.same { str, num, bool }, seq\match { str, num, bool } + + it 'can assign non-numeric keys', -> + named = seq\named 'str', 'num', 'bool' + assert.is.same { :str, :num, :bool }, named\match { str, num, bool } + assert.is.same { str, num, bool }, seq\match { str, num, bool } + + it 'fails if too little arguments', -> + assert.has.error -> seq\match { str, num } + + it 'fails if too many arguments', -> + assert.has.error -> seq\match { str, num, bool, bool } + + it 'can handle optional children', -> + opt = -sig.str + sig.num + assert.is.same { str, num }, opt\match { str, num } + assert.is.same { nil, num }, opt\match { num } + assert.has.error -> opt\match { str, str, num } + assert.has.error -> opt\match { str, num, num } + + it 'can handle repeat children', -> + rep = sig.str + sig.num*2 + assert.is.same { str, {num} }, rep\match { str, num } + assert.is.same { str, {num,num} }, rep\match { str, num, num } + assert.has.error -> rep\match { str } + assert.has.error -> rep\match { str, num, num, num } + + it 'stringifies well', -> + assert.is.equal '(str~ num~ bool!)', tostring seq + +describe 'repeat', -> + str = mk_val 'str' + num = mk_val 'num' + + times = (n, arg) -> return for i=1,n do arg + + it '*x is [1,x[', -> + rep = sig.str*3 + assert.has.error -> rep\match (times 0, str) + assert.is.same (times 1, str), rep\match (times 1, str) + assert.is.same (times 2, str), rep\match (times 2, str) + assert.is.same (times 3, str), rep\match (times 3, str) + assert.has.error -> rep\match (times 4, str) + assert.has.error -> rep\match (times 3, num) + + it '*0 is [1,[', -> + rep = sig.str*0 + assert.has.error -> rep\match (times 0, str) + assert.is.same (times 1, str), rep\match (times 1, str) + assert.is.same (times 2, str), rep\match (times 2, str) + assert.is.same (times 20, str), rep\match (times 20, str) + assert.has.error -> rep\match (times 3, num) + + it '^x is [0,x[', -> + rep = sig.str^3 + assert.is.same {}, rep\match {} + assert.is.same (times 1, str), rep\match (times 1, str) + assert.is.same (times 2, str), rep\match (times 2, str) + assert.is.same (times 3, str), rep\match (times 3, str) + assert.has.error -> rep\match (times 4, str) + assert.has.error -> rep\match (times 3, num) + + it '^0 is [0,[', -> + rep = sig.str^0 + assert.is.same {}, rep\match {} + assert.is.same (times 1, str), rep\match (times 1, str) + assert.is.same (times 2, str), rep\match (times 2, str) + assert.is.same (times 20, str), rep\match (times 20, str) + assert.has.error -> rep\match (times 3, num) + + it ':rep(min, max) does anything else', -> + rep = sig.str\rep 2, 2 + assert.has.error -> rep\match {} + assert.has.error -> rep\match (times 1, str) + assert.is.same (times 2, str), rep\match (times 2, str) + assert.has.error -> rep\match (times 3, str) + assert.has.error -> rep\match (times 2, num) + + it 'works with complex inner types', -> + rep = (sig.num + sig.str)\rep 2, 2 + assert.has.error -> rep\match {} + assert.has.error -> rep\match {num, str} + assert.has.error -> rep\match {num, num} + assert.is.same {{num, str}, {num, str}}, rep\match {num, str, num, str} + assert.has.error -> rep\match {str, str, str, str} + assert.has.error -> rep\match {num, str, num, str, num, str} + + it 'stringifies well', -> + assert.is.equal 'str~{1-3}', tostring sig.str*3 + assert.is.equal 'str~{1-*}', tostring sig.str*0 + assert.is.equal 'str~{0-*}', tostring sig.str^0 + assert.is.equal 'str~{2-2}', tostring sig.str\rep 2, 2 + +describe 'complex nesting', -> + bang = mk_evt 'bang' + str = mk_val 'str' + num = mk_val 'num' + num_c = mk_const 'num' + pattern = -evt.bang + sig.num*4 + + (sig.str + (sig.num / sig.str))\named('key', 'val')^0 + + it 'just works', -> + assert.is.same { bang, { num, num }, {} }, pattern\match { bang, num, num } + assert.is.same { nil, { num }, { { key: str, val: num }, + { key: str, val: str } } }, + pattern\match { num, str, num, str, str } + assert.has.error -> pattern\match { num, str } + assert.has.error -> pattern\match { num_c, str } + assert.has.error -> pattern\match { bang, num_c, num, num_c, num, num, num } + assert.has.error -> pattern\match { bang, bang, num } + assert.has.error -> pattern\match { num, str, num_c, str } + assert.has.error -> pattern\match { num, str, num, str, mk_val 'bool' } + + it 'stringifies well', -> + assert.is.equal '(bang!? num~{1-4} (str~ (num~ | str~)){0-*})', tostring pattern + + it 'gives useful error feedback', -> + msg = "couldn't match arguments (num~ str~ bool~) against pattern #{pattern}" + err = Error 'argument', msg + assert.has.error (-> pattern\match { num, str, mk_val 'bool' }), err diff --git a/spec/internal/parsing_spec.moon b/spec/internal/parsing_spec.moon new file mode 100644 index 0000000..0486276 --- /dev/null +++ b/spec/internal/parsing_spec.moon @@ -0,0 +1,141 @@ +import space, atom, expr, explist, cell, program, comment + from require 'alv.parsing' +import Constant from require 'alv' +import Logger from require 'alv.logger' +Logger\init 'silent' + +verify_parse = (parser, val) -> + with assert parser\match val + assert.is.equal val, \stringify! + +verify_parse_nope = (parser, val) -> + with assert parser\match val + without_nope = val\match '^(.*) nope$' + assert.is.equal without_nope, \stringify! + +describe 'atom parsing', -> + test 'symbols', -> + sym = verify_parse_nope atom, 'some-toast nope' + assert.is.equal (Constant.sym 'some-toast'), sym + + describe 'numbers', -> + it 'parses ints', -> + num = verify_parse_nope atom, '1234 nope' + assert.is.equal (Constant.num 1234), num + + it 'parses floats', -> + num = verify_parse_nope atom, '0.123 nope' + assert.is.equal (Constant.num 0.123), num + + num = verify_parse_nope atom, '.123 nope' + assert.is.equal (Constant.num 0.123), num + + num = verify_parse_nope atom, '0. nope' + assert.is.equal (Constant.num 0), num + + describe 'strings', -> + it 'parses double-quote strings', -> + str = verify_parse_nope atom, '"help some stuff!" nope' + assert.is.equal (Constant.str 'help some stuff!'), str + + it 'parses single-quote strings', -> + str = verify_parse_nope atom, "'help some stuff!' nope" + assert.is.equal (Constant.str "help some stuff!"), str + + it 'handles escapes', -> + str = verify_parse_nope atom, '"string with \\"quote\\"s and \\\\" nope' + assert.is.equal (Constant.str 'string with \"quote\"s and \\'), str + + str = verify_parse_nope atom, "'string with \\'quote\\'s and \\\\' nope" + assert.is.equal (Constant.str "string with \'quote\'s and \\"), str + +describe 'Cell', -> + test 'basic parsing', -> + node = verify_parse cell, '( 3 ok-yes + "friend" )' + + assert.is.equal 3, #node.children + assert.is.equal (Constant.num 3), node.children[1] + assert.is.equal (Constant.sym 'ok-yes'), node.children[2] + assert.is.equal (Constant.str 'friend'), node.children[3] + + test 'tag parsing', -> + node = verify_parse cell, '([42]tagged 2)' + + assert.is.equal 2, #node.children + assert.is.equal 42, node.tag.value + + test 'tag parsing with whitespace', -> + node = verify_parse cell, '([42] + tagged 2)' + + assert.is.equal 2, #node.children + assert.is.equal 42, node.tag.value + +describe 'RootCell parsing', -> + describe 'handles whitespace', -> + verify = (str) -> + node = verify_parse program, str + + assert.is.equal 2, #node.children + assert.is.equal (Constant.num 3), node.children[1] + assert.is.equal (Constant.sym 'ok-yes'), node.children[2] + + it 'at the front of the string', -> + verify ' 3\tok-yes' + + it 'at the end of the string', -> + verify ' 3\tok-yes\n' + + it 'everywhere', -> + verify ' 3\tok-yes\n' + +test 'whitespace', -> + assert.is.equal ' ', space\match ' ' + assert.is.equal '\n\t ', space\match '\n\t ' + +describe 'comments', -> + comment = comment / 1 + it 'are parsed', -> + str = '#(this is a comment)' + assert.is.equal str, comment\match str + + it 'extend to matching braces', -> + str = '#(this is a comment #(with nested comments))' + assert.is.equal str, comment\match str + + it 'can nest', -> + str = '#(this is a comment (with nested parenthesis))' + assert.is.equal str, comment\match str + +describe 'resynthesis', -> + test 'mixed parsing', -> + str = '( 3 ok-yes + "friend" )' + node = verify_parse program, str + assert.is.equal str, node\stringify! + + test 'complex', -> + str = ' + #(send a CC controlled LFO to /radius) + (osc "/radius" (lfo (cc 14))) + + (osc rot + (step + #(whenever a kick is received...) + (note "kick") + + #(..cycle through random rotation values) + (random-rot) + (random-rot) + (random-rot) + (random-rot) + ) + ) ' + matched = assert.is.truthy verify_parse program, str + assert.is.equal str, matched\stringify! + + test 'nested tags', -> + str = '([2]a ([3]b))' + matched = assert.is.truthy verify_parse program, str + assert.is.equal str, matched\stringify! diff --git a/spec/internal/pureop_spec.moon b/spec/internal/pureop_spec.moon new file mode 100644 index 0000000..a8fa823 --- /dev/null +++ b/spec/internal/pureop_spec.moon @@ -0,0 +1,128 @@ +import do_setup, do_teardown, invoke_op from require 'spec.test_setup' +import PureOp, Input, T, sig, any from require 'alv.base' +import RTNode from require 'alv' + +setup do_setup +teardown do_teardown + +class TestPureOp extends PureOp + pattern: (any.num / sig.str)*3 + type: T.num + tick: => @out\set 1 + +literal = (result) -> + eval: -> + si = T.num\mk_sig! + RTNode :result, side_inputs: { [si]: Input.hot si } + +describe 'PureOp', -> + it 'matches the pattern', -> + assert.has.error -> invoke_op TestPureOp, {} + assert.has.error -> invoke_op TestPureOp, { literal T.bool\mk_evt! } + invoke_op TestPureOp, { literal T.num\mk_const 1 } + invoke_op TestPureOp, { literal T.str\mk_const 'hello' } + invoke_op TestPureOp, { literal T.num\mk_evt! } + + describe 'with constant inputs', -> + local rtn + it 'ticks once', -> + tiq = spy.on TestPureOp.__base, 'tick' + rtn = invoke_op TestPureOp, { T.num\mk_const(1), T.str\mk_const('hello') } + assert.spy(tiq).was_called_with match.is_ref(rtn.op), true + assert.is.equal 1, rtn.result! + + it 'is constant', -> + assert.is.equal '=', rtn\metatype! + + describe 'with signal inputs', -> + a = T.num\mk_sig 1 + b = T.num\mk_sig 2 + c = T.num\mk_sig 3 + tiq = spy.on TestPureOp.__base, 'tick' + rtn = invoke_op TestPureOp, { (literal a), (literal b), (literal c) } + op = rtn.op + + it 'sets up hot inputs', -> + assert.is.equal 3, #op.inputs + assert.is.equal a, op.inputs[1].result + assert.is.equal b, op.inputs[2].result + assert.is.equal c, op.inputs[3].result + assert.is.equal 'hot', op.inputs[1].mode + assert.is.equal 'hot', op.inputs[2].mode + assert.is.equal 'hot', op.inputs[3].mode + assert.spy(tiq).was_called! + + it 'has signal output', -> + assert.is.equal '~', op.out.metatype + + describe 'with event inputs', -> + a = T.num\mk_sig 1 + b = T.num\mk_evt 2 + c = T.num\mk_sig 3 + tiq = spy.on TestPureOp.__base, 'tick' + rtn = invoke_op TestPureOp, { (literal a), (literal b), (literal c) } + op = rtn.op + + it 'sets up hot input only for evt', -> + assert.is.equal 3, #op.inputs + assert.is.equal a, op.inputs[1].result + assert.is.equal b, op.inputs[2].result + assert.is.equal c, op.inputs[3].result + assert.is.equal 'cold', op.inputs[1].mode + assert.is.equal 'hot', op.inputs[2].mode + assert.is.equal 'cold', op.inputs[3].mode + assert.spy(tiq).was_not_called! + + it 'has event output', -> + assert.is.equal '!', op.out.metatype + + it 'only allows one event input', -> + a, b = T.num\mk_evt!, T.num\mk_evt! + assert.has.error -> invoke_op TestPureOp, { (literal a), (literal b) } + + it 'supports nested input patterns', -> + class NestedInputOp extends PureOp + pattern: (any.num + sig.str)\named('a', 'b')\rep 2, 2 + type: T.num + tick: => @out\set 1 + + num = T.num\mk_sig 1 + str = T.str\mk_sig 'hello' + oth = T.num\mk_evt 2 + args = { (literal num), (literal str), (literal oth), (literal str) } + rtn = invoke_op NestedInputOp, args + op = rtn.op + + assert.is.equal 2, #op.inputs + assert.is.equal num, op.inputs[1].a.result + assert.is.equal str, op.inputs[1].b.result + assert.is.equal oth, op.inputs[2].a.result + assert.is.equal str, op.inputs[2].b.result + assert.is.equal 'cold', op.inputs[1].a.mode + assert.is.equal 'cold', op.inputs[1].b.mode + assert.is.equal 'hot', op.inputs[2].a.mode + assert.is.equal 'cold', op.inputs[2].b.mode + + it 'supports dynamically generating the output type', -> + class DynamicOp extends PureOp + pattern: sig.num + any! + type: (inputs) => inputs[2]\type! + tick: => @out\set @inputs[2]! + typ = spy.on DynamicOp, 'type' + + a = T.num\mk_sig 1 + num = T.num\mk_sig 1 + str = T.str\mk_sig 1 + sym = T.sym\mk_evt 1 + + rtn = invoke_op DynamicOp, { (literal a), (literal num) } + assert.is.equal '~', rtn.result.metatype + assert.is.equal num, rtn.result + + rtn = invoke_op DynamicOp, { (literal a), (literal str) } + assert.is.equal '~', rtn.result.metatype + assert.is.equal str, rtn.result + + rtn = invoke_op DynamicOp, { (literal a), (literal sym) } + assert.is.equal '!', rtn.result.metatype + assert.is.equal T.sym, rtn.result.type diff --git a/spec/internal/registry_spec.moon b/spec/internal/registry_spec.moon new file mode 100644 index 0000000..4b0cfc3 --- /dev/null +++ b/spec/internal/registry_spec.moon @@ -0,0 +1,8 @@ +import Registry, Tag from require 'alv.registry' +import Logger from require 'alv.logger' +Logger\init 'silent' + +mk = -> + mock destroy: => + +describe 'registry', -> diff --git a/spec/internal/result/const_spec.moon b/spec/internal/result/const_spec.moon new file mode 100644 index 0000000..3f31306 --- /dev/null +++ b/spec/internal/result/const_spec.moon @@ -0,0 +1,158 @@ +import do_setup from require 'spec.test_setup' +import Constant, RTNode, Scope, SimpleRegistry, T from require 'alv' +import Op, Builtin from require 'alv.base' + +class TestOp extends Op + new: (...) => super ... + +class TestBuiltin extends Builtin + new: (...) => + +setup do_setup + +describe 'Constant', -> + it 'requires a value', -> + assert.has.error -> Constant.num! + assert.has.error -> Constant T.num + assert.has.no.error -> Constant T.bool, false + + it 'stringifies well', -> + assert.is.equal "<num= 4>", tostring Constant.num 4 + assert.is.equal "<bool= true>", tostring Constant.bool true + assert.is.equal "<bool= false>", tostring Constant.bool false + + describe '.wrap', -> + it 'wraps numbers', -> + got = Constant.wrap 3 + assert.is.equal T.num, got.type + assert.is.equal 3, got.value + + it 'wraps strings', -> + got = Constant.wrap "im a happy string" + assert.is.equal T.str, got.type + assert.is.equal "im a happy string", got.value + + it 'wraps Constants', -> + pi = Constant.num 3.14 + got = Constant.wrap pi + + assert.is.equal pi, got + + it 'wraps Opdefs', -> + got = Constant.wrap TestOp + + assert.is.equal T.opdef, got.type + assert.is.equal TestOp, got.value + + it 'wraps Bultins', -> + got = Constant.wrap TestBuiltin + + assert.is.equal T.builtin, got.type + assert.is.equal TestBuiltin, got.value + + it 'wraps Scopes', -> + sub = Scope! + got = Constant.wrap sub + + assert.is.equal T.scope, got.type + assert.is.equal sub, got.value + + it 'wraps tables', -> + pi = Constant.num 3.14 + got = Constant.wrap { :pi } + + assert.is.equal T.scope, got.type + assert.is.equal pi, (got.value\get 'pi')\const! + + describe ':unwrap', -> + it 'returns the raw value!', -> + assert.is.equal 3.14, (Constant.num 3.14)\unwrap! + assert.is.equal 'hi', (Constant.str 'hi')\unwrap! + assert.is.equal 'hi', (Constant.sym 'hi')\unwrap! + + test 'can assert the type', -> + assert.is.equal 3.14, (Constant.num 3.14)\unwrap T.num + assert.is.equal 'hi', (Constant.str 'hi')\unwrap T.str + assert.is.equal 'hi', (Constant.sym 'hi')\unwrap T.sym + assert.has_error -> (Constant.num 3.14)\unwrap T.sym + assert.has_error -> (Constant.str 'hi')\unwrap T.num + assert.has_error -> (Constant.sym 'hi')\unwrap T.str + + test 'has __call shorthand', -> + assert.is.equal 3.14, (Constant.num 3.14)! + assert.is.equal 'hi', (Constant.str 'hi')! + assert.is.equal 'hi', (Constant.sym 'hi')! + assert.is.equal 3.14, (Constant.num 3.14) T.num + assert.is.equal 'hi', (Constant.str 'hi') T.str + assert.is.equal 'hi', (Constant.sym 'hi') T.sym + assert.has_error -> (Constant.num 3.14) T.sym + assert.has_error -> (Constant.str 'hi') T.num + assert.has_error -> (Constant.sym 'hi') T.str + + describe 'overrides __eq', -> + it 'compares the type', -> + val = Constant.num 3 + assert.is.equal (Constant.num 3), val + assert.not.equal (Constant.str '3'), val + + val = Constant.str 'hello' + assert.is.equal (Constant.str 'hello'), val + assert.not.equal (Constant.sym 'hello'), val + + it 'compares the value', -> + val = Constant.num 3 + assert.is.equal (Constant.num 3), val + assert.not.equal (Constant.num 4), val + + it ':dirty is always false', -> + val = Constant.num 3 + assert.is.false val\dirty! + + val.value = 4 + assert.is.false val\dirty! + + describe ':eval', -> + it 'turns numbers into consts', -> + assert_noop = (val) -> + assert.is.equal val, val\eval!\const! + + assert_noop Constant.num 2 + assert_noop Constant.str 'hello' + + it 'looks up symbols in the scope', -> + scope = with Scope! + \set 'number', RTNode result: Constant.num 3 + \set 'hello', RTNode result: Constant.str "world" + \set 'goodbye', RTNode result: Constant.sym "again" + + assert_eval = (sym, val) -> + const = Constant.sym sym + assert.is.equal val, (const\eval scope)\const! + + assert_eval 'number', Constant.num 3 + assert_eval 'hello', Constant.str "world" + assert_eval 'goodbye', Constant.sym "again" + + it ':clones literals as themselves', -> + assert_noop = (val) -> assert.is.equal val, val\clone! + + assert_noop Constant.num 2 + assert_noop Constant.str 'hello' + assert_noop Constant.sym 'world' + + describe ':fork', -> + it 'is equal to the original', -> + a = Constant.num 2 + b = Constant.str 'asdf' + c = Constant T.weird, {}, '(raw)' + + aa, bb, cc = a\fork!, b\fork!, c\fork! + assert.is.equal a, aa + assert.is.equal b, bb + assert.is.equal c, cc + + assert.is.false aa\dirty! + assert.is.false bb\dirty! + assert.is.false cc\dirty! + + assert.is.equal c.raw, cc.raw diff --git a/spec/internal/result/evt_spec.moon b/spec/internal/result/evt_spec.moon new file mode 100644 index 0000000..46d4cec --- /dev/null +++ b/spec/internal/result/evt_spec.moon @@ -0,0 +1,144 @@ +import do_setup from require 'spec.test_setup' +import EvtStream, RTNode, Scope, SimpleRegistry, T from require 'alv' +import Op, Builtin from require 'alv.base' + +setup do_setup + +describe 'EvtStream', -> + it 'stringifies well', -> + number = EvtStream T.num + assert.is.equal "<num! nil>", tostring number + + number\set 4 + assert.is.equal "<num! 4>", tostring number + + bool = EvtStream T.bool + bool\set true + assert.is.equal "<bool! true>", tostring bool + + COPILOT\next_tick! + + bool\set false + assert.is.equal "<bool! false>", tostring bool + + describe ':unwrap', -> + it 'returns the set value', -> + stream = EvtStream T.num + assert.is.nil stream\unwrap! + + stream\set 3.14 + assert.is.equal 3.14, stream\unwrap! + + it 'returns nil if not dirty', -> + stream = EvtStream T.num + assert.is.nil stream\unwrap! + + stream\set 3.14 + COPILOT\next_tick! + assert.is.nil stream\unwrap! + + test 'can assert the type', -> + assert.is.nil (EvtStream T.num)\unwrap T.num + assert.is.nil (EvtStream T.str)\unwrap T.str + assert.is.nil (EvtStream T.sym)\unwrap T.sym + assert.has_error -> (EvtStream T.num)\unwrap T.sym + assert.has_error -> (EvtStream T.str)\unwrap T.num + assert.has_error -> (EvtStream T.sym)\unwrap T.str + + test 'has __call shorthand', -> + stream = EvtStream T.num + assert.is.nil stream! + + stream\set 3.14 + assert.is.equal 3.14, stream! + + describe ':set', -> + it 'sets the value', -> + stream = EvtStream T.num + assert.is.false stream\dirty! + + stream\set 4 + assert.is.equal 4, stream\unwrap! + assert.is.true stream\dirty! + + COPILOT\next_tick! + + assert.is.false stream\dirty! + stream\set 3 + assert.is.equal 3, stream\unwrap! + assert.is.true stream\dirty! + + it 'ignores nil values', -> + stream = EvtStream T.num + assert.is.nil stream\unwrap! + assert.is.false stream\dirty! + + stream\set! + assert.is.nil stream\unwrap! + assert.is.false stream\dirty! + + stream\set nil + assert.is.nil stream\unwrap! + assert.is.false stream\dirty! + + stream\set false + assert.is.equal false, stream\unwrap! + assert.is.true stream\dirty! + + it 'errors when set twice', -> + stream = EvtStream T.num + stream\set 1 + assert.has.error -> stream\set 2 + assert.is.equal 1, stream\unwrap! + + it 'resets on the next tick', -> + stream = EvtStream T.num + stream\set 1 + + COPILOT\next_tick! + + assert.is.false stream\dirty! + stream\set 2 + assert.is.equal 2, stream\unwrap! + assert.is.true stream\dirty! + + describe ':fork', -> + it 'is clean', -> + a = EvtStream T.num + b = EvtStream T.str + b\set 'asdf' + + aa, bb = a\fork!, b\fork! + assert.is.nil aa! + assert.is.nil bb! + assert.is.false aa\dirty! + assert.is.false bb\dirty! + + it 'leaves the original', -> + a = EvtStream T.num + b = EvtStream T.str + b\set 'asdf' + + aa, bb = a\fork!, b\fork! + assert.is.nil a! + assert.is.equal 'asdf', b! + assert.is.false a\dirty! + assert.is.true b\dirty! + + it 'isolates the original from the fork', -> + a = EvtStream T.num + b = EvtStream T.str + + aa, bb = a\fork!, b\fork! + a\set 1 + bb\set 2 + + assert.is.equal 1, a! + assert.is.true a\dirty! + assert.is.nil aa! + assert.is.false aa\dirty! + + assert.is.equal 2, bb! + assert.is.true bb\dirty! + assert.is.nil b! + assert.is.false b\dirty! diff --git a/spec/internal/result/sig_spec.moon b/spec/internal/result/sig_spec.moon new file mode 100644 index 0000000..65363bc --- /dev/null +++ b/spec/internal/result/sig_spec.moon @@ -0,0 +1,116 @@ +import do_setup from require 'spec.test_setup' +import SigStream, Constant, RTNode, Scope, SimpleRegistry, T from require 'alv' +import Op, Builtin from require 'alv.base' + +setup do_setup + +describe 'SigStream', -> + it 'stringifies well', -> + assert.is.equal "<num~ 4>", tostring SigStream T.num, 4 + assert.is.equal "<bool~ true>", tostring SigStream T.bool, true + assert.is.equal "<bool~ false>", tostring SigStream T.bool, false + + describe ':unwrap', -> + it 'returns the raw value!', -> + assert.is.equal 3.14, (SigStream T.num, 3.14)\unwrap! + assert.is.equal 'hi', (SigStream T.str, 'hi')\unwrap! + assert.is.equal 'hi', (SigStream T.sym, 'hi')\unwrap! + + test 'can assert the type', -> + assert.is.equal 3.14, (SigStream T.num, 3.14)\unwrap T.num + assert.is.equal 'hi', (SigStream T.str, 'hi')\unwrap T.str + assert.is.equal 'hi', (SigStream T.sym, 'hi')\unwrap T.sym + assert.has_error -> (SigStream T.num, 3.14)\unwrap T.sym + assert.has_error -> (SigStream T.str, 'hi')\unwrap T.num + assert.has_error -> (SigStream T.sym, 'hi')\unwrap T.str + + test 'has __call shorthand', -> + assert.is.equal 3.14, (SigStream T.num, 3.14)! + assert.is.equal 'hi', (SigStream T.str, 'hi')! + assert.is.equal 'hi', (SigStream T.sym, 'hi')! + assert.is.equal 3.14, (SigStream T.num, 3.14) T.num + assert.is.equal 'hi', (SigStream T.str, 'hi') T.str + assert.is.equal 'hi', (SigStream T.sym, 'hi') T.sym + assert.has_error -> (SigStream T.num, 3.14) T.sym + assert.has_error -> (SigStream T.str, 'hi') T.num + assert.has_error -> (SigStream T.sym, 'hi') T.str + + describe 'overrides __eq', -> + it 'compares the type', -> + val = SigStream T.num, 3 + assert.is.equal (SigStream T.num, 3), val + assert.not.equal (SigStream T.str, '3'), val + + val = SigStream T.str, 'hello' + assert.is.equal (SigStream T.str, 'hello'), val + assert.not.equal (SigStream T.sym, 'hello'), val + + it 'compares the value', -> + val = SigStream T.num, 3 + assert.is.equal (SigStream T.num, 3), val + assert.not.equal (SigStream T.num, 4), val + + it 'can be compared to a Constant', -> + val = SigStream T.num, 3 + assert.is.equal (Constant.num 3), val + assert.not.equal (Constant.num 4), val + + val = SigStream T.str, 'hello' + assert.is.equal (Constant.str 'hello'), val + assert.not.equal (Constant.sym 'hello'), val + + describe ':set', -> + it 'sets the value', -> + val = SigStream T.num, 3 + assert.is.equal (SigStream T.num, 3), val + + val\set 4 + assert.is.equal (SigStream T.num, 4), val + assert.not.equal (SigStream T.num, 3), val + + it 'marks the value dirty', -> + val = SigStream T.num, 3 + assert.is.false val\dirty! + + val\set 4 + assert.is.true val\dirty! + + describe ':fork', -> + it 'is equal to the original', -> + a = SigStream T.num, 2 + b = SigStream T.str, 'asdf' + c = with SigStream T.weird, {}, '(raw)' + \set {} + + aa, bb, cc = a\fork!, b\fork!, c\fork! + assert.is.equal a, aa + assert.is.equal b, bb + assert.is.equal c, cc + + assert.is.false aa\dirty! + assert.is.false bb\dirty! + assert.is.true cc\dirty! + + assert.is.equal c.raw, cc.raw + + it 'isolates the original from the fork', -> + a = SigStream T.num, 3 + b = with SigStream T.weird, {}, '(raw)' + \set {} + + aa, bb = a\fork!, b\fork! + + bb\set {false} + + assert.is.same {}, b! + assert.is.same {false}, bb! + assert.is.true b\dirty! + assert.is.true bb\dirty! + + COPILOT\next_tick! + aa\set 4 + + assert.is.equal 3, a! + assert.is.equal 4, aa! + assert.is.false a\dirty! + assert.is.true aa\dirty! diff --git a/spec/internal/rtnode_spec.moon b/spec/internal/rtnode_spec.moon new file mode 100644 index 0000000..f757067 --- /dev/null +++ b/spec/internal/rtnode_spec.moon @@ -0,0 +1,216 @@ +import do_setup from require 'spec.test_setup' +import RTNode, Scope, SimpleRegistry from require 'alv' +import T, Input, Op, Constant from require 'alv.base' + +class IOOp extends Op + new: (...) => + super ... + @is_dirty = true + + poll: => @is_dirty + +make_op = (Class, inputs={}) -> + with Class! + \setup inputs + +setup do_setup + +describe 'RTNode', -> + describe 'constructor', -> + it 'takes result, children', -> + result = Constant.num 3 + + a = RTNode! + b = RTNode! + children = { a, b } + + node = RTNode :result, :children + + assert.is.equal result, node.result + assert.is.same children, node.children + + it 'takes op and detects io_ops', -> + op = make_op Op + rtn = RTNode :op + assert.is.equal op, rtn.op + assert.is.same {}, rtn.io_ops + + op = make_op IOOp + rtn = RTNode :op + assert.is.equal op, rtn.op + assert.is.same { op }, rtn.io_ops + + describe 'detects Op inputs', -> + it '(hot -> side_input)', -> + sig = T.num\mk_sig! + inp = Input.hot sig + rtn = RTNode op: make_op Op, { inp } + assert.is.same { [sig]: inp }, rtn.side_inputs + + it '(cold -> discard)', -> + rtn = RTNode op: make_op Op, { Input.cold T.num\mk_sig! } + assert.is.same {}, rtn.side_inputs + + it "(childrens' results -> discard)", -> + child = RTNode op: make_op(Op), result: T.num\mk_sig 123 + parent = RTNode children: { child }, op: make_op Op, { Input.hot child.result } + assert.is.same {}, parent.side_inputs + + describe 'lifts up', -> + it 'side_inputs from children', -> + expected_inputs = {} + children = {} + + for i=1,3 + result = T.num\mk_sig! + input = Input.hot result + table.insert children, RTNode op: make_op Op, { input } + expected_inputs[result] = input + + mid_rtn = RTNode children: children + out_rtn = RTNode children: { mid_rtn } + + assert.is.same expected_inputs, mid_rtn.side_inputs + assert.is.same expected_inputs, out_rtn.side_inputs + + it 'io_ops from children', -> + a_op = make_op IOOp + a_rtn = RTNode op: a_op + + b_op = make_op IOOp + b_rtn = RTNode op: b_op + + c_op = make_op Op + c_rtn = RTNode op: c_op + + mid_rtn = RTNode children: { a_rtn, b_rtn, c_rtn } + out_op = make_op IOOp + out_rtn = RTNode children: { mid_rtn }, op: out_op + + assert.is.same { a_op, b_op }, mid_rtn.io_ops + assert.is.same { a_op, b_op, out_op }, out_rtn.io_ops + + it ':type gets type and assets value', -> + node = RTNode result: Constant.num 2 + assert.is.equal T.num, node\type! + + node = RTNode! + assert.has.error -> node\type! + + it ':is_const', -> + result = Constant.num 2 + pure = RTNode :result + impure = RTNode op: make_op Op, { Input.hot T.num\mk_sig! } + + assert.is.true pure\is_const! + assert.is.false impure\is_const! + + assert.is.equal result, pure\const! + assert.has.error -> impure\const! + assert.has.error (-> impure\const 'test'), 'test' + + it ':make_ref', -> + result = T.num\mk_sig 2 + input = Input.hot result + op = make_op IOOp, { input } + thick = RTNode :result, :op, children: { RTNode!, RTNode! } + ref = thick\make_ref! + + assert ref + assert.is.equal thick.result, ref.result + assert.is.same thick.side_inputs, ref.side_inputs + assert.is.same {}, ref.children + assert.is.same {}, ref.io_ops + assert.is.nil ref.op + + describe ':poll_io', -> + it 'polls all io_ops in tree', -> + child = RTNode op: make_op IOOp + node = RTNode children: { child }, op: make_op IOOp + + sc = spy.on child.op, 'poll' + sn = spy.on node.op, 'poll' + node\poll_io! + + assert.spy(sc).was_called_with match.ref child.op + assert.spy(sn).was_called_with match.ref node.op + + it 'returns whether any ops were dirty', -> + child = RTNode op: make_op IOOp + node = RTNode children: { child }, op: make_op IOOp + + assert.is.true node\poll_io! + + child.op.is_dirty = false + assert.is.true node\poll_io! + + node.op.is_dirty = false + assert.is.false node\poll_io! + + child.op.is_dirty = true + assert.is.true node\poll_io! + + describe ':tick', -> + local a_value, a_child, a_input + local b_value, b_child, b_input + before_each -> + a_value = T.num\mk_evt! + a_input = Input.hot a_value + a_child = RTNode op: make_op Op, { a_input } + + b_value = T.num\mk_evt! + b_input = Input.hot b_value + b_child = RTNode op: make_op Op, { b_input } + + it 'updates children when a side_input is dirty', -> + a_value\set 1 + assert.is.true a_input\dirty! + assert.is.false b_input\dirty! + + a = spy.on a_child, 'tick' + b = spy.on b_child, 'tick' + + node = RTNode children: { a_child, b_child } + node\tick! + + assert.spy(a).was_called_with match.ref a_child + assert.spy(b).was_called_with match.ref b_child + + it 'early-outs when no side_inputs are dirty', -> + assert.is.false a_input\dirty! + assert.is.false b_input\dirty! + + a = spy.on a_child, 'tick' + b = spy.on b_child, 'tick' + + node = RTNode children: { a_child, b_child } + node\tick! + + assert.spy(a).was_not_called! + assert.spy(b).was_not_called! + + it 'updates op when any op-inputs are dirty', -> + a_value\set 1 + assert.is.true a_input\dirty! + assert.is.false b_input\dirty! + + op = make_op Op, { a: Input.hot a_value } + s = spy.on op, 'tick' + + node = RTNode :op, children: { a_child, b_child } + node\tick! + + assert.spy(s).was_called_with match.ref op + + it 'early-outs when no op-inputs are dirty', -> + a_value\set 1 + assert.is.true a_input\dirty! + assert.is.false b_input\dirty! + + op = make_op Op, { Input.hot b_value } + s = spy.on op, 'tick' + + node = RTNode :op, children: { a_child, b_child } + node\tick! + + assert.spy(s).was_not_called! diff --git a/spec/internal/scope_spec.moon b/spec/internal/scope_spec.moon new file mode 100644 index 0000000..b4a55f1 --- /dev/null +++ b/spec/internal/scope_spec.moon @@ -0,0 +1,166 @@ +import Scope, T, Constant, RTNode from require 'alv' +import Op from require 'alv.base' +import Logger from require 'alv.logger' +Logger\init 'silent' + +class TestOp extends Op + new: (...) => super ... + +wrap_res = (result) -> RTNode :result + +describe 'Scope', -> + describe 'constifies', -> + scope = Scope! + + test 'numbers', -> + scope\set_raw 'num', 3 + + got = (scope\get 'num')\const! + assert.is.equal (Constant.num 3), got + + test 'strings', -> + scope\set_raw 'str', "im a happy string" + + got = (scope\get 'str')\const! + assert.is.equal (Constant.str "im a happy string"), got + + test 'Values', -> + pi = Constant.num 3.14 + scope\set_raw 'pi', pi + assert.is.equal pi, (scope\get 'pi')\const! + + test 'Opdefs', -> + scope\set_raw 'test', TestOp + + got = (scope\get 'test')\const! + assert.is.equal TestOp, got T.opdef + + test 'Scopes', -> + sub = Scope! + scope\set_raw 'sub', sub + + got = (scope\get 'sub')\const! + assert.is.equal sub, got T.scope + + test 'tables', -> + pi = Constant.num 3.14 + scope\set_raw 'math', { :pi } + + got = (scope\get 'math')\const! + assert.is.equal T.scope, got.type + assert.is.equal Scope, got.value.__class + assert.is.equal pi, (got.value\get 'pi')\const! + assert.is.equal pi, (scope\get 'math/pi')\const! + + it 'stringifies well', -> + scope = Scope.from_table { + num: 3 + str: "im a happy string" + :pi + math: :pi + test: TestOp + } + sub = Scope Scope scope + + assert.is.equal "<Scope ^1 []>", tostring sub + assert.is.equal "<Scope ^-1 [math, num, str, test]>", tostring scope + scope\set_raw 'a', Constant.num 1 + scope\set_raw 'b', Constant.num 2 + scope\set_raw 'c', Constant.num 3 + assert.is.equal "<Scope ^-1 [a, b, c, math, num, …]>", tostring scope + + it 'wraps Values in from_table', -> + pi = Constant.num 3.14 + scope = Scope.from_table { + num: 3 + str: "im a happy string" + :pi + math: :pi + test: TestOp + } + + got = (scope\get 'num')\const! + assert.is.equal 3, got T.num + + got = (scope\get 'str')\const! + assert.is.equal "im a happy string", got T.str + + assert.is.equal pi, (scope\get 'pi')\const! + + got = (scope\get 'test')\const! + assert.is.equal TestOp, got T.opdef + + got = (scope\get 'math')\const! + assert.is.equal T.scope, got.type + assert.is.equal pi, (scope\get 'math/pi')\const! + + it 'gets from nested scopes', -> + root = Scope! + a = Scope! + b = Scope! + + pi = Constant.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')\const! + + describe 'can set symbols', -> + one = wrap_res Constant.num 1 + two = wrap_res Constant.num 2 + scope = Scope! + + it 'disallows re-setting symbols', -> + scope\set 'test', one + assert.is.equal one, scope\get 'test' + + it 'throws if overwriting', -> + assert.has.error -> scope\set 'test', two + assert.is.equal one, scope\get 'test' + + describe 'inheritance', -> + root = Scope! + root\set_raw 'hidden', 1234 + root\set_raw 'inherited', "inherited string" + + scope = Scope root + + it 'allows access', -> + got = (scope\get 'inherited')\const! + assert.is.equal "inherited string", got T.str + + it 'can be shadowed', -> + scope\set_raw 'hidden', "overwritten" + + got = (scope\get 'hidden')\const! + assert.is.equal "overwritten", got T.str + + describe 'dynamic inheritance', -> + root = Scope! + dyn_root = Scope! + + root\set_raw 'normal', 'normal' + root\set_raw '*dynamic*', 'normal' + dyn_root\set_raw 'normal', 'dynamic' + dyn_root\set_raw '*dynamic*', 'dynamic' + + dyn_root\set_raw '*nested*', { value: 3 } + + it 'follows a different parent', -> + merged = Scope root, dyn_root + assert.is.equal 'normal', (merged\get 'normal').result! + assert.is.equal 'dynamic', (merged\get '*dynamic*').result! + + it 'falls back to the immediate parent', -> + merged = Scope root + assert.is.equal 'normal', (merged\get '*dynamic*').result! + + it 'looks in self first', -> + merged = Scope root + merged\set_raw '*dynamic*', 'merged' + assert.is.equal 'merged', (merged\get '*dynamic*').result! + + it 'can resolve nested', -> + merged = Scope root, dyn_root + assert.is.equal 3, (merged\get '*nested*/value').result! diff --git a/spec/internal/tag_spec.moon b/spec/internal/tag_spec.moon new file mode 100644 index 0000000..f92dca0 --- /dev/null +++ b/spec/internal/tag_spec.moon @@ -0,0 +1,91 @@ +import do_setup from require 'spec.test_setup' +import Tag from require 'alv.tag' +import Registry from require 'alv.registry' + +setup do_setup + +describe 'Tag', -> + describe 'should be constructable', -> + it 'by parsing', -> + tag = Tag.parse '2' + assert tag + assert.is.equal 2, tag.value + assert.is.equal '[2]', tag\stringify! + assert.is.equal '2', tostring tag + + it 'as blank Tags', -> + tag = Tag.blank! + assert tag + assert.is.nil tag.value + assert.is.equal '', tag\stringify! + assert.is.equal '?', tostring tag + + describe 'should be clonable', -> + do_asserts = (tag, expect) -> + assert tag + assert.is.nil tag.value + assert.is.equal expect, tostring tag + assert.has.error tag\stringify + + it 'from parsed tags', -> + parent = Tag.parse '1' + original = Tag.parse '2' + tag = original\clone parent + do_asserts tag, '1.2' + + it 'but not from blank tags', -> + parent = Tag.parse '1' + original = Tag.blank! + tag = original\clone parent + do_asserts tag, '1.?' + + it 'with blank parent', -> + parent = Tag.blank! + original = Tag.parse '2' + tag = original\clone parent + do_asserts tag, '?.2' + + it 'completely blank', -> + parent = Tag.blank! + original = Tag.blank! + tag = original\clone parent + do_asserts tag, '?.?' + + describe 'should be set-able', -> + it 'only if blank', -> + tag = Tag.parse '42' + assert.has.error -> tag\set 43 + + clone = tag\clone Tag.parse '3' + assert.has.error -> clone\set 42 + + clone = tag\clone Tag.blank! + assert.has.error -> clone\set 42 + + it 'and stores the value', -> + blank = Tag.blank! + blank\set 12 + + assert.is.equal blank.value, 12 + + it 'sets the original if cloned', -> + original = Tag.blank! + parent = Tag.parse '7' + + o_set = spy.on original, 'set' + p_set = spy.on parent, 'set' + + clone = original\clone parent + clone\set 11 + + assert.spy(o_set).was_called_with (match.is_ref original), 11 + assert.spy(p_set).was_not_called! + + assert.is.equal original.value, 11 + + it 'requires the parent to be registered if cloned', -> + original = Tag.blank! + parent = Tag.blank! + + clone = original\clone parent + assert.has.error -> clone\set 11 diff --git a/spec/internal/type_spec.moon b/spec/internal/type_spec.moon new file mode 100644 index 0000000..727e90b --- /dev/null +++ b/spec/internal/type_spec.moon @@ -0,0 +1,175 @@ +require 'spec.test_setup' +import T, Type, Primitive, Array, Struct from require 'alv.type' +import ancestor from require 'alv.util' + +bool = Primitive 'bool' +num = Primitive 'num' +str = Primitive 'str' + +describe 'Primitive', -> + it 'inherits from Type', -> + assert.is.equal Type, ancestor Primitive.__class + + it 'exposes name', -> + assert.is.equal 'bool', bool.name + assert.is.equal 'num', num.name + assert.is.equal 'str', str.name + + it 'stringifies well', -> + assert.is.equal 'bool', tostring bool + assert.is.equal 'num', tostring num + assert.is.equal 'str', tostring str + + it 'implements __eq sensibly', -> + assert.is.equal (Primitive 'bool'), bool + assert.is.equal (Primitive 'num'), num + assert.is.equal (Primitive 'str'), str + assert.not.equal num, bool + assert.not.equal str, num + + it ':pp pretty-prints values', -> + assert.is.equal 'true', bool\pp true + assert.is.equal 'false', bool\pp false + assert.is.equal 'nil', bool\pp nil + assert.is.equal '4.134', num\pp 4.134 + assert.is.equal '"hello"', str\pp "hello" + + it ':eq compares values', -> + a, b = {}, {} + assert.is.true bool\eq true, true + assert.is.false bool\eq true, false + assert.is.true num\eq 1, 1 + assert.is.false num\eq 1, -1 + assert.is.true num\eq a, a + assert.is.false num\eq a, b + + it ':get errors', -> + assert.has.error -> bool\get 'hello' + assert.has.error -> bool\get 2 + assert.has.error -> bool\get! + +describe 'Array', -> + vec3 = Array 3, num + str32 = Array 2, Array 3, str + + it 'inherits from Type', -> + assert.is.equal Type, ancestor Array.__class + + it 'exposes size', -> + assert.is.equal 3, vec3.size + assert.is.equal 2, str32.size + + it 'exposes inner type', -> + assert.is.equal num, vec3.type + assert.is.equal (Array 3, str), str32.type + + it 'stringifies well', -> + assert.is.equal 'num[3]', tostring vec3 + assert.is.equal 'my-type[3][24]', tostring Array 24, Array 3, 'my-type' + + it 'implements __eq sensibly', -> + assert.is.equal vec3, Array 3, num + assert.not.equal vec3, Array 2, num + assert.not.equal vec3, Array 3, str + + it ':pp pretty-prints values', -> + assert.is.equal '[1 2 3]', vec3\pp { 1, 2, 3 } + assert.is.equal '[["a" "b" "c"] ["d" "e" "f"]]', + str32\pp { {'a', 'b', 'c'}, {'d', 'e', 'f'} } + + it ':eq compares values', -> + a, b, c = {1, 2, 3}, {1, 2, 3}, {} + assert.is.true vec3\eq a, a + assert.is.true vec3\eq a, b + assert.is.false vec3\eq a, {1, 2, 4} + assert.is.true vec3\eq {1, 2, c}, {1, 2, c} + assert.is.false vec3\eq {1, 2, c}, {1, 2, {}} + assert.is.true str32\eq { {'a', 'b', 'c'}, {'d', 'e', 'f'} }, + { {'a', 'b', 'c'}, {'d', 'e', 'f'} } + assert.is.false str32\eq { {'a', 'b', 'c'}, {'d', 'e', 'f'} }, + { {'a', 'b', 'c'}, {'d', 'e', 'g'} } + + it ':get verifies size range', -> + assert.has.error -> vec3\get -1 + assert.is.equal num, vec3\get 0 + assert.is.equal num, vec3\get 1 + assert.is.equal num, vec3\get 2 + assert.has.error -> vec3\get 3 + + assert.is.equal (Array 3, str), str32\get 1 + + assert.has.error -> vec3\get! + assert.has.error -> vec3\get 'fail' + +describe 'Struct', -> + play = Struct { note: str, dur: num } + abc = Struct { c: num, b: num, a: num } + + it 'inherits from Type', -> + assert.is.equal Type, ancestor Struct.__class + + it 'exposes inner types', -> + assert.is.same { note: str, dur: num }, play.types + assert.is.same { c: num, b: num, a: num }, abc.types + + it 'stringifies well', -> + assert.is.equal '{dur: num note: str}', tostring play + assert.is.equal '{a: num b: num c: num}', tostring abc + + it 'implements __eq sensibly', -> + assert.is.equal play, Struct { note: str, dur: num } + assert.not.equal play, Struct { note: str } + assert.not.equal play, Struct { note: str, dur: str } + assert.not.equal play, Struct { note: str, dur: num, extra: num } + + it ':pp pretty-prints values', -> + assert.is.equal '{dur: 0.5 note: "a"}', play\pp { dur: 0.5, note: 'a' } + assert.is.equal '{a: 1 b: 2 c: 3}', abc\pp { a: 1, b: 2, c: 3 } + + it ':eq compares values', -> + a, b, c = { dur: 0.5, note: 'a' }, { dur: 0.5, note: 'a' }, {} + assert.is.true play\eq a, a + assert.is.true play\eq a, b + assert.is.false play\eq a, { dur: 0.5, note: 'b' } + assert.is.true play\eq { dur: 0.5, note: c }, { dur: 0.5, note: c } + assert.is.false play\eq { dur: 0.5, note: c }, { dur: 0.5, note: {} } + + it ':get verifies key exists', -> + assert.is.equal str, play\get 'note' + assert.is.equal num, play\get 'dur' + assert.has.error -> play\get! + assert.has.error -> play\get '' + assert.has.error -> play\get 'something' + +describe 'T', -> + it 'provides shorthand for Primitives', -> + assert.is.equal num, T.num + assert.is.equal str, T.str + assert.is.equal (Primitive 'midi/evt'), T['midi/evt'] + +for type in *{num, str, (Array 3, num)} + describe "#{type}", -> + describe ':mk_sig', -> + it 'can create value-less Streams', -> + stream = type\mk_sig! + assert.is.equal 'SigStream', stream.__class.__name + assert.is.equal type, stream.type + assert.is.nil stream! + + it 'can take an initial value', -> + stream = type\mk_sig 4 + assert.is.equal 'SigStream', stream.__class.__name + assert.is.equal type, stream.type + assert.is.equal 4, stream! + + describe ':mk_const', -> + it 'takes a value', -> + stream = type\mk_const 4 + assert.is.equal 'Constant', stream.__class.__name + assert.is.equal type, stream.type + assert.is.equal 4, stream! + + describe ':mk_evt', -> + stream = type\mk_evt! + assert.is.equal 'EvtStream', stream.__class.__name + assert.is.equal type, stream.type |
