mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-28 09:09:31 +00:00
Anselme v2.0.0-alpha rewrite
Woke up and felt like changing a couple things. It's actually been worked on for a while, little at a time... The goal was to make the language and implementation much simpler. Well I don't know if it really ended up being simpler but it sure is more robust. Main changes: * proper first class functions and closures supports! proper scoping rules! no more namespace shenanigans! * everything is an expression, no more statements! make the implementation both simpler and more complex, but it's much more consistent now! the syntax has massively changed as a result though. * much more organized and easy to modify codebase: one file for each AST node, no more random fields or behavior set by some random node exceptionally, everything should now follow the same API defined in ast.abstract.Node Every foundational feature should be implemented right now. The vast majority of things that were possible in v2 are possible now; some things aren't, but that's usually because v2 is a bit more sane. The main missing things before a proper release are tests and documentation. There's a few other things that might be implemented later, see the ideas.md file.
This commit is contained in:
parent
2ff494d108
commit
fe351b5ca4
484 changed files with 7099 additions and 18084 deletions
207
ast/ArgumentTuple.lua
Normal file
207
ast/ArgumentTuple.lua
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
local ast = require("ast")
|
||||
local Identifier, Number
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local ArgumentTuple
|
||||
ArgumentTuple = ast.abstract.Node {
|
||||
type = "argument tuple",
|
||||
|
||||
list = nil, -- list of expr
|
||||
named = nil, -- { [string] = expr, ... }
|
||||
assignment = nil, -- expr
|
||||
arity = 0,
|
||||
|
||||
init = function(self, ...)
|
||||
self.list = { ... }
|
||||
self.named = {}
|
||||
self.arity = #self.list
|
||||
end,
|
||||
insert_positional = function(self, position, val) -- only for construction
|
||||
local l = {}
|
||||
for k, v in pairs(self.list) do
|
||||
if k >= position then l[k+1] = v
|
||||
else l[k] = v end
|
||||
end
|
||||
l[position] = val
|
||||
self.list = l
|
||||
self.arity = self.arity + 1
|
||||
end,
|
||||
set_positional = function(self, position, val) -- only for construction
|
||||
assert(not self.list[position])
|
||||
self.list[position] = val
|
||||
self.arity = self.arity + 1
|
||||
end,
|
||||
set_named = function(self, identifier, val) -- only for construction
|
||||
local name = identifier.name
|
||||
assert(not self.named[name])
|
||||
self.named[name] = val
|
||||
self.arity = self.arity + 1
|
||||
end,
|
||||
set_assignment = function(self, val) -- only for construction
|
||||
assert(not self.assignment)
|
||||
self.assignment = val
|
||||
self.arity = self.arity + 1
|
||||
self.format_priority = operator_priority["_=_"]
|
||||
end,
|
||||
|
||||
_format = function(self, state, priority, ...)
|
||||
local l = {}
|
||||
for _, e in pairs(self.list) do
|
||||
table.insert(l, e:format(state, operator_priority["_,_"], ...))
|
||||
end
|
||||
for n, e in pairs(self.named) do
|
||||
table.insert(l, n.."="..e:format_right(state, operator_priority["_=_"], ...))
|
||||
end
|
||||
local s = ("(%s)"):format(table.concat(l, ", "))
|
||||
if self.assignment then
|
||||
s = s .. (" = %s"):format(self.assignment:format_right(state, operator_priority["_=_"], ...))
|
||||
end
|
||||
return s
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
for _, e in pairs(self.list) do
|
||||
fn(e, ...)
|
||||
end
|
||||
for _, e in pairs(self.named) do
|
||||
fn(e, ...)
|
||||
end
|
||||
if self.assignment then
|
||||
fn(self.assignment, ...)
|
||||
end
|
||||
end,
|
||||
|
||||
-- need to redefine hash to include a table.sort as pairs() in :traverse is non-deterministic
|
||||
-- as well as doesn't account for named arguments names
|
||||
_hash = function(self)
|
||||
local t = {}
|
||||
for _, e in pairs(self.list) do
|
||||
table.insert(t, e:hash())
|
||||
end
|
||||
for n, e in pairs(self.named) do
|
||||
table.insert(t, ("%s=%s"):format(n, e:hash()))
|
||||
end
|
||||
if self.assignment then
|
||||
table.insert(t, self.assignment:hash())
|
||||
end
|
||||
table.sort(t)
|
||||
return ("%s<%s>"):format(self.type, table.concat(t, ";"))
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
local r = ArgumentTuple:new()
|
||||
for i, e in pairs(self.list) do
|
||||
r:set_positional(i, e:eval(state))
|
||||
end
|
||||
for n, e in pairs(self.named) do
|
||||
r:set_named(Identifier:new(n), e:eval(state))
|
||||
end
|
||||
if self.assignment then
|
||||
r:set_assignment(self.assignment:eval(state))
|
||||
end
|
||||
return r
|
||||
end,
|
||||
|
||||
with_first_argument = function(self, first)
|
||||
local r = ArgumentTuple:new()
|
||||
r:set_positional(1, first)
|
||||
for i, e in pairs(self.list) do
|
||||
r:set_positional(i+1, e)
|
||||
end
|
||||
for n, e in pairs(self.named) do
|
||||
r:set_named(Identifier:new(n), e)
|
||||
end
|
||||
if self.assignment then
|
||||
r:set_assignment(self.assignment)
|
||||
end
|
||||
return r
|
||||
end,
|
||||
|
||||
-- return specificity (>=0), secondary specificity (>=0)
|
||||
-- return false, failure message
|
||||
match_parameter_tuple = function(self, state, params)
|
||||
-- basic arity checks
|
||||
if self.arity > params.max_arity or self.arity < params.min_arity then
|
||||
if params.min_arity == params.max_arity then
|
||||
return false, ("expected %s arguments, received %s"):format(params.min_arity, self.arity)
|
||||
else
|
||||
return false, ("expected between %s and %s arguments, received %s"):format(params.min_arity, params.max_arity, self.arity)
|
||||
end
|
||||
end
|
||||
if params.assignment and not self.assignment then
|
||||
return false, "expected an assignment argument"
|
||||
end
|
||||
-- search for parameter -> argument match
|
||||
local specificity = 0
|
||||
local used_list = {}
|
||||
local used_named = {}
|
||||
local used_assignment = false
|
||||
for i, param in ipairs(params.list) do
|
||||
-- search in args
|
||||
local arg
|
||||
if self.list[i] then
|
||||
used_list[i] = true
|
||||
arg = self.list[i]
|
||||
elseif self.named[param.identifier.name] then
|
||||
used_named[param.identifier.name] = true
|
||||
arg = self.named[param.identifier.name]
|
||||
elseif i == params.max_arity and params.assignment and self.assignment then
|
||||
used_assignment = true
|
||||
arg = self.assignment
|
||||
elseif param.default then
|
||||
arg = param.default
|
||||
end
|
||||
-- not found
|
||||
if not arg then return false, ("missing parameter %s"):format(param.identifier:format(state)) end
|
||||
-- type check
|
||||
if param.type_check then
|
||||
local r = param.type_check:call(state, ArgumentTuple:new(arg))
|
||||
if not r:truthy() then return false, ("type check failure for parameter %s in function %s"):format(param.identifier:format(state), params:format(state)) end
|
||||
if Number:is(r) then
|
||||
specificity = specificity + r.number
|
||||
else
|
||||
specificity = specificity + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
-- check for unused arguments
|
||||
for i in pairs(self.list) do
|
||||
if not used_list[i] then
|
||||
return false, ("%sth positional argument is unused"):format(i)
|
||||
end
|
||||
end
|
||||
for n in pairs(self.named) do
|
||||
if not used_named[n] then
|
||||
return false, ("named argument %s is unused"):format(n)
|
||||
end
|
||||
end
|
||||
if self.assignment and not used_assignment then
|
||||
return false, "assignment argument is unused"
|
||||
end
|
||||
-- everything is A-ok
|
||||
return specificity, params.eval_depth
|
||||
end,
|
||||
|
||||
-- assume :match_parameter_tuple was already called and returned true
|
||||
bind_parameter_tuple = function(self, state, params)
|
||||
for i, arg in ipairs(params.list) do
|
||||
if self.list[i] then
|
||||
state.scope:define(arg.identifier:to_symbol(), self.list[i])
|
||||
elseif self.named[arg.identifier.name] then
|
||||
state.scope:define(arg.identifier:to_symbol(), self.named[arg.identifier.name])
|
||||
elseif i == params.max_arity and params.assignment then
|
||||
state.scope:define(arg.identifier:to_symbol(), self.assignment)
|
||||
elseif arg.default then
|
||||
state.scope:define(arg.identifier:to_symbol(), arg.default:eval(state))
|
||||
else
|
||||
error(("no argument matching parameter %q"):format(arg.identifier.name))
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = ArgumentTuple
|
||||
Identifier, Number = ast.Identifier, ast.Number
|
||||
|
||||
return ArgumentTuple
|
||||
37
ast/Assignment.lua
Normal file
37
ast/Assignment.lua
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
local ast = require("ast")
|
||||
local Nil
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local Assignment = ast.abstract.Node {
|
||||
type = "assignment",
|
||||
|
||||
identifier = nil,
|
||||
expression = nil,
|
||||
format_priority = operator_priority["_=_"],
|
||||
|
||||
init = function(self, identifier, expression)
|
||||
self.identifier = identifier
|
||||
self.expression = expression
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
return self.identifier:format(...).." = "..self.expression:format_right(...)
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.identifier, ...)
|
||||
fn(self.expression, ...)
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
local val = self.expression:eval(state)
|
||||
state.scope:set(self.identifier, val)
|
||||
return Nil:new()
|
||||
end,
|
||||
}
|
||||
|
||||
package.loaded[...] = Assignment
|
||||
Nil = ast.Nil
|
||||
|
||||
return Assignment
|
||||
49
ast/AttachBlock.lua
Normal file
49
ast/AttachBlock.lua
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
local ast = require("ast")
|
||||
local Identifier, Quote
|
||||
|
||||
local attached_block_identifier
|
||||
|
||||
local AttachBlock = ast.abstract.Node {
|
||||
type = "attach block",
|
||||
|
||||
expression = nil,
|
||||
block = nil,
|
||||
|
||||
init = function(self, expression, block)
|
||||
self.expression = expression
|
||||
self.block = block
|
||||
self.format_priority = self.expression.format_priority
|
||||
end,
|
||||
|
||||
_format = function(self, state, priority, indentation, ...)
|
||||
return self.expression:format(state, priority, indentation, ...).."\n\t"..self.block:format(state, priority, indentation + 1, ...)
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.expression, ...)
|
||||
fn(self.block, ...)
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
state.scope:push_partial(attached_block_identifier)
|
||||
state.scope:define(attached_block_identifier:to_symbol(), Quote:new(self.block)) -- _ is always wrapped in a Call when it appears
|
||||
local exp = self.expression:eval(state)
|
||||
state.scope:pop()
|
||||
|
||||
return exp
|
||||
end,
|
||||
|
||||
_prepare = function(self, state)
|
||||
state.scope:push_partial(attached_block_identifier)
|
||||
state.scope:define(attached_block_identifier:to_symbol(), Quote:new(self.block))
|
||||
self.expression:prepare(state)
|
||||
state.scope:pop()
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = AttachBlock
|
||||
Identifier, Quote = ast.Identifier, ast.Quote
|
||||
|
||||
attached_block_identifier = Identifier:new("_")
|
||||
|
||||
return AttachBlock
|
||||
80
ast/Block.lua
Normal file
80
ast/Block.lua
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
local ast = require("ast")
|
||||
local Nil, Return, AutoCall, ArgumentTuple, Flush
|
||||
|
||||
local Block = ast.abstract.Node {
|
||||
type = "block",
|
||||
|
||||
expressions = {},
|
||||
|
||||
init = function(self)
|
||||
self.expressions = {}
|
||||
end,
|
||||
add = function(self, expression) -- only for construction
|
||||
table.insert(self.expressions, expression)
|
||||
end,
|
||||
|
||||
_format = function(self, state, prio, ...)
|
||||
local l = {}
|
||||
for _, e in ipairs(self.expressions) do
|
||||
if Flush:is(e) then
|
||||
table.insert(l, (e:format(state, 0, ...):gsub("\n$", "")))
|
||||
else
|
||||
table.insert(l, e:format(state, 0, ...))
|
||||
end
|
||||
end
|
||||
return table.concat(l, "\n")
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
for _, e in ipairs(self.expressions) do
|
||||
fn(e, ...)
|
||||
end
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
local r
|
||||
state.scope:push()
|
||||
if self:resuming(state) then
|
||||
local resuming = self:get_resume_data(state)
|
||||
local resumed = false
|
||||
for _, e in ipairs(self.expressions) do
|
||||
if e == resuming then resumed = true end
|
||||
if resumed then
|
||||
r = e:eval(state)
|
||||
if AutoCall:issub(r) then
|
||||
r = r:call(state, ArgumentTuple:new())
|
||||
end
|
||||
if Return:is(r) then
|
||||
break -- pass on to parent block until we reach a function boundary
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
for _, e in ipairs(self.expressions) do
|
||||
self:set_resume_data(state, e)
|
||||
r = e:eval(state)
|
||||
if AutoCall:issub(r) then
|
||||
r = r:call(state, ArgumentTuple:new())
|
||||
end
|
||||
if Return:is(r) then
|
||||
break -- pass on to parent block until we reach a function boundary
|
||||
end
|
||||
end
|
||||
end
|
||||
state.scope:pop()
|
||||
return r or Nil:new()
|
||||
end,
|
||||
|
||||
_prepare = function(self, state)
|
||||
state.scope:push()
|
||||
for _, e in ipairs(self.expressions) do
|
||||
e:prepare(state)
|
||||
end
|
||||
state.scope:pop()
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = Block
|
||||
Nil, Return, AutoCall, ArgumentTuple, Flush = ast.Nil, ast.Return, ast.abstract.AutoCall, ast.ArgumentTuple, ast.Flush
|
||||
|
||||
return Block
|
||||
28
ast/Boolean.lua
Normal file
28
ast/Boolean.lua
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
local ast = require("ast")
|
||||
|
||||
return ast.abstract.Node {
|
||||
type = "boolean",
|
||||
_evaluated = true, -- no evaluation needed
|
||||
|
||||
value = nil,
|
||||
|
||||
init = function(self, val)
|
||||
self.value = val
|
||||
end,
|
||||
|
||||
_hash = function(self)
|
||||
return ("boolean<%q>"):format(self.value)
|
||||
end,
|
||||
|
||||
_format = function(self)
|
||||
return tostring(self.value)
|
||||
end,
|
||||
|
||||
to_lua = function(self, state)
|
||||
return self.value
|
||||
end,
|
||||
|
||||
truthy = function(self)
|
||||
return self.value
|
||||
end
|
||||
}
|
||||
59
ast/Branched.lua
Normal file
59
ast/Branched.lua
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
-- branched: associate to each branch a different value
|
||||
-- used to handle mutability. probably the only mutable node you'll ever need! it's literally perfect!
|
||||
-- note: all values here are expected to be already evaluated
|
||||
|
||||
local ast = require("ast")
|
||||
|
||||
local Branched = ast.abstract.Runtime {
|
||||
type = "branched",
|
||||
mutable = true,
|
||||
|
||||
value = nil, -- { [branch name] = value, ... }
|
||||
|
||||
init = function(self, state, value)
|
||||
self.value = {}
|
||||
self:set(state, value)
|
||||
end,
|
||||
|
||||
in_branch = function(self, state)
|
||||
return not not self.value[state.branch_id]
|
||||
end,
|
||||
get = function(self, state)
|
||||
return self.value[state.branch_id] or self.value[state.source_branch_id]
|
||||
end,
|
||||
set = function(self, state, value)
|
||||
self.value[state.branch_id] = value
|
||||
end,
|
||||
_merge = function(self, state, cache)
|
||||
local val = self.value[state.branch_id]
|
||||
if val then
|
||||
self.value[state.source_branch_id] = val
|
||||
self.value[state.branch_id] = nil
|
||||
val:merge(state, cache)
|
||||
end
|
||||
end,
|
||||
|
||||
_format = function(self, state, ...)
|
||||
if state then
|
||||
return self:get(state):format(state, ...)
|
||||
else
|
||||
local t = {}
|
||||
for b, v in pairs(self.value) do
|
||||
table.insert(t, ("%s→%s"):format(b, v))
|
||||
end
|
||||
return "<"..table.concat(t, ", ")..">"
|
||||
end
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
for _, v in pairs(self.value) do
|
||||
fn(v, ...)
|
||||
end
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
return self:get(state)
|
||||
end
|
||||
}
|
||||
|
||||
return Branched
|
||||
86
ast/Call.lua
Normal file
86
ast/Call.lua
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
local ast = require("ast")
|
||||
local Identifier
|
||||
|
||||
local regular_operators = require("common").regular_operators
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local function reverse(t, fmt)
|
||||
for _, v in ipairs(t) do t[fmt:format(v[1])] = v[2] end
|
||||
return t
|
||||
end
|
||||
local infix = reverse(regular_operators.infixes, "_%s_")
|
||||
local prefix = reverse(regular_operators.prefixes, "%s_")
|
||||
local suffix = reverse(regular_operators.suffixes, "_%s")
|
||||
|
||||
local Call
|
||||
|
||||
Call = ast.abstract.Node {
|
||||
type = "call",
|
||||
|
||||
func = nil,
|
||||
arguments = nil, -- ArgumentTuple
|
||||
format_priority = infix["_!"], -- often overwritten in :init
|
||||
|
||||
init = function(self, func, arguments)
|
||||
self.func = func
|
||||
self.arguments = arguments
|
||||
|
||||
-- get priority: operators
|
||||
if Identifier:is(self.func) then
|
||||
local name, arity = self.func.name, self.arguments.arity
|
||||
if infix[name] and arity == 2 then
|
||||
self.format_priority = infix[name]
|
||||
elseif prefix[name] and arity == 1 then
|
||||
self.format_priority = prefix[name]
|
||||
elseif suffix[name] and arity == 1 then
|
||||
self.format_priority = suffix[name]
|
||||
end
|
||||
end
|
||||
if self.arguments.assignment then
|
||||
self.format_priority = operator_priority["_=_"]
|
||||
end
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
if self.arguments.arity == 0 then
|
||||
if Identifier:is(self.func) and self.func.name == "_" then
|
||||
return "_" -- the _ identifier is automatically re-wrapped in a Call when it appears
|
||||
end
|
||||
local func = self.func:format(...)
|
||||
return func.."!"
|
||||
else
|
||||
if Identifier:is(self.func) then
|
||||
local name, arity = self.func.name, self.arguments.arity
|
||||
if infix[name] and arity == 2 then
|
||||
local left = self.arguments.list[1]:format(...)
|
||||
local right = self.arguments.list[2]:format_right(...)
|
||||
return ("%s %s %s"):format(left, name:match("^_(.*)_$"), right)
|
||||
elseif prefix[name] and arity == 1 then
|
||||
local right = self.arguments.list[1]:format_right(...)
|
||||
return ("%s%s"):format(name:match("^(.*)_$"), right)
|
||||
elseif suffix[name] and arity == 1 then
|
||||
local left = self.arguments.list[1]:format(...)
|
||||
return ("%s%s"):format(left, name:match("^_(.*)$"))
|
||||
end
|
||||
end
|
||||
return self.func:format(...)..self.arguments:format(...) -- no need for format_right, we already handle the assignment priority here
|
||||
end
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.func, ...)
|
||||
fn(self.arguments, ...)
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
local func = self.func:eval(state)
|
||||
local arguments = self.arguments:eval(state)
|
||||
|
||||
return func:call(state, arguments)
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = Call
|
||||
Identifier = ast.Identifier
|
||||
|
||||
return Call
|
||||
52
ast/Choice.lua
Normal file
52
ast/Choice.lua
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
local ast = require("ast")
|
||||
local ArgumentTuple
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local Choice
|
||||
Choice = ast.abstract.Runtime {
|
||||
type = "choice",
|
||||
|
||||
text = nil,
|
||||
func = nil,
|
||||
format_priority = operator_priority["_|>_"],
|
||||
|
||||
init = function(self, text, func)
|
||||
self.text = text
|
||||
self.func = func
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.text, ...)
|
||||
fn(self.func, ...)
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
return ("%s |> %s"):format(self.text:format(...), self.func:format_right(...))
|
||||
end,
|
||||
|
||||
build_event_data = function(self, state, event_buffer)
|
||||
local l = {
|
||||
_selected = nil,
|
||||
choose = function(self, choice)
|
||||
self._selected = choice
|
||||
end
|
||||
}
|
||||
for _, c in event_buffer:iter(state) do
|
||||
table.insert(l, c.text)
|
||||
end
|
||||
return l
|
||||
end,
|
||||
post_flush_callback = function(self, state, event_buffer, data)
|
||||
local choice = data._selected
|
||||
assert(choice, "no choice made")
|
||||
assert(choice > 0 and choice <= event_buffer:len(state), "choice out of bounds")
|
||||
|
||||
event_buffer:get(state, choice).func:call(state, ArgumentTuple:new())
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = Choice
|
||||
ArgumentTuple = ast.ArgumentTuple
|
||||
|
||||
return Choice
|
||||
58
ast/Closure.lua
Normal file
58
ast/Closure.lua
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
-- note: functions only appear in non-evaluated nodes! once evaluated, they always become closures
|
||||
|
||||
local ast = require("ast")
|
||||
local Overloadable, Runtime = ast.abstract.Overloadable, ast.abstract.Runtime
|
||||
local Definition
|
||||
|
||||
local Closure
|
||||
Closure = Runtime(Overloadable) {
|
||||
type = "closure",
|
||||
|
||||
func = nil, -- Function
|
||||
scope = nil, -- Environment
|
||||
exported_scope = nil, -- Environment
|
||||
|
||||
init = function(self, func, state)
|
||||
self.func = func
|
||||
self.scope = state.scope:capture()
|
||||
|
||||
-- layer a new export layer on top of captured/current scope
|
||||
state.scope:push_export()
|
||||
self.exported_scope = state.scope:capture()
|
||||
|
||||
-- pre-define exports
|
||||
for sym, exp in pairs(self.func.exports) do
|
||||
Definition:new(sym, exp):eval(state)
|
||||
end
|
||||
|
||||
state.scope:pop()
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
return self.func:format(...)
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.func, ...)
|
||||
fn(self.scope, ...)
|
||||
fn(self.exported_scope, ...)
|
||||
end,
|
||||
|
||||
compatible_with_arguments = function(self, state, args)
|
||||
return args:match_parameter_tuple(state, self.func.parameters)
|
||||
end,
|
||||
format_parameters = function(self, state)
|
||||
return self.func.parameters:format(state)
|
||||
end,
|
||||
call_compatible = function(self, state, args)
|
||||
state.scope:push(self.exported_scope)
|
||||
local exp = self.func:call_compatible(state, args)
|
||||
state.scope:pop()
|
||||
return exp
|
||||
end,
|
||||
}
|
||||
|
||||
package.loaded[...] = Closure
|
||||
Definition = ast.Definition
|
||||
|
||||
return Closure
|
||||
60
ast/Definition.lua
Normal file
60
ast/Definition.lua
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
local ast = require("ast")
|
||||
local Nil, Overloadable
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local Definition = ast.abstract.Node {
|
||||
type = "definition",
|
||||
|
||||
symbol = nil,
|
||||
expression = nil,
|
||||
format_priority = operator_priority["_=_"],
|
||||
|
||||
init = function(self, symbol, expression)
|
||||
self.symbol = symbol
|
||||
self.expression = expression
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
return self.symbol:format(...).." = "..self.expression:format_right(...)
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.symbol, ...)
|
||||
fn(self.expression, ...)
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
if self.symbol.exported and state.scope:defined_in_current(self.symbol) then
|
||||
return Nil:new() -- export vars: can reuse existing defining
|
||||
end
|
||||
|
||||
local symbol = self.symbol:eval(state)
|
||||
local val = self.expression:eval(state)
|
||||
|
||||
if Overloadable:issub(val) then
|
||||
state.scope:define_overloadable(symbol, val)
|
||||
else
|
||||
state.scope:define(symbol, val)
|
||||
end
|
||||
|
||||
return Nil:new()
|
||||
end,
|
||||
|
||||
_prepare = function(self, state)
|
||||
local symbol, val = self.symbol, self.expression
|
||||
symbol:prepare(state)
|
||||
val:prepare(state)
|
||||
|
||||
if Overloadable:issub(val) then
|
||||
state.scope:define_overloadable(symbol, val)
|
||||
else
|
||||
state.scope:define(symbol, val)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = Definition
|
||||
Nil, Overloadable = ast.Nil, ast.abstract.Overloadable
|
||||
|
||||
return Definition
|
||||
214
ast/Environment.lua
Normal file
214
ast/Environment.lua
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
local ast = require("ast")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local Branched, ArgumentTuple, Overload, Overloadable, Table
|
||||
|
||||
local VariableMetadata = ast.abstract.Runtime {
|
||||
type = "variable metadata",
|
||||
|
||||
symbol = nil,
|
||||
branched = nil,
|
||||
format_priority = operator_priority["_=_"],
|
||||
|
||||
init = function(self, state, symbol, value)
|
||||
self.symbol = symbol
|
||||
self.branched = Branched:new(state, value)
|
||||
end,
|
||||
get = function(self, state)
|
||||
return self.branched:get(state)
|
||||
end,
|
||||
set = function(self, state, value)
|
||||
assert(not self.symbol.constant, ("trying to change the value of constant %s"):format(self.symbol.string))
|
||||
if self.symbol.type_check then
|
||||
local r = self.symbol.type_check:call(state, ArgumentTuple:new(value))
|
||||
if not r:truthy() then error(("type check failure for %s; %s does not satisfy %s"):format(self.symbol.string, value, self.symbol.type_check)) end
|
||||
end
|
||||
self.branched:set(state, value)
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
return ("%s=%s"):format(self.symbol:format(...), self.branched:format(...))
|
||||
end,
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.symbol, ...)
|
||||
fn(self.branched, ...)
|
||||
end,
|
||||
|
||||
_merge = function(self, state, cache)
|
||||
if not self.symbol.confined_to_branch then
|
||||
self.branched:merge(state, cache)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
local Environment = ast.abstract.Runtime {
|
||||
type = "environment",
|
||||
|
||||
parent = nil, -- environment or nil
|
||||
variables = nil, -- Table of { {identifier} = variable metadata, ... }
|
||||
|
||||
partial = nil, -- { [name string] = true, ... }
|
||||
export = nil, -- bool
|
||||
|
||||
init = function(self, state, parent, partial_names, is_export)
|
||||
self.variables = Table:new(state)
|
||||
self.parent = parent
|
||||
self.partial = partial_names
|
||||
self.export = is_export
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
if self.parent then
|
||||
fn(self.parent, ...)
|
||||
end
|
||||
fn(self.variables, ...)
|
||||
end,
|
||||
_format = function(self, state)
|
||||
return "<environment>"
|
||||
end,
|
||||
|
||||
-- define new variable in the environment
|
||||
define = function(self, state, symbol, exp)
|
||||
local name = symbol.string
|
||||
if self:defined_in_current(state, symbol) then
|
||||
error(name.." is already defined in the current scope")
|
||||
end
|
||||
if (self.partial and not self.partial[name])
|
||||
or (self.export ~= symbol.exported) then
|
||||
return self.parent:define(state, symbol, exp)
|
||||
end
|
||||
self.variables:set(state, symbol:to_identifier(), VariableMetadata:new(state, symbol, exp))
|
||||
end,
|
||||
-- define or redefine new overloadable variable in current environment, inheriting existing overload variants from (parent) scopes
|
||||
define_overloadable = function(self, state, symbol, exp)
|
||||
assert(Overloadable:issub(exp), "trying to add an non-overloadable value to an overload")
|
||||
|
||||
local identifier = symbol:to_identifier()
|
||||
|
||||
-- add overload variants already defined in current or parent scope
|
||||
if self:defined(state, identifier) then
|
||||
local val = self:get(state, identifier)
|
||||
if Overload:is(val) then
|
||||
exp = Overload:new(exp)
|
||||
for _, v in ipairs(val.list) do
|
||||
exp:insert(v)
|
||||
end
|
||||
elseif Overloadable:issub(val) then
|
||||
exp = Overload:new(exp, val)
|
||||
elseif self:defined_in_current(state, symbol) then
|
||||
error(("can't add an overload variant to non-overloadable variable %s defined in the same scope"):format(identifier))
|
||||
end
|
||||
end
|
||||
|
||||
-- update/define in current scope
|
||||
if self:defined_in_current(state, symbol) then
|
||||
self:set(state, identifier, exp)
|
||||
else
|
||||
self:define(state, symbol, exp)
|
||||
end
|
||||
end,
|
||||
|
||||
-- returns bool if variable defined in current or parent environment
|
||||
defined = function(self, state, identifier)
|
||||
if self.variables:has(state, identifier) then
|
||||
return true
|
||||
elseif self.parent then
|
||||
return self.parent:defined(state, identifier)
|
||||
end
|
||||
return false
|
||||
end,
|
||||
-- returns bool if variable defined in current environment layer
|
||||
-- (note: by current layer, we mean the closest one where the variable is able to exist - if it is exported, the closest export layer, etc.)
|
||||
defined_in_current = function(self, state, symbol)
|
||||
local name = symbol.string
|
||||
if self.variables:has(state, symbol:to_identifier()) then
|
||||
return true
|
||||
elseif (self.partial and not self.partial[name])
|
||||
or (self.export ~= symbol.exported) then
|
||||
return self.parent:defined_in_current(state, symbol)
|
||||
end
|
||||
return false
|
||||
end,
|
||||
-- return bool if variable is defined in the current environment only - won't search in parent event for exported & partial names
|
||||
defined_in_current_strict = function(self, state, identifier)
|
||||
return self.variables:has(state, identifier)
|
||||
end,
|
||||
|
||||
-- get variable in current or parent scope, with metadata
|
||||
_get_variable = function(self, state, identifier)
|
||||
if self:defined(state, identifier) then
|
||||
if self.variables:has(state, identifier) then
|
||||
return self.variables:get(state, identifier)
|
||||
elseif self.parent then
|
||||
return self.parent:_get_variable(state, identifier)
|
||||
end
|
||||
else
|
||||
error(("identifier %q is undefined in branch %s"):format(identifier.name, state.branch_id), 0)
|
||||
end
|
||||
end,
|
||||
-- get variable value in current or parent environment
|
||||
get = function(self, state, identifier)
|
||||
return self:_get_variable(state, identifier):get(state)
|
||||
end,
|
||||
-- set variable value in current or parent environment
|
||||
set = function(self, state, identifier, val)
|
||||
return self:_get_variable(state, identifier):set(state, val)
|
||||
end,
|
||||
|
||||
-- returns a list {[symbol]=val,...} of all persistent variables in the current strict layer
|
||||
list_persistent = function(self, state)
|
||||
assert(self.export, "not an export scope layer")
|
||||
local r = {}
|
||||
for _, vm in self.variables:iter(state) do
|
||||
if vm.symbol.persistent then
|
||||
r[vm.symbol] = vm:get(state)
|
||||
end
|
||||
end
|
||||
return r
|
||||
end,
|
||||
-- returns a list {[symbol]=val,...} of all exported variables in the current strict layer
|
||||
list_exported = function(self, state)
|
||||
assert(self.export, "not an export scope layer")
|
||||
local r = {}
|
||||
for _, vm in self.variables:iter(state) do
|
||||
if vm.symbol.exported then
|
||||
r[vm.symbol] = vm:get(state)
|
||||
end
|
||||
end
|
||||
return r
|
||||
end,
|
||||
-- return the depth of the environmenet, i.e. the number of parents
|
||||
depth = function(self)
|
||||
local d = 0
|
||||
local e = self
|
||||
while e.parent do
|
||||
e = e.parent
|
||||
d = d + 1
|
||||
end
|
||||
return d
|
||||
end,
|
||||
|
||||
_debug_state = function(self, state, filter, t, level)
|
||||
level = level or 0
|
||||
t = t or {}
|
||||
|
||||
local indentation = string.rep(" ", level)
|
||||
table.insert(t, ("%s> %s %s scope"):format(indentation, self.export and "exported" or "", self.partial and "partial" or ""))
|
||||
|
||||
for name, var in self.variables:iter(state) do
|
||||
if name.name:match(filter) then
|
||||
table.insert(t, ("%s| %s = %s"):format(indentation, name, var:get(state)))
|
||||
end
|
||||
end
|
||||
if self.parent then
|
||||
self.parent:_debug_state(state, filter, t, level+1)
|
||||
end
|
||||
return t
|
||||
end,
|
||||
}
|
||||
|
||||
package.loaded[...] = Environment
|
||||
Branched, ArgumentTuple, Overload, Overloadable, Table = ast.Branched, ast.ArgumentTuple, ast.Overload, ast.abstract.Overloadable, ast.Table
|
||||
|
||||
return Environment
|
||||
28
ast/Flush.lua
Normal file
28
ast/Flush.lua
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
local ast = require("ast")
|
||||
local Nil
|
||||
|
||||
local event_manager = require("state.event_manager")
|
||||
|
||||
local Flush = ast.abstract.Node {
|
||||
type = "flush",
|
||||
|
||||
init = function(self) end,
|
||||
|
||||
_hash = function(self)
|
||||
return "flush"
|
||||
end,
|
||||
|
||||
_format = function(self)
|
||||
return "\n"
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
event_manager:flush(state)
|
||||
return Nil:new()
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = Flush
|
||||
Nil = ast.Nil
|
||||
|
||||
return Flush
|
||||
78
ast/Function.lua
Normal file
78
ast/Function.lua
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
-- note: functions only appear in non-evaluated nodes! once evaluated, they always become closures
|
||||
|
||||
local ast = require("ast")
|
||||
local Overloadable = ast.abstract.Overloadable
|
||||
local Closure, ReturnBoundary
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local Function
|
||||
Function = Overloadable {
|
||||
type = "function",
|
||||
|
||||
parameters = nil, -- ParameterTuple
|
||||
expression = nil,
|
||||
format_priority = operator_priority["$_"],
|
||||
|
||||
exports = nil, -- { [sym] = exp, ... }, exctracted from expression during :prepare
|
||||
|
||||
init = function(self, parameters, expression, exports)
|
||||
self.parameters = parameters
|
||||
self.expression = ReturnBoundary:new(expression)
|
||||
self.exports = exports or {}
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
if self.parameters.assignment then
|
||||
return "$"..self.parameters:format(...).."; "..self.expression:format_right(...)
|
||||
else
|
||||
return "$"..self.parameters:format(...).." "..self.expression:format_right(...)
|
||||
end
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.parameters, ...)
|
||||
fn(self.expression, ...)
|
||||
end,
|
||||
|
||||
compatible_with_arguments = function(self, state, args)
|
||||
return args:match_parameter_tuple(state, self.parameters)
|
||||
end,
|
||||
format_parameters = function(self, state)
|
||||
return self.parameters:format(state)
|
||||
end,
|
||||
call_compatible = function(self, state, args)
|
||||
state.scope:push()
|
||||
args:bind_parameter_tuple(state, self.parameters)
|
||||
|
||||
local exp = self.expression:eval_resumable(state)
|
||||
|
||||
state.scope:pop()
|
||||
|
||||
-- reminder: don't do any additionnal processing here as that won't be executed when resuming self.expression
|
||||
-- instead wrap it in some additional node, like our friend ReturnBoundary
|
||||
|
||||
return exp
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
return Closure:new(Function:new(self.parameters:eval(state), self.expression, self.exports), state)
|
||||
end,
|
||||
|
||||
_prepare = function(self, state)
|
||||
state.scope:push_export() -- recreate scope context that will be created by closure
|
||||
|
||||
state.scope:push()
|
||||
self.parameters:prepare(state)
|
||||
self.expression:prepare(state)
|
||||
state.scope:pop()
|
||||
|
||||
self.exports = state.scope:capture():list_exported(state)
|
||||
state.scope:pop()
|
||||
end,
|
||||
}
|
||||
|
||||
package.loaded[...] = Function
|
||||
Closure, ReturnBoundary = ast.Closure, ast.ReturnBoundary
|
||||
|
||||
return Function
|
||||
45
ast/FunctionParameter.lua
Normal file
45
ast/FunctionParameter.lua
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
local ast = require("ast")
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local FunctionParameter
|
||||
FunctionParameter = ast.abstract.Node {
|
||||
type = "function parameter",
|
||||
|
||||
identifier = nil,
|
||||
default = nil, -- can be nil
|
||||
type_check = nil, -- can be nil
|
||||
|
||||
init = function(self, identifier, default, type_check)
|
||||
self.identifier = identifier
|
||||
self.default = default
|
||||
self.type_check = type_check
|
||||
if default then
|
||||
self.format_priority = operator_priority["_=_"]
|
||||
elseif type_check then -- type_check has higher prio than assignment in any case
|
||||
self.format_priority = operator_priority["_::_"]
|
||||
end
|
||||
end,
|
||||
|
||||
_format = function(self, state, prio, ...)
|
||||
local s = self.identifier:format(state, prio, ...)
|
||||
if self.type_check then
|
||||
s = s .. "::" .. self.type_check:format_right(state, operator_priority["_::_"], ...)
|
||||
end
|
||||
if self.default then
|
||||
s = s .. "=" .. self.default:format_right(state, operator_priority["_=_"], ...)
|
||||
end
|
||||
return s
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.identifier, ...)
|
||||
if self.default then fn(self.default, ...) end
|
||||
if self.type_check then fn(self.type_check, ...) end
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
return FunctionParameter:new(self.identifier, self.default, self.type_check and self.type_check:eval(state))
|
||||
end
|
||||
}
|
||||
|
||||
return FunctionParameter
|
||||
44
ast/Identifier.lua
Normal file
44
ast/Identifier.lua
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
local ast = require("ast")
|
||||
local Symbol, String
|
||||
|
||||
local Identifier
|
||||
Identifier = ast.abstract.Node {
|
||||
type = "identifier",
|
||||
|
||||
name = nil,
|
||||
|
||||
init = function(self, name)
|
||||
self.name = name
|
||||
end,
|
||||
|
||||
_hash = function(self)
|
||||
return ("identifier<%q>"):format(self.name)
|
||||
end,
|
||||
|
||||
_format = function(self)
|
||||
return self.name
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
return state.scope:get(self)
|
||||
end,
|
||||
|
||||
to_string = function(self)
|
||||
return String:new(self.name)
|
||||
end,
|
||||
to_symbol = function(self, modifiers)
|
||||
return Symbol:new(self.name, modifiers)
|
||||
end,
|
||||
|
||||
_prepare = function(self, state)
|
||||
if state.scope:defined(self) then
|
||||
state.scope:get(self):prepare(state)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = Identifier
|
||||
|
||||
Symbol, String = ast.Symbol, ast.String
|
||||
|
||||
return Identifier
|
||||
81
ast/List.lua
Normal file
81
ast/List.lua
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
local ast = require("ast")
|
||||
local Branched, Tuple
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local List
|
||||
List = ast.abstract.Runtime {
|
||||
type = "list",
|
||||
|
||||
format_priority = operator_priority["*_"],
|
||||
|
||||
-- note: yeah technically this isn't mutable, only .branched is
|
||||
|
||||
-- note: this a Branched of Tuple, and we *will* forcefully mutate the tuples, so make sure to not disseminate any reference to them outside the List
|
||||
-- unless you want rumors about mutable tuples to spread
|
||||
branched = nil,
|
||||
|
||||
init = function(self, state, from_tuple)
|
||||
from_tuple = from_tuple or Tuple:new()
|
||||
self.branched = Branched:new(state, from_tuple:copy())
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
return "*"..self.branched:format_right(...)
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.branched, ...)
|
||||
end,
|
||||
|
||||
-- List is always created from an evaluated Tuple, so no need to _eval here
|
||||
|
||||
-- create copy of the list in branch if not here
|
||||
-- do this before any mutation
|
||||
-- return the tuple for the current branch
|
||||
_prepare_branch = function(self, state)
|
||||
if not self.branched:in_branch(state) then
|
||||
self.branched:set(state, self.branched:get(state):copy())
|
||||
end
|
||||
return self.branched:get(state)
|
||||
end,
|
||||
|
||||
len = function(self, state)
|
||||
return #self.branched:get(state).list
|
||||
end,
|
||||
iter = function(self, state)
|
||||
return ipairs(self.branched:get(state).list)
|
||||
end,
|
||||
get = function(self, state, index)
|
||||
local list = self.branched:get(state)
|
||||
if index < 0 then index = #list.list + 1 + index end
|
||||
if index > #list.list or index == 0 then error("list index out of bounds") end
|
||||
return list.list[index]
|
||||
end,
|
||||
set = function(self, state, index, val)
|
||||
local list = self:_prepare_branch(state)
|
||||
if index < 0 then index = #list.list + 1 + index end
|
||||
if index > #list.list or index == 0 then error("list index out of bounds") end
|
||||
list.list[index] = val
|
||||
end,
|
||||
insert = function(self, state, val)
|
||||
local l = self:_prepare_branch(state)
|
||||
table.insert(l.list, val)
|
||||
end,
|
||||
remove = function(self, state)
|
||||
local l = self:_prepare_branch(state)
|
||||
table.remove(l.list)
|
||||
end,
|
||||
|
||||
to_tuple = function(self, state)
|
||||
return self.branched:get(state):copy()
|
||||
end,
|
||||
to_lua = function(self, state)
|
||||
return self.branched:get(state):to_lua(state)
|
||||
end,
|
||||
}
|
||||
|
||||
package.loaded[...] = List
|
||||
Branched, Tuple = ast.Branched, ast.Tuple
|
||||
|
||||
return List
|
||||
63
ast/LuaFunction.lua
Normal file
63
ast/LuaFunction.lua
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
local ast = require("ast")
|
||||
local Overloadable = ast.abstract.Overloadable
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local LuaFunction
|
||||
LuaFunction = ast.abstract.Runtime(Overloadable) {
|
||||
type = "lua function",
|
||||
|
||||
parameters = nil, -- ParameterTuple
|
||||
func = nil, -- lua function
|
||||
format_priority = operator_priority["$_"],
|
||||
|
||||
init = function(self, parameters, func)
|
||||
self.parameters = parameters
|
||||
self.func = func
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.parameters, ...)
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
if self.parameters.assignment then
|
||||
return "$"..self.parameters:format(...).."; <lua function>"
|
||||
else
|
||||
return "$"..self.parameters:format(...).." <lua function>"
|
||||
end
|
||||
end,
|
||||
|
||||
compatible_with_arguments = function(self, state, args)
|
||||
return args:match_parameter_tuple(state, self.parameters)
|
||||
end,
|
||||
format_parameters = function(self, state)
|
||||
return self.parameters:format(state)
|
||||
end,
|
||||
call_compatible = function(self, state, args)
|
||||
local lua_args = { state }
|
||||
|
||||
state.scope:push()
|
||||
|
||||
args:bind_parameter_tuple(state, self.parameters)
|
||||
for _, param in ipairs(self.parameters.list) do
|
||||
table.insert(lua_args, state.scope:get(param.identifier))
|
||||
end
|
||||
|
||||
state.scope:pop()
|
||||
|
||||
local r = self.func(table.unpack(lua_args))
|
||||
assert(r, "lua function returned no value")
|
||||
return r
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
return LuaFunction:new(self.parameters:eval(state), self.func)
|
||||
end,
|
||||
|
||||
to_lua = function(self, state)
|
||||
return self.func
|
||||
end,
|
||||
}
|
||||
|
||||
return LuaFunction
|
||||
20
ast/Nil.lua
Normal file
20
ast/Nil.lua
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
local ast = require("ast")
|
||||
|
||||
return ast.abstract.Node {
|
||||
type = "nil",
|
||||
_evaluated = true, -- no evaluation needed
|
||||
|
||||
init = function(self) end,
|
||||
|
||||
_hash = function(self)
|
||||
return "nil"
|
||||
end,
|
||||
|
||||
_format = function(self)
|
||||
return "()"
|
||||
end,
|
||||
|
||||
to_lua = function(self, state) return nil end,
|
||||
|
||||
truthy = function(self) return false end
|
||||
}
|
||||
25
ast/Number.lua
Normal file
25
ast/Number.lua
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
local ast = require("ast")
|
||||
|
||||
local Number
|
||||
Number = ast.abstract.Node {
|
||||
type = "number",
|
||||
_evaluated = true, -- no evaluation needed
|
||||
|
||||
number = nil,
|
||||
|
||||
init = function(self, number)
|
||||
self.number = number
|
||||
end,
|
||||
|
||||
_hash = function(self)
|
||||
return ("number<%s>"):format(self.number)
|
||||
end,
|
||||
|
||||
_format = function(self)
|
||||
return tostring(self.number)
|
||||
end,
|
||||
|
||||
to_lua = function(self, state) return self.number end,
|
||||
}
|
||||
|
||||
return Number
|
||||
62
ast/Overload.lua
Normal file
62
ast/Overload.lua
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
local ast = require("ast")
|
||||
|
||||
local Overload
|
||||
Overload = ast.abstract.Node {
|
||||
type = "overload",
|
||||
_evaluated = true,
|
||||
|
||||
list = nil,
|
||||
|
||||
init = function(self, ...)
|
||||
self.list = { ... }
|
||||
end,
|
||||
insert = function(self, val) -- only for construction
|
||||
table.insert(self.list, val)
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
local s = "overload<"
|
||||
for i, e in ipairs(self.list) do
|
||||
s = s .. e:format(...)
|
||||
if i < #self.list then s = s .. ", " end
|
||||
end
|
||||
return s..">"
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
for _, e in ipairs(self.list) do
|
||||
fn(e, ...)
|
||||
end
|
||||
end,
|
||||
|
||||
call = function(self, state, args)
|
||||
local failure = {} -- list of failure messages (kept until we find the first success)
|
||||
local success, success_specificity, success_secondary_specificity = nil, -1, -1
|
||||
-- some might think that iterating a list for every function call is a terrible idea, but that list has a fixed number of elements, so big O notation says suck it up
|
||||
for _, fn in ipairs(self.list) do
|
||||
local specificity, secondary_specificity = fn:compatible_with_arguments(state, args)
|
||||
if specificity then
|
||||
if specificity > success_specificity then
|
||||
success, success_specificity, success_secondary_specificity = fn, specificity, secondary_specificity
|
||||
elseif specificity == success_specificity then
|
||||
if secondary_specificity > success_secondary_specificity then
|
||||
success, success_specificity, success_secondary_specificity = fn, specificity, secondary_specificity
|
||||
elseif secondary_specificity == success_secondary_specificity then
|
||||
error(("more than one function match %s, matching functions were at least (specificity %s.%s):\n\t• %s\n\t• %s"):format(args:format(state), specificity, secondary_specificity, fn:format_parameters(state), success:format_parameters(state)), 0)
|
||||
end
|
||||
end
|
||||
-- no need to add error message for less specific function since we already should have at least one success
|
||||
elseif not success then
|
||||
table.insert(failure, fn:format_parameters(state) .. ": " .. secondary_specificity)
|
||||
end
|
||||
end
|
||||
if success then
|
||||
return success:call_compatible(state, args)
|
||||
else
|
||||
-- error
|
||||
error(("no function match %s, possible functions were:\n\t• %s"):format(args:format(state), table.concat(failure, "\n\t• ")), 0)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
return Overload
|
||||
25
ast/Pair.lua
Normal file
25
ast/Pair.lua
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
local ast = require("ast")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return ast.abstract.Runtime {
|
||||
type = "pair",
|
||||
|
||||
name = nil,
|
||||
value = nil,
|
||||
format_priority = operator_priority["_:_"],
|
||||
|
||||
init = function(self, name, value)
|
||||
self.name = name
|
||||
self.value = value
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.name, ...)
|
||||
fn(self.value, ...)
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
return ("%s:%s"):format(self.name:format(...), self.value:format(...))
|
||||
end,
|
||||
}
|
||||
67
ast/ParameterTuple.lua
Normal file
67
ast/ParameterTuple.lua
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
local ast = require("ast")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local ParameterTuple
|
||||
ParameterTuple = ast.abstract.Node {
|
||||
type = "parameter tuple",
|
||||
|
||||
assignment = false,
|
||||
list = nil,
|
||||
min_arity = 0,
|
||||
max_arity = 0,
|
||||
|
||||
eval_depth = 0, -- scope deth where this parametertuple was evaluated, used as secondary specificity
|
||||
|
||||
init = function(self, ...)
|
||||
self.list = {...}
|
||||
end,
|
||||
insert = function(self, val) -- only for construction
|
||||
assert(not self.assignment, "can't add new parameters after assignment parameter was added")
|
||||
table.insert(self.list, val)
|
||||
self.max_arity = self.max_arity + 1
|
||||
if not val.default then
|
||||
self.min_arity = self.min_arity + 1
|
||||
end
|
||||
end,
|
||||
insert_assignment = function(self, val) -- only for construction
|
||||
self:insert(val)
|
||||
self.assignment = true
|
||||
self.format_priority = operator_priority["_=_"]
|
||||
end,
|
||||
|
||||
_format = function(self, state, prio, ...)
|
||||
local l = {}
|
||||
for i, e in ipairs(self.list) do
|
||||
if i < self.max_arity or not self.assignment then
|
||||
table.insert(l, e:format(state, operator_priority["_,_"], ...))
|
||||
end
|
||||
end
|
||||
local s = ("(%s)"):format(table.concat(l, ", "))
|
||||
if self.assignment then
|
||||
s = s .. (" = %s"):format(self.list[#self.list]:format_right(state, operator_priority["_=_"], ...))
|
||||
end
|
||||
return s
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
for _, e in ipairs(self.list) do
|
||||
fn(e, ...)
|
||||
end
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
local r = ParameterTuple:new()
|
||||
for i, param in ipairs(self.list) do
|
||||
if i < self.max_arity or not self.assignment then
|
||||
r:insert(param:eval(state))
|
||||
else
|
||||
r:insert_assignment(param:eval(state))
|
||||
end
|
||||
end
|
||||
r.eval_depth = state.scope:depth()
|
||||
return r
|
||||
end
|
||||
}
|
||||
|
||||
return ParameterTuple
|
||||
34
ast/Quote.lua
Normal file
34
ast/Quote.lua
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
-- prevent an expression from being immediately evaluated, and instead only evaluate it when the node is explicitely called
|
||||
-- it can be used to evaluate the expression on demand, as if the quote call AST was simply replaced by the unevaluated associated expression AST (like a macro)
|
||||
-- keep in mind that this thus bypass any scoping rule, closure, etc.
|
||||
--
|
||||
-- used for infix operators where the evaluation of the right term depends of the left one (lazy boolean operators, conditionals, etc.)
|
||||
|
||||
local ast = require("ast")
|
||||
|
||||
local Quote
|
||||
Quote = ast.abstract.Node {
|
||||
type = "quote",
|
||||
|
||||
expression = nil,
|
||||
|
||||
init = function(self, expression)
|
||||
self.expression = expression
|
||||
self.format_priority = expression.format_priority
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
return self.expression:format(...) -- Quote is generated transparently by operators
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.expression, ...)
|
||||
end,
|
||||
|
||||
call = function(self, state, args)
|
||||
assert(args.arity == 0, "Quote! does not accept arguments")
|
||||
return self.expression:eval(state)
|
||||
end
|
||||
}
|
||||
|
||||
return Quote
|
||||
60
ast/Resumable.lua
Normal file
60
ast/Resumable.lua
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
local ast = require("ast")
|
||||
local Table
|
||||
|
||||
local resumable_manager
|
||||
|
||||
local Resumable
|
||||
Resumable = ast.abstract.Runtime {
|
||||
type = "resumable",
|
||||
|
||||
resuming = false,
|
||||
|
||||
expression = nil,
|
||||
scope = nil,
|
||||
data = nil,
|
||||
|
||||
init = function(self, state, expression, scope, data)
|
||||
self.expression = expression
|
||||
self.scope = scope
|
||||
self.data = data or Table:new(state)
|
||||
end,
|
||||
|
||||
_format = function(self)
|
||||
return "<resumable>"
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.expression, ...)
|
||||
fn(self.data, ...)
|
||||
fn(self.scope, ...)
|
||||
end,
|
||||
|
||||
-- returns a copy with the data copied
|
||||
capture = function(self, state)
|
||||
return Resumable:new(state, self.expression, self.scope, self.data:copy(state))
|
||||
end,
|
||||
|
||||
-- resume from this resumable
|
||||
call = function(self, state, args)
|
||||
assert(args.arity == 0, "Resumable! does not accept arguments")
|
||||
|
||||
state.scope:push(self.scope)
|
||||
|
||||
local resuming = self:capture(state)
|
||||
resuming.resuming = true
|
||||
resumable_manager:push(state, resuming)
|
||||
local r = self.expression:eval(state)
|
||||
resumable_manager:pop(state)
|
||||
|
||||
state.scope:pop()
|
||||
|
||||
return r
|
||||
end,
|
||||
}
|
||||
|
||||
package.loaded[...] = Resumable
|
||||
Table = ast.Table
|
||||
|
||||
resumable_manager = require("state.resumable_manager")
|
||||
|
||||
return Resumable
|
||||
44
ast/ResumeParentFunction.lua
Normal file
44
ast/ResumeParentFunction.lua
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
-- intended to be wrapped in a Function, so that when resuming from the function, will keep resuming to where the function was called from
|
||||
-- used in Choices to resume back from where the event was flushed
|
||||
-- note: when resuming, the return value will be discarded, instead returning what the parent function will return
|
||||
|
||||
local ast = require("ast")
|
||||
local ArgumentTuple
|
||||
|
||||
local resumable_manager
|
||||
|
||||
local ResumeParentFunction = ast.abstract.Node {
|
||||
type = "resume parent function",
|
||||
|
||||
expression = nil,
|
||||
|
||||
init = function(self, expression)
|
||||
self.expression = expression
|
||||
self.format_priority = expression.format_priority
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
return self.expression:format(...)
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.expression, ...)
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
if resumable_manager:resuming(state, self) then
|
||||
self.expression:eval(state)
|
||||
return resumable_manager:get_data(state, self):call(state, ArgumentTuple:new())
|
||||
else
|
||||
resumable_manager:set_data(state, self, resumable_manager:capture(state, 1))
|
||||
return self.expression:eval(state)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = ResumeParentFunction
|
||||
ArgumentTuple = ast.ArgumentTuple
|
||||
|
||||
resumable_manager = require("state.resumable_manager")
|
||||
|
||||
return ResumeParentFunction
|
||||
33
ast/Return.lua
Normal file
33
ast/Return.lua
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
local ast = require("ast")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local Return
|
||||
Return = ast.abstract.Node {
|
||||
type = "return",
|
||||
|
||||
expression = nil,
|
||||
format_priority = operator_priority["@_"],
|
||||
|
||||
init = function(self, expression)
|
||||
self.expression = expression
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
return ("@%s"):format(self.expression:format_right(...))
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.expression, ...)
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
return Return:new(self.expression:eval(state))
|
||||
end,
|
||||
|
||||
to_lua = function(self, state)
|
||||
return self.expression:to_lua(state)
|
||||
end
|
||||
}
|
||||
|
||||
return Return
|
||||
37
ast/ReturnBoundary.lua
Normal file
37
ast/ReturnBoundary.lua
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
-- used stop propagating Return when leaving functions
|
||||
|
||||
local ast = require("ast")
|
||||
local Return
|
||||
|
||||
local ReturnBoundary = ast.abstract.Node {
|
||||
type = "return boundary",
|
||||
|
||||
expression = nil,
|
||||
|
||||
init = function(self, expression)
|
||||
self.expression = expression
|
||||
self.format_priority = self.expression.format_priority
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
return self.expression:format(...)
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.expression, ...)
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
local exp = self.expression:eval(state)
|
||||
if Return:is(exp) then
|
||||
return exp.expression
|
||||
else
|
||||
return exp
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = ReturnBoundary
|
||||
Return = ast.Return
|
||||
|
||||
return ReturnBoundary
|
||||
34
ast/String.lua
Normal file
34
ast/String.lua
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
local ast = require("ast")
|
||||
local Identifier
|
||||
|
||||
local String = ast.abstract.Node {
|
||||
type = "string",
|
||||
_evaluated = true, -- no evaluation needed
|
||||
|
||||
string = nil,
|
||||
|
||||
init = function(self, str)
|
||||
self.string = str
|
||||
end,
|
||||
|
||||
_hash = function(self)
|
||||
return ("string<%q>"):format(self.string)
|
||||
end,
|
||||
|
||||
_format = function(self)
|
||||
return ("%q"):format(self.string)
|
||||
end,
|
||||
|
||||
to_lua = function(self, state)
|
||||
return self.string
|
||||
end,
|
||||
|
||||
to_identifier = function(self)
|
||||
return Identifier:new(self.string)
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = String
|
||||
Identifier = ast.Identifier
|
||||
|
||||
return String
|
||||
53
ast/StringInterpolation.lua
Normal file
53
ast/StringInterpolation.lua
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
local ast = require("ast")
|
||||
local String
|
||||
|
||||
local StringInterpolation = ast.abstract.Node {
|
||||
type = "string interpolation",
|
||||
|
||||
list = nil,
|
||||
|
||||
init = function(self, ...)
|
||||
self.list = {...}
|
||||
end,
|
||||
insert = function(self, val) -- only for construction
|
||||
table.insert(self.list, val)
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
for _, e in ipairs(self.list) do
|
||||
fn(e, ...)
|
||||
end
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
local l = {}
|
||||
for _, e in ipairs(self.list) do
|
||||
if String:is(e) then
|
||||
local t = e.string:gsub("\\", "\\\\"):gsub("\n", "\\n"):gsub("\t", "\\t"):gsub("\"", "\\\"")
|
||||
table.insert(l, t)
|
||||
else
|
||||
table.insert(l, ("{%s}"):format(e:format(...)))
|
||||
end
|
||||
end
|
||||
return ("\"%s\""):format(table.concat(l))
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
local t = {}
|
||||
for _, e in ipairs(self.list) do
|
||||
local r = e:eval(state)
|
||||
if String:is(r) then
|
||||
r = r.string
|
||||
else
|
||||
r = r:format(state)
|
||||
end
|
||||
table.insert(t, r)
|
||||
end
|
||||
return String:new(table.concat(t))
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = StringInterpolation
|
||||
String = ast.String
|
||||
|
||||
return StringInterpolation
|
||||
121
ast/Struct.lua
Normal file
121
ast/Struct.lua
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
local ast = require("ast")
|
||||
local Pair, Number, Nil
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local Struct
|
||||
|
||||
local TupleToStruct = ast.abstract.Node {
|
||||
type = "tuple to struct",
|
||||
|
||||
tuple = nil,
|
||||
|
||||
init = function(self, tuple)
|
||||
self.tuple = tuple
|
||||
end,
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.tuple, ...)
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
return self.tuple:format(...):gsub("^%[", "{"):gsub("%]$", "}")
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
local t = Struct:new()
|
||||
for i, e in ipairs(self.tuple.list) do
|
||||
if Pair:is(e) then
|
||||
t:set(e.name, e.value)
|
||||
else
|
||||
t:set(Number:new(i), e)
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
}
|
||||
|
||||
Struct = ast.abstract.Runtime {
|
||||
type = "struct",
|
||||
|
||||
table = nil,
|
||||
|
||||
init = function(self)
|
||||
self.table = {}
|
||||
end,
|
||||
set = function(self, key, value) -- only for construction
|
||||
self.table[key:hash()] = { key, value }
|
||||
end,
|
||||
include = function(self, other) -- only for construction
|
||||
for _, e in pairs(other.table) do
|
||||
self:set(e[1], e[2])
|
||||
end
|
||||
end,
|
||||
|
||||
copy = function(self)
|
||||
local s = Struct:new()
|
||||
for _, e in pairs(self.table) do
|
||||
s:set(e[1], e[2])
|
||||
end
|
||||
return s
|
||||
end,
|
||||
|
||||
-- build from (non-evaluated) tuple
|
||||
-- results needs to be evaluated
|
||||
from_tuple = function(self, tuple)
|
||||
return TupleToStruct:new(tuple)
|
||||
end,
|
||||
|
||||
_format = function(self, state, prio, ...)
|
||||
local l = {}
|
||||
for _, e in pairs(self.table) do
|
||||
-- _:_ has higher priority than _,_
|
||||
table.insert(l, e[1]:format(state, operator_priority["_:_"], ...)..":"..e[2]:format_right(state, operator_priority["_:_"], ...))
|
||||
end
|
||||
return ("{%s}"):format(table.concat(l, ", "))
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
for _, e in pairs(self.table) do
|
||||
fn(e[1], ...)
|
||||
fn(e[2], ...)
|
||||
end
|
||||
end,
|
||||
|
||||
-- need to redefine hash to include a table.sort as pairs() in :traverse is non-deterministic
|
||||
_hash = function(self)
|
||||
local t = {}
|
||||
for _, e in pairs(self.table) do
|
||||
table.insert(t, ("%s;%s"):format(e[1]:hash(), e[2]:hash()))
|
||||
end
|
||||
table.sort(t)
|
||||
return ("%s<%s>"):format(self.type, table.concat(t, ";"))
|
||||
end,
|
||||
|
||||
-- regarding eval: Struct is built from TupleToStruct function call which already eval, so every Struct should be fully evaluated
|
||||
|
||||
to_lua = function(self, state)
|
||||
local l = {}
|
||||
for _, e in ipairs(self.table) do
|
||||
l[e[1]:to_lua(state)] = e[2]:to_lua(state)
|
||||
end
|
||||
return l
|
||||
end,
|
||||
|
||||
get = function(self, key)
|
||||
local hash = key:hash()
|
||||
if self.table[hash] then
|
||||
return self.table[hash][2]
|
||||
else
|
||||
return Nil:new()
|
||||
end
|
||||
end,
|
||||
has = function(self, key)
|
||||
local hash = key:hash()
|
||||
return not not self.table[hash]
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = Struct
|
||||
Pair, Number, Nil = ast.Pair, ast.Number, ast.Nil
|
||||
|
||||
return Struct
|
||||
78
ast/Symbol.lua
Normal file
78
ast/Symbol.lua
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
local ast = require("ast")
|
||||
local Identifier, String
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local Symbol
|
||||
Symbol = ast.abstract.Node {
|
||||
type = "symbol",
|
||||
|
||||
string = nil,
|
||||
constant = nil, -- bool
|
||||
type_check = nil, -- exp
|
||||
|
||||
exported = nil, -- bool
|
||||
persistent = nil, -- bool, imply exported
|
||||
|
||||
confined_to_branch = nil, -- bool
|
||||
|
||||
init = function(self, str, modifiers)
|
||||
modifiers = modifiers or {}
|
||||
self.string = str
|
||||
self.constant = modifiers.constant
|
||||
self.persistent = modifiers.persistent
|
||||
self.type_check = modifiers.type_check
|
||||
self.confined_to_branch = modifiers.confined_to_branch
|
||||
self.exported = modifiers.exported or modifiers.persistent
|
||||
if self.type_check then
|
||||
self.format_priority = operator_priority["_::_"]
|
||||
end
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
return Symbol:new(self.string, {
|
||||
constant = self.constant,
|
||||
persistent = self.persistent,
|
||||
type_check = self.type_check and self.type_check:eval(state),
|
||||
confined_to_branch = self.confined_to_branch,
|
||||
exported = self.exported
|
||||
})
|
||||
end,
|
||||
|
||||
_hash = function(self)
|
||||
return ("symbol<%q>"):format(self.string)
|
||||
end,
|
||||
|
||||
_format = function(self, state, prio, ...)
|
||||
local s = ":"
|
||||
if self.constant then
|
||||
s = s .. ":"
|
||||
end
|
||||
if self.persistent then
|
||||
s = s .. "&"
|
||||
end
|
||||
if self.exported then
|
||||
s = s .. "@"
|
||||
end
|
||||
s = s .. self.string
|
||||
if self.type_check then
|
||||
s = s .. "::" .. self.type_check:format_right(state, operator_priority["_::_"], ...)
|
||||
end
|
||||
return s
|
||||
end,
|
||||
|
||||
to_lua = function(self, state)
|
||||
return self.string
|
||||
end,
|
||||
to_identifier = function(self)
|
||||
return Identifier:new(self.string)
|
||||
end,
|
||||
to_string = function(self)
|
||||
return String:new(self.string)
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = Symbol
|
||||
Identifier, String = ast.Identifier, ast.String
|
||||
|
||||
return Symbol
|
||||
85
ast/Table.lua
Normal file
85
ast/Table.lua
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
local ast = require("ast")
|
||||
local Branched, Struct, Nil = ast.Branched, ast.Struct, ast.Nil
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local Table
|
||||
Table = ast.abstract.Runtime {
|
||||
type = "table",
|
||||
|
||||
format_priority = operator_priority["*_"],
|
||||
|
||||
-- note: technically this isn't mutable, only .branched is
|
||||
|
||||
-- note: this a Branched of Struct, and we *will* forcefully mutate the tuples, so make sure to not disseminate any reference to them outside the Table
|
||||
-- unless you want rumors about mutable structs to spread
|
||||
branched = nil,
|
||||
|
||||
init = function(self, state, from_struct)
|
||||
from_struct = from_struct or Struct:new()
|
||||
self.branched = Branched:new(state, from_struct:copy())
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
return "*"..self.branched:format_right(...)
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.branched, ...)
|
||||
end,
|
||||
|
||||
-- Table is always created from an evaluated Struct, so no need to _eval here
|
||||
|
||||
-- create copy of the table in branch if not here
|
||||
-- do this before any mutation
|
||||
-- return the struct for the current branch
|
||||
_prepare_branch = function(self, state)
|
||||
if not self.branched:in_branch(state) then
|
||||
self.branched:set(state, self.branched:get(state):copy())
|
||||
end
|
||||
return self.branched:get(state)
|
||||
end,
|
||||
|
||||
get = function(self, state, key)
|
||||
local s = self.branched:get(state)
|
||||
return s:get(key)
|
||||
end,
|
||||
set = function(self, state, key, val)
|
||||
local s = self:_prepare_branch(state)
|
||||
local hash = key:hash()
|
||||
if Nil:is(val) then
|
||||
s.table[hash] = nil
|
||||
else
|
||||
s.table[hash] = { key, val }
|
||||
end
|
||||
end,
|
||||
has = function(self, state, key)
|
||||
local s = self.branched:get(state)
|
||||
return s:has(key)
|
||||
end,
|
||||
iter = function(self, state)
|
||||
local t, h = self.branched:get(state).table, nil
|
||||
return function()
|
||||
local e
|
||||
h, e = next(t, h)
|
||||
if h == nil then return nil
|
||||
else return e[1], e[2]
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
to_struct = function(self, state)
|
||||
return self.branched:get(state):copy()
|
||||
end,
|
||||
to_lua = function(self, state)
|
||||
return self.branched:get(state):to_lua(state)
|
||||
end,
|
||||
copy = function(self, state)
|
||||
return Table:new(state, self:to_struct(state))
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = Table
|
||||
Branched, Struct, Nil = ast.Branched, ast.Struct, ast.Nil
|
||||
|
||||
return Table
|
||||
36
ast/Text.lua
Normal file
36
ast/Text.lua
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
local ast = require("ast")
|
||||
local AutoCall, Event, Runtime = ast.abstract.AutoCall, ast.abstract.Event, ast.abstract.Runtime
|
||||
|
||||
return Runtime(AutoCall, Event) {
|
||||
type = "text",
|
||||
|
||||
list = nil, -- { { String, tag Table }, ... }
|
||||
|
||||
init = function(self)
|
||||
self.list = {}
|
||||
end,
|
||||
insert = function(self, str, tags) -- only for construction
|
||||
table.insert(self.list, { str, tags })
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
for _, e in ipairs(self.list) do
|
||||
fn(e[1], ...)
|
||||
fn(e[2], ...)
|
||||
end
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
local t = {}
|
||||
for _, e in ipairs(self.list) do
|
||||
table.insert(t, ("%s%s"):format(e[2]:format(...), e[1]:format(...)))
|
||||
end
|
||||
return ("| %s |"):format(table.concat(t, " "))
|
||||
end,
|
||||
|
||||
-- Text comes from TextInterpolation which already evals the contents
|
||||
|
||||
to_event_data = function(self)
|
||||
return self
|
||||
end
|
||||
}
|
||||
59
ast/TextInterpolation.lua
Normal file
59
ast/TextInterpolation.lua
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
local ast = require("ast")
|
||||
local Text, String
|
||||
|
||||
local tag_manager = require("state.tag_manager")
|
||||
|
||||
local TextInterpolation = ast.abstract.Node {
|
||||
type = "text interpolation",
|
||||
|
||||
list = nil,
|
||||
|
||||
init = function(self, ...)
|
||||
self.list = {...}
|
||||
end,
|
||||
insert = function(self, val) -- only for construction
|
||||
table.insert(self.list, val)
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
for _, e in ipairs(self.list) do
|
||||
fn(e, ...)
|
||||
end
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
local l = {}
|
||||
for _, e in ipairs(self.list) do
|
||||
if String:is(e) then
|
||||
local t = e.string:gsub("\\", "\\\\"):gsub("\n", "\\n"):gsub("\t", "\\t"):gsub("\"", "\\\"")
|
||||
table.insert(l, t)
|
||||
else
|
||||
table.insert(l, ("{%s}"):format(e:format(...)))
|
||||
end
|
||||
end
|
||||
return ("| %s|"):format(table.concat(l))
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
local t = Text:new()
|
||||
local tags = tag_manager:get(state)
|
||||
for _, e in ipairs(self.list) do
|
||||
local r = e:eval(state)
|
||||
if String:is(r) then
|
||||
t:insert(r, tags)
|
||||
elseif Text:is(r) then
|
||||
for _, v in ipairs(r.list) do
|
||||
t:insert(v[1], v[2])
|
||||
end
|
||||
else
|
||||
t:insert(String:new(r:format(state)), tags)
|
||||
end
|
||||
end
|
||||
return t
|
||||
end,
|
||||
}
|
||||
|
||||
package.loaded[...] = TextInterpolation
|
||||
Text, String = ast.Text, ast.String
|
||||
|
||||
return TextInterpolation
|
||||
66
ast/Tuple.lua
Normal file
66
ast/Tuple.lua
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
local ast = require("ast")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local Tuple
|
||||
Tuple = ast.abstract.Node {
|
||||
type = "tuple",
|
||||
explicit = true, -- false for implicitely created tuples, e.g. 1,2,3 without the brackets []
|
||||
|
||||
list = nil,
|
||||
|
||||
init = function(self, ...)
|
||||
self.list = { ... }
|
||||
end,
|
||||
insert = function(self, val) -- only for construction
|
||||
table.insert(self.list, val)
|
||||
end,
|
||||
|
||||
_format = function(self, state, prio, ...)
|
||||
local l = {}
|
||||
for _, e in ipairs(self.list) do
|
||||
table.insert(l, e:format(state, operator_priority["_,_"], ...))
|
||||
end
|
||||
return ("[%s]"):format(table.concat(l, ", "))
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
for _, e in ipairs(self.list) do
|
||||
fn(e, ...)
|
||||
end
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
local t = Tuple:new()
|
||||
for _, e in ipairs(self.list) do
|
||||
t:insert(e:eval(state))
|
||||
end
|
||||
if not self.explicit then
|
||||
t.explicit = false
|
||||
end
|
||||
return t
|
||||
end,
|
||||
copy = function(self)
|
||||
local t = Tuple:new()
|
||||
for _, e in ipairs(self.list) do
|
||||
t:insert(e)
|
||||
end
|
||||
return t
|
||||
end,
|
||||
|
||||
to_lua = function(self, state)
|
||||
local l = {}
|
||||
for _, e in ipairs(self.list) do
|
||||
table.insert(l, e:to_lua(state))
|
||||
end
|
||||
return l
|
||||
end,
|
||||
|
||||
get = function(self, index)
|
||||
if index < 0 then index = #self.list + 1 + index end
|
||||
if index > #self.list or index == 0 then error("tuple index out of bounds") end
|
||||
return self.list[index]
|
||||
end
|
||||
}
|
||||
|
||||
return Tuple
|
||||
24
ast/Typed.lua
Normal file
24
ast/Typed.lua
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
local ast = require("ast")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return ast.abstract.Runtime {
|
||||
type = "typed",
|
||||
|
||||
expression = nil,
|
||||
type_expression = nil,
|
||||
|
||||
init = function(self, type, expression)
|
||||
self.type_expression = type
|
||||
self.expression = expression
|
||||
end,
|
||||
|
||||
_format = function(self, state, prio, ...)
|
||||
return ("type(%s, %s)"):format(self.type_expression:format(state, operator_priority["_,_"], ...), self.expression:format_right(state, operator_priority["_,_"], ...))
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.type_expression, ...)
|
||||
fn(self.expression, ...)
|
||||
end
|
||||
}
|
||||
8
ast/abstract/AutoCall.lua
Normal file
8
ast/abstract/AutoCall.lua
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
-- called automatically when returned by one of the expression in a block
|
||||
|
||||
local ast = require("ast")
|
||||
|
||||
return ast.abstract.Node {
|
||||
type = "auto call",
|
||||
init = false
|
||||
}
|
||||
22
ast/abstract/Event.lua
Normal file
22
ast/abstract/Event.lua
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
-- for nodes that can be written to the event buffer
|
||||
|
||||
local ast = require("ast")
|
||||
|
||||
return ast.abstract.Node {
|
||||
type = "event",
|
||||
init = false,
|
||||
|
||||
-- returns value that will be yielded by the whole event buffer data on flush
|
||||
-- by default a list of what is returned by :to_event_data for each event of the buffer
|
||||
build_event_data = function(self, state, event_buffer)
|
||||
local l = {}
|
||||
for _, event in event_buffer:iter(state) do
|
||||
table.insert(l, event:to_event_data(state))
|
||||
end
|
||||
return l
|
||||
end,
|
||||
to_event_data = function(self, state) error("unimplemented") end,
|
||||
|
||||
-- post_flush_callback(self, state, event_buffer, event_data)
|
||||
post_flush_callback = false
|
||||
}
|
||||
282
ast/abstract/Node.lua
Normal file
282
ast/abstract/Node.lua
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
local class = require("class")
|
||||
local fmt = require("common").fmt
|
||||
local binser = require("lib.binser")
|
||||
|
||||
-- NODES SHOULD BE IMMUTABLE AFTER CREATION IF POSSIBLE!
|
||||
-- i don't think i actually rely on this behavior for anything but it makes me feel better about life in general
|
||||
-- (well, unless node.mutable == true, in which case go ahead and break my little heart)
|
||||
-- UPDATE: i actually assumed nodes to be immutable by default in a lot of places now, thank you past me, it did indeed make me feel better about life in general
|
||||
|
||||
-- reminder: when requiring AST nodes somewhere, try to do it at the end of the file. and if you need to require something in this file, do it in the :_i_hate_cycles method.
|
||||
-- i've had enough headaches with cyclics references and nodes required several times...
|
||||
|
||||
local uuid = require("common").uuid
|
||||
|
||||
local State, Runtime
|
||||
local resumable_manager
|
||||
|
||||
local custom_call_identifier
|
||||
|
||||
local context_max_length = 50
|
||||
local function cutoff_text(str)
|
||||
if str:match("\n") or utf8.len(str) > context_max_length then
|
||||
local cut_pos = math.min((str:match("()\n") or math.huge)-1, (utf8.offset(str, context_max_length, 1) or math.huge)-1)
|
||||
str = str:sub(1, cut_pos) .. "…"
|
||||
end
|
||||
return str
|
||||
end
|
||||
local function format_error(state, node, message)
|
||||
local ctx = cutoff_text(node:format(state)) -- get some context code around error
|
||||
return fmt("%{red}%s%{reset}\n\t↳ from %{underline}%s%{reset} in %s: %{dim}%s", message, node.source, node.type, ctx)
|
||||
end
|
||||
|
||||
-- traverse helpers
|
||||
local traverse
|
||||
traverse = {
|
||||
set_source = function(self, source)
|
||||
self:set_source(source)
|
||||
end,
|
||||
prepare = function(self, state)
|
||||
self:prepare(state)
|
||||
end,
|
||||
merge = function(self, state, cache)
|
||||
self:merge(state, cache)
|
||||
end,
|
||||
hash = function(self, t)
|
||||
table.insert(t, self:hash())
|
||||
end
|
||||
}
|
||||
|
||||
local Node
|
||||
Node = class {
|
||||
type = "node",
|
||||
source = "?",
|
||||
mutable = false,
|
||||
|
||||
-- abstract class
|
||||
-- must be redefined
|
||||
init = false,
|
||||
|
||||
-- set the source of this node and its children (unless a source is already set)
|
||||
-- to be preferably used during construction only
|
||||
set_source = function(self, source)
|
||||
local str_source = tostring(source)
|
||||
if self.source == "?" then
|
||||
self.source = str_source
|
||||
self:traverse(traverse.set_source, str_source)
|
||||
end
|
||||
return self
|
||||
end,
|
||||
|
||||
-- call function callback with args ... on the children Nodes of this Node
|
||||
-- by default, assumes no children Nodes
|
||||
-- you will want to redefine this for nodes with children nodes
|
||||
-- (note: when calling, remember that cycles are common place in the AST, so stay safe use a cache)
|
||||
traverse = function(self, callback, ...) end,
|
||||
|
||||
-- returns new AST
|
||||
-- whatever this function returned is assumed to be already evaluated
|
||||
-- the actual evaluation is done in _eval
|
||||
eval = function(self, state)
|
||||
if self._evaluated then return self end
|
||||
local s, r = pcall(self._eval, self, state)
|
||||
if s then
|
||||
r._evaluated = true
|
||||
return r
|
||||
else
|
||||
error(format_error(state, self, r), 0)
|
||||
end
|
||||
end,
|
||||
_evaluated = false, -- if true, node is assumed to be already evaluated and :eval will be the identity function
|
||||
-- evaluate this node and return the result
|
||||
-- by default assume the node can't be evaluated further and return itself; redefine for everything else, probably
|
||||
-- THIS SHOULD NOT MUTATE THE CURRENT NODE; create and return a new Node instead! (even if node is mutable)
|
||||
_eval = function(self, state)
|
||||
return self
|
||||
end,
|
||||
|
||||
-- prepare the AST after parsing and before evaluation
|
||||
-- this behave like a cached :traverse through the AST, except this keeps track of the scope stack
|
||||
-- i.e. when :prepare is called on a node, it should be in a similar scope stack context as will be when it will be evaluated
|
||||
-- used to predefine exported variables and other compile-time variable handling
|
||||
-- note: the state here is a temporary state only used during the prepare step
|
||||
-- the actual preparation is done in _prepare
|
||||
-- (this can mutate the node as needed and is automatically called after each parse)
|
||||
prepare = function(self, state)
|
||||
assert(not Runtime:issub(self), ("can't prepare a %s node that should only exist at runtime"):format(self.type))
|
||||
state = state or State:new()
|
||||
if self._prepared then return end
|
||||
local s, r = pcall(self._prepare, self, state)
|
||||
if s then
|
||||
self._prepared = true
|
||||
else
|
||||
error(format_error(state, self, r), 0)
|
||||
end
|
||||
end,
|
||||
_prepared = false, -- indicate that the node was prepared and :prepare should nop
|
||||
-- prepare this node. can mutate the node (considered to be part of construction).
|
||||
_prepare = function(self, state)
|
||||
self:traverse(traverse.prepare, state)
|
||||
end,
|
||||
|
||||
-- same as eval, but make the evaluated expression as a resume boundary
|
||||
-- i.e. if a checkpoint is defined somewhere in this eval, it will start back from this node eval when resuming
|
||||
eval_resumable = function(self, state)
|
||||
return resumable_manager:eval(state, self)
|
||||
end,
|
||||
-- set the current resume data for this node
|
||||
-- (relevant inside :eval)
|
||||
set_resume_data = function(self, state, data)
|
||||
resumable_manager:set_data(state, self, data)
|
||||
end,
|
||||
-- get the current resume data for this node
|
||||
get_resume_data = function(self, state)
|
||||
return resumable_manager:get_data(state, self)
|
||||
end,
|
||||
-- returns true if the current node is in a resuming state
|
||||
-- (relevant inside :eval)
|
||||
resuming = function(self, state)
|
||||
return resumable_manager:resuming(state, self)
|
||||
end,
|
||||
|
||||
-- return result AST
|
||||
-- arg is a ArgumentTuple node (already evaluated)
|
||||
-- redefine if relevant
|
||||
call = function(self, state, arg)
|
||||
if state.scope:defined(custom_call_identifier) then
|
||||
local custom_call = custom_call_identifier:eval(state)
|
||||
return custom_call:call(state, arg:with_first_argument(self))
|
||||
else
|
||||
error("trying to call a "..self.type..": "..self:format(state))
|
||||
end
|
||||
end,
|
||||
|
||||
-- merge any changes back into the main branch
|
||||
-- cache is a table indicating nodes when the merge has already been triggered { [node] = true, ... }
|
||||
-- (just give an empty table on the initial call)
|
||||
-- redefine :_merge if needed, not this
|
||||
merge = function(self, state, cache)
|
||||
if not cache[self] then
|
||||
cache[self] = true
|
||||
self:_merge(state, cache)
|
||||
self:traverse(traverse.merge, state, cache)
|
||||
end
|
||||
end,
|
||||
_merge = function(self, state, cache) end,
|
||||
|
||||
-- return string that uniquely represent this node
|
||||
-- the actual hash is computed in :_hash, don't redefine :hash directly
|
||||
-- note: if the node is mutable, this will return a UUID instead of calling :_hash
|
||||
hash = function(self)
|
||||
if not self._hash_cache then
|
||||
if self.mutable then
|
||||
self._hash_cache = uuid()
|
||||
else
|
||||
self._hash_cache = self:_hash()
|
||||
end
|
||||
end
|
||||
return self._hash_cache
|
||||
end,
|
||||
_hash_cache = nil, -- cached hash
|
||||
-- return string that uniquely represent this node
|
||||
-- by default, build a "node type<children node hash;...>" representation using :traverse
|
||||
-- you may want to redefine this for base types and other nodes with discriminating info that's not in children nodes.
|
||||
-- also beware if :traverse uses pairs() or any other non-deterministic function, it'd be nice if this was properly bijective...
|
||||
-- (no need to redefine for mutable nodes, since an uuid is used instead)
|
||||
_hash = function(self)
|
||||
local t = {}
|
||||
self:traverse(traverse.hash, t)
|
||||
return ("%s<%s>"):format(self.type, table.concat(t, ";"))
|
||||
end,
|
||||
|
||||
-- return a pretty string representation of the node.
|
||||
-- for non-runtime nodes (what was generated by a parse without any evaluation), this should return valid Anselme code that is functionnally equivalent to the parsed code. note that it currently does not preserve comment.
|
||||
-- redefine _format, not this - note that _format is a mandary method for all nodes.
|
||||
-- state is optional and should only be relevant for runtime nodes; if specified, only show what is relevant for the current branch.
|
||||
-- indentation_level and parent_priority are optional value that respectively keep track in nester :format calls of the indentation level (number) and parent operator priority (number); if the node has a strictly lower priority than the parent node, parentheses will be added
|
||||
-- also remember that execution is done left-to-right, so in case of priority equality, all is fine if the term appear left of the operator, but parentheses will need to be added if the term is right of the operator - so make sure to call :format_right for such cases
|
||||
-- (:format is not cached as even immutable nodes may contain mutable children)
|
||||
format = function(self, state, parent_priority, indentation_level)
|
||||
indentation_level = indentation_level or 0
|
||||
parent_priority = parent_priority or 0
|
||||
|
||||
local s = self:_format(state, self.format_priority, indentation_level)
|
||||
|
||||
if self.format_priority < parent_priority then
|
||||
s = ("(%s)"):format(s)
|
||||
end
|
||||
|
||||
local indentation = ("\t"):rep(indentation_level)
|
||||
s = s:gsub("\n", "\n"..indentation)
|
||||
|
||||
return s
|
||||
end,
|
||||
-- same as :format, but should be called only for nodes right of the current operator
|
||||
format_right = function(self, state, parent_priority, indentation_level)
|
||||
indentation_level = indentation_level or 0
|
||||
parent_priority = parent_priority or 0
|
||||
|
||||
local s = self:_format(state, self.format_priority, indentation_level)
|
||||
|
||||
if self.format_priority <= parent_priority then
|
||||
s = ("(%s)"):format(s)
|
||||
end
|
||||
|
||||
local indentation = (" "):rep(indentation_level)
|
||||
s = indentation..s:gsub("\n", "\n"..indentation)
|
||||
|
||||
return s
|
||||
end,
|
||||
-- redefine this to provide a custom :format. returns a string.
|
||||
_format = function(self, state, self_priority, identation)
|
||||
error("format not implemented for "..self.type)
|
||||
end,
|
||||
-- priority of the node that will be used in :format to add eventually needed parentheses.
|
||||
-- should not be modified after object construction!
|
||||
format_priority = math.huge, -- by default, assumes primary node, i.e. never wrap in parentheses
|
||||
|
||||
-- return Lua value
|
||||
-- this should probably be only called on a Node that is already evaluated
|
||||
-- redefine if you want, probably only for nodes that are already evaluated
|
||||
to_lua = function(self, state)
|
||||
error("cannot convert "..self.type.." to a Lua value")
|
||||
end,
|
||||
|
||||
-- returns truthiness of node
|
||||
-- redefine for false stuff
|
||||
truthy = function(self)
|
||||
return true
|
||||
end,
|
||||
|
||||
-- register the node for serialization on creation
|
||||
__created = function(self)
|
||||
if self.init then -- only call on non-abstract node
|
||||
binser.register(self, self.type)
|
||||
end
|
||||
end,
|
||||
|
||||
__tostring = function(self) return self:format() end,
|
||||
|
||||
-- Node is required by every other AST node, some of which exist in cyclic require loops.
|
||||
-- Delaying the requires in each node after it is defined is enough to fix it, but not for abstract Nodes, since because we are subclassing each node from
|
||||
-- them, we need them to be available BEFORE the Node is defined. But Node require several other modules, which themselves require some other AST...
|
||||
-- The worst thing with this kind of require loop combined with our existing cycle band-aids is that Lua won't error, it will just execute the first node to subclass from Node twice. Which is annoying since now we have several, technically distinct classes representing the same node frolicking around.
|
||||
-- Thus, any require here that may require other Nodes shall be done here. This method is called in anselme.lua after everything else is required.
|
||||
_i_hate_cycles = function(self)
|
||||
local ast = require("ast")
|
||||
custom_call_identifier = ast.Identifier:new("_!")
|
||||
Runtime = ast.abstract.Runtime
|
||||
|
||||
State = require("state.State")
|
||||
resumable_manager = require("state.resumable_manager")
|
||||
end,
|
||||
|
||||
_debug_traverse = function(self, level)
|
||||
level = level or 0
|
||||
local t = {}
|
||||
self:traverse(function(v) table.insert(t, v:_debug_ast(level+1)) end)
|
||||
return ("%s%s:\n%s"):format((" "):rep(level), self.type, table.concat(t, "\n"))
|
||||
end,
|
||||
}
|
||||
|
||||
return Node
|
||||
27
ast/abstract/Overloadable.lua
Normal file
27
ast/abstract/Overloadable.lua
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
local ast = require("ast")
|
||||
|
||||
return ast.abstract.Node {
|
||||
type = "overloadable",
|
||||
init = false,
|
||||
|
||||
-- return specificity (number>=0), secondary specificity (number >=0)
|
||||
-- return false, failure message (string)
|
||||
compatible_with_arguments = function(self, state, args)
|
||||
error("not implemented for "..self.type)
|
||||
end,
|
||||
-- same as :call, but assumes :compatible_with_arguments was checked before the call
|
||||
call_compatible = function(self, state, args)
|
||||
error("not implemented for "..self.type)
|
||||
end,
|
||||
|
||||
-- return string
|
||||
format_parameters = function(self, state)
|
||||
return self:format(state)
|
||||
end,
|
||||
|
||||
-- default for :call
|
||||
call = function(self, state, args)
|
||||
assert(self:compatible_with_arguments(state, args))
|
||||
return self:call_compatible(state, args)
|
||||
end
|
||||
}
|
||||
12
ast/abstract/Runtime.lua
Normal file
12
ast/abstract/Runtime.lua
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
-- indicate a Runtime node: it should not exist in the AST generated by the parser but only as a result of an evaluation or call
|
||||
-- is assumed to be already evaluated and prepared (will actually error on prepare)
|
||||
|
||||
local ast = require("ast")
|
||||
|
||||
return ast.abstract.Node {
|
||||
type = "runtime",
|
||||
init = false,
|
||||
|
||||
_evaluated = true,
|
||||
_prepared = true
|
||||
}
|
||||
13
ast/init.lua
Normal file
13
ast/init.lua
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
return setmetatable({
|
||||
abstract = setmetatable({}, {
|
||||
__index = function(self, key)
|
||||
self[key] = require("ast.abstract."..key)
|
||||
return self[key]
|
||||
end
|
||||
})
|
||||
}, {
|
||||
__index = function(self, key)
|
||||
self[key] = require("ast."..key)
|
||||
return self[key]
|
||||
end
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue