1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-28 09:09:31 +00:00

Rework variable undefinition

This commit is contained in:
Étienne Fildadut 2024-01-03 16:40:49 +01:00
parent 93dadb3b5d
commit 3d10b1b15c
4 changed files with 55 additions and 37 deletions

View file

@ -2,7 +2,7 @@ local ast = require("anselme.ast")
local operator_priority = require("anselme.common").operator_priority 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 { local VariableMetadata = ast.abstract.Runtime {
type = "variable metadata", type = "variable metadata",
@ -22,6 +22,10 @@ local VariableMetadata = ast.abstract.Runtime {
return v return v
end end
end, end,
undefined = function(self, state)
local v = self.branched:get(state)
return Undefined:is(v)
end,
get_symbol = function(self) get_symbol = function(self)
return self.symbol return self.symbol
end, end,
@ -68,21 +72,16 @@ local Environment = ast.abstract.Runtime {
variables = nil, -- Table of { {identifier} = variable metadata, ... } variables = nil, -- Table of { {identifier} = variable metadata, ... }
partial = nil, -- { [name string] = true, ... } partial = nil, -- { [name string] = true, ... }
undefine = nil, -- { [name string] = true, ... } - variable present here will not be looked up in parent env
export = nil, -- bool export = nil, -- bool
_lookup_cache = nil, -- { [name string] = variable metadata, ... } _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) init = function(self, state, parent, partial_names, is_export)
self.variables = Table:new(state) self.variables = Table:new(state)
self.parent = parent self.parent = parent
self.partial = partial_names self.partial = partial_names
self.export = is_export 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 = {}
self._lookup_cache_current = {} self._lookup_cache_current = {}
end, end,
@ -107,14 +106,10 @@ local Environment = ast.abstract.Runtime {
or (self.export ~= symbol.exported) then or (self.export ~= symbol.exported) then
return self.parent:define(state, symbol, exp) return self.parent:define(state, symbol, exp)
end end
if symbol.undefine then local variable = VariableMetadata:new(state, symbol, exp)
self.undefine[name] = true self.variables:set(state, symbol:to_identifier(), variable)
else self._lookup_cache[name] = variable
local variable = VariableMetadata:new(state, symbol, exp) self._lookup_cache_current[name] = variable
self.variables:set(state, symbol:to_identifier(), variable)
self._lookup_cache[name] = variable
self._lookup_cache_current[name] = true
end
end, end,
-- define or redefine new overloadable variable in current environment, inheriting existing overload variants from (parent) scopes -- define or redefine new overloadable variable in current environment, inheriting existing overload variants from (parent) scopes
define_overloadable = function(self, state, symbol, exp) define_overloadable = function(self, state, symbol, exp)
@ -152,11 +147,34 @@ local Environment = ast.abstract.Runtime {
if _cache[name] == nil then if _cache[name] == nil then
if self.variables:has(state, identifier) then if self.variables:has(state, identifier) then
_cache[name] = self.variables:get(state, identifier) _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) _cache[name] = self.parent:_lookup(state, identifier)
end end
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, end,
-- returns bool if variable defined in current or parent environment -- 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 -- 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.) -- (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) defined_in_current = function(self, state, symbol)
local name = symbol.string return self:_lookup_in_current(state, symbol) ~= nil
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]
end, end,
-- return bool if variable is defined in the current environment only - won't search in parent env for exported & partial names -- 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) 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, end,
-- get variable in current or parent scope, with metadata -- get variable in current or parent scope, with metadata
@ -236,6 +243,6 @@ local Environment = ast.abstract.Runtime {
} }
package.loaded[...] = Environment 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 return Environment

View file

@ -1,7 +1,7 @@
-- create a partial layer to define temporary variables -- create a partial layer to define temporary variables
local ast = require("anselme.ast") local ast = require("anselme.ast")
local Identifier, Quote, Nil local Identifier, Quote, Undefined
local attached_block_identifier, attached_block_symbol local attached_block_identifier, attached_block_symbol
local unpack = table.unpack or unpack local unpack = table.unpack or unpack
@ -74,14 +74,14 @@ PartialScope = ast.abstract.Node {
attach_block = function(self, expression, block) attach_block = function(self, expression, block)
local partial = PartialScope:new(expression) local partial = PartialScope:new(expression)
local unpartial = PartialScope:new(block) 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)) partial:define(attached_block_symbol, Quote:new(unpartial))
return partial return partial
end end
} }
package.loaded[...] = PartialScope 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_identifier = Identifier:new("_")
attached_block_symbol = attached_block_identifier:to_symbol() attached_block_symbol = attached_block_identifier:to_symbol()

View file

@ -15,7 +15,6 @@ Symbol = ast.abstract.Node {
exported = nil, -- bool exported = nil, -- bool
confined_to_branch = nil, -- bool confined_to_branch = nil, -- bool
undefine = nil, -- bool
init = function(self, str, modifiers) init = function(self, str, modifiers)
modifiers = modifiers or {} modifiers = modifiers or {}
@ -25,7 +24,6 @@ Symbol = ast.abstract.Node {
self.alias = modifiers.alias self.alias = modifiers.alias
self.confined_to_branch = modifiers.confined_to_branch self.confined_to_branch = modifiers.confined_to_branch
self.exported = modifiers.exported self.exported = modifiers.exported
self.undefine = modifiers.undefine
end, end,
_eval = function(self, state) _eval = function(self, state)
@ -36,7 +34,7 @@ Symbol = ast.abstract.Node {
with = function(self, modifiers) with = function(self, modifiers)
modifiers = modifiers or {} 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 if modifiers[k] == nil then
modifiers[k] = self[k] modifiers[k] = self[k]
end end

13
anselme/ast/Undefined.lua Normal file
View file

@ -0,0 +1,13 @@
local ast = require("anselme.ast")
return ast.abstract.Runtime {
type = "undefined",
init = function(self) end,
_format = function(self)
return "<undefined>"
end,
truthy = function(self) return false end
}