From acb8945dec653aaadaa9dfc886a50d673ee698e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Reuh=20Fildadut?= Date: Thu, 9 Dec 2021 14:11:18 +0100 Subject: [PATCH] Add variable reference and use them in alias(ref, id) --- REFERENCE.md | 14 +++++-- anselme.lua | 2 + interpreter/expression.lua | 12 ++++-- parser/common.lua | 17 ++++---- parser/expression.lua | 16 +++++++- stdlib/bootscript.lua | 1 + stdlib/functions.lua | 43 ++++++++++++++++----- stdlib/languages/frFR.lua | 42 ++++++++++---------- stdlib/types.lua | 17 +++++++- test/tests/variable reference get value.ans | 5 +++ test/tests/variable reference get value.lua | 14 +++++++ 11 files changed, 138 insertions(+), 45 deletions(-) create mode 100644 test/tests/variable reference get value.ans create mode 100644 test/tests/variable reference get value.lua diff --git a/REFERENCE.md b/REFERENCE.md index ed8964a..8c06832 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -534,6 +534,8 @@ Default types are: * `function reference`: reference to one or more function(s) with a given name. Can be defined using `&function name`, which will create a reference to every function with this name accessible from the current namespace. Can be called as if it was the original function using `func ref!` and `func ref(args)`. +* `variable reference`: reference to a single variable with a given name. Can be defined using `&variable name`, which will create a reference to the closest variable with this name accessible from the current namespace. Can get the referenced variable value using `var ref!`. + * `list`: a list of values. Mutable. Types can be mixed. Can be defined between square brackets and use comma as a separator '[1,2,3,4]'. Every type is immutable, except `list`. @@ -835,7 +837,7 @@ This only works on strings: `a | b`: or operator, lazy -##### Functions +##### Functions and function references `fn(args)`: call the function, checkpoint or function reference with the given arguments. @@ -845,6 +847,12 @@ This only works on strings: `a!fn(args)`: call the function or function reference with the variable as first argument. Parantheses are optional. +##### Variable references + +`&var`: returns a variable reference to the given variable. + +`a!`: returns the value associated with the reference variable. + ##### Various `a ; b`: evaluate a, discard its result, then evaluate b. Returns the result of b. @@ -891,7 +899,7 @@ This only works on strings: ##### Various -`alias(identifier::string, alias::string)`: define an alias `alias` for variable `identifier`. Expect fully qualified names. +`alias(ref::reference, alias::string)`: define an alias `alias` for the variable or function referenced by `ref`. Expect fully qualified names for the alias. `rand([m[, n]])`: when called whitout arguments, returns a random float in [0,1). Otherwise, returns a random number in [m,n]; m=1 if not given. @@ -903,7 +911,7 @@ This only works on strings: #### 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`. +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`. #### Built-in languages diff --git a/anselme.lua b/anselme.lua index 6f29869..e3e046a 100644 --- a/anselme.lua +++ b/anselme.lua @@ -197,6 +197,8 @@ local interpreter_methods = { anselme.running = previous if not success then return nil, event + elseif event == "error" then + return nil, data elseif event ~= "return" then return nil, ("evaluated expression generated an %q event; at %s"):format(event, self.state.interpreter.running_line.source) else diff --git a/interpreter/expression.lua b/interpreter/expression.lua index 73f8bb7..7f4d4c5 100644 --- a/interpreter/expression.lua +++ b/interpreter/expression.lua @@ -148,12 +148,18 @@ local function eval(state, exp) -- variable elseif exp.type == "variable" then return get_variable(state, exp.name) - -- function + -- references elseif exp.type == "function reference" then return { type = "function reference", value = exp.names } + elseif exp.type == "variable reference" then + return { + type = "variable reference", + value = exp.name + } + -- function elseif exp.type == "function call" then -- eval args: list_brackets local args = {} @@ -372,7 +378,7 @@ local function eval(state, exp) if r then ret = r else - return nil, ("%s; in Lua function %q"):format(e, exp.called_name) + return nil, ("%s; in Lua function %q"):format(e or "raw function returned nil and no error message", exp.called_name) end -- untyped raw mode: same as raw, but strips custom types from the arguments elseif lua_fn.mode == "untyped raw" then @@ -386,7 +392,7 @@ local function eval(state, exp) if r then ret = r else - return nil, ("%s; in Lua function %q"):format(e, exp.called_name) + return nil, ("%s; in Lua function %q"):format(e or "untyped raw function returned nil and no error message", exp.called_name) end -- normal mode: convert args to Lua and convert back Lua value to Anselme elseif lua_fn.mode == nil then diff --git a/parser/common.lua b/parser/common.lua index c85b0ff..a1a84a3 100644 --- a/parser/common.lua +++ b/parser/common.lua @@ -6,16 +6,19 @@ local common --- rewrite name to use defined aliases (under namespace only) -- namespace should not contain aliases +-- returns the final fqm 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)) + local prefix = namespace + for i=1, #name_list, 1 do -- search alias for each part of the fqm + local n = ("%s%s%s"):format(prefix, prefix == "" and "" or ".", name_list[i]) if aliases[n] then - name_list[i] = aliases[n]:match("[^%.]+$") + prefix = aliases[n] + else + prefix = n end end - return table.concat(name_list, ".") + return prefix end local disallowed_set = ("~`^+-=<>/[]*{}|\\_!?,;:()\"@&$#%"):gsub("[^%w]", "%%%1") @@ -93,7 +96,7 @@ common = { local ns = common.split(namespace) for i=#ns, 1, -1 do local current_namespace = table.concat(ns, ".", 1, i) - local fqm = ("%s.%s"):format(current_namespace, replace_aliases(aliases, current_namespace, name)) + local fqm = replace_aliases(aliases, current_namespace, name) if list[fqm] then return list[fqm], fqm end @@ -112,7 +115,7 @@ common = { local ns = common.split(namespace) for i=#ns, 1, -1 do local current_namespace = table.concat(ns, ".", 1, i) - local fqm = ("%s.%s"):format(current_namespace, replace_aliases(aliases, current_namespace, name)) + local fqm = replace_aliases(aliases, current_namespace, name) if list[fqm] then table.insert(l, fqm) end diff --git a/parser/expression.lua b/parser/expression.lua index dff09e9..dc58f59 100644 --- a/parser/expression.lua +++ b/parser/expression.lua @@ -190,15 +190,27 @@ local function expression(s, state, namespace, current_priority, operating_on) local escaped = escape(op) if s:match("^"..escaped) then local sright = s:match("^"..escaped.."(.*)$") - -- function reference + -- function and variable reference if op == "&" and sright:match("^"..identifier_pattern) then local name, r = sright:match("^("..identifier_pattern..")(.-)$") name = format_identifier(name) -- get all functions this name can reference - -- try prefixes until we find a valid function name + -- try prefixes until we find a valid function or variable name local nl = split(name) for i=#nl, 1, -1 do local name_prefix = table.concat(nl, ".", 1, i) + -- variable ref + local var, vfqm = find(state.aliases, state.variables, namespace, name_prefix) + if var then + if i < #nl then + r = "."..table.concat(nl, ".", i+1, #nl)..r + end + return expression(r, state, namespace, current_priority, { + type = "variable reference", + name = vfqm + }) + end + -- function ref local lfnqm = find_all(state.aliases, state.functions, namespace, name_prefix) if #lfnqm > 0 then if i < #nl then diff --git a/stdlib/bootscript.lua b/stdlib/bootscript.lua index 21b31a7..97481b2 100644 --- a/stdlib/bootscript.lua +++ b/stdlib/bootscript.lua @@ -7,4 +7,5 @@ return [[ :list="list" :pair="pair" :function reference="function reference" +:variable reference="variable reference" ]] diff --git a/stdlib/functions.lua b/stdlib/functions.lua index a3f6936..a3016c3 100644 --- a/stdlib/functions.lua +++ b/stdlib/functions.lua @@ -145,9 +145,15 @@ functions = { ["()(fn::function reference, l...)"] = { -- bypassed, this case is manually handled in the expression interpreter }, - ["_!(fn::function reference, l...)"] = { + ["_!(fn::function reference)"] = { -- bypassed, this case is manually handled in the expression interpreter }, + ["_!(fn::variable reference)"] = { + mode = "untyped raw", + value = function(v) + return anselme.running.state.variables[v.value] + end + }, -- format ["{}(v)"] = { mode = "raw", @@ -156,21 +162,40 @@ functions = { end }, -- alias - ["alias(identifier::string, alias::string)"] = { - value = function(identifier, alias) + ["alias(ref::function reference, alias::string)"] = { + mode = "untyped raw", + value = function(ref, alias) -- check identifiers - local fqm = identifier:match("^"..identifier_pattern.."$") - if not fqm then error(("%q is not a valid identifier"):format(identifier)) end - fqm = format_identifier(fqm) + alias = alias.value + local aliasfqm = alias:match("^"..identifier_pattern.."$") + if not aliasfqm then error(("%q is not a valid identifier for an alias"):format(alias)) end + aliasfqm = format_identifier(aliasfqm) + -- define alias + for _, fnfqm in ipairs(ref.value) do + local aliases = anselme.running.state.aliases + if aliases[aliasfqm] ~= nil and aliases[aliasfqm] ~= fnfqm then + error(("trying to define alias %q for %q, but already exist and refer to %q"):format(aliasfqm, fnfqm, aliases[alias])) + end + aliases[aliasfqm] = fnfqm + end + return { type = "nil" } + end + }, + ["alias(ref::variable reference, alias::string)"] = { + mode = "untyped raw", + value = function(ref, alias) + -- check identifiers + alias = alias.value local aliasfqm = alias:match("^"..identifier_pattern.."$") if not aliasfqm then error(("%q is not a valid identifier for an alias"):format(alias)) end aliasfqm = format_identifier(aliasfqm) -- define alias local aliases = anselme.running.state.aliases - if aliases[aliasfqm] ~= nil and aliases[aliasfqm] ~= fqm then - error(("trying to define alias %q for %q, but already exist and refer to %q"):format(aliasfqm, fqm, aliases[alias])) + if aliases[aliasfqm] ~= nil and aliases[aliasfqm] ~= ref.value then + error(("trying to define alias %q for %q, but already exist and refer to %q"):format(aliasfqm, ref.value, aliases[alias])) end - aliases[aliasfqm] = fqm + aliases[aliasfqm] = ref.value + return { type = "nil" } end }, -- pair methods diff --git a/stdlib/languages/frFR.lua b/stdlib/languages/frFR.lua index 8b19c86..40ee240 100644 --- a/stdlib/languages/frFR.lua +++ b/stdlib/languages/frFR.lua @@ -1,27 +1,29 @@ return [[ (Types) -~ "nil"!alias("nul") -~ "number"!alias("nombre") -~ "string"!alias("texte") -~ "list"!alias("liste") -~ "pair"!alias("paire") +~ &nil!alias("nul") +~ &number!alias("nombre") +~ &string!alias("texte") +~ &list!alias("liste") +~ &pair!alias("paire") +~ &function reference!alias("réference de fonction") +~ &variable reference!alias("réference de variable") (Built-in functions) -(~ "alias"!alias("alias") -~ "name"!alias("nom") -~ "value"!alias("valeur") -~ "len"!alias("longueur") -~ "insert"!alias("ajouter") -~ "remove"!alias("retirer") -~ "find"!alias("trouver") -~ "error"!alias("erreur") -~ "rand"!alias("aléa") -~ "raw"!alias("brut") -(~ "type"!alias("type") -~ "is of type"!alias("est de type") -~ "cycle"!alias("cycler") -~ "random"!alias("aléatoire") -~ "next"!alias("séquence") +(~ &alias!alias("alias") +~ &name!alias("nom") +~ &value!alias("valeur") +~ &len!alias("longueur") +~ &insert!alias("ajouter") +~ &remove!alias("retirer") +~ &find!alias("trouver") +~ &error!alias("erreur") +~ &rand!alias("aléa") +~ &raw!alias("brut") +(~ &type!alias("type") +~ &is of type!alias("est de type") +~ &cycle!alias("cycler") +~ &random!alias("aléatoire") +~ &next!alias("séquence") (Built-in variables) :alias 👁️ = "vu" diff --git a/stdlib/types.lua b/stdlib/types.lua index 1f9c200..444b26f 100644 --- a/stdlib/types.lua +++ b/stdlib/types.lua @@ -145,7 +145,22 @@ types.anselme = { return k end }, - ["function reference"] = nil, + ["function reference"] = { + format = function(val) + if #val > 1 then + return ("&(%s)"):format(table.concat(val, ", ")) + else + return ("&%s"):format(table.concat(val, ", ")) + end + end, + to_lua = nil + }, + ["variable reference"] = { + format = function(val) + return ("&%s"):format(val) + end, + to_lua = nil + }, -- internal types ["event buffer"] = { format = function(val) -- triggered from subtexts diff --git a/test/tests/variable reference get value.ans b/test/tests/variable reference get value.ans new file mode 100644 index 0000000..5727af7 --- /dev/null +++ b/test/tests/variable reference get value.ans @@ -0,0 +1,5 @@ +:x = 52 + +:y = &x + +{y!} diff --git a/test/tests/variable reference get value.lua b/test/tests/variable reference get value.lua new file mode 100644 index 0000000..172747d --- /dev/null +++ b/test/tests/variable reference get value.lua @@ -0,0 +1,14 @@ +local _={} +_[5]={} +_[4]={tags=_[5],text="52"} +_[3]={_[4]} +_[2]={"return"} +_[1]={"text",_[3]} +return {_[1],_[2]} +--[[ +{ "text", { { + tags = {}, + text = "52" + } } } +{ "return" } +]]-- \ No newline at end of file