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

Variable must now be explicitly marked as persistent

This commit is contained in:
Étienne Fildadut 2022-09-27 18:41:40 +09:00
parent e9606cdee0
commit 2c6d66c222
11 changed files with 384 additions and 106 deletions

View file

@ -305,7 +305,7 @@ When a parameter list is given (or just empty parentheses `()`), the function is
~ g
```
This is basically the behaviour you'd expect from functions in most other programming languages, and what you would use in Anselme any time you don't care about storing the function variables or want the exact same initial function variables each time you call the function (e.g. recursion). Scoped variables are not kept in save files, and are not affected by checkpointing.
This is basically the behaviour you'd expect from functions in most other programming languages, and what you would use in Anselme any time you don't care about storing the function variables or want the exact same initial function variables each time you call the function (e.g. recursion). Scoped variables can not be persistent, and are not affected by checkpointing.
Functions with the same name can be defined, as long as they have a different arguments. Functions will be selected based on the number of arguments given, their name and their type constraint:
@ -362,6 +362,8 @@ Functions always have the following variables defined in its namespace by defaul
`👁️`: number, number of times the function was executed before
`🔖`: function reference, last reached checkpoint. `nil` if no checkpoint reached.
These variables are persistent, unless the function is scoped.
* `:!`: checkpoint definition. Followed by an [identifier](#identifiers), then eventually an [alias](#aliases). Define a checkpoint. Also define a new namespace for its children.
Checkpoints share most of their behavior with functions, with several exceptions. Like functions, the body is not executed when the line is reached; it must either be explicitely called in an expression or executed when resuming the parent function (see checkpoint behaviour below). Can be called in an expression. See [expressions](#checkpoint-calls) to see the different ways of calling a checkpoint manually.
@ -383,6 +385,8 @@ Checkpoints always have the following variable defined in its namespace by defau
`👁️`: number, number of times the checkpoint was executed before
`🏁`: number, number of times the checkpoint was reached before (includes times where it was resumed from and executed)
These variables are persistent.
* `:%`: class definition. Followed by an [identifier](#identifiers), then eventually an [alias](#aliases). Define a class. Also define a new namespace for its children.
Classes share most of their behavior with functions, with a few exceptions. Classes can not take arguments or be scoped; and when called, if the function does not return a value or returns `()` (nil), it will returns a new object instead based on this class. The object can be used to access variables ("attributes") defined in the class, but if one of these attributes is modified on the object it will not change the value in the base class but only in the object.
@ -415,6 +419,8 @@ Note that the new object returned by the class is also automatically given an an
~ object!show
```
Classes have the same default variable defined as functions.
* `:`: variable declaration. Followed by an [identifier](#identifiers) (with eventually an [alias](#aliases)), a `=` and an [expression](#expressions). Defines a variable with a default value and this identifier in the current [namespace]("identifiers"). The expression is not evaluated instantly, but the first time the variable is used. Don't accept children lines.
```
@ -422,12 +428,19 @@ Note that the new object returned by the class is also automatically given an an
:bar : alias = 12
```
* `::`: constant declaration. Work the same way as a variable declaration, but the variable can't be reassigned after their declaration and first evaluation, and their value is marked as constant (i.e. can not be modified even it is of a mutable type). Constants are not stored in save files and should therefore always contain the result of the expression written in the script file, even if the script has been updated.
* `::`: constant declaration. Work the same way as a variable declaration, but the variable can't be reassigned after their declaration and first evaluation, and their value is marked as constant (i.e. can not be modified even it is of a mutable type).
```
::foo = 42
```
* `:@`: persistent variable declaration. Work the same way as a variable declaration, but the variable will be stored in the save file, and if we loaded a save file its value will be retrieved from the save file instead of from the expression's result.
```
:@foo = 42
:@bar : alias = 12
```
### Text interpolation
Text and choice lines allow for arbitrary text. Expression can be evaluated and inserted into the text as the line is executed by enclosing the [expression](#expressions) into brackets. The expressions are evaluated in the same order as the reading direction.
@ -575,7 +588,7 @@ Var1 in the fn1 namespace = 2: {fn1.var1}
#### Aliases
When defining identifiers (in variables, functions or checkpoint definitions), they can be followed by a colon and another identifier. This identifier can be used as a new way to access the identifier (i.e., an alias).
When defining identifiers (in variables, functions, checkpoint or class definitions), they can be followed by a colon and another identifier. This identifier can be used as a new way to access the identifier (i.e., an alias).
```
:name: alias = 42
@ -585,20 +598,20 @@ When defining identifiers (in variables, functions or checkpoint definitions), t
Note that alias have priority over normal identifiers; if both an identifier and an alias have the same name, the alias will be used.
The main purpose of aliases is translation. When saving the state of your game's script, Anselme will store the name of the variables and their contents, and require the name to be the same when loading the save later, in order to correctly restore their values.
The main purpose of aliases is translation. When saving the state of your game's script, Anselme will store the name of the persistent variables and their contents, and require the name to be the same when loading the save later, in order to correctly restore their values.
This behaviour is fine if you only have one language; but if you want to translate your game, this means the translations will need to keep using the original, untranslated variables and functions names if it wants to be compatible with saves in differents languages. Which is not very practical or nice to read.
This behaviour is fine if you only have one language; but if you want to translate your game, this means the translations will need to keep using the original, untranslated persistent variables and functions names if it wants to be compatible with saves in differents languages. Which is not very practical or nice to read.
Anselme's solution is to keep the original name in the translated script file, but alias them with a translated name. This way, the translated script can be written withou constantly switching languages:
```
(in the original, english script)
:player name = "John Pizzapone"
:@player name = "John Pizzapone"
Hi {player name}!
(in a translated, french script)
:player name : nom du joueur = "John Pizzapone"
:@player name : nom du joueur = "John Pizzapone"
Salut {nom du joueur} !
```

View file

@ -560,15 +560,26 @@ local vm_mt = {
--- Save/load script state
--
-- Only saves variables full names and values, so make sure to not change important variables, checkpoints and functions names between a save and a load.
-- Also only save variables with usable identifiers, so will skip functions with arguments, operators, etc. (i.e. every scoped functions).
-- Loading should be done after loading all the game scripts (otherwise you will "variable already defined" errors).
-- Only saves persistent variables' full names and values.
-- Make sure to not change persistent variables names, class name, class attribute names, checkpoint names and functions names between a
-- save and a load (alias can of course be changed), as Anselme will not be able to match them to the old names stored in the save file.
--
-- If a variable is stored in the save file but is not marked as persistent in the current scripts (e.g. if you updated the Anselme scripts to
-- remove the persistence), it will not be loaded.
--
-- Loading should be done after loading all the game scripts (otherwise you will get "variable already defined" errors).
--
-- Returns this VM.
load = function(self, data)
assert(anselme.versions.save == data.anselme.versions.save, ("trying to load data from an incompatible version of Anselme; save was done using save version %s but current version is %s"):format(data.anselme.versions.save, anselme.versions.save))
for k, v in pairs(data.variables) do
self.state.variables[k] = v
if self.state.variable_metadata[k] then
if self.state.variable_metadata[k].persistent then
self.state.variables[k] = v
end
else
self.state.variables[k] = v -- non-existent variable: keep it in case there was a mistake, it's not going to affect anything anyway
end
end
return self
end,
@ -580,23 +591,7 @@ local vm_mt = {
local vars = {}
for k, v in pairs(self.state.variables) do
if should_keep_variable(self.state, k, v) then
if v.type == "object" then -- filter object attributes
local attributes = {}
for kk, vv in pairs(v.value.attributes) do
if should_keep_variable(self.state, kk, vv) then
attributes[kk] = vv
end
end
vars[k] = {
type = "object",
value = {
class = v.value.class,
attributes = attributes
}
}
else
vars[k] = v
end
vars[k] = v
end
end
return {
@ -669,8 +664,7 @@ local vm_mt = {
builtin_aliases = self.state.builtin_aliases,
aliases = setmetatable({}, { __index = self.state.aliases }),
functions = self.state.functions, -- no need for a cache as we can't define or modify any function from the interpreter for now
variable_constraints = self.state.variable_constraints, -- no cache as constraints are expected to be constant
variable_constants = self.state.variable_constants,
variable_metadata = self.state.variable_metadata, -- no cache as metadata are expected to be constant
variables = setmetatable({}, {
__index = function(variables, k)
local cache = getmetatable(variables).cache
@ -763,11 +757,8 @@ return setmetatable(anselme, {
-- }, ...
-- }, ...
},
variable_constraints = {
-- foo = { constraint }, ...
},
variable_constants = {
-- foo = true, ...
variable_metadata = {
-- foo = { constant = true, persistent = true, constraint = constraint, ... }, ...
},
variables = {
-- foo = {

View file

@ -214,6 +214,8 @@ Set some code that will be injected at specific places in all code loaded after
* `"function return"`: injected at the end of each return's children that is contained in a non-scoped function
* `"checkpoint start"`: injected at the start of every checkpoint
* `"checkpoint end"`: injected at the end of every checkpoint
* `"class start"`: injected at the start of every class
* `"class end"`: injected at the end of every class
* `"scoped function start"`: injected at the start of every scoped function
* `"scoped function end"`: injected at the end of every scoped function
* `"scoped function return"`: injected at the end of each return's children that is contained in a scoped function
@ -248,9 +250,14 @@ Define functions from Lua.
Save/load script state
Only saves variables full names and values, so make sure to not change important variables, checkpoints and functions names between a save and a load.
Also only save variables with usable identifiers, so will skip functions with arguments, operators, etc. (i.e. every scoped functions).
Loading should be done after loading all the game scripts (otherwise you will "variable already defined" errors).
Only saves persistent variables' full names and values.
Make sure to not change persistent variables names, class name, class attribute names, checkpoint names and functions names between a
save and a load (alias can of course be changed), as Anselme will not be able to match them to the old names stored in the save file.
If a variable is stored in the save file but is not marked as persistent in the current scripts (e.g. if you updated the Anselme scripts to
remove the persistence), it will not be loaded.
Loading should be done after loading all the game scripts (otherwise you will get "variable already defined" errors).
Returns this VM.

View file

@ -66,7 +66,7 @@ common = {
-- returns depth, or math.huge if no constraint
-- returns nil, err
check_constraint = function(state, fqm, val)
local constraint = state.variable_constraints[fqm]
local constraint = state.variable_metadata[fqm].constraint
if constraint then
if not constraint.value then
local v, e = eval(state, constraint.pending)
@ -87,7 +87,7 @@ common = {
-- returns true
-- returns nil, mutation illegal message
check_mutable = function(state, fqm)
if state.variable_constants[fqm] then
if state.variable_metadata[fqm].constant then
return nil, ("can't change the value of a constant %q"):format(fqm)
end
return true
@ -114,12 +114,12 @@ common = {
return nil, ("%s; while evaluating default value for variable %q defined at %s"):format(e, fqm, var.value.source)
end
-- make constant if variable is constant
if state.variable_constants[fqm] then
if state.variable_metadata[fqm].constant then
v = copy(v)
common.mark_constant(v)
end
-- set variable
local s, err = common.set_variable(state, fqm, v, state.variable_constants[fqm])
local s, err = common.set_variable(state, fqm, v, state.variable_metadata[fqm].constant)
if not s then return nil, err end
return v
else
@ -218,9 +218,10 @@ common = {
table.insert(modified, v)
end,
--- returns true if a variable should be persisted on save
-- will exclude: undefined variables, variables in scoped functions, constants, internal anselme variables
-- will exclude: variable that have not been evaluated yet and non-persistent variable
-- this will by consequence excludes variable in scoped variables (can be neither persistent not evaluated into global state), constants (can not be persistent), internal anselme variables (not marked persistent), etc.
should_keep_variable = function(state, name, value)
return value.type ~= "undefined argument" and value.type ~= "pending definition" and name:match("^"..identifier_pattern.."$") and not name:match("^anselme%.") and not state.variable_constants[name]
return value.type ~= "pending definition" and state.variable_metadata[name].persistent
end,
--- check truthyness of an anselme value
truthy = function(val)

View file

@ -309,7 +309,7 @@ local function eval(state, exp)
local depth, err = check_constraint(state, param.full_name, val)
if not depth then
ok = false
local v = state.variable_constraints[param.full_name].value
local v = state.variable_metadata[param.full_name].constraint.value
table.insert(tried_function_error_messages, ("%s: argument %s is not of expected type %s"):format(fn.pretty_signature, param.name, format(v) or v))
break
end
@ -347,7 +347,7 @@ local function eval(state, exp)
local depth, err = check_constraint(state, param.full_name, assignment)
if not depth then
ok = false
local v = state.variable_constraints[param.full_name].value
local v = state.variable_metadata[param.full_name].constraint.value
table.insert(tried_function_error_messages, ("%s: argument %s is not of expected type %s"):format(fn.pretty_signature, param.name, format(v) or v))
end
depths.assignment = depth
@ -418,8 +418,11 @@ local function eval(state, exp)
if not s then return nil, e end
end
-- get function vars
local checkpoint, checkpointe = get_variable(state, fn.namespace.."🔖")
if not checkpoint then return nil, checkpointe end
local checkpoint, checkpointe
if fn.resumable then
checkpoint, checkpointe = get_variable(state, fn.namespace.."🔖")
if not checkpoint then return nil, checkpointe end
end
local seen, seene = get_variable(state, fn.namespace.."👁️")
if not seen then return nil, seene end
-- execute lua functions
@ -488,7 +491,7 @@ local function eval(state, exp)
else
local e
-- eval function from start
if paren_call or checkpoint.type == "nil" then
if paren_call or not fn.resumable or checkpoint.type == "nil" then
ret, e = run(state, fn.child)
-- resume at last checkpoint
else

View file

@ -119,7 +119,7 @@ run_line = function(state, line)
value = reached.value + 1
})
if not s then return nil, e end
s, e = set_variable(state, line.parent_function.namespace.."🔖", {
s, e = set_variable(state, line.parent_resumable.namespace.."🔖", {
type = "function reference",
value = { line.name }
})
@ -160,7 +160,7 @@ run_block = function(state, block, resume_from_there, i, j)
if not reached then return nil, reachede end
local seen, seene = get_variable(state, parent_line.namespace.."👁️")
if not seen then return nil, seene end
local checkpoint, checkpointe = get_variable(state, parent_line.parent_function.namespace.."🔖")
local checkpoint, checkpointe = get_variable(state, parent_line.parent_resumable.namespace.."🔖")
if not checkpoint then return nil, checkpointe end
local s, e = set_variable(state, parent_line.namespace.."👁️", {
type = "number",
@ -175,7 +175,7 @@ run_block = function(state, block, resume_from_there, i, j)
-- don't update checkpoint if an already more precise checkpoint is set
-- (since we will go up the whole checkpoint hierarchy when resuming from a nested checkpoint)
if checkpoint.type == "nil" or not checkpoint.value[1]:match("^"..escape(parent_line.name)) then
s, e = set_variable(state, parent_line.parent_function.namespace.."🔖", {
s, e = set_variable(state, parent_line.parent_resumable.namespace.."🔖", {
type = "function reference",
value = { parent_line.name }
})
@ -184,11 +184,11 @@ run_block = function(state, block, resume_from_there, i, j)
merge_state(state)
end
-- go up hierarchy if asked to resume
-- will stop at function boundary
-- will stop at resumable function boundary
-- if parent is a choice, will ignore choices that belong to the same block (like the whole block was executed naturally from a higher parent)
-- if parent if a condition, will mark it as a success (skipping following else-conditions) (for the same reasons as for choices)
-- if parent pushed a tag, will pop it (tags from parents are added to the stack in run())
if resume_from_there and block.parent_line and not block.parent_line.resume_boundary then
if resume_from_there and block.parent_line and not block.parent_line.resumable then
local parent_line = block.parent_line
if parent_line.type == "choice" then
state.interpreter.skip_choices_until_flush = true
@ -212,9 +212,9 @@ local function run(state, block, resume_from_there, i, j)
local tags_len = tags:len(state)
if resume_from_there then
local tags_to_add = {}
-- go up in hierarchy in ascending order until function boundary
-- go up in hierarchy in ascending order until resumable function boundary
local parent_line = block.parent_line
while parent_line and not parent_line.resume_boundary do
while parent_line and not parent_line.resumable do
if parent_line.type == "tag" then
local v, e = eval(state, parent_line.expression)
if not v then return v, ("%s; at %s"):format(e, parent_line.source) end

View file

@ -18,7 +18,7 @@ local function parse(state)
if rem:match("[^%s]") then
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param.full_name, rem, line.source)
end
state.variable_constraints[param.full_name] = { pending = type_exp }
state.variable_metadata[param.full_name].constraint = { pending = type_exp }
end
-- get default value
if param.default then
@ -30,7 +30,7 @@ local function parse(state)
param.default = default_exp
-- extract type constraint from default value
if default_exp.type == "function call" and default_exp.called_name == "_::_" then
state.variable_constraints[param.full_name] = { pending = default_exp.argument.expression.right }
state.variable_metadata[param.full_name].constraint = { pending = default_exp.argument.expression.right }
end
end
end
@ -41,7 +41,7 @@ local function parse(state)
if rem:match("[^%s]") then
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(line.assignment.full_name, rem, line.source)
end
state.variable_constraints[line.assignment.full_name] = { pending = type_exp }
state.variable_metadata[line.assignment.full_name].constraint = { pending = type_exp }
end
-- get list of scoped variables
-- (note includes every variables in the namespace of subnamespace, so subfunctions are scoped alongside this function)
@ -49,6 +49,7 @@ local function parse(state)
line.scoped = {}
for name in pairs(state.variables) do
if name:sub(1, #namespace) == namespace then
if state.variable_metadata[name].persistent then return nil, ("variable %q can not be persistent as it is in a scoped function"):format(name) end
table.insert(line.scoped, name)
end
end
@ -80,7 +81,7 @@ local function parse(state)
if rem2:match("[^%s]") then
return nil, ("unexpected characters after variable %q: %q; at %s"):format(line.name, rem2, line.source)
end
state.variable_constraints[line.name] = { pending = type_exp }
state.variable_metadata[line.name].constraint = { pending = type_exp }
end
end
end

View file

@ -48,7 +48,7 @@ end
--- parse a single line into AST
-- * ast: if success
-- * nil, error: in case of error
local function parse_line(line, state, namespace, parent_function)
local function parse_line(line, state, namespace, parent_resumable, in_scoped)
local l = line.content
local r = {
source = line.source
@ -93,10 +93,10 @@ local function parse_line(line, state, namespace, parent_function)
local keep_in_ast = false
if lr:match("^%$") then
r.subtype = "function"
r.resume_boundary = true
r.resumable = true
elseif lr:match("^%%") then
r.subtype = "class"
r.resume_boundary = true
r.resumable = true
r.properties = true
allow_params = false
allow_assign = false
@ -105,7 +105,7 @@ local function parse_line(line, state, namespace, parent_function)
allow_params = false
allow_assign = false
keep_in_ast = true
r.parent_function = parent_function -- store parent function and run checkpoint when line is read
r.parent_resumable = parent_resumable -- store parent resumable function and run checkpoint when line is read
else
error("unknown function line type")
end
@ -231,36 +231,23 @@ local function parse_line(line, state, namespace, parent_function)
end
-- define variables
if not line.children then line.children = {} end
local scoped = in_scoped or r.scoped
-- define 👁️ variable
local seen_alias = state.global_state.builtin_aliases["👁️"]
if seen_alias then
table.insert(line.children, 1, { content = (":👁️:%s=0"):format(seen_alias), source = line.source })
else
table.insert(line.children, 1, { content = ":👁️=0", source = line.source })
end
if r.subtype ~= "checkpoint" then
-- define 🔖 variable
table.insert(line.children, 1, { content = (":%s👁%s=0"):format(scoped and "" or "@", seen_alias and ":"..seen_alias or ""), source = line.source })
-- define 🔖 variable
if r.resumable then
local checkpoint_alias = state.global_state.builtin_aliases["🔖"]
if checkpoint_alias then
table.insert(line.children, 1, { content = (":🔖:%s=()"):format(checkpoint_alias), source = line.source })
else
table.insert(line.children, 1, { content = ":🔖=()", source = line.source })
end
-- custom code injection
inject(state, r, "start", line.children, 2)
inject(state, r, "end", line.children)
elseif r.subtype == "checkpoint" then
-- define 🏁 variable
local reached_alias = state.global_state.builtin_aliases["🏁"]
if reached_alias then
table.insert(line.children, 1, { content = (":🏁:%s=0"):format(reached_alias), source = line.source })
else
table.insert(line.children, 1, { content = ":🏁=0", source = line.source })
end
-- custom code injection
inject(state, r, "start", line.children, 2)
inject(state, r, "end", line.children)
table.insert(line.children, 1, { content = (":%s🔖%s=()"):format(scoped and "" or "@", checkpoint_alias and ":"..checkpoint_alias or ""), source = line.source })
end
-- define 🏁 variable
if r.subtype == "checkpoint" then
local reached_alias = state.global_state.builtin_aliases["🏁"]
table.insert(line.children, 1, { content = (":%s🏁%s=0"):format(scoped and "" or "@", reached_alias and ":"..reached_alias or ""), source = line.source })
end
-- custom code injection
inject(state, r, "start", line.children, 2)
inject(state, r, "end", line.children)
-- define args
for _, param in ipairs(r.params) do
if not state.variables[param.full_name] then
@ -268,6 +255,7 @@ local function parse_line(line, state, namespace, parent_function)
type = "undefined argument",
value = nil
}
state.variable_metadata[param.full_name] = {}
else
return nil, ("trying to define parameter %q, but a variable with the same name exists; at %s"):format(param.full_name, line.source)
end
@ -278,6 +266,7 @@ local function parse_line(line, state, namespace, parent_function)
type = "undefined argument",
value = nil
}
state.variable_metadata[r.assignment.full_name] = {}
else
return nil, ("trying to define parameter %q, but a variable with the same name exists; at %s"):format(r.assignment.full_name, line.source)
end
@ -298,6 +287,9 @@ local function parse_line(line, state, namespace, parent_function)
if rem:match("^:") then
rem = rem:match("^:(.*)$")
r.constant = true
elseif rem:match("^@") then
rem = rem:match("^@(.*)$")
r.persistent = true
end
-- get identifier
local identifier
@ -328,7 +320,9 @@ local function parse_line(line, state, namespace, parent_function)
r.name = fqm
r.expression = exp
state.variables[fqm] = { type = "pending definition", value = { expression = nil, source = r.source } }
if r.constant then state.variable_constants[fqm] = true end
state.variable_metadata[fqm] = {}
if r.constant then state.variable_metadata[fqm].constant = true end
if r.persistent then state.variable_metadata[fqm].persistent = true end
end
-- add expression line after to perform the immediate execution
if run_immediately then
@ -344,7 +338,6 @@ local function parse_line(line, state, namespace, parent_function)
elseif l:match("^%@") then
r.type = "return"
r.child = true
r.parent_function = parent_function
local expr = l:match("^%@(.*)$")
if expr:match("[^%s]") then
r.expression = expr
@ -353,7 +346,7 @@ local function parse_line(line, state, namespace, parent_function)
end
-- custom code injection
if not line.children then line.children = {} end
inject(state, parent_function, "return", line.children)
inject(state, parent_resumable, "return", line.children)
-- text
elseif l:match("[^%s]") then
r.type = "text"
@ -369,11 +362,11 @@ end
--- parse an indented into final AST
-- * block: in case of success
-- * nil, err: in case of error
local function parse_block(indented, state, namespace, parent_function)
local function parse_block(indented, state, namespace, parent_resumable, in_scoped)
local block = { type = "block" }
for i, l in ipairs(indented) do
-- parsable line
local ast, err = parse_line(l, state, namespace, parent_function)
local ast, err = parse_line(l, state, namespace, parent_resumable, in_scoped)
if err then return nil, err end
-- add to block AST
if not ast.remove_from_block_ast then
@ -392,7 +385,7 @@ local function parse_block(indented, state, namespace, parent_function)
if not ast.child then
return nil, ("line %s (%s) can't have children"):format(ast.source, ast.type)
else
local r, e = parse_block(l.children, state, ast.namespace or namespace, (ast.type == "function" and ast.subtype ~= "checkpoint") and ast or parent_function)
local r, e = parse_block(l.children, state, ast.namespace or namespace, (ast.type == "function" and ast.resumable) and ast or parent_resumable, (ast.type == "function" and ast.scoped) or in_scoped)
if not r then return r, e end
r.parent_line = ast
ast.child = r
@ -507,8 +500,7 @@ local function parse(state, s, name, source)
local state_proxy = {
inject = {},
aliases = setmetatable({}, { __index = state.aliases }),
variable_constraints = setmetatable({}, { __index = state.variable_constraints }),
variable_constants = setmetatable({}, { __index = state.variable_constants }),
variable_metadata = setmetatable({}, { __index = state.variable_metadata }),
variables = setmetatable({}, { __index = state.aliases }),
functions = setmetatable({}, {
__index = function(self, key)
@ -541,11 +533,8 @@ local function parse(state, s, name, source)
for k,v in pairs(state_proxy.aliases) do
state.aliases[k] = v
end
for k,v in pairs(state_proxy.variable_constraints) do
state.variable_constraints[k] = v
end
for k,v in pairs(state_proxy.variable_constants) do
state.variable_constants[k] = v
for k,v in pairs(state_proxy.variable_metadata) do
state.variable_metadata[k] = v
end
for k,v in pairs(state_proxy.variables) do
state.variables[k] = v

View file

@ -158,12 +158,10 @@ lua_functions = {
if r.constant then
return nil, "can't change the value of an attribute of a constant object"
end
if not check_mutable(state, obj.class.."."..name) then
return nil, "can't change the value of a constant attribute"
end
-- attribute already present in object
local var, vfqm = find(state.aliases, obj.attributes, "", obj.class.."."..name)
if var then
if not check_mutable(state, vfqm) then return nil, "can't change the value of a constant attribute" end
obj.attributes[vfqm] = v
mark_as_modified(anselme.running.state, obj.attributes)
return v
@ -171,6 +169,7 @@ lua_functions = {
-- search for attribute in base class
local cvar, cvfqm = find(state.aliases, state.interpreter.global_state.variables, "", obj.class.."."..name)
if cvar then
if not check_mutable(state, cvfqm) then return nil, "can't change the value of a constant attribute" end
obj.attributes[cvfqm] = v
mark_as_modified(anselme.running.state, obj.attributes)
return v

View file

@ -0,0 +1,44 @@
:$ f()
:a = 1
{a}
~ a := a + 1
:$ g
:a = 1
{a}
~ a := a + 1
:$ h()
:a = 1
{a}
~ a := a + 1
\> depth 2, unscoped:
~ g
~ g
~ g
\> depth 2, scoped:
~ h
~ h
~ h
depth 1:
~ f
~ f
~ f

View file

@ -0,0 +1,230 @@
local _={}
_[113]={}
_[112]={}
_[111]={}
_[110]={}
_[109]={}
_[108]={}
_[107]={}
_[106]={}
_[105]={}
_[104]={}
_[103]={}
_[102]={}
_[101]={}
_[100]={}
_[99]={}
_[98]={}
_[97]={}
_[96]={}
_[95]={}
_[94]={}
_[93]={}
_[92]={}
_[91]={}
_[90]={}
_[89]={}
_[88]={}
_[87]={}
_[86]={}
_[85]={tags=_[113],text="1"}
_[84]={tags=_[112],text="1"}
_[83]={tags=_[111],text="1"}
_[82]={tags=_[110],text="> depth 2, scoped:"}
_[81]={tags=_[109],text="3"}
_[80]={tags=_[108],text="2"}
_[79]={tags=_[107],text="1"}
_[78]={tags=_[106],text="> depth 2, unscoped:"}
_[77]={tags=_[105],text="1"}
_[76]={tags=_[104],text="1"}
_[75]={tags=_[103],text="1"}
_[74]={tags=_[102],text="1"}
_[73]={tags=_[101],text="> depth 2, scoped:"}
_[72]={tags=_[100],text="3"}
_[71]={tags=_[99],text="2"}
_[70]={tags=_[98],text="1"}
_[69]={tags=_[97],text="> depth 2, unscoped:"}
_[68]={tags=_[96],text="1"}
_[67]={tags=_[95],text="1"}
_[66]={tags=_[94],text="1"}
_[65]={tags=_[93],text="1"}
_[64]={tags=_[92],text="> depth 2, scoped:"}
_[63]={tags=_[91],text="3"}
_[62]={tags=_[90],text="2"}
_[61]={tags=_[89],text="1"}
_[60]={tags=_[88],text="> depth 2, unscoped:"}
_[59]={tags=_[87],text="1"}
_[58]={tags=_[86],text="depth 1:"}
_[57]={_[85]}
_[56]={_[84]}
_[55]={_[83]}
_[54]={_[82]}
_[53]={_[81]}
_[52]={_[80]}
_[51]={_[79]}
_[50]={_[78]}
_[49]={_[77]}
_[48]={_[76]}
_[47]={_[75]}
_[46]={_[74]}
_[45]={_[73]}
_[44]={_[72]}
_[43]={_[71]}
_[42]={_[70]}
_[41]={_[69]}
_[40]={_[68]}
_[39]={_[67]}
_[38]={_[66]}
_[37]={_[65]}
_[36]={_[64]}
_[35]={_[63]}
_[34]={_[62]}
_[33]={_[61]}
_[32]={_[60]}
_[31]={_[59]}
_[30]={_[58]}
_[29]={"return"}
_[28]={"text",_[57]}
_[27]={"text",_[56]}
_[26]={"text",_[55]}
_[25]={"text",_[54]}
_[24]={"text",_[53]}
_[23]={"text",_[52]}
_[22]={"text",_[51]}
_[21]={"text",_[50]}
_[20]={"text",_[49]}
_[19]={"text",_[48]}
_[18]={"text",_[47]}
_[17]={"text",_[46]}
_[16]={"text",_[45]}
_[15]={"text",_[44]}
_[14]={"text",_[43]}
_[13]={"text",_[42]}
_[12]={"text",_[41]}
_[11]={"text",_[40]}
_[10]={"text",_[39]}
_[9]={"text",_[38]}
_[8]={"text",_[37]}
_[7]={"text",_[36]}
_[6]={"text",_[35]}
_[5]={"text",_[34]}
_[4]={"text",_[33]}
_[3]={"text",_[32]}
_[2]={"text",_[31]}
_[1]={"text",_[30]}
return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10],_[11],_[12],_[13],_[14],_[15],_[16],_[17],_[18],_[19],_[20],_[21],_[22],_[23],_[24],_[25],_[26],_[27],_[28],_[29]}
--[[
{ "text", { {
tags = {},
text = "depth 1:"
} } }
{ "text", { {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "> depth 2, unscoped:"
} } }
{ "text", { {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "2"
} } }
{ "text", { {
tags = {},
text = "3"
} } }
{ "text", { {
tags = {},
text = "> depth 2, scoped:"
} } }
{ "text", { {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "> depth 2, unscoped:"
} } }
{ "text", { {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "2"
} } }
{ "text", { {
tags = {},
text = "3"
} } }
{ "text", { {
tags = {},
text = "> depth 2, scoped:"
} } }
{ "text", { {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "> depth 2, unscoped:"
} } }
{ "text", { {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "2"
} } }
{ "text", { {
tags = {},
text = "3"
} } }
{ "text", { {
tags = {},
text = "> depth 2, scoped:"
} } }
{ "text", { {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "1"
} } }
{ "return" }
]]--