From 99ca67938c0145db37182bdb709d9424a20dfee0 Mon Sep 17 00:00:00 2001 From: s-ol Date: Fri, 8 May 2020 11:41:08 +0200 Subject: wip new type system and refactoring - Result -> RTNode - Stream -> Result - ValueStream -> SigStream - EventStream -> EvtStream --- alv/ast.moon | 2 +- alv/base/init.moon | 15 +-- alv/base/input.moon | 27 ++--- alv/base/match.moon | 29 +++--- alv/builtin.moon | 75 +++++++------- alv/cell.moon | 11 ++- alv/copilot.moon | 8 +- alv/init.moon | 16 +-- alv/invoke.moon | 27 ++--- alv/parsing.moon | 10 +- alv/result.moon | 126 ------------------------ alv/result/base.moon | 78 +++++++++++++++ alv/result/const.moon | 196 ++++++++++++++++++++++++++++++++++++ alv/result/evt.moon | 95 ++++++++++++++++++ alv/result/init.moon | 21 ++++ alv/result/io.moon | 55 +++++++++++ alv/result/sig.moon | 121 +++++++++++++++++++++++ alv/rtnode.moon | 126 ++++++++++++++++++++++++ alv/scope.moon | 25 ++--- alv/stream/base.moon | 68 ------------- alv/stream/event.moon | 100 ------------------- alv/stream/init.moon | 18 ---- alv/stream/io.moon | 55 ----------- alv/stream/value.moon | 235 -------------------------------------------- alv/types.moon | 101 +++++++++++++++++++ spec/cell_spec.moon | 18 ++-- spec/input_spec.moon | 25 ++--- spec/match_spec.moon | 11 ++- spec/parsing_spec.moon | 53 ++++------ spec/result/const_spec.moon | 149 ++++++++++++++++++++++++++++ spec/result/sig_spec.moon | 109 ++++++++++++++++++++ spec/result_spec.moon | 181 ---------------------------------- spec/rtnode_spec.moon | 184 ++++++++++++++++++++++++++++++++++ spec/scope_spec.moon | 46 +++++---- spec/value_spec.moon | 180 --------------------------------- 35 files changed, 1440 insertions(+), 1156 deletions(-) delete mode 100644 alv/result.moon create mode 100644 alv/result/base.moon create mode 100644 alv/result/const.moon create mode 100644 alv/result/evt.moon create mode 100644 alv/result/init.moon create mode 100644 alv/result/io.moon create mode 100644 alv/result/sig.moon create mode 100644 alv/rtnode.moon delete mode 100644 alv/stream/base.moon delete mode 100644 alv/stream/event.moon delete mode 100644 alv/stream/init.moon delete mode 100644 alv/stream/io.moon delete mode 100644 alv/stream/value.moon create mode 100644 alv/types.moon create mode 100644 spec/result/const_spec.moon create mode 100644 spec/result/sig_spec.moon delete mode 100644 spec/result_spec.moon create mode 100644 spec/rtnode_spec.moon delete mode 100644 spec/value_spec.moon diff --git a/alv/ast.moon b/alv/ast.moon index 7090869..cff8947 100644 --- a/alv/ast.moon +++ b/alv/ast.moon @@ -1,7 +1,7 @@ ---- -- AST Node Interface. -- --- implemented by `ValueStream` and `Cell`. +-- implemented by `Constant` and `Cell`. -- -- @classmod AST diff --git a/alv/base/init.moon b/alv/base/init.moon index c37d85c..71e9cea 100644 --- a/alv/base/init.moon +++ b/alv/base/init.moon @@ -11,10 +11,11 @@ -- @see Input -- @see base.match.val -- @see base.match.evt --- @see ValueStream --- @see EventStream +-- @see Constant +-- @see SigStream +-- @see EvtStream -- @see IOStream --- @see Result +-- @see RTNode -- @see Error import Op from require 'alv.base.op' @@ -22,8 +23,8 @@ import Builtin from require 'alv.base.builtin' import FnDef from require 'alv.base.fndef' import Input from require 'alv.base.input' import val, evt from require 'alv.base.match' -import ValueStream, EventStream, IOStream from require 'alv.stream' -import Result from require 'alv.result' +import Constant, SigStream, EvtStream, IOStream from require 'alv.result' +import RTNode from require 'alv.rtnode' import Error from require 'alv.error' { @@ -34,6 +35,6 @@ import Error from require 'alv.error' :val, :evt -- redundant exports, to keep anything an extension might need in one import - :ValueStream, :EventStream, :IOStream - :Result, :Error + :Constant, :SigStream, :EvtStream, :IOStream + :RTNode, :Error } diff --git a/alv/base/input.moon b/alv/base/input.moon index fe01f88..dea91c9 100644 --- a/alv/base/input.moon +++ b/alv/base/input.moon @@ -2,8 +2,8 @@ -- Update scheduling policy for `Op` arguments. -- -- @classmod Input -import ValueStream, EventStream, IOStream from require 'alv.stream' -import Result from require 'alv.result' +import Constant, SigStream, EvtStream, IOStream from require 'alv.result' +import RTNode from require 'alv.rtnode' inherits = (klass, frm) -> assert klass, "cant find the ancestor of nil" @@ -53,7 +53,7 @@ class Input -- -- must return a boolean indicating whether `Op`s that refer to this instance -- should be notified (via `Op:tick`). If not overwritten, delegates to - -- `stream`:@{ValueStream:dirty|dirty}. + -- `stream`:@{SigStream:dirty|dirty}. -- -- @treturn bool whether processing is necessary dirty: => @stream\dirty! @@ -77,7 +77,7 @@ class Input --- the current value -- - -- @tfield ValueStream stream + -- @tfield SigStream stream --- members -- @section members @@ -98,9 +98,9 @@ class Input -- Never marked dirty. Use this for input streams that are only read when -- another `Input` is dirty. -- - -- @tparam Stream|Result value + -- @tparam Stream|RTNode value @cold: (value) -> - if value.__class == Result + if value.__class == RTNode value = assert value.value, "Input from result without value!" ColdInput value @@ -108,15 +108,15 @@ class Input -- -- Behaviour depends on what kind of `Stream` `value` is: -- - -- - `ValueStream`: Marked dirty only if old and new `ValueStream` differ. - -- - `EventStream` and `IOStream`: Marked dirty only if the current - -- `EventStream` is dirty. + -- - `SigStream`: Marked dirty only if old and new `SigStream` differ. + -- - `EvtStream` and `IOStream`: Marked dirty only if the current + -- `EvtStream` is dirty. -- -- This is the most common `Input` strategy. -- - -- @tparam Stream|Result value + -- @tparam Stream|RTNode value @hot: (value) -> - if value.__class == Result + if value.__class == RTNode value = assert value.value, "Input from result without value!" InputType = match_parent value, mapping @@ -137,8 +137,9 @@ class IOInput extends Input io: true mapping = { - [ValueStream]: ValueInput - [EventStream]: Input + [Constant]: ColdInput + [SigStream]: ValueInput + [EvtStream]: Input [IOStream]: IOInput } diff --git a/alv/base/match.moon b/alv/base/match.moon index 62075c0..819b0b4 100644 --- a/alv/base/match.moon +++ b/alv/base/match.moon @@ -2,7 +2,7 @@ --- Pattern capturing for Op argument parsing. -- -- There is only one basic buildings block for assembling patterns: --- `Type`. It can match `ValueStream`s and `EventStream`s depending on its +-- `Type`. It can match `SigStream`s and `EvtStream`s depending on its -- metatype argument and can take an optional type name to match as an argument. -- -- In addition to this primitive, the following modifiers are available: @@ -21,12 +21,12 @@ -- - `-pat`: Shorthand for `Optional(pat)` -- -- To perform the actual matching, call the `:match` method on a pattern and --- pass a sequence of `Result`s. The method will either return the captured --- `Result`s (or a table structuring them) +-- pass a sequence of `RTNode`s. The method will either return the captured +-- `RTNode`s (or a table structuring them) -- -- Any ambiguous pattern can be set to 'recall mode' by invoking it. --- Recalling patterns will memorize the first Result they match, and --- only match further Results of the same type. For example +-- Recalling patterns will memorize the first RTNode they match, and +-- only match further RTNodes of the same type. For example -- -- arg = (val.num / val.str)! -- pattern = arg + arg @@ -45,14 +45,11 @@ -- -- @module base.match import Error from require 'alv.error' -import ValueStream, EventStream from require 'alv.stream' +import Primitive from require 'alv.types' local Repeat, Sequence, Choice, Optional -typestr = (result) -> - str = result\type! - str ..= '!' if result\metatype! == 'event' - str +typestr = (result) -> tostring result.value class Pattern match: (seq) => @@ -107,13 +104,17 @@ class Type extends Pattern capture: (seq, i) => return unless seq[i] type, mt = seq[i]\type!, seq[i]\metatype! - return unless @metatype == mt + if @metatype == 'event' + return unless mt == '!' + else + return if mt == '!' + match = if @type then type == @type else @remember type if match 1, seq[i] __tostring: => - str = @type or @metatype + str = tostring @type or @metatype str ..= '!' if @metatype == 'event' str @@ -272,7 +273,7 @@ class Optional extends Pattern -- @table val val = setmetatable {}, { __index: (key) => - with v = Type 'value', key + with v = Type 'value', Primitive key @[key] = v __call: (...) => Type 'value', ... @@ -290,7 +291,7 @@ val = setmetatable {}, { -- @table evt evt = setmetatable {}, { __index: (key) => - with v = Type 'event', key + with v = Type 'event', Primitive key @[key] = v __call: (...) => Type 'event', ... diff --git a/alv/builtin.moon b/alv/builtin.moon index c80e483..767d8e1 100644 --- a/alv/builtin.moon +++ b/alv/builtin.moon @@ -6,16 +6,17 @@ -- -- @module builtin import Builtin, Op, FnDef, Input, val, evt from require 'alv.base' -import ValueStream, LiteralValue from require 'alv.stream.value' +import Constant from require 'alv.result' import Error from require 'alv.error' -import Result from require 'alv.result' +import RTNode from require 'alv.rtnode' import Cell from require 'alv.cell' import Scope from require 'alv.scope' import Tag from require 'alv.tag' import op_invoke from require 'alv.invoke' +import Primitive from require 'alv.types' lfs = require 'lfs' -doc = ValueStream.meta +doc = Constant.meta meta: name: 'doc' summary: "Print documentation in console." @@ -36,11 +37,11 @@ doc = ValueStream.meta assert #tail == 1, "'doc' takes exactly one parameter" result = L\push tail[1]\eval, scope - with Result children: { def } + with RTNode children: { def } meta = result.value.meta L\print "(doc #{tail[1]}):\n#{format_meta meta}\n" -def = ValueStream.meta +def = Constant.meta meta: name: 'def' summary: "Declare symbols in current scope." @@ -63,9 +64,9 @@ Define the symbols `sym1`, `sym2`, … to resolve to the values of `val-expr1`, with val_expr\eval scope scope\set name, \make_ref! - Result :children + RTNode :children -use = ValueStream.meta +use = Constant.meta meta: name: 'use' summary: "Merge scopes into current scope." @@ -82,9 +83,9 @@ All arguments have to be evaltime constant." value = result\const! scope\use value\unwrap 'scope', "'use' only works on scopes" - Result! + RTNode! -require_ = ValueStream.meta +require_ = Constant.meta meta: name: 'require' summary: "Load a module." @@ -102,7 +103,7 @@ require_ = ValueStream.meta L\trace @, "loading module #{name}" COPILOT\require name -import_ = ValueStream.meta +import_ = Constant.meta meta: name: 'import' summary: "Require and define modules." @@ -120,9 +121,9 @@ current scope." name = child\unwrap 'sym' with COPILOT\require name scope\set name, \make_ref! - Result :children + RTNode :children -import_star = ValueStream.meta +import_star = Constant.meta meta: name: 'import*' summary: "Require and use modules." @@ -138,9 +139,9 @@ Requires modules `sym1`, `sym2`, … and merges them into the current scope." children = for i, child in ipairs tail with COPILOT\require child\unwrap 'sym' scope\use .value\unwrap 'scope' - Result :children + RTNode :children -export_ = ValueStream.meta +export_ = Constant.meta meta: name: 'export' summary: "Evaluate definitions in a new scope and return it." @@ -152,9 +153,9 @@ Evaluate `expr1`, `expr2`, … in a new Scope and return scope." eval: (scope, tail) => new_scope = Scope scope children = [expr\eval new_scope for expr in *tail] - Result :children, value: ValueStream.wrap new_scope + RTNode :children, value: Constant.wrap new_scope -export_star = ValueStream.meta +export_star = Constant.meta meta: name: 'export*' summary: "Export specific symbol definitions as a module/scope." @@ -179,9 +180,9 @@ Copies the containing scope if no symbols are given." with result = scope\get name new_scope\set name, result - Result :children, value: ValueStream.wrap new_scope + RTNode :children, value: Constant.wrap new_scope -fn = ValueStream.meta +fn = Constant.meta meta: name: 'fn' summary: "Declare a function." @@ -201,13 +202,13 @@ function is invoked." assert param.type == 'sym', "function parameter declaration has to be a symbol" param - Result value: with ValueStream.wrap FnDef param_symbols, body, scope + RTNode value: with Constant.wrap FnDef param_symbols, body, scope .meta = { summary: "(user defined function)" examples: { "(??? #{table.concat [p! for p in *param_symbols], ' '})" } } -defn = ValueStream.meta +defn = Constant.meta meta: name: 'defn' summary: "Define a function." @@ -229,16 +230,16 @@ function is invoked." assert param.type == 'sym', "function parameter declaration has to be a symbol" param - value = with ValueStream.wrap FnDef param_symbols, body, scope + value = with Constant.wrap FnDef param_symbols, body, scope .meta = :name summary: "(user defined function)" examples: { "(#{name} #{table.concat [p! for p in *param_symbols], ' '})" } - scope\set name, Result :value - Result! + scope\set name, RTNode :value + RTNode! -do_expr = ValueStream.meta +do_expr = Constant.meta meta: name: 'do' summary: "Evaluate multiple expressions in a new scope." @@ -250,9 +251,9 @@ Evaluate `expr1`, `expr2`, … and return the value of the last expression." eval: (scope, tail) => scope = Scope scope children = [expr\eval scope for expr in *tail] - Result :children, value: children[#children].value + RTNode :children, value: children[#children].value -if_ = ValueStream.meta +if_ = Constant.meta meta: name: 'if' summary: "Make an evaltime const choice." @@ -277,7 +278,7 @@ to `then-expr`, otherwise it is equivalent to `else-xpr` if given, or nil otherw elseif xelse xelse\eval scope -trace_ = ValueStream.meta +trace_ = Constant.meta meta: name: 'trace!' summary: "Trace an expression's value at evaltime." @@ -291,7 +292,7 @@ trace_ = ValueStream.meta with result = L\push tail[1]\eval, scope L\print "trace! #{tail[1]\stringify!}: #{result.value}" -trace = ValueStream.meta +trace = Constant.meta meta: name: 'trace' summary: "Trace an expression's values at runtime." @@ -313,13 +314,13 @@ trace = ValueStream.meta tag = @tag\clone Tag.parse '-1' inner = Cell tag, { - LiteralValue 'opdef', traceOp, 'trace' - ValueStream.str tostring tail[1] + Constant.literal (Primitive 'opdef'), traceOp, 'trace' + Constant.str tostring tail[1] tail[1] } inner\eval scope -print = ValueStream.meta +print = Constant.meta meta: name: 'print' summary: "Print string values." @@ -348,23 +349,23 @@ Scope.from_table { export: export_ 'export*': export_star - true: ValueStream.meta + true: Constant.meta meta: name: 'true' summary: "The boolean constant `true`." - value: ValueStream.bool true + value: Constant.bool true - false: ValueStream.meta + false: Constant.meta meta: name: 'false' summary: "The boolean constant `false`." - value: ValueStream.bool false + value: Constant.bool false - bang: ValueStream.meta + bang: Constant.meta meta: name: 'bang' summary: "A `bang` value-constant." - value: ValueStream 'bang', true + value: Constant 'bang', true :fn, :defn 'do': do_expr diff --git a/alv/cell.moon b/alv/cell.moon index 6d5e207..2e00195 100644 --- a/alv/cell.moon +++ b/alv/cell.moon @@ -5,9 +5,10 @@ -- nodes), a `Tag`, and optionally the internal whitespace as parsed. -- -- @classmod Cell -import ValueStream from require 'alv.stream' +import Constant from require 'alv.result' import Error from require 'alv.error' import op_invoke, fn_invoke from require 'alv.invoke' +import Primitive from require 'alv.types' import Tag from require 'alv.tag' local RootCell @@ -73,11 +74,11 @@ class Cell head = assert @head!, Error 'syntax', "cannot evaluate empty expr" head = (head\eval scope)\const! Builtin = switch head.type - when 'opdef' + when (Primitive 'opdef') op_invoke - when 'fndef' + when (Primitive 'fndef') fn_invoke - when 'builtin' + when (Primitive 'builtin') head\unwrap! else error Error 'type', "#{head} is not an opdef, fndef or builtin" @@ -157,7 +158,7 @@ class Cell -- @type RootCell class RootCell extends Cell - head: => ValueStream.sym 'do' + head: => Constant.sym 'do' tail: => @children clone: (parent) => diff --git a/alv/copilot.moon b/alv/copilot.moon index 7ef2134..d9b674f 100644 --- a/alv/copilot.moon +++ b/alv/copilot.moon @@ -6,8 +6,8 @@ lfs = require 'lfs' import Scope from require 'alv.scope' import Module from require 'alv.module' import Error from require 'alv.error' -import Result from require 'alv.result' -import ValueStream from require 'alv.stream' +import RTNode from require 'alv.rtnode' +import Constant from require 'alv.result' export COPILOT @@ -41,12 +41,12 @@ class Copilot --- require a module. -- @tparam string name - -- @treturn Result result + -- @treturn RTNode root require: (name) => Error.wrap "loading module '#{name}'", -> ok, lua = pcall require, "alv-lib.#{name}" if ok - Result value: ValueStream.wrap lua + RTNode value: Constant.wrap lua else assert @modules, "no current eval cycle?" if mod = @modules[name] diff --git a/alv/init.moon b/alv/init.moon index f2fed2f..95432f3 100644 --- a/alv/init.moon +++ b/alv/init.moon @@ -13,8 +13,9 @@ cycle = require 'alv.cycle' version = require 'alv.version' import Logger from require 'alv.logger' -import ValueStream, EventStream, IOStream from require 'alv.stream' -import Result from require 'alv.result' + +import Constant, SigStream, EvtStream, IOStream from require 'alv.result' +import RTNode from require 'alv.rtnode' import Scope from require 'alv.scope' import Error from require 'alv.error' import Registry, SimpleRegistry from require 'alv.registry' @@ -34,10 +35,11 @@ import Copilot from require 'alv.copilot' --- exports -- @table exports -- @tfield version version --- @tfield ValueStream ValueStream --- @tfield EventStream EventStream +-- @tfield Constant Constant +-- @tfield SigStream SigStream +-- @tfield EvtStream EvtStream -- @tfield IOStream IOStream --- @tfield Result Result +-- @tfield RTNode RTNode -- @tfield Cell Cell -- @tfield RootCell RootCell -- @tfield Scope Scope @@ -51,9 +53,9 @@ import Copilot from require 'alv.copilot' { :version - :ValueStream, :EventStream, :IOStream + :Constant, :SigStream, :EvtStream, :IOStream :Cell, :RootCell - :Result, :Scope, :Error + :RTNode, :Scope, :Error :Registry, :SimpleRegistry, :Tag diff --git a/alv/invoke.moon b/alv/invoke.moon index 439af49..4d9dfc9 100644 --- a/alv/invoke.moon +++ b/alv/invoke.moon @@ -2,10 +2,15 @@ -- Builtins for invoking `Op`s and `FnDef`s. -- -- @module invoke -import Result from require 'alv.result' +import RTNode from require 'alv.rtnode' import Builtin from require 'alv.base' import Scope from require 'alv.scope' import Error from require 'alv.error' +import Primitive from require 'alv.types' + +opdef = Primitive 'opdef' +fndef = Primitive 'fndef' +sym = Primitive 'sym' get_name = (value, raw) -> meta = if value.meta then value.meta.name @@ -34,7 +39,7 @@ class op_invoke extends Builtin @op = prev.op\fork! prev.forked = COPILOT.T else - def = @head\unwrap 'opdef', "cant op-invoke #{@head}" + def = @head\unwrap opdef, "cant op-invoke #{@head}" @op = def! --- `Builtin:destroy` implementation. @@ -50,11 +55,11 @@ class op_invoke extends Builtin -- checks if any of `op`:@{Op:all_inputs|all_inputs} are @{Input:dirty|dirty}, -- and if so, calls `op`:@{Op:tick|tick}. -- - -- The `Result` contains `op`, `Op.value` and all the `Result`s from the tail. + -- The `RTNode` contains `op`, `Op.value` and all the `RTNode`s from the tail. -- -- @tparam Scope scope the active scope -- @tparam {AST,...} tail the arguments to this expression - -- @treturn Result + -- @treturn RTNode eval: (scope, tail) => children = [L\push expr\eval, scope for expr in *tail] @@ -73,7 +78,7 @@ class op_invoke extends Builtin for input in @op\all_inputs! input\finish_setup! - Result :children, value: @op.out, op: @op + RTNode :children, value: @op.out, op: @op --- The `Op` instance. -- @@ -92,18 +97,18 @@ class fn_invoke extends Builtin -- `FnDef.body` with the prefix `Builtin.tag`, and `AST:eval`s it in the newly -- created `Scope`. -- - -- The `Result` contains the `Stream` from the cloned AST, and its children - -- are all the `Result`s from evaluating the tail as well as the cloned + -- The `RTNode` contains the `Stream` from the cloned AST, and its children + -- are all the `RTNode`s from evaluating the tail as well as the cloned -- `AST`s. -- -- @tparam Scope caller_scope the active scope -- @tparam {AST,...} tail the arguments to this expression - -- @treturn Result the result of this evaluation + -- @treturn RTNode the result of this evaluation eval: (caller_scope, tail) => name = get_name @head, @cell\head! frame = "invoking function #{name} at [#{@tag}]" - fndef = @head\unwrap 'fndef', "cant fn-invoke #{@head}" + fndef = @head\unwrap fndef, "cant fn-invoke #{@head}" { :params, :body } = fndef if #params != #tail err = Error 'argument', "expected #{#params} arguments, found #{#tail}" @@ -113,7 +118,7 @@ class fn_invoke extends Builtin fn_scope = Scope fndef.scope, caller_scope children = for i=1,#params - name = params[i]\unwrap 'sym' + name = params[i]\unwrap sym with L\push tail[i]\eval, caller_scope fn_scope\set name, \make_ref! @@ -121,7 +126,7 @@ class fn_invoke extends Builtin result = Error.wrap frame, clone\eval, fn_scope table.insert children, result - Result :children, value: result.value + RTNode :children, value: result.value { :op_invoke, :fn_invoke diff --git a/alv/parsing.moon b/alv/parsing.moon index f9d6f98..9cc46d7 100644 --- a/alv/parsing.moon +++ b/alv/parsing.moon @@ -2,7 +2,7 @@ -- Lpeg Grammar for parsing `alive` code. -- -- @module parsing -import ValueStream from require 'alv.stream' +import Constant from require 'alv.result' import Cell from require 'alv.cell' import Tag from require 'alv.tag' import R, S, P, V, C, Ct from require 'lpeg' @@ -20,15 +20,15 @@ mspace = (comment + wc)^0 / 1 -- optional whitespace -- atoms digit = R '09' first = (R 'az', 'AZ') + S '-_+*/.!?=%' -sym = first * (first + digit)^0 / ValueStream\parse 'sym' +sym = first * (first + digit)^0 / Constant\parse 'sym' -strd = '"' * (C ((P '\\"') + (P '\\\\') + (1 - P '"'))^0) * '"' / ValueStream\parse 'str', '\"' -strq = "'" * (C ((P "\\'") + (P '\\\\') + (1 - P "'"))^0) * "'" / ValueStream\parse 'str', '\'' +strd = '"' * (C ((P '\\"') + (P '\\\\') + (1 - P '"'))^0) * '"' / Constant\parse 'str', '\"' +strq = "'" * (C ((P "\\'") + (P '\\\\') + (1 - P "'"))^0) * "'" / Constant\parse 'str', '\'' str = strd + strq int = digit^1 float = (digit^1 * '.' * digit^0) + (digit^0 * '.' * digit^1) -num = ((P '-')^-1 * (float + int)) / ValueStream\parse 'num' +num = ((P '-')^-1 * (float + int)) / Constant\parse 'num' atom = num + sym + str diff --git a/alv/result.moon b/alv/result.moon deleted file mode 100644 index 6f5ea28..0000000 --- a/alv/result.moon +++ /dev/null @@ -1,126 +0,0 @@ ----- --- Result of evaluating an expression. --- --- `Result`s form a tree that controls execution order and message passing --- between `Op`s. --- --- @classmod Result -class Result ---- members --- @section members - - --- return whether this Result's value is const. - is_const: => not next @side_inputs - - --- assert value-constness and return the value. - -- @tparam[opt] string msg the error message to throw - -- @treturn any - const: (msg) => - assert not (next @side_inputs), msg or "eval-time const expected" - @value - - --- assert this result has a value, return its type. - -- @treturn string - type: => - assert @value, "Result with value expected" - @value.type - - --- assert this result has a value, returns its metatype. - -- @treturn string `"value"` or `"event"` - metatype: => - assert @value, "Result with value expected" - @value.metatype - - --- create a copy of this result with value-copy semantics. - -- the copy has the same @value and @side_inputs, but will not update - -- anything on \tick. - make_ref: => - with Result value: @value - .side_inputs = @side_inputs - - --- poll all IOStream instances that are effecting this (sub)tree. - -- should be called once per frame on the root, right before tick. - poll_io: => - for stream, input in pairs @side_inputs - stream\poll! if input.io - - --- in depth-first order, tick all Ops which have dirty Inputs. - -- - -- short-circuits if there are no dirty Inputs in the entire subtree - tick: => - any_dirty = false - for stream, input in pairs @side_inputs - if input\dirty! - any_dirty = true - break - - -- early-out if no Inputs are dirty in this whole subtree - return unless any_dirty - - for child in *@children - child\tick! - - if @op - -- we have to check self_dirty here, because Inputs from children may - -- have become dirty due to \tick - self_dirty = false - for input in @op\all_inputs! - if input\dirty! - self_dirty = true - - return unless self_dirty - - @op\tick! - - __tostring: => - buf = " 0 - buf ..= ">" - buf - - --- the `Stream` result - -- - -- @tfield ?Stream value - - --- an Op - -- - -- @tfield ?Op op - - --- list of child `Result`s from subexpressions - -- - -- @tfield {}|{Result,...} children - - --- cached mapping of all `Stream`/`Input` pairs affecting this Result. - -- - -- This is the union of all `children`s `side_inputs` and all `Input`s from - -- `op` that are not the `value` of any child. - -- - -- @tfield {[Stream]=Input,...} side_inputs - ---- static functions --- @section static - - --- create a new Result. - -- @classmethod - -- @param params table with optional keys op, value, children. default: {} - new: (params={}) => - @value = params.value - @op = params.op - @children = params.children or {} - - @side_inputs, is_child = {}, {} - for child in *@children - for stream, input in pairs child.side_inputs - @side_inputs[stream] = input - if child.value - is_child[child.value] = true - - if @op - for input in @op\all_inputs! - if input.io or not is_child[input.stream] - @side_inputs[input.stream] = input - -{ - :Result -} diff --git a/alv/result/base.moon b/alv/result/base.moon new file mode 100644 index 0000000..26af4b4 --- /dev/null +++ b/alv/result/base.moon @@ -0,0 +1,78 @@ +---- +-- base Stream interface. +-- +-- implemented by `Constant`, `SigStream`, `EvtStream`, and `IOStream`. +-- +-- @classmod Stream + +class Stream +--- Stream interface. +-- +-- Methods that have to be implemented by `Stream` implementations +-- (`SigStream`, `EvtStream`, `IOStream`). +-- +-- @section interface + + --- return whether this Stream was changed in the current tick. + -- + -- @function dirty + -- @treturn boolean + + --- create a mutable copy of this Stream. + -- + -- Used to insulate eval-cycles from each other. + -- + -- @function fork + -- @treturn Stream + + __tostring: => "<#{@type}#{@metatype} #{@type\pp @value}>" + __inherited: (cls) => cls.__base.__tostring or= @__tostring + + --- the type name of this Stream's value. + -- + -- the following builtin typenames are used: + -- + -- - `str` - strings, `value` is a Lua string + -- - `sym` - symbols, `value` is a Lua string + -- - `num` - numbers, `value` is a Lua number + -- - `bool` - booleans, `value` is a Lua boolean + -- - `bang` - trigger signals, `value` is a Lua boolean + -- - `opdef` - `value` is an `Op` subclass + -- - `builtin` - `value` is a `Builtin` subclass + -- - `fndef` - `value` is a `FnDef` instance + -- - `scope` - `value` is a `Scope` instance + -- + -- @tfield string type + + --- the metatype string for this Stream. + -- + -- one of `=` (`Constant`), `~` (`SigStream`), + -- `!` (`EvtStream` and `IOStream`). + -- + -- @tfield string type + + --- documentation metadata. + -- + -- an optional table containing metadata for error messages and + -- documentation. The following keys are recognized: + -- + -- - `name`: optional name + -- - `summary`: single-line description (markdown) + -- - `examples`: optional list of single-line code examples + -- - `description`: optional full-text description (markdown) + -- + -- @tfield ?table meta + +--- static functions +-- @section static + + --- construct a new Stream. + -- + -- @classmethod + -- @tparam string type the type name + -- @tparam ?table meta the `meta` table + new: (@type, @meta={}) => + +{ + :Stream +} diff --git a/alv/result/const.moon b/alv/result/const.moon new file mode 100644 index 0000000..10bc943 --- /dev/null +++ b/alv/result/const.moon @@ -0,0 +1,196 @@ +---- +-- Constant Value. +-- +-- Implements the `Stream` and `AST` inteface. +-- +-- @classmod Constant +import Stream from require 'alv.result.base' +import RTNode from require 'alv.rtnode' +import Error from require 'alv.error' +import scope, base from require 'alv.cycle' +import Primitive from require 'alv.types' + +num = Primitive 'num' +str = Primitive 'str' +sym = Primitive 'sym' +bool = Primitive 'bool' + +ancestor = (klass) -> + assert klass, "cant find the ancestor of nil" + while klass.__parent + klass = klass.__parent + klass + +class Constant extends Stream + --- Whether this Result is dirty. + -- + -- @tresult bool always `false`. + dirty: => false + + --- unwrap to the Lua type. + -- + -- Asserts `@type == type` if `type` is given. + -- + -- @tparam[opt] string type the type to check for + -- @tparam[optchain] string msg message to throw if type don't match + -- @treturn any `value` + unwrap: (type, msg) => + assert type == @type, msg or "#{@} is not a #{type}" if type + @value + + --- create a mutable copy of this stream. + -- + -- Used to insulate eval-cycles from each other. + -- + -- @treturn Constant + fork: => @ + + --- alias for `unwrap`. + __call: (...) => @unwrap ... + + --- compare two values. + -- + -- Compares two `SigStream`s by comparing their types and their Lua values. + __eq: (other) => other.type == @type and other.value == @value + + --- Stream metatype. + -- + -- @tfield string metatype (`=`) + metatype: '=' + +--- AST interface +-- +-- `SignStream` implements the `AST` interface. +-- @section ast + + --- evaluate this literal constant. + -- + -- Throws an error if `type` is not a literal (`num`, `str` or `sym`). + -- Returns an eval-time const result for `num` and `str`. + -- Resolves `sym`s in `scope` and returns a reference to them. + -- + -- @tparam Scope scope the scope to evaluate in + -- @treturn RTNode the evaluation result + eval: (scope) => + return RTNode value: @ if @literal + + switch @type + when num, str + RTNode value: @ + when sym + Error.wrap "resolving symbol '#{@value}'", scope\get, @value + else + error "cannot evaluate #{@}" + + --- stringify this literal constant. + -- + -- Throws an error if `raw` is not set. + -- + -- @treturn string the exact string this stream was parsed from + stringify: => @raw + + --- clone this literal constant. + -- + -- @treturn SignStream self + clone: (prefix) => @ + +--- static functions +-- @section static + + --- construct a new Constant. + -- + -- @classmethod + -- @tparam string type the type name + -- @tparam any value the Lua value to be accessed through `unwrap` + -- @tparam string raw the raw string that resulted in this value. Used by `parsing`. + new: (type, @value, @raw) => super type + + unescape = (str) -> str\gsub '\\([\'"\\])', '%1' + --- create a capture-function (for parsing with Lpeg). + -- + -- @tparam string type the type name (one of `num`, `sym` or `str`) + -- @tparam string sep the seperator char (only for `str`) + @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 + + --- wrap a Lua value. + -- + -- Attempts to guess the type and wrap a Lua value. + -- + -- @tparam any val the value to wrap + -- @tparam[opt] string name the name of this value (for error logging) + -- @treturn Constant + @wrap: (val, name='(unknown)') -> + typ = switch type val + when 'number' then 'num' + when 'string' then 'str' + when 'table' + if rawget val, '__base' + -- a class + switch ancestor val + when base.Op then 'opdef' + when base.Builtin then 'builtin' + else + error "#{name}: cannot wrap class '#{val.__name}'" + elseif val.__class + -- an instance + switch ancestor val.__class + when scope.Scope then 'scope' + when base.FnDef then 'fndef' + when Stream then return val + else + error "#{name}: cannot wrap '#{val.__class.__name}' instance" + else + -- plain table + val = scope.Scope.from_table val + 'scope' + else + error "#{name}: cannot wrap Lua type '#{type val}'" + + Constant (Primitive typ), val + + --- create a constant number. + -- @tparam number val the number + -- @treturn Constant + @num: (val) -> Constant num, val, tostring val + + --- create a constant string. + -- @tparam string val the string + -- @treturn Constant + @str: (val) -> Constant str, val, "'#{val}'" + + --- create a constant symbol. + -- @tparam string val the symbol + -- @treturn Constant + @sym: (val) -> Constant sym, val, val + + --- create a constant boolean. + -- @tparam boolean val the boolean + -- @treturn Constant + @bool: (val) -> Constant bool, val, tostring val + + --- create a forced-literal Constant. + -- + -- For internal use in `Builtin`s only. + -- + -- @treturn Constant + @literal: (...) -> + with Constant ... + .literal = true + + --- wrap and document a value. + -- + -- wraps `args.value` using `wrap`, then assigns `meta`. + -- + -- @tparam table args table with keys `value` and `meta` + -- @treturn Constant + @meta: (args) -> + with Constant.wrap args.value + .meta = args.meta if args.meta + +{ + :Constant +} diff --git a/alv/result/evt.moon b/alv/result/evt.moon new file mode 100644 index 0000000..0559a70 --- /dev/null +++ b/alv/result/evt.moon @@ -0,0 +1,95 @@ +---- +-- Stream of momentary events. +-- +-- @classmod EvtStream +import Stream from require 'alv.result.base' + +class EvtStream extends Stream +--- members +-- @section members + + --- return whether this stream was changed in the current tick. + -- + -- @treturn bool + dirty: => @updated == COPILOT.T + + --- push an event value into the stream. + -- + -- Marks this stream as dirty for the remainder of the current tick. + -- + -- @tparam any event + add: (event) => + if not @dirty! + @events = {} + + @updated = COPILOT.T + table.insert @events, event + + --- get the sequence of current events (if any). + -- + -- Returns `events` if `dirty`, or an empty table otherwise. + -- Asserts `@type == type` if `type` is given. + -- + -- @tparam[opt] string type the type to check for + -- @tparam[optchain] string msg message to throw if type don't match + -- @treturn {any,...} `events` + unwrap: (type, msg) => + assert type == @type, msg or "#{@} is not a #{type}" if type + if @dirty! then @events else {} + + --- create a mutable copy of this stream. + -- + -- Used to wrap insulate eval-cycles from each other. + -- + -- @treturn EvtStream + fork: => @@ @type + + --- alias for `unwrap`. + __call: (...) => @unwrap ... + __tostring: => "<#{@type}#{@metatype} #{@type\pp @value}>" + + --- Stream metatype. + -- + -- @tfield string metatype (`!`) + metatype: '!' + + --- the type name of the stream. + -- + -- the following builtin typenames are used: + -- + -- - `str` - strings, `value` is a Lua string + -- - `sym` - symbols, `value` is a Lua string + -- - `num` - numbers, `value` is a Lua number + -- - `bool` - booleans, `value` is a Lua boolean + -- - `bang` - trigger signals, `value` is a Lua boolean + -- - `opdef` - `value` is an `Op` subclass + -- - `builtin` - `value` is a `Builtin` subclass + -- - `fndef` - `value` is a `FnDef` instance + -- - `scope` - `value` is a `Scope` instance + -- + -- @tfield string type + + --- documentation metadata. + -- + -- an optional table containing metadata for error messages and + -- documentation. The following keys are recognized: + -- + -- - `name`: optional name + -- - `summary`: single-line description (markdown) + -- - `examples`: optional list of single-line code examples + -- - `description`: optional full-text description (markdown) + -- + -- @tfield ?table meta + +--- static functions +-- @section static + + --- construct a new EvtStream. + -- + -- @classmethod + -- @tparam string type the type name + new: (type) => super type + +{ + :EvtStream +} diff --git a/alv/result/init.moon b/alv/result/init.moon new file mode 100644 index 0000000..aa7a115 --- /dev/null +++ b/alv/result/init.moon @@ -0,0 +1,21 @@ +---- +-- `Stream` interface and implementations. +-- +-- @see Stream +-- @see Constant +-- @see SigStream +-- @see EvtStream +-- @see IOStream +-- +-- @module stream +import Constant from require 'alv.result.const' +import SigStream from require 'alv.result.sig' +import EvtStream from require 'alv.result.evt' +import IOStream from require 'alv.result.io' + +{ + :Constant + :SigStream + :EvtStream + :IOStream +} diff --git a/alv/result/io.moon b/alv/result/io.moon new file mode 100644 index 0000000..b12322c --- /dev/null +++ b/alv/result/io.moon @@ -0,0 +1,55 @@ +---- +-- Stream of external side-effects. +-- +-- Unlike other `Stream`s, this is not updated/set by an `Op` instace, but is +-- continuously polled for changes by the runtime and may mark itself as +-- *dirty* at any point in time. All runtime execution happens due to IOStream +-- updates, which ripple through the `RTNode` tree. +-- +-- @classmod IOStream +import EvtStream from require 'alv.result.evt' + +class IOStream extends EvtStream +--- IOStream interface. +-- +-- methods that have to be implemented by `IOStream` implementations. +-- @section interface + + --- construct a new IOStream. + -- + -- Must prepare the instance for `dirty` to be called. + -- The super-constructor should be called to set `Stream.type`. + -- + -- @classmethod + -- @tparam string type the typename of this stream. + new: (type) => super type + + --- create a mutable copy of this stream. + -- + -- Used to wrap insulate eval-cycles from each other. + -- + -- @treturn IOStream + fork: => @ + + --- poll for changes. + -- + -- Called every frame by the main event loop to update internal state. + poll: => + + --- check whether this adapter requires processing. + -- + -- Must return a boolean indicating whether `Op`s that refer to this instance + -- via `Input.hot` should be notified (via `Op:tick`). May be called multiple + -- times. May be called before `poll` on the first frame after construction. + -- + -- If this is not overriden, the `EvtStream` interface can be used, see + -- `EvtStream.add`, `EvtStream.unwrap`, and `EvtStream.dirty`. + -- + -- @function dirty + -- @treturn bool whether processing is required + + __inherited: (cls) => cls.__base.__tostring or= @__tostring + +{ + :IOStream +} diff --git a/alv/result/sig.moon b/alv/result/sig.moon new file mode 100644 index 0000000..d7c18a3 --- /dev/null +++ b/alv/result/sig.moon @@ -0,0 +1,121 @@ +---- +-- Continuous stream of values. +-- +-- @classmod SigStream +import Stream from require 'alv.result.base' +import Primitive from require 'alv.types' + +class SigStream extends Stream +--- members +-- @section members + + --- return whether this stream was changed in the current tick. + -- + -- @treturn bool + dirty: => @updated == COPILOT.T + + --- update this stream's value. + -- + -- Marks this stream as dirty for the remainder of the current tick. + set: (value) => + if value != @value + @value = value + @updated = COPILOT.T + + --- unwrap to the Lua type. + -- + -- Asserts `@type == type` if `type` is given. + -- + -- @tparam[opt] string type the type to check for + -- @tparam[optchain] string msg message to throw if type don't match + -- @treturn any `value` + unwrap: (type, msg) => + assert type == @type, msg or "#{@} is not a #{type}" if type + @value + + --- create a mutable copy of this stream. + -- + -- Used to insulate eval-cycles from each other. + -- + -- @treturn SigStream + fork: => + with @@ @type, @value + .updated = @updated + + --- alias for `unwrap`. + __call: (...) => @unwrap ... + + --- compare two values. + -- + -- Compares two `SigStream`s by comparing their types and their Lua values. + __eq: (other) => other.type == @type and other.value == @value + + --- Stream metatype. + -- + -- @tfield string metatype (`~`) + metatype: '~' + + --- the type name of this stream. + -- + -- the following builtin typenames are used: + -- + -- - `str` - strings, `value` is a Lua string + -- - `sym` - symbols, `value` is a Lua string + -- - `num` - numbers, `value` is a Lua number + -- - `bool` - booleans, `value` is a Lua boolean + -- - `bang` - trigger signals, `value` is a Lua boolean + -- - `opdef` - `value` is an `Op` subclass + -- - `builtin` - `value` is a `Builtin` subclass + -- - `fndef` - `value` is a `FnDef` instance + -- - `scope` - `value` is a `Scope` instance + -- + -- @tfield string type + + --- the wrapped Lua value. + -- @tfield any value + + --- documentation metadata. + -- + -- an optional table containing metadata for error messages and + -- documentation. The following keys are recognized: + -- + -- - `name`: optional name + -- - `summary`: single-line description (markdown) + -- - `examples`: optional list of single-line code examples + -- - `description`: optional full-text description (markdown) + -- + -- @tfield ?table meta + +--- static functions +-- @section static + + --- construct a new SigStream. + -- + -- @classmethod + -- @tparam string type the type name + -- @tparam any value the Lua value to be accessed through `unwrap` + new: (type, @value) => super type + + --- create a number stream. + -- @tparam number val the number + -- @treturn SigStream + @num: (val) -> SigStream Primitive'num', val + + --- create a string stream. + -- @tparam string val the string + -- @treturn SigStream + @str: (val) -> SigStream Primitive'str', val + + --- create a symbol stream. + -- @tparam string val the symbol + -- @treturn symbol + @sym: (val) -> SigStream Primitive'sym', val + + --- create a boolean stream. + -- @tparam boolean val the boolean + -- @treturn SigStream + @bool: (val) -> SigStream Primitive'bool', val + +{ + :SigStream +} diff --git a/alv/rtnode.moon b/alv/rtnode.moon new file mode 100644 index 0000000..dab4507 --- /dev/null +++ b/alv/rtnode.moon @@ -0,0 +1,126 @@ +---- +-- RTNode of evaluating an expression. +-- +-- `RTNode`s form a tree that controls execution order and message +-- between `Op`s. +-- +-- @classmod RTNode +class RTNode +--- members +-- @section members + + --- return whether this RTNode's value is const. + is_const: => not next @side_inputs + + --- assert value-constness and return the value. + -- @tparam[opt] string msg the error message to throw + -- @treturn any + const: (msg) => + assert not (next @side_inputs), msg or "eval-time const expected" + @value + + --- assert this result has a value, return its type. + -- @treturn string + type: => + assert @value, "RTNode with value expected" + @value.type + + --- assert this result has a value, returns its metatype. + -- @treturn string `"value"` or `"event"` + metatype: => + assert @value, "RTNode with value expected" + @value.metatype + + --- create a copy of this result with value-copy semantics. + -- the copy has the same @value and @side_inputs, but will not update + -- anything on \tick. + make_ref: => + with RTNode value: @value + .side_inputs = @side_inputs + + --- poll all IOStream instances that are effecting this (sub)tree. + -- should be called once per frame on the root, right before tick. + poll_io: => + for stream, input in pairs @side_inputs + stream\poll! if input.io + + --- in depth-first order, tick all Ops which have dirty Inputs. + -- + -- short-circuits if there are no dirty Inputs in the entire subtree + tick: => + any_dirty = false + for stream, input in pairs @side_inputs + if input\dirty! + any_dirty = true + break + + -- early-out if no Inputs are dirty in this whole subtree + return unless any_dirty + + for child in *@children + child\tick! + + if @op + -- we have to check self_dirty here, because Inputs from children may + -- have become dirty due to \tick + self_dirty = false + for input in @op\all_inputs! + if input\dirty! + self_dirty = true + + return unless self_dirty + + @op\tick! + + __tostring: => + buf = " 0 + buf ..= ">" + buf + + --- the `Stream` result + -- + -- @tfield ?Stream value + + --- an Op + -- + -- @tfield ?Op op + + --- list of child `RTNode`s from subexpressions + -- + -- @tfield {}|{RTNode,...} children + + --- cached mapping of all `Stream`/`Input` pairs affecting this RTNode. + -- + -- This is the union of all `children`s `side_inputs` and all `Input`s from + -- `op` that are not the `value` of any child. + -- + -- @tfield {[Stream]=Input,...} side_inputs + +--- static functions +-- @section static + + --- create a new RTNode. + -- @classmethod + -- @param params table with optional keys op, value, children. default: {} + new: (params={}) => + @value = params.value + @op = params.op + @children = params.children or {} + + @side_inputs, is_child = {}, {} + for child in *@children + for stream, input in pairs child.side_inputs + @side_inputs[stream] = input + if child.value + is_child[child.value] = true + + if @op + for input in @op\all_inputs! + if input.io or not is_child[input.stream] + @side_inputs[input.stream] = input + +{ + :RTNode +} diff --git a/alv/scope.moon b/alv/scope.moon index 6a0e2a5..575c36e 100644 --- a/alv/scope.moon +++ b/alv/scope.moon @@ -1,10 +1,11 @@ ---- --- Mapping from `sym`s to `Result`s. +-- Mapping from `sym`s to `RTNode`s. -- -- @classmod Scope -import ValueStream from require 'alv.stream' -import Result from require 'alv.result' +import Constant from require 'alv.result' +import RTNode from require 'alv.rtnode' import Error from require 'alv.error' +import Primitive from require 'alv.types' class Scope --- members @@ -12,21 +13,21 @@ class Scope --- set a Lua value in the scope. -- - -- wraps `val` in a `ValueStream` and `Result` before calling `set`. + -- wraps `val` in a `Constant` and `RTNode` before calling `set`. -- -- @tparam string key -- @tparam any val set_raw: (key, val) => - value = ValueStream.wrap val, key - @values[key] = Result :value + value = Constant.wrap val, key + @values[key] = RTNode :value - --- set a symbol to a `Result`. + --- set a symbol to a `RTNode`. -- -- @tparam string key - -- @tparam Result val + -- @tparam RTNode val set: (key, val) => L\trace "setting #{key} = #{val} in #{@}" - assert val.__class == Result, "expected #{key}=#{val} to be Result" + assert val.__class == RTNode, "expected #{key}=#{val} to be RTNode" assert val.value, Error 'type', "cannot define symbol to nil" assert not @values[key], Error 'type', "cannot redefine symbol '#{key}'!" @values[key] = val @@ -42,7 +43,7 @@ class Scope --- resolve a key in this Scope. -- -- @tparam string key the key to resolve - -- @treturn ?Result the value of the definition that was found, or `nil` + -- @treturn ?RTNode the value of the definition that was found, or `nil` get: (key) => L\debug "checking for #{key} in #{@}" if val = @values[key] @@ -56,7 +57,7 @@ class Scope child = @get start if not child error Error 'reference', "undefined symbol '#{start}'" - if child\type! != 'scope' + if child\type! != (Primitive 'scope') error Error 'reference', "'#{start}' is not a scope" child.value!\get rest, while_msg @@ -104,7 +105,7 @@ class Scope --- convert a Lua table to a Scope. -- -- `tbl` may contain more tables (or `Scope`s). - -- Uses `ValueStream.wrap` on the values recursively. + -- Uses `Constant.wrap` on the values recursively. -- -- @tparam table tbl the table to convert -- @treturn Scope diff --git a/alv/stream/base.moon b/alv/stream/base.moon deleted file mode 100644 index 37cc8a4..0000000 --- a/alv/stream/base.moon +++ /dev/null @@ -1,68 +0,0 @@ ----- --- base Stream interface. --- --- implemented by `ValueStream`, `EventStream`, and `IOStream`. --- --- @classmod Stream - -class Stream ---- Stream interface. --- --- Methods that have to be implemented by `Stream` implementations --- (`ValueStream`, `EventStream`, `IOStream`). --- --- @section interface - - --- return whether this Stream was changed in the current tick. - -- - -- @function dirty - -- @treturn boolean - - --- create a mutable copy of this Stream. - -- - -- Used to insulate eval-cycles from each other. - -- - -- @function fork - -- @treturn Stream - - --- the type name of this Stream's value. - -- - -- the following builtin typenames are used: - -- - -- - `str` - strings, `value` is a Lua string - -- - `sym` - symbols, `value` is a Lua string - -- - `num` - numbers, `value` is a Lua number - -- - `bool` - booleans, `value` is a Lua boolean - -- - `bang` - trigger signals, `value` is a Lua boolean - -- - `opdef` - `value` is an `Op` subclass - -- - `builtin` - `value` is a `Builtin` subclass - -- - `fndef` - `value` is a `FnDef` instance - -- - `scope` - `value` is a `Scope` instance - -- - -- @tfield string type - - --- documentation metadata. - -- - -- an optional table containing metadata for error messages and - -- documentation. The following keys are recognized: - -- - -- - `name`: optional name - -- - `summary`: single-line description (markdown) - -- - `examples`: optional list of single-line code examples - -- - `description`: optional full-text description (markdown) - -- - -- @tfield ?table meta - ---- static functions --- @section static - - --- construct a new Stream. - -- - -- @classmethod - -- @tparam string type the type name - -- @tparam ?table meta the `meta` table - new: (@type, @meta={}) => - -{ - :Stream -} diff --git a/alv/stream/event.moon b/alv/stream/event.moon deleted file mode 100644 index 16716be..0000000 --- a/alv/stream/event.moon +++ /dev/null @@ -1,100 +0,0 @@ ----- --- Stream of momentary events. --- --- @classmod EventStream -import Stream from require 'alv.stream.base' -import Result from require 'alv.result' -import Error from require 'alv.error' -import scope, base from require 'alv.cycle' - -class EventStream extends Stream ---- members --- @section members - - --- return whether this stream was changed in the current tick. - -- - -- @treturn bool - dirty: => @updated == COPILOT.T - - --- push an event value into the stream. - -- - -- Marks this stream as dirty for the remainder of the current tick. - -- - -- @tparam any event - add: (event) => - if not @dirty! - @events = {} - - @updated = COPILOT.T - table.insert @events, event - - --- get the sequence of current events (if any). - -- - -- Returns `events` if `dirty`, or an empty table otherwise. - -- Asserts `@type == type` if `type` is given. - -- - -- @tparam[opt] string type the type to check for - -- @tparam[optchain] string msg message to throw if type don't match - -- @treturn {any,...} `events` - unwrap: (type, msg) => - assert type == @type, msg or "#{@} is not a #{type}" if type - if @dirty! then @events else {} - - --- create a mutable copy of this stream. - -- - -- Used to wrap insulate eval-cycles from each other. - -- - -- @treturn EventStream - fork: => @@ @type - - --- alias for `unwrap`. - __call: (...) => @unwrap ... - - __tostring: => - "<#{@@__name} #{@type}>" - - --- Stream metatype. - -- - -- @tfield string metatype - metatype: 'event' - - --- the type name of the stream. - -- - -- the following builtin typenames are used: - -- - -- - `str` - strings, `value` is a Lua string - -- - `sym` - symbols, `value` is a Lua string - -- - `num` - numbers, `value` is a Lua number - -- - `bool` - booleans, `value` is a Lua boolean - -- - `bang` - trigger signals, `value` is a Lua boolean - -- - `opdef` - `value` is an `Op` subclass - -- - `builtin` - `value` is a `Builtin` subclass - -- - `fndef` - `value` is a `FnDef` instance - -- - `scope` - `value` is a `Scope` instance - -- - -- @tfield string type - - --- documentation metadata. - -- - -- an optional table containing metadata for error messages and - -- documentation. The following keys are recognized: - -- - -- - `name`: optional name - -- - `summary`: single-line description (markdown) - -- - `examples`: optional list of single-line code examples - -- - `description`: optional full-text description (markdown) - -- - -- @tfield ?table meta - ---- static functions --- @section static - - --- construct a new EventStream. - -- - -- @classmethod - -- @tparam string type the type name - new: (type) => super type - -{ - :EventStream -} diff --git a/alv/stream/init.moon b/alv/stream/init.moon deleted file mode 100644 index f9393b0..0000000 --- a/alv/stream/init.moon +++ /dev/null @@ -1,18 +0,0 @@ ----- --- `Stream` interface and implementations. --- --- @see Stream --- @see ValueStream --- @see EventStream --- @see IOStream --- --- @module stream -import ValueStream from require 'alv.stream.value' -import EventStream from require 'alv.stream.event' -import IOStream from require 'alv.stream.io' - -{ - :ValueStream - :EventStream - :IOStream -} diff --git a/alv/stream/io.moon b/alv/stream/io.moon deleted file mode 100644 index e17daa8..0000000 --- a/alv/stream/io.moon +++ /dev/null @@ -1,55 +0,0 @@ ----- --- Stream of external side-effects. --- --- Unlike other `Stream`s, this is not updated/set by an `Op` instace, but is --- continuously polled for changes by the runtime and may mark itself as --- *dirty* at any point in time. All runtime execution happens due to IOStream --- updates, which ripple through the `Result` tree. --- --- @classmod IOStream -import EventStream from require 'alv.stream.event' - -class IOStream extends EventStream ---- IOStream interface. --- --- methods that have to be implemented by `IOStream` implementations. --- @section interface - - --- construct a new IOStream. - -- - -- Must prepare the instance for `dirty` to be called. - -- The super-constructor should be called to set `Stream.type`. - -- - -- @classmethod - -- @tparam string type the typename of this stream. - new: (type) => super type - - --- create a mutable copy of this stream. - -- - -- Used to wrap insulate eval-cycles from each other. - -- - -- @treturn IOStream - fork: => @ - - --- poll for changes. - -- - -- Called every frame by the main event loop to update internal state. - poll: => - - --- check whether this adapter requires processing. - -- - -- Must return a boolean indicating whether `Op`s that refer to this instance - -- via `Input.hot` should be notified (via `Op:tick`). May be called multiple - -- times. May be called before `poll` on the first frame after construction. - -- - -- If this is not overrided, the `EventStream` interface can be used, see - -- `EventStream.add`, `EventStream.unwrap`, and `EventStream.dirty`. - -- - -- @function dirty - -- @treturn bool whether processing is required - - __inherited: (cls) => cls.__base.__tostring = @__tostring - -{ - :IOStream -} diff --git a/alv/stream/value.moon b/alv/stream/value.moon deleted file mode 100644 index 3409537..0000000 --- a/alv/stream/value.moon +++ /dev/null @@ -1,235 +0,0 @@ ----- --- Continuous stream of values. --- --- Implements the `Stream` and `AST` intefaces. --- --- @classmod ValueStream -import Stream from require 'alv.stream.base' -import Result from require 'alv.result' -import Error from require 'alv.error' -import scope, base from require 'alv.cycle' - -ancestor = (klass) -> - assert klass, "cant find the ancestor of nil" - while klass.__parent - klass = klass.__parent - klass - -class ValueStream extends Stream ---- members --- @section members - - --- return whether this stream was changed in the current tick. - -- - -- @treturn bool - dirty: => @updated == COPILOT.T - - --- update this stream's value. - -- - -- Marks this stream as dirty for the remainder of the current tick. - set: (value) => - if value != @value - @value = value - @updated = COPILOT.T - - --- unwrap to the Lua type. - -- - -- Asserts `@type == type` if `type` is given. - -- - -- @tparam[opt] string type the type to check for - -- @tparam[optchain] string msg message to throw if type don't match - -- @treturn any `value` - unwrap: (type, msg) => - assert type == @type, msg or "#{@} is not a #{type}" if type - @value - - --- create a mutable copy of this stream. - -- - -- Used to insulate eval-cycles from each other. - -- - -- @treturn ValueStream - fork: => - with ValueStream @type, @value, @raw - .updated = @updated - - --- alias for `unwrap`. - __call: (...) => @unwrap ... - - --- compare two values. - -- - -- Compares two `ValueStream`s by comparing their types and their Lua values. - __eq: (other) => other.type == @type and other.value == @value - - __tostring: => - value = if @meta.name - @meta.name - else if 'table' == (type @value) and rawget @value, '__base' - @value.__name - else - tostring @value - "<#{@@__name} #{@type}: #{value}>" - - --- Stream metatype. - -- - -- @tfield string metatype - metatype: 'value' - - --- the type name of this stream. - -- - -- the following builtin typenames are used: - -- - -- - `str` - strings, `value` is a Lua string - -- - `sym` - symbols, `value` is a Lua string - -- - `num` - numbers, `value` is a Lua number - -- - `bool` - booleans, `value` is a Lua boolean - -- - `bang` - trigger signals, `value` is a Lua boolean - -- - `opdef` - `value` is an `Op` subclass - -- - `builtin` - `value` is a `Builtin` subclass - -- - `fndef` - `value` is a `FnDef` instance - -- - `scope` - `value` is a `Scope` instance - -- - -- @tfield string type - - --- the wrapped Lua value. - -- @tfield any value - - --- documentation metadata. - -- - -- an optional table containing metadata for error messages and - -- documentation. The following keys are recognized: - -- - -- - `name`: optional name - -- - `summary`: single-line description (markdown) - -- - `examples`: optional list of single-line code examples - -- - `description`: optional full-text description (markdown) - -- - -- @tfield ?table meta - ---- AST interface --- --- `ValueStream` implements the `AST` interface. --- @section ast - - --- evaluate this literal constant. - -- - -- Throws an error if `type` is not a literal (`num`, `str` or `sym`). - -- Returns an eval-time const result for `num` and `str`. - -- Resolves `sym`s in `scope` and returns a reference to them. - -- - -- @tparam Scope scope the scope to evaluate in - -- @treturn Result the evaluation result - eval: (scope) => - switch @type - when 'num', 'str' - Result value: @ - when 'sym' - Error.wrap "resolving symbol '#{@value}'", scope\get, @value - else - error "cannot evaluate #{@}" - - --- stringify this literal constant. - -- - -- Throws an error if `raw` is not set. - -- - -- @treturn string the exact string this stream was parsed from - stringify: => assert @raw, "stringifying ValueStream that wasn't parsed" - - --- clone this literal constant. - -- - -- @treturn ValueStream self - clone: (prefix) => @ - ---- static functions --- @section static - - --- construct a new ValueStream. - -- - -- @classmethod - -- @tparam string type the type name - -- @tparam any value the Lua value to be accessed through `unwrap` - -- @tparam string raw the raw string that resulted in this value. Used by `parsing`. - new: (type, @value, @raw) => super type - - unescape = (str) -> str\gsub '\\([\'"\\])', '%1' - --- create a capture-function (for parsing with Lpeg). - -- - -- @tparam string type the type name (one of `num`, `sym` or `str`) - -- @tparam string sep the seperator char (only for `str`) - @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 - - --- wrap a Lua value. - -- - -- Attempts to guess the type and wrap a Lua value. - -- - -- @tparam any val the value to wrap - -- @tparam[opt] string name the name of this value (for error logging) - -- @treturn ValueStream - @wrap: (val, name='(unknown)') -> - typ = switch type val - when 'number' then 'num' - when 'string' then 'str' - when 'table' - if rawget val, '__base' - -- a class - switch ancestor val - when base.Op then 'opdef' - when base.Builtin then 'builtin' - else - error "#{name}: cannot wrap class '#{val.__name}'" - elseif val.__class - -- an instance - switch ancestor val.__class - when scope.Scope then 'scope' - when base.FnDef then 'fndef' - when Stream then return val - else - error "#{name}: cannot wrap '#{val.__class.__name}' instance" - else - -- plain table - return ValueStream 'scope', scope.Scope.from_table val - else - error "#{name}: cannot wrap Lua type '#{type val}'" - - ValueStream typ, val - - --- create a constant number. - -- @tparam number num the number - -- @treturn ValueStream - @num: (num) -> ValueStream 'num', num, tostring num - - --- create a constant string. - -- @tparam string str the string - -- @treturn ValueStream - @str: (str) -> ValueStream 'str', str, "'#{str}'" - - --- create a constant symbol. - -- @tparam string sym the symbol - -- @treturn ValueStream - @sym: (sym) -> ValueStream 'sym', sym, sym - - --- create a constant boolean. - -- @tparam boolean bool the boolean - -- @treturn ValueStream - @bool: (bool) -> ValueStream 'bool', bool, tostring bool - - --- wrap and document a value. - -- - -- wraps `args.value` using `wrap`, then assigns `meta`. - -- - -- @tparam table args table with keys `value` and `meta` - -- @treturn ValueStream - @meta: (args) -> - with ValueStream.wrap args.value - .meta = args.meta if args.meta - -class LiteralValue extends ValueStream - eval: => Result value: @ - -{ - :ValueStream - :LiteralValue -} diff --git a/alv/types.moon b/alv/types.moon new file mode 100644 index 0000000..3fcf722 --- /dev/null +++ b/alv/types.moon @@ -0,0 +1,101 @@ +-- what do i need types for? +-- +-- ## implementation side +-- argument specs - without values +-- output specs & output streams +-- - evt outputs are created without values, +-- - val outputs are created *with* values! +-- +-- ## language side +-- explicit casting + +-- Check whether two tables have all the same, +-- and *only the same* keys. +shared_shape = (a, b) -> + for key in pairs a + return false unless b[key] + + for key in pairs b + return false unless a[key] + + true + +same = (a, b) -> + return unless shared_shape a, b + for key, val in pairs a + return false unless val == b[key] + + true + +class Type + __eq: (a, b) -> + if a.__class == b.__class + a.__class.__base.__self_eq a, b + + __inherited: (cls) => + cls.__base.__self_eq = cls.__base.__eq + cls.__base.__eq = @__eq + +class Primitive extends Type + new: (@type) => + + pp: (value) => tostring value + + __eq: (other) => @type == other.type + __tostring: => @type + +class Struct extends Type + new: (@types) => + + --- create a new struct with a selection of keys + project: (keys) => + types = {} + for key in *keys + types[key] = @types[key] + @@ types + + pp: (value) => + inner = table.concat ["#{k}: #{@types[k]\pp v}" for k, v in pairs value], ', ' + "{#{inner}}" + + __eq: (other) => same @types, other.types + __tostring: => + inner = table.concat ["#{k}: #{v}" for k, v in pairs @types], ', ' + "{#{inner}}" + +class Array extends Type + new: (@size, @type) => + + pp: (value) => + inner = table.concat [@type\pp v for v in *value], ' ' + "[#{inner}]" + + __eq: (other) => @size == other.size and @type == other.type + __tostring: => "#{@type}[#{@size}]" + +" +bool = Primitive 'bool' +num = Primitive 'num' +str = Primitive 'str' + +vec3 = Array 3, num +noteevt = Struct { note: str, dur: num } + +assert.is.equal 'num[3]', tostring vec3 +assert.is.equal '{dur: num, note: str}', tostring noteevt + +assert.is.equal vec3 == Array 3, num +assert.not.equal vec3, Array 3, str +assert.is.equal noteevt, Struct { oct: num, note: str, dur: num } +assert.not.equal noteevt, Struct { oct: num, note: str, dur: str } + +print Constant bool, false +print SigStream vec3, { 0.5, 0.3, 0.7 } +print EvtStream noteevt +" + +{ + :Primitive + :Array + :Struct +} diff --git a/spec/cell_spec.moon b/spec/cell_spec.moon index 0b2ff64..de46245 100644 --- a/spec/cell_spec.moon +++ b/spec/cell_spec.moon @@ -1,15 +1,15 @@ import do_setup from require 'spec.test_setup' import Cell, RootCell from require 'alv.cell' -import ValueStream, Scope, Tag, SimpleRegistry, globals from require 'alv' +import Constant, Scope, Tag, SimpleRegistry, globals from require 'alv' import Copilot from require 'alv.copilot' setup do_setup hello_world = Cell.parse Tag.parse('2'), { - '', (ValueStream.sym 'hello'), ' ', (ValueStream.str 'world'), '' + '', (Constant.sym 'hello'), ' ', (Constant.str 'world'), '' } two_plus_two = Cell.parse Tag.parse('3'), { - '', (ValueStream.sym '+'), ' ', (ValueStream.num 2), ' ', (ValueStream.num 2), '' + '', (Constant.sym '+'), ' ', (Constant.num 2), ' ', (Constant.num 2), '' } describe 'Cell', -> @@ -18,8 +18,8 @@ describe 'Cell', -> with hello_world\clone parent it 'keeps children', -> assert.is.equal Cell, .__class - assert.is.equal (ValueStream.sym 'hello'), \head! - assert.is.same { ValueStream.str 'world' }, \tail! + 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 @@ -31,8 +31,8 @@ describe 'Cell', -> assert.has.error -> cell\eval globals it 'evaluates its head', -> - head = ValueStream.sym 'trace' - cell = Cell.parse { '', head, ' ', (ValueStream.sym 'true'), '' } + head = Constant.sym 'trace' + cell = Cell.parse { '', head, ' ', (Constant.sym 'true'), '' } s = spy.on head, 'eval' cell\eval globals @@ -45,10 +45,10 @@ describe 'RootCell', -> test 'head is always "do"', -> cell = Cell.parse_root {} - assert.is.equal (ValueStream.sym 'do'), cell\head! + assert.is.equal (Constant.sym 'do'), cell\head! cell = RootCell nil, { hello_world, two_plus_two } - assert.is.equal (ValueStream.sym 'do'), cell\head! + assert.is.equal (Constant.sym 'do'), cell\head! test 'tail is all children', -> cell = Cell.parse_root {} diff --git a/spec/input_spec.moon b/spec/input_spec.moon index 6ab4388..a8107ae 100644 --- a/spec/input_spec.moon +++ b/spec/input_spec.moon @@ -1,10 +1,13 @@ import do_setup from require 'spec.test_setup' -import Input, Result, ValueStream, EventStream, IOStream from require 'alv.base' +import Input, Result, SigStream, EvtStream, IOStream from require 'alv.base' +import Primitive from require 'alv.types' setup do_setup +my_io = Primitive 'my-io' + class MyIO extends IOStream - new: => super 'my-io' + new: => super my_io dirty: => @is_dirty basic_tests = (stream, input) -> @@ -22,7 +25,7 @@ basic_tests = (stream, input) -> assert.is.equal stream.metatype, input\metatype! describe 'Input.cold', -> - stream = ValueStream.num 1 + stream = SigStream.num 1 input = Input.cold stream basic_tests stream, input @@ -36,7 +39,7 @@ describe 'Input.cold', -> assert.is.false input\dirty! input\finish_setup! - new_input = Input.cold ValueStream.num 3 + new_input = Input.cold SigStream.num 3 new_input\setup input assert.is.false new_input\dirty! new_input.stream\set 4 @@ -44,8 +47,8 @@ describe 'Input.cold', -> input\finish_setup! describe 'Input.hot', -> - describe 'with EventStream', -> - stream = EventStream 'num' + describe 'with EvtStream', -> + stream = EvtStream Primitive 'num' input = Input.hot stream basic_tests stream, input @@ -53,7 +56,7 @@ describe 'Input.hot', -> it 'is marked for lifting', -> assert.is.nil input.io - it 'is dirty when the EventStream is dirty', -> + it 'is dirty when the EvtStream is dirty', -> assert.is.false input\dirty! assert.is.false stream\dirty! @@ -106,8 +109,8 @@ describe 'Input.hot', -> assert.is.true input\dirty! assert.is.true stream\dirty! - describe 'with ValueStream', -> - stream = ValueStream.num 1 + describe 'with SigStream', -> + stream = SigStream.num 1 local input describe 'at evaltime', -> @@ -120,7 +123,7 @@ describe 'Input.hot', -> input\finish_setup! it 'is dirty when different', -> - newval = ValueStream.num 2 + newval = SigStream.num 2 assert.is.false newval\dirty! newinput = Input.hot newval @@ -129,7 +132,7 @@ describe 'Input.hot', -> newinput\finish_setup! it 'is not dirty when equal', -> - newval = ValueStream.num! + newval = SigStream.num! newval\set 1 assert.is.true newval\dirty! diff --git a/spec/match_spec.moon b/spec/match_spec.moon index 52911db..91cee02 100644 --- a/spec/match_spec.moon +++ b/spec/match_spec.moon @@ -1,14 +1,15 @@ import val, evt from require 'alv.base.match' -import Result, ValueStream, EventStream from require 'alv' +import RTNode, SigStream, EvtStream from require 'alv' +import Primitive from require 'alv.types' mk_val = (type, const) -> - value = ValueStream type - with Result :value + value = SigStream Primitive type + with RTNode :value .side_inputs = { 'fake' } unless const mk_evt = (type, const) -> - value = EventStream type - with Result :value + value = EvtStream Primitive type + with RTNode :value .side_inputs = { 'fake' } unless const describe 'val and evt', -> diff --git a/spec/parsing_spec.moon b/spec/parsing_spec.moon index ecc8ff3..0486276 100644 --- a/spec/parsing_spec.moon +++ b/spec/parsing_spec.moon @@ -1,64 +1,53 @@ import space, atom, expr, explist, cell, program, comment from require 'alv.parsing' -import ValueStream from require 'alv' +import Constant from require 'alv' import Logger from require 'alv.logger' Logger\init 'silent' -verify_parse = (parser, str) -> - with assert parser\match str - assert.is.equal str, \stringify! +verify_parse = (parser, val) -> + with assert parser\match val + assert.is.equal val, \stringify! -verify_parse_nope = (parser, str) -> - with assert parser\match str - without_nope = str\match '^(.*) nope$' +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 'sym', sym.type - assert.is.equal 'some-toast', sym\unwrap! - assert.is.equal 'some-toast', sym\stringify! + assert.is.equal (Constant.sym 'some-toast'), sym describe 'numbers', -> it 'parses ints', -> num = verify_parse_nope atom, '1234 nope' - assert.is.equal 'num', num.type - assert.is.equal 1234, num\unwrap! - assert.is.equal '1234', num\stringify! + assert.is.equal (Constant.num 1234), num it 'parses floats', -> num = verify_parse_nope atom, '0.123 nope' - assert.is.equal 'num', num.type - assert.is.equal 0.123, num\unwrap! + assert.is.equal (Constant.num 0.123), num num = verify_parse_nope atom, '.123 nope' - assert.is.equal 'num', num.type - assert.is.equal 0.123, num\unwrap! + assert.is.equal (Constant.num 0.123), num num = verify_parse_nope atom, '0. nope' - assert.is.equal 'num', num.type - assert.is.equal 0, num\unwrap! + 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 'str', str.type - assert.is.equal 'help some stuff!', str\unwrap! + 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 'str', str.type - assert.is.equal "help some stuff!", str\unwrap! + 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 'str', str.type - assert.is.equal 'string with \"quote\"s and \\', str\unwrap! + 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 'str', str.type - assert.is.equal "string with \'quote\'s and \\", str\unwrap! + assert.is.equal (Constant.str "string with \'quote\'s and \\"), str describe 'Cell', -> test 'basic parsing', -> @@ -66,9 +55,9 @@ describe 'Cell', -> "friend" )' assert.is.equal 3, #node.children - assert.is.equal (ValueStream.num 3), node.children[1] - assert.is.equal (ValueStream.sym 'ok-yes'), node.children[2] - assert.is.equal (ValueStream.str 'friend'), node.children[3] + 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)' @@ -89,8 +78,8 @@ describe 'RootCell parsing', -> node = verify_parse program, str assert.is.equal 2, #node.children - assert.is.equal (ValueStream.num 3), node.children[1] - assert.is.equal (ValueStream.sym 'ok-yes'), node.children[2] + 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' diff --git a/spec/result/const_spec.moon b/spec/result/const_spec.moon new file mode 100644 index 0000000..6bd7999 --- /dev/null +++ b/spec/result/const_spec.moon @@ -0,0 +1,149 @@ +import do_setup from require 'spec.test_setup' +import Constant, RTNode, Scope, SimpleRegistry from require 'alv' +import Op, Builtin from require 'alv.base' +import Primitive from require 'alv.types' + +class TestOp extends Op + new: (...) => super ... + +class TestBuiltin extends Builtin + new: (...) => + +setup do_setup + +describe 'Constant', -> + describe '.wrap', -> + it 'wraps numbers', -> + got = Constant.wrap 3 + assert.is.equal (Primitive 'num'), got.type + assert.is.equal 3, got.value + + it 'wraps strings', -> + got = Constant.wrap "im a happy string" + assert.is.equal (Primitive '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 (Primitive 'opdef'), got.type + assert.is.equal TestOp, got.value + + it 'wraps Bultins', -> + got = Constant.wrap TestBuiltin + + assert.is.equal (Primitive 'builtin'), got.type + assert.is.equal TestBuiltin, got.value + + it 'wraps Scopes', -> + sub = Scope! + got = Constant.wrap sub + + assert.is.equal (Primitive 'scope'), got.type + assert.is.equal sub, got.value + + it 'wraps tables', -> + pi = Constant.num 3.14 + got = Constant.wrap { :pi } + + assert.is.equal (Primitive '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 Primitive 'num' + assert.is.equal 'hi', (Constant.str 'hi')\unwrap Primitive 'str' + assert.is.equal 'hi', (Constant.sym 'hi')\unwrap Primitive 'sym' + assert.has_error -> (Constant.num 3.14)\unwrap Primitive 'sym' + assert.has_error -> (Constant.str 'hi')\unwrap Primitive 'num' + assert.has_error -> (Constant.sym 'hi')\unwrap Primitive '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) Primitive 'num' + assert.is.equal 'hi', (Constant.str 'hi') Primitive 'str' + assert.is.equal 'hi', (Constant.sym 'hi') Primitive 'sym' + assert.has_error -> (Constant.num 3.14) Primitive 'sym' + assert.has_error -> (Constant.str 'hi') Primitive 'num' + assert.has_error -> (Constant.sym 'hi') Primitive '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 value: Constant.num 3 + \set 'hello', RTNode value: Constant.str "world" + \set 'goodbye', RTNode value: 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 (Primitive '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/result/sig_spec.moon b/spec/result/sig_spec.moon new file mode 100644 index 0000000..4bc0a67 --- /dev/null +++ b/spec/result/sig_spec.moon @@ -0,0 +1,109 @@ +import do_setup from require 'spec.test_setup' +import SigStream, RTNode, Scope, SimpleRegistry from require 'alv' +import Op, Builtin from require 'alv.base' +import Primitive from require 'alv.types' + +class TestOp extends Op + new: (...) => super ... + +class TestBuiltin extends Builtin + new: (...) => + +setup do_setup + +describe 'SigStream', -> + describe ':unwrap', -> + it 'returns the raw value!', -> + assert.is.equal 3.14, (SigStream.num 3.14)\unwrap! + assert.is.equal 'hi', (SigStream.str 'hi')\unwrap! + assert.is.equal 'hi', (SigStream.sym 'hi')\unwrap! + + test 'can assert the type', -> + assert.is.equal 3.14, (SigStream.num 3.14)\unwrap Primitive 'num' + assert.is.equal 'hi', (SigStream.str 'hi')\unwrap Primitive 'str' + assert.is.equal 'hi', (SigStream.sym 'hi')\unwrap Primitive 'sym' + assert.has_error -> (SigStream.num 3.14)\unwrap Primitive 'sym' + assert.has_error -> (SigStream.str 'hi')\unwrap Primitive 'num' + assert.has_error -> (SigStream.sym 'hi')\unwrap Primitive 'str' + + test 'has __call shorthand', -> + assert.is.equal 3.14, (SigStream.num 3.14)! + assert.is.equal 'hi', (SigStream.str 'hi')! + assert.is.equal 'hi', (SigStream.sym 'hi')! + assert.is.equal 3.14, (SigStream.num 3.14) Primitive 'num' + assert.is.equal 'hi', (SigStream.str 'hi') Primitive 'str' + assert.is.equal 'hi', (SigStream.sym 'hi') Primitive 'sym' + assert.has_error -> (SigStream.num 3.14) Primitive 'sym' + assert.has_error -> (SigStream.str 'hi') Primitive 'num' + assert.has_error -> (SigStream.sym 'hi') Primitive 'str' + + describe 'overrides __eq', -> + it 'compares the type', -> + val = SigStream.num 3 + assert.is.equal (SigStream.num 3), val + assert.not.equal (SigStream.str '3'), val + + val = SigStream.str 'hello' + assert.is.equal (SigStream.str 'hello'), val + assert.not.equal (SigStream.sym 'hello'), val + + it 'compares the value', -> + val = SigStream.num 3 + assert.is.equal (SigStream.num 3), val + assert.not.equal (SigStream.num 4), val + + describe ':set', -> + it 'sets the value', -> + val = SigStream.num 3 + assert.is.equal (SigStream.num 3), val + + val\set 4 + assert.is.equal (SigStream.num 4), val + assert.not.equal (SigStream.num 3), val + + it 'marks the value dirty', -> + val = SigStream.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.num 2 + b = SigStream.str 'asdf' + c = with SigStream '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.num 3 + b = with SigStream '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/result_spec.moon b/spec/result_spec.moon deleted file mode 100644 index f7006f1..0000000 --- a/spec/result_spec.moon +++ /dev/null @@ -1,181 +0,0 @@ -import do_setup from require 'spec.test_setup' -import Result, Scope, SimpleRegistry from require 'alv' -import Input, Op, ValueStream, EventStream, IOStream from require 'alv.base' - -setup do_setup - -op_with_inputs = (inputs) -> - with Op! - \setup inputs if inputs - -result_with_sideinput = (value, input) -> - with Result :value - .side_inputs = { [value]: input } - -class DirtyIO extends IOStream - new: => super 'dirty-io' - dirty: => true - -describe 'Result', -> - it 'wraps value, children', -> - value = ValueStream.num 3 - - a = Result! - b = Result! - children = { a, b } - - result = Result :value, :children - - assert.is.equal value, result.value - assert.is.same children, result.children - - it ':type gets type and assets value', -> - result = Result value: ValueStream.num 2 - assert.is.equal 'num', result\type! - - result = Result! - assert.has.error -> result\type! - - it ':is_const', -> - value = ValueStream.num 2 - pure = Result :value - impure = result_with_sideinput value, {} - - assert.is.true pure\is_const! - assert.is.false impure\is_const! - - assert.is.equal value, pure\const! - assert.has.error -> impure\const! - assert.has.error (-> impure\const 'test'), 'test' - - it ':make_ref', -> - value = ValueStream.num 2 - input = Input.hot value - op = op_with_inputs { input } - thick = Result :value, :op, children: { Result!, Result! } - ref = thick\make_ref! - - assert ref - assert.is.equal thick.value, ref.value - assert.is.same thick.side_inputs, ref.side_inputs - assert.is.same {}, ref.children - assert.is.nil ref.op - - it 'lifts up inputs from op', -> - event = ValueStream 'bang', false - event_input = Input.hot event - - value = ValueStream 'num', 4 - value_input = Input.hot value - - op = op_with_inputs { event_input, value_input } - result = Result op: op, :value - - assert.is.equal op, result.op - assert.is.same { [event]: event_input, [value]: value_input }, - result.side_inputs - - it 'does not lift up op inputs that are also child values', -> - event = ValueStream 'bang', false - event_input = Input.hot event - - value = ValueStream 'num', 4 - value_input = Input.hot value - - op = op_with_inputs { event_input, value_input } - result = Result op: op, :value, children: { Result :value } - - assert.is.same { [event]: event_input }, result.side_inputs - - it 'lifts up side_inputs from children', -> - event_value = ValueStream 'bang', false - event_input = Input.hot event_value - event = Result op: op_with_inputs { event_input } - assert.is.same { [event_value]: event_input }, event.side_inputs - - value_value = ValueStream 'num', 4 - value_input = Input.hot value_value - value = Result op: op_with_inputs { value_input } - assert.is.same { [value_value]: value_input }, value.side_inputs - - result = Result children: { event, value } - assert.is.same { [event_value]: event_input, [value_value]: value_input }, - result.side_inputs - - describe ':tick', -> - local a_value, a_child, a_input - local b_value, b_child, b_input - before_each -> - a_value = EventStream 'num' - a_input = Input.hot a_value - a_child = result_with_sideinput a_value, a_input - - b_value = EventStream 'num' - b_input = Input.hot b_value - b_child = result_with_sideinput b_value, b_input - - it 'updates children when a side_input is dirty', -> - a_value\add 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' - - result = Result children: { a_child, b_child } - result\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' - - result = Result children: { a_child, b_child } - result\tick! - - assert.spy(a).was_not_called! - assert.spy(b).was_not_called! - - it 'updates op when any op-inputs are dirty', -> - a_value\add 1 - assert.is.true a_input\dirty! - assert.is.false b_input\dirty! - - op = op_with_inputs a: Input.hot a_value - s = spy.on op, 'tick' - - result = Result :op, children: { a_child, b_child } - result\tick! - - assert.spy(s).was_called_with match.ref op - - it 'early-outs when no op-inputs are dirty', -> - a_value\add 1 - assert.is.true a_input\dirty! - assert.is.false b_input\dirty! - - op = op_with_inputs { Input.hot b_value } - s = spy.on op, 'tick' - - result = Result :op, children: { a_child, b_child } - result\tick! - - assert.spy(s).was_not_called! - - describe ':poll_io', -> - it 'polls IOs referenced in side_inputs', -> - io = DirtyIO! - input = Input.hot io - op = op_with_inputs { input } - result = Result :op - - s = spy.on io, 'poll' - assert.is.same { [io]: input }, result.side_inputs - result\poll_io! - - assert.spy(s).was_called_with match.ref io diff --git a/spec/rtnode_spec.moon b/spec/rtnode_spec.moon new file mode 100644 index 0000000..c4cf4e9 --- /dev/null +++ b/spec/rtnode_spec.moon @@ -0,0 +1,184 @@ +import do_setup from require 'spec.test_setup' +import RTNode, Scope, SimpleRegistry from require 'alv' +import Input, Op, Constant, EvtStream, IOStream from require 'alv.base' +import Primitive from require 'alv.types' + +setup do_setup +num = Primitive 'num' +bang = Primitive 'bang' + +op_with_inputs = (inputs) -> + with Op! + \setup inputs if inputs + +result_with_sideinput = (value, input) -> + with RTNode :value + .side_inputs = { [value]: input } + +class DirtyIO extends IOStream + new: => super Primitive 'dirty-io' + dirty: => true + +describe 'RTNode', -> + it 'wraps value, children', -> + value = Constant.num 3 + + a = RTNode! + b = RTNode! + children = { a, b } + + result = RTNode :value, :children + + assert.is.equal value, result.value + assert.is.same children, result.children + + it ':type gets type and assets value', -> + result = RTNode value: Constant.num 2 + assert.is.equal num, result\type! + + result = RTNode! + assert.has.error -> result\type! + + it ':is_const', -> + value = Constant.num 2 + pure = RTNode :value + impure = result_with_sideinput value, {} + + assert.is.true pure\is_const! + assert.is.false impure\is_const! + + assert.is.equal value, pure\const! + assert.has.error -> impure\const! + assert.has.error (-> impure\const 'test'), 'test' + + it ':make_ref', -> + value = Constant.num 2 + input = Input.hot value + op = op_with_inputs { input } + thick = RTNode :value, :op, children: { RTNode!, RTNode! } + ref = thick\make_ref! + + assert ref + assert.is.equal thick.value, ref.value + assert.is.same thick.side_inputs, ref.side_inputs + assert.is.same {}, ref.children + assert.is.nil ref.op + + it 'lifts up inputs from op', -> + event = Constant bang, false + event_input = Input.hot event + + value = Constant num, 4 + value_input = Input.hot value + + op = op_with_inputs { event_input, value_input } + result = RTNode op: op, :value + + assert.is.equal op, result.op + assert.is.same { [event]: event_input, [value]: value_input }, + result.side_inputs + + it 'does not lift up op inputs that are also child values', -> + event = Constant bang, false + event_input = Input.hot event + + value = Constant num, 4 + value_input = Input.hot value + + op = op_with_inputs { event_input, value_input } + result = RTNode op: op, :value, children: { RTNode :value } + + assert.is.same { [event]: event_input }, result.side_inputs + + it 'lifts up side_inputs from children', -> + event_value = Constant bang, false + event_input = Input.hot event_value + event = RTNode op: op_with_inputs { event_input } + assert.is.same { [event_value]: event_input }, event.side_inputs + + value_value = Constant num, 4 + value_input = Input.hot value_value + value = RTNode op: op_with_inputs { value_input } + assert.is.same { [value_value]: value_input }, value.side_inputs + + result = RTNode children: { event, value } + assert.is.same { [event_value]: event_input, [value_value]: value_input }, + result.side_inputs + + describe ':tick', -> + local a_value, a_child, a_input + local b_value, b_child, b_input + before_each -> + a_value = EvtStream num + a_input = Input.hot a_value + a_child = result_with_sideinput a_value, a_input + + b_value = EvtStream num + b_input = Input.hot b_value + b_child = result_with_sideinput b_value, b_input + + it 'updates children when a side_input is dirty', -> + a_value\add 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' + + result = RTNode children: { a_child, b_child } + result\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' + + result = RTNode children: { a_child, b_child } + result\tick! + + assert.spy(a).was_not_called! + assert.spy(b).was_not_called! + + it 'updates op when any op-inputs are dirty', -> + a_value\add 1 + assert.is.true a_input\dirty! + assert.is.false b_input\dirty! + + op = op_with_inputs a: Input.hot a_value + s = spy.on op, 'tick' + + result = RTNode :op, children: { a_child, b_child } + result\tick! + + assert.spy(s).was_called_with match.ref op + + it 'early-outs when no op-inputs are dirty', -> + a_value\add 1 + assert.is.true a_input\dirty! + assert.is.false b_input\dirty! + + op = op_with_inputs { Input.hot b_value } + s = spy.on op, 'tick' + + result = RTNode :op, children: { a_child, b_child } + result\tick! + + assert.spy(s).was_not_called! + + describe ':poll_io', -> + it 'polls IOs referenced in side_inputs', -> + io = DirtyIO! + input = Input.hot io + op = op_with_inputs { input } + result = RTNode :op + + s = spy.on io, 'poll' + assert.is.same { [io]: input }, result.side_inputs + result\poll_io! + + assert.spy(s).was_called_with match.ref io diff --git a/spec/scope_spec.moon b/spec/scope_spec.moon index beeeeac..405e9f8 100644 --- a/spec/scope_spec.moon +++ b/spec/scope_spec.moon @@ -1,4 +1,5 @@ -import Scope, ValueStream, Result from require 'alv' +import Scope, Constant, RTNode from require 'alv' +import Primitive from require 'alv.types' import Op from require 'alv.base' import Logger from require 'alv.logger' Logger\init 'silent' @@ -6,7 +7,12 @@ Logger\init 'silent' class TestOp extends Op new: (...) => super ... -wrap_res = (value) -> Result :value +wrap_res = (value) -> RTNode :value + +num = Primitive 'num' +str = Primitive 'str' +opdef = Primitive 'opdef' +scope_t = Primitive 'scope' describe 'Scope', -> describe 'constifies', -> @@ -16,18 +22,18 @@ describe 'Scope', -> scope\set_raw 'num', 3 got = (scope\get 'num')\const! - assert.is.equal 'num', got.type + 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')\const! - assert.is.equal 'str', got.type + assert.is.equal str, got.type assert.is.equal "im a happy string", got.value test 'Values', -> - pi = ValueStream 'num', 3.14 + pi = Constant.num 3.14 scope\set_raw 'pi', pi assert.is.equal pi, (scope\get 'pi')\const! @@ -36,7 +42,7 @@ describe 'Scope', -> scope\set_raw 'test', TestOp got = (scope\get 'test')\const! - assert.is.equal 'opdef', got.type + assert.is.equal opdef, got.type assert.is.equal TestOp, got.value test 'Scopes', -> @@ -44,21 +50,21 @@ describe 'Scope', -> scope\set_raw 'sub', sub got = (scope\get 'sub')\const! - assert.is.equal 'scope', got.type + assert.is.equal scope_t, got.type assert.is.equal sub, got.value test 'tables', -> - pi = ValueStream 'num', 3.14 - scope\set_raw 'math', { :pi } + pi = Constant.num 3.14 + scope\set_raw 'math', { :pi } got = (scope\get 'math')\const! - assert.is.equal 'scope', got.type + assert.is.equal scope_t, 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 'wraps Values in from_table', -> - pi = ValueStream 'num', 3.14 + pi = Constant.num 3.14 scope = Scope.from_table { num: 3 str: "im a happy string" @@ -68,21 +74,21 @@ describe 'Scope', -> } got = (scope\get 'num')\const! - assert.is.equal 'num', got.type + assert.is.equal num, got.type assert.is.equal 3, got.value got = (scope\get 'str')\const! - assert.is.equal 'str', got.type + assert.is.equal str, got.type assert.is.equal "im a happy string", got.value assert.is.equal pi, (scope\get 'pi')\const! got = (scope\get 'test')\const! - assert.is.equal 'opdef', got.type + assert.is.equal opdef, got.type assert.is.equal TestOp, got.value got = (scope\get 'math')\const! - assert.is.equal 'scope', got.type + assert.is.equal scope_t, got.type assert.is.equal pi, (scope\get 'math/pi')\const! it 'gets from nested scopes', -> @@ -90,7 +96,7 @@ describe 'Scope', -> a = Scope! b = Scope! - pi = ValueStream 'num', 3.14 + pi = Constant.num 3.14 b\set_raw 'test', pi a\set_raw 'child', b root\set_raw 'deep', a @@ -98,8 +104,8 @@ describe 'Scope', -> assert.is.equal pi, (root\get 'deep/child/test')\const! describe 'can set symbols', -> - one = wrap_res ValueStream.num 1 - two = wrap_res ValueStream.num 2 + one = wrap_res Constant.num 1 + two = wrap_res Constant.num 2 scope = Scope! it 'disallows re-setting symbols', -> @@ -119,14 +125,14 @@ describe 'Scope', -> it 'allows access', -> got = (scope\get 'inherited')\const! - assert.is.equal 'str', got.type + assert.is.equal str, got.type assert.is.equal "inherited string", got.value it 'can be shadowed', -> scope\set_raw 'hidden', "overwritten" got = (scope\get 'hidden')\const! - assert.is.equal 'str', got.type + assert.is.equal str, got.type assert.is.equal "overwritten", got.value describe 'dynamic inheritance', -> diff --git a/spec/value_spec.moon b/spec/value_spec.moon deleted file mode 100644 index d81ed40..0000000 --- a/spec/value_spec.moon +++ /dev/null @@ -1,180 +0,0 @@ -import do_setup from require 'spec.test_setup' -import ValueStream, Result, Scope, SimpleRegistry 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 'ValueStream', -> - describe '.wrap', -> - it 'wraps numbers', -> - got = ValueStream.wrap 3 - assert.is.equal 'num', got.type - assert.is.equal 3, got.value - - it 'wraps strings', -> - got = ValueStream.wrap "im a happy string" - assert.is.equal 'str', got.type - assert.is.equal "im a happy string", got.value - - it 'wraps Values', -> - pi = ValueStream 'num', 3.14 - got = ValueStream.wrap pi - - assert.is.equal pi, got - - it 'wraps Opdefs', -> - got = ValueStream.wrap TestOp - - assert.is.equal 'opdef', got.type - assert.is.equal TestOp, got.value - - it 'wraps Bultins', -> - got = ValueStream.wrap TestBuiltin - - assert.is.equal 'builtin', got.type - assert.is.equal TestBuiltin, got.value - - it 'wraps Scopes', -> - sub = Scope! - got = ValueStream.wrap sub - - assert.is.equal 'scope', got.type - assert.is.equal sub, got.value - - it 'wraps tables', -> - pi = ValueStream 'num', 3.14 - got = ValueStream.wrap { :pi } - - assert.is.equal 'scope', got.type - assert.is.equal pi, (got.value\get 'pi')\const! - - describe ':unwrap', -> - it 'returns the raw value!', -> - assert.is.equal 3.14, (ValueStream.num 3.14)\unwrap! - assert.is.equal 'hi', (ValueStream.str 'hi')\unwrap! - assert.is.equal 'hi', (ValueStream.sym 'hi')\unwrap! - - test 'can assert the type', -> - assert.is.equal 3.14, (ValueStream.num 3.14)\unwrap 'num' - assert.is.equal 'hi', (ValueStream.str 'hi')\unwrap 'str' - assert.is.equal 'hi', (ValueStream.sym 'hi')\unwrap 'sym' - assert.has_error -> (ValueStream.num 3.14)\unwrap 'sym' - assert.has_error -> (ValueStream.str 'hi')\unwrap 'num' - assert.has_error -> (ValueStream.sym 'hi')\unwrap 'str' - - test 'has __call shorthand', -> - assert.is.equal 3.14, (ValueStream.num 3.14)! - assert.is.equal 'hi', (ValueStream.str 'hi')! - assert.is.equal 'hi', (ValueStream.sym 'hi')! - assert.is.equal 3.14, (ValueStream.num 3.14) 'num' - assert.is.equal 'hi', (ValueStream.str 'hi') 'str' - assert.is.equal 'hi', (ValueStream.sym 'hi') 'sym' - assert.has_error -> (ValueStream.num 3.14) 'sym' - assert.has_error -> (ValueStream.str 'hi') 'num' - assert.has_error -> (ValueStream.sym 'hi') 'str' - - describe 'overrides __eq', -> - it 'compares the type', -> - val = ValueStream 'num', 3 - assert.is.equal (ValueStream.num 3), val - assert.not.equal (ValueStream.str '3'), val - - val = ValueStream 'str', 'hello' - assert.is.equal (ValueStream.str 'hello'), val - assert.not.equal (ValueStream.sym 'hello'), val - - it 'compares the value', -> - val = ValueStream 'num', 3 - assert.is.equal (ValueStream.num 3), val - assert.not.equal (ValueStream.num 4), val - - describe ':set', -> - it 'sets the value', -> - val = ValueStream 'num', 3 - assert.is.equal (ValueStream.num 3), val - - val\set 4 - assert.is.equal (ValueStream.num 4), val - assert.not.equal (ValueStream.num 3), val - - it 'marks the value dirty', -> - val = ValueStream 'num', 3 - assert.is.false val\dirty! - - val\set 4 - assert.is.true val\dirty! - - describe ':eval', -> - it 'turns numbers into consts', -> - assert_noop = (val) -> - assert.is.equal val, val\eval!\const! - - assert_noop ValueStream.num 2 - assert_noop ValueStream.str 'hello' - - it 'looks up symbols in the scope', -> - scope = with Scope! - \set 'number', Result value: ValueStream.num 3 - \set 'hello', Result value: ValueStream.str "world" - \set 'goodbye', Result value: ValueStream.sym "again" - - assert_eval = (sym, val) -> - const = ValueStream.sym sym - assert.is.equal val, (const\eval scope)\const! - - assert_eval 'number', ValueStream.num 3 - assert_eval 'hello', ValueStream.str "world" - assert_eval 'goodbye', ValueStream.sym "again" - - it ':clone sliterals as themselves', -> - assert_noop = (val) -> assert.is.equal val, val\clone! - - assert_noop ValueStream.num 2 - assert_noop ValueStream.str 'hello' - assert_noop ValueStream.sym 'world' - - describe ':fork', -> - it 'is equal to the original', -> - a = ValueStream.num 2 - b = ValueStream.str 'asdf' - c = with ValueStream '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 = ValueStream.num 3 - b = with ValueStream '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! -- cgit v1.2.3