From 3d10b1b15c503676c5c39f96ee2abe42b607a0d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Reuh=20Fildadut?= Date: Wed, 3 Jan 2024 16:40:49 +0100 Subject: [PATCH] Rework variable undefinition --- anselme/ast/Environment.lua | 69 ++++++++++++++++++++---------------- anselme/ast/PartialScope.lua | 6 ++-- anselme/ast/Symbol.lua | 4 +-- anselme/ast/Undefined.lua | 13 +++++++ 4 files changed, 55 insertions(+), 37 deletions(-) create mode 100644 anselme/ast/Undefined.lua diff --git a/anselme/ast/Environment.lua b/anselme/ast/Environment.lua index fad34e0..1b32d20 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 +local Branched, ArgumentTuple, Overload, Overloadable, Table, Undefined local VariableMetadata = ast.abstract.Runtime { type = "variable metadata", @@ -22,6 +22,10 @@ local VariableMetadata = ast.abstract.Runtime { return v end end, + undefined = function(self, state) + local v = self.branched:get(state) + return Undefined:is(v) + end, get_symbol = function(self) return self.symbol end, @@ -68,21 +72,16 @@ local Environment = ast.abstract.Runtime { variables = nil, -- Table of { {identifier} = variable metadata, ... } partial = nil, -- { [name string] = true, ... } - undefine = nil, -- { [name string] = true, ... } - variable present here will not be looked up in parent env export = nil, -- bool _lookup_cache = nil, -- { [name string] = variable metadata, ... } - _lookup_cache_current = nil, -- { [name string] = true, ... } + _lookup_cache_current = nil, -- { [name string] = variable metadata, ... } 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 - self.undefine = {} - -- TODO: something defined in a parent scope after caching here will not be reflected - -- 99% of the time a scope is not reused after popping it off the stack, except when captured in closures - -- so we might want to make accessing variable defined after environment capture illegal or precache all upvalues self._lookup_cache = {} self._lookup_cache_current = {} end, @@ -107,14 +106,10 @@ local Environment = ast.abstract.Runtime { or (self.export ~= symbol.exported) then return self.parent:define(state, symbol, exp) end - if symbol.undefine then - self.undefine[name] = true - else - local variable = VariableMetadata:new(state, symbol, exp) - self.variables:set(state, symbol:to_identifier(), variable) - self._lookup_cache[name] = variable - self._lookup_cache_current[name] = true - end + local variable = VariableMetadata:new(state, symbol, exp) + self.variables:set(state, symbol:to_identifier(), variable) + self._lookup_cache[name] = variable + self._lookup_cache_current[name] = variable end, -- define or redefine new overloadable variable in current environment, inheriting existing overload variants from (parent) scopes define_overloadable = function(self, state, symbol, exp) @@ -152,11 +147,34 @@ local Environment = ast.abstract.Runtime { if _cache[name] == nil then if self.variables:has(state, identifier) then _cache[name] = self.variables:get(state, identifier) - elseif self.parent and not self.undefine[identifier.name] then + elseif self.parent then _cache[name] = self.parent:_lookup(state, identifier) end end - return _cache[name] + local var = _cache[name] + if var and not var:undefined(state) then + return var + end + return nil + end, + _lookup_in_current = function(self, state, symbol) + local name = symbol.string + local _cache = self._lookup_cache_current + if _cache[name] == nil then + local identifier = symbol:to_identifier() + if self.variables:has(state, identifier) then + _cache[name] = self.variables:get(state, identifier) + elseif self.parent then + if (self.partial and not self.partial[name]) or (self.export ~= symbol.exported) then + _cache[name] = self.parent:_lookup_in_current(state, symbol) + end + end + end + local var = _cache[name] + if var and not var:undefined(state) then + return var + end + return nil end, -- returns bool if variable defined in current or parent environment @@ -166,22 +184,11 @@ local Environment = ast.abstract.Runtime { -- 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 - local _cache = self._lookup_cache_current - if _cache[name] == nil then - if self.variables:has(state, symbol:to_identifier()) then - _cache[name] = true - elseif self.parent and not self.undefine[name] then - if (self.partial and not self.partial[name]) or (self.export ~= symbol.exported) then - _cache[name] = self.parent:defined_in_current(state, symbol) - end - end - end - return _cache[name] + return self:_lookup_in_current(state, symbol) ~= nil end, -- return bool if variable is defined in the current environment only - won't search in parent env for exported & partial names defined_in_current_strict = function(self, state, identifier) - return self.variables:has(state, identifier) + return self.variables:has(state, identifier) and not self.variables:get(state, identifier):undefined(state) end, -- get variable in current or parent scope, with metadata @@ -236,6 +243,6 @@ local Environment = ast.abstract.Runtime { } package.loaded[...] = Environment -Branched, ArgumentTuple, Overload, Overloadable, Table = ast.Branched, ast.ArgumentTuple, ast.Overload, ast.abstract.Overloadable, ast.Table +Branched, ArgumentTuple, Overload, Overloadable, Table, Undefined = ast.Branched, ast.ArgumentTuple, ast.Overload, ast.abstract.Overloadable, ast.Table, ast.Undefined return Environment diff --git a/anselme/ast/PartialScope.lua b/anselme/ast/PartialScope.lua index 48f6aa7..3b8d9b6 100644 --- a/anselme/ast/PartialScope.lua +++ b/anselme/ast/PartialScope.lua @@ -1,7 +1,7 @@ -- create a partial layer to define temporary variables local ast = require("anselme.ast") -local Identifier, Quote, Nil +local Identifier, Quote, Undefined local attached_block_identifier, attached_block_symbol local unpack = table.unpack or unpack @@ -74,14 +74,14 @@ PartialScope = ast.abstract.Node { attach_block = function(self, expression, block) local partial = PartialScope:new(expression) local unpartial = PartialScope:new(block) - unpartial:define(attached_block_symbol:with{undefine=true}, Nil:new()) + unpartial:define(attached_block_symbol, Undefined:new()) partial:define(attached_block_symbol, Quote:new(unpartial)) return partial end } package.loaded[...] = PartialScope -Identifier, Quote, Nil = ast.Identifier, ast.Quote, ast.Nil +Identifier, Quote, Undefined = ast.Identifier, ast.Quote, ast.Undefined attached_block_identifier = Identifier:new("_") attached_block_symbol = attached_block_identifier:to_symbol() diff --git a/anselme/ast/Symbol.lua b/anselme/ast/Symbol.lua index c7e7ab7..189f626 100644 --- a/anselme/ast/Symbol.lua +++ b/anselme/ast/Symbol.lua @@ -15,7 +15,6 @@ Symbol = ast.abstract.Node { exported = nil, -- bool confined_to_branch = nil, -- bool - undefine = nil, -- bool init = function(self, str, modifiers) modifiers = modifiers or {} @@ -25,7 +24,6 @@ Symbol = ast.abstract.Node { self.alias = modifiers.alias self.confined_to_branch = modifiers.confined_to_branch self.exported = modifiers.exported - self.undefine = modifiers.undefine end, _eval = function(self, state) @@ -36,7 +34,7 @@ Symbol = ast.abstract.Node { with = function(self, modifiers) modifiers = modifiers or {} - for _, k in ipairs{"constant", "type_check", "alias", "exported", "confined_to_branch", "undefine"} do + for _, k in ipairs{"constant", "type_check", "alias", "exported", "confined_to_branch"} do if modifiers[k] == nil then modifiers[k] = self[k] end diff --git a/anselme/ast/Undefined.lua b/anselme/ast/Undefined.lua new file mode 100644 index 0000000..6bef7e7 --- /dev/null +++ b/anselme/ast/Undefined.lua @@ -0,0 +1,13 @@ +local ast = require("anselme.ast") + +return ast.abstract.Runtime { + type = "undefined", + + init = function(self) end, + + _format = function(self) + return "" + end, + + truthy = function(self) return false end +}