mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-27 08:39:30 +00:00
Add constants
This commit is contained in:
parent
3e658e4780
commit
95683a0ffd
17 changed files with 290 additions and 8 deletions
10
LANGUAGE.md
10
LANGUAGE.md
|
|
@ -366,6 +366,14 @@ $ f
|
|||
:bar : alias = 12
|
||||
```
|
||||
|
||||
You can also use two colons instead of one to define a constant:
|
||||
|
||||
```
|
||||
::foo = 42
|
||||
```
|
||||
|
||||
After their declaration and first evaluation, constants cannot be reassigned 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.
|
||||
|
||||
* empty line: flush the event buffer, i.e., if there are any pending lines of text or choices, send them to your game. See [Event buffer](#event-buffer). This line always keep the same identation as the last non-empty line, so you don't need to put invisible whitespace on an empty-looking line. Is also automatically added at the end of a file.
|
||||
|
||||
* regular text: write some text into the [event buffer](#event-buffer). Support [text interpolation](#text-interpolation). Support [escape codes](#escape-codes).
|
||||
|
|
@ -1040,6 +1048,8 @@ This only works on strings:
|
|||
|
||||
`is a(v, type or annotation)`: check if v is of a certain type or annotation
|
||||
|
||||
`constant(v)`: create a constant copy of v and returns it. The resulting value is immutable, even if it contains mutable types (will raise an error if you try to change it).
|
||||
|
||||
#### Built-in variables
|
||||
|
||||
Variables for default types (each is associated to a string of the internal variable type name): `nil`, `number`, `string`, `list`, `pair`, `function reference`, `variable reference`.
|
||||
|
|
|
|||
|
|
@ -668,6 +668,7 @@ local vm_mt = {
|
|||
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,
|
||||
variables = setmetatable({}, {
|
||||
__index = function(variables, k)
|
||||
local cache = getmetatable(variables).cache
|
||||
|
|
@ -763,6 +764,9 @@ return setmetatable(anselme, {
|
|||
variable_constraints = {
|
||||
-- foo = { constraint }, ...
|
||||
},
|
||||
variable_constants = {
|
||||
-- foo = true, ...
|
||||
},
|
||||
variables = {
|
||||
-- foo = {
|
||||
-- type = "number",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ local eval, run_block
|
|||
local replace_with_copied_values, fix_not_modified_references
|
||||
local common
|
||||
local identifier_pattern
|
||||
local copy
|
||||
|
||||
--- copy some text & process it to be suited to be sent to Lua in an event
|
||||
local function post_process_text(state, text)
|
||||
|
|
@ -117,6 +118,31 @@ common = {
|
|||
end
|
||||
return math.huge
|
||||
end,
|
||||
--- checks if the variable is mutable
|
||||
-- returns true
|
||||
-- returns nil, mutation illegal message
|
||||
check_mutable = function(state, fqm)
|
||||
if state.variable_constants[fqm] then
|
||||
return nil, ("can't change the value of a constant %q"):format(fqm)
|
||||
end
|
||||
return true
|
||||
end,
|
||||
--- mark a value as constant, recursively affecting all the potentially mutable subvalues
|
||||
mark_constant = function(v)
|
||||
if v.type == "list" then
|
||||
v.constant = true
|
||||
for _, item in ipairs(v.value) do
|
||||
common.mark_constant(item)
|
||||
end
|
||||
elseif v.type == "object" then
|
||||
v.constant = true
|
||||
elseif v.type == "pair" or v.type == "annotated" then
|
||||
common.mark_constant(v.value[1])
|
||||
common.mark_constant(v.value[2])
|
||||
elseif v.type ~= "nil" and v.type ~= "number" and v.type ~= "string" and v.type ~= "function reference" and v.type ~= "variable reference" then
|
||||
error("unknown type")
|
||||
end
|
||||
end,
|
||||
--- returns a variable's value, evaluating a pending expression if neccessary
|
||||
-- if you're sure the variable has already been evaluated, use state.variables[fqm] directly
|
||||
-- return var
|
||||
|
|
@ -128,7 +154,7 @@ common = {
|
|||
if not v then
|
||||
return nil, ("%s; while evaluating default value for variable %q defined at %s"):format(e, fqm, var.value.source)
|
||||
end
|
||||
local s, err = common.set_variable(state, fqm, v)
|
||||
local s, err = common.set_variable(state, fqm, v, state.variable_constants[fqm])
|
||||
if not s then return nil, err end
|
||||
return v
|
||||
else
|
||||
|
|
@ -138,8 +164,19 @@ common = {
|
|||
--- set the value of a variable
|
||||
-- returns true
|
||||
-- returns nil, err
|
||||
set_variable = function(state, name, val)
|
||||
set_variable = function(state, name, val, defining_a_constant)
|
||||
if val.type ~= "pending definition" then
|
||||
-- check constant
|
||||
if defining_a_constant then
|
||||
val = copy(val)
|
||||
common.mark_constant(val)
|
||||
else
|
||||
local s, e = common.check_mutable(state, name)
|
||||
if not s then
|
||||
return nil, ("%s; while assigning value to variable %q"):format(e, name)
|
||||
end
|
||||
end
|
||||
-- check constraint
|
||||
local s, e = common.check_constraint(state, name, val)
|
||||
if not s then
|
||||
return nil, ("%s; while assigning value to variable %q"):format(e, name)
|
||||
|
|
@ -219,9 +256,9 @@ common = {
|
|||
table.insert(modified, v)
|
||||
end,
|
||||
--- returns true if a variable should be persisted on save
|
||||
-- will exclude: undefined variables, variables in scoped functions, internal anselme variables
|
||||
-- will exclude: undefined variables, variables in scoped functions, constants, internal anselme variables
|
||||
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%.")
|
||||
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]
|
||||
end,
|
||||
--- check truthyness of an anselme value
|
||||
truthy = function(val)
|
||||
|
|
@ -560,5 +597,6 @@ run_block = require((...):gsub("common$", "interpreter")).run_block
|
|||
local acommon = require((...):gsub("interpreter%.common$", "common"))
|
||||
replace_with_copied_values, fix_not_modified_references = acommon.replace_with_copied_values, acommon.fix_not_modified_references
|
||||
identifier_pattern = require((...):gsub("interpreter%.common$", "parser.common")).identifier_pattern
|
||||
copy = require((...):gsub("interpreter%.common$", "common")).copy
|
||||
|
||||
return common
|
||||
|
|
|
|||
|
|
@ -291,8 +291,15 @@ local function parse_line(line, state, namespace, parent_function)
|
|||
elseif l:match("^:") then
|
||||
r.type = "definition"
|
||||
r.remove_from_block_ast = true
|
||||
local rem = l:match("^:(.*)$")
|
||||
-- check if constant
|
||||
if rem:match("^:") then
|
||||
rem = rem:match("^:(.*)$")
|
||||
r.constant = true
|
||||
end
|
||||
-- get identifier
|
||||
local identifier, rem = l:match("^:("..identifier_pattern..")(.-)$")
|
||||
local identifier
|
||||
identifier, rem = rem:match("^("..identifier_pattern..")(.-)$")
|
||||
if not identifier then return nil, ("no valid identifier at start of definition line %q; at %s"):format(l, line.source) end
|
||||
-- format identifier
|
||||
local fqm = ("%s%s"):format(namespace, format_identifier(identifier))
|
||||
|
|
@ -319,6 +326,7 @@ local function parse_line(line, state, namespace, parent_function)
|
|||
r.fqm = 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
|
||||
-- tag
|
||||
elseif l:match("^%#") then
|
||||
r.type = "tag"
|
||||
|
|
@ -514,6 +522,7 @@ local function parse(state, s, name, source)
|
|||
inject = {},
|
||||
aliases = setmetatable({}, { __index = state.aliases }),
|
||||
variable_constraints = setmetatable({}, { __index = state.variable_constraints }),
|
||||
variable_constants = setmetatable({}, { __index = state.variable_constants }),
|
||||
variables = setmetatable({}, { __index = state.aliases }),
|
||||
functions = setmetatable({}, {
|
||||
__index = function(self, key)
|
||||
|
|
@ -549,6 +558,9 @@ local function parse(state, s, name, source)
|
|||
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
|
||||
end
|
||||
for k,v in pairs(state_proxy.variables) do
|
||||
state.variables[k] = v
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
local truthy, anselme, compare, is_of_type, identifier_pattern, format_identifier, find, get_variable, mark_as_modified, set_variable
|
||||
local truthy, anselme, compare, is_of_type, identifier_pattern, format_identifier, find, get_variable, mark_as_modified, set_variable, check_mutable, copy, mark_constant
|
||||
|
||||
local lua_functions
|
||||
lua_functions = {
|
||||
|
|
@ -145,6 +145,13 @@ lua_functions = {
|
|||
local state = anselme.running.state
|
||||
local obj = r.value
|
||||
local name = n.value
|
||||
-- check constant state
|
||||
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
|
||||
|
|
@ -186,6 +193,7 @@ lua_functions = {
|
|||
value = function(l, i, v)
|
||||
local lv = l.type == "annotated" and l.value[1] or l
|
||||
local iv = i.type == "annotated" and i.value[1] or i
|
||||
if lv.constant then return nil, "can't change the contents of a constant list" end
|
||||
lv.value[iv.value] = v
|
||||
mark_as_modified(anselme.running.state, lv.value)
|
||||
return v
|
||||
|
|
@ -196,11 +204,12 @@ lua_functions = {
|
|||
value = function(l, k, v)
|
||||
local lv = l.type == "annotated" and l.value[1] or l
|
||||
local kv = k.type == "annotated" and k.value[1] or k
|
||||
if lv.constant then return nil, "can't change the contents of a constant list" end
|
||||
-- update index
|
||||
for _, x in ipairs(lv.value) do
|
||||
if x.type == "pair" and compare(x.value[1], kv) then
|
||||
x.value[2] = v
|
||||
mark_as_modified(anselme.running.state, x.value)
|
||||
mark_as_modified(anselme.running.state, x.value) -- FIXME i thought pairs were immutable...
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
|
@ -296,6 +305,7 @@ lua_functions = {
|
|||
mode = "raw",
|
||||
value = function(l, v)
|
||||
local lv = l.type == "annotated" and l.value[1] or l
|
||||
if lv.constant then return nil, "can't insert values into a constant list" end
|
||||
table.insert(lv.value, v)
|
||||
mark_as_modified(anselme.running.state, lv.value)
|
||||
return l
|
||||
|
|
@ -306,6 +316,7 @@ lua_functions = {
|
|||
value = function(l, i, v)
|
||||
local lv = l.type == "annotated" and l.value[1] or l
|
||||
local iv = i.type == "annotated" and i.value[1] or i
|
||||
if lv.constant then return nil, "can't insert values into a constant list" end
|
||||
table.insert(lv.value, iv.value, v)
|
||||
mark_as_modified(anselme.running.state, lv.value)
|
||||
return l
|
||||
|
|
@ -314,6 +325,7 @@ lua_functions = {
|
|||
["remove(l::list)"] = {
|
||||
mode = "unannotated raw",
|
||||
value = function(l)
|
||||
if l.constant then return nil, "can't remove values from a constant list" end
|
||||
mark_as_modified(anselme.running.state, l.value)
|
||||
return table.remove(l.value)
|
||||
end
|
||||
|
|
@ -321,6 +333,7 @@ lua_functions = {
|
|||
["remove(l::list, i::number)"] = {
|
||||
mode = "unannotated raw",
|
||||
value = function(l, i)
|
||||
if l.constant then return nil, "can't remove values from a constant list" end
|
||||
mark_as_modified(anselme.running.state, l.value)
|
||||
return table.remove(l.value, i.value)
|
||||
end
|
||||
|
|
@ -379,6 +392,14 @@ lua_functions = {
|
|||
value = is_of_type(v, t) or 0
|
||||
}
|
||||
end
|
||||
},
|
||||
["constant(v)"] = {
|
||||
mode = "raw",
|
||||
value = function(v)
|
||||
local c = copy(v)
|
||||
mark_constant(c)
|
||||
return c
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -414,9 +435,10 @@ local functions = {
|
|||
|
||||
package.loaded[...] = functions
|
||||
local icommon = require((...):gsub("stdlib%.functions$", "interpreter.common"))
|
||||
truthy, compare, is_of_type, get_variable, mark_as_modified, set_variable = icommon.truthy, icommon.compare, icommon.is_of_type, icommon.get_variable, icommon.mark_as_modified, icommon.set_variable
|
||||
truthy, compare, is_of_type, get_variable, mark_as_modified, set_variable, check_mutable, mark_constant = icommon.truthy, icommon.compare, icommon.is_of_type, icommon.get_variable, icommon.mark_as_modified, icommon.set_variable, icommon.check_mutable, icommon.mark_constant
|
||||
local pcommon = require((...):gsub("stdlib%.functions$", "parser.common"))
|
||||
identifier_pattern, format_identifier, find = pcommon.identifier_pattern, pcommon.format_identifier, pcommon.find
|
||||
anselme = require((...):gsub("stdlib%.functions$", "anselme"))
|
||||
copy = require((...):gsub("stdlib%.functions$", "common")).copy
|
||||
|
||||
return functions
|
||||
|
|
|
|||
8
test/tests/constant object attribute.ans
Normal file
8
test/tests/constant object attribute.ans
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
% obj
|
||||
::a = 12
|
||||
|
||||
:x = obj()
|
||||
|
||||
{x.a}
|
||||
|
||||
{x.a := 52}
|
||||
14
test/tests/constant object attribute.lua
Normal file
14
test/tests/constant object attribute.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
local _={}
|
||||
_[5]={}
|
||||
_[4]={tags=_[5],text="12"}
|
||||
_[3]={_[4]}
|
||||
_[2]={"error","can't change the value of a constant attribute; in Lua function \"_._\"; at test/tests/constant object attribute.ans:8"}
|
||||
_[1]={"text",_[3]}
|
||||
return {_[1],_[2]}
|
||||
--[[
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "12"
|
||||
} } }
|
||||
{ "error", "can't change the value of a constant attribute; in Lua function \"_._\"; at test/tests/constant object attribute.ans:8" }
|
||||
]]--
|
||||
8
test/tests/constant object.ans
Normal file
8
test/tests/constant object.ans
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
% obj
|
||||
:a = 12
|
||||
|
||||
::x = obj()
|
||||
|
||||
{x.a}
|
||||
|
||||
{x.a := 52}
|
||||
14
test/tests/constant object.lua
Normal file
14
test/tests/constant object.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
local _={}
|
||||
_[5]={}
|
||||
_[4]={tags=_[5],text="12"}
|
||||
_[3]={_[4]}
|
||||
_[2]={"error","can't change the value of an attribute of a constant object; in Lua function \"_._\"; at test/tests/constant object.ans:8"}
|
||||
_[1]={"text",_[3]}
|
||||
return {_[1],_[2]}
|
||||
--[[
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "12"
|
||||
} } }
|
||||
{ "error", "can't change the value of an attribute of a constant object; in Lua function \"_._\"; at test/tests/constant object.ans:8" }
|
||||
]]--
|
||||
15
test/tests/constant values variable.ans
Normal file
15
test/tests/constant values variable.ans
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
:l = [1,2,3]
|
||||
|
||||
::x = l
|
||||
|
||||
{l}
|
||||
{x}
|
||||
|
||||
-----
|
||||
|
||||
~ l!remove()
|
||||
|
||||
{l}
|
||||
{x}
|
||||
|
||||
~ x!remove()
|
||||
40
test/tests/constant values variable.lua
Normal file
40
test/tests/constant values variable.lua
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
local _={}
|
||||
_[17]={}
|
||||
_[16]={}
|
||||
_[15]={}
|
||||
_[14]={}
|
||||
_[13]={}
|
||||
_[12]={text="[1, 2, 3]",tags=_[17]}
|
||||
_[11]={text="[1, 2]",tags=_[16]}
|
||||
_[10]={text="-----",tags=_[15]}
|
||||
_[9]={text="[1, 2, 3]",tags=_[14]}
|
||||
_[8]={text="[1, 2, 3]",tags=_[13]}
|
||||
_[7]={_[11],_[12]}
|
||||
_[6]={_[10]}
|
||||
_[5]={_[8],_[9]}
|
||||
_[4]={"error","can't remove values from a constant list; in Lua function \"remove\"; at test/tests/constant values variable.ans:15"}
|
||||
_[3]={"text",_[7]}
|
||||
_[2]={"text",_[6]}
|
||||
_[1]={"text",_[5]}
|
||||
return {_[1],_[2],_[3],_[4]}
|
||||
--[[
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "[1, 2, 3]"
|
||||
}, {
|
||||
tags = {},
|
||||
text = "[1, 2, 3]"
|
||||
} } }
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "-----"
|
||||
} } }
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "[1, 2]"
|
||||
}, {
|
||||
tags = {},
|
||||
text = "[1, 2, 3]"
|
||||
} } }
|
||||
{ "error", "can't remove values from a constant list; in Lua function \"remove\"; at test/tests/constant values variable.ans:15" }
|
||||
]]--
|
||||
15
test/tests/constant values.ans
Normal file
15
test/tests/constant values.ans
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
:l = [1,2,3]
|
||||
|
||||
:x = constant(l)
|
||||
|
||||
{l}
|
||||
{x}
|
||||
|
||||
-----
|
||||
|
||||
~ l!remove()
|
||||
|
||||
{l}
|
||||
{x}
|
||||
|
||||
~ x!remove()
|
||||
40
test/tests/constant values.lua
Normal file
40
test/tests/constant values.lua
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
local _={}
|
||||
_[17]={}
|
||||
_[16]={}
|
||||
_[15]={}
|
||||
_[14]={}
|
||||
_[13]={}
|
||||
_[12]={text="[1, 2, 3]",tags=_[17]}
|
||||
_[11]={text="[1, 2]",tags=_[16]}
|
||||
_[10]={text="-----",tags=_[15]}
|
||||
_[9]={text="[1, 2, 3]",tags=_[14]}
|
||||
_[8]={text="[1, 2, 3]",tags=_[13]}
|
||||
_[7]={_[11],_[12]}
|
||||
_[6]={_[10]}
|
||||
_[5]={_[8],_[9]}
|
||||
_[4]={"error","can't remove values from a constant list; in Lua function \"remove\"; at test/tests/constant values.ans:15"}
|
||||
_[3]={"text",_[7]}
|
||||
_[2]={"text",_[6]}
|
||||
_[1]={"text",_[5]}
|
||||
return {_[1],_[2],_[3],_[4]}
|
||||
--[[
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "[1, 2, 3]"
|
||||
}, {
|
||||
tags = {},
|
||||
text = "[1, 2, 3]"
|
||||
} } }
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "-----"
|
||||
} } }
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "[1, 2]"
|
||||
}, {
|
||||
tags = {},
|
||||
text = "[1, 2, 3]"
|
||||
} } }
|
||||
{ "error", "can't remove values from a constant list; in Lua function \"remove\"; at test/tests/constant values.ans:15" }
|
||||
]]--
|
||||
7
test/tests/constant variable list.ans
Normal file
7
test/tests/constant variable list.ans
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
::a = [3]
|
||||
|
||||
{a}
|
||||
|
||||
~ a!insert(52)
|
||||
|
||||
{a}
|
||||
14
test/tests/constant variable list.lua
Normal file
14
test/tests/constant variable list.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
local _={}
|
||||
_[5]={}
|
||||
_[4]={tags=_[5],text="[3]"}
|
||||
_[3]={_[4]}
|
||||
_[2]={"error","can't insert values into a constant list; in Lua function \"insert\"; at test/tests/constant variable list.ans:5"}
|
||||
_[1]={"text",_[3]}
|
||||
return {_[1],_[2]}
|
||||
--[[
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "[3]"
|
||||
} } }
|
||||
{ "error", "can't insert values into a constant list; in Lua function \"insert\"; at test/tests/constant variable list.ans:5" }
|
||||
]]--
|
||||
7
test/tests/constant variable.ans
Normal file
7
test/tests/constant variable.ans
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
::a = 3
|
||||
|
||||
{a}
|
||||
|
||||
~ a := 52
|
||||
|
||||
{a}
|
||||
14
test/tests/constant variable.lua
Normal file
14
test/tests/constant variable.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
local _={}
|
||||
_[5]={}
|
||||
_[4]={tags=_[5],text="3"}
|
||||
_[3]={_[4]}
|
||||
_[2]={"error","can't change the value of a constant \"constant variable.a\"; while assigning value to variable \"constant variable.a\"; at test/tests/constant variable.ans:5"}
|
||||
_[1]={"text",_[3]}
|
||||
return {_[1],_[2]}
|
||||
--[[
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "3"
|
||||
} } }
|
||||
{ "error", "can't change the value of a constant \"constant variable.a\"; while assigning value to variable \"constant variable.a\"; at test/tests/constant variable.ans:5" }
|
||||
]]--
|
||||
Loading…
Add table
Add a link
Reference in a new issue