diff --git a/anselme/ast/Closure.lua b/anselme/ast/Closure.lua index 15772f9..5a1d2bc 100644 --- a/anselme/ast/Closure.lua +++ b/anselme/ast/Closure.lua @@ -19,6 +19,7 @@ Closure = Runtime(Overloadable) { -- to allow future define in the function (fn.:var = "foo") state.scope:push() self.scope = state.scope:capture() + state.scope:define(ast.Symbol:new("_calling_environment"), self.scope) state.scope:pop() end, @@ -38,20 +39,33 @@ Closure = Runtime(Overloadable) { return self.func.parameters:format(state) end, call_dispatched = function(self, state, args) + local calling_environment = state.scope:capture() state.scope:push(self.scope) + state.scope:set(ast.Identifier:new("_calling_environment"), calling_environment) local exp = self.func:call_dispatched(state, args) state.scope:pop() return exp end, resume = function(self, state, target) if self.func.parameters.min_arity > 0 then error("can't resume function with parameters") end + local calling_environment = state.scope:capture() state.scope:push(self.scope) + state.scope:set(ast.Identifier:new("_calling_environment"), calling_environment) resume_manager:push(state, target) local exp = self.func:call(state, ast.ArgumentTuple:new()) resume_manager:pop(state) state.scope:pop() return exp end, + get_level = function(self, state, level) -- TODO make generic to all calls? only closures? + local env = state.scope:capture() + while level > 0 do + assert(env:defined(state, ast.Identifier:new("_calling_environment"))) + env = env:get(state, ast.Identifier:new("_calling_environment")) + level = level - 1 + end + return env + end } package.loaded[...] = Closure diff --git a/anselme/ast/Environment.lua b/anselme/ast/Environment.lua index 2986299..891a671 100644 --- a/anselme/ast/Environment.lua +++ b/anselme/ast/Environment.lua @@ -2,7 +2,7 @@ local ast = require("anselme.ast") local operator_priority = require("anselme.common").operator_priority -local Branched, ArgumentTuple, Overload, Overloadable, Table, Call, ParameterTuple, Function, FunctionParameter, Identifier +local Branched, ArgumentTuple, Overload, Overloadable, Table local VariableMetadata = ast.abstract.Runtime { type = "variable metadata", @@ -180,6 +180,7 @@ local Environment = ast.abstract.Runtime { end, -- returns a list {[symbol]=val,...} of all exported variables (evaluated) in the current strict layer + -- TODO currently unused list_exported = function(self, state) assert(self.export, "not an export scope layer") local r = {} @@ -219,6 +220,6 @@ local Environment = ast.abstract.Runtime { } package.loaded[...] = Environment -Branched, ArgumentTuple, Overload, Overloadable, Table, Call, ParameterTuple, Function, FunctionParameter, Identifier = ast.Branched, ast.ArgumentTuple, ast.Overload, ast.abstract.Overloadable, ast.Table, ast.Call, ast.ParameterTuple, ast.Function, ast.FunctionParameter, ast.Identifier +Branched, ArgumentTuple, Overload, Overloadable, Table = ast.Branched, ast.ArgumentTuple, ast.Overload, ast.abstract.Overloadable, ast.Table return Environment diff --git a/anselme/stdlib/boolean.lua b/anselme/stdlib/boolean.lua index b797716..03fb242 100644 --- a/anselme/stdlib/boolean.lua +++ b/anselme/stdlib/boolean.lua @@ -6,13 +6,20 @@ return { "_==_", "(a, b)", function(state, a, b) if a.mutable ~= b.mutable then return Boolean:new(false) - elseif a.mutable then - return Boolean:new(a == b) else return Boolean:new(a:hash() == b:hash()) end end }, + { + "_!=_", "(a, b)", + function(state, a, b) + if a.mutable ~= b.mutable then return Boolean:new(true) + else + return Boolean:new(a:hash() ~= b:hash()) + end + end + }, { "!_", "(a)", function(state, a) diff --git a/anselme/stdlib/checkpoint.lua b/anselme/stdlib/checkpoint.lua index a57af8f..95c53ee 100644 --- a/anselme/stdlib/checkpoint.lua +++ b/anselme/stdlib/checkpoint.lua @@ -1,5 +1,5 @@ local ast = require("anselme.ast") -local ArgumentTuple = ast.ArgumentTuple +local ArgumentTuple, Boolean = ast.ArgumentTuple, ast.Boolean local resume_manager = require("anselme.state.resume_manager") @@ -18,5 +18,33 @@ return { function(state, anchor, quote) return quote:call(state, ArgumentTuple:new()) end - } + }, + { + "resume", "(function::closure, anchor::anchor)", + function(state, func, anchor) + return func:resume(state, anchor) + end + }, + { + "resuming", "()", + function(state) + return Boolean:new(resume_manager:resuming(state)) + end + }, + { + "resuming", "(level::number)", + function(state, level) + local env = ast.Closure:get_level(state, level:to_lua(state)) + state.scope:push(env) + local r = Boolean:new(resume_manager:resuming(state)) + state.scope:pop() + return r + end + }, + { + "resume target", "()", + function(state) + return resume_manager:get(state) + end + }, } diff --git a/anselme/stdlib/init.lua b/anselme/stdlib/init.lua index 4efbac9..9b3d8de 100644 --- a/anselme/stdlib/init.lua +++ b/anselme/stdlib/init.lua @@ -20,8 +20,7 @@ return function(main_state) "type_check" }) - local boot = parser(require("anselme.stdlib.boot_script"), "boot.ans") - boot:eval(main_state) + parser(require("anselme.stdlib.boot_script"), "boot.ans"):eval(main_state) load(main_state, { "number", @@ -32,4 +31,6 @@ return function(main_state) "checkpoint", "persist", }) + + parser(require("anselme.stdlib.script_script"), "script.ans"):eval(main_state) end diff --git a/anselme/stdlib/script_script.lua b/anselme/stdlib/script_script.lua new file mode 100644 index 0000000..8b08bc6 --- /dev/null +++ b/anselme/stdlib/script_script.lua @@ -0,0 +1,57 @@ +return [[ +:@script = $(name, fn) + :¤t checkpoint => "{name}.checkpoint"!persist(false) + :&reached => "{name}.reached"!persist(*{}) + :resumed from = () + + fn.:check = $(anchor::anchor) + reached(anchor) = (reached(anchor) | 0) + 1 + fn.:checkpoint = $(anchor::anchor) + current checkpoint = anchor + resumed from != anchor ~ + reached(anchor) = (reached(anchor) | 0) + 1 + fn.:checkpoint = $(anchor::anchor, on resume::function) + current checkpoint = anchor + resumed from == anchor | resuming(1) ~ + on resume! + ~ + reached(anchor) = (reached(anchor) | 0) + 1 + + :f = $ + current checkpoint ~ + resumed from = current checkpoint + fn!resume(current checkpoint) + ~ + resumed from = () + fn! + run += 1 + f.:&run => "{name}.run"!persist(0) + + f!type("script") + +:is script = is("script") + +:@$_!(s::is script) + s!value! + +:@$_._(s::is script, k::string) + :v = s!value + v.fn!has upvalue(k) ~ + @v.fn.(k) + ~ + @v.(k) + +:@$_._(s::is script, k::string) = val + :v = s!value + v.fn!has upvalue(k) ~ + v.fn.(k) = val + ~ + v.(k) = val + +:@$from(s::is script, a::anchor) + s.current checkpoint = a + @s! +:@$from(s::is script) + s.current checkpoint = () + @s! +]] \ No newline at end of file