diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-03-24 11:39:25 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2020-03-24 11:39:25 +0000 |
| commit | 677c0d2f01e14bbeca1583ec7878d80c71c3aa68 (patch) | |
| tree | cc20e04697da590b546de370599eeaaef647d20c | |
| parent | internals/plugin-guide: first draft (diff) | |
| download | alive-677c0d2f01e14bbeca1583ec7878d80c71c3aa68.tar.gz alive-677c0d2f01e14bbeca1583ec7878d80c71c3aa68.zip | |
Value -> Value/Event/IO-Stream
Close 12
| -rw-r--r-- | core/ast.moon | 13 | ||||
| -rw-r--r-- | core/base/init.moon | 12 | ||||
| -rw-r--r-- | core/base/input.moon | 92 | ||||
| -rw-r--r-- | core/base/io.moon | 40 | ||||
| -rw-r--r-- | core/base/op.moon | 66 | ||||
| -rw-r--r-- | core/builtin.moon | 48 | ||||
| -rw-r--r-- | core/cell.moon | 4 | ||||
| -rw-r--r-- | core/init.moon | 20 | ||||
| -rw-r--r-- | core/invoke.moon | 6 | ||||
| -rw-r--r-- | core/parsing.moon | 10 | ||||
| -rw-r--r-- | core/plugin-guide.md | 111 | ||||
| -rw-r--r-- | core/registry.moon | 2 | ||||
| -rw-r--r-- | core/result.moon | 21 | ||||
| -rw-r--r-- | core/scope.moon | 8 | ||||
| -rw-r--r-- | core/stream/base.moon | 79 | ||||
| -rw-r--r-- | core/stream/event.moon | 92 | ||||
| -rw-r--r-- | core/stream/init.moon | 18 | ||||
| -rw-r--r-- | core/stream/io.moon | 48 | ||||
| -rw-r--r-- | core/stream/value.moon (renamed from core/value.moon) | 80 | ||||
| -rw-r--r-- | spec/core/cell_spec.moon | 26 | ||||
| -rw-r--r-- | spec/core/input_spec.moon | 264 | ||||
| -rw-r--r-- | spec/core/parsing_spec.moon | 12 | ||||
| -rw-r--r-- | spec/core/pattern_spec.moon | 20 | ||||
| -rw-r--r-- | spec/core/result_spec.moon | 65 | ||||
| -rw-r--r-- | spec/core/scope_spec.moon | 14 | ||||
| -rw-r--r-- | spec/core/value_spec.moon | 126 |
26 files changed, 740 insertions, 557 deletions
diff --git a/core/ast.moon b/core/ast.moon index 31aed1f..a25d1cd 100644 --- a/core/ast.moon +++ b/core/ast.moon @@ -5,7 +5,6 @@ -- -- @classmod AST - --- members -- @section members @@ -13,8 +12,7 @@ -- -- Evaluate this node and return a `Result`. -- - -- @class function - -- @name eval + -- @function eval -- @tparam Scope scope the scope to evaluate in -- @treturn Result the evaluation result @@ -22,8 +20,7 @@ -- --- Returns a mutable copy of this Node that shares its identity. -- - -- @class function - -- @name quote + -- @function quote -- @treturn AST --- create a clone with its own identity. @@ -31,8 +28,7 @@ -- creates a clone of this Cell with its own identity by prepending a `parent` -- Tag (and cloning all child expressions recursively). -- - -- @class function - -- @name clone + -- @function clone -- @tparam Tag parent -- @treturn AST @@ -40,6 +36,5 @@ -- -- Should return the exact string this node was parsed from (if it was parsed). -- - -- @class function - -- @name stringify + -- @function stringify -- @treturn string the exact string this Node was parsed from diff --git a/core/base/init.moon b/core/base/init.moon index bb35140..8e376fc 100644 --- a/core/base/init.moon +++ b/core/base/init.moon @@ -4,28 +4,27 @@ -- This module exports the following classes that extension modules may need: -- -- @module base --- @see IO -- @see Op -- @see Action -- @see FnDef -- @see Input -- @see match --- @see Value +-- @see ValueStream +-- @see EventStream +-- @see IOStream -- @see Result -- @see Error -import IO from require 'core.base.io' import Op from require 'core.base.op' import Action from require 'core.base.action' import FnDef from require 'core.base.fndef' import Input from require 'core.base.input' import match from require 'core.base.match' -import Value from require 'core.value' +import ValueStream, EventStream, IOStream from require 'core.stream' import Result from require 'core.result' import Error from require 'core.error' { - :IO :Op :Action :FnDef @@ -33,5 +32,6 @@ import Error from require 'core.error' :match -- redundant exports, to keep anything an extension might need in one import - :Value, :Result, :Error + :ValueStream, :EventStream, :IOStream + :Result, :Error } diff --git a/core/base/input.moon b/core/base/input.moon index c89337f..6922a4f 100644 --- a/core/base/input.moon +++ b/core/base/input.moon @@ -2,10 +2,29 @@ -- Update scheduling policy for `Op` arguments. -- -- @classmod Input -import Value from require 'core.value' +import ValueStream, EventStream, IOStream from require 'core.stream' import Result from require 'core.result' -local ColdInput, ValueInput, EventInput, IOInput +inherits = (klass, frm) -> + assert klass, "cant find the ancestor of nil" + return true if klass == frm + while klass.__parent + return true if klass.__parent == frm + klass = klass.__parent + false + +match_parent = (inst, map) -> + klass = assert inst and inst.__class, "not an instance" + if key = map[klass] + return key + + while klass.__parent + if key = map[klass.__parent] + return key + + klass = klass.__parent + +local ColdInput, ValueInput, IOInput, mapping class Input --- Input interface. @@ -15,20 +34,10 @@ class Input --- create a new Input. -- - -- `value` is either a `Value` or a `Result` instance and should be - -- unwrapped and assigned to `stream`. - -- -- @classmethod - -- @tparam Value|Result value - new: (value) => - assert value, "nil passed to Input: #{value}" - @stream = switch value.__class - when Result - assert value.value, "Input from result without value!" - when Value - value - else - error "Input from unknown value: #{value}" + -- @tparam Stream stream + new: (@stream) => + assert @stream, "nil passed to Input: #{value}" --- copy state from old instance (optional). -- @@ -44,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`:@{Value:dirty|dirty}. + -- `stream`:@{ValueStream:dirty|dirty}. -- -- @treturn bool whether processing is necessary dirty: => @stream\dirty! @@ -65,7 +74,7 @@ class Input --- the current value -- - -- @tfield Value stream + -- @tfield ValueStream stream --- members -- @section members @@ -86,33 +95,29 @@ class Input -- Never marked dirty. Use this for input streams that are only read when -- another `Input` is dirty. -- - -- @tparam Value|Result value - @cold: (value) -> ColdInput value + -- @tparam Stream|Result value + @cold: (value) -> + if value.__class == Result + value = assert value.value, "Input from result without value!" + ColdInput value - --- Create a `value` `Input`. + --- Create a `hot` `Input`. -- - -- Marked dirty for the eval-tick if old and new `Value` differ. This is the - -- most common `Input` strategy. Should be used whenever a - -- value denotes state. + -- Behaviour depends on what kind of `Stream` `value` is: -- - -- @tparam Value|Result value - @value: (value) -> ValueInput value - - --- Create an `event` `Input`. + -- - `ValueStream`: Marked dirty for the eval-tick if old and new `ValueStream` differ. + -- - `EventStream` and `IOStream`: Marked dirty only if the current `EventStream` is dirty. -- - -- Only marked dirty if the `Value` itself is dirty. Should be used whenever - -- an `Input` denotes a momentary event or impulse. + -- This is the most common `Input` strategy. -- - -- @tparam Value|Result value - @event: (value) -> EventInput value + -- @tparam Stream|Result value + @hot: (value) -> + if value.__class == Result + value = assert value.value, "Input from result without value!" - --- Create an `IO` `Input`. - -- - -- Marked dirty only when an `IO` is dirty. Must be used only for `Value`s - -- which @{Value:unwrap|unwrap} to `IO` instances. - -- - -- @tparam Value|Result value - @io: (value) -> IOInput value + InputType = match_parent value, mapping + assert InputType, "Input from unknown value: #{value}" + InputType value class ColdInput extends Input dirty: => false @@ -124,11 +129,14 @@ class ValueInput extends Input return @dirty_setup if @dirty_setup != nil @stream\dirty! -class EventInput extends Input - class IOInput extends Input - impure: true - dirty: => @stream\unwrap!\dirty! + io: true + +mapping = { + [ValueStream]: ValueInput + [EventStream]: Input + [IOStream]: IOInput +} { :Input diff --git a/core/base/io.moon b/core/base/io.moon deleted file mode 100644 index cbb5823..0000000 --- a/core/base/io.moon +++ /dev/null @@ -1,40 +0,0 @@ ----- --- Incoming side-effect adapter, creating events for the dataflow graph. --- --- Polled by the main event loop to kick of events that cause the the dataflow --- graph to ripple results. --- --- @classmod IO - -class IO ---- IO interface. --- --- methods that have to be implemented by `IO` implementations. --- @section interface - - --- construct a new instance. - -- - -- Must prepare the instance for `dirty` to be called. - -- @classmethod - new: => - - --- poll for changes. - -- - -- Called every frame by the main event loop to update internal state. - tick: => - - --- check whether this adapter requires processing. - -- - -- Must return a boolean indicating whether `Op`s that refer to this instance - -- via `Input.io` should be notified (via `Op:tick`). May be called multiple - -- times. May be called before `tick` on the first frame after construction. - -- - -- @treturn bool whether processing is required - dirty: => - - __tostring: => "<IO #{@@__name}>" - __inherited: (cls) => cls.__base.__tostring = @__tostring - -{ - :IO -} diff --git a/core/base/op.moon b/core/base/op.moon index a8da100..4880afd 100644 --- a/core/base/op.moon +++ b/core/base/op.moon @@ -2,16 +2,16 @@ -- Persistent expression Operator. -- -- @classmod Op -import Value from require 'core.value' -ident = (tbl) -> - mt = getmetatable tbl - setmetatable tbl, nil - - id = tostring tbl - - setmetatable tbl, mt - id\sub #'table: 0x' +deepcopy = (val) -> + switch type val + when 'number', 'string', 'boolean' + val + when 'table' + assert (not getmetatable {}), "state should only contain simple tables!" + {(deepcopy k), (deepcopy v) for k,v in pairs val} + else + error "state cannot contain values of type '#{type val}'" class Op --- members @@ -33,23 +33,30 @@ class Op -- Used to wrap insulate eval-cycles from each other. The copy does not have -- `inputs` set, since it is expected that this is (re)set in `setup`. -- - -- @treturn Value + -- @treturn Op fork: => - with setmetatable {}, getmetatable @ - .state = {k,v for k,v in pairs @state} if @state - .out = @out\fork! if @out + out = if @out then @out\fork + state = if @state then deepcopy state + @@ out, state + + --- internal state of this Op. + -- + -- This may be any simple Lua value, including Lua tables, as long as it has + -- no metatables, multiple references/loops, userdata etc. + -- + -- @tfield table state - --- `Value` instance representing this Op's computed output value. + --- `Stream` instance representing this Op's computed output value. -- - -- Must be set to a `Value` instance once `setup` finishes. Must not change - -- type, be removed or replaced outside of `new` and `setup`. Should have a - -- value assigned via `set` or the `Value` constructor once `tick` is - -- called the first time. If `out`'s value is not initialized in `new` - -- or `setup`, the implementation must make sure `tick``(true)` is called at - -- least on the first eval-cycle the Op goes through, e.g. by using an - -- `Input.value`. + -- Must be set to a `Stream` instance once `setup` finishes. Must not change + -- type, be removed or replaced outside of `new` and `setup`. If it is a + -- `ValueStream`, it should have a value assigned via `set` or the + -- constructor once `tick` is called the first time. If `out`'s value is not + -- initialized in `new` or `setup`, the implementation must make sure + -- `tick``(true)` is called at -- least on the first eval-cycle the Op goes + -- through, e.g. by using an `Input.value`. -- - -- @tfield Value out + -- @tfield Stream out --- table containing `Input`s to this Op. -- @@ -66,10 +73,14 @@ class Op --- construct a new instance. -- - -- The super-constructor can be used to construct a `Value` instance in `out`. + -- The optional parameters `out` and `state` are used by `fork` to duplicate + -- an instance. If the constructor is overriden, these parameters must be + -- forwarded to the superconstructor unchanged. -- -- @function new -- @classmethod + -- @tparam ?Stream out `out` + -- @tparam ?table state `state` --- parse arguments and patch self. -- @@ -109,12 +120,9 @@ class Op -- type is not known at this time. -- -- @classmethod - -- @tparam[opt] string type the type-name for `out` - -- @tparam[optchain] any init the initial value for `out` - new: (type, init) => - @state = {} - if type - @out = Value type, init + -- @tparam ?Stream out `out` + -- @tparam ?table state `state` + new: (@out, @state) => do_setup = (old, cur) -> for k, cur_val in pairs cur diff --git a/core/builtin.moon b/core/builtin.moon index bd3183f..f9087c4 100644 --- a/core/builtin.moon +++ b/core/builtin.moon @@ -6,14 +6,14 @@ -- -- @module builtin import Action, Op, FnDef, Input, match from require 'core.base' -import Value, LiteralValue from require 'core.value' +import ValueStream, LiteralValue from require 'core.stream.value' import Result from require 'core.result' import Cell from require 'core.cell' import Scope from require 'core.scope' import Tag from require 'core.tag' import op_invoke from require 'core.invoke' -doc = Value.meta +doc = ValueStream.meta meta: name: 'doc' summary: "Print documentation in console." @@ -38,7 +38,7 @@ doc = Value.meta meta = result.value.meta L\print "(doc #{tail[1]}):\n#{format_meta meta}\n" -def = Value.meta +def = ValueStream.meta meta: name: 'def' summary: "Declare symbols in current scope." @@ -63,7 +63,7 @@ Define the symbols `sym1`, `sym2`, … to resolve to the values of `val-expr1`, Result :children -use = Value.meta +use = ValueStream.meta meta: name: 'use' summary: "Merge scopes into current scope." @@ -82,7 +82,7 @@ All arguments have to be evaltime constant." Result! -require_ = Value.meta +require_ = ValueStream.meta meta: name: 'require' summary: "Load a module." @@ -98,10 +98,10 @@ require_ = Value.meta name = result\const! L\trace @, "loading module #{name}" - scope = Value.wrap require "lib.#{name\unwrap 'str'}" + scope = ValueStream.wrap require "lib.#{name\unwrap 'str'}" Result :value -import_ = Value.meta +import_ = ValueStream.meta meta: name: 'import' summary: "Require and define modules." @@ -117,11 +117,11 @@ current scope." for child in *tail name = (child\quote scope)\unwrap 'sym' - value = Value.wrap require "lib.#{name}" + value = ValueStream.wrap require "lib.#{name}" scope\set name, Result :value -- (require "lib.#{name})\unwrap 'scope' Result! -import_star = Value.meta +import_star = ValueStream.meta meta: name: 'import*' summary: "Require and use modules." @@ -137,12 +137,12 @@ Requires modules `sym1`, `sym2`, … and merges them into the current scope." for child in *tail name = (child\quote scope)\unwrap 'sym' - value = Value.wrap require "lib.#{name}" + value = ValueStream.wrap require "lib.#{name}" scope\use value\unwrap 'scope' -- (require "lib.#{name}")\unwrap 'scope' Result! -fn = Value.meta +fn = ValueStream.meta meta: name: 'fn' summary: "Declare a function." @@ -163,13 +163,13 @@ function is invoked." param\quote scope body = body\quote scope - Result value: with Value.wrap FnDef param_symbols, body, scope + Result value: with ValueStream.wrap FnDef param_symbols, body, scope .meta = { summary: "(user defined function)" examples: { "(??? #{table.concat [p! for p in *param_symbols], ' '})" } } -defn = Value.meta +defn = ValueStream.meta meta: name: 'defn' summary: "Define a function." @@ -193,7 +193,7 @@ function is invoked." body = body\quote scope - value = with Value.wrap FnDef param_symbols, body, scope + value = with ValueStream.wrap FnDef param_symbols, body, scope .meta = :name summary: "(user defined function)" @@ -202,7 +202,7 @@ function is invoked." scope\set name, Result :value Result! -do_expr = Value.meta +do_expr = ValueStream.meta meta: name: 'do_expr' summary: "Evaluate multiple expressions in a new scope." @@ -215,7 +215,7 @@ Evaluate `expr1`, `expr2`, … and return the value of the last expression." scope = Scope scope Result children: [expr\eval scope for expr in *tail] -if_ = Value.meta +if_ = ValueStream.meta meta: name: 'if' summary: "Make an evaltime const choice." @@ -240,7 +240,7 @@ to `then-expr`, otherwise it is equivalent to `else-xpr` if given, or nil otherw elseif xelse xelse\eval scope -trace_ = Value.meta +trace_ = ValueStream.meta meta: name: 'trace!' summary: "Trace an expression's value at evaltime." @@ -254,7 +254,7 @@ trace_ = Value.meta with result = L\push tail[1]\eval, scope L\print "trace! #{tail[1]\stringify!}: #{result.value}" -trace = Value.meta +trace = ValueStream.meta meta: name: 'trace' summary: "Trace an expression's values at runtime." @@ -266,7 +266,7 @@ trace = Value.meta { prefix, value } = match 'str any', inputs super prefix: Input.cold prefix - value: Input.value value + value: Input.hot value tick: => L\print "trace #{@inputs.prefix!}: #{@inputs.value.stream}" @@ -278,7 +278,7 @@ trace = Value.meta tag = @tag\clone Tag.parse '-1' inner = Cell tag, { LiteralValue 'opdef', traceOp, 'trace' - Value.str tostring tail[1] + ValueStream.str tostring tail[1] tail[1] } inner\eval scope @@ -292,17 +292,17 @@ trace = Value.meta import: import_ 'import*': import_star - true: Value.meta + true: ValueStream.meta meta: name: 'true' summary: "The boolean constant `true`." - value: Value.bool true + value: ValueStream.bool true - false: Value.meta + false: ValueStream.meta meta: name: 'false' summary: "The boolean constant `false`." - value: Value.bool false + value: ValueStream.bool false :fn, :defn 'do': do_expr diff --git a/core/cell.moon b/core/cell.moon index 14bea2c..91634f8 100644 --- a/core/cell.moon +++ b/core/cell.moon @@ -5,7 +5,7 @@ -- nodes), a `Tag`, and optionally the internal whitespace as parsed. -- -- @classmod Cell -import Value from require 'core.value' +import ValueStream from require 'core.stream' import Error from require 'core.error' import op_invoke, fn_invoke from require 'core.invoke' import Tag from require 'core.tag' @@ -170,7 +170,7 @@ class Cell -- @type RootCell class RootCell extends Cell - head: => Value.sym 'do' + head: => ValueStream.sym 'do' tail: => @children stringify: => diff --git a/core/init.moon b/core/init.moon index 8a0154c..6e23fa7 100644 --- a/core/init.moon +++ b/core/init.moon @@ -1,20 +1,10 @@ ---- -- `alive` public API. -- --- @see Value --- @see Result --- @see Cell --- @see Scope --- @see Registry --- @see Tag --- @field globals --- @field parse --- @field eval --- -- @module init L or= setmetatable {}, __index: => -> -import Value from require 'core.value' +import ValueStream, EventStream, IOStream from require 'core.stream' import Result from require 'core.result' import Scope from require 'core.scope' import Error from require 'core.error' @@ -31,7 +21,9 @@ globals = Scope.from_table require 'core.builtin' --- exports -- @table exports --- @tfield Value Value +-- @tfield ValueStream ValueStream +-- @tfield EventStream EventStream +-- @tfield IOStream IOStream -- @tfield Result Result -- @tfield Cell Cell -- @tfield RootCell RootCell @@ -42,9 +34,9 @@ globals = Scope.from_table require 'core.builtin' -- @tfield Scope globals global definitons -- @tfield parse function to turn a `string` into a root `Cell` { - :Value, :Result + :ValueStream, :EventStream, :IOStream :Cell, :RootCell - :Scope, :Error + :Result, :Scope, :Error :Registry, :SimpleRegistry, :Tag diff --git a/core/invoke.moon b/core/invoke.moon index 654e436..539a778 100644 --- a/core/invoke.moon +++ b/core/invoke.moon @@ -2,7 +2,6 @@ -- Builtins for invoking `Op`s and `FnDef`s. -- -- @module invoke -import Value from require 'core.value' import Result from require 'core.result' import Action from require 'core.base' import Scope from require 'core.scope' @@ -90,8 +89,9 @@ class fn_invoke extends Action -- `FnDef.body` with the prefix `Action.tag`, and `AST:eval`s it in the newly -- created `Scope`. -- - -- The `Result` contains the `Value` from the cloned AST, and its children are - -- all the `Result`s from evaluating the tail as well as the cloned `AST`s. + -- 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 + -- `AST`s. -- -- @tparam Scope outer_scope the active scope -- @tparam {AST,...} tail the arguments to this expression diff --git a/core/parsing.moon b/core/parsing.moon index b96f173..aadcab4 100644 --- a/core/parsing.moon +++ b/core/parsing.moon @@ -2,7 +2,7 @@ -- Lpeg Grammar for parsing `alive` code. -- -- @module parsing -import Value from require 'core.value' +import ValueStream from require 'core.stream' import Cell from require 'core.cell' import Tag from require 'core.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 / Value\parse 'sym' +sym = first * (first + digit)^0 / ValueStream\parse 'sym' -strd = '"' * (C ((P '\\"') + (P '\\\\') + (1 - P '"'))^0) * '"' / Value\parse 'str', '\"' -strq = "'" * (C ((P "\\'") + (P '\\\\') + (1 - P "'"))^0) * "'" / Value\parse 'str', '\'' +strd = '"' * (C ((P '\\"') + (P '\\\\') + (1 - P '"'))^0) * '"' / ValueStream\parse 'str', '\"' +strq = "'" * (C ((P "\\'") + (P '\\\\') + (1 - P "'"))^0) * "'" / ValueStream\parse 'str', '\'' str = strd + strq int = digit^1 float = (digit^1 * '.' * digit^0) + (digit^0 * '.' * digit^1) -num = ((P '-')^-1 * (float + int)) / Value\parse 'num' +num = ((P '-')^-1 * (float + int)) / ValueStream\parse 'num' atom = num + sym + str diff --git a/core/plugin-guide.md b/core/plugin-guide.md index f5e3c50..1086452 100644 --- a/core/plugin-guide.md +++ b/core/plugin-guide.md @@ -11,13 +11,13 @@ are exported in the `base` module. ## documentation metadata The lua module should return a `Scope` or a table that will be converted using -`Scope.from_table`. All exports should be documented using `Value.meta`, which -attaches a `meta` table to the value that is used for error messages, +`Scope.from_table`. All exports should be documented using `ValueStream.meta`, +which attaches a `meta` table to the value that is used for error messages, documentation generation and [`(doc)`][builtins-doc]. - import Value from require 'core.base' + import ValueStream from require 'core.base' - two = Value.meta + two = ValueStream.meta meta: name: 'two' summary: "the number two" @@ -43,30 +43,31 @@ Most plugins will want to define a number of *Op*s to be used by the user. They are implemented by deriving from the `Op` class and implementing at least the `Op:setup` and `Op:tick` methods. - import Value, Op, Input, match from require 'core.base' + import ValueStream, Op, Input, match from require 'core.base' - my_add = Value.meta + total_sum = ValueStream.meta meta: - name: 'my-add' - summary: "Add two numbers." - examples: { '(my-add a b)' } - description: "Add two numbers, plugin-style." + name: 'total-sum' + summary: "Keep a total of incoming numbers." + examples: { '(total-sum num!)' } + description: "Keep a total sum of incoming number events, plugin-style." value: class extends Op - new: => super 'num' - + new: (...) => + super ... + @state or= { total: 0 } + @out or= ValueStream 'num', @state.total + setup: (inputs, scope) => - { a, b } = match 'num num', inputs + { num } = match 'num!', inputs - super - a: Inputs.value a - b: Inputs.value b + super num: Inputs.hot num tick: => - @out\set @inputs.a! + @inputs.b! - + @state.total += @inputs.num! + @out\set @state.total { - 'my-add': my_add + 'total-sum': total_sum } ### Op:setup @@ -86,9 +87,9 @@ argument types and matches them against the provided arguments: { str, numbers, optional } = match 'str *num any?', inputs `match` matches arguments greedily from left to right. Each part of the string -is the type-name of a Value. Parts can be optional (`num?`), multiple (`*num` - -one or more numbers) or both (`*num?` - zero or more numbers). If there is an -equals sign in front of a part, the corresponding `Result` has to be +is the type-name of an argument. Parts can be optional (`num?`), multiple +(`*num` - one or more numbers) or both (`*num?` - zero or more numbers). If +there is an equals sign in front of a part, the corresponding `Result` has to be *evaltime constant*. The special typename `any` can be used for generic Ops. If there are more complex dependencies between arguments, it is recommended to @@ -97,17 +98,67 @@ manually. For invalid or missing arguments, `Error` instances should be thrown using `error` or `assert`. #### input setup - -(section wip since changes are anticipated) +There are two types of inputs: `Input.hot` and `Input.cold`: + +*Cold* inputs do not cause the Op to update when changes to the input stream +are made. They are useful to 'ignore' changes to inputs which are only relevant +when another input changed value. Imagine for example a `send-value-when` Op, +which sends a value only when a `bang!` input is live. This Op doesn't have to +update when the value changes, it's enough to update only when the trigger input +changes and simply read the value in that moment. + +*Hot* inputs on the other hand mark the input stream as a dependency for the +Op. Depending on the type of `Stream`, the semantics are a little different: + +- For `ValueStream`s, the Op updates whenever the current value changes. When + an input stream is swapped out for another one at evaltime, but their values + are momentarily equal, the input is not considered dirty. +- For `EventStream`s and `IOStream`s, the Op updates whenever the stream is + dirty. There is no special handling when the stream is swapped out at + evaltime. + +All `Result`s from the `inputs` argument that are taken into consideration +should be wrapped in an `Input` instance using either `Input.hot` or +`Input.cold`, and need to be passed to the `Op:setup` super implementation. +To illustrate with the `send-value-when` example: + + setup: (inputs, scope) => + { trig, value } = match 'bang! any', inputs + + super + trig: Inputs.hot trig + value: Inputs.cold value + +`Op:setup` takes a table that can have any (even nested) shape you want, as +long as all 'leaf values' are `Input` instances. The following are both valid: + + super { (Inputs.hot trig), (Inputs.cold value) } + + super + trigger:Inputs.hot trig + values: { (Inputs.cold val0), (Inputs.cold val1), (Inputs.cold val2) } #### output setup - -When `Op:setup` finishes, `@out` has to be set to a `Value` instance. The +When `Op:setup` finishes, `@out` has to be set to a `Stream` instance. The instance can be created in `Op:setup`, or by overriding the constructor and -delegating to the original one using `super`. In general this way of creating -the output value is preferred, and it is only moved to `Op:setup` if the output +delegating to the original one using `super`. In general setting it in the +constructor is preferred, and it is only moved to `Op:setup` if the output type depends on the arguments received. +There are three types of `Stream`s that can be created: + +- `ValueStream`s track *continuous values*. They can only have one value per + tick, and downstream Ops will not update when a *ValueStream* has been set + to the same value it already had. They are updated using `ValueStream:set`. +- `EventStream`s transmit *momentary events*. They can transmit multiple events + in a single tick. `EventStream`s do not keep a value set on the last tick on + the next tick. They are updated using `EventStream:add`. +- `IOStream`s are like `EventStream`s, but their `IOStream:tick` method is + polled by the event loop at the start of every tick. This gives them a chance + to effectively create changes 'out of thin air' and kickstart the execution + of the dataflow engine. All *runtime* execution is due to an `IOStream` + becoming dirty somewhere. + ### Op:tick `Op:tick` is called whenever any of the inputs are *dirty*. This is where the Op's main logic will go. Generally here it should be checked which input(s) @@ -120,10 +171,6 @@ Since it is rarely necessary to implement `Action`s, there is currently no documentation on implementing them, but the `Action` class documentation and the examples in `core/builtin.moon` should be enough to get started. -## `IO`s - -(wip) - [lua]: https://www.lua.org/ [moonscript]: http://moonscript.org/ [builtins-doc]: ../../reference/index.html#doc diff --git a/core/registry.moon b/core/registry.moon index 332c9e1..53d2d3a 100644 --- a/core/registry.moon +++ b/core/registry.moon @@ -2,8 +2,6 @@ -- `Tag` Registry. -- -- @classmod Registry - -import Value from require 'core.value' import Result from require 'core.result' import Error from require 'core.error' diff --git a/core/result.moon b/core/result.moon index 52203da..cf228ec 100644 --- a/core/result.moon +++ b/core/result.moon @@ -30,13 +30,11 @@ class Result with Result value: @value .side_inputs = @side_inputs - --- tick all IO instances that are effecting this (sub)tree. + --- tick all IOStream instances that are effecting this (sub)tree. -- should be called once per frame on the root, right before tick. tick_io: => for stream, input in pairs @side_inputs - if input.__class.__name == "IOInput" - io = input! - io\tick! + stream\tick! if input.io --- in depth-first order, tick all Ops which have dirty Inputs. -- @@ -73,9 +71,9 @@ class Result buf ..= ">" buf - --- the `Value` result + --- the `Stream` result -- - -- @tfield ?Value value + -- @tfield ?Stream value --- an Op -- @@ -85,12 +83,12 @@ class Result -- -- @tfield {}|{Result,...} children - --- cached mapping of all `Value`/`Input` pairs affecting this Result. + --- 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 {[Value]=Input,...} side_inputs + -- @tfield {[Stream]=Input,...} side_inputs --- static functions -- @section static @@ -105,17 +103,16 @@ class Result @side_inputs, is_child = {}, {} for child in *@children - for s, d in pairs child.side_inputs - @side_inputs[s] = d + 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.impure or not is_child[input.stream] + if input.io or not is_child[input.stream] @side_inputs[input.stream] = input - { :Result } diff --git a/core/scope.moon b/core/scope.moon index 37a1ba3..526ca8d 100644 --- a/core/scope.moon +++ b/core/scope.moon @@ -2,7 +2,7 @@ -- Mapping from `sym`s to `Result`s. -- -- @classmod Scope -import Value from require 'core.value' +import ValueStream from require 'core.stream' import Result from require 'core.result' import Error from require 'core.error' @@ -12,12 +12,12 @@ class Scope --- set a Lua value in the scope. -- - -- wraps `val` in a `Value` and `Result` before calling `set`. + -- wraps `val` in a `ValueStream` and `Result` before calling `set`. -- -- @tparam string key -- @tparam any val set_raw: (key, val) => - value = Value.wrap val, key + value = ValueStream.wrap val, key @values[key] = Result :value --- set a symbol to a `Result`. @@ -104,7 +104,7 @@ class Scope --- convert a Lua table to a Scope. -- -- `tbl` may contain more tables (or `Scope`s). - -- Uses `Value.wrap` on the values recursively. + -- Uses `ValueStream.wrap` on the values recursively. -- -- @tparam table tbl the table to convert -- @treturn Scope diff --git a/core/stream/base.moon b/core/stream/base.moon new file mode 100644 index 0000000..ee4ee17 --- /dev/null +++ b/core/stream/base.moon @@ -0,0 +1,79 @@ +---- +-- 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 an `Action` 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 + + __tostring: => + value = if @meta.name + @meta.name + else if 'table' == (type @value) and rawget @value, '__base' + @value.__name + else + tostring @value + "<#{@@__name} #{@type}: #{value}>" + + __inherited: (cls) => cls.__base.__tostring = @__tostring + +--- 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/core/stream/event.moon b/core/stream/event.moon new file mode 100644 index 0000000..5dbf846 --- /dev/null +++ b/core/stream/event.moon @@ -0,0 +1,92 @@ +---- +-- Stream of momentary events. +-- +-- @classmod EventStream +import Stream from require 'core.stream.base' +import Result from require 'core.result' +import Error from require 'core.error' +import scope, base, registry from require 'core.cycle' + +class EventStream extends Stream +--- members +-- @section members + + --- return whether this stream was changed in the current tick. + -- + -- @treturn bool + dirty: => @updated == registry.Registry.active!.tick + + --- 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 = registry.Registry.active!.tick + table.insert @events, {} + + --- 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: => EventStream @type + + --- alias for `unwrap`. + __call: (...) => @unwrap ... + + --- 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 an `Action` 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/core/stream/init.moon b/core/stream/init.moon new file mode 100644 index 0000000..3fff136 --- /dev/null +++ b/core/stream/init.moon @@ -0,0 +1,18 @@ +---- +-- `Stream` interface and implementations. +-- +-- @see Stream +-- @see ValueStream +-- @see EventStream +-- @see IOStream +-- +-- @module stream +import ValueStream from require 'core.stream.value' +import EventStream from require 'core.stream.event' +import IOStream from require 'core.stream.io' + +{ + :ValueStream + :EventStream + :IOStream +} diff --git a/core/stream/io.moon b/core/stream/io.moon new file mode 100644 index 0000000..fcaeb9f --- /dev/null +++ b/core/stream/io.moon @@ -0,0 +1,48 @@ +---- +-- 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 'core.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 + + --- poll for changes. + -- + -- Called every frame by the main event loop to update internal state. + tick: => + + --- check whether this adapter requires processing. + -- + -- Must return a boolean indicating whether `Op`s that refer to this instance + -- via `Input.io` should be notified (via `Op:tick`). May be called multiple + -- times. May be called before `tick` 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/core/value.moon b/core/stream/value.moon index bdc4b39..0220f06 100644 --- a/core/value.moon +++ b/core/stream/value.moon @@ -1,7 +1,10 @@ ---- --- Value(stream), implements the `AST` inteface. +-- Continuous stream of values. -- --- @classmod Value +-- Implements the `Stream` and `AST` intefaces. +-- +-- @classmod ValueStream +import Stream from require 'core.stream.base' import Result from require 'core.result' import Error from require 'core.error' import scope, base, registry from require 'core.cycle' @@ -12,18 +15,18 @@ ancestor = (klass) -> klass = klass.__parent klass -class Value +class ValueStream extends Stream --- members -- @section members - --- return whether this Value was changed in the current tick. + --- return whether this stream was changed in the current tick. -- -- @treturn bool dirty: => @updated == registry.Registry.active!.tick - --- update this Value. + --- update this stream's value. -- - -- Marks this Value as dirty for the remainder of the current tick. + -- Marks this stream as dirty for the remainder of the current tick. set: (@value) => @updated = registry.Registry.active!.tick --- unwrap to the Lua type. @@ -37,13 +40,13 @@ class Value assert type == @type, msg or "#{@} is not a #{type}" if type @value - --- create a mutable copy of this Value. + --- create a mutable copy of this stream. -- - -- Used to wrap insulate eval-cycles from each other. + -- Used to insulate eval-cycles from each other. -- - -- @treturn Value + -- @treturn ValueStream fork: => - with Value @type, @value, @raw + with ValueStream @type, @value, @raw .updated = @updated --- alias for `unwrap`. @@ -51,14 +54,10 @@ class Value --- compare two values. -- - -- Compares two `Value`s by comparing their types and their Lua 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 'table' == (type @value) and rawget @value, '__base' then @value.__name else @value - "<#{@@__name} #{@type}: #{value}>" - - --- the type name of the Value. + --- the type name of this stream. -- -- the following builtin typenames are used: -- @@ -91,7 +90,7 @@ class Value --- AST interface -- --- `Value` implements the `AST` interface. +-- `ValueStream` implements the `AST` interface. -- @section ast --- evaluate this literal constant. @@ -113,32 +112,31 @@ class Value --- quote this literal constant. -- - -- @treturn Value self + -- @treturn ValueStream self quote: => @ --- stringify this literal constant. -- -- Throws an error if `raw` is not set. -- - -- @treturn string the exact string this Value was parsed from - stringify: => assert @raw, "stringifying Value that wasn't parsed" + -- @treturn string the exact string this stream was parsed from + stringify: => assert @raw, "stringifying ValueStream that wasn't parsed" --- clone this literal constant. -- - -- @treturn Value self + -- @treturn ValueStream self clone: (prefix) => @ --- static functions -- @section static - --- construct a new Value. + --- 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) => - @meta = {} + new: (type, @value, @raw) => super type unescape = (str) -> str\gsub '\\([\'"\\])', '%1' --- create a capture-function (for parsing with Lpeg). @@ -157,7 +155,7 @@ class Value -- -- @tparam any val the value to wrap -- @tparam[opt] string name the name of this value (for error logging) - -- @treturn Value + -- @treturn ValueStream @wrap: (val, name='(unknown)') -> typ = switch type val when 'number' then 'num' @@ -175,53 +173,51 @@ class Value switch ancestor val.__class when scope.Scope then 'scope' when base.FnDef then 'fndef' - when Value - return val + when Stream then return val else error "#{name}: cannot wrap '#{val.__class.__name}' instance" else -- plain table - return Value 'scope', scope.Scope.from_table val + return ValueStream 'scope', scope.Scope.from_table val else error "#{name}: cannot wrap Lua type '#{type val}'" - Value typ, val + ValueStream typ, val --- create a constant number. -- @tparam number num the number - -- @treturn Value - @num: (num) -> Value 'num', num, tostring num + -- @treturn ValueStream + @num: (num) -> ValueStream 'num', num, tostring num --- create a constant string. -- @tparam string str the string - -- @treturn Value - @str: (str) -> Value 'str', str, "'#{str}'" + -- @treturn ValueStream + @str: (str) -> ValueStream 'str', str, "'#{str}'" --- create a constant symbol. -- @tparam string sym the symbol - -- @treturn Value - @sym: (sym) -> Value 'sym', sym, sym + -- @treturn ValueStream + @sym: (sym) -> ValueStream 'sym', sym, sym --- create a constant boolean. -- @tparam boolean bool the boolean - -- @treturn Value - @bool: (bool) -> Value 'bool', bool, tostring bool + -- @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 Value + -- @treturn ValueStream @meta: (args) -> - with Value.wrap args.value + with ValueStream.wrap args.value .meta = args.meta if args.meta -class LiteralValue extends Value +class LiteralValue extends ValueStream eval: => Result value: @ { - :Value + :ValueStream :LiteralValue - :load_ } diff --git a/spec/core/cell_spec.moon b/spec/core/cell_spec.moon index 710b252..d16c9d8 100644 --- a/spec/core/cell_spec.moon +++ b/spec/core/cell_spec.moon @@ -1,10 +1,10 @@ import Cell, RootCell from require 'core.cell' -import Value, Scope, Tag, SimpleRegistry, globals from require 'core' +import ValueStream, Scope, Tag, SimpleRegistry, globals from require 'core' import Logger from require 'logger' Logger.init 'silent' -hello_world = Cell.parse (Tag.parse '2'), { '', (Value.sym 'hello'), ' ', (Value.str 'world'), '' } -two_plus_two = Cell.parse (Tag.parse '3'), { '', (Value.sym '+'), ' ', (Value.num 2), ' ', (Value.num 2), '' } +hello_world = Cell.parse (Tag.parse '2'), { '', (ValueStream.sym 'hello'), ' ', (ValueStream.str 'world'), '' } +two_plus_two = Cell.parse (Tag.parse '3'), { '', (ValueStream.sym '+'), ' ', (ValueStream.num 2), ' ', (ValueStream.num 2), '' } reg = SimpleRegistry! setup -> reg\grab! @@ -15,8 +15,8 @@ describe 'Cell', -> with hello_world\quote! it 'stays equal', -> assert.is.equal Cell, .__class - assert.is.equal (Value.sym 'hello'), \head! - assert.is.same { Value.str 'world' }, \tail! + assert.is.equal (ValueStream.sym 'hello'), \head! + assert.is.same { ValueStream.str 'world' }, \tail! it 'shares the tag', -> assert.is.equal hello_world.tag, .tag @@ -24,8 +24,8 @@ describe 'Cell', -> with two_plus_two\quote! it 'stays equal', -> assert.is.equal Cell, .__class - assert.is.equal (Value.sym '+'), \head! - assert.is.same { (Value.num 2), (Value.num 2) }, \tail! + assert.is.equal (ValueStream.sym '+'), \head! + assert.is.same { (ValueStream.num 2), (ValueStream.num 2) }, \tail! it 'shares the tag', -> assert.is.equal two_plus_two.tag, .tag @@ -35,8 +35,8 @@ describe 'Cell', -> with hello_world\clone parent it 'keeps children', -> assert.is.equal Cell, .__class - assert.is.equal (Value.sym 'hello'), \head! - assert.is.same { Value.str 'world' }, \tail! + assert.is.equal (ValueStream.sym 'hello'), \head! + assert.is.same { ValueStream.str 'world' }, \tail! it 'clones the tag', -> assert.is.equal hello_world.tag, .tag.original @@ -48,8 +48,8 @@ describe 'Cell', -> assert.has.error -> cell\eval globals it 'evaluates its head', -> - head = Value.sym 'trace' - cell = Cell.parse { '', head, ' ', (Value.sym 'true'), '' } + head = ValueStream.sym 'trace' + cell = Cell.parse { '', head, ' ', (ValueStream.sym 'true'), '' } s = spy.on head, 'eval' cell\eval globals @@ -62,10 +62,10 @@ describe 'RootCell', -> test 'head is always "do"', -> cell = Cell.parse_root {} - assert.is.equal (Value.sym 'do'), cell\head! + assert.is.equal (ValueStream.sym 'do'), cell\head! cell = RootCell nil, { hello_world, two_plus_two } - assert.is.equal (Value.sym 'do'), cell\head! + assert.is.equal (ValueStream.sym 'do'), cell\head! test 'tail is all children', -> cell = Cell.parse_root {} diff --git a/spec/core/input_spec.moon b/spec/core/input_spec.moon index e2ec7e3..4b470db 100644 --- a/spec/core/input_spec.moon +++ b/spec/core/input_spec.moon @@ -1,4 +1,4 @@ -import Input, Result, Value, IO from require 'core.base' +import Input, Result, ValueStream, EventStream, IOStream from require 'core.base' import SimpleRegistry from require 'core' import Logger from require 'logger' Logger.init 'silent' @@ -7,198 +7,144 @@ reg = SimpleRegistry! setup -> reg\grab! teardown -> reg\release! -describe 'Input.event', -> - val = Value.num 1 - input = Input.event val - - describe 'at evaltime', -> - it 'follows Value when new', -> - input\setup nil - - val\set 2 - assert.is.true val\dirty! - assert.is.true input\dirty! - - reg\next_tick! - assert.is.false val\dirty! - assert.is.false input\dirty! - - input\finish_setup! - - it 'follows Value when different', -> - new_input = Input.event Value.num 3 - new_input\setup input - - assert.is.false new_input.stream\dirty! - assert.is.false new_input\dirty! - - new_input.stream\set 3 - assert.is.true new_input.stream\dirty! - assert.is.true new_input\dirty! - - reg\next_tick! - new_input\finish_setup! - - it 'follows Value when equal', -> - new_input = Input.event Value.num 2 - new_input\setup input - - assert.is.false new_input.stream\dirty! - assert.is.false new_input\dirty! - - new_input.stream\set 2 - assert.is.true new_input.stream\dirty! - assert.is.true new_input\dirty! - - describe 'at runtime', -> - it 'is dirty when the value is dirty', -> - val\set 3 - assert.is.true val\dirty! - assert.is.true input\dirty! - - reg\next_tick! - assert.is.false val\dirty! - assert.is.false input\dirty! - - it 'unwraps to the lua value', -> - assert.is.equal 3, input\unwrap! - assert.is.equal 3, input! - - it 'gives access to the type string', -> - assert.is.equal 'num', input\type! - - it 'gives access to the Value', -> - assert.is.equal val, input.stream - -describe 'Input.value', -> - val = Value.num 1 - local input - - describe 'at evaltime', -> - it 'is dirty when new', -> - assert.is.false val\dirty! - - input = Input.value val - input\setup nil - assert.is.true input\dirty! - input\finish_setup! - - it 'is dirty when different', -> - newval = Value.num 2 - - assert.is.false newval\dirty! - newinput = Input.value newval - newinput\setup input - assert.is.true newinput\dirty! - newinput\finish_setup! - - it 'is not dirty when equal', -> - newval = Value.num 1 - newval\set 1 - - assert.is.true newval\dirty! - newinput = Input.value newval - newinput\setup input - assert.is.false newinput\dirty! - newinput\finish_setup! - - describe 'at runtime', -> - it 'is dirty when the value is dirty', -> - val\set 3 - assert.is.true val\dirty! - assert.is.true input\dirty! +class MyIO extends IOStream + new: => super 'my-io' + dirty: => @is_dirty - reg\next_tick! - assert.is.false val\dirty! - assert.is.false input\dirty! +basic_tests = (stream, input) -> + it 'gives access to the Stream', -> + assert.is.equal stream, input.stream - it 'unwraps to the lua value', -> - assert.is.equal 3, input\unwrap! - assert.is.equal 3, input! + it 'forwards :unwrap', -> + assert.is.same stream\unwrap!, input\unwrap! + assert.is.same stream\unwrap!, input! it 'gives access to the type string', -> - assert.is.equal 'num', input\type! - - it 'gives access to the Value', -> - assert.is.equal val, input.stream + assert.is.equal stream.type, input\type! describe 'Input.cold', -> - val = Value.num 1 - input = Input.cold val + stream = ValueStream.num 1 + input = Input.cold stream + + basic_tests stream, input it 'is never dirty', -> assert.is.false input\dirty! - val\set 2 + stream\set 2 assert.is.false input\dirty! input\setup nil assert.is.false input\dirty! input\finish_setup! - new_input = Input.cold Value.num 3 + new_input = Input.cold ValueStream.num 3 new_input\setup input assert.is.false new_input\dirty! new_input.stream\set 4 assert.is.false new_input\dirty! input\finish_setup! - it 'unwraps to the lua value', -> - assert.is.equal 2, input\unwrap! - assert.is.equal 2, input! +describe 'Input.hot', -> + describe 'with EventStream', -> + stream = EventStream 'num' + input = Input.hot stream - it 'gives access to the type string', -> - assert.is.equal 'num', input\type! + basic_tests stream, input - it 'gives access to the Value', -> - assert.is.equal val, input.stream + it 'is marked for lifting', -> + assert.is.nil input.io -class MyIO extends IO - dirty: => @is_dirty + it 'is dirty when the EventStream is dirty', -> + assert.is.false input\dirty! + assert.is.false stream\dirty! -describe 'Input.io', -> - io = MyIO! - val = Value 'test-io', io - input = Input.io val + input\setup nil + assert.is.false input\dirty! + input\finish_setup! - it 'is dirty when the IO is dirty', -> - io.is_dirty = false + reg\next_tick! + stream\add 1 - assert.is.false val\dirty! - assert.is.false input\dirty! - assert.is.false io\dirty! + assert.is.true input\dirty! + assert.is.true stream\dirty! - input\setup nil - assert.is.false input\dirty! - input\finish_setup! + input\setup nil + assert.is.true input\dirty! + input\finish_setup! - val\set io - assert.is.true val\dirty! - assert.is.false input\dirty! - assert.is.false io\dirty! + assert.is.true input\dirty! + assert.is.true stream\dirty! - reg\next_tick! + describe 'with IOStream', -> + stream = MyIO! + input = Input.hot stream - io.is_dirty = true + basic_tests stream, input - assert.is.false val\dirty! - assert.is.true input\dirty! - assert.is.true io\dirty! + it 'is marked for lifting', -> + assert.is.true input.io - input\setup nil - assert.is.true input\dirty! - input\finish_setup! + it 'is dirty when the IOStream is dirty', -> + stream.is_dirty = false - val\set io - assert.is.true val\dirty! - assert.is.true input\dirty! - assert.is.true io\dirty! + assert.is.false input\dirty! + assert.is.false stream\dirty! - it 'unwraps to the io value', -> - assert.is.equal io, input\unwrap! - assert.is.equal io, input! + input\setup nil + assert.is.false input\dirty! + input\finish_setup! - it 'gives access to the type string', -> - assert.is.equal 'test-io', input\type! + reg\next_tick! + stream.is_dirty = true + + assert.is.true input\dirty! + assert.is.true stream\dirty! + + input\setup nil + assert.is.true input\dirty! + input\finish_setup! - it 'gives access to the Value', -> - assert.is.equal val, input.stream + assert.is.true input\dirty! + assert.is.true stream\dirty! + + describe 'with ValueStream', -> + stream = ValueStream.num 1 + local input + + describe 'at evaltime', -> + it 'is dirty when new', -> + assert.is.false stream\dirty! + + input = Input.hot stream + input\setup nil + assert.is.true input\dirty! + input\finish_setup! + + it 'is dirty when different', -> + newval = ValueStream.num 2 + + assert.is.false newval\dirty! + newinput = Input.hot newval + newinput\setup input + assert.is.true newinput\dirty! + newinput\finish_setup! + + it 'is not dirty when equal', -> + newval = ValueStream.num 1 + newval\set 1 + + assert.is.true newval\dirty! + newinput = Input.hot newval + newinput\setup input + assert.is.false newinput\dirty! + newinput\finish_setup! + + describe 'at runtime', -> + it 'is dirty when the stream is dirty', -> + stream\set 3 + assert.is.true stream\dirty! + assert.is.true input\dirty! + + reg\next_tick! + assert.is.false stream\dirty! + assert.is.false input\dirty! diff --git a/spec/core/parsing_spec.moon b/spec/core/parsing_spec.moon index 9958e22..32d8530 100644 --- a/spec/core/parsing_spec.moon +++ b/spec/core/parsing_spec.moon @@ -1,6 +1,6 @@ import space, atom, expr, explist, cell, program, comment from require 'core.parsing' -import Value from require 'core' +import ValueStream from require 'core' import Logger from require 'logger' Logger.init 'silent' @@ -66,9 +66,9 @@ describe 'Cell', -> "friend" )' assert.is.equal 3, #node.children - assert.is.equal (Value.num 3), node.children[1] - assert.is.equal (Value.sym 'ok-yes'), node.children[2] - assert.is.equal (Value.str 'friend'), node.children[3] + 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] test 'tag parsing', -> node = verify_parse cell, '([42]tagged 2)' @@ -89,8 +89,8 @@ describe 'RootCell parsing', -> node = verify_parse program, str assert.is.equal 2, #node.children - assert.is.equal (Value.num 3), node.children[1] - assert.is.equal (Value.sym 'ok-yes'), node.children[2] + assert.is.equal (ValueStream.num 3), node.children[1] + assert.is.equal (ValueStream.sym 'ok-yes'), node.children[2] it 'at the front of the string', -> verify ' 3\tok-yes' diff --git a/spec/core/pattern_spec.moon b/spec/core/pattern_spec.moon index 4825dcc..523ce86 100644 --- a/spec/core/pattern_spec.moon +++ b/spec/core/pattern_spec.moon @@ -1,5 +1,5 @@ import Pattern, match from require 'core.base.match' -import Result, Value from require 'core' +import Result, ValueStream from require 'core' -- wrap in non-const result wrap = (value) -> @@ -10,11 +10,11 @@ wrap = (value) -> wrap_const = (value) -> Result :value describe 'Type Pattern', -> - num = wrap Value.num 1 - str = wrap Value.str 'hello' - special = wrap Value 'midi/sysex-message' - c_num = wrap_const Value.num 1 - c_str = wrap_const Value.str 'hello' + num = wrap ValueStream.num 1 + str = wrap ValueStream.str 'hello' + special = wrap ValueStream 'midi/sysex-message' + c_num = wrap_const ValueStream.num 1 + c_str = wrap_const ValueStream.str 'hello' describe 'simple types', -> it 'matches self type', -> @@ -151,10 +151,10 @@ describe 'Type Pattern', -> assert.is.same {str}, stream describe 'match', -> - num = wrap Value.num 1 - str = wrap Value.str 'hello' - c_num = wrap_const Value.num 1 - c_str = wrap_const Value.str 'hello' + num = wrap ValueStream.num 1 + str = wrap ValueStream.str 'hello' + c_num = wrap_const ValueStream.num 1 + c_str = wrap_const ValueStream.str 'hello' it 'matches lists', -> assert.is.same {num, num, str}, match 'num num str', {num, num, str} diff --git a/spec/core/result_spec.moon b/spec/core/result_spec.moon index cc7cd71..017be58 100644 --- a/spec/core/result_spec.moon +++ b/spec/core/result_spec.moon @@ -1,5 +1,5 @@ -import Result, Value, Scope, SimpleRegistry from require 'core' -import Input, Op, IO from require 'core.base' +import Result, Scope, SimpleRegistry from require 'core' +import Input, Op, ValueStream, EventStream, IOStream from require 'core.base' import Logger from require 'logger' Logger.init 'silent' @@ -15,13 +15,13 @@ reg = SimpleRegistry! setup -> reg\grab! teardown -> reg\release! -class DirtyIO extends IO - tick: => +class DirtyIO extends IOStream + new: => super 'dirty-io' dirty: => true describe 'Result', -> it 'wraps value, children', -> - value = Value.num 3 + value = ValueStream.num 3 a = Result! b = Result! @@ -33,14 +33,14 @@ describe 'Result', -> assert.is.same children, result.children it ':type gets type and assets value', -> - result = Result value: Value.num 2 + result = Result value: ValueStream.num 2 assert.is.equal 'num', result\type! result = Result! assert.has.error -> result\type! it ':is_const', -> - value = Value.num 2 + value = ValueStream.num 2 pure = Result :value impure = result_with_sideinput value, {} @@ -52,8 +52,8 @@ describe 'Result', -> assert.has.error (-> impure\const 'test'), 'test' it ':make_ref', -> - value = Value.num 2 - input = Input.value value + value = ValueStream.num 2 + input = Input.hot value op = op_with_inputs { input } thick = Result :value, :op, children: { Result!, Result! } ref = thick\make_ref! @@ -65,11 +65,11 @@ describe 'Result', -> assert.is.nil ref.op it 'lifts up inputs from op', -> - event = Value 'bang', false - event_input = Input.event event + event = ValueStream 'bang', false + event_input = Input.hot event - value = Value 'num', 4 - value_input = Input.value value + value = ValueStream 'num', 4 + value_input = Input.hot value op = op_with_inputs { event_input, value_input } result = Result op: op, :value @@ -79,11 +79,11 @@ describe 'Result', -> result.side_inputs it 'does not lift up op inputs that are also child values', -> - event = Value 'bang', false - event_input = Input.event event + event = ValueStream 'bang', false + event_input = Input.hot event - value = Value 'num', 4 - value_input = Input.value value + 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 } @@ -91,13 +91,13 @@ describe 'Result', -> assert.is.same { [event]: event_input }, result.side_inputs it 'lifts up side_inputs from children', -> - event_value = Value 'bang', false - event_input = Input.event event_value + 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 = Value 'num', 4 - value_input = Input.value value_value + 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 @@ -109,16 +109,16 @@ describe 'Result', -> local a_value, a_child, a_input local b_value, b_child, b_input before_each -> - a_value = Value 'num' - a_input = Input.event a_value + a_value = EventStream 'num' + a_input = Input.hot a_value a_child = result_with_sideinput a_value, a_input - b_value = Value 'num' - b_input = Input.event b_value + 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\set 1 + a_value\add 1 assert.is.true a_input\dirty! assert.is.false b_input\dirty! @@ -145,11 +145,11 @@ describe 'Result', -> assert.spy(b).was_not_called! it 'updates op when any op-inputs are dirty', -> - a_value\set 1 + a_value\add 1 assert.is.true a_input\dirty! assert.is.false b_input\dirty! - op = op_with_inputs a: Input.event a_value + op = op_with_inputs a: Input.hot a_value s = spy.on op, 'tick' result = Result :op, children: { a_child, b_child } @@ -158,11 +158,11 @@ describe 'Result', -> assert.spy(s).was_called_with match.ref op it 'early-outs when no op-inputs are dirty', -> - a_value\set 1 + a_value\add 1 assert.is.true a_input\dirty! assert.is.false b_input\dirty! - op = op_with_inputs { Input.event b_value } + op = op_with_inputs { Input.hot b_value } s = spy.on op, 'tick' result = Result :op, children: { a_child, b_child } @@ -173,13 +173,12 @@ describe 'Result', -> describe ':tick_io', -> it 'ticks IOs referenced in side_inputs', -> io = DirtyIO! - value = Value 'an_io', io - input = Input.io value + input = Input.hot io op = op_with_inputs { input } result = Result :op s = spy.on io, 'tick' - assert.is.same { [value]: input }, result.side_inputs + assert.is.same { [io]: input }, result.side_inputs result\tick_io! assert.spy(s).was_called_with match.ref io diff --git a/spec/core/scope_spec.moon b/spec/core/scope_spec.moon index 6d7d754..59c6bd1 100644 --- a/spec/core/scope_spec.moon +++ b/spec/core/scope_spec.moon @@ -1,4 +1,4 @@ -import Scope, Value, Result from require 'core' +import Scope, ValueStream, Result from require 'core' import Op from require 'core.base' import Logger from require 'logger' Logger.init 'silent' @@ -27,7 +27,7 @@ describe 'Scope', -> assert.is.equal "im a happy string", got.value test 'Values', -> - pi = Value 'num', 3.14 + pi = ValueStream 'num', 3.14 scope\set_raw 'pi', pi assert.is.equal pi, (scope\get 'pi')\const! @@ -48,7 +48,7 @@ describe 'Scope', -> assert.is.equal sub, got.value test 'tables', -> - pi = Value 'num', 3.14 + pi = ValueStream 'num', 3.14 scope\set_raw 'math', { :pi } got = (scope\get 'math')\const! @@ -58,7 +58,7 @@ describe 'Scope', -> assert.is.equal pi, (scope\get 'math/pi')\const! it 'wraps Values in from_table', -> - pi = Value 'num', 3.14 + pi = ValueStream 'num', 3.14 scope = Scope.from_table { num: 3 str: "im a happy string" @@ -90,7 +90,7 @@ describe 'Scope', -> a = Scope! b = Scope! - pi = Value 'num', 3.14 + pi = ValueStream 'num', 3.14 b\set_raw 'test', pi a\set_raw 'child', b root\set_raw 'deep', a @@ -98,8 +98,8 @@ describe 'Scope', -> assert.is.equal pi, (root\get 'deep/child/test')\const! describe 'can set symbols', -> - one = wrap_res Value.num 1 - two = wrap_res Value.num 2 + one = wrap_res ValueStream.num 1 + two = wrap_res ValueStream.num 2 scope = Scope! it 'disallows re-setting symbols', -> diff --git a/spec/core/value_spec.moon b/spec/core/value_spec.moon index eeb3339..b447d32 100644 --- a/spec/core/value_spec.moon +++ b/spec/core/value_spec.moon @@ -1,4 +1,4 @@ -import Value, Result, Scope, SimpleRegistry from require 'core' +import ValueStream, Result, Scope, SimpleRegistry from require 'core' import Op, Action from require 'core.base' import Logger from require 'logger' Logger.init 'silent' @@ -13,101 +13,101 @@ reg = SimpleRegistry! setup -> reg\grab! teardown -> reg\release! -describe 'Value', -> +describe 'ValueStream', -> describe '.wrap', -> it 'wraps numbers', -> - got = Value.wrap 3 + got = ValueStream.wrap 3 assert.is.equal 'num', got.type assert.is.equal 3, got.value it 'wraps strings', -> - got = Value.wrap "im a happy string" + 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 = Value 'num', 3.14 - got = Value.wrap pi + pi = ValueStream 'num', 3.14 + got = ValueStream.wrap pi assert.is.equal pi, got it 'wraps Opdefs', -> - got = Value.wrap TestOp + got = ValueStream.wrap TestOp assert.is.equal 'opdef', got.type assert.is.equal TestOp, got.value it 'wraps Bultins', -> - got = Value.wrap TestAction + got = ValueStream.wrap TestAction assert.is.equal 'builtin', got.type assert.is.equal TestAction, got.value it 'wraps Scopes', -> sub = Scope! - got = Value.wrap sub + got = ValueStream.wrap sub assert.is.equal 'scope', got.type assert.is.equal sub, got.value it 'wraps tables', -> - pi = Value 'num', 3.14 - got = Value.wrap { :pi } + 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, (Value.num 3.14)\unwrap! - assert.is.equal 'hi', (Value.str 'hi')\unwrap! - assert.is.equal 'hi', (Value.sym 'hi')\unwrap! + 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, (Value.num 3.14)\unwrap 'num' - assert.is.equal 'hi', (Value.str 'hi')\unwrap 'str' - assert.is.equal 'hi', (Value.sym 'hi')\unwrap 'sym' - assert.has_error -> (Value.num 3.14)\unwrap 'sym' - assert.has_error -> (Value.str 'hi')\unwrap 'num' - assert.has_error -> (Value.sym 'hi')\unwrap 'str' + 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, (Value.num 3.14)! - assert.is.equal 'hi', (Value.str 'hi')! - assert.is.equal 'hi', (Value.sym 'hi')! - assert.is.equal 3.14, (Value.num 3.14) 'num' - assert.is.equal 'hi', (Value.str 'hi') 'str' - assert.is.equal 'hi', (Value.sym 'hi') 'sym' - assert.has_error -> (Value.num 3.14) 'sym' - assert.has_error -> (Value.str 'hi') 'num' - assert.has_error -> (Value.sym 'hi') 'str' + 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 = Value 'num', 3 - assert.is.equal (Value.num 3), val - assert.not.equal (Value.str '3'), val + val = ValueStream 'num', 3 + assert.is.equal (ValueStream.num 3), val + assert.not.equal (ValueStream.str '3'), val - val = Value 'str', 'hello' - assert.is.equal (Value.str 'hello'), val - assert.not.equal (Value.sym 'hello'), val + val = ValueStream 'str', 'hello' + assert.is.equal (ValueStream.str 'hello'), val + assert.not.equal (ValueStream.sym 'hello'), val it 'compares the value', -> - val = Value 'num', 3 - assert.is.equal (Value.num 3), val - assert.not.equal (Value.num 4), val + 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 = Value 'num', 3 - assert.is.equal (Value.num 3), val + val = ValueStream 'num', 3 + assert.is.equal (ValueStream.num 3), val val\set 4 - assert.is.equal (Value.num 4), val - assert.not.equal (Value.num 3), val + assert.is.equal (ValueStream.num 4), val + assert.not.equal (ValueStream.num 3), val it 'marks the value dirty', -> - val = Value 'num', 3 + val = ValueStream 'num', 3 assert.is.false val\dirty! val\set 4 @@ -118,42 +118,42 @@ describe 'Value', -> assert_noop = (val) -> assert.is.equal val, val\eval!\const! - assert_noop Value.num 2 - assert_noop Value.str 'hello' + assert_noop ValueStream.num 2 + assert_noop ValueStream.str 'hello' it 'looks up symbols in the scope', -> scope = with Scope! - \set 'number', Result value: Value.num 3 - \set 'hello', Result value: Value.str "world" - \set 'goodbye', Result value: Value.sym "again" + \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 = Value.sym sym + const = ValueStream.sym sym assert.is.equal val, (const\eval scope)\const! - assert_eval 'number', Value.num 3 - assert_eval 'hello', Value.str "world" - assert_eval 'goodbye', Value.sym "again" + assert_eval 'number', ValueStream.num 3 + assert_eval 'hello', ValueStream.str "world" + assert_eval 'goodbye', ValueStream.sym "again" it ':quote s literals as themselves', -> assert_noop = (val) -> assert.is.equal val, val\quote! - assert_noop Value.num 2 - assert_noop Value.str 'hello' - assert_noop Value.sym 'world' + assert_noop ValueStream.num 2 + assert_noop ValueStream.str 'hello' + assert_noop ValueStream.sym 'world' it ':clone sliterals as themselves', -> assert_noop = (val) -> assert.is.equal val, val\clone! - assert_noop Value.num 2 - assert_noop Value.str 'hello' - assert_noop Value.sym 'world' + assert_noop ValueStream.num 2 + assert_noop ValueStream.str 'hello' + assert_noop ValueStream.sym 'world' describe ':fork', -> it 'is equal to the original', -> - a = Value.num 2 - b = Value.str 'asdf' - c = with Value 'weird', {}, '(raw)' + a = ValueStream.num 2 + b = ValueStream.str 'asdf' + c = with ValueStream 'weird', {}, '(raw)' \set {} aa, bb, cc = a\fork!, b\fork!, c\fork! @@ -168,8 +168,8 @@ describe 'Value', -> assert.is.equal c.raw, cc.raw it 'isolates the original from the fork', -> - a = Value.num 3 - b = with Value 'weird', {}, '(raw)' + a = ValueStream.num 3 + b = with ValueStream 'weird', {}, '(raw)' \set {} aa, bb = a\fork!, b\fork! |
