mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-27 16:49:31 +00:00
Reuse a global state with standard library already loaded in tests, and associated branch handling fixes
This commit is contained in:
parent
8ff082625a
commit
c027c87dc4
10 changed files with 80 additions and 60 deletions
|
|
@ -20,15 +20,22 @@ Branched = ast.abstract.Runtime {
|
||||||
return not not self.value[state.branch_id]
|
return not not self.value[state.branch_id]
|
||||||
end,
|
end,
|
||||||
get = function(self, state)
|
get = function(self, state)
|
||||||
return self.value[state.branch_id] or self.value[state.source_branch_id]
|
local branch = state
|
||||||
|
repeat
|
||||||
|
if self.value[branch.branch_id] then
|
||||||
|
return self.value[branch.branch_id]
|
||||||
|
end
|
||||||
|
branch = branch.source_branch
|
||||||
|
until not branch
|
||||||
|
error("no value assigned in this branch or any parent branch")
|
||||||
end,
|
end,
|
||||||
set = function(self, state, value)
|
set = function(self, state, value)
|
||||||
self.value[state.branch_id] = value
|
self.value[state.branch_id] = value
|
||||||
end,
|
end,
|
||||||
_merge = function(self, state, cache)
|
_merge = function(self, state, cache)
|
||||||
local val = self.value[state.branch_id]
|
local val = self.value[state.branch_id]
|
||||||
if val then
|
if val and state.source_branch then
|
||||||
self.value[state.source_branch_id] = val
|
self.value[state.source_branch.branch_id] = val
|
||||||
self.value[state.branch_id] = nil
|
self.value[state.branch_id] = nil
|
||||||
val:merge(state, cache)
|
val:merge(state, cache)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -75,16 +75,16 @@ local Environment = ast.abstract.Runtime {
|
||||||
partial = nil, -- { [name string] = true, ... }
|
partial = nil, -- { [name string] = true, ... }
|
||||||
export = nil, -- bool
|
export = nil, -- bool
|
||||||
|
|
||||||
_lookup_cache = nil, -- { [name string] = variable metadata, ... }
|
_lookup_cache = nil, -- Table of { [identifier] = variable metadata, ... }
|
||||||
_lookup_cache_current = nil, -- { [name string] = variable metadata, ... }
|
_lookup_cache_current = nil, -- Table of { [identifier] = 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._lookup_cache = {}
|
self._lookup_cache = Table:new(state)
|
||||||
self._lookup_cache_current = {}
|
self._lookup_cache_current = Table:new(state)
|
||||||
end,
|
end,
|
||||||
-- precache variable and return its variable metadata
|
-- precache variable and return its variable metadata
|
||||||
-- when cached, if a variable is defined in a parent scope after it has been cached here from a higher parent, it will not be used in this env
|
-- when cached, if a variable is defined in a parent scope after it has been cached here from a higher parent, it will not be used in this env
|
||||||
|
|
@ -101,6 +101,8 @@ local Environment = ast.abstract.Runtime {
|
||||||
fn(self.parent, ...)
|
fn(self.parent, ...)
|
||||||
end
|
end
|
||||||
fn(self.variables, ...)
|
fn(self.variables, ...)
|
||||||
|
fn(self._lookup_cache, ...)
|
||||||
|
fn(self._lookup_cache_current, ...)
|
||||||
end,
|
end,
|
||||||
_format = function(self, state)
|
_format = function(self, state)
|
||||||
return "<environment>"
|
return "<environment>"
|
||||||
|
|
@ -117,9 +119,10 @@ local Environment = ast.abstract.Runtime {
|
||||||
return self.parent:define(state, symbol, exp)
|
return self.parent:define(state, symbol, exp)
|
||||||
end
|
end
|
||||||
local variable = VariableMetadata:new(state, symbol, exp)
|
local variable = VariableMetadata:new(state, symbol, exp)
|
||||||
self.variables:set(state, symbol:to_identifier(), variable)
|
local identifier = symbol:to_identifier()
|
||||||
self._lookup_cache[name] = variable
|
self.variables:set(state, identifier, variable)
|
||||||
self._lookup_cache_current[name] = variable
|
self._lookup_cache:set(state, identifier, variable)
|
||||||
|
self._lookup_cache_current:set(state, identifier, variable)
|
||||||
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)
|
||||||
|
|
@ -151,40 +154,46 @@ local Environment = ast.abstract.Runtime {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
-- lookup variable in current or parent scope, cache the result
|
-- lookup variable in current or parent scope, cache the result
|
||||||
|
-- returns nil if undefined
|
||||||
_lookup = function(self, state, identifier)
|
_lookup = function(self, state, identifier)
|
||||||
local name = identifier.name
|
|
||||||
local _cache = self._lookup_cache
|
local _cache = self._lookup_cache
|
||||||
if _cache[name] == nil then
|
local var
|
||||||
|
if not _cache:has(state, identifier) then
|
||||||
if self.variables:has(state, identifier) then
|
if self.variables:has(state, identifier) then
|
||||||
_cache[name] = self.variables:get(state, identifier)
|
var = self.variables:get(state, identifier)
|
||||||
elseif self.parent then
|
elseif self.parent then
|
||||||
_cache[name] = self.parent:_lookup(state, identifier)
|
var = self.parent:_lookup(state, identifier)
|
||||||
end
|
end
|
||||||
|
if var then _cache:set(state, identifier, var) end
|
||||||
|
else
|
||||||
|
var = _cache:get(state, identifier)
|
||||||
end
|
end
|
||||||
local var = _cache[name]
|
|
||||||
if var and not var:undefined(state) then
|
if var and not var:undefined(state) then
|
||||||
return var
|
return var
|
||||||
end
|
end
|
||||||
return nil
|
|
||||||
end,
|
end,
|
||||||
|
-- lookup variable in current scope, cache the result
|
||||||
|
-- returns nil if undefined
|
||||||
_lookup_in_current = function(self, state, symbol)
|
_lookup_in_current = function(self, state, symbol)
|
||||||
local name = symbol.string
|
local identifier = symbol:to_identifier()
|
||||||
local _cache = self._lookup_cache_current
|
local _cache = self._lookup_cache_current
|
||||||
if _cache[name] == nil then
|
local var
|
||||||
local identifier = symbol:to_identifier()
|
if not _cache:has(state, identifier) then
|
||||||
|
local name = symbol.string
|
||||||
if self.variables:has(state, identifier) then
|
if self.variables:has(state, identifier) then
|
||||||
_cache[name] = self.variables:get(state, identifier)
|
var = self.variables:get(state, identifier)
|
||||||
elseif self.parent then
|
elseif self.parent then
|
||||||
if (self.partial and not self.partial[name]) or (self.export ~= symbol.exported) then
|
if (self.partial and not self.partial[name]) or (self.export ~= symbol.exported) then
|
||||||
_cache[name] = self.parent:_lookup_in_current(state, symbol)
|
var = self.parent:_lookup_in_current(state, symbol)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if var then _cache:set(state, identifier, var) end
|
||||||
|
else
|
||||||
|
var = _cache:get(state, identifier)
|
||||||
end
|
end
|
||||||
local var = _cache[name]
|
|
||||||
if var and not var:undefined(state) then
|
if var and not var:undefined(state) then
|
||||||
return var
|
return var
|
||||||
end
|
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
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
-- note: functions only appear in non-evaluated nodes! once evaluated, they always become closures
|
-- note: functions only appear in non-evaluated nodes! once evaluated, they always become closures
|
||||||
|
|
||||||
local ast = require("anselme.ast")
|
local ast = require("anselme.ast")
|
||||||
local ReturnBoundary, Environment, Identifier
|
local ReturnBoundary, Identifier
|
||||||
|
|
||||||
local operator_priority = require("anselme.common").operator_priority
|
local operator_priority = require("anselme.common").operator_priority
|
||||||
|
|
||||||
|
|
@ -62,7 +62,8 @@ Function = ast.abstract.Overloadable {
|
||||||
end
|
end
|
||||||
local upvalues = {}
|
local upvalues = {}
|
||||||
for _, identifier in ipairs(used_identifiers) do
|
for _, identifier in ipairs(used_identifiers) do
|
||||||
table.insert(upvalues, scope:precache(state, identifier))
|
local var = scope:precache(state, identifier)
|
||||||
|
if var then table.insert(upvalues, var) end
|
||||||
end
|
end
|
||||||
|
|
||||||
return Function:new(self.parameters:eval(state), self.expression, scope, upvalues)
|
return Function:new(self.parameters:eval(state), self.expression, scope, upvalues)
|
||||||
|
|
@ -159,7 +160,7 @@ Function = ast.abstract.Overloadable {
|
||||||
}
|
}
|
||||||
|
|
||||||
package.loaded[...] = Function
|
package.loaded[...] = Function
|
||||||
ReturnBoundary, Environment, Identifier = ast.ReturnBoundary, ast.Environment, ast.Identifier
|
ReturnBoundary, Identifier = ast.ReturnBoundary, ast.Identifier
|
||||||
|
|
||||||
resume_manager = require("anselme.state.resume_manager")
|
resume_manager = require("anselme.state.resume_manager")
|
||||||
calling_environment_manager = require("anselme.state.calling_environment_manager")
|
calling_environment_manager = require("anselme.state.calling_environment_manager")
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,12 @@ local State
|
||||||
State = class {
|
State = class {
|
||||||
type = "anselme state",
|
type = "anselme state",
|
||||||
|
|
||||||
init = function(self, branch_from)
|
init = function(self, branch_from, branch_id)
|
||||||
-- create a new branch from an existing state
|
-- create a new branch from an existing state
|
||||||
-- note: the existing state must not currently have an active script
|
-- note: the existing state must not currently have an active script
|
||||||
if branch_from then
|
if branch_from then
|
||||||
self.branch_id = uuid()
|
self.branch_id = branch_id or uuid()
|
||||||
self.source_branch_id = branch_from.branch_id
|
self.source_branch = branch_from
|
||||||
self.scope = ScopeStack:new(self, branch_from)
|
self.scope = ScopeStack:new(self, branch_from)
|
||||||
|
|
||||||
event_manager:reset(self) -- events are isolated per branch
|
event_manager:reset(self) -- events are isolated per branch
|
||||||
|
|
@ -74,16 +74,16 @@ State = class {
|
||||||
|
|
||||||
--- Name of the branch associated to this State.
|
--- Name of the branch associated to this State.
|
||||||
branch_id = "main",
|
branch_id = "main",
|
||||||
--- Name of the branch this State was branched from.
|
--- State this State was branched from.
|
||||||
source_branch_id = "main",
|
source_branch = nil,
|
||||||
|
|
||||||
--- Return a new branch of this State.
|
--- Return a new branch of this State.
|
||||||
--
|
--
|
||||||
-- Branches act as indepent copies of this State where any change will not be reflected in the source State until it is merged back into the source branch.
|
-- Branches act as indepent copies of this State where any change will not be reflected in the source State until it is merged back into the source branch.
|
||||||
-- Note: probably makes the most sense to create branches from the main State only.
|
-- Note: probably makes the most sense to create branches from the main State only.
|
||||||
branch = function(self)
|
branch = function(self, branch_id)
|
||||||
assert(not self:active(), "can't branch while a script is active")
|
assert(not self:active(), "can't branch while a script is active")
|
||||||
return State:new(self)
|
return State:new(self, branch_id)
|
||||||
end,
|
end,
|
||||||
--- Merge everything that was changed in this branch back into the main State branch.
|
--- Merge everything that was changed in this branch back into the main State branch.
|
||||||
--
|
--
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
--- text ---
|
--- text ---
|
||||||
| {}"" {}"42" {}"" |
|
| {}"" {}"42" {}"" |
|
||||||
--- error ---
|
--- error ---
|
||||||
[0m[31m[0m[31m[0m[31midentifier "z" is undefined in branch 1c25ebb8-5027-4ccf-105b9-370d83e78fd7[0m
|
[0m[31m[0m[31m[0m[31midentifier "z" is undefined in branch test/tests/exported variable nested.ans - run[0m
|
||||||
↳ from [4mtest/tests/exported variable nested.ans:12:3[0m in identifier: [2mz[0m[0m
|
↳ from [4mtest/tests/exported variable nested.ans:12:3[0m in identifier: [2mz[0m[0m
|
||||||
↳ from [4mtest/tests/exported variable nested.ans:12:1[0m in text interpolation: [2m| {z} |[0m[0m
|
↳ from [4mtest/tests/exported variable nested.ans:12:1[0m in text interpolation: [2m| {z} |[0m[0m
|
||||||
↳ from [4m?[0m in block: [2m:f = ($() _)…[0m
|
↳ from [4m?[0m in block: [2m:f = ($() _)…[0m
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
type check failure for parameter tuple
|
type check failure for parameter tuple
|
||||||
• $(range::is range) (from stdlib/for.ans:19:1):
|
• $(range::is range) (from stdlib/for.ans:19:1):
|
||||||
type check failure for parameter range
|
type check failure for parameter range
|
||||||
• $(s::is struct) (from stdlib/for.ans:3:14):
|
• $(s::is struct) (from stdlib/for.ans:2:1):
|
||||||
type check failure for parameter s[0m
|
type check failure for parameter s[0m
|
||||||
↳ from [4mstdlib/for.ans:3:18[0m in call: [2miter(var)[0m[0m
|
↳ from [4mstdlib/for.ans:3:18[0m in call: [2miter(var)[0m[0m
|
||||||
↳ from [4mstdlib/for.ans:3:12[0m in definition: [2m:iterator = iter(var)[0m[0m
|
↳ from [4mstdlib/for.ans:3:12[0m in definition: [2m:iterator = iter(var)[0m[0m
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
--# run #--
|
--# run #--
|
||||||
--- text ---
|
--- text ---
|
||||||
| {}"a" |
|
|
||||||
--- text ---
|
|
||||||
| {}"c" |
|
| {}"c" |
|
||||||
--- text ---
|
--- text ---
|
||||||
|
| {}"a" |
|
||||||
|
--- text ---
|
||||||
| {}"b" |
|
| {}"b" |
|
||||||
--- text ---
|
--- text ---
|
||||||
| {}"c" |
|
| {}"c" |
|
||||||
--- text ---
|
--- text ---
|
||||||
| {}"c" |
|
| {}"a" |
|
||||||
--- return ---
|
--- return ---
|
||||||
()
|
()
|
||||||
--# saved #--
|
--# saved #--
|
||||||
{"a.checkpoint":false, "a.run":1, "b.checkpoint":false, "b.run":1, "c.checkpoint":false, "c.run":3}
|
{"a.checkpoint":false, "a.run":2, "b.checkpoint":false, "b.run":1, "c.checkpoint":false, "c.run":2}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
--# run #--
|
--# run #--
|
||||||
--- error ---
|
--- error ---
|
||||||
[0m[31m[0m[31m[0m[31midentifier "b" is undefined in branch 1c25ebb8-5027-4ccf-105b9-370d83e78fd7[0m
|
[0m[31m[0m[31m[0m[31midentifier "b" is undefined in branch test/tests/function scope wrong.ans - run[0m
|
||||||
↳ from [4mtest/tests/function scope wrong.ans:4:7[0m in identifier: [2mb[0m[0m
|
↳ from [4mtest/tests/function scope wrong.ans:4:7[0m in identifier: [2mb[0m[0m
|
||||||
↳ from [4mtest/tests/function scope wrong.ans:4:1[0m in text interpolation: [2m| a: {b} |[0m[0m
|
↳ from [4mtest/tests/function scope wrong.ans:4:1[0m in text interpolation: [2m| a: {b} |[0m[0m
|
||||||
↳ from [4m?[0m in block: [2m:a = ($() _)…[0m
|
↳ from [4m?[0m in block: [2m:a = ($() _)…[0m
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,17 @@
|
||||||
expected 3 arguments, received 2
|
expected 3 arguments, received 2
|
||||||
• $(s::is script, k::is string) (from stdlib/script.ans:41:1):
|
• $(s::is script, k::is string) (from stdlib/script.ans:41:1):
|
||||||
type check failure for parameter s
|
type check failure for parameter s
|
||||||
• $(c::is function, s::is symbol) = v (from test/tests/function separate variable from variants.ans:10:4):
|
• $(c::is function, s::is symbol) = v (from stdlib/for.ans:2:1):
|
||||||
expected 3 arguments, received 2
|
expected 3 arguments, received 2
|
||||||
• $(c::is function, s::is string) = v (from test/tests/function separate variable from variants.ans:10:4):
|
• $(c::is function, s::is string) = v (from stdlib/for.ans:2:1):
|
||||||
expected 3 arguments, received 2
|
expected 3 arguments, received 2
|
||||||
• $(c::is function, s::is string) (from test/tests/function separate variable from variants.ans:10:4):
|
• $(c::is function, s::is string) (from stdlib/for.ans:2:1):
|
||||||
type check failure for parameter c
|
type check failure for parameter c
|
||||||
• $(c::is environment, s::is symbol) = v (from test/tests/function separate variable from variants.ans:10:4):
|
• $(c::is environment, s::is symbol) = v (from stdlib/for.ans:2:1):
|
||||||
expected 3 arguments, received 2
|
expected 3 arguments, received 2
|
||||||
• $(c::is environment, s::is string) = v (from test/tests/function separate variable from variants.ans:10:4):
|
• $(c::is environment, s::is string) = v (from stdlib/for.ans:2:1):
|
||||||
expected 3 arguments, received 2
|
expected 3 arguments, received 2
|
||||||
• $(c::is environment, s::is string) (from test/tests/function separate variable from variants.ans:10:4):
|
• $(c::is environment, s::is string) (from stdlib/for.ans:2:1):
|
||||||
type check failure for parameter c[0m
|
type check failure for parameter c[0m
|
||||||
↳ from [4mtest/tests/function separate variable from variants.ans:10:4[0m in call: [2mf . "a"[0m[0m
|
↳ from [4mtest/tests/function separate variable from variants.ans:10:4[0m in call: [2mf . "a"[0m[0m
|
||||||
↳ from [4mtest/tests/function separate variable from variants.ans:10:1[0m in text interpolation: [2m| {f . "a"} = 2 |[0m[0m
|
↳ from [4mtest/tests/function separate variable from variants.ans:10:1[0m in text interpolation: [2m| {f . "a"} = 2 |[0m[0m
|
||||||
|
|
|
||||||
31
test/run.lua
31
test/run.lua
|
|
@ -64,6 +64,15 @@ local function run_loop(run_state, write_output, interactive)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- create execution state
|
||||||
|
local global_state = anselme:new()
|
||||||
|
global_state:load_stdlib()
|
||||||
|
global_state:define("interrupt", "(code::is string)", function(state, code) state:interrupt(code:to_lua(state), "interrupt") return ast.Nil:new() end, true)
|
||||||
|
global_state:define("interrupt", "()", function(state) state:interrupt() return ast.Nil:new() end, true)
|
||||||
|
global_state:define("wait", "(duration::is number)", function(duration) coroutine.yield("wait", duration) end)
|
||||||
|
global_state:define("serialize", "(value)", function(state, value) return ast.String:new(value:serialize(state)) end, true)
|
||||||
|
global_state:define("deserialize", "(str::is string)", function(state, str) return ast.abstract.Node:deserialize(state, str.string) end, true)
|
||||||
|
|
||||||
-- run a test file and return the result
|
-- run a test file and return the result
|
||||||
local function run(path, interactive)
|
local function run(path, interactive)
|
||||||
local out = { "--# run #--" }
|
local out = { "--# run #--" }
|
||||||
|
|
@ -73,23 +82,17 @@ local function run(path, interactive)
|
||||||
end
|
end
|
||||||
math.randomseed()
|
math.randomseed()
|
||||||
|
|
||||||
local state = anselme:new()
|
local state = global_state:branch(path)
|
||||||
state:load_stdlib()
|
state:define("run in new branch", "(code)", function(state, code)
|
||||||
|
local parallel_state = state.source_branch:branch()
|
||||||
state:define("interrupt", "(code::is string)", function(state, code) state:interrupt(code:to_lua(state), "interrupt") return ast.Nil:new() end, true)
|
|
||||||
state:define("interrupt", "()", function(state) state:interrupt() return ast.Nil:new() end, true)
|
|
||||||
state:define("wait", "(duration::is number)", function(duration) coroutine.yield("wait", duration) end)
|
|
||||||
state:define("run in new branch", "(code)", function(code)
|
|
||||||
local parallel_state = state:branch()
|
|
||||||
write_output("--# parallel script #--")
|
write_output("--# parallel script #--")
|
||||||
parallel_state:run(code, "parallel")
|
parallel_state:run(code.string, "parallel")
|
||||||
run_loop(parallel_state, write_output, interactive)
|
run_loop(parallel_state, write_output, interactive)
|
||||||
write_output("--# main script #--")
|
write_output("--# main script #--")
|
||||||
end)
|
return ast.Nil:new()
|
||||||
state:define("serialize", "(value)", function(state, value) return ast.String:new(value:serialize(state)) end, true)
|
end, true)
|
||||||
state:define("deserialize", "(str::is string)", function(state, str) return ast.abstract.Node:deserialize(state, str.string) end, true)
|
|
||||||
|
|
||||||
local run_state = state:branch()
|
local run_state = state:branch(path.." - run")
|
||||||
|
|
||||||
local f = assert(io.open(path, "r"))
|
local f = assert(io.open(path, "r"))
|
||||||
local s, block = pcall(anselme.parse, f:read("a"), path)
|
local s, block = pcall(anselme.parse, f:read("a"), path)
|
||||||
|
|
@ -106,7 +109,7 @@ local function run(path, interactive)
|
||||||
run_loop(run_state, write_output, interactive)
|
run_loop(run_state, write_output, interactive)
|
||||||
|
|
||||||
if state:defined("post run check") then
|
if state:defined("post run check") then
|
||||||
local post_run_state = state:branch()
|
local post_run_state = state:branch(path.." - post run check")
|
||||||
post_run_state:run("post run check!")
|
post_run_state:run("post run check!")
|
||||||
|
|
||||||
write_output("--# post run check #--")
|
write_output("--# post run check #--")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue