aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2020-02-19 21:17:46 +0000
committers-ol <s-ol@users.noreply.github.com>2020-02-19 21:17:53 +0000
commite62ef99244bb3aa906810a5cfd53c0dac68b0f37 (patch)
treee7379e2add7014e7124f678f4a7a8edc699fe914 /core
parentmajor refactoring: Const, Stream + ResultNode (diff)
downloadalive-e62ef99244bb3aa906810a5cfd53c0dac68b0f37.tar.gz
alive-e62ef99244bb3aa906810a5cfd53c0dac68b0f37.zip
merge Const and Stream into Value; Dataflow logic
lib not fully ported / functonial
Diffstat (limited to 'core')
-rw-r--r--core/base.moon37
-rw-r--r--core/builtin.moon36
-rw-r--r--core/cell.moon10
-rw-r--r--core/init.moon6
-rw-r--r--core/invoke.moon13
-rw-r--r--core/parsing.moon10
-rw-r--r--core/registry.moon58
-rw-r--r--core/scope.moon17
-rw-r--r--core/value.moon154
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_
}