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

Add constants

This commit is contained in:
Étienne Fildadut 2022-09-08 22:09:11 +09:00
parent 3e658e4780
commit 95683a0ffd
17 changed files with 290 additions and 8 deletions

View file

@ -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`.

View file

@ -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",

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,8 @@
% obj
::a = 12
:x = obj()
{x.a}
{x.a := 52}

View 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" }
]]--

View file

@ -0,0 +1,8 @@
% obj
:a = 12
::x = obj()
{x.a}
{x.a := 52}

View 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" }
]]--

View file

@ -0,0 +1,15 @@
:l = [1,2,3]
::x = l
{l}
{x}
-----
~ l!remove()
{l}
{x}
~ x!remove()

View 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" }
]]--

View file

@ -0,0 +1,15 @@
:l = [1,2,3]
:x = constant(l)
{l}
{x}
-----
~ l!remove()
{l}
{x}
~ x!remove()

View 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" }
]]--

View file

@ -0,0 +1,7 @@
::a = [3]
{a}
~ a!insert(52)
{a}

View 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" }
]]--

View file

@ -0,0 +1,7 @@
::a = 3
{a}
~ a := 52
{a}

View 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" }
]]--