From 56ed6c912b28d79fccc614e9eb22da412f04a2c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Reuh=20Fildadut?= Date: Wed, 27 Dec 2023 17:06:35 +0100 Subject: [PATCH] Replace checkpoint system The previous system needed to store of the scope and full AST to build a Resumable object, which means that if persisted, updating the resumable script will have no effect. The new system instead uses an anchor token and does not require any information besides the anchor name. --- ast/Anchor.lua | 35 ++++++++++ ast/Block.lua | 9 +-- ast/Function.lua | 2 +- ast/Resumable.lua | 60 ----------------- ast/ResumeParentFunction.lua | 44 ------------ ast/abstract/Node.lua | 58 ++++++++-------- common/init.lua | 4 +- parser/expression/primary/anchor.lua | 25 +++++++ parser/expression/primary/init.lua | 1 + parser/expression/secondary/infix/choice.lua | 4 +- parser/expression/secondary/infix/resume.lua | 9 +++ parser/expression/secondary/init.lua | 3 +- state/ScopeStack.lua | 2 +- state/State.lua | 3 - state/event_manager.lua | 41 +++++++++--- state/resumable_manager.lua | 70 -------------------- state/resume_manager.lua | 44 ++++++++++++ stdlib/checkpoint.lua | 20 ++++-- stdlib/conditionals.lua | 6 +- stdlib/text.lua | 9 ++- stdlib/type_check.lua | 2 + 21 files changed, 217 insertions(+), 234 deletions(-) create mode 100644 ast/Anchor.lua delete mode 100644 ast/Resumable.lua delete mode 100644 ast/ResumeParentFunction.lua create mode 100644 parser/expression/primary/anchor.lua create mode 100644 parser/expression/secondary/infix/resume.lua delete mode 100644 state/resumable_manager.lua create mode 100644 state/resume_manager.lua diff --git a/ast/Anchor.lua b/ast/Anchor.lua new file mode 100644 index 0000000..72b53b6 --- /dev/null +++ b/ast/Anchor.lua @@ -0,0 +1,35 @@ +local ast = require("ast") + +local resume_manager + +local Anchor +Anchor = ast.abstract.Node { + type = "anchor", + + name = nil, + + init = function(self, name) + self.name = name + self._list_anchors_cache = { [name] = true } + end, + + _hash = function(self) + return ("anchor<%q>"):format(self.name) + end, + + _format = function(self, ...) + return "#"..self.name + end, + + _eval = function(self, state) + if self:contains_resume_target(state) then + resume_manager:set_reached(state) + end + return Anchor:new(self.name) + end +} + +package.loaded[...] = Anchor +resume_manager = require("state.resume_manager") + +return Anchor diff --git a/ast/Block.lua b/ast/Block.lua index 76ea90f..670e1d5 100644 --- a/ast/Block.lua +++ b/ast/Block.lua @@ -1,6 +1,8 @@ local ast = require("ast") local Nil, Return, AutoCall, ArgumentTuple, Flush +local resume_manager = require("state.resume_manager") + local Block = ast.abstract.Node { type = "block", @@ -34,11 +36,11 @@ local Block = ast.abstract.Node { _eval = function(self, state) local r state.scope:push() - if self:resuming(state) then - local resuming = self:get_resume_data(state) + if self:contains_resume_target(state) then + local anchor = resume_manager:get(state) local resumed = false for _, e in ipairs(self.expressions) do - if e == resuming then resumed = true end + if e:contains_anchor(anchor) then resumed = true end if resumed then r = e:eval(state) if AutoCall:issub(r) then @@ -51,7 +53,6 @@ local Block = ast.abstract.Node { 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()) diff --git a/ast/Function.lua b/ast/Function.lua index 63132b8..5a95b66 100644 --- a/ast/Function.lua +++ b/ast/Function.lua @@ -45,7 +45,7 @@ Function = Overloadable { state.scope:push() args:bind_parameter_tuple(state, self.parameters) - local exp = self.expression:eval_resumable(state) + local exp = self.expression:eval(state) state.scope:pop() diff --git a/ast/Resumable.lua b/ast/Resumable.lua deleted file mode 100644 index b9235a9..0000000 --- a/ast/Resumable.lua +++ /dev/null @@ -1,60 +0,0 @@ -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 "" - 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 diff --git a/ast/ResumeParentFunction.lua b/ast/ResumeParentFunction.lua deleted file mode 100644 index acae124..0000000 --- a/ast/ResumeParentFunction.lua +++ /dev/null @@ -1,44 +0,0 @@ --- 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 self:resuming(state) then - self.expression:eval(state) - return self:get_data(state):call(state, ArgumentTuple:new()) - else - self:set_data(state, 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 diff --git a/ast/abstract/Node.lua b/ast/abstract/Node.lua index 0df4919..bcf2475 100644 --- a/ast/abstract/Node.lua +++ b/ast/abstract/Node.lua @@ -12,8 +12,8 @@ local binser = require("lib.binser") local uuid = require("common").uuid -local State, Runtime -local resumable_manager +local State, Runtime, Call, Identifier, ArgumentTuple +local resume_manager local custom_call_identifier @@ -99,8 +99,8 @@ Node = class { 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 + -- this behave like a cached :traverse through the AST, except this keeps track of the scope stack and preserve evaluation order + -- i.e. when :prepare is called on a node, it should be in a similar scope stack context and order 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 @@ -122,6 +122,30 @@ Node = class { self:traverse(traverse.prepare, state) end, + -- returns a reversed list { [anchor] = true, ... } of the anchors contained in this node and its children + _list_anchors = function(self) + if not self._list_anchors_cache then + self._list_anchors_cache = {} + self:traverse(function(v) + for name in pairs(v:_list_anchors()) do + self._list_anchors_cache[name] = true + end + end) + end + return self._list_anchors_cache + end, + _list_anchors_cache = nil, + + -- returns true if the node or its children contains the anchor + contains_anchor = function(self, anchor) + return not not self:_list_anchors()[anchor.name] + end, + + -- returns true if we are currently trying to resume to an anchor target contained in the current node + contains_resume_target = function(self, state) + return resume_manager:resuming(state) and self:contains_anchor(resume_manager:get(state)) + end, + -- generate a list of translatable nodes that appear in this node -- should only be called on non-runtime nodes -- if a node is translatable, redefine this to add it to the table - note that it shouldn't call :traverse or :list_translatable on its children, as nested translations should not be needed @@ -131,26 +155,6 @@ Node = class { return t 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 @@ -276,11 +280,11 @@ Node = class { -- 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 + Runtime, Call, Identifier, ArgumentTuple = ast.abstract.Runtime, ast.Call, ast.Identifier, ast.ArgumentTuple + custom_call_identifier = Identifier:new("_!") State = require("state.State") - resumable_manager = require("state.resumable_manager") + resume_manager = require("state.resume_manager") end, _debug_traverse = function(self, level) diff --git a/common/init.lua b/common/init.lua index 9802c35..a0e6ee1 100644 --- a/common/init.lua +++ b/common/init.lua @@ -35,8 +35,8 @@ local common = { { "!", 12 } }, infixes = { - { ";", 1 }, { "->", 1 }, - { "#", 2 }, + { ";", 1 }, + { "#", 2 }, { "->", 2 }, { "~>", 2 }, { "~", 4 }, { "~?", 4 }, { "|>", 5 }, { "&", 5 }, { "|", 5 }, { "==", 7 }, { "!=", 7 }, { ">=", 7 }, { "<=", 7 }, { "<", 7 }, { ">", 7 }, diff --git a/parser/expression/primary/anchor.lua b/parser/expression/primary/anchor.lua new file mode 100644 index 0000000..9247762 --- /dev/null +++ b/parser/expression/primary/anchor.lua @@ -0,0 +1,25 @@ +local primary = require("parser.expression.primary.primary") + +local identifier = require("parser.expression.primary.identifier") + +local ast = require("ast") +local Anchor = ast.Anchor + +return primary { + match = function(self, str) + if str:match("^#") then + return identifier:match(str:match("^#(.-)$")) + end + return false + end, + + parse = function(self, source, str) + local start_source = source:clone() + local rem = source:consume(str:match("^(#)(.-)$")) + + local ident + ident, rem = identifier:parse(source, rem) + + return Anchor:new(ident.name):set_source(start_source), rem + end +} diff --git a/parser/expression/primary/init.lua b/parser/expression/primary/init.lua index 370bb69..eae9843 100644 --- a/parser/expression/primary/init.lua +++ b/parser/expression/primary/init.lua @@ -12,6 +12,7 @@ local primaries = { r("function_definition"), r("symbol"), r("identifier"), + r("anchor"), r("block_identifier"), r("tuple"), r("struct"), diff --git a/parser/expression/secondary/infix/choice.lua b/parser/expression/secondary/infix/choice.lua index 5590eee..af3394e 100644 --- a/parser/expression/secondary/infix/choice.lua +++ b/parser/expression/secondary/infix/choice.lua @@ -3,7 +3,7 @@ local infix = require("parser.expression.secondary.infix.infix") local operator_priority = require("common").operator_priority local ast = require("ast") -local Call, Identifier, ArgumentTuple, ResumeParentFunction, ParameterTuple, Function = ast.Call, ast.Identifier, ast.ArgumentTuple, ast.ResumeParentFunction, ast.ParameterTuple, ast.Function +local Call, Identifier, ArgumentTuple, ParameterTuple, Function = ast.Call, ast.Identifier, ast.ArgumentTuple, ast.ParameterTuple, ast.Function return infix { operator = "|>", @@ -11,7 +11,7 @@ return infix { priority = operator_priority["_|>_"], build_ast = function(self, left, right) - right = Function:new(ParameterTuple:new(), ResumeParentFunction:new(right)) + right = Function:new(ParameterTuple:new(), right) return Call:new(Identifier:new(self.identifier), ArgumentTuple:new(left, right)) end } diff --git a/parser/expression/secondary/infix/resume.lua b/parser/expression/secondary/infix/resume.lua new file mode 100644 index 0000000..8d077a2 --- /dev/null +++ b/parser/expression/secondary/infix/resume.lua @@ -0,0 +1,9 @@ +local infix_quote_right = require("parser.expression.secondary.infix.infix_quote_right") + +local operator_priority = require("common").operator_priority + +return infix_quote_right { + operator = "~>", + identifier = "_~>_", + priority = operator_priority["_~>_"] +} diff --git a/parser/expression/secondary/init.lua b/parser/expression/secondary/init.lua index 784765b..e7426b2 100644 --- a/parser/expression/secondary/init.lua +++ b/parser/expression/secondary/init.lua @@ -8,10 +8,11 @@ local secondaries = { -- binary infix operators -- 1 r("infix.semicolon"), - r("infix.translate"), -- 2 r("infix.tuple"), r("infix.tag"), + r("infix.translate"), + r("infix.resume"), -- 4 r("infix.while"), r("infix.if"), diff --git a/state/ScopeStack.lua b/state/ScopeStack.lua index 90639c8..378a84c 100644 --- a/state/ScopeStack.lua +++ b/state/ScopeStack.lua @@ -76,7 +76,7 @@ local ScopeStack = class { define_overloadable = function(self, symbol, exp) return self.current:define_overloadable(self.state, symbol, exp) end, defined = function(self, identifier) return self.current:defined(self.state, identifier) end, defined_in_current = function(self, symbol) return self.current:defined_in_current(self.state, symbol) end, - set = function(self, identifier, exp) self.current:set(self.state, identifier, exp) end, + set = function(self, identifier, exp) self.current:set(self.state, identifier, exp) end, get = function(self, identifier) return self.current:get(self.state, identifier) end, depth = function(self) return self.current:depth() end, diff --git a/state/State.lua b/state/State.lua index e9846cf..225e582 100644 --- a/state/State.lua +++ b/state/State.lua @@ -5,7 +5,6 @@ local class = require("class") local ScopeStack = require("state.ScopeStack") local tag_manager = require("state.tag_manager") local event_manager = require("state.event_manager") -local resumable_manager = require("state.resumable_manager") local translation_manager = require("state.translation_manager") local uuid = require("common").uuid local parser = require("parser") @@ -25,14 +24,12 @@ State = class { self.scope = ScopeStack:new(self, branch_from) event_manager:reset(self) -- events are isolated per branch - resumable_manager:reset(self) -- resumable stack is isolated per branch -- create new empty state else self.scope = ScopeStack:new(self) event_manager:setup(self) tag_manager:setup(self) - resumable_manager:setup(self) translation_manager:setup(self) end end, diff --git a/state/event_manager.lua b/state/event_manager.lua index cf6dda9..9646c84 100644 --- a/state/event_manager.lua +++ b/state/event_manager.lua @@ -1,7 +1,7 @@ local class = require("class") local ast = require("ast") -local Nil, String, List, Identifier = ast.Nil, ast.String, ast.List, ast.Identifier +local Nil, String, List, Identifier, Boolean = ast.Nil, ast.String, ast.List, ast.Identifier, ast.Boolean -- list of event data local event_buffer_identifier = Identifier:new("_event_buffer") @@ -11,18 +11,26 @@ local event_buffer_symbol = event_buffer_identifier:to_symbol{ confined_to_branc local last_event_type_identifier = Identifier:new("_last_event_type") local last_event_type_symbol = last_event_type_identifier:to_symbol{ confined_to_branch = true } +-- indicate if the next flush should be ignored for the current buffered event +local discard_next_flush_identifier = Identifier:new("_discard_next_flush") +local discard_next_flush_symbol = discard_next_flush_identifier:to_symbol{ confined_to_branch = true } + return class { init = false, setup = function(self, state) state.scope:define(event_buffer_symbol, List:new(state)) state.scope:define(last_event_type_symbol, Nil:new()) + state.scope:define(discard_next_flush_symbol, Nil:new()) end, reset = function(self, state) state.scope:set(event_buffer_identifier, List:new(state)) state.scope:set(last_event_type_identifier, Nil:new()) + state.scope:set(discard_next_flush_identifier, Nil:new()) end, + -- write an event into the event buffer + -- will flush if an event of a different type is present in the buffer write = function(self, state, event) local current_type = state.scope:get(last_event_type_identifier):to_lua(state) if current_type ~= nil and current_type ~= event.type then @@ -31,22 +39,33 @@ return class { state.scope:set(last_event_type_identifier, String:new(event.type)) state.scope:get(event_buffer_identifier):insert(state, event) end, + -- same as :write, but the buffer will be discarded instead of yielded on the next flush + write_and_discard_on_flush = function(self, state, event) + self:write(state, event) + state.scope:set(discard_next_flush_identifier, Boolean:new(true)) + end, + -- flush the event buffer: build the event data and yield it flush = function(self, state) local last_type = state.scope:get(last_event_type_identifier):to_lua(state) if last_type then - local last_buffer = state.scope:get(event_buffer_identifier) - local event_president = last_buffer:get(state, 1) -- elected representative of all concerned events - -- yield event data - local data = event_president:build_event_data(state, last_buffer) - coroutine.yield(last_type, data) - -- clear room for the future - state.scope:set(last_event_type_identifier, Nil:new()) - state.scope:set(event_buffer_identifier, List:new(state)) - -- post callback - if event_president.post_flush_callback then event_president:post_flush_callback(state, last_buffer, data) end + local discard_next_flush = state.scope:get(discard_next_flush_identifier):truthy() + if discard_next_flush then + self:reset(state) + else + local last_buffer = state.scope:get(event_buffer_identifier) + local event_president = last_buffer:get(state, 1) -- elected representative of all concerned events + -- yield event data + local data = event_president:build_event_data(state, last_buffer) + coroutine.yield(last_type, data) + -- clear room for the future + self:reset(state) + -- post callback + if event_president.post_flush_callback then event_president:post_flush_callback(state, last_buffer, data) end + end end end, + -- keep flushing until nothing is left (a flush may re-fill the buffer during its execution) final_flush = function(self, state) while state.scope:get(last_event_type_identifier):to_lua(state) do self:flush(state) end end diff --git a/state/resumable_manager.lua b/state/resumable_manager.lua deleted file mode 100644 index 424be53..0000000 --- a/state/resumable_manager.lua +++ /dev/null @@ -1,70 +0,0 @@ -local class = require("class") - -local ast = require("ast") -local Resumable, Nil, List, Identifier - --- stack of resumable contexts -local resumable_stack_identifier, resumable_stack_symbol - -local resumable_manager = class { - init = false, - - setup = function(self, state) - state.scope:define(resumable_stack_symbol, List:new(state)) - self:push(state, Resumable:new(state, Nil:new(), state.scope:capture())) - end, - reset = function(self, state) - state.scope:set(resumable_stack_identifier, List:new(state)) - self:push(state, Resumable:new(state, Nil:new(), state.scope:capture())) - end, - - push = function(self, state, resumable) - local stack = state.scope:get(resumable_stack_identifier) - stack:insert(state, resumable) - end, - pop = function(self, state) - local stack = state.scope:get(resumable_stack_identifier) - stack:remove(state) - end, - _get = function(self, state) - return state.scope:get(resumable_stack_identifier):get(state, -1) - end, - - -- returns the Resumable object that resumes from this point - -- level indicate which function to resume: level=0 means resume the current function, level=1 the parent function (resume from the call to the current function in the parent function), etc. - capture = function(self, state, level) - level = level or 0 - return state.scope:get(resumable_stack_identifier):get(state, -1-level):capture(state) - end, - - eval = function(self, state, exp) - self:push(state, Resumable:new(state, exp, state.scope:capture())) - local r = exp:eval(state) - self:pop(state) - return r - end, - - set_data = function(self, state, node, data) - self:_get(state).data:set(state, node, data) - end, - get_data = function(self, state, node) - return self:_get(state).data:get(state, node) - end, - resuming = function(self, state, node) - local resumable = self:_get(state) - if node then - return resumable.resuming and resumable.data:has(state, node) - else - return resumable.resuming - end - end -} - -package.loaded[...] = resumable_manager - -Resumable, Nil, List, Identifier = ast.Resumable, ast.Nil, ast.List, ast.Identifier - -resumable_stack_identifier = Identifier:new("_resumable_stack") -resumable_stack_symbol = resumable_stack_identifier:to_symbol{ confined_to_branch = true } -- per-branch, global variables - -return resumable_manager diff --git a/state/resume_manager.lua b/state/resume_manager.lua new file mode 100644 index 0000000..259cd73 --- /dev/null +++ b/state/resume_manager.lua @@ -0,0 +1,44 @@ +local class = require("class") + +local ast = require("ast") +local Nil, Identifier, Anchor + +-- stack of resumable contexts +local resume_anchor_identifier, resume_anchor_symbol + +local resume_manager = class { + init = false, + + -- push a new resume context: all run code between this and the next push will try to resume to anchor + push = function(self, state, anchor) + assert(Anchor:is(anchor), "can only resume to an anchor target") -- well technically it wouldn't be hard to allow to resume to any node, but I feel like there's already enough stuff in Anselme that was done just because it could be done + state.scope:push_partial(resume_anchor_identifier) + state.scope:define(resume_anchor_symbol, anchor) + end, + -- pop the current resume context + pop = function(self, state) + state.scope:pop() + end, + + -- returns true if we are currently trying to resume to an anchor + resuming = function(self, state) + return state.scope:defined(resume_anchor_identifier) and not Nil:is(state.scope:get(resume_anchor_identifier)) + end, + -- returns the anchor we are trying to resume to + get = function(self, state) + return state.scope:get(resume_anchor_identifier) + end, + -- mark the anchor as reached and stop the resume + set_reached = function(self, state) + state.scope:set(resume_anchor_identifier, Nil:new()) + end, +} + +package.loaded[...] = resume_manager + +Nil, Identifier, Anchor = ast.Nil, ast.Identifier, ast.Anchor + +resume_anchor_identifier = Identifier:new("_resume_anchor") +resume_anchor_symbol = resume_anchor_identifier:to_symbol() + +return resume_manager diff --git a/stdlib/checkpoint.lua b/stdlib/checkpoint.lua index 605daac..bca3b43 100644 --- a/stdlib/checkpoint.lua +++ b/stdlib/checkpoint.lua @@ -1,10 +1,22 @@ -local resumable_manager = require("state.resumable_manager") +local ast = require("ast") +local ArgumentTuple = ast.ArgumentTuple + +local resume_manager = require("state.resume_manager") return { { - "new checkpoint", "(level::number=0)", - function(state, level) - return resumable_manager:capture(state, level.number) + "_~>_", "(anchor::anchor, quote)", + function(state, anchor, quote) + resume_manager:push(state, anchor) + local r = quote:call(state, ArgumentTuple:new()) + resume_manager:pop(state) + return r + end + }, + { + "_~>_", "(anchor::nil, quote)", + function(state, anchor, quote) + return quote:call(state, ArgumentTuple:new()) end } } diff --git a/stdlib/conditionals.lua b/stdlib/conditionals.lua index 37bc11b..75a341d 100644 --- a/stdlib/conditionals.lua +++ b/stdlib/conditionals.lua @@ -21,8 +21,9 @@ return { "_~_", "(condition, expression)", function(state, condition, expression) ensure_if_variable(state) if condition:truthy() then + local r = expression:call(state, ArgumentTuple:new()) set_if_variable(state, true) - return expression:call(state, ArgumentTuple:new()) + return r else set_if_variable(state, false) return Nil:new() @@ -36,8 +37,9 @@ return { if last_if_success(state) then return Nil:new() else + local r = expression:call(state, ArgumentTuple:new()) set_if_variable(state, true) - return expression:call(state, ArgumentTuple:new()) + return r end end }, diff --git a/stdlib/text.lua b/stdlib/text.lua index 061b30d..20cd362 100644 --- a/stdlib/text.lua +++ b/stdlib/text.lua @@ -1,5 +1,5 @@ local ast = require("ast") -local Nil, Choice, AttachBlock = ast.Nil, ast.Choice, ast.AttachBlock +local Nil, Choice, AttachBlock, ArgumentTuple = ast.Nil, ast.Choice, ast.AttachBlock, ast.ArgumentTuple local event_manager = require("state.event_manager") local translation_manager = require("state.translation_manager") @@ -19,7 +19,12 @@ return { { "_|>_", "(txt::text, fn)", function(state, text, func) - event_manager:write(state, Choice:new(text, func)) + if func:contains_resume_target(state) then + func:call(state, ArgumentTuple:new()) + event_manager:write_and_discard_on_flush(state, Choice:new(text, func)) + else + event_manager:write(state, Choice:new(text, func)) + end return Nil:new() end }, diff --git a/stdlib/type_check.lua b/stdlib/type_check.lua index ad6a436..faad07e 100644 --- a/stdlib/type_check.lua +++ b/stdlib/type_check.lua @@ -2,10 +2,12 @@ local ast = require("ast") local Boolean = ast.Boolean return { + { "nil", "(x)", function(state, x) return Boolean:new(x.type == "nil") end }, { "number", "(x)", function(state, x) return Boolean:new(x.type == "number") end }, { "string", "(x)", function(state, x) return Boolean:new(x.type == "string") end }, { "boolean", "(x)", function(state, x) return Boolean:new(x.type == "boolean") end }, { "symbol", "(x)", function(state, x) return Boolean:new(x.type == "symbol") end }, + { "anchor", "(x)", function(state, x) return Boolean:new(x.type == "anchor") end }, { "text", "(x)", function(state, x) return Boolean:new(x.type == "text") end },