diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-02-19 21:17:46 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2020-02-19 21:17:53 +0000 |
| commit | e62ef99244bb3aa906810a5cfd53c0dac68b0f37 (patch) | |
| tree | e7379e2add7014e7124f678f4a7a8edc699fe914 /core | |
| parent | major refactoring: Const, Stream + ResultNode (diff) | |
| download | alive-e62ef99244bb3aa906810a5cfd53c0dac68b0f37.tar.gz alive-e62ef99244bb3aa906810a5cfd53c0dac68b0f37.zip | |
merge Const and Stream into Value; Dataflow logic
lib not fully ported / functonial
Diffstat (limited to 'core')
| -rw-r--r-- | core/base.moon | 37 | ||||
| -rw-r--r-- | core/builtin.moon | 36 | ||||
| -rw-r--r-- | core/cell.moon | 10 | ||||
| -rw-r--r-- | core/init.moon | 6 | ||||
| -rw-r--r-- | core/invoke.moon | 13 | ||||
| -rw-r--r-- | core/parsing.moon | 10 | ||||
| -rw-r--r-- | core/registry.moon | 58 | ||||
| -rw-r--r-- | core/scope.moon | 17 | ||||
| -rw-r--r-- | core/value.moon | 154 |
9 files changed, 206 insertions, 135 deletions
diff --git a/core/base.moon b/core/base.moon index 1b1be5d..60ee460 100644 --- a/core/base.moon +++ b/core/base.moon @@ -1,21 +1,47 @@ -- base definitions for extensions +import Value from require 'core.value' + +unpack or= table.unpack -- a persistent expression Operator -- -- accepts Const or Stream inputs and produces a Stream output class Op - update: (dt) => + new: (type, init) => + @impulses = {} + + if type + @out = Value type, init + + -- (re)-initialize this Op with the given inputs + -- after this method finishes, :tick(true) is called once, after which + -- @impulses and @out have to be set and may not change until :setup() + -- is called again. + setup: (@inputs) => - -- set @out to a Value (Const or Stream) - setup: (...) => + -- called once per frame if any inputs or impulses are dirty, and once + -- immediately after :setup(). 'first' will be true in the latter case. + -- Should update @out. + tick: (first) => + -- called once when the Op is destroyed destroy: => +-- utilities + unwrap_inputs: => + unpack [input! for input in *@inputs] + + assert_types: (...) => + num = select '#', ... + assert #@inputs >= num, "argument count mismatch" + for i = 1, num + expect = select i, ... + assert @inputs[i].type == expect, "expected argument #{i} of #{@} to be of type #{expect} but found #{@inputs[i]}" + -- static __tostring: => "<op: #{@@__name}>" __inherited: (cls) => cls.__base.__tostring = @__tostring - -- a builtin / special form / cell-evaluation strategy -- -- responsible for quoting/evaluating subexpressions, @@ -51,7 +77,7 @@ class Action -- static -- find & patch the action for the expression with Tag 'tag' if it exists, -- and is compatible with the new Cell contents, otherwise instantiate it. - -- register the action with the tag, evaluate it and return the ResultNode + -- register the action with the tag, evaluate it and return the Result @eval_cell: (scope, tag, head, tail) => last = tag\last! compatible = last and @@ -93,6 +119,7 @@ class FnDef "(fn (#{table.concat [p\stringify! for p in *@params], ' '}) ...)" { + :Dispatcher :Op :Action :FnDef diff --git a/core/builtin.moon b/core/builtin.moon index 71beac3..6abcbd9 100644 --- a/core/builtin.moon +++ b/core/builtin.moon @@ -1,6 +1,6 @@ -- builtin special forms import Action, FnDef from require 'core.base' -import ResultNode, Value, Const from require 'core.value' +import Result, Value from require 'core.value' import Cell from require 'core.cell' import Scope from require 'core.scope' @@ -13,7 +13,7 @@ prints the docstring for sym in the console" assert #tail == 1, "'doc' takes exactly one parameter" def = L\push tail[1]\eval, scope - with ResultNode children: { def } + with Result children: { def } def = def.value\const!\unwrap! L\print "(doc #{tail[1]\stringify!}):\n#{def.doc}\n" @@ -35,9 +35,9 @@ updates all val-exprs." name = (name\quote scope)\unwrap 'sym' with val_expr\eval scope - scope\set name, .value + scope\set name, \make_ref! - ResultNode :children + Result :children class use extends Action @doc: "(use scope1 [scope2]...) - merge scopes into parent scope @@ -49,10 +49,10 @@ all scopes have to be eval-time constants." L\trace "evaling #{@}" for child in *tail result = L\push child\eval, scope - value = result\value_only!\const! + value = result\const! scope\use value\unwrap 'scope', "'use' only works on scopes" - ResultNode! + Result! class require_ extends Action @doc: "(require name-str) - require a module @@ -65,10 +65,10 @@ name-str has to be an eval-time constant." assert #tail == 1, "'require' takes exactly one parameter" result = L\push tail[1]\eval, scope - name = result\value_only!\const! + name = result\const! L\trace @, "loading module #{name}" - ResultNode value: Value.wrap require "lib.#{name\unwrap 'str'}" + Result value: Value.wrap require "lib.#{name\unwrap 'str'}" class import_ extends Action @doc: "(import sym1 [sym2]...) - require and define modules @@ -81,9 +81,9 @@ requires modules sym1, sym2, ... and defines them as sym1, sym2, ... in the curr for child in *tail name = (child\quote scope)\unwrap 'sym' - scope\set name, Value.wrap require "lib.#{name}" + scope\set name, Result value: Value.wrap require "lib.#{name}" - ResultNode! + Result! class import_star extends Action @doc: "(import* sym1 [sym2]...) - require and use modules @@ -99,7 +99,7 @@ requires modules sym1, sym2, ... and merges them into the current scope" name = (child\quote scope)\unwrap 'sym' scope\use (Value.wrap require "lib.#{name}")\unwrap 'scope' - ResultNode! + Result! class fn extends Action @doc: "(fn (p1 [p2]...) body-expr) - declare a (lambda) function @@ -117,7 +117,7 @@ the symbols p1, p2, ... will resolve to the arguments passed to the function." param\quote scope body = body\quote scope - ResultNode value: Value.wrap FnDef param_symbols, body, scope + Result value: Value.wrap FnDef param_symbols, body, scope class defn extends Action @doc: "(defn name-sym (p1 [p2]...) body-expr) - define a function @@ -138,8 +138,8 @@ declares a lambda (see (doc fn)) and defines it in the current scope" body = body\quote scope fn = FnDef param_symbols, body, scope - scope\set name, Value.wrap fn - ResultNode! + scope\set name, Result value: Value.wrap fn + Result! class do_expr extends Action @doc: "(do expr1 [expr2]...) - update multiple expressions @@ -148,7 +148,7 @@ evaluates and continously updates expr1, expr2, ... the last expression's value is returned." eval: (scope, tail) => - ResultNode children: [expr\eval scope for expr in *tail] + Result children: [expr\eval scope for expr in *tail] class if_ extends Action @doc: "(if bool then-expr [else-xpr]) - make an eval-time const choice @@ -164,7 +164,7 @@ to then-expr, otherwise it is equivalent to else-xpr if given, or nil otherwise. { xif, xthen, xelse } = tail xif = L\push xif\eval, scope - xif = xif\value_only!\const!\unwrap! + xif = xif\const!\unwrap! if xif xthen\eval scope @@ -189,8 +189,8 @@ class trace extends Action import: import_ 'import*': import_star - true: Const.bool true - false: Const.bool false + true: Value.bool true + false: Value.bool false :fn, :defn 'do': do_expr diff --git a/core/cell.moon b/core/cell.moon index 642e1a6..0cb8e9f 100644 --- a/core/cell.moon +++ b/core/cell.moon @@ -1,5 +1,5 @@ -- ALV Cell type -import Const from require 'core.value' +import Value from require 'core.value' import op_invoke, fn_invoke from require 'core.invoke' import Tag from require 'core.tag' @@ -11,7 +11,8 @@ class Cell -- white: optional sequence of whitespace segments ([0 .. #@children]) new: (@tag=Tag.blank!, @children, @white) => if not @white - @white = ['' for i=1,#@children+1] + @white = ['' for i=1,#@children] + @white[0] = '' assert #@white == #@children, "mismatched whitespace length" @@ -22,8 +23,7 @@ class Cell -- AST interface eval: (scope) => - head_result = @head!\eval scope - head = head_result.value\const! + head = (@head!\eval scope)\const! Action = switch head.type when 'opdef' -- scope\get 'op-invoke' @@ -89,7 +89,7 @@ class Cell -- -- evaluates with an implicit 'do' in the head class RootCell extends Cell - head: => Const.sym 'do' + head: => Value.sym 'do' tail: => @children stringify: => diff --git a/core/init.moon b/core/init.moon index b885747..a309722 100644 --- a/core/init.moon +++ b/core/init.moon @@ -2,7 +2,7 @@ L or= setmetatable {}, __index: => -> import Op, Action, FnDef from require 'core.base' -import Stream, Const, load_ from require 'core.value' +import Value, Result, load_ from require 'core.value' import Scope from require 'core.scope' load_! @@ -15,7 +15,7 @@ import cell, program from require 'core.parsing' globals = Scope.from_table require 'core.builtin' { - :Stream, :Const + :Value, :Result :Cell, :RootCell :Op, :Action, :FnDef :Scope @@ -30,5 +30,5 @@ globals = Scope.from_table require 'core.builtin' ast = assert (cell\match str), "failed to parse: #{str}" result = ast\eval scope - result\value_only! + result\const! } diff --git a/core/invoke.moon b/core/invoke.moon index 5a6c420..a3932f8 100644 --- a/core/invoke.moon +++ b/core/invoke.moon @@ -1,4 +1,4 @@ -import ResultNode, Value from require 'core.value' +import Result, Value from require 'core.value' import Action from require 'core.base' import Scope from require 'core.scope' @@ -8,16 +8,17 @@ class op_invoke extends Action @op\destroy! if @op - def = head\const!\unwrap 'opdef', "cant op-invoke #{@head}" + def = head\unwrap 'opdef', "cant op-invoke #{@head}" @head, @op = head, def! true eval: (scope, tail) => children = L\push -> [L\push expr\eval, scope for expr in *tail] - value = @op\setup unpack [child.value for child in *children] + @op\setup [child.value for child in *children] + @op\tick true - ResultNode :children, :value, op: @op + Result :children, value: @op.out, op: @op class fn_invoke extends Action -- @TODO: @@ -32,7 +33,7 @@ class fn_invoke extends Action true eval: (outer_scope, tail) => - { :params, :body, :scope } = @head\const!\unwrap 'fndef', "cant fn-invoke #{@head}" + { :params, :body, :scope } = @head\unwrap 'fndef', "cant fn-invoke #{@head}" assert #params == #tail, "argument count mismatch in #{@head}" @@ -47,7 +48,7 @@ class fn_invoke extends Action result = body\eval fn_scope table.insert children, result - ResultNode :children, value: result.value + Result :children, value: result.value { :op_invoke, :fn_invoke diff --git a/core/parsing.moon b/core/parsing.moon index 96d8125..03ef85c 100644 --- a/core/parsing.moon +++ b/core/parsing.moon @@ -1,4 +1,4 @@ -import Const from require 'core.value' +import Value from require 'core.value' import Cell, RootCell from require 'core.cell' import Tag from require 'core.tag' import R, S, P, V, C, Ct from require 'lpeg' @@ -16,15 +16,15 @@ mspace = (comment + wc)^0 / 1 -- optional whitespace -- atoms digit = R '09' first = (R 'az', 'AZ') + S '-_+*/.!?=' -sym = first * (first + digit)^0 / Const\parse 'sym' +sym = first * (first + digit)^0 / Value\parse 'sym' -strd = '"' * (C ((P '\\"') + (P '\\\\') + (1 - P '"'))^0) * '"' / Const\parse 'str', '\"' -strq = "'" * (C ((P "\\'") + (P '\\\\') + (1 - P "'"))^0) * "'" / Const\parse 'str', '\'' +strd = '"' * (C ((P '\\"') + (P '\\\\') + (1 - P '"'))^0) * '"' / Value\parse 'str', '\"' +strq = "'" * (C ((P "\\'") + (P '\\\\') + (1 - P "'"))^0) * "'" / Value\parse 'str', '\'' str = strd + strq int = digit^1 float = (digit^1 * '.' * digit^0) + (digit^0 * '.' * digit^1) -num = (float + int) / Const\parse 'num' +num = (float + int) / Value\parse 'num' atom = num + sym + str diff --git a/core/registry.moon b/core/registry.moon index 4dd5bbb..e8272fa 100644 --- a/core/registry.moon +++ b/core/registry.moon @@ -1,7 +1,12 @@ +import Value from require 'core.value' + class Registry new: () => @map = {} + @tick = 0 + @kr = Value.bool true + -- methods for Tag last: (index) => @last_map[index] @@ -15,37 +20,44 @@ class Registry L\trace "reg: init pending to #{expr}" table.insert @pending, { :tag, :expr } - active: -> - assert Registry.active_registry, "no active Registry!" + active: -> assert Registry.active_registry, "no active Registry!" -- public methods - wrap: (fn) => - (...) -> - @prepare! - with fn ... - @finalize! - - prepare: => - assert not @prev, "already have a previous registry? #{@prev}" - @prev, @@active_registry = @@active_registry, @ + wrap_eval: (fn) => (...) -> + @grab! @last_map, @map, @pending = @map, {}, {} - finalize: => - for tag, val in pairs @last_map - if not @map[tag] - val\destroy! + with fn ... + for tag, val in pairs @last_map + if not @map[tag] + val\destroy! - for { :tag, :expr } in *@pending - -- tag was solved by another pending registration - -- (e.g. first [A] is solved, then [5.A] is solved) - continue if tag\index! + for { :tag, :expr } in *@pending + -- tag was solved by another pending registration + -- (e.g. first [A] is solved, then [5.A] is solved) + continue if tag\index! - next_tag = @next_tag! - L\trace "assigned new tag #{next_tag} to #{tag} #{expr}" - tag\set next_tag - @map[tag\index!] = expr + next_tag = @next_tag! + L\trace "assigned new tag #{next_tag} to #{tag} #{expr}" + tag\set next_tag + @map[tag\index!] = expr + + @release! + + wrap_tick: (fn) => (...) -> + @grab! + @tick += 1 + @kr\set true + + with fn ... + @release! + + grab: => + assert not @prev, "already have a previous registry? #{@prev}" + @prev, @@active_registry = @@active_registry, @ + release: => assert @ == @@active_registry, "not the active registry!" @@active_registry, @prev = @prev, nil diff --git a/core/scope.moon b/core/scope.moon index aaa38f2..f795f97 100644 --- a/core/scope.moon +++ b/core/scope.moon @@ -1,12 +1,16 @@ -import Value from require 'core.value' +import Result, Value from require 'core.value' class Scope new: (@node, @parent) => @values = {} - set_raw: (key, val) => @values[key] = Value.wrap val, key + set_raw: (key, val) => + value = Value.wrap val, key + @values[key] = Result :value + set: (key, val) => L\trace "setting #{key} = #{val} in #{@}" + assert val.__class == Result, "expected #{key}=#{val} to be Result" @values[key] = val get: (key, prefix='') => @@ -20,9 +24,9 @@ class Scope if not start return @parent and L\push -> @parent\get key - scope = @get start - assert scope and scope.type == 'scope', "cant find '#{prefix}#{start}' for '#{prefix}#{key}'" - scope\unwrap!\get rest, "#{prefix}#{start}/" + child = @get start + assert child and child.value.type == 'scope', "#{start} is not a scope (looking for #{key})" + child.value\unwrap!\get rest, "#{prefix}#{start}/" use: (other) => L\trace "using defs from #{other} in #{@}" @@ -31,7 +35,8 @@ class Scope from_table: (tbl) -> with Scope! - .values = { k, Value.wrap v, k for k,v in pairs tbl } + for k, v in pairs tbl + \set_raw k, v __tostring: => buf = "<Scope" diff --git a/core/value.moon b/core/value.moon index f3f9796..986c846 100644 --- a/core/value.moon +++ b/core/value.moon @@ -1,9 +1,9 @@ -- ALV Value types -import Op, Action, FnDef from require 'core.base' - -local Scope +local Scope, Registry, Op, Action, FnDef load_ = -> import Scope from require 'core.scope' + import Registry from require 'core.registry' + import Op, Action, FnDef from require 'core.base' ancestor = (klass) -> assert klass, "cant find the ancestor of nil" @@ -16,26 +16,73 @@ ancestor = (klass) -> -- - a Value -- - an Op (to update) -- - children (results of subexpressions that were evaluated) +-- - cached list of all Dispatchers affecting all Ops in the subtree -- --- ResultNodes form a tree that controls execution order and message passing +-- Results form a tree that controls execution order and message passing -- between Ops. -class ResultNode +class Result -- params: table with optional keys op, value, children new: (params={}) => - @op = params.op @value = params.value + @op = params.op @children = params.children or {} - -- unwrap value and assert that neither @op nor @children were set - value_only: (msg) => - assert not @op and #@children == 0, msg or "pure expression expected" + @all_impulses = {} + for child in *@children + for d in pairs child.all_impulses + @all_impulses[d] = true + + if @op + assert @op.impulses, "#{@op} not set up correctly (impulses)" + for d in *@op.impulses + @all_impulses[d] = true + + -- asserts value-constness and returns the value + const: (msg) => + assert not (next @all_impulses), msg or "eval-time const expected" @value - update: (dt) => + -- create a value-copy of this result that has the same impulses but without + -- affecting the original's update logic + make_ref: => + with Result value: @value + .all_impulses = @all_impulses + + -- in depth-first order, tick all Ops who have dirty Stream inputs or impulses + -- + -- short-circuits if there are no dirty Streams in the entire subtree + tick: => + any_dirty = false + for stream in pairs @all_impulses + if stream\dirty! + any_dirty = true + break + + -- early-out if no streams are dirty in this whole subtree + return unless any_dirty + for child in *@children - child\update dt - @op\update dt if @op + child\tick! + + if @op + -- we have to check self_dirty here, because streams from child + -- expressions might have changed + self_dirty = false + for stream in *@op.impulses + if stream\dirty! + self_dirty = true + break + for stream in *@op.inputs + if stream\dirty! + self_dirty = true + break + + L\trace "#{op} is #{if self_dirty then 'dirty' else 'clean'}" + return unless self_dirty + + @op\tick! +-- static __tostring: => buf = "<result=#{@value}" buf ..= " #{@op}" if @op @@ -43,19 +90,18 @@ class ResultNode buf ..= ">" buf -local Const, Stream - -- ALV Type wrapper class Value -- @type - type name. -- builtin types: * literals: sym, num, bool -- * scope, opdef, fndef, builtin -- @value - Lua value - access through :unwrap() - new: (@type, @value) => + new: (@type, @value, @raw) => + @updated = 0 - -- asserts value-constness - -- returns self (for chaining) - const: => error 'not a constant' + dirty: => @updated == Registry.active!.tick + + set: (@value) => @updated = Registry.active!.tick -- unwrap to the Lua type -- asserts @type == type, msg if given @@ -63,14 +109,30 @@ class Value assert type == @type, msg or "#{@} is not a #{type}" if type @value +-- AST interface + eval: (scope) => + switch @type + when 'num', 'str' + Result value: @ + when 'sym' + assert (scope\get @value), "undefined reference to symbol '#{@value}'" + else + error "cannot evaluate #{@}" + + quote: => @ + + stringify: => assert @raw, "stringifying Value that wasn't parsed" + + clone: (prefix) => @ + -- in case of doubt: + -- clone: (prefix) => Value @type, @value, @raw + -- static __tostring: => value = if 'table' == (type @value) and rawget @value, '__base' then @value.__name else @value "<#{@@__name} #{@type}: #{value}>" + __call: (...) => @unwrap ... __eq: (other) => other.type == @type and other.value == @value - __inherited: (cls) => - cls.__base.__tostring = @__tostring - cls.__base.__eq = @__eq -- wrap a Lua type @wrap: (val, name='(unknown)') -> @@ -96,62 +158,26 @@ class Value error "#{name}: cannot wrap '#{val.__class.__name}' instance" else -- plain table - return Const 'scope', Scope.from_table val + return Value 'scope', Scope.from_table val else error "#{name}: cannot wrap Lua type '#{type val}'" - Const typ, val - -class Stream extends Value - new: (@type, @value=nil) => - - set: (@value) => - -- set dirty flag (?) - -class Const extends Value - new: (type, value, @raw) => - super type, value - --- Value interface - const: => @ - --- AST interface - eval: (scope) => - value = switch @type - when 'num', 'str' - @ - when 'sym' - assert (scope\get @value), "undefined reference to symbol '#{@value}'" - else - error "cannot evaluate #{@}" - - ResultNode :value - - quote: => @ - - stringify: => @raw + Value typ, val - clone: (prefix) => @ - -- in case of doubt: - -- clone: (prefix) => Const @type, @value, @raw - --- static unescape = (str) -> str\gsub '\\([\'"\\])', '%1' - @parse: (type, sep) => switch type when 'num' then (match) -> @ 'num', (tonumber match), match when 'sym' then (match) -> @ 'sym', match, match when 'str' then (match) -> @ 'str', (unescape match), sep .. match .. sep - @num: (num) -> Const 'num', num, tostring num - @str: (str) -> Const 'str', str, "'#{str}'" - @sym: (sym) -> Const 'sym', sym, sym - @bool: (bool) -> Const 'bool', bool, tostring bool + @num: (num) -> Value 'num', num, tostring num + @str: (str) -> Value 'str', str, "'#{str}'" + @sym: (sym) -> Value 'sym', sym, sym + @bool: (bool) -> Value 'bool', bool, tostring bool { - :ResultNode + :Result :Value - :Stream, :Const :load_ } |
