From dfe838a7690f778c9d540d16b685d5b2316d8313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Reuh=20Fildadut?= Date: Fri, 14 Jan 2022 23:27:21 +0100 Subject: [PATCH] Add custom code inject at function/checkpoint start/end --- anselme.lua | 34 +++++++++++++++++++ parser/preparser.lua | 79 +++++++++++++++++++++++++++++++++++--------- 2 files changed, 98 insertions(+), 15 deletions(-) diff --git a/anselme.lua b/anselme.lua index 1acf69c..ffdeae6 100644 --- a/anselme.lua +++ b/anselme.lua @@ -351,6 +351,35 @@ local vm_mt = { self.state.builtin_aliases["🏁"] = reached return self end, + --- set some code that will be added at the start of every function defined after this is called + -- nil to disable + -- can typically be used to define variables for every function like 👁️ + -- return self + injectfunctionstart = function(self, code) + self.state.inject.functionstart = code + return self + end, + --- same as injectfunctionstart, but inject code at the start of every checkpoint + -- nil to disable + -- return self + injectcheckpointstart = function(self, code) + self.state.inject.checkpointstart = code + return self + end, + --- same as injectfunctionstart, but inject code at the end of every function + -- nil to disable + -- return self + injectfunctionend = function(self, code) + self.state.inject.functionend = code + return self + end, + --- same as injectfunctionend, but inject code at the end of every checkpoint + -- nil to disable + -- return self + injectcheckpointend = function(self, code) + self.state.inject.checkpointend = code + return self + end, --- load & execute a built-in language file -- the language file may optionally contain the special variables: @@ -473,6 +502,7 @@ local vm_mt = { local interpreter interpreter = { state = { + inject = self.state.inject, feature_flags = self.state.feature_flags, builtin_aliases = self.state.builtin_aliases, aliases = setmetatable({}, { __index = self.state.aliases }), @@ -544,6 +574,10 @@ return setmetatable(anselme, { __call = function() -- global state local state = { + inject = { + functionstart = nil, functionend = nil, + checkpointstart = nil, checkpointend = nil + }, feature_flags = { ["strip trailing spaces"] = true, ["strip duplicate spaces"] = true diff --git a/parser/preparser.lua b/parser/preparser.lua index a64dad9..76b2d10 100644 --- a/parser/preparser.lua +++ b/parser/preparser.lua @@ -1,6 +1,8 @@ local format_identifier, identifier_pattern, escape, special_functions_names, pretty_signature, signature --- try to define an alias using rem, the text that follows the identifier +local parse_indented + +--- try to define an alias using rem, the text that follows the identifier -- returns true, new_rem, alias_name in case of success -- returns true, rem in case of no alias and no error -- returns nil, err in case of alias and error @@ -22,6 +24,7 @@ local function maybe_alias(rem, fqm, namespace, line, state) return true, rem, alias end +--- parse a single line into AST -- * ast: if success -- * nil, error: in case of error local function parse_line(line, state, namespace) @@ -195,6 +198,17 @@ local function parse_line(line, state, namespace) else table.insert(line.children, 1, { content = ":🔖=()", source = line.source }) end + -- custom code injection + if state.inject.functionstart then + for i, ll in ipairs(state.inject.functionstart) do + table.insert(line.children, 1+i, ll) + end + end + if state.inject.functionend then + for _, ll in ipairs(state.inject.functionend) do + table.insert(line.children, ll) + end + end elseif r.type == "checkpoint" then -- define 🏁 variable local reached_alias = state.global_state.builtin_aliases["🏁"] @@ -203,6 +217,17 @@ local function parse_line(line, state, namespace) else table.insert(line.children, 1, { content = ":🏁=0", source = line.source }) end + -- custom code injection + if state.inject.checkpointstart then + for i, ll in ipairs(state.inject.checkpointstart) do + table.insert(line.children, 1+i, ll) + end + end + if state.inject.checkpointend then + for _, ll in ipairs(state.inject.checkpointend) do + table.insert(line.children, ll) + end + end end -- define args for _, param in ipairs(r.params) do @@ -286,6 +311,7 @@ local function parse_line(line, state, namespace) return r end +--- parse an indented into final AST -- * block: in case of success -- * nil, err: in case of error local function parse_block(indented, state, namespace, parent_function) @@ -398,28 +424,43 @@ local function parse_lines(s) return lines end +--- make indented from intial string +-- * list: in case of success +-- * nil, err: in case of error +parse_indented = function(s, fnname, source) + source = source or fnname + -- parse lines + local lines = parse_lines(s) + local indented, e = parse_indent(lines, source) + if not indented then return nil, e end + -- wrap in named function if neccessary + if fnname ~= nil and fnname ~= "" then + if not fnname:match("^"..identifier_pattern.."$") then + return nil, ("invalid function name %q"):format(fnname) + end + indented = { + { content = "$ "..fnname, source = ("%s:%s"):format(source, 0), children = indented }, + } + end + -- transform ast + indented = transform_indented(indented) + return indented +end + --- preparse shit: create AST structure, define variables and functions, but don't parse expression or perform any type checking -- (wait for other files to be parsed before doing this with postparse) -- * block: in case of success -- * nil, err: in case of error local function parse(state, s, name, source) - -- parse lines - local lines = parse_lines(s) - local indented, e = parse_indent(lines, source or name) + -- get indented + local indented, e = parse_indented(s, name, source) if not indented then return nil, e end - -- wrap in named function if neccessary - if name ~= "" then - if not name:match("^"..identifier_pattern.."$") then - return nil, ("invalid function name %q"):format(name) - end - indented = { - { content = "$ "..name, source = ("%s:%s"):format(source or name, 0), children = indented }, - } - end - -- transform ast - indented = transform_indented(indented) -- build state proxy local state_proxy = { + inject = { + functionstart = nil, functionend = nil, + checkpointstart = nil, checkpointend = nil + }, aliases = setmetatable({}, { __index = state.aliases }), variables = setmetatable({}, { __index = state.aliases }), functions = setmetatable({}, { @@ -438,6 +479,14 @@ local function parse(state, s, name, source) queued_lines = {}, global_state = state } + -- parse injects + for _, inject in ipairs{"functionstart", "functionend", "checkpointstart", "checkpointend"} do + if state.inject[inject] then + local inject_indented, err = parse_indented(state.inject[inject], nil, "injected "..inject) + if not inject_indented then return nil, err end + state_proxy.inject[inject] = inject_indented + end + end -- parse local root, err = parse_block(indented, state_proxy, "") if not root then return nil, err end