From 404e7dd56e9e574034aac8f640607ae48b9348d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Reuh=20Fildadut?= Date: Fri, 29 Dec 2023 17:56:01 +0100 Subject: [PATCH] Replace AttachBlock with more generic PartialScope --- ast/AttachBlock.lua | 64 --------------- ast/Environment.lua | 7 ++ ast/Function.lua | 4 + ast/PartialScope.lua | 90 +++++++++++++++++++++ parser/tree_to_ast.lua | 6 +- state/ScopeStack.lua | 1 + stdlib/text.lua | 5 +- test/results/define override variable.ans | 2 +- test/results/translate text attachblock.ans | 9 +++ test/tests/translate text attachblock.ans | 6 ++ 10 files changed, 124 insertions(+), 70 deletions(-) delete mode 100644 ast/AttachBlock.lua create mode 100644 ast/PartialScope.lua create mode 100644 test/results/translate text attachblock.ans create mode 100644 test/tests/translate text attachblock.ans diff --git a/ast/AttachBlock.lua b/ast/AttachBlock.lua deleted file mode 100644 index b1de863..0000000 --- a/ast/AttachBlock.lua +++ /dev/null @@ -1,64 +0,0 @@ -local ast = require("ast") -local Identifier, Quote - -local attached_block_identifier, attached_block_symbol - -local AttachBlock -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, ...) - local exp = self.expression:format(state, priority, indentation, ...) - if exp:sub(-2) == " _" then exp = exp:sub(1, -3) end - return exp.."\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_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_symbol, Quote:new(self.block)) - self.expression:prepare(state) - state.scope:pop() - end, - - -- class method: if the block identifier is defined in the current scope, wrap node in an AttachBlock so the block is still defined in this node - -- used to preserve the defined _ block without the need to build a full closure - -- used e.g. for -> translation, as we want to preserve _ while still executing the translation in the Translatable scope and not restore a different scope from a closure - -- (operates on un-evaluated nodes!) - preserve = function(self, state, node) - if state.scope:defined_in_current(attached_block_symbol) then - return AttachBlock:new(node, state.scope:get(attached_block_identifier).expression) -- unwrap Quote as that will be rewrap on eval - end - return node - end, -} - -package.loaded[...] = AttachBlock -Identifier, Quote = ast.Identifier, ast.Quote - -attached_block_identifier = Identifier:new("_") -attached_block_symbol = attached_block_identifier:to_symbol() - -return AttachBlock diff --git a/ast/Environment.lua b/ast/Environment.lua index b9b7d87..3bbe460 100644 --- a/ast/Environment.lua +++ b/ast/Environment.lua @@ -22,6 +22,9 @@ local VariableMetadata = ast.abstract.Runtime { return self.branched:get(state) end end, + get_symbol = function(self) + return self.symbol + end, set = function(self, state, value) if self.symbol.constant then error(("trying to change the value of constant %s"):format(self.symbol.string), 0) @@ -180,6 +183,10 @@ local Environment = ast.abstract.Runtime { get = function(self, state, identifier) return self:_get_variable(state, identifier):get(state) end, + -- get the symbol that was used to define the variable in current or parent environment + get_symbol = function(self, state, identifier) + return self:_get_variable(state, identifier):get_symbol() + end, -- set variable value in current or parent environment set = function(self, state, identifier, val) return self:_get_variable(state, identifier):set(state, val) diff --git a/ast/Function.lua b/ast/Function.lua index f5116f4..d6f6b02 100644 --- a/ast/Function.lua +++ b/ast/Function.lua @@ -33,6 +33,10 @@ Function = Overloadable { traverse = function(self, fn, ...) fn(self.parameters, ...) fn(self.expression, ...) + for sym, val in pairs(self.exports) do + fn(sym, ...) + fn(val, ...) + end end, compatible_with_arguments = function(self, state, args) diff --git a/ast/PartialScope.lua b/ast/PartialScope.lua new file mode 100644 index 0000000..179c19c --- /dev/null +++ b/ast/PartialScope.lua @@ -0,0 +1,90 @@ +-- create a partial layer to define temporary variables + +local ast = require("ast") +local Identifier, Quote + +local attached_block_identifier, attached_block_symbol + +local PartialScope +PartialScope = ast.abstract.Node { + type = "partial scope", + + expression = nil, + definitions = nil, -- {[sym]=value,...} where values are already evaluated! + _identifiers = nil, -- {identifier,...} - just a cache so we don't rebuild it on every eval + + init = function(self, expression) + self.expression = expression + self.definitions = {} + self._identifiers = {} + self.format_priority = self.expression.format_priority + end, + define = function(self, symbol, value) -- for construction only + assert(not self.definitions[symbol], ("%s already defined in partial layer"):format(symbol)) + table.insert(self._identifiers, symbol:to_identifier()) + self.definitions[symbol] = value + end, + + _format = function(self, state, priority, indentation, ...) + if self.definitions[attached_block_symbol] then + local block = self.definitions[attached_block_symbol] + local exp = self.expression:format(state, priority, indentation, ...) + if exp:sub(-2) == " _" then exp = exp:sub(1, -3) end + return exp.."\n\t"..block:format(state, priority, indentation + 1, ...) + else + return self.expression:format(state, priority, indentation, ...) + end + end, + + traverse = function(self, fn, ...) + fn(self.expression, ...) + for sym, val in pairs(self.definitions) do + fn(sym, ...) + fn(val, ...) + end + end, + + _eval = function(self, state) + state.scope:push_partial(table.unpack(self._identifiers)) + for sym, val in pairs(self.definitions) do state.scope:define(sym, val) end + local exp = self.expression:eval(state) + state.scope:pop() + + return exp + end, + + _prepare = function(self, state) + state.scope:push_partial(table.unpack(self._identifiers)) + for sym, val in pairs(self.definitions) do state.scope:define(sym, val) end + self.expression:prepare(state) + state.scope:pop() + end, + + -- class method: if the identifier is currently defined, wrap node in an PartialScope so the identifier is still defined in this node + -- used to e.g. preserve the defined _ block without the need to build a full closure + -- used e.g. for -> translation, as we want to preserve _ while still executing the translation in the Translatable scope and not restore a different scope from a closure + -- (operates on un-evaluated nodes!) + preserve = function(self, state, expression, ...) + local partial = PartialScope:new(expression) + for _, ident in ipairs{...} do + if state.scope:defined(ident) then + partial:define(state.scope:get_symbol(ident), state.scope:get(ident)) + end + end + return partial + end, + -- class method: return a PartialScope that define the block identifier _ to a Quote of `block` + attach_block = function(self, expression, block) + local partial = ast.PartialScope:new(expression) + partial:define(attached_block_symbol, Quote:new(block)) + return partial + end +} + +package.loaded[...] = PartialScope +Identifier, Quote = ast.Identifier, ast.Quote + +attached_block_identifier = Identifier:new("_") +attached_block_symbol = attached_block_identifier:to_symbol() + +return PartialScope diff --git a/parser/tree_to_ast.lua b/parser/tree_to_ast.lua index 44412b6..0d4e86a 100644 --- a/parser/tree_to_ast.lua +++ b/parser/tree_to_ast.lua @@ -3,7 +3,7 @@ local tree_to_block local ast = require("ast") -local Block, Flush, AttachBlock +local Block, Flush, PartialScope local expression_to_ast = require("parser.expression.to_ast") @@ -26,7 +26,7 @@ end local function line_to_expression(content, tree) if #tree > 0 then local child_block = tree_to_block(tree) - return AttachBlock:new(expect_end_block(expression_to_ast(tree.source:clone(), content.." _", " _$")), child_block):set_source(tree.source) + return PartialScope:attach_block(expect_end_block(expression_to_ast(tree.source:clone(), content.." _", " _$")), child_block):set_source(tree.source) else return expect_end(expression_to_ast(tree.source:clone(), content, nil, nil, nil, Flush:new())):set_source(tree.source) end @@ -47,6 +47,6 @@ tree_to_block = function(tree) end package.loaded[...] = tree_to_block -Block, Flush, AttachBlock = ast.Block, ast.Flush, ast.AttachBlock +Block, Flush, PartialScope = ast.Block, ast.Flush, ast.PartialScope return tree_to_block diff --git a/state/ScopeStack.lua b/state/ScopeStack.lua index e7b1480..8883cdd 100644 --- a/state/ScopeStack.lua +++ b/state/ScopeStack.lua @@ -79,6 +79,7 @@ local ScopeStack = class { 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, get = function(self, identifier) return self.current:get(self.state, identifier) end, + get_symbol = function(self, identifier) return self.current:get_symbol(self.state, identifier) end, depth = function(self) return self.current:depth() end, -- push new scope diff --git a/stdlib/text.lua b/stdlib/text.lua index 20cd362..4b892b9 100644 --- a/stdlib/text.lua +++ b/stdlib/text.lua @@ -1,5 +1,5 @@ local ast = require("ast") -local Nil, Choice, AttachBlock, ArgumentTuple = ast.Nil, ast.Choice, ast.AttachBlock, ast.ArgumentTuple +local Nil, Choice, PartialScope, ArgumentTuple = ast.Nil, ast.Choice, ast.PartialScope, ast.ArgumentTuple local event_manager = require("state.event_manager") local translation_manager = require("state.translation_manager") @@ -33,7 +33,8 @@ return { { "_->_", "(original::is(\"quote\"), translated::is(\"quote\"))", function(state, original, translated) - translation_manager:set(state, tag_manager:get(state), original.expression, AttachBlock:preserve(state, translated.expression)) + local exp = PartialScope:preserve(state, translated.expression, ast.Identifier:new("_")) + translation_manager:set(state, tag_manager:get(state), original.expression, exp) return Nil:new() end } diff --git a/test/results/define override variable.ans b/test/results/define override variable.ans index 469dd0c..7d6d95a 100644 --- a/test/results/define override variable.ans +++ b/test/results/define override variable.ans @@ -2,7 +2,7 @@ --- error --- can't add an overload variant to non-overloadable variable a defined in the same scope ↳ from test/tests/define override variable.ans:3:1 in definition: :a = ($() _) - ↳ from test/tests/define override variable.ans:3:1 in attach block: :a = ($() _)… + ↳ from test/tests/define override variable.ans:3:1 in partial scope: :a = ($() _)… ↳ from ? in block: :a = 2… --# saved #-- {} \ No newline at end of file diff --git a/test/results/translate text attachblock.ans b/test/results/translate text attachblock.ans new file mode 100644 index 0000000..0c9983f --- /dev/null +++ b/test/results/translate text attachblock.ans @@ -0,0 +1,9 @@ +--# run #-- +--- text --- +| {}"Hello"| +--- text --- +| {}"Bonjour"| +--- return --- +() +--# saved #-- +{} \ No newline at end of file diff --git a/test/tests/translate text attachblock.ans b/test/tests/translate text attachblock.ans new file mode 100644 index 0000000..10c73a2 --- /dev/null +++ b/test/tests/translate text attachblock.ans @@ -0,0 +1,6 @@ +| Hello + +| Hello| -> + | Bonjour + +| Hello