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:
parent
e9606cdee0
commit
2c6d66c222
11 changed files with 384 additions and 106 deletions
27
LANGUAGE.md
27
LANGUAGE.md
|
|
@ -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} !
|
||||
```
|
||||
|
|
|
|||
47
anselme.lua
47
anselme.lua
|
|
@ -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 = {
|
||||
|
|
|
|||
13
anselme.md
13
anselme.md
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
44
test/tests/function scoped nested.ans
Normal file
44
test/tests/function scoped nested.ans
Normal 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
|
||||
230
test/tests/function scoped nested.lua
Normal file
230
test/tests/function scoped nested.lua
Normal 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" }
|
||||
]]--
|
||||
Loading…
Add table
Add a link
Reference in a new issue