aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/base.moon69
-rw-r--r--core/builtin.moon64
-rw-r--r--core/cell.moon28
-rw-r--r--core/const.moon136
-rw-r--r--core/init.moon8
-rw-r--r--core/invoke.moon50
-rw-r--r--core/parsing.moon15
-rw-r--r--core/registry.moon5
-rw-r--r--core/scope.moon8
-rw-r--r--core/value.moon157
10 files changed, 289 insertions, 251 deletions
diff --git a/core/base.moon b/core/base.moon
index dcf600a..1b1be5d 100644
--- a/core/base.moon
+++ b/core/base.moon
@@ -1,15 +1,13 @@
-class Op
--- common
- new: =>
- -- (...) => @setup ...
+-- base definitions for extensions
- get: => @value
- getc: =>
- L\warn "stream #{@} cast to constant"
- @value
+-- a persistent expression Operator
+--
+-- accepts Const or Stream inputs and produces a Stream output
+class Op
+ update: (dt) =>
--- Value interface
- update: =>
+ -- set @out to a Value (Const or Stream)
+ setup: (...) =>
destroy: =>
@@ -17,11 +15,16 @@ class Op
__tostring: => "<op: #{@@__name}>"
__inherited: (cls) => cls.__base.__tostring = @__tostring
- spawn: (Opdef, ...) ->
- Opdef ...
+-- a builtin / special form / cell-evaluation strategy
+--
+-- responsible for quoting/evaluating subexpressions,
+-- instantiating and patching Ops,
+-- updating the current Scope,
+-- etc.
class Action
--- common
+ -- head: the (:eval'd) head of the Cell to evaluate (a Const)
+ -- tag: the Tag of the expression to evaluate
new: (head, @tag) =>
@patch head
@@ -35,9 +38,9 @@ class Action
-- free resources
destroy: =>
- -- update this instance for :eavl() with new head
- -- if :patch() returns false, this instance is :destroy'ed and recreated instead
- -- must *not* return false when called after :new()
+ -- update this instance for :eval() with new head
+ -- if :patch() returns false, this instance is :destroy'ed and recreated
+ -- instead must *not* return false when called after :new()
-- only considered if Action types match
patch: (head) =>
if head == @head
@@ -46,35 +49,51 @@ class Action
@head = head
-- static
- @get_or_create: (ActionType, head, tag) ->
+ -- 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
+ @eval_cell: (scope, tag, head, tail) =>
last = tag\last!
compatible = last and
- (last.__class == ActionType) and
+ (last.__class == @) and
(last\patch head) and
last
L\trace if compatible
- "reusing #{last} for #{tag} <#{ActionType.__name} #{head}>"
+ "reusing #{last} for #{tag} <#{@__name} #{head}>"
else if last
- "replacing #{last} with new #{tag} <#{ActionType.__name} #{head}>"
+ "replacing #{last} with new #{tag} <#{@__name} #{head}>"
else
- "initializing #{tag} <#{ActionType.__name} #{head}>"
+ "initializing #{tag} <#{@__name} #{head}>"
- if compatible
+ action = if compatible
tag\keep compatible
compatible
else
last\destroy! if last
- with next = ActionType head, tag
+ with next = @ head, tag
tag\replace next
+ action\eval scope, tail
+
__tostring: => "<#{@@__name} #{@head}>"
__inherited: (cls) => cls.__base.__tostring = @__tostring
+-- a ALV function definition
+--
+-- when called, expands its body with params bound to the fn arguments
+-- (see core.invoke.fn-invoke)
class FnDef
+ -- params: sequence of (:quote'd) symbols, each naming a function parameter
+ -- body: (:quote'd) expression the function evaluates to
+ -- scoe: the lexical scope the function was defined in (closure)
new: (@params, @body, @scope) =>
__tostring: =>
- table.concat [p\stringify! for p in *@params], ' '
+ "(fn (#{table.concat [p\stringify! for p in *@params], ' '}) ...)"
-:Op, :Action, :FnDef
+{
+ :Op
+ :Action
+ :FnDef
+}
diff --git a/core/builtin.moon b/core/builtin.moon
index 97f4457..71beac3 100644
--- a/core/builtin.moon
+++ b/core/builtin.moon
@@ -1,8 +1,8 @@
+-- builtin special forms
import Action, FnDef from require 'core.base'
-import Const from require 'core.const'
+import ResultNode, Value, Const from require 'core.value'
import Cell from require 'core.cell'
import Scope from require 'core.scope'
-import UpdateChildren from require 'core.invoke'
class doc extends Action
@doc: "(doc sym) - print documentation in console
@@ -13,8 +13,9 @@ prints the docstring for sym in the console"
assert #tail == 1, "'doc' takes exactly one parameter"
def = L\push tail[1]\eval, scope
- L\print "(doc #{tail[1]\stringify!}):\n#{def\getc!.doc}\n"
- nil
+ with ResultNode children: { def }
+ def = def.value\const!\unwrap!
+ L\print "(doc #{tail[1]\stringify!}):\n#{def.doc}\n"
class def extends Action
@doc: "(def sym1 val-expr1
@@ -28,16 +29,15 @@ updates all val-exprs."
assert #tail > 1, "'def' requires at least 2 arguments"
assert #tail % 2 == 0, "'def' requires an even number of arguments"
- values = L\push ->
+ children = L\push ->
return for i=1,#tail,2
name, val_expr = tail[i], tail[i+1]
- name = (name\quote scope)\getc 'sym'
+ name = (name\quote scope)\unwrap 'sym'
- val = val_expr\eval scope
- scope\set name, Const.wrap_ref val
- val
+ with val_expr\eval scope
+ scope\set name, .value
- UpdateChildren values
+ ResultNode :children
class use extends Action
@doc: "(use scope1 [scope2]...) - merge scopes into parent scope
@@ -48,12 +48,11 @@ all scopes have to be eval-time constants."
eval: (scope, tail) =>
L\trace "evaling #{@}"
for child in *tail
- value = L\push child\eval, scope
- L\trace @, "merging #{value} into #{scope}"
- assert value.type == 'scope', "'use' only works on scopes"
- scope\use value\getc 'scope'
+ result = L\push child\eval, scope
+ value = result\value_only!\const!
+ scope\use value\unwrap 'scope', "'use' only works on scopes"
- nil
+ ResultNode!
class require_ extends Action
@doc: "(require name-str) - require a module
@@ -65,10 +64,11 @@ name-str has to be an eval-time constant."
L\trace "evaling #{@}"
assert #tail == 1, "'require' takes exactly one parameter"
- name = L\push tail[1]\eval, scope
+ result = L\push tail[1]\eval, scope
+ name = result\value_only!\const!
L\trace @, "loading module #{name}"
- Const.wrap require "lib.#{name\getc 'str'}"
+ ResultNode value: Value.wrap require "lib.#{name\unwrap 'str'}"
class import_ extends Action
@doc: "(import sym1 [sym2]...) - require and define modules
@@ -79,12 +79,11 @@ requires modules sym1, sym2, ... and defines them as sym1, sym2, ... in the curr
L\trace "evaling #{@}"
assert #tail > 0, "'import' requires at least one arguments"
-
for child in *tail
- name = (child\quote scope)\getc 'sym'
- scope\set name, Const.wrap require "lib.#{name}"
+ name = (child\quote scope)\unwrap 'sym'
+ scope\set name, Value.wrap require "lib.#{name}"
- nil
+ ResultNode!
class import_star extends Action
@doc: "(import* sym1 [sym2]...) - require and use modules
@@ -97,10 +96,10 @@ requires modules sym1, sym2, ... and merges them into the current scope"
for child in *tail
- name = (child\quote scope)\getc 'sym'
- scope\use (Const.wrap require "lib.#{name}")\getc 'scope'
+ name = (child\quote scope)\unwrap 'sym'
+ scope\use (Value.wrap require "lib.#{name}")\unwrap 'scope'
- nil
+ ResultNode!
class fn extends Action
@doc: "(fn (p1 [p2]...) body-expr) - declare a (lambda) function
@@ -118,7 +117,7 @@ the symbols p1, p2, ... will resolve to the arguments passed to the function."
param\quote scope
body = body\quote scope
- Const.wrap FnDef param_symbols, body, scope
+ ResultNode value: Value.wrap FnDef param_symbols, body, scope
class defn extends Action
@doc: "(defn name-sym (p1 [p2]...) body-expr) - define a function
@@ -130,7 +129,7 @@ declares a lambda (see (doc fn)) and defines it in the current scope"
assert #tail == 3, "'defn' takes exactly three arguments"
{ name, params, body } = tail
- name = (name\quote scope)\getc 'sym'
+ name = (name\quote scope)\unwrap 'sym'
assert params.__class == Cell, "'defn's second argument has to be an expression"
param_symbols = for param in *params.children
assert param.type == 'sym', "function parameter declaration has to be a symbol"
@@ -139,9 +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, Const.wrap fn
-
- nil
+ scope\set name, Value.wrap fn
+ ResultNode!
class do_expr extends Action
@doc: "(do expr1 [expr2]...) - update multiple expressions
@@ -150,7 +148,7 @@ evaluates and continously updates expr1, expr2, ...
the last expression's value is returned."
eval: (scope, tail) =>
- UpdateChildren [(expr\eval scope) or Const.empty! for expr in *tail]
+ ResultNode 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
@@ -166,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\getc!
+ xif = xif\value_only!\const!\unwrap!
if xif
xthen\eval scope
@@ -180,8 +178,8 @@ class trace extends Action
L\trace "evaling #{@}"
assert #tail == 1, "'trace' takes exactly one parameter"
- with val = L\push tail[1]\eval, scope
- L\print "trace:", val
+ with result = L\push tail[1]\eval, scope
+ L\print "trace #{tail[1]\stringify!}: #{result.value}"
{
:doc, :trace
diff --git a/core/cell.moon b/core/cell.moon
index 4aa847b..642e1a6 100644
--- a/core/cell.moon
+++ b/core/cell.moon
@@ -1,15 +1,19 @@
-import Const from require 'core.const'
+-- ALV Cell type
+import Const from require 'core.value'
import op_invoke, fn_invoke from require 'core.invoke'
import Tag from require 'core.tag'
+-- ALV Cell type
class Cell
-- common
- new: (@tag, @children, @white) =>
+ -- tag: the parsed Tag
+ -- children: sequence of child AST Nodes
+ -- white: optional sequence of whitespace segments ([0 .. #@children])
+ new: (@tag=Tag.blank!, @children, @white) =>
if not @white
@white = ['' for i=1,#@children+1]
- if not @tag
- @tag = Tag.blank!
+ assert #@white == #@children, "mismatched whitespace length"
head: => @children[1]
tail: => [c for c in *@children[2,]]
@@ -18,7 +22,8 @@ class Cell
-- AST interface
eval: (scope) =>
- head = @head!\eval scope
+ head_result = @head!\eval scope
+ head = head_result.value\const!
Action = switch head.type
when 'opdef'
-- scope\get 'op-invoke'
@@ -27,7 +32,7 @@ class Cell
-- scope\get 'fn-invoke'
fn_invoke
when 'builtin'
- head\getc!
+ head\unwrap!
else
print head
for k,v in pairs head
@@ -35,8 +40,7 @@ class Cell
print head.__class.__name
error "cannot evaluate expr with head #{head}"
- action = Action\get_or_create head, @tag
- action\eval scope, @tail!
+ Action\eval_cell scope, @tag, head, @tail!
quote: (scope) =>
children = [child\quote scope for child in *@children]
@@ -81,6 +85,9 @@ class Cell
tag, children, white = parse_args ...
@ tag, children, white
+-- A parenthesis-less Cell (root of an ALV document)
+--
+-- evaluates with an implicit 'do' in the head
class RootCell extends Cell
head: => Const.sym 'do'
tail: => @children
@@ -98,4 +105,7 @@ class RootCell extends Cell
@parse: (...) =>
@__parent.parse @, (Tag\parse '0'), ...
-:Cell, :RootCell
+{
+ :Cell
+ :RootCell
+}
diff --git a/core/const.moon b/core/const.moon
deleted file mode 100644
index 99e4178..0000000
--- a/core/const.moon
+++ /dev/null
@@ -1,136 +0,0 @@
-import Op, Action, FnDef from require 'core.base'
-
-local Scope
-load_ = ->
- import Scope from require 'core.scope'
-
-ancestor = (klass) ->
- assert klass, "cant find the ancestor of nil"
- while klass.__parent
- klass = klass.__parent
- klass
-
-class Ref
- new: (@original) =>
-
- get: (...) => @original\get ...
- getc: (...) => @original\get ...
-
- destroy: =>
- update: =>
-
-class Const
- types = {
- sym: true
- str: true
- num: true
- bool: true
- scope: true
- op: true
- opdef: true
- fndef: true
- builtin: true
- }
-
- new: (@type, @value, @raw) =>
- assert types[@type], "invalid Const type: #{@type}"
-
--- Value interface
- get: (type) =>
- assert not type or type == @type, "#{@} is not a #{type}"
- @value
-
- getc: (type) =>
- assert not type or type == @type, "#{@} is not a #{type}"
- @value
-
- update: (dt) =>
- switch @type
- when 'op'
- @value\update dt
-
--- AST interface
- eval: (scope) =>
- switch @type
- when 'num', 'str'
- @
- when 'sym'
- assert (scope\get @value), "undefined reference to symbol '#{@value}'"
- else
- error "cannot evaluate #{@}"
-
- quote: => @
-
- stringify: => @raw
-
- clone: (prefix) => @
- -- in case of doubt:
- -- clone: (prefix) => Const @type, @value, @raw
-
--- static
- __tostring: =>
- value = if @type == 'opdef' or @type == 'builtin' then @value.__name else @value
- "<#{@type}: #{value}>"
-
- __eq: (other) =>
- other.type == @type and other.value == @value
-
- 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
- @empty: -> Const 'str', '', "''"
-
- @wrap: (val, name='(unknown)') ->
- typ = switch type val
- when 'number' then 'num'
- when 'string' then 'str'
- when 'table'
- if base = rawget val, '__base'
- -- a class
- switch ancestor val
- when Op then 'opdef'
- when Action then 'builtin'
- else
- error "#{name}: cannot wrap class '#{val.__name}'"
- elseif val.__class
- -- an instance
- switch ancestor val.__class
- when Op then 'op'
- when Scope then 'scope'
- when FnDef then 'fndef'
- when Const
- return val
- else
- error "#{name}: cannot wrap '#{val.__class.__name}' instance"
- else
- -- plain table
- return Const 'scope', Scope.from_table val
- else
- error "#{name}: cannot wrap Lua type '#{type val}'"
-
- Const typ, val
-
- @wrap_ref: (val) ->
- if base = rawget val, '__base'
- -- a class
- error "#{name}: cannot wrap_ref class '#{val.__name}'"
- elseif val.__class
- -- an instance
- switch ancestor val.__class
- when Op then Ref val
- when Const then val
- else
- error "#{name}: cannot wrap_ref '#{val.__class.__name}' instance"
- else
- error "#{name}: cannot wrap_ref Lua type '#{type val}'"
-
-:Const, :load_
diff --git a/core/init.moon b/core/init.moon
index 1d9e191..b885747 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 Const, load_ from require 'core.const'
+import Stream, Const, load_ from require 'core.value'
import Scope from require 'core.scope'
load_!
@@ -15,7 +15,8 @@ import cell, program from require 'core.parsing'
globals = Scope.from_table require 'core.builtin'
{
- :Const, :Cell, :RootCell
+ :Stream, :Const
+ :Cell, :RootCell
:Op, :Action, :FnDef
:Scope
@@ -28,5 +29,6 @@ globals = Scope.from_table require 'core.builtin'
scope\use inject if inject
ast = assert (cell\match str), "failed to parse: #{str}"
- Const.wrap ast\eval scope
+ result = ast\eval scope
+ result\value_only!
}
diff --git a/core/invoke.moon b/core/invoke.moon
index 5ec4a82..5a6c420 100644
--- a/core/invoke.moon
+++ b/core/invoke.moon
@@ -1,39 +1,23 @@
-import Const from require 'core.const'
+import ResultNode, Value from require 'core.value'
import Action from require 'core.base'
import Scope from require 'core.scope'
-class UpdateChildren
- new: (@children) =>
-
- update: (dt) =>
- for child in *@children
- -- L\trace "updating #{child}"
- L\push child\update, dt
-
- get: => @children[#@children]\get!
- getc: => @children[#@children]\getc!
-
- __tostring: => '<forwarder>'
-
class op_invoke extends Action
patch: (head) =>
return true if head == @head
@op\destroy! if @op
- @head = head
- assert @head.type == 'opdef', "cant op-invoke #{@head}"
- @op = @head\getc!!
-
+ def = head\const!\unwrap 'opdef', "cant op-invoke #{@head}"
+ @head, @op = head, def!
+
true
eval: (scope, tail) =>
- L\trace "evaling #{@}"
- args = L\push -> [L\push expr\eval, scope for expr in *tail]
+ children = L\push -> [L\push expr\eval, scope for expr in *tail]
+ value = @op\setup unpack [child.value for child in *children]
- -- Const 'op', with @op
- with @op
- \setup unpack args
+ ResultNode :children, :value, op: @op
class fn_invoke extends Action
-- @TODO:
@@ -48,27 +32,23 @@ class fn_invoke extends Action
true
eval: (outer_scope, tail) =>
- assert @head.type == 'fndef', "cant fn-invoke #{@head}"
- { :params, :body, :scope } = @head\getc!
+ { :params, :body, :scope } = @head\const!\unwrap 'fndef', "cant fn-invoke #{@head}"
assert #params == #tail, "argument count mismatch in #{@head}"
fn_scope = Scope @, scope
- for i=1,#params
- name = params[i]\getc!
- argm = tail[i]
- fn_scope\set name, L\push argm\eval, outer_scope
+ children = for i=1,#params
+ name = params[i]\unwrap 'sym'
+ with L\push tail[i]\eval, outer_scope
+ fn_scope\set name, .value
body = body\clone @tag
+ result = body\eval fn_scope
- body\eval fn_scope
-
-class do_expr extends Action
- eval: (scope, tail) =>
- UpdateChildren [(expr\eval scope) or Const.empty! for expr in *tail]
+ table.insert children, result
+ ResultNode :children, value: result.value
{
:op_invoke, :fn_invoke
- :UpdateChildren
}
diff --git a/core/parsing.moon b/core/parsing.moon
index 58dbf62..96d8125 100644
--- a/core/parsing.moon
+++ b/core/parsing.moon
@@ -1,4 +1,4 @@
-import Const from require 'core.const'
+import Const 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'
@@ -14,13 +14,14 @@ space = (wc^1 * (comment * wc^1)^0) / 1 -- required whitespace
mspace = (comment + wc)^0 / 1 -- optional whitespace
-- atoms
-sym = ((R 'az', 'AZ') + (S '-_+*/.!?')) ^ 1 / Const\parse 'sym'
+digit = R '09'
+first = (R 'az', 'AZ') + S '-_+*/.!?='
+sym = first * (first + digit)^0 / Const\parse 'sym'
strd = '"' * (C ((P '\\"') + (P '\\\\') + (1 - P '"'))^0) * '"' / Const\parse 'str', '\"'
strq = "'" * (C ((P "\\'") + (P '\\\\') + (1 - P "'"))^0) * "'" / Const\parse 'str', '\''
str = strd + strq
-digit = R '09'
int = digit^1
float = (digit^1 * '.' * digit^0) + (digit^0 * '.' * digit^1)
num = (float + int) / Const\parse 'num'
@@ -43,7 +44,13 @@ cell = P {
:expr, :explist, :cell
}
-program = root * -1
+parse_error = (root, rest) ->
+ if #rest > 0
+ error "failed to parse, rest is:\n#{rest}"
+
+ root
+
+program = root * (C (P 1)^0) * -1 / parse_error
{
:comment
diff --git a/core/registry.moon b/core/registry.moon
index 3fc6d55..4dd5bbb 100644
--- a/core/registry.moon
+++ b/core/registry.moon
@@ -41,8 +41,9 @@ class Registry
-- (e.g. first [A] is solved, then [5.A] is solved)
continue if tag\index!
- L\trace "assigning new tag #{value} to #{tag} #{expr}"
- tag\set @next_tag!
+ next_tag = @next_tag!
+ L\trace "assigned new tag #{next_tag} to #{tag} #{expr}"
+ tag\set next_tag
@map[tag\index!] = expr
assert @ == @@active_registry, "not the active registry!"
diff --git a/core/scope.moon b/core/scope.moon
index ed4d67d..aaa38f2 100644
--- a/core/scope.moon
+++ b/core/scope.moon
@@ -1,10 +1,10 @@
-import Const from require 'core.const'
+import Value from require 'core.value'
class Scope
new: (@node, @parent) =>
@values = {}
- set_raw: (key, val) => @values[key] = Const.wrap val, key
+ set_raw: (key, val) => @values[key] = Value.wrap val, key
set: (key, val) =>
L\trace "setting #{key} = #{val} in #{@}"
@values[key] = val
@@ -22,7 +22,7 @@ class Scope
scope = @get start
assert scope and scope.type == 'scope', "cant find '#{prefix}#{start}' for '#{prefix}#{key}'"
- scope\getc!\get rest, "#{prefix}#{start}/"
+ scope\unwrap!\get rest, "#{prefix}#{start}/"
use: (other) =>
L\trace "using defs from #{other} in #{@}"
@@ -31,7 +31,7 @@ class Scope
from_table: (tbl) ->
with Scope!
- .values = { k, Const.wrap v, k for k,v in pairs tbl }
+ .values = { k, Value.wrap v, k for k,v in pairs tbl }
__tostring: =>
buf = "<Scope"
diff --git a/core/value.moon b/core/value.moon
new file mode 100644
index 0000000..f3f9796
--- /dev/null
+++ b/core/value.moon
@@ -0,0 +1,157 @@
+-- ALV Value types
+import Op, Action, FnDef from require 'core.base'
+
+local Scope
+load_ = ->
+ import Scope from require 'core.scope'
+
+ancestor = (klass) ->
+ assert klass, "cant find the ancestor of nil"
+ while klass.__parent
+ klass = klass.__parent
+ klass
+
+-- Result of evaluating an expression
+-- carries (all optional):
+-- - a Value
+-- - an Op (to update)
+-- - children (results of subexpressions that were evaluated)
+--
+-- ResultNodes form a tree that controls execution order and message passing
+-- between Ops.
+class ResultNode
+ -- params: table with optional keys op, value, children
+ new: (params={}) =>
+ @op = params.op
+ @value = params.value
+ @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"
+ @value
+
+ update: (dt) =>
+ for child in *@children
+ child\update dt
+ @op\update dt if @op
+
+ __tostring: =>
+ buf = "<result=#{@value}"
+ buf ..= " #{@op}" if @op
+ buf ..= " (#{#@children} children)" if #@children > 0
+ 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) =>
+
+ -- asserts value-constness
+ -- returns self (for chaining)
+ const: => error 'not a constant'
+
+ -- unwrap to the Lua type
+ -- asserts @type == type, msg if given
+ unwrap: (type, msg) =>
+ assert type == @type, msg or "#{@} is not a #{type}" if type
+ @value
+
+-- static
+ __tostring: =>
+ value = if 'table' == (type @value) and rawget @value, '__base' then @value.__name else @value
+ "<#{@@__name} #{@type}: #{value}>"
+ __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)') ->
+ typ = switch type val
+ when 'number' then 'num'
+ when 'string' then 'str'
+ when 'table'
+ if base = rawget val, '__base'
+ -- a class
+ switch ancestor val
+ when Op then 'opdef'
+ when Action then 'builtin'
+ else
+ error "#{name}: cannot wrap class '#{val.__name}'"
+ elseif val.__class
+ -- an instance
+ switch ancestor val.__class
+ when Scope then 'scope'
+ when FnDef then 'fndef'
+ when Value
+ return val
+ else
+ error "#{name}: cannot wrap '#{val.__class.__name}' instance"
+ else
+ -- plain table
+ return Const '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
+
+ 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
+
+{
+ :ResultNode
+ :Value
+ :Stream, :Const
+ :load_
+}