mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-27 16:49:31 +00:00
Improve alias system
This commit is contained in:
parent
b0d7a0bfb5
commit
51e8c82181
6 changed files with 165 additions and 40 deletions
38
README.md
38
README.md
|
|
@ -164,7 +164,7 @@ There's different types of lines, depending on their first character(s) (after i
|
|||
> Last choice
|
||||
```
|
||||
|
||||
* `$`: function line. Followed by an [identifier](#identifiers), and eventually a parameter list. Define a function using its children as function body.
|
||||
* `$`: function line. Followed by an [identifier](#identifiers), then eventually an [alias](#aliases), and eventually a parameter list. Define a function using its children as function body.
|
||||
|
||||
The function body is not executed when the line is reached; it must be explicitely called in an expression. See [expressions](#function-calls) to see the different ways of calling a function.
|
||||
|
||||
|
|
@ -217,7 +217,7 @@ Functions always have the following variables defined in its namespace by defaul
|
|||
`👁️`: number, number of times the function was executed before
|
||||
`🏁`: string, name of last reached checkpoint/paragraph
|
||||
|
||||
* `§`: paragraph. Followed by an [identifier](#identifiers). Define a paragraph. A paragraph act as a checkpoint.
|
||||
* `§`: paragraph. Followed by an [identifier](#identifiers), then eventually an [alias](#aliases). Define a paragraph. A paragraph act as a checkpoint.
|
||||
|
||||
The function 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](#paragraph-calls) to see the different ways of calling a paragraph.
|
||||
|
||||
|
|
@ -249,7 +249,7 @@ Paragraphs always have the following variable defined in its namespace by defaul
|
|||
|
||||
#### Lines that can't have children:
|
||||
|
||||
* `:`: variable declaration. Followed by an [expression](#expressions) and an [identifier](#identifiers). Defines a variable with a default value and this identifier in the current [namespace]("identifiers"). Once defined, the type of a variable can not change.
|
||||
* `:`: variable declaration. Followed by an [expression](#expressions) and an [identifier](#identifiers), then eventually an [alias](#aliases). Defines a variable with a default value and this identifier in the current [namespace]("identifiers"). Once defined, the type of a variable can not change.
|
||||
|
||||
```
|
||||
:42 foo
|
||||
|
|
@ -375,6 +375,38 @@ Var1 in the fn1 namespace = 2: {fn1.var1}
|
|||
~ fn1.var1 = 3
|
||||
```
|
||||
|
||||
#### Aliases
|
||||
|
||||
When defining identifiers (in variables, functions or paragraph 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).
|
||||
|
||||
```
|
||||
:42 name: alias
|
||||
|
||||
{name} is the same as {alias}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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)
|
||||
:"John Pizzapone" player name
|
||||
|
||||
Hi {player name}!
|
||||
|
||||
(in a translated, french script)
|
||||
:"John Pizzapone" player name : nom du joueur
|
||||
|
||||
Salut {nom du joueur} !
|
||||
```
|
||||
|
||||
Variables that are defined automatically by Anselme (`👁️` and `🏁` in paragraphs and functions) can be automatically aliased using `vm:setaliases("👁️alias", "🏁alias")`. See [API](#api-reference).
|
||||
|
||||
### Expressions
|
||||
|
||||
Besides lines, plenty of things in Anselme take expressions, which allow various operations on values and variables.
|
||||
|
|
|
|||
29
anselme.lua
29
anselme.lua
|
|
@ -194,19 +194,6 @@ local vm_mt = {
|
|||
return self
|
||||
end,
|
||||
|
||||
--- set aliases
|
||||
-- return self
|
||||
loadalias = function(self, name, dest)
|
||||
if type(name) == "table" then
|
||||
for k, v in pairs(name) do
|
||||
self:loadalias(k, v)
|
||||
end
|
||||
else
|
||||
self.state.aliases[name] = dest
|
||||
end
|
||||
return self
|
||||
end,
|
||||
|
||||
--- define functions
|
||||
-- return self
|
||||
loadfunction = function(self, name, fn)
|
||||
|
|
@ -237,6 +224,14 @@ local vm_mt = {
|
|||
return self
|
||||
end,
|
||||
|
||||
--- set aliases for built-in variables 👁️ and 🏁 that will be defined on every new paragraph and function
|
||||
-- return self
|
||||
setaliases = function(self, seen, checkpoint)
|
||||
self.state.builtin_aliases["👁️"] = seen
|
||||
self.state.builtin_aliases["🏁"] = checkpoint
|
||||
return self
|
||||
end,
|
||||
|
||||
--- save/load
|
||||
load = function(self, data)
|
||||
assert(data.anselme_version == anselme.version, ("trying to load a save from Anselme %s but current Anselme version is %s"):format(data.anselme_version, anselme.version))
|
||||
|
|
@ -268,6 +263,7 @@ local vm_mt = {
|
|||
local interpreter
|
||||
interpreter = {
|
||||
state = {
|
||||
builtin_aliases = self.builtin_aliases,
|
||||
aliases = self.state.aliases,
|
||||
functions = self.state.functions,
|
||||
variables = setmetatable({}, { __index = self.state.variables }),
|
||||
|
|
@ -310,9 +306,12 @@ return setmetatable(anselme, {
|
|||
__call = function()
|
||||
-- global state
|
||||
local state = {
|
||||
builtin_aliases = {
|
||||
-- ["👁️"] = "seen",
|
||||
-- ["🏁"] = "checkpoint"
|
||||
},
|
||||
aliases = {
|
||||
-- seen = "👁️",
|
||||
-- checkpoint = "🏁"
|
||||
-- ["bonjour.salutation"] = "hello.greeting",
|
||||
},
|
||||
functions = {
|
||||
-- [":="] = {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,21 @@ local expression
|
|||
local escapeCache = {}
|
||||
|
||||
local common
|
||||
|
||||
--- rewrite name to use defined aliases (under namespace only)
|
||||
-- namespace should not contain aliases
|
||||
local replace_aliases = function(aliases, namespace, name)
|
||||
namespace = namespace == "" and "" or namespace.."."
|
||||
local name_list = common.split(name)
|
||||
for i=1, #name_list, 1 do
|
||||
local n = ("%s%s"):format(namespace, table.concat(name_list, ".", 1, i))
|
||||
if aliases[n] then
|
||||
name_list[i] = aliases[n]:match("[^%.]+$")
|
||||
end
|
||||
end
|
||||
return table.concat(name_list, ".")
|
||||
end
|
||||
|
||||
common = {
|
||||
--- valid identifier pattern
|
||||
identifier_pattern = "[^%%%/%*%+%-%(%)%!%&%|%=%$%§%?%>%<%:%{%}%[%]%,%\"]+",
|
||||
|
|
@ -26,24 +41,27 @@ common = {
|
|||
return address
|
||||
end,
|
||||
--- find a variable/function in a list, going up through the namespace hierarchy
|
||||
find = function(list, namespace, name)
|
||||
-- will apply aliases
|
||||
find = function(aliases, list, namespace, name)
|
||||
local ns = common.split(namespace)
|
||||
for i=#ns, 1, -1 do
|
||||
local fqm = ("%s.%s"):format(table.concat(ns, ".", 1, i), name)
|
||||
local current_namespace = table.concat(ns, ".", 1, i)
|
||||
local fqm = ("%s.%s"):format(current_namespace, replace_aliases(aliases, current_namespace, name))
|
||||
if list[fqm] then
|
||||
return list[fqm], fqm
|
||||
end
|
||||
end
|
||||
-- root namespace
|
||||
name = replace_aliases(aliases, "", name)
|
||||
if list[name] then
|
||||
return list[name], name
|
||||
end
|
||||
return nil, ("can't find %q in namespace %s"):format(name, namespace)
|
||||
end,
|
||||
--- transform an identifier into a clean version (trim & alias)
|
||||
--- transform an identifier into a clean version (trim each part)
|
||||
format_identifier = function(identifier, state)
|
||||
local r = identifier:gsub("[^%.]+", function(str)
|
||||
str = common.trim(str)
|
||||
return state.aliases[str] or str
|
||||
return common.trim(str)
|
||||
end)
|
||||
return r
|
||||
end,
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
|||
local name, r = s:match("^("..identifier_pattern..")(.-)$")
|
||||
name = format_identifier(name, state)
|
||||
-- variables
|
||||
local var, vfqm = find(state.variables, namespace, name)
|
||||
local var, vfqm = find(state.aliases, state.variables, namespace, name)
|
||||
if var then
|
||||
return expression(r, state, namespace, currentPriority, {
|
||||
type = "variable",
|
||||
|
|
@ -103,7 +103,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
|||
-- suffix call: detect if prefix is valid variable, suffix call is handled in the binop section below
|
||||
local sname, suffix = name:match("^(.*)(%."..identifier_pattern..")$")
|
||||
if sname then
|
||||
local svar, svfqm = find(state.variables, namespace, sname)
|
||||
local svar, svfqm = find(state.aliases, state.variables, namespace, sname)
|
||||
if svar then
|
||||
return expression(suffix..r, state, namespace, currentPriority, {
|
||||
type = "variable",
|
||||
|
|
@ -113,7 +113,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
|||
end
|
||||
end
|
||||
-- functions
|
||||
local funcs, ffqm = find(state.functions, namespace, name)
|
||||
local funcs, ffqm = find(state.aliases, state.functions, namespace, name)
|
||||
if funcs then
|
||||
local args, explicit_call
|
||||
if r:match("^%b()") then
|
||||
|
|
@ -162,7 +162,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
|||
if op == "." and sright:match("^"..identifier_pattern) then
|
||||
local name, r = sright:match("^("..identifier_pattern..")(.-)$")
|
||||
name = format_identifier(name, state)
|
||||
local funcs, ffqm = find(state.functions, namespace, name)
|
||||
local funcs, ffqm = find(state.aliases, state.functions, namespace, name)
|
||||
if funcs then
|
||||
local args, explicit_call
|
||||
if r:match("^%b()") then
|
||||
|
|
|
|||
|
|
@ -29,9 +29,25 @@ local function parse_line(line, state, namespace)
|
|||
l, name = l:match("^(.-)%s*§(.-)$")
|
||||
local identifier, rem = name:match("^("..identifier_pattern..")(.-)$")
|
||||
if not identifier then return nil, ("no valid identifier in paragraph decorator %q; at %s"):format(identifier, line.source) end
|
||||
if rem:match("[^%s]") then return nil, ("expected end-of-line after identifier in paragraph decorator, but got %q; at %s"):format(rem, line.source) end
|
||||
-- format identifier
|
||||
local fqm = ("%s%s"):format(namespace, format_identifier(identifier, state))
|
||||
-- get alias
|
||||
if rem:match("^%:") then
|
||||
local content = rem:sub(2)
|
||||
local alias, rem2 = content:match("^("..identifier_pattern..")(.-)$")
|
||||
if not alias then return nil, ("expected an identifier in alias in paragraph decorator, but got %q; at %s"):format(content, line.source) end
|
||||
if rem2:match("[^%s]") then return nil, ("expected end-of-line after identifier in alias in paragraph decorator, but got %q; at %s"):format(rem2, line.source) end
|
||||
-- format alias
|
||||
local aliasfqm = ("%s%s"):format(namespace, format_identifier(alias, state))
|
||||
-- define alias
|
||||
if state.aliases[aliasfqm] ~= nil and state.aliases[aliasfqm] ~= fqm then
|
||||
return nil, ("trying to define alias %q for variable %q, but already exist and refer to different variable %q; at %s"):format(aliasfqm, fqm, state.aliases[aliasfqm], line.source)
|
||||
end
|
||||
state.aliases[aliasfqm] = fqm
|
||||
elseif rem:match("[^%s]") then
|
||||
return nil, ("expected end-of-line after identifier in paragraph decorator, but got %q; at %s"):format(rem, line.source)
|
||||
end
|
||||
-- define paragraph
|
||||
namespace = fqm.."."
|
||||
r.paragraph = true
|
||||
r.parent_function = true
|
||||
|
|
@ -50,6 +66,15 @@ local function parse_line(line, state, namespace)
|
|||
value = 0
|
||||
}
|
||||
end
|
||||
-- define alias for 👁️
|
||||
local seen_alias = state.builtin_aliases["👁️"]
|
||||
if seen_alias then
|
||||
local alias = ("%s.%s"):format(fqm, seen_alias)
|
||||
if state.aliases[alias] ~= nil and state.aliases[alias] then
|
||||
return nil, ("trying to define alias %q for variable %q, but already exist and refer to different variable %q; at %s"):format(alias, fqm..".👁️", state.aliases[alias], line.source)
|
||||
end
|
||||
state.aliases[alias] = fqm..".👁️"
|
||||
end
|
||||
else
|
||||
table.insert(state.functions[fqm], {
|
||||
arity = 0,
|
||||
|
|
@ -89,6 +114,20 @@ local function parse_line(line, state, namespace)
|
|||
if not identifier then return nil, ("no valid identifier in paragraph/function definition line %q; at %s"):format(lc, line.source) end
|
||||
-- format identifier
|
||||
local fqm = ("%s%s"):format(namespace, format_identifier(identifier, state))
|
||||
-- get alias
|
||||
if rem:match("^%:") then
|
||||
local content = rem:sub(2)
|
||||
local alias
|
||||
alias, rem = content:match("^("..identifier_pattern..")(.-)$")
|
||||
if not alias then return nil, ("expected an identifier in alias in paragraph/function definition line, but got %q; at %s"):format(content, line.source) end
|
||||
-- format alias
|
||||
local aliasfqm = ("%s%s"):format(namespace, format_identifier(alias, state))
|
||||
-- define alias
|
||||
if state.aliases[aliasfqm] ~= nil and state.aliases[aliasfqm] ~= fqm then
|
||||
return nil, ("trying to define alias %q for function/paragraph %q, but already exist and refer to %q; at %s"):format(aliasfqm, fqm, state.aliases[aliasfqm], line.source)
|
||||
end
|
||||
state.aliases[aliasfqm] = fqm
|
||||
end
|
||||
-- get params
|
||||
r.params = {}
|
||||
if r.type == "function" and rem:match("^%b()$") then
|
||||
|
|
@ -113,12 +152,6 @@ local function parse_line(line, state, namespace)
|
|||
-- don't keep function node in block AST
|
||||
if r.type == "function" then
|
||||
r.remove_from_block_ast = true
|
||||
if not state.variables[fqm..".🏁"] then
|
||||
state.variables[fqm..".🏁"] = {
|
||||
type = "string",
|
||||
value = ""
|
||||
}
|
||||
end
|
||||
end
|
||||
-- define function and variables
|
||||
r.namespace = fqm.."."
|
||||
|
|
@ -130,14 +163,44 @@ local function parse_line(line, state, namespace)
|
|||
vararg = vararg,
|
||||
value = r
|
||||
}
|
||||
-- new function (no overloading yet)
|
||||
if not state.functions[fqm] then
|
||||
state.functions[fqm] = { r.variant }
|
||||
-- define 👁️ variable
|
||||
if not state.variables[fqm..".👁️"] then
|
||||
state.variables[fqm..".👁️"] = {
|
||||
type = "number",
|
||||
value = 0
|
||||
}
|
||||
end
|
||||
-- define alias for 👁️
|
||||
local seen_alias = state.builtin_aliases["👁️"]
|
||||
if seen_alias then
|
||||
local alias = ("%s.%s"):format(fqm, seen_alias)
|
||||
if state.aliases[alias] ~= nil and state.aliases[alias] then
|
||||
return nil, ("trying to define alias %q for variable %q, but already exist and refer to different variable %q; at %s"):format(alias, fqm..".👁️", state.aliases[alias], line.source)
|
||||
end
|
||||
state.aliases[alias] = fqm..".👁️"
|
||||
end
|
||||
if r.type == "function" then
|
||||
-- define 🏁 variable
|
||||
if not state.variables[fqm..".🏁"] then
|
||||
state.variables[fqm..".🏁"] = {
|
||||
type = "string",
|
||||
value = ""
|
||||
}
|
||||
end
|
||||
-- define alias for 🏁
|
||||
local checkpoint_alias = state.builtin_aliases["🏁"]
|
||||
if checkpoint_alias then
|
||||
local alias = ("%s.%s"):format(fqm, checkpoint_alias)
|
||||
if state.aliases[alias] ~= nil and state.aliases[alias] then
|
||||
return nil, ("trying to define alias %q for variable %q, but already exist and refer to different variable %q; at %s"):format(alias, fqm..".🏁", state.aliases[alias], line.source)
|
||||
end
|
||||
state.aliases[alias] = fqm..".🏁"
|
||||
end
|
||||
end
|
||||
-- overloading
|
||||
else
|
||||
-- check for arity conflict
|
||||
for _, variant in ipairs(state.functions[fqm]) do
|
||||
|
|
@ -181,9 +244,25 @@ local function parse_line(line, state, namespace)
|
|||
-- get identifier
|
||||
local identifier, rem2 = rem:match("^("..identifier_pattern..")(.-)$")
|
||||
if not identifier then return nil, ("no valid identifier after expression in definition line %q; at %s"):format(rem, line.source) end
|
||||
if rem2:match("[^%s]") then return nil, ("expected end-of-line after identifier in definition line, but got %q; at %s"):format(rem2, line.source) end
|
||||
-- format identifier & define
|
||||
-- format identifier
|
||||
local fqm = ("%s%s"):format(namespace, format_identifier(identifier, state))
|
||||
-- get alias
|
||||
if rem2:match("^%:") then
|
||||
local content = rem2:sub(2)
|
||||
local alias, rem3 = content:match("^("..identifier_pattern..")(.-)$")
|
||||
if not alias then return nil, ("expected an identifier in alias in definition line, but got %q; at %s"):format(content, line.source) end
|
||||
if rem3:match("[^%s]") then return nil, ("expected end-of-line after identifier in alias in definition line, but got %q; at %s"):format(rem3, line.source) end
|
||||
-- format alias
|
||||
local aliasfqm = ("%s%s"):format(namespace, format_identifier(alias, state))
|
||||
-- define alias
|
||||
if state.aliases[aliasfqm] ~= nil and state.aliases[aliasfqm] ~= fqm then
|
||||
return nil, ("trying to define alias %s for variable %s, but already exist and refer to different variable %s; at %s"):format(aliasfqm, fqm, state.aliases[aliasfqm], line.source)
|
||||
end
|
||||
state.aliases[aliasfqm] = fqm
|
||||
elseif rem2:match("[^%s]") then
|
||||
return nil, ("expected end-of-line after identifier in definition line, but got %q; at %s"):format(rem2, line.source)
|
||||
end
|
||||
-- define identifier
|
||||
if state.functions[fqm] then return nil, ("trying to define variable %s, but a function with the same name exists; at %s"):format(fqm, line.source) end
|
||||
if not state.variables[fqm] or state.variables[fqm].type == "undefined argument" then
|
||||
local v, e = eval(state, exp)
|
||||
|
|
|
|||
|
|
@ -96,10 +96,7 @@ else
|
|||
local namespace = filebase:match("([^/]*)$")
|
||||
math.randomseed(0)
|
||||
local vm = anselme()
|
||||
vm:loadalias {
|
||||
seen = "👁️",
|
||||
checkpoint = "🏁"
|
||||
}
|
||||
vm:setaliases("seen", "checkpoint")
|
||||
vm:loadfunction {
|
||||
-- custom event test
|
||||
["wait"] = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue