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

Exported variables: no longer add export scope to every function, allow freely access and modifiy variable in function scope

Too many issues with predefining exported variables, and this is more flexible.
This commit is contained in:
Étienne Fildadut 2023-12-30 23:43:05 +01:00
parent 0eea4b80a6
commit 2cd910389b
14 changed files with 65 additions and 73 deletions

View file

@ -2,7 +2,6 @@
local ast = require("anselme.ast") local ast = require("anselme.ast")
local Overloadable, Runtime = ast.abstract.Overloadable, ast.abstract.Runtime local Overloadable, Runtime = ast.abstract.Overloadable, ast.abstract.Runtime
local Definition
local resume_manager local resume_manager
@ -12,27 +11,14 @@ Closure = Runtime(Overloadable) {
func = nil, -- Function func = nil, -- Function
scope = nil, -- Environment scope = nil, -- Environment
exported_scope = nil, -- Environment
init = function(self, func, state) init = function(self, func, state)
self.func = func self.func = func
-- layer a new scope layer on top of captured/current scope
-- to allow future define in the function (fn.:var = "foo")
state.scope:push()
self.scope = state.scope:capture() self.scope = state.scope:capture()
-- layer a new export layer on top of captured/current scope
state.scope:push_export()
self.exported_scope = state.scope:capture()
-- pre-define exports
for _, target in pairs(self:list_resume_targets()) do
if Definition:is(target) and target.symbol.exported then
resume_manager:push_no_continue(state, target)
state.scope:push() -- create temp func scope, in case non-export definitions are done in the resume
self.func.expression:eval(state)
state.scope:pop()
resume_manager:pop(state)
end
end
state.scope:pop() state.scope:pop()
end, end,
@ -43,7 +29,6 @@ Closure = Runtime(Overloadable) {
traverse = function(self, fn, ...) traverse = function(self, fn, ...)
fn(self.func, ...) fn(self.func, ...)
fn(self.scope, ...) fn(self.scope, ...)
fn(self.exported_scope, ...)
end, end,
compatible_with_arguments = function(self, state, args) compatible_with_arguments = function(self, state, args)
@ -53,15 +38,23 @@ Closure = Runtime(Overloadable) {
return self.func.parameters:format(state) return self.func.parameters:format(state)
end, end,
call_dispatched = function(self, state, args) call_dispatched = function(self, state, args)
state.scope:push(self.exported_scope) state.scope:push(self.scope)
local exp = self.func:call_dispatched(state, args) local exp = self.func:call_dispatched(state, args)
state.scope:pop() state.scope:pop()
return exp return exp
end, end,
resume = function(self, state, target)
if self.func.parameters.min_arity > 0 then error("can't resume function with parameters") end
state.scope:push(self.scope)
resume_manager:push(state, target)
local exp = self.func:call(state, ast.ArgumentTuple:new())
resume_manager:pop(state)
state.scope:pop()
return exp
end,
} }
package.loaded[...] = Closure package.loaded[...] = Closure
Definition = ast.Definition
resume_manager = require("anselme.state.resume_manager") resume_manager = require("anselme.state.resume_manager")
return Closure return Closure

View file

@ -3,7 +3,7 @@ local Nil, Overloadable
local operator_priority = require("anselme.common").operator_priority local operator_priority = require("anselme.common").operator_priority
local Definition = ast.abstract.ResumeTarget { local Definition = ast.abstract.Node {
type = "definition", type = "definition",
symbol = nil, symbol = nil,
@ -27,14 +27,7 @@ local Definition = ast.abstract.ResumeTarget {
end, end,
_eval = function(self, state) _eval = function(self, state)
if self.symbol.exported and state.scope:defined_in_current(self.symbol) then
return Nil:new() -- export vars: can reuse existing definition
end
local symbol = self.symbol:eval(state) local symbol = self.symbol:eval(state)
if symbol.alias then
state.scope:define_alias(symbol, self.expression)
else
local val = self.expression:eval(state) local val = self.expression:eval(state)
if Overloadable:issub(val) then if Overloadable:issub(val) then
@ -42,7 +35,6 @@ local Definition = ast.abstract.ResumeTarget {
else else
state.scope:define(symbol, val) state.scope:define(symbol, val)
end end
end
return Nil:new() return Nil:new()
end, end,

View file

@ -169,7 +169,7 @@ local Environment = ast.abstract.Runtime {
end end
return false return false
end, end,
-- return bool if variable is defined in the current environment only - won't search in parent event 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)
end, end,

View file

@ -13,10 +13,9 @@ Function = Overloadable {
parameters = nil, -- ParameterTuple parameters = nil, -- ParameterTuple
expression = nil, expression = nil,
init = function(self, parameters, expression, exports) init = function(self, parameters, expression)
self.parameters = parameters self.parameters = parameters
self.expression = ReturnBoundary:new(expression) self.expression = ReturnBoundary:new(expression)
self.exports = exports or {}
end, end,
_format = function(self, ...) _format = function(self, ...)
@ -33,10 +32,6 @@ Function = Overloadable {
traverse = function(self, fn, ...) traverse = function(self, fn, ...)
fn(self.parameters, ...) fn(self.parameters, ...)
fn(self.expression, ...) fn(self.expression, ...)
for sym, val in pairs(self.exports) do
fn(sym, ...)
fn(val, ...)
end
end, end,
compatible_with_arguments = function(self, state, args) compatible_with_arguments = function(self, state, args)
@ -61,7 +56,7 @@ Function = Overloadable {
end, end,
_eval = function(self, state) _eval = function(self, state)
return Closure:new(Function:new(self.parameters:eval(state), self.expression, self.exports), state) return Closure:new(Function:new(self.parameters:eval(state), self.expression), state)
end, end,
} }

View file

@ -21,6 +21,12 @@ local common = {
:gsub("N", math.random(0x8, 0xb)) -- variant 1 :gsub("N", math.random(0x8, 0xb)) -- variant 1
:gsub("x", function() return ("%x"):format(math.random(0x0, 0xf)) end) -- random hexadecimal digit :gsub("x", function() return ("%x"):format(math.random(0x0, 0xf)) end) -- random hexadecimal digit
end, end,
-- same as assert, but do not add position information
-- useful for errors raised from anselme (don't care about Lua error position)
assert0 = function(v, message, ...)
if not v then error(message, 0) end
return v, message, ...
end,
-- list of operators and their priority that are handled through regular function calls & can be overloaded/etc. by the user -- list of operators and their priority that are handled through regular function calls & can be overloaded/etc. by the user
regular_operators = { regular_operators = {
prefixes = { prefixes = {

View file

@ -10,17 +10,9 @@ local persistent_manager = require("anselme.state.persistent_manager")
local uuid = require("anselme.common").uuid local uuid = require("anselme.common").uuid
local parser = require("anselme.parser") local parser = require("anselme.parser")
local binser = require("anselme.lib.binser") local binser = require("anselme.lib.binser")
local assert0 = require("anselme.common").assert0
local anselme local anselme
-- same as assert, but do not add position information
-- useful for errors raised from anselme (don't care about Lua error position)
local function assert0(v, message, ...)
if not v then
error(message, 0)
end
return v, message, ...
end
local State local State
State = class { State = class {
type = "anselme state", type = "anselme state",

View file

@ -1,35 +1,41 @@
local ast = require("anselme.ast") local ast = require("anselme.ast")
local Nil, Boolean, Definition = ast.Nil, ast.Boolean, ast.Definition local Nil, Boolean, Definition, Call, Function, ParameterTuple, FunctionParameter, Identifier, Overload = ast.Nil, ast.Boolean, ast.Definition, ast.Call, ast.Function, ast.ParameterTuple, ast.FunctionParameter, ast.Identifier, ast.Overload
local assert0 = require("anselme.common").assert0
return { return {
{ {
"defined", "(c::closure, s::string)", "defined", "(c::closure, s::string)",
function(state, c, s) function(state, c, s)
return Boolean:new(c.exported_scope:defined_in_current_strict(state, s:to_identifier())) return Boolean:new(c.scope:defined_in_current_strict(state, s:to_identifier()))
end
},
{
"has upvalue", "(c::closure, s::string)",
function(state, c, s)
return Boolean:new(c.scope:defined(state, s:to_identifier()))
end end
}, },
{ {
"_._", "(c::closure, s::string)", "_._", "(c::closure, s::string)",
function(state, c, s) function(state, c, s)
local identifier = s:to_identifier() local identifier = s:to_identifier()
assert(c.exported_scope:defined_in_current_strict(state, identifier), ("no exported variable %q defined in closure"):format(s.string)) assert0(c.scope:defined(state, identifier), ("no variable %q defined in closure"):format(s.string))
return c.exported_scope:get(state, identifier) return c.scope:get(state, identifier)
end end
}, },
{ {
"_._", "(c::closure, s::string) = v", "_._", "(c::closure, s::string) = v",
function(state, c, s, v) function(state, c, s, v)
local identifier = s:to_identifier() local identifier = s:to_identifier()
assert(c.exported_scope:defined_in_current_strict(state, identifier), ("no exported variable %q defined in closure"):format(s.string)) assert0(c.scope:defined(state, identifier), ("no variable %q defined in closure"):format(s.string))
c.exported_scope:set(state, identifier, v) c.scope:set(state, identifier, v)
return Nil:new() return Nil:new()
end end
}, },
{ {
"_._", "(c::closure, s::symbol) = v", "_._", "(c::closure, s::symbol) = v",
function(state, c, s, v) function(state, c, s, v)
assert(s.exported, "can't define a non-exported variable from the outside of the closure") state.scope:push(c.scope)
state.scope:push(c.exported_scope)
local r = Definition:new(s, v):eval(state) local r = Definition:new(s, v):eval(state)
state.scope:pop() state.scope:pop()
return r return r

View file

@ -1,11 +1,14 @@
--# run #-- --# run #--
--- text --- --- text ---
| {}"kk" |
| {}"ko" |
--- text ---
| {}"" {}"42" {}"" | | {}"" {}"42" {}"" |
--- error --- --- error ---
./anselme/stdlib/closure.lua:15: no exported variable "y" defined in closure identifier "z" is undefined in branch 6843ee85-cea8-445d-10f21-9a8e54372094
↳ from test/tests/exported variable nested.ans:10:4 in call: f . "y" ↳ from test/tests/exported variable nested.ans:12:3 in identifier: z
↳ from test/tests/exported variable nested.ans:10:1 in text interpolation: | {f . "y"} | ↳ from test/tests/exported variable nested.ans:12:1 in text interpolation: | {z} |
↳ from test/tests/exported variable nested.ans:10:1 in translatable: | {f . "y"} | ↳ from test/tests/exported variable nested.ans:12:1 in translatable: | {z} |
↳ from ? in block: :f = ($() _)… ↳ from ? in block: :f = ($() _)…
--# saved #-- --# saved #--
{} {}

View file

@ -8,7 +8,7 @@
--- text --- --- text ---
| {}"" {}"1" {}"" | | {}"" {}"1" {}"" |
--- text --- --- text ---
| {}"exported:" | | {}"upvalue:" |
--- text --- --- text ---
| {}"" {}"1" {}"" | | {}"" {}"1" {}"" |
--- text --- --- text ---

View file

@ -5,6 +5,8 @@
:@z = 12 :@z = 12
| ko | ko
|{f.x} f!
|{f.y} |{x}
|{z}

View file

@ -1,4 +1,5 @@
:f = $ :f = $
:@x = "ok" :@x = "ok"
f!
f.x x

View file

@ -6,11 +6,10 @@
a = a + 1 a = a + 1
:$ g :$ g
:@a = 1
|{a} |{a}
a = a + 1 a = a + 1
g.:a = 1
|local: |local:
@ -20,7 +19,7 @@ f!
f! f!
|exported: |upvalue:
g! g!

View file

@ -1,4 +1,5 @@
:b = 5
:$ a :$ a
:@b = 5 :b = 12
|a: {a.b} |a: {a.b}

View file

@ -1,5 +1,7 @@
:$ f :$ f
:@x = 5 ()
f.:x = 5
:a = f :a = f