1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-28 00:59:31 +00:00
anselme/ast/Environment.lua
Étienne Reuh Fildadut fe351b5ca4 Anselme v2.0.0-alpha rewrite
Woke up and felt like changing a couple things. It's actually been worked on for a while, little at a time...

The goal was to make the language and implementation much simpler. Well I don't know if it really ended up being simpler but it sure is more robust.

Main changes:
* proper first class functions and closures supports! proper scoping rules! no more namespace shenanigans!
* everything is an expression, no more statements! make the implementation both simpler and more complex, but it's much more consistent now! the syntax has massively changed as a result though.
* much more organized and easy to modify codebase: one file for each AST node, no more random fields or behavior set by some random node exceptionally, everything should now follow the same API defined in ast.abstract.Node

Every foundational feature should be implemented right now. The vast majority of things that were possible in v2 are possible now; some things aren't, but that's usually because v2 is a bit more sane.
The main missing things before a proper release are tests and documentation. There's a few other things that might be implemented later, see the ideas.md file.
2023-12-22 13:25:28 +01:00

214 lines
6.7 KiB
Lua

local ast = require("ast")
local operator_priority = require("common").operator_priority
local Branched, ArgumentTuple, Overload, Overloadable, Table
local VariableMetadata = ast.abstract.Runtime {
type = "variable metadata",
symbol = nil,
branched = nil,
format_priority = operator_priority["_=_"],
init = function(self, state, symbol, value)
self.symbol = symbol
self.branched = Branched:new(state, value)
end,
get = function(self, state)
return self.branched:get(state)
end,
set = function(self, state, value)
assert(not self.symbol.constant, ("trying to change the value of constant %s"):format(self.symbol.string))
if self.symbol.type_check then
local r = self.symbol.type_check:call(state, ArgumentTuple:new(value))
if not r:truthy() then error(("type check failure for %s; %s does not satisfy %s"):format(self.symbol.string, value, self.symbol.type_check)) end
end
self.branched:set(state, value)
end,
_format = function(self, ...)
return ("%s=%s"):format(self.symbol:format(...), self.branched:format(...))
end,
traverse = function(self, fn, ...)
fn(self.symbol, ...)
fn(self.branched, ...)
end,
_merge = function(self, state, cache)
if not self.symbol.confined_to_branch then
self.branched:merge(state, cache)
end
end
}
local Environment = ast.abstract.Runtime {
type = "environment",
parent = nil, -- environment or nil
variables = nil, -- Table of { {identifier} = variable metadata, ... }
partial = nil, -- { [name string] = true, ... }
export = nil, -- bool
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
end,
traverse = function(self, fn, ...)
if self.parent then
fn(self.parent, ...)
end
fn(self.variables, ...)
end,
_format = function(self, state)
return "<environment>"
end,
-- define new variable in the environment
define = function(self, state, symbol, exp)
local name = symbol.string
if self:defined_in_current(state, symbol) then
error(name.." is already defined in the current scope")
end
if (self.partial and not self.partial[name])
or (self.export ~= symbol.exported) then
return self.parent:define(state, symbol, exp)
end
self.variables:set(state, symbol:to_identifier(), VariableMetadata:new(state, symbol, exp))
end,
-- define or redefine new overloadable variable in current environment, inheriting existing overload variants from (parent) scopes
define_overloadable = function(self, state, symbol, exp)
assert(Overloadable:issub(exp), "trying to add an non-overloadable value to an overload")
local identifier = symbol:to_identifier()
-- add overload variants already defined in current or parent scope
if self:defined(state, identifier) then
local val = self:get(state, identifier)
if Overload:is(val) then
exp = Overload:new(exp)
for _, v in ipairs(val.list) do
exp:insert(v)
end
elseif Overloadable:issub(val) then
exp = Overload:new(exp, val)
elseif self:defined_in_current(state, symbol) then
error(("can't add an overload variant to non-overloadable variable %s defined in the same scope"):format(identifier))
end
end
-- update/define in current scope
if self:defined_in_current(state, symbol) then
self:set(state, identifier, exp)
else
self:define(state, symbol, exp)
end
end,
-- returns bool if variable defined in current or parent environment
defined = function(self, state, identifier)
if self.variables:has(state, identifier) then
return true
elseif self.parent then
return self.parent:defined(state, identifier)
end
return false
end,
-- 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
if self.variables:has(state, symbol:to_identifier()) then
return true
elseif (self.partial and not self.partial[name])
or (self.export ~= symbol.exported) then
return self.parent:defined_in_current(state, symbol)
end
return false
end,
-- return bool if variable is defined in the current environment only - won't search in parent event for exported & partial names
defined_in_current_strict = function(self, state, identifier)
return self.variables:has(state, identifier)
end,
-- get variable in current or parent scope, with metadata
_get_variable = function(self, state, identifier)
if self:defined(state, identifier) then
if self.variables:has(state, identifier) then
return self.variables:get(state, identifier)
elseif self.parent then
return self.parent:_get_variable(state, identifier)
end
else
error(("identifier %q is undefined in branch %s"):format(identifier.name, state.branch_id), 0)
end
end,
-- get variable value in current or parent environment
get = function(self, state, identifier)
return self:_get_variable(state, identifier):get(state)
end,
-- set variable value in current or parent environment
set = function(self, state, identifier, val)
return self:_get_variable(state, identifier):set(state, val)
end,
-- returns a list {[symbol]=val,...} of all persistent variables in the current strict layer
list_persistent = function(self, state)
assert(self.export, "not an export scope layer")
local r = {}
for _, vm in self.variables:iter(state) do
if vm.symbol.persistent then
r[vm.symbol] = vm:get(state)
end
end
return r
end,
-- returns a list {[symbol]=val,...} of all exported variables in the current strict layer
list_exported = function(self, state)
assert(self.export, "not an export scope layer")
local r = {}
for _, vm in self.variables:iter(state) do
if vm.symbol.exported then
r[vm.symbol] = vm:get(state)
end
end
return r
end,
-- return the depth of the environmenet, i.e. the number of parents
depth = function(self)
local d = 0
local e = self
while e.parent do
e = e.parent
d = d + 1
end
return d
end,
_debug_state = function(self, state, filter, t, level)
level = level or 0
t = t or {}
local indentation = string.rep(" ", level)
table.insert(t, ("%s> %s %s scope"):format(indentation, self.export and "exported" or "", self.partial and "partial" or ""))
for name, var in self.variables:iter(state) do
if name.name:match(filter) then
table.insert(t, ("%s| %s = %s"):format(indentation, name, var:get(state)))
end
end
if self.parent then
self.parent:_debug_state(state, filter, t, level+1)
end
return t
end,
}
package.loaded[...] = Environment
Branched, ArgumentTuple, Overload, Overloadable, Table = ast.Branched, ast.ArgumentTuple, ast.Overload, ast.abstract.Overloadable, ast.Table
return Environment