diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..76fcd26 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.sublime-workspace +*.sublime-project \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d17eaf6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,5 @@ +Copyright 2019-2021 Γtienne "Reuh" Fildadut + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/README.md b/README.md index 35dc94f..05e5077 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Anselme The overengineered dialog scripting system in pure Lua. -**Has been rewritten recently, doc and language are still WIP** +**Documentation and language are still WIP and will change.** Purpose ------- @@ -94,7 +94,7 @@ Right after reaching a checkpoint line, Anselme will merge the local state with ``` $ main - :5 var + :var = 5 ~ var := 2 @@ -164,11 +164,11 @@ There's different types of lines, depending on their first character(s) (after i > Last choice ``` -* `$`: 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. Also define a new namespace for its children. +* `$`: 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. Also define a new namespace for its children (using the function name if it has no arguments, or a unique name otherwise). 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. -A parameter list can be optionally given after the identifier. Parameter names are identifiers, with eventually an alias (after a `:`) and a default value (after a `=`). It is enclosed with paranthesis and contain a comma-separated list of identifiers: +A parameter list can be optionally given after the identifier. Parameter names are identifiers, with eventually an alias (after a `:`) and a default value (after a `=`), and then a type annotation (after a `::`). It is enclosed with paranthesis and contain a comma-separated list of identifiers: ``` $ f(a, b: alias for b, c="default for c", d: alias for d = "default for d") @@ -176,6 +176,9 @@ $ f(a, b: alias for b, c="default for c", d: alias for d = "default for d") second argument: {b} third argument: {c} fourth argument: {d} + +$ f(a::string, b: alias for b::string, c::alias="default for c"::string) + same ``` Functions can also have a variable number of arguments. By adding `...` after the last argument identifier, it will be considered a variable length argument ("vararg"), and will contain a list of every extraneous argument. @@ -184,7 +187,6 @@ Functions can also have a variable number of arguments. By adding `...` after th $ f(a, b...) {b} - (will print [1]) ~ f("discarded", 1) @@ -195,7 +197,7 @@ $ f(a, b...) ~ f("discarded") ``` -Functions with the same name can be defined, as long as they have a different number of argument. Functions will be selected based on the number of arguments given: +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 annotation: ``` $ f(a, b) @@ -204,11 +206,40 @@ $ f(a, b) $ f(x) b +$ f(x::string) + c + (will print a) ~ f(1,2) (will print b) ~ f(1) + +(will print c) +~ f("hello") +``` + +Every operator, except assignement operators, `|`, `&` and `,` can also be use as a function name in order to overload the operator: + +``` +$ /(a::string, b::string) + @"{a}/{b}" +``` + +After the parameter list, you may also write `:=` followed by an identifier, and eventually an alias. This defines an assignement function, which will be called when assigning a value to the function: + +``` +:x = "value" +$ f() + @x +$ f() := v + @x := v + +value = {f} + +~ f() := "other" + +other = {f} ``` Functions can return a value using a [return line](#lines-that-can-t-have-children). @@ -251,10 +282,11 @@ Checkpoints always have the following variable defined in its namespace by defau #### Lines that can't have children: -* `:`: 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. +* `:`: 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. ``` -:42 foo +:foo = 42 +:bar : alias = 12 ``` * `@`: return line. Can be followed by an [expression](#expressions); otherwise nil expression is assumed. Exit the current function and returns the expression's value. @@ -366,11 +398,13 @@ $ loop 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. ``` -:5 a +:a = 5 Value of a: {a} ``` +The expression is automatically wrapped in a call to `format(expr)`. You can overload `format` to change its behaviour for custom types. + ### Event buffer Since Lua mainly run into a signle thread, Anselme need to give back control to the game at some point. This is done with flush event lines (empty lines), where the intrepreter yield its coroutine and returns a buch of data to your game (called the event buffer). It's called an event buffer because, well, it's a buffer, and events are what we call whatever Anselme sends back to your game. @@ -416,9 +450,9 @@ Every event have a type (`text`, `choice`, `return` or `error` by default, custo ### Identifiers -Valid identifiers must be at least 1 caracters long and can contain anything except the caracters `%/*+-()!&|=$Β§?><:{}[],\`. They can contain spaces. +Valid identifiers must be at least 1 caracters long and can contain anything except the caracters ``~`^+-=<>/[]*{}|\_!?,;:()"@&$#%`` (that is, every special caracter on a US keyboard except '). They can contain spaces. They can not start with a number. -When defining an identifier (using a function, checkpoint or variable delcaration line), it will be defined into the current namespace (defined by the parent function/checkpoint). When evaluating an expression, Anselme will look for variables into the current line's namespace, then go up a level if it isn't found, and so on. +When defining an identifier (using a function, checkpoint or variable delcaration line), it will be defined into the current namespace (defined by the parent function/checkpoint). When evaluating an expression, Anselme will look for variables into the current line's namespace, then go up a level if it isn't found, and so on. Note that the namespace of functions with arguments are not accessible from outside the function. In practise, this means you have to use the "genealogy" of the variable to refer to it from a line not in the same namespace: @@ -427,17 +461,17 @@ $ fn1 (everything here is in the fn1 namespace) $ fn2 (fn1.fn2 namespace) - :var2 42 + :var2 = 42 Var2 = 42: {var2} Var2 = not found: {var2} Var2 = 42: {fn2.var2} - :var1 1 + :var1 = 1 Var2 = 42: {fn1.fn2.var2} -:var1 2 +:var1 = 2 Var1 in the current namespace = 1: {var1} Var1 in the fn1 namespace = 2: {fn1.var1} @@ -451,7 +485,7 @@ Var1 in the fn1 namespace = 2: {fn1.var1} 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). ``` -:42 name: alias +:name: alias = 42 {name} is the same as {alias} ``` @@ -466,12 +500,12 @@ Anselme's solution is to keep the original name in the translated script file, b ``` (in the original, english script) -:"John Pizzapone" player name +:player name = "John Pizzapone" Hi {player name}! (in a translated, french script) -:"John Pizzapone" player name : nom du joueur +:player name : nom du joueur = "John Pizzapone" Salut {nom du joueur} ! ``` @@ -490,14 +524,16 @@ Default types are: * `nil`: nil. Can be defined using empty parantheses `()`. -* `number`: a number. Can be defined similarly to Lua number literals. +* `number`: a number (double). Can be defined using the forms `42`, `.42`, `42.42`. -* `string`: a string. Can be defined between double quotes `"string"`. Support [text interpolation](#text-interpolation). +* `string`: a string. Can be defined between double quotes `"string"`. Support [text interpolation](#text-interpolation). Support the escape codes `\\` for `\`, `\"` for `"`, `\n` for a newline and `\t` for a tabulation. * `list`: a list of values. Types can be mixed. Can be defined between square brackets and use comma as a separator '[1,2,3,4]'. * `pair`: a couple of values. Types can be mixed. Can be defined using colon `"key":5`. Pairs named by a string that is also a valid identifier can be created using the `key=5` shorthand syntax. +* `type`: a couple of values. Types can be mixed. Can be defined using colon `expr::type`. The second value is used in type checks, this is intended to be use to give a custom type to a value. + How conversions are handled from Anselme to Lua: * `nil` -> `nil` @@ -628,6 +664,36 @@ $ f(a, b...) [2,3,4,5] ``` +Anselme use dynamic dispatch, meaning the correct function is selected at runtime. The correct function is selected based on number of arguments, argument names, and argument type annotations. The function with the most specific arguments will be selected. If several functions match, an error is thrown. + +``` +$ fn(x::number, y) + a + +$ fn(x::number) + b + +$ fn(a::string) + c + +$ fn(x::number, y::number) + c + +a = {fn(5, "s")} + +b = {fn("s")} + +c = {fn(5)} + +d = {fn(5, 2)} + +$ g(x) + +$ g(x, a="t") + +error, can't select unique function: {g(5)} +``` + #### Checkpoint calls Most of the time, you should'nt need to call checkpoints yourself - they will be automatically be set as the active checkpoint when the interperter reach their line, and they will be automatically called when resuming its parent function. @@ -677,6 +743,24 @@ Please also be aware that when resuming from a checkpoint, Anselme will try to r * if the checkpoint is in a choice block, it will assume this choice was selected (but will not re-evaluate any of the choices from the same choice group) * will try to re-add every tag from parent lines; this require Anselme to re-evaluate every tag line and decorator that's a parent of the checkpoint in the function. Be careful if your tag expressions have side-effects. +##### Operator priority + +From lowest to highest priority: + +``` +; +:= += -= //= /= *= %= ^= +, +| & +!= == >= <= < > ++ - +* // / % +:: : +unary -, unary ! +^ +. +``` + #### Operators Built-in operators: @@ -731,7 +815,9 @@ This only works on strings: `a : b`: evaluate a and b, returns a new pair with a as key and b as value. -`a(b)`: evaluate b (number), returns the value with this index in a (list). Use 1-based indexing. If b is a string, will search the first pair in the list with this string as its name. +`a :: b`: evaluate a and b, returns a new typed value with a as value and b as type. + +`a(b)`: evaluate b (number), returns the value with this index in a (list). Use 1-based indexing. If b is a string, will search the first pair in the list with this string as its name. Operator is named `()`. #### Built-in functions @@ -761,8 +847,20 @@ This only works on strings: ##### Various +`format(v)`: function called when formatting a value in a text interpolation + `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. +`error(str)`: throw an error with the specified message + +`raw(v)`: return v, stripped of its custom types + +`type(v)`: return v's type + +#### Built-in variables + +TODO see stdlib/bootscript.lua + API reference ------------- diff --git a/anselme.lua b/anselme.lua index 3cb3609..f3a2eb1 100644 --- a/anselme.lua +++ b/anselme.lua @@ -4,12 +4,14 @@ local anselme = { -- major.minor.fix -- saves files are incompatible between major versions -- scripts files may break between minor versions - version = "0.14.0", + version = "0.15.0", --- currently running interpreter running = nil } package.loaded[...] = anselme +i = require("inspect") -- luacheck: ignore + -- load libs local preparse = require((...):gsub("anselme$", "parser.preparser")) local postparse = require((...):gsub("anselme$", "parser.postparser")) @@ -17,8 +19,10 @@ local expression = require((...):gsub("anselme$", "parser.expression")) local eval = require((...):gsub("anselme$", "interpreter.expression")) local run_line = require((...):gsub("anselme$", "interpreter.interpreter")).run_line local to_lua = require((...):gsub("anselme$", "interpreter.common")).to_lua +local identifier_pattern = require((...):gsub("anselme$", "parser.common")).identifier_pattern local merge_state = require((...):gsub("anselme$", "interpreter.common")).merge_state local stdfuncs = require((...):gsub("anselme$", "stdlib.functions")) +local bootscript = require((...):gsub("anselme$", "stdlib.bootscript")) -- wrappers for love.filesystem / luafilesystem local function list_directory(path) @@ -151,7 +155,7 @@ local interpreter_methods = { local co = coroutine.create(function() local r, e = eval(self.state, expr) if not r then return "error", e end - return "return", to_lua(r) + return "return", r end) local previous = anselme.running anselme.running = self @@ -162,7 +166,7 @@ local interpreter_methods = { elseif event ~= "return" then return nil, ("evaluated expression generated an %q event"):format(event) else - return data + return to_lua(data) end end, } @@ -170,6 +174,9 @@ interpreter_methods.__index = interpreter_methods --- vm methods local vm_mt = { + -- anselme state + state = nil, + --- wrapper for loading a whole set of scripts -- should be preferred to other loading functions if possible -- will load in path, in order: @@ -270,39 +277,30 @@ local vm_mt = { end, --- define functions from Lua - -- name: full name of the function + -- signature: full signature of the function -- fn: function (Lua function or table, see examples in stdlib/functions.lua) -- return self - loadfunction = function(self, name, fn) - if type(name) == "table" then - for k, v in pairs(name) do - if type(v) == "table" then - for _, variant in ipairs(v) do - self:loadfunction(k, variant) - end - else - self:loadfunction(k, v) - end + loadfunction = function(self, signature, fn) + if type(signature) == "table" then + for k, v in pairs(signature) do + local s, e = self:loadfunction(k, v) + if not s then return nil, e end end else - if not self.state.functions[name] then - self.state.functions[name] = {} - end - if type(fn) == "function" then - local info = debug.getinfo(fn) - table.insert(self.state.functions[name], { - arity = info.isvararg and {info.nparams, math.huge} or info.nparams, - value = fn - }) - else - table.insert(self.state.functions[name], fn) - end + if type(fn) == "function" then fn = { value = fn } end + self.state.link_next_function_definition_to_lua_function = fn + local s, e = self:loadstring("$"..signature, "", "lua") + if not s then return nil, e end + assert(self.state.link_next_function_definition_to_lua_function == nil, "unexpected error while defining lua function") + return self end return self end, --- 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 + -- only save variables with usable identifiers, so will skip functions with arguments, operators, etc. + -- loading should be after loading scripts (otherwise you will "variable already defined" errors) load = function(self, data) local saveMajor, currentMajor = data.anselme_version:match("^[^%.]*"), anselme.version:match("^[^%.]*") assert(saveMajor == currentMajor, ("trying to load data from an incompatible version of Anselme; save was done using %s but current version is %s"):format(data.anselme_version, anselme.version)) @@ -314,7 +312,7 @@ local vm_mt = { save = function(self) local vars = {} for k, v in pairs(self.state.variables) do - if v.type ~= "undefined argument" then + if v.type ~= "undefined argument" and v.type ~= "pending definition" and k:match("^"..identifier_pattern.."$") then vars[k] = v end end @@ -343,7 +341,7 @@ local vm_mt = { local interpreter interpreter = { state = { - builtin_aliases = self.builtin_aliases, + builtin_aliases = self.state.builtin_aliases, aliases = self.state.aliases, functions = self.state.functions, variables = setmetatable({}, { __index = self.state.variables }), @@ -365,7 +363,7 @@ local vm_mt = { interrupt = nil, -- tag stack tags = tags or {}, - } + }, }, vm = self } @@ -397,29 +395,29 @@ return setmetatable(anselme, { -- ["π"] = "reached" }, aliases = { - -- ["bonjour.salutation"] = "hello.greeting", + -- ["bonjour.salutation"] = "hello.greeting", ... }, functions = { - -- [":="] = { + -- ["script.fn"] = { -- { - -- arity = {3,42}, type = { [1] = "variable" }, check = function, rewrite = function, mode = "custom", - -- value = function(state, exp) - -- end -- or checkpoint, function, line - -- } - -- }, + -- function or checkpoint table + -- }, ... + -- }, ... }, variables = { -- foo = { -- type = "number", -- value = 42 - -- }, + -- }, ... }, queued_lines = { - -- { line = line, namespace = "foo" }, - } + -- { line = line, namespace = "foo" }, ... + }, + link_next_function_definition_to_lua_function = nil -- temporarly set to tell the preparser to link a anselme function definition with a lua function } local vm = setmetatable({ state = state }, vm_mt) - vm:loadfunction(stdfuncs) + assert(vm:loadstring(bootscript, "", "boot script")) + assert(vm:loadfunction(stdfuncs)) return vm end }) diff --git a/ideas.txt b/ideas.txt deleted file mode 100644 index 352d355..0000000 --- a/ideas.txt +++ /dev/null @@ -1,14 +0,0 @@ -TODO: improve type checking. -Right now, there is some basic type checking done at parsing - but since Lua and Anselme functions may not always define the type of -their parameters and return value, a lot of checks are skipped ("undefined argument" type). -Probably won't be able to remove them completely (since lists can have mixed types, etc.), but would be good to limit them. -Ideally, we'd avoid runtime type checking. - -TODO: test reacheability of script paths - -Symbols still available: - ` - ' - _ - Β€ Β£ β¬ - ? diff --git a/interpreter/common.lua b/interpreter/common.lua index 510acda..d83ea01 100644 --- a/interpreter/common.lua +++ b/interpreter/common.lua @@ -10,6 +10,23 @@ common = { global_vars[var] = value 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 + -- return nil, err + get_variable = function(state, fqm) + local var = state.variables[fqm] + if var.type == "pending definition" then + local v, e = eval(state, var.value.expression) + if not v then + return nil, ("%s; while evaluating default value for variable %q defined at %s"):format(e, fqm, var.value.source) + end + state.variables[fqm] = v + return v + else + return var + end + end, -- check truthyness of an anselme value truthy = function(val) if val.type == "number" then @@ -20,6 +37,29 @@ common = { return true end end, + -- compare two anselme value for equality + compare = function(a, b) + if a.type ~= b.type then + return false + end + if a.type == "pair" or a.type == "type" then + return common.compare(a.value[1], b.value[1]) and common.compare(a.value[2], b.value[2]) + elseif a.type == "list" then + if #a.value ~= #b.value then + return false + end + for i, v in ipairs(a.value) do + if not common.compare(v, b.value[i]) then + return false + end + end + return true + else + return a.value == b.value + end + end, + -- format a anselme value to something printable + -- does not call custom format() functions, only built-in ones, so it should not be able to fail -- str: if success -- * nil, err: if error format = function(val) @@ -63,6 +103,37 @@ common = { end end return s + end, + -- specificity(number): if var is of type type + -- false: if not + is_of_type = function(var, type) + local depth = 1 + -- var has a custom type + if var.type == "type" then + local var_type = var.value[2] + while true do + if common.compare(var_type, type) then -- same type + return depth + elseif var_type.type == "type" then -- compare parent type + depth = depth + 1 + var_type = var_type.value[2] + else -- no parent, fall back on base type + depth = depth + 1 + var = var.value[1] + break + end + end + end + -- var has a base type + return type.type == "string" and type.value == var.type and depth + end, + -- return a pretty printable type value for var + pretty_type = function(var) + if var.type == "type" then + return common.format(var.value[2]) + else + return var.type + end end } diff --git a/interpreter/expression.lua b/interpreter/expression.lua index 1a860af..0a45190 100644 --- a/interpreter/expression.lua +++ b/interpreter/expression.lua @@ -1,5 +1,5 @@ local expression -local to_lua, from_lua, eval_text +local to_lua, from_lua, eval_text, is_of_type, truthy, format, pretty_type, get_variable local run @@ -52,9 +52,6 @@ local function eval(state, exp) value = {} } end - -- variable - elseif exp.type == "variable" then - return state.variables[exp.name] -- list elseif exp.type == "list" then local l = {} @@ -72,107 +69,319 @@ local function eval(state, exp) type = "list", value = l } + -- assignment + elseif exp.type == ":=" then + if exp.left.type == "variable" then + local name = exp.left.name + local val, vale = eval(state, exp.right) + if not val then return val, vale end + state.variables[name] = val + return val + else + return nil, ("don't know how to perform assignment on %s expression"):format(exp.left.type) + end + -- lazy boolean operators + elseif exp.type == "&" then + local left, lefte = eval(state, exp.left) + if not left then return left, lefte end + if truthy(left) then + local right, righte = eval(state, exp.right) + if not right then return right, righte end + if truthy(right) then + return { + type = "number", + value = 1 + } + end + end + return { + type = "number", + value = 0 + } + elseif exp.type == "|" then + local left, lefte = eval(state, exp.left) + if not left then return left, lefte end + if truthy(left) then + return { + type = "number", + value = 1 + } + end + local right, righte = eval(state, exp.right) + if not right then return right, righte end + return { + type = "number", + value = truthy(right) and 1 or 0 + } + -- variable + elseif exp.type == "variable" then + return get_variable(state, exp.name) -- function elseif exp.type == "function" then - local fn = exp.variant - -- custom lua functions - if fn.mode == "custom" then - return fn.value(state, exp) - else - -- eval args: list_brackets - local args = {} - if exp.argument then - local arg, arge = eval(state, exp.argument) - if not arg then return arg, arge end - args = arg.value + -- eval args: list_brackets + local args = {} + if exp.argument then + local arg, arge = eval(state, exp.argument) + if not arg then return arg, arge end + args = arg.value + end + -- map named arguments + local named_args = {} + for i, arg in ipairs(args) do + if arg.type == "pair" and arg.value[1].type == "string" then + named_args[arg.value[1].value] = { i, arg.value[2] } end - -- anselme function - if type(fn.value) == "table" then - -- checkpoint - if fn.value.type == "checkpoint" then - local r, e = run(state, fn.value.child, not exp.explicit_call) - if not r then return r, e end - return r - -- function - elseif fn.value.type == "function" then - -- map named arguments - for _, arg in ipairs(args) do - if arg.type == "pair" and arg.value[1].type == "string" then - args[arg.value[1].value] = arg.value[2] - end - end + end + -- eval assignment arg + local assignment + if exp.assignment then + local arge + assignment, arge = eval(state, exp.assignment) + if not assignment then return assignment, arge end + end + -- try to select a function + local tried_function_error_messages = {} + local selected_variant = { depths = { assignment = nil }, variant = nil } + for _, fn in ipairs(exp.variants) do + -- checkpoint: no args, nothing to select on + if fn.type == "checkpoint" then + if not selected_variant.variant then + selected_variant.depths = {} + selected_variant.variant = fn + else + return nil, ("checkpoint call %q is ambigous; may be at least either:\n\t%s\n\t%s"):format(exp.called_name, fn.pretty_signature, selected_variant.variant.pretty_signature) + end + -- function + elseif fn.type == "function" then + if not fn.assignment or exp.assignment then + local ok = true -- get and set args - for j, param in ipairs(fn.value.params) do + local used_args = {} + local depths = { assignment = nil } + for j, param in ipairs(fn.params) do local val -- named - if param.alias and args[param.alias] then - val = args[param.alias] - elseif args[param.name] then - val = args[param.name] + if param.alias and named_args[param.alias] then + val = named_args[param.alias][2] + used_args[named_args[param.alias][1]] = true + elseif named_args[param.name] then + val = named_args[param.name][2] + used_args[named_args[param.name][1]] = true -- vararg elseif param.vararg then val = { type = "list", value = {} } for k=j, #args do table.insert(val.value, args[k]) + used_args[k] = true end -- positional elseif args[j] and args[j].type ~= "pair" then val = args[j] - -- default - elseif param.default then - local v, e = eval(state, param.default) - if not v then return v, e end - val = v + used_args[j] = true end if val then + -- check type annotation + if param.type_annotation then + local v, e = eval(state, param.type_annotation) + if not v then return v, e end + local depth = is_of_type(val, v) + if not depth then + ok = false + 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 + depths[j] = depth + else + depths[j] = math.huge + end + -- set state.variables[param.full_name] = val + -- default: evaluate once function is selected + -- there's no need to type check because the type annotation is already the default value's type, because of syntax + elseif param.default then + state.variables[param.full_name] = { type = "pending definition", value = { expression = param.default, source = fn.source } } else - return nil, ("missing mandatory argument %q in function %q call"):format(param.name, fn.value.name) + ok = false + table.insert(tried_function_error_messages, ("%s: missing mandatory argument %q in function %q call"):format(fn.pretty_signature, param.name, fn.name)) + break end end - -- eval function - local r, e - if exp.explicit_call or state.variables[fn.value.namespace.."π"].value == "" then - r, e = run(state, fn.value.child) - -- resume at last checkpoint - else - local expr, err = expression(state.variables[fn.value.namespace.."π"].value, state, "") - if not expr then return expr, err end - r, e = eval(state, expr) + -- check for unused arguments + if ok then + for i, arg in ipairs(args) do + if not used_args[i] then + ok = false + if arg.type == "pair" and arg.value[1].type == "string" then + table.insert(tried_function_error_messages, ("%s: unexpected %s argument"):format(fn.pretty_signature, arg.value[1].value)) + else + table.insert(tried_function_error_messages, ("%s: unexpected argument in position %s"):format(fn.pretty_signature, i)) + end + break + end + end + end + -- assignment arg + if ok and exp.assignment then + -- check type annotation + local param = fn.assignment + if param.type_annotation then + local v, e = eval(state, param.type_annotation) + if not v then return v, e end + local depth = is_of_type(assignment, v) + if not depth then + ok = false + 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)) + else + depths.assignment = depth + end + else + depths.assignment = math.huge + end + -- set + state.variables[param.full_name] = assignment + end + if ok then + if not selected_variant.variant then + selected_variant.depths = depths + selected_variant.variant = fn + else + -- check specificity order + local lower + for j, d in ipairs(depths) do + local current_depth = selected_variant.depths[j] or math.huge -- not every arg may be set on every variant (varargs) + if d < current_depth then -- stricly lower, i.e. more specific function + lower = true + break + elseif d > current_depth then -- stricly greater, i.e. less specific function + lower = false + break + end + end + if lower == nil and exp.assignment then -- use assignment if still ambigous + local current_depth = selected_variant.depths.assignment + if depths.assignment < current_depth then -- stricly lower, i.e. more specific function + lower = true + elseif depths.assignment > current_depth then -- stricly greater, i.e. less specific function + lower = false + end + end + if lower then + selected_variant.depths = depths + selected_variant.variant = fn + elseif lower == nil then -- equal, ambigous dispatch + return nil, ("function call %q is ambigous; may be at least either:\n\t%s\n\t%s"):format(exp.called_name, fn.pretty_signature, selected_variant.variant.pretty_signature) + end + end end - if not r then return r, e end - state.variables[fn.value.namespace.."ποΈ"] = { - type = "number", - value = state.variables[fn.value.namespace.."ποΈ"].value + 1 - } - return r - else - return nil, ("unknown function type %q"):format(fn.value.type) end - -- lua functions - -- TODO: handle named and default arguments else - if fn.mode == "raw" then - return fn.value(unpack(args)) - else - local l_lua = {} - for _, v in ipairs(args) do - table.insert(l_lua, to_lua(v)) - end - local r, e - if _VERSION == "Lua 5.1" and not jit then -- PUC Lua 5.1 doesn't allow yield from a pcall - r, e = true, fn.value(unpack(l_lua)) - else - r, e = pcall(fn.value, unpack(l_lua)) -- pcall to produce a more informative error message (instead of full coroutine crash) - end - if r then - return from_lua(e) - else - return nil, ("%s; in Lua function %q"):format(e, exp.name) - end - end + return nil, ("unknown function type %q"):format(fn.type) end end + -- function successfully selected + if selected_variant.variant then + local fn = selected_variant.variant + if fn.type == "checkpoint" then + local r, e = run(state, fn.child, not exp.explicit_call) + if not r then return r, e end + return r + elseif fn.type == "function" then + local ret + -- get function vars + local checkpoint, checkpointe = get_variable(state, fn.namespace.."π") + if not checkpoint then return nil, checkpointe end + local seen, seene = get_variable(state, fn.namespace.."ποΈ") + if not seen then return nil, seene end + -- execute lua functions + -- I guess we could technically skip getting & updating the seen and checkpoints vars since they can't be used from Anselme + -- but it's also kinda fun to known how many time a function was ran + if fn.lua_function then + local lua_fn = fn.lua_function + -- get args + local final_args = {} + for j, param in ipairs(fn.params) do + local v, e = get_variable(state, param.full_name) + if not v then return v, e end + final_args[j] = v + end + if fn.assignment then + local v, e = get_variable(state, fn.assignment.full_name) + if not v then return v, e end + final_args[#final_args+1] = v + end + -- execute function + -- raw mode: pass raw anselme values to the Lua function + if lua_fn.mode == "raw" then + ret = lua_fn.value(unpack(final_args)) + -- untyped raw mode: same as raw, but strips custom types from the arguments + elseif lua_fn.mode == "untyped raw" then + -- extract value from custom types + for i, arg in ipairs(final_args) do + if arg.type == "type" then + final_args[i] = arg.value[1] + end + end + ret = lua_fn.value(unpack(final_args)) + -- normal mode: convert args to Lua and convert back Lua value to Anselme + elseif lua_fn.mode == nil then + local l_lua = {} + for _, v in ipairs(final_args) do + table.insert(l_lua, to_lua(v)) + end + local r, e + if _VERSION == "Lua 5.1" and not jit then -- PUC Lua 5.1 doesn't allow yield from a pcall + r, e = true, lua_fn.value(unpack(l_lua)) + else + r, e = pcall(lua_fn.value, unpack(l_lua)) -- pcall to produce a more informative error message (instead of full coroutine crash) + end + if r then + ret = from_lua(e) + else + return nil, ("%s; in Lua function %q"):format(e, exp.called_name) + end + else + return nil, ("unknown Lua function mode %q"):format(lua_fn.mode) + end + -- execute anselme functions + else + local e + -- eval function from start + if exp.explicit_call or checkpoint.value == "" then + ret, e = run(state, fn.child) + -- resume at last checkpoint + else + local expr, err = expression(checkpoint.value, state, "") + if not expr then return expr, err end + ret, e = eval(state, expr) + end + if not ret then return ret, e end + end + -- update function vars + state.variables[fn.namespace.."ποΈ"] = { + type = "number", + value = seen.value + 1 + } + -- return value + if not ret then return nil, ("function %q didn't return a value"):format(exp.called_name) end + return ret + end + end + -- no matching function found + local args_txt = {} + for _, arg in ipairs(args) do + local s = "" + if arg.type == "pair" and arg.value[1].type == "string" then + s = s .. ("%s="):format(arg.value[1].value) + arg = arg.value[2] + end + s = s .. pretty_type(arg) + table.insert(args_txt, s) + end + local called_name = ("%s(%s)"):format(exp.called_name, table.concat(args_txt, ", ")) + if assignment then + called_name = called_name .. " := " .. pretty_type(assignment) + end + return nil, ("no compatible function found for call to %s; potential candidates were:\n\t%s"):format(called_name, table.concat(tried_function_error_messages, "\n\t")) else return nil, ("unknown expression %q"):format(tostring(exp.type)) end @@ -182,6 +391,6 @@ package.loaded[...] = eval run = require((...):gsub("expression$", "interpreter")).run expression = require((...):gsub("interpreter%.expression$", "parser.expression")) local common = require((...):gsub("expression$", "common")) -to_lua, from_lua, eval_text = common.to_lua, common.from_lua, common.eval_text +to_lua, from_lua, eval_text, is_of_type, truthy, format, pretty_type, get_variable = common.to_lua, common.from_lua, common.eval_text, common.is_of_type, common.truthy, common.format, common.pretty_type, common.get_variable return eval diff --git a/interpreter/interpreter.lua b/interpreter/interpreter.lua index 1db25b0..e185832 100644 --- a/interpreter/interpreter.lua +++ b/interpreter/interpreter.lua @@ -1,5 +1,5 @@ local eval -local truthy, merge_state, to_lua, eval_text, escape +local truthy, merge_state, to_lua, eval_text, escape, get_variable local tags = { --- push new tags on top of the stack, from Anselme values @@ -135,9 +135,11 @@ local function run_line(state, line) end end elseif line.type == "checkpoint" then + local reached, reachede = get_variable(state, line.namespace.."π") + if not reached then return nil, reachede end state.variables[line.namespace.."π"] = { type = "number", - value = state.variables[line.namespace.."π"].value + 1 + value = reached.value + 1 } state.variables[line.parent_function.namespace.."π"] = { type = "string", @@ -179,17 +181,23 @@ run_block = function(state, block, resume_from_there, i, j) -- (and we want this to be done after executing the checkpoint block anyway) if block.parent_line and block.parent_line.type == "checkpoint" then local parent_line = block.parent_line + local reached, reachede = get_variable(state, parent_line.namespace.."π") + 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.."π") + if not checkpoint then return nil, checkpointe end state.variables[parent_line.namespace.."ποΈ"] = { type = "number", - value = state.variables[parent_line.namespace.."ποΈ"].value + 1 + value = seen.value + 1 } state.variables[parent_line.namespace.."π"] = { type = "number", - value = state.variables[parent_line.namespace.."π"].value + 1 + value = reached.value + 1 } -- 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) - local current_checkpoint = state.variables[parent_line.parent_function.namespace.."π"].value + local current_checkpoint = checkpoint.value if not current_checkpoint:match("^"..escape(parent_line.name)) then state.variables[parent_line.parent_function.namespace.."π"] = { type = "string", @@ -272,7 +280,7 @@ local interpreter = { package.loaded[...] = interpreter eval = require((...):gsub("interpreter$", "expression")) local common = require((...):gsub("interpreter$", "common")) -truthy, merge_state, to_lua, eval_text = common.truthy, common.merge_state, common.to_lua, common.eval_text +truthy, merge_state, to_lua, eval_text, get_variable = common.truthy, common.merge_state, common.to_lua, common.eval_text, common.get_variable escape = require((...):gsub("interpreter%.interpreter$", "parser.common")).escape return interpreter diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..943e4e4 --- /dev/null +++ b/notes.txt @@ -0,0 +1,61 @@ +# Symbol selection + +Anselme favor symbols over keywords, as it make translation easier. + +We prefer to use symbols available on a standard US keyboard as it often is the lowest common denominator. + +As we want to be able to write identifiers with little restriction, we try to only use symbols which are unlikely to appear naturally in a name. + +Considering Anselme is aimed to people with a light programming introduction, are safe to use for syntax purposes: + +* Diacritics (should be safe when used on their own): ~`^ +* Usual mathematical symbols (should be safe to use): +-=<>/ +* Unusual punctuation / main use is already programming (should be safe to use): []*{}|\_ +* Usual punctuation used to separate parts of a sentence (should be safe to use): !?.,;:() +* Signs (could be used in a name, but also common programming symbols): @&$#% +* Usual punctuation (could be used in a name): '" + +In the end, we decided to reserve all of those except '. + +Using other unicode symbols may be also alright, but there also should be a way to only use these symbols. + +TODO: add alias to Β§ + +Reserved symbols that are still not used in expressions: ~`\_?@$# + +Reserved symbols that are still not used as a line type: `^+-=[]*{}|\_!?.,;()"&% + +# Code Q&A + +* What does "fqm" means? + It means "fully qualified matriname", which is the same as a fully qualified name, but considers the hierarchy to be mostly mother-daugher based. + It has nothing to do with the fact I'm inept at writing acronyms and realized I wrote it wrong after using it for a whole year. +* What's a "variant"? + One of the different forms of a same function with a given fqm. No idea why I chose "variant". +* Why emojis? + They're kinda language independent I guess. I have no idea. +* Why? + I still have no idea. + +# Other + +TODO: test reacheability of script paths + +TODO: redisign the checkpoint system to work better when used with parallel scripts (as they will rewrite each other's variables) + +TODO: redisign a static type checking system +If we want to go full gradual typing, it would help to: +* add type anotation+type check to variables (:a::number=5) and functions return ($ f()::number) +* enforce some restrictions on type (assume they're constant?) +* make some tuple/list distinction (homogenous/heterogenous types) as right now index operations are a type roulette. Or type annotate lists using some parametric type. +Advantages: +* can be used for better static variant selection; if everything is type annotated, selection could be restricted to a single function +Disadvantages: +* idk if it's worth the trouble + +TODO: allow to define aliases after the initial definition +~ alias(number, "nombre") + +TODO: ensure that most stuff in the state stays consistent after an error was thrown + +TODO: way to migrate save files diff --git a/parser/common.lua b/parser/common.lua index 6828cd4..6d7488e 100644 --- a/parser/common.lua +++ b/parser/common.lua @@ -18,9 +18,33 @@ local replace_aliases = function(aliases, namespace, name) return table.concat(name_list, ".") end +local disallowed_set = ("~`^+-=<>/[]*{}|\\_!?,;:()\"@&$#%"):gsub("[^%w]", "%%%1") + common = { --- valid identifier pattern - identifier_pattern = "[^%%%/%*%+%-%(%)%!%&%|%=%$%Β§%?%>%<%:%{%}%[%]%,%\"]+", + identifier_pattern = "%s*[^0-9%s"..disallowed_set.."][^"..disallowed_set.."]*", + -- names allowed for a function that aren't valide identifiers, mainly for overloading operators + special_functions_names = { + -- operators not included here: + -- * assignment operators (:=, +=, -=, //=, /=, *=, %=, ^=): handled with its own syntax (function assignment) + -- * list operator (,): is used when calling every functions, sounds like more trouble than it's worth + -- * | and & oprators: are lazy and don't behave like regular functions + -- * . operator: don't behave like regular functions either + ";", + "!=", "==", ">=", "<=", "<", ">", + "+", "-", + "*", "//", "/", "%", + "!", + "^", "::", ":", "()" + }, + -- escapement code and their value in strings + -- I don't think there's a point in supporting form feed, carriage return, and other printer and terminal related codes + string_escapes = { + ["\\\\"] = "\\", + ["\\\""] = "\"", + ["\\n"] = "\n", + ["\\t"] = "\t" + }, --- escape a string to be used as an exact match pattern escape = function(str) if not escapeCache[str] then @@ -42,6 +66,8 @@ common = { end, --- find a variable/function in a list, going up through the namespace hierarchy -- will apply aliases + -- returns value, fqm in case of success + -- returns nil, err in case of error find = function(aliases, list, namespace, name) local ns = common.split(namespace) for i=#ns, 1, -1 do @@ -58,6 +84,25 @@ common = { end return nil, ("can't find %q in namespace %s"):format(name, namespace) end, + --- same as find, but return a list of every encoutered possibility + -- returns a list of fqm + find_all = function(aliases, list, namespace, name) + local l = {} + 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)) + if list[fqm] then + table.insert(l, fqm) + end + end + -- root namespace + name = replace_aliases(aliases, "", name) + if list[name] then + table.insert(l, name) + end + return l + end, --- transform an identifier into a clean version (trim each part) format_identifier = function(identifier) local r = identifier:gsub("[^%.]+", function(str) @@ -76,6 +121,7 @@ common = { end return t end, + -- parse interpolated expressions in a text -- * list of strings and expressions -- * nil, err: in case of error parse_text = function(text, state, namespace) @@ -89,7 +135,11 @@ common = { local exp, rem = expression(e:gsub("^{", ""), state, namespace) if not exp then return nil, rem end if not rem:match("^%s*}") then return nil, ("expected closing } at end of expression before %q"):format(rem) end - table.insert(l, exp) + -- wrap in format() call + local variant, err = common.find_function_variant(state, namespace, "format", exp, true) + if not variant then return variant, err end + -- add to text + table.insert(l, variant) text = rem:match("^%s*}(.*)$") else break @@ -97,82 +147,106 @@ common = { end return l end, - -- find compatible function variant - -- * variant: if success + -- find compatible function variants from a fully qualified name + -- this functions does not guarantee that functions are fully compatible with the given arguments and only performs a pre-selection without the ones which definitely aren't + -- * list of variants: if success -- * nil, err: if error - find_function_variant = function(fqm, state, arg, explicit_call) - local err = ("function %q variant not found"):format(fqm) + find_function_variant_from_fqm = function(fqm, state, arg) + local err = ("compatible function %q variant not found"):format(fqm) local func = state.functions[fqm] or {} local args = arg and common.flatten_list(arg) or {} + local variants = {} for _, variant in ipairs(func) do local ok = true - local return_type = variant.return_type -- arity check - -- note: because named args can't be predicted in advance (pairs need to be evaluated), this arity check isn't enough to guarantee a compatible function + -- note: because named args can't be predicted in advance (pairs need to be evaluated), this arity check isn't enough to guarantee a compatible arity -- (e.g., if there's 3 required args but only provide 3 optional arg in a call, will pass) - if variant.arity then - local min, max - if type(variant.arity) == "table" then - min, max = variant.arity[1], variant.arity[2] + local min, max = variant.arity[1], variant.arity[2] + if #args < min or #args > max then + if min == max then + err = ("function %q expected %s arguments but received %s"):format(fqm, min, #args) else - min, max = variant.arity, variant.arity - end - if #args < min or #args > max then - if min == max then - err = ("function %q expected %s arguments but received %s"):format(fqm, min, #args) - else - err = ("function %q expected between %s and %s arguments but received %s"):format(fqm, min, max, #args) - end - ok = false - end - end - -- custom check - if ok and variant.check then - local s, e = variant.check(state, args) - if not s then - err = e or ("function %q variant failed to check arguments"):format(fqm) - ok = false - end - return_type = s == true and return_type or s - end - -- type check - if ok and variant.types then - for j, t in pairs(variant.types) do - if args[j] and args[j].return_type and args[j].return_type ~= t then - err = ("function %q expected a %s as argument %s but received a %s"):format(fqm, t, j, args[j].return_type) - ok = false - end + err = ("function %q expected between %s and %s arguments but received %s"):format(fqm, min, max, #args) end + ok = false end -- done if ok then - if variant.rewrite then - local r, e = variant.rewrite(fqm, state, arg, explicit_call) - if not r then - err = e - ok = false - end - if ok then - return r - end - else - return { - type = "function", - return_type = return_type, - name = fqm, - explicit_call = explicit_call, - variant = variant, - argument = { -- wrap everything in a list literal to simply later things (otherwise may be nil, single value, list constructor) - type = "list_brackets", - return_type = "list", - expression = arg - } - } + table.insert(variants, variant) + end + end + if #variants > 0 then + return variants + else + return nil, err + end + end, + --- same as find_function_variant_from_fqm, but will search every function from the current namespace and up using find + -- returns directly a function expression in case of success + -- return nil, err otherwise + find_function_variant = function(state, namespace, name, arg, explicit_call) + local variants = {} + local err = ("compatible function %q variant not found"):format(name) + local l = common.find_all(state.aliases, state.functions, namespace, name) + for _, ffqm in ipairs(l) do + local found + found, err = common.find_function_variant_from_fqm(ffqm, state, arg) + if found then + for _, v in ipairs(found) do + table.insert(variants, v) end end end - return nil, err - end + if #variants > 0 then + return { + type = "function", + called_name = name, + explicit_call = explicit_call, + variants = variants, + argument = { -- wrap everything in a list literal to simplify later things (otherwise may be nil, single value, list constructor) + type = "list_brackets", + expression = arg + } + } + else + return nil, err -- returns last error + end + end, + -- returns the function's signature text + signature = function(fn) + if fn.signature then return fn.signature end + local signature + local function make_param_signature(p) + local sig = p.name + if p.vararg then + sig = sig .. "..." + end + if p.alias then + sig = sig .. ":" .. p.alias + end + if p.type_annotation then + sig = sig .. "::" .. p.type_annotation + end + if p.default then + sig = sig .. "=" .. p.default + end + return sig + end + local arg_sig = {} + for j, p in ipairs(fn.params) do + arg_sig[j] = make_param_signature(p) + end + if fn.assignment then + signature = ("%s(%s) := %s"):format(fn.name, table.concat(arg_sig, ", "), make_param_signature(fn.assignment)) + else + signature = ("%s(%s)"):format(fn.name, table.concat(arg_sig, ", ")) + end + return signature + end, + -- same as signature, format the signature for displaying to the user and add some debug information + pretty_signature = function(fn) + return ("%s (at %s)"):format(common.signature(fn), fn.source) + end, } package.loaded[...] = common diff --git a/parser/expression.lua b/parser/expression.lua index 49939b8..6040083 100644 --- a/parser/expression.lua +++ b/parser/expression.lua @@ -1,4 +1,4 @@ -local identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text +local identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text, string_escapes --- binop priority local binops_prio = { @@ -9,9 +9,10 @@ local binops_prio = { [5] = { "!=", "==", ">=", "<=", "<", ">" }, [6] = { "+", "-" }, [7] = { "*", "//", "/", "%" }, - [8] = {}, -- unary operators - [9] = { "^", ":" }, - [10] = { "." } + [8] = { "::", ":" }, + [9] = {}, -- unary operators + [10] = { "^" }, + [11] = { "." } } -- unop priority local unops_prio = { @@ -22,8 +23,10 @@ local unops_prio = { [5] = {}, [6] = {}, [7] = {}, - [8] = { "-", "!" }, - [9] = {} + [8] = {}, + [9] = { "-", "!" }, + [10] = {}, + [11] = {}, } --- parse an expression @@ -34,29 +37,47 @@ local function expression(s, state, namespace, currentPriority, operatingOn) currentPriority = currentPriority or 0 if not operatingOn then -- number - if s:match("^%d+%.%d*") or s:match("^%d*%.%d+") or s:match("^%d+") then - local d, r = s:match("^(%d*%.%d*)(.*)$") + if s:match("^%d*%.%d+") or s:match("^%d+") then + local d, r = s:match("^(%d*%.%d+)(.*)$") if not d then d, r = s:match("^(%d+)(.*)$") end return expression(r, state, namespace, currentPriority, { type = "number", - return_type = "number", value = tonumber(d) }) -- string - elseif s:match("^%\"[^\"]*%\"") then - local d, r = s:match("^%\"([^\"]*)%\"(.*)$") - while d:match("\\$") and not d:match("\\\\$") do - local nd, nr = r:match("([^\"]*)%\"(.*)$") - if not nd then return nil, ("unfinished string near %q"):format(r) end - d, r = d:sub(1, -2) .. "\"" .. nd, nr + elseif s:match("^%\"") then + local d, r + -- find end of string + local i = 2 + while true do + local skip + skip = s:match("^[^%\\\"]-%b{}()", i) -- skip interpolated expressions + if skip then i = skip end + skip = s:match("^[^%\\\"]-\\.()", i) -- skip escape codes (need to skip every escape code in order to correctly parse \\": the " is not escaped) + if skip then i = skip end + if not skip then -- nothing skipped + local end_pos = s:match("^[^%\"]-\"()", i) -- search final double quote + if end_pos then + d, r = s:sub(2, end_pos-2), s:sub(end_pos) + break + else + return nil, ("expected \" to finish string near %q"):format(s:sub(i)) + end + end end - local l, e = parse_text(tostring(d), state, namespace) + -- parse interpolated expressions + local l, e = parse_text(d, state, namespace) if not l then return l, e end + -- escape the string parts + for j, ls in ipairs(l) do + if type(ls) == "string" then + l[j] = ls:gsub("\\.", string_escapes) + end + end return expression(r, state, namespace, currentPriority, { type = "string", - return_type = "string", value = l }) -- paranthesis @@ -70,11 +91,10 @@ local function expression(s, state, namespace, currentPriority, operatingOn) if not exp then return nil, "invalid expression inside parentheses: "..r_paren end if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of parenthesis expression"):format(r_paren) end else - exp = { type = "nil", return_type = "nil", value = nil } + exp = { type = "nil", value = nil } end return expression(r, state, namespace, currentPriority, { type = "parentheses", - return_type = exp.return_type, expression = exp }) -- list parenthesis @@ -90,7 +110,6 @@ local function expression(s, state, namespace, currentPriority, operatingOn) end return expression(r, state, namespace, currentPriority, { type = "list_brackets", - return_type = "list", expression = exp }) -- identifier @@ -104,16 +123,14 @@ local function expression(s, state, namespace, currentPriority, operatingOn) if not val then return val, r end local args = { type = "list", - return_type = "list", left = { type = "string", - return_type = "string", value = { name } }, right = val } -- find compatible variant - local variant, err = find_function_variant(":", state, args, true) + local variant, err = find_function_variant(state, namespace, ":", args, true) if not variant then return variant, err end return expression(r, state, namespace, currentPriority, variant) end @@ -122,7 +139,6 @@ local function expression(s, state, namespace, currentPriority, operatingOn) if var then return expression(r, state, namespace, currentPriority, { type = "variable", - return_type = var.type ~= "undefined argument" and var.type or nil, name = vfqm }) end @@ -133,34 +149,29 @@ local function expression(s, state, namespace, currentPriority, operatingOn) if svar then return expression(suffix..r, state, namespace, currentPriority, { type = "variable", - return_type = svar.type ~= "undefined argument" and svar.type or nil, name = svfqm }) end end - -- functions - local funcs, ffqm = find(state.aliases, state.functions, namespace, name) - if funcs then - local args, explicit_call - if r:match("^%b()") then - explicit_call = true - local content, rem = r:match("^(%b())(.*)$") - content = content:gsub("^%(", ""):gsub("%)$", "") - r = rem - -- get arguments - if content:match("[^%s]") then - local err - args, err = expression(content, state, namespace) - if not args then return args, err end - if err:match("[^%s]") then return nil, ("unexpected %q at end of argument list"):format(err) end - end + -- function call + local args, explicit_call + if r:match("^%b()") then + explicit_call = true + local content, rem = r:match("^(%b())(.*)$") + content = content:gsub("^%(", ""):gsub("%)$", "") + r = rem + -- get arguments + if content:match("[^%s]") then + local err + args, err = expression(content, state, namespace) + if not args then return args, err end + if err:match("[^%s]") then return nil, ("unexpected %q at end of argument list"):format(err) end end - -- find compatible variant - local variant, err = find_function_variant(ffqm, state, args, explicit_call) - if not variant then return variant, err end - return expression(r, state, namespace, currentPriority, variant) end - return nil, ("unknown identifier %q"):format(name) + -- find compatible variant + local variant, err = find_function_variant(state, namespace, name, args, explicit_call) + if not variant then return variant, err end + return expression(r, state, namespace, currentPriority, variant) end -- unops for prio, oplist in ipairs(unops_prio) do @@ -170,7 +181,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn) local right, r = expression(s:match("^"..escaped.."(.*)$"), state, namespace, prio) if not right then return nil, ("invalid expression after unop %q: %s"):format(op, r) end -- find variant - local variant, err = find_function_variant(op, state, right, true) + local variant, err = find_function_variant(state, namespace, op, right, true) if not variant then return variant, err end return expression(r, state, namespace, currentPriority, variant) end @@ -189,38 +200,35 @@ 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) - local funcs, ffqm = find(state.aliases, state.functions, namespace, name) - if funcs then - local args, explicit_call - if r:match("^%b()") then - explicit_call = true - local content, rem = r:match("^(%b())(.*)$") - content = content:gsub("^%(", ""):gsub("%)$", "") - r = rem - -- get arguments - if content:match("[^%s]") then - local err - args, err = expression(content, state, namespace) - if not args then return args, err end - if err:match("[^%s]") then return nil, ("unexpected %q at end of argument list"):format(err) end - end + local args, explicit_call + if r:match("^%b()") then + explicit_call = true + local content, rem = r:match("^(%b())(.*)$") + content = content:gsub("^%(", ""):gsub("%)$", "") + r = rem + -- get arguments + if content:match("[^%s]") then + local err + args, err = expression(content, state, namespace) + if not args then return args, err end + if err:match("[^%s]") then return nil, ("unexpected %q at end of argument list"):format(err) end end - -- add first argument - if not args then - args = operatingOn - else - args = { - type = "list", - return_type = "list", - left = operatingOn, - right = args - } - end - -- find compatible variant - local variant, err = find_function_variant(ffqm, state, args, explicit_call) - if not variant then return variant, err end - return expression(r, state, namespace, currentPriority, variant) end + -- add first argument + if not args then + args = operatingOn + else + args = { + type = "list", + left = operatingOn, + right = args + } + end + -- find compatible variant + local variant, err = find_function_variant(state, namespace, name, args, explicit_call) + if not variant then return variant, err end + return expression(r, state, namespace, currentPriority, variant) + -- other binops else local right, r = expression(sright, state, namespace, prio) if not right then return nil, ("invalid expression after binop %q: %s"):format(op, r) end @@ -228,7 +236,48 @@ local function expression(s, state, namespace, currentPriority, operatingOn) if op == "," then return expression(r, state, namespace, currentPriority, { type = "list", - return_type = "list", + left = operatingOn, + right = right + }) + -- special binops + elseif op == ":=" or op == "+=" or op == "-=" or op == "//=" or op == "/=" or op == "*=" or op == "%=" or op == "^=" then + -- rewrite assignment + arithmetic operators into a normal assignment + if op ~= ":=" then + local args = { + type = "list", + left = operatingOn, + right = right + } + local variant, err = find_function_variant(state, namespace, op:match("^(.*)%=$"), args, true) + if not variant then return variant, err end + right = variant + end + -- assign to a function + if operatingOn.type == "function" then + -- remove non-assignment functions + for i=#operatingOn.variants, 1, -1 do + if not operatingOn.variants[i].assignment then + table.remove(operatingOn.variants, i) + end + end + if #operatingOn.variants == 0 then + return nil, ("trying to perform assignment on function %s with no compatible assignment variant"):format(operatingOn.called_name) + end + -- rewrite function to perform assignment + operatingOn.assignment = right + return expression(r, state, namespace, currentPriority, operatingOn) + elseif operatingOn.type ~= "variable" then + return nil, ("trying to perform assignment on a %s expression"):format(operatingOn.type) + end + -- assign to a variable + return expression(r, state, namespace, currentPriority, { + type = ":=", + left = operatingOn, + right = right + }) + elseif op == "&" or op == "|" then + return expression(r, state, namespace, currentPriority, { + type = op, left = operatingOn, right = right }) @@ -237,12 +286,11 @@ local function expression(s, state, namespace, currentPriority, operatingOn) -- find variant local args = { type = "list", - return_type = "list", left = operatingOn, -- wrap in parentheses to avoid appending to argument list if right is a list - right = { type = "parentheses", return_type = right.return_type, expression = right } + right = { type = "parentheses", expression = right } } - local variant, err = find_function_variant(op, state, args, true) + local variant, err = find_function_variant(state, namespace, op, args, true) if not variant then return variant, err end return expression(r, state, namespace, currentPriority, variant) end @@ -259,7 +307,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn) if not right then return right, r_paren end if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of index expression"):format(r_paren) end local args = { type = "list", left = operatingOn, right = right } - local variant, err = find_function_variant("(", state, args, true) + local variant, err = find_function_variant(state, namespace, "()", args, true) if not variant then return variant, err end return expression(r, state, namespace, currentPriority, variant) end @@ -270,6 +318,6 @@ end package.loaded[...] = expression local common = require((...):gsub("expression$", "common")) -identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text = common.identifier_pattern, common.format_identifier, common.find, common.escape, common.find_function_variant, common.parse_text +identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text, string_escapes = common.identifier_pattern, common.format_identifier, common.find, common.escape, common.find_function_variant, common.parse_text, common.string_escapes return expression diff --git a/parser/postparser.lua b/parser/postparser.lua index 325dec0..c72a7ad 100644 --- a/parser/postparser.lua +++ b/parser/postparser.lua @@ -4,22 +4,44 @@ local parse_text -- * true: if success -- * nil, error: in case of error local function parse(state) + -- expression parsing for _, l in ipairs(state.queued_lines) do local line, namespace = l.line, l.namespace - -- default arguments + -- default arguments and type annotation if line.type == "function" then - for i, param in ipairs(line.params) do + for _, param in ipairs(line.params) do + -- get type annotation + if param.type_annotation then + local type_exp, rem = expression(param.type_annotation, state, namespace) + if not type_exp then return nil, ("in type annotation, %s; at %s"):format(rem, line.source) end + if rem:match("[^%s]") then + return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param.full_name, rem, line.source) + end + param.type_annotation = type_exp + end + -- get default value if param.default then - local exp, rem = expression(param.default, state, namespace) - if not exp then return nil, ("%s; at %s"):format(rem, line.source) end - if rem:match("[^%s]") then return nil, ("expected end of expression before %q; at %s"):format(rem, line.source) end - param.default = exp - -- complete type information - if exp.return_type then - line.variant.types[i] = exp.return_type + local default_exp, rem = expression(param.default, state, namespace) + if not default_exp then return nil, ("in default value, %s; at %s"):format(rem, line.source) end + if rem:match("[^%s]") then + return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param.full_name, rem, line.source) + end + param.default = default_exp + -- extract type annotation from default value + if default_exp.type == "function" and default_exp.called_name == "::" then + param.type_annotation = default_exp.argument.expression.right end end end + -- assignment argument + if line.assignment and line.assignment.type_annotation then + local type_exp, rem = expression(line.assignment.type_annotation, state, namespace) + if not type_exp then return nil, ("in type annotation, %s; at %s"):format(rem, line.source) end + if rem:match("[^%s]") then + return nil, ("unexpected characters after parameter %q: %q; at %s"):format(line.assignment.full_name, rem, line.source) + end + line.assignment.type_annotation = type_exp + end end -- expressions if line.expression then @@ -27,17 +49,9 @@ local function parse(state) if not exp then return nil, ("%s; at %s"):format(rem, line.source) end if rem:match("[^%s]") then return nil, ("expected end of expression before %q; at %s"):format(rem, line.source) end line.expression = exp - -- function return type information - if line.type == "return" then - local variant = line.parent_function.variant - local return_type = line.expression.return_type - if return_type then - if not variant.return_type then - variant.return_type = return_type - elseif variant.return_type ~= return_type then - return nil, ("trying to return a %s in a function that returns a %s; at %s"):format(return_type, variant.return_type, line.source) - end - end + -- variable pending definition: expression will be evaluated when variable is needed + if line.type == "definition" then + state.variables[line.fqm].value.expression = line.expression end end -- text diff --git a/parser/preparser.lua b/parser/preparser.lua index c1612c1..7bbde7e 100644 --- a/parser/preparser.lua +++ b/parser/preparser.lua @@ -1,6 +1,4 @@ -local expression -local format_identifier, identifier_pattern -local eval +local format_identifier, identifier_pattern, escape, special_functions_names, pretty_signature, signature -- try to define an alias using rem, the text that follows the identifier -- returns true, new_rem, alias_name in case of success @@ -8,7 +6,7 @@ local eval -- returns nil, err in case of alias and error local function maybe_alias(rem, fqm, namespace, line, state) local alias - if rem:match("^%:") then + if rem:match("^%:[^%:%=]") then local param_content = rem:sub(2) alias, rem = param_content:match("^("..identifier_pattern..")(.-)$") if not alias then return nil, ("expected an identifier in alias, but got %q; at %s"):format(param_content, line.source) end @@ -51,41 +49,100 @@ local function parse_line(line, state, namespace) elseif l:match("^%$") or l:match("^Β§") then -- Β§ is a 2-bytes caracter, DO NOT USE LUA PATTERN OPERATORS as they operate on single bytes r.type = l:match("^%$") and "function" or "checkpoint" r.child = true + -- store parent function and run checkpoint when line is read + if r.type == "checkpoint" then + r.parent_function = true + end + -- don't keep function node in block AST + if r.type == "function" then + r.remove_from_block_ast = true + -- lua function + if state.link_next_function_definition_to_lua_function then + r.lua_function = state.link_next_function_definition_to_lua_function + state.link_next_function_definition_to_lua_function = nil + end + end -- get identifier - local lc = l:match("^%$(.*)$") or l:match("^Β§(.*)$") + local lc = l:match("^%$(.-)$") or l:match("^Β§(.-)$") local identifier, rem = lc:match("^("..identifier_pattern..")(.-)$") - if not identifier then return nil, ("no valid identifier in checkpoint/function definition line %q; at %s"):format(lc, line.source) end + if not identifier then + for _, name in ipairs(special_functions_names) do + identifier, rem = lc:match("^(%s*"..escape(name).."%s*)(.-)$") + if identifier then break end + end + end + if not identifier then + return nil, ("no valid identifier in checkpoint/function definition line %q; at %s"):format(lc, line.source) + end -- format identifier local fqm = ("%s%s"):format(namespace, format_identifier(identifier)) + local func_namespace = fqm .. "." -- get alias local ok_alias ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state) if not ok_alias then return ok_alias, rem end + -- anything else are argument, isolate function it its own namespace + -- (to not mix its args and variables with the main variant) + if rem:match("[^%s]") then + func_namespace = ("%s(%s)."):format(fqm, r) + r.private_namespace = true + end + -- define function + if state.variables[fqm] then return nil, ("trying to define %s %s, but a variable with the same name exists; at %s"):format(r.type, fqm, line.source) end + r.namespace = func_namespace + r.name = fqm -- get params r.params = {} - if r.type == "function" and rem:match("^%b()$") then - local content = rem:gsub("^%(", ""):gsub("%)$", "") + if r.type == "function" and rem:match("^%b()") then + local content + content, rem = rem:match("^(%b())%s*(.*)$") + content = content:gsub("^%(", ""):gsub("%)$", "") for param in content:gmatch("[^%,]+") do -- get identifier local param_identifier, param_rem = param:match("^("..identifier_pattern..")(.-)$") - if not identifier then return nil, ("no valid identifier in function parameter %q; at %s"):format(param, line.source) end + if not param_identifier then return nil, ("no valid identifier in function parameter %q; at %s"):format(param, line.source) end param_identifier = format_identifier(param_identifier) -- format identifier - local param_fqm = ("%s.%s"):format(fqm, param_identifier) + local param_fqm = ("%s%s"):format(func_namespace, param_identifier) -- get alias local ok_param_alias, param_alias - ok_param_alias, param_rem, param_alias = maybe_alias(param_rem, param_fqm, fqm..".", line, state) + ok_param_alias, param_rem, param_alias = maybe_alias(param_rem, param_fqm, func_namespace, line, state) if not ok_param_alias then return ok_param_alias, param_rem end - -- get default value - local default - if param_rem:match("^=") then + -- get potential type annotation and default value + local type_annotation, default + if param_rem:match("^::") then + type_annotation = param_rem:match("^::(.*)$") + elseif param_rem:match("^=") then default = param_rem:match("^=(.*)$") elseif param_rem:match("[^%s]") then return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param_fqm, param_rem, line.source) end -- add parameter - table.insert(r.params, { name = param_identifier, alias = param_alias, full_name = param_fqm, default = default }) + table.insert(r.params, { name = param_identifier, alias = param_alias, full_name = param_fqm, type_annotation = type_annotation, default = default, vararg = nil }) end + end + -- get assignment param + if r.type == "function" and rem:match("^%:%=") then + local param = rem:match("^%:%=(.*)$") + -- get identifier + local param_identifier, param_rem = param:match("^("..identifier_pattern..")(.-)$") + if not param_identifier then return nil, ("no valid identifier in function parameter %q; at %s"):format(param, line.source) end + param_identifier = format_identifier(param_identifier) + -- format identifier + local param_fqm = ("%s%s"):format(func_namespace, param_identifier) + -- get alias + local ok_param_alias, param_alias + ok_param_alias, param_rem, param_alias = maybe_alias(param_rem, param_fqm, func_namespace, line, state) + if not ok_param_alias then return ok_param_alias, param_rem end + -- get potential type annotation + local type_annotation + if param_rem:match("^::") then + type_annotation = param_rem:match("^::(.*)$") + elseif param_rem:match("[^%s]") then + return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param_fqm, param_rem, line.source) + end + -- add parameter + r.assignment = { name = param_identifier, alias = param_alias, full_name = param_fqm, type_annotation = type_annotation, default = nil, vararg = nil } elseif rem:match("[^%s]") then return nil, ("expected end-of-line at end of checkpoint/function definition line, but got %q; at %s"):format(rem, line.source) end @@ -96,152 +153,100 @@ local function parse_line(line, state, namespace) minarity = minarity - 1 end end - if maxarity > 0 and r.params[maxarity].full_name:match("%.%.%.$") then -- varargs + -- varargs + if maxarity > 0 and r.params[maxarity].full_name:match("%.%.%.$") then r.params[maxarity].name = r.params[maxarity].name:match("^(.*)%.%.%.$") r.params[maxarity].full_name = r.params[maxarity].full_name:match("^(.*)%.%.%.$") r.params[maxarity].vararg = true minarity = minarity - 1 maxarity = math.huge end - -- store parent function and run checkpoint when line is read - if r.type == "checkpoint" then - r.parent_function = true - end - -- don't keep function node in block AST - if r.type == "function" then - r.remove_from_block_ast = true - end - -- define function and variables - r.namespace = fqm.."." - r.name = fqm - if state.variables[fqm] then return nil, ("trying to define %s %s, but a variable with the same name exists; at %s"):format(r.type, fqm, line.source) end - r.variant = { - arity = { minarity, maxarity }, - types = {}, - 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 - elseif r.type == "checkpoint" then - -- define π variable - if not state.variables[fqm..".π"] then - state.variables[fqm..".π"] = { - type = "number", - value = 0 - } - end - -- define alias for π - local reached_alias = state.builtin_aliases["π"] - if reached_alias then - local alias = ("%s.%s"):format(fqm, reached_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 + r.arity = { minarity, maxarity } + r.signature = signature(r) + r.pretty_signature = pretty_signature(r) + -- define variables + if not line.children then line.children = {} end + -- define ποΈ variable + local seen_alias = state.builtin_aliases["ποΈ"] + if seen_alias then + table.insert(line.children, 1, { content = (":ποΈ:%s=0"):format(seen_alias), source = line.source }) else - -- check for arity conflict - for _, variant in ipairs(state.functions[fqm]) do - local vmin, vmax = 0, math.huge - if type(variant.arity) == "table" then - vmin, vmax = variant.arity[1], variant.arity[2] - elseif variant.arity then - vmin, vmax = variant.arity, variant.arity - end - local min, max = 0, math.huge - if type(r.variant.arity) == "table" then - min, max = r.variant.arity[1], r.variant.arity[2] - elseif r.variant.arity then - min, max = variant.arity, r.variant.arity - end - if min == vmin and max == vmax then - return nil, ("trying to define %s %s with arity [%s;%s], but another function with the same name and arity exist; at %s"):format(r.type, fqm, min, max, line.source) - end - end - -- add - table.insert(state.functions[fqm], r.variant) + table.insert(line.children, 1, { content = ":ποΈ=0", source = line.source }) end - -- define args and set type check information - for i, param in ipairs(r.params) do + if r.type == "function" then + -- define π variable + local checkpoint_alias = 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 + elseif r.type == "checkpoint" then + -- define π variable + local reached_alias = 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 + end + -- define args + for _, param in ipairs(r.params) do if not state.variables[param.full_name] then state.variables[param.full_name] = { type = "undefined argument", - value = { r.variant, i } + value = nil } - elseif state.variables[param.full_name].type ~= "undefined argument" then - r.variant.types[i] = state.variables[param.full_name].type + 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 end + if r.assignment then + if not state.variables[r.assignment.full_name] then + state.variables[r.assignment.full_name] = { + type = "undefined argument", + value = nil + } + 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 + end + -- define new function, no other variant yet + if not state.functions[fqm] then + state.functions[fqm] = { r } + -- overloading + else + -- check for signature conflict with functions with the same fqm + for _, variant in ipairs(state.functions[fqm]) do + if r.signature == variant.signature then + return nil, ("trying to define %s %s, but another function with same signature %s exists; at %s"):format(r.type, r.pretty_signature, variant.pretty_signature, line.source) + end + end + -- add + table.insert(state.functions[fqm], r) + end -- definition elseif l:match("^:") then r.type = "definition" r.remove_from_block_ast = true - -- get expression - local exp, rem = expression(l:match("^:(.*)$"), state, namespace) -- expression parsing is done directly to get type information - if not exp then return nil, ("%s; at %s"):format(rem, line.source) end -- get identifier - local identifier - identifier, rem = 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 + local identifier, rem = l: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)) -- get alias local ok_alias ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state) if not ok_alias then return ok_alias, rem end - if rem:match("[^%s]") then - return nil, ("expected end-of-line after identifier in definition line, but got %q; at %s"):format(rem, line.source) - end + -- get expression + local exp = rem:match("^=(.*)$") + if not exp then return nil, ("expected \"= expression\" after %q in definition line; at %s"):format(rem, 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) - if not v then return v, e end - -- update function typecheck information - if state.variables[fqm] and state.variables[fqm].type == "undefined argument" then - local und = state.variables[fqm].value - und[1].types[und[2]] = v.type - end - state.variables[fqm] = v - elseif state.variables[fqm].type ~= exp.type then - return nil, ("trying to define variable %s of type %s but it is already defined with type %s; at %s"):format(fqm, exp.type, state.variables[fqm].type, line.source) - end + if state.functions[fqm] then return nil, ("trying to define variable %q, but a function with the same name exists; at %s"):format(fqm, line.source) end + if state.variables[fqm] then return nil, ("trying to define variable %q but it is already defined; at %s"):format(fqm, line.source) end + r.fqm = fqm + r.expression = exp + state.variables[fqm] = { type = "pending definition", value = { expression = nil, source = r.source } } -- tag elseif l:match("^%#") then r.type = "tag" @@ -424,9 +429,7 @@ local function parse(state, s, name, source) end package.loaded[...] = parse -expression = require((...):gsub("preparser$", "expression")) local common = require((...):gsub("preparser$", "common")) -format_identifier, identifier_pattern = common.format_identifier, common.identifier_pattern -eval = require((...):gsub("parser%.preparser$", "interpreter.expression")) +format_identifier, identifier_pattern, escape, special_functions_names, pretty_signature, signature = common.format_identifier, common.identifier_pattern, common.escape, common.special_functions_names, common.pretty_signature, common.signature return parse diff --git a/stdlib/bootscript.lua b/stdlib/bootscript.lua new file mode 100644 index 0000000..094ed4c --- /dev/null +++ b/stdlib/bootscript.lua @@ -0,0 +1,12 @@ +return [[ +(Built-in type definition) +:nil="nil" +:number="number" +:string="string" +:list="list" +:pair="pair" + +(Default formatter: doesn't do anything and let built-in formatters do the job) +$ format(v) + @v +]] diff --git a/stdlib/functions.lua b/stdlib/functions.lua index eee173c..8949b60 100644 --- a/stdlib/functions.lua +++ b/stdlib/functions.lua @@ -1,421 +1,222 @@ -local truthy, eval, find_function_variant, anselme - -local function rewrite_assignement(fqm, state, arg, explicit_call) - local op, e = find_function_variant(fqm:match("^(.*)%=$"), state, arg, true) - if not op then return op, e end - local ass, err = find_function_variant(":=", state, { type = "list", left = arg.left, right = op }, explicit_call) - if not ass then return ass, err end - return ass -end - -local function compare(a, b) - if a.type ~= b.type then - return false - end - if a.type == "pair" then - return compare(a.value[1], b.value[1]) and compare(a.value[2], b.value[2]) - elseif a.type == "list" then - if #a.value ~= #b.value then - return false - end - for i, v in ipairs(a.value) do - if not compare(v, b.value[i]) then - return false - end - end - return true - else - return a.value == b.value - end -end - -local numeric_index -local string_index +local truthy, anselme, compare, is_of_type local functions functions = { -- discard left - [";"] = { - { - arity = 2, mode = "raw", - value = function(a, b) return b end - } - }, - -- assignement - [":="] = { - -- assign to numeric index - { - arity = 2, mode = "custom", - check = function(state, args) - local left = args[1] - return left.type == "function" and left.variant == numeric_index and left.argument.expression.left.type == "variable" - end, - value = function(state, exp) - local arg = exp.argument.expression - local name = arg.left.argument.expression.left.name - local index, indexe = eval(state, arg.left.argument.expression.right) - if not index then return index, indexe end - local right, righte = eval(state, arg.right) - if not right then return right, righte end - state.variables[name].value[index.value] = right - return right - end - }, - -- assign to string index - { - arity = 2, mode = "custom", - check = function(state, args) - local left = args[1] - return left.type == "function" and left.variant == string_index and left.argument.expression.left.type == "variable" - end, - value = function(state, exp) - local arg = exp.argument.expression - local name = arg.left.argument.expression.left.name - local index, indexe = eval(state, arg.left.argument.expression.right) - if not index then return index, indexe end - local right, righte = eval(state, arg.right) - if not right then return right, righte end - -- update index - local list = state.variables[name].value - for _,v in ipairs(list) do - if v.type == "pair" and compare(v.value[1], index) then - v.value[2] = right - return right - end - end - -- new index - table.insert(list, { - type = "pair", - value = { index, right } - }) - return right - end - }, - -- assign to direct variable - { - arity = 2, mode = "custom", - check = function(state, args) - local left, right = args[1], args[2] - if left.type ~= "variable" then - return nil, ("assignement expected a variable as a left argument but received a %s"):format(left.type) - end - if left.return_type and right.return_type and left.return_type ~= right.return_type then - return nil, ("trying to assign a %s value to a %s variable"):format(right.return_type, left.return_type) - end - return right.return_type or true - end, - value = function(state, exp) - local arg = exp.argument.expression - local name = arg.left.name - local right, righte = eval(state, arg.right) - if not right then return right, righte end - state.variables[name] = right - return right - end - } - }, - ["+="] = { - { rewrite = rewrite_assignement } - }, - ["-="] = { - { rewrite = rewrite_assignement } - }, - ["*="] = { - { rewrite = rewrite_assignement } - }, - ["/="] = { - { rewrite = rewrite_assignement } - }, - ["//="] = { - { rewrite = rewrite_assignement } - }, - ["%="] = { - { rewrite = rewrite_assignement } - }, - ["^="] = { - { rewrite = rewrite_assignement } + [";(a, b)"] = { + mode = "raw", + value = function(a, b) return b end }, -- comparaison - ["=="] = { - { - arity = 2, return_type = "number", mode = "raw", - value = function(a, b) - return { - type = "number", - value = compare(a, b) and 1 or 0 - } - end - } + ["==(a, b)"] = { + mode = "raw", + value = function(a, b) + return { + type = "number", + value = compare(a, b) and 1 or 0 + } + end }, - ["!="] = { - { - arity = 2, return_type = "number", mode = "raw", - value = function(a, b) - return { - type = "number", - value = compare(a, b) and 0 or 1 - } - end - } - }, - [">"] = { - { - arity = 2, types = { "number", "number" }, return_type = "number", - value = function(a, b) return a > b end - } - }, - ["<"] = { - { - arity = 2, types = { "number", "number" }, return_type = "number", - value = function(a, b) return a < b end - } - }, - [">="] = { - { - arity = 2, types = { "number", "number" }, return_type = "number", - value = function(a, b) return a >= b end - } - }, - ["<="] = { - { - arity = 2, types = { "number", "number" }, return_type = "number", - value = function(a, b) return a <= b end - } + ["!=(a, b)"] = { + mode = "raw", + value = function(a, b) + return { + type = "number", + value = compare(a, b) and 0 or 1 + } + end }, + [">(a::number, b::number)"] = function(a, b) return a > b end, + ["<(a::number, b::number)"] = function(a, b) return a < b end, + [">=(a::number, b::number)"] = function(a, b) return a >= b end, + ["<=(a::number, b::number)"] = function(a, b) return a <= b end, -- arithmetic - ["+"] = { - { - arity = 2, - value = function(a, b) - if type(a) == "string" then - return a .. b - else - return a + b - end - end - } - }, - ["-"] = { - { - arity = 2, types = { "number", "number" }, return_type = "number", - value = function(a, b) return a - b end - }, - { - arity = 1, types = { "number" }, return_type = "number", - value = function(a) return -a end - } - }, - ["*"] = { - { - arity = 2, types = { "number", "number" }, return_type = "number", - value = function(a, b) return a * b end - } - }, - ["/"] = { - { - arity = 2, types = { "number", "number" }, return_type = "number", - value = function(a, b) return a / b end - } - }, - ["//"] = { - { - arity = 2, types = { "number", "number" }, return_type = "number", - value = function(a, b) return math.floor(a / b) end - } - }, - ["^"] = { - { - arity = 2, types = { "number", "number" }, return_type = "number", - value = function(a, b) return a ^ b end - } - }, + ["+(a::number, b::number)"] = function(a, b) return a + b end, + ["+(a::string, b::string)"] = function(a, b) return a .. b end, + ["-(a::number, b::number)"] = function(a, b) return a - b end, + ["-(a::number)"] = function(a) return -a end, + ["*(a::number, b::number)"] = function(a, b) return a * b end, + ["/(a::number, b::number)"] = function(a, b) return a / b end, + ["//(a::number, b::number)"] = function(a, b) return math.floor(a / b) end, + ["^(a::number, b::number)"] = function(a, b) return a ^ b end, -- boolean - ["!"] = { - { - arity = 1, return_type = "number", mode = "raw", - value = function(a) - return { - type = "number", - value = truthy(a) and 0 or 1 - } - end - } - }, - ["&"] = { - { - arity = 2, return_type = "number", mode = "custom", - value = function(state, exp) - local arg = exp.argument.expression - local left, lefte = eval(state, arg.left) - if not left then return left, lefte end - if truthy(left) then - local right, righte = eval(state, arg.right) - if not right then return right, righte end - if truthy(right) then - return { - type = "number", - value = 1 - } - end - end - return { - type = "number", - value = 0 - } - end - } - }, - ["|"] = { - { - arity = 2, return_type = "number", mode = "custom", - value = function(state, exp) - local arg = exp.argument.expression - local left, lefte = eval(state, arg.left) - if not left then return left, lefte end - if truthy(left) then - return { - type = "number", - value = 1 - } - end - local right, righte = eval(state, arg.right) - if not right then return right, righte end - return { - type = "number", - value = truthy(right) and 1 or 0 - } - end - } + ["!(a)"] = { + mode = "raw", + value = function(a) + return { + type = "number", + value = truthy(a) and 0 or 1 + } + end }, -- pair - [":"] = { - { - arity = 2, return_type = "pair", mode = "raw", - value = function(a, b) - return { - type = "pair", - value = { a, b } - } - end - } + [":(a, b)"] = { + mode = "raw", + value = function(a, b) + return { + type = "pair", + value = { a, b } + } + end + }, + -- type + ["::(a, b)"] = { + mode = "raw", + value = function(a, b) + return { + type = "type", + value = { a, b } + } + end }, -- index - ["("] = { - { - arity = 2, types = { "list", "number" }, mode = "raw", - value = function(a, b) - return a.value[b.value] or { type = "nil", value = nil } - end - }, - { - arity = 2, types = { "list", "string" }, mode = "raw", - value = function(a, b) - for _,v in ipairs(a.value) do - if v.type == "pair" and compare(v.value[1], b) then - return v.value[2] - end + ["()(l::list, i::number)"] = { + mode = "untyped raw", + value = function(l, i) + return l.value[i.value] or { type = "nil", value = nil } + end + }, + ["()(l::list, i::string)"] = { + mode = "untyped raw", + value = function(l, i) + for _, v in ipairs(l.value) do + if v.type == "pair" and compare(v.value[1], i) then + return v.value[2] end - return { type = "nil", value = nil } end - } + return { type = "nil", value = nil } + end + }, + -- index assignment + ["()(l::list, i::number) := v"] = { + mode = "raw", + value = function(l, i, v) + local lv = l.type == "type" and l.value[1] or l + local iv = i.type == "type" and i.value[1] or i + lv.value[iv.value] = v + return v + end + }, + ["()(l::list, k::string) := v"] = { + mode = "raw", + value = function(l, k, v) + local lv = l.type == "type" and l.value[1] or l + local kv = k.type == "type" and k.value[1] or k + -- update index + for _, x in ipairs(lv.value) do + if x.type == "pair" and compare(x.value[1], kv) then + x.value[2] = v + return v + end + end + -- new index + table.insert(lv.value, { + type = "pair", + value = { kv, v } + }) + return v + end }, -- pair methods - name = { - { - arity = 1, types = { "pair" }, mode = "raw", - value = function(a) - return a.value[1] - end - } + ["name(p::pair)"] = { + mode = "untyped raw", + value = function(a) + return a.value[1] + end }, - value = { - { - arity = 1, types = { "pair" }, mode = "raw", - value = function(a) - return a.value[2] - end - } + ["value(p::pair)"] = { + mode = "untyped raw", + value = function(a) + return a.value[2] + end }, -- list methods - len = { - { - arity = 1, types = { "list" }, return_type = "number", mode = "raw", -- raw to count pairs in the list - value = function(a) - return { - type = "number", - value = #a.value - } - end - } + ["len(l::list)"] = { + mode = "untyped raw", -- raw to count pairs in the list + value = function(a) + return { + type = "number", + value = #a.value + } + end }, - insert = { - { - arity = 2, types = { "list" }, return_type = "list", mode = "raw", - value = function(a, v) - table.insert(a.value, v) - return a - end - }, - { - arity = 3, types = { "list", "number" }, return_type = "list", mode = "raw", - value = function(a, k, v) - table.insert(a.value, k.value, v) - return a - end - } + ["insert(l::list, v)"] = { + mode = "raw", + value = function(l, v) + local lv = l.type == "type" and l.value[1] or l + table.insert(lv.value, v) + end }, - remove = { - { - arity = 1, types = { "list" }, return_type = "list", mode = "raw", - value = function(a) - table.remove(a.value) - return a - end - }, - { - arity = 2, types = { "list", "number" }, return_type = "list", mode = "raw", - value = function(a, k) - table.remove(a.value, k.value) - return a - end - } + ["insert(l::list, i::number, v)"] = { + mode = "raw", + value = function(l, i, v) + local lv = l.type == "type" and l.value[1] or l + local iv = i.type == "type" and i.value[1] or i + table.insert(lv.value, iv.value, v) + end }, - find = { - { - arity = 2, types = { "list" }, return_type = "number", mode = "raw", - value = function(a, v) - for i, x in ipairs(v.value) do - if compare(v, x) then - return i - end + ["remove(l::list)"] = { + mode = "untyped raw", + value = function(l) + return table.remove(l.value) + end + }, + ["remove(l::list, i::number)"] = { + mode = "untyped raw", + value = function(l, i) + return table.remove(l.value, i.value) + end + }, + ["find(l::list, v)"] = { + mode = "raw", + value = function(l, v) + local lv = l.type == "type" and l.value[1] or l + for i, x in ipairs(lv.value) do + if compare(x, v) then + return i end - return 0 end - }, + return { type = "number", value = 0 } + end }, -- other methods - rand = { - { - arity = 0, return_type = "number", - value = function() - return math.random() + ["error(m::string)"] = function(m) error(m, 0) end, + ["rand"] = function() return math.random() end, + ["rand(a::number)"] = function(a) return math.random(a) end, + ["rand(a::number, b::number)"] = function(a, b) return math.random(a, b) end, + ["raw(v)"] = { + mode = "raw", + value = function(v) + if v.type == "type" then + return v.value[1] + else + return v end - }, - { - arity = 1, types = { "number" }, return_type = "number", - value = function(a) - return math.random(a) - end - }, - { - arity = 2, types = { "number", "number" }, return_type = "number", - value = function(a, b) - return math.random(a, b) - end - } + end }, - cycle = function(...) - local l = {...} + ["type(v)"] = { + mode = "raw", + value = function(v) + if v.type == "type" then + return v.value[2] + else + return { + type = "string", + value = v.type + } + end + end + }, + ["is of type(v, t)"] = { + mode = "raw", + value = function(v, t) + return { + type = "number", + value = is_of_type(v, t) or 0 + } + end + }, + ["cycle(l...)"] = function(l) local f, fseen = l[1], assert(anselme.running:eval(l[1]..".ποΈ", anselme.running:current_namespace())) for j=2, #l do local seen = assert(anselme.running:eval(l[j]..".ποΈ", anselme.running:current_namespace())) @@ -426,12 +227,10 @@ functions = { end return anselme.running:run(f, anselme.running:current_namespace()) end, - random = function(...) - local l = {...} + ["random(l...)"] = function(l) return anselme.running:run(l[math.random(1, #l)], anselme.running:current_namespace()) end, - next = function(...) - local l = {...} + ["next(l...)"] = function(l) local f = l[#l] for j=1, #l-1 do local seen = assert(anselme.running:eval(l[j]..".ποΈ", anselme.running:current_namespace())) @@ -444,13 +243,7 @@ functions = { end } -numeric_index = functions["("][1] -string_index = functions["("][2] - package.loaded[...] = functions -truthy = require((...):gsub("stdlib%.functions$", "interpreter.common")).truthy -eval = require((...):gsub("stdlib%.functions$", "interpreter.expression")) -find_function_variant = require((...):gsub("stdlib%.functions$", "parser.common")).find_function_variant +local common = require((...):gsub("stdlib%.functions$", "interpreter.common")) +truthy, compare, is_of_type = common.truthy, common.compare, common.is_of_type anselme = require((...):gsub("stdlib%.functions$", "anselme")) - -return functions diff --git a/stdlib/types.lua b/stdlib/types.lua index c355118..4460b69 100644 --- a/stdlib/types.lua +++ b/stdlib/types.lua @@ -130,6 +130,20 @@ types.anselme = { if not v and ve then return v, ve end return { [k] = v } end + }, + type = { + format = function(val) + local k, ke = format(val[1]) + if not k then return k, ke end + local v, ve = format(val[2]) + if not v then return v, ve end + return ("%s::%s"):format(k, v) + end, + to_lua = function(val) + local k, ke = to_lua(val[1]) + if not k and ke then return k, ke end + return k + end } } diff --git a/test/run.lua b/test/run.lua index fc4de96..0e186e9 100644 --- a/test/run.lua +++ b/test/run.lua @@ -88,6 +88,8 @@ if args.script then elseif t == "choice" then print(format_text(d, "\n> ")) istate:choose(io.read()) + elseif t == "error" then + print(t, d) else print(t, inspect(d)) end @@ -110,51 +112,38 @@ else vm:setaliases("seen", "checkpoint", "reached") vm:loadfunction { -- custom event test - ["wait"] = { - { - arity = 1, types = { "number" }, - value = function(duration) - coroutine.yield("wait", duration) - end - } + ["wait(time::number)"] = { + value = function(duration) + coroutine.yield("wait", duration) + end }, -- run another function in parallel - ["run"] = { - { - arity = 1, types = { "string" }, - value = function(str) - local istate, e = anselme.running.vm:run(str, anselme.running:current_namespace()) - if not istate then coroutine.yield("error", e) end - local event, data = istate:step() - coroutine.yield(event, data) - end - } + ["run(name::string)"] = { + value = function(str) + local istate, e = anselme.running.vm:run(str, anselme.running:current_namespace()) + if not istate then coroutine.yield("error", e) end + local event, data = istate:step() + coroutine.yield(event, data) + end }, -- manual choice - choose = { - { - arity = 1, types = { "number" }, - value = function(c) - anselme.running:choose(c) - end - } + ["choose(choice::number)"] = { + value = function(c) + anselme.running:choose(c) + end }, -- manual interrupt - interrupt = { - { - arity = 1, types = { "string" }, - value = function(str) - anselme.running:interrupt(str) - coroutine.yield("wait", 0) - end - }, - { - arity = 0, - value = function() - anselme.running:interrupt() - coroutine.yield("wait", 0) - end - } + ["interrupt(name::string)"] = { + value = function(str) + anselme.running:interrupt(str) + coroutine.yield("wait", 0) + end + }, + ["interrupt()"] = { + value = function() + anselme.running:interrupt() + coroutine.yield("wait", 0) + end } } local state, err = vm:loadfile(file, namespace) diff --git a/test/tests/binary operator overload.ans b/test/tests/binary operator overload.ans new file mode 100644 index 0000000..d6987a2 --- /dev/null +++ b/test/tests/binary operator overload.ans @@ -0,0 +1,11 @@ +$ -(a, b) + @"generic minus" + +$ -(a::string, b::string) + @a + " minus " + b + +{2-5} + +{"heh"-"lol"} + +{[]-[]} diff --git a/test/tests/binary operator overload.lua b/test/tests/binary operator overload.lua new file mode 100644 index 0000000..728f831 --- /dev/null +++ b/test/tests/binary operator overload.lua @@ -0,0 +1,30 @@ +local _={} +_[13]={} +_[12]={} +_[11]={} +_[10]={data="generic minus",tags=_[13]} +_[9]={data="heh minus lol",tags=_[12]} +_[8]={data="-3",tags=_[11]} +_[7]={_[10]} +_[6]={_[9]} +_[5]={_[8]} +_[4]={"return"} +_[3]={"text",_[7]} +_[2]={"text",_[6]} +_[1]={"text",_[5]} +return {_[1],_[2],_[3],_[4]} +--[[ +{ "text", { { + data = "-3", + tags = {} + } } } +{ "text", { { + data = "heh minus lol", + tags = {} + } } } +{ "text", { { + data = "generic minus", + tags = {} + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/choice preserve tags.lua b/test/tests/choice preserve tags.lua index 040d8cb..b01b74f 100644 --- a/test/tests/choice preserve tags.lua +++ b/test/tests/choice preserve tags.lua @@ -4,14 +4,14 @@ _[25]={k="v"} _[24]={42,k="v"} _[23]={} _[22]={42} -_[21]={tags=_[26],data="f"} -_[20]={tags=_[25],data="e"} -_[19]={tags=_[24],data="b"} -_[18]={tags=_[25],data="d"} -_[17]={tags=_[24],data="a"} -_[16]={tags=_[22],data="b"} -_[15]={tags=_[23],data="c"} -_[14]={tags=_[22],data="a"} +_[21]={data="f",tags=_[26]} +_[20]={data="e",tags=_[25]} +_[19]={data="b",tags=_[24]} +_[18]={data="d",tags=_[25]} +_[17]={data="a",tags=_[24]} +_[16]={data="b",tags=_[22]} +_[15]={data="c",tags=_[23]} +_[14]={data="a",tags=_[22]} _[13]={_[21]} _[12]={_[20]} _[11]={_[19]} diff --git a/test/tests/commit.ans b/test/tests/commit.ans index 8dfb5fe..273c158 100644 --- a/test/tests/commit.ans +++ b/test/tests/commit.ans @@ -1,5 +1,5 @@ $ bar - :5 var + :var=5 ~ var := 2 diff --git a/test/tests/condition else false.ans b/test/tests/condition else false.ans index 2a1214b..1f68306 100644 --- a/test/tests/condition else false.ans +++ b/test/tests/condition else false.ans @@ -1,4 +1,4 @@ -:5 a +:a = 5 ~ a == 2 ko diff --git a/test/tests/condition else true.ans b/test/tests/condition else true.ans index 3a9f0a1..9c2b2ad 100644 --- a/test/tests/condition else true.ans +++ b/test/tests/condition else true.ans @@ -1,4 +1,4 @@ -:5 a +:a = 5 ~ a == 5 ok diff --git a/test/tests/condition elseif false.ans b/test/tests/condition elseif false.ans index 6549d9f..f87d64e 100644 --- a/test/tests/condition elseif false.ans +++ b/test/tests/condition elseif false.ans @@ -1,4 +1,4 @@ -:5 a +:a = 5 ~ a == 2 ko diff --git a/test/tests/condition elseif true.ans b/test/tests/condition elseif true.ans index bf2776c..709bd65 100644 --- a/test/tests/condition elseif true.ans +++ b/test/tests/condition elseif true.ans @@ -1,4 +1,4 @@ -:5 a +:a = 5 ~ a == 2 ko diff --git a/test/tests/condition false.ans b/test/tests/condition false.ans index 05065b3..6794221 100644 --- a/test/tests/condition false.ans +++ b/test/tests/condition false.ans @@ -1,4 +1,4 @@ -:5 a +:a = 5 ~ a == 2 ko diff --git a/test/tests/condition true.ans b/test/tests/condition true.ans index 28cb238..07a9e4c 100644 --- a/test/tests/condition true.ans +++ b/test/tests/condition true.ans @@ -1,4 +1,4 @@ -:5 a +:a = 5 ~ a == 5 ok diff --git a/test/tests/custom text formatting.ans b/test/tests/custom text formatting.ans new file mode 100644 index 0000000..4c2fdd8 --- /dev/null +++ b/test/tests/custom text formatting.ans @@ -0,0 +1,11 @@ +:person = "personne" + +$ Person(name, age) + @[name=name, age=age]::person + +:abject = Person("Darmanin", 38) + +{abject} + +$ format(p::person) + @"Name: {p("name")}\nAge: {p("age")}" diff --git a/test/tests/custom text formatting.lua b/test/tests/custom text formatting.lua new file mode 100644 index 0000000..0246063 --- /dev/null +++ b/test/tests/custom text formatting.lua @@ -0,0 +1,14 @@ +local _={} +_[5]={} +_[4]={data="Name: Darmanin\nAge: 38",tags=_[5]} +_[3]={_[4]} +_[2]={"return"} +_[1]={"text",_[3]} +return {_[1],_[2]} +--[[ +{ "text", { { + data = "Name: Darmanin\nAge: 38", + tags = {} + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/define override function.ans b/test/tests/define override function.ans index 6b0e7cd..36ff9c6 100644 --- a/test/tests/define override function.ans +++ b/test/tests/define override function.ans @@ -1,3 +1,3 @@ $ a -:2 a +:a = 2 diff --git a/test/tests/define override function.lua b/test/tests/define override function.lua index 5550f10..7f7e3da 100644 --- a/test/tests/define override function.lua +++ b/test/tests/define override function.lua @@ -1,6 +1,6 @@ local _={} -_[1]={"error","trying to define variable define override function.a, but a function with the same name exists; at test/tests/define override function.ans:3"} +_[1]={"error","trying to define variable \"define override function.a\", but a function with the same name exists; at test/tests/define override function.ans:3"} return {_[1]} --[[ -{ "error", "trying to define variable define override function.a, but a function with the same name exists; at test/tests/define override function.ans:3" } +{ "error", 'trying to define variable "define override function.a", but a function with the same name exists; at test/tests/define override function.ans:3' } ]]-- \ No newline at end of file diff --git a/test/tests/define override variable.ans b/test/tests/define override variable.ans index 31d489e..3a7b80f 100644 --- a/test/tests/define override variable.ans +++ b/test/tests/define override variable.ans @@ -1,3 +1,3 @@ -:2 a +:a = 2 $ a diff --git a/test/tests/define override.ans b/test/tests/define override.ans index 10a8e97..1fdefc8 100644 --- a/test/tests/define override.ans +++ b/test/tests/define override.ans @@ -1,5 +1,5 @@ -:5 a +:a = 5 -:2 a +:a = 2 a: {a} diff --git a/test/tests/define override.lua b/test/tests/define override.lua index ab7c161..a7527f7 100644 --- a/test/tests/define override.lua +++ b/test/tests/define override.lua @@ -1,14 +1,6 @@ local _={} -_[5]={} -_[4]={data="a: 5",tags=_[5]} -_[3]={_[4]} -_[2]={"return"} -_[1]={"text",_[3]} -return {_[1],_[2]} +_[1]={"error","trying to define variable \"define override.a\" but it is already defined; at test/tests/define override.ans:3"} +return {_[1]} --[[ -{ "text", { { - data = "a: 5", - tags = {} - } } } -{ "return" } +{ "error", 'trying to define variable "define override.a" but it is already defined; at test/tests/define override.ans:3' } ]]-- \ No newline at end of file diff --git a/test/tests/define.ans b/test/tests/define.ans index 3fe5226..db1999d 100644 --- a/test/tests/define.ans +++ b/test/tests/define.ans @@ -1 +1 @@ -:5 a +:a = 5 diff --git a/test/tests/equality operator.ans b/test/tests/equality operator.ans index 9586dca..c48d644 100644 --- a/test/tests/equality operator.ans +++ b/test/tests/equality operator.ans @@ -1,12 +1,12 @@ -:(1:2) a +:a = [1:2] -0 = {a == (5:2)} +0 = {a == [5:2]} -0 = {a == (1:3)} +0 = {a == [1:3]} -1 = {a == (1:2)} +1 = {a == [1:2]} -:[1,2,3] b +:b = [1,2,3] 0 = {b == a} diff --git a/test/tests/function arity conflict.lua b/test/tests/function arity conflict.lua deleted file mode 100644 index c77b958..0000000 --- a/test/tests/function arity conflict.lua +++ /dev/null @@ -1,6 +0,0 @@ -local _={} -_[1]={"error","trying to define function function arity conflict.f with arity [2;2], but another function with the same name and arity exist; at test/tests/function arity conflict.ans:5"} -return {_[1]} ---[[ -{ "error", "trying to define function function arity conflict.f with arity [2;2], but another function with the same name and arity exist; at test/tests/function arity conflict.ans:5" } -]]-- \ No newline at end of file diff --git a/test/tests/function assignement.ans b/test/tests/function assignement.ans new file mode 100644 index 0000000..f55ec30 --- /dev/null +++ b/test/tests/function assignement.ans @@ -0,0 +1,22 @@ +:a = 5 + +$ f(p) + @a + +$ f(p) := v + v={v} + ~ a := v + +$ f(p) := v::string + v2={v} + ~ a := p + +{f(a)} + +~ f(3) := 50 + +{f(a)} + +~ f(3) := "ok" + +{f(a)} diff --git a/test/tests/function assignement.lua b/test/tests/function assignement.lua new file mode 100644 index 0000000..300798a --- /dev/null +++ b/test/tests/function assignement.lua @@ -0,0 +1,46 @@ +local _={} +_[21]={} +_[20]={} +_[19]={} +_[18]={} +_[17]={} +_[16]={data="3",tags=_[21]} +_[15]={data="v2=ok",tags=_[20]} +_[14]={data="50",tags=_[19]} +_[13]={data="v=50",tags=_[18]} +_[12]={data="5",tags=_[17]} +_[11]={_[16]} +_[10]={_[15]} +_[9]={_[14]} +_[8]={_[13]} +_[7]={_[12]} +_[6]={"return"} +_[5]={"text",_[11]} +_[4]={"text",_[10]} +_[3]={"text",_[9]} +_[2]={"text",_[8]} +_[1]={"text",_[7]} +return {_[1],_[2],_[3],_[4],_[5],_[6]} +--[[ +{ "text", { { + data = "5", + tags = {} + } } } +{ "text", { { + data = "v=50", + tags = {} + } } } +{ "text", { { + data = "50", + tags = {} + } } } +{ "text", { { + data = "v2=ok", + tags = {} + } } } +{ "text", { { + data = "3", + tags = {} + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/function arity conflict.ans b/test/tests/function conflict.ans similarity index 65% rename from test/tests/function arity conflict.ans rename to test/tests/function conflict.ans index b8f2153..1a3f8c7 100644 --- a/test/tests/function arity conflict.ans +++ b/test/tests/function conflict.ans @@ -2,4 +2,4 @@ $ f(a, b) $ f(x) -$ f(u, v) \ No newline at end of file +$ f(a, b) diff --git a/test/tests/function conflict.lua b/test/tests/function conflict.lua new file mode 100644 index 0000000..27482ad --- /dev/null +++ b/test/tests/function conflict.lua @@ -0,0 +1,6 @@ +local _={} +_[1]={"error","trying to define function function conflict.f(a, b) (at test/tests/function conflict.ans:5), but another function with same signature function conflict.f(a, b) (at test/tests/function conflict.ans:1) exists; at test/tests/function conflict.ans:5"} +return {_[1]} +--[[ +{ "error", "trying to define function function conflict.f(a, b) (at test/tests/function conflict.ans:5), but another function with same signature function conflict.f(a, b) (at test/tests/function conflict.ans:1) exists; at test/tests/function conflict.ans:5" } +]]-- \ No newline at end of file diff --git a/test/tests/function custom type dispatch error.ans b/test/tests/function custom type dispatch error.ans new file mode 100644 index 0000000..231cf35 --- /dev/null +++ b/test/tests/function custom type dispatch error.ans @@ -0,0 +1,17 @@ +:name="name"::string +:french name="french"::name +:esperanto name="esperanto"::name + +$ a(name::string) + {name} is english or generic + +$ a(name:nom::french name) + {nom} is french + +~ a("bob"::name) + +~ a("pierre"::french name) + +~ a("idk"::esperanto name) + +~ a(5) diff --git a/test/tests/function custom type dispatch error.lua b/test/tests/function custom type dispatch error.lua new file mode 100644 index 0000000..93a1c39 --- /dev/null +++ b/test/tests/function custom type dispatch error.lua @@ -0,0 +1,30 @@ +local _={} +_[13]={} +_[12]={} +_[11]={} +_[10]={data="idk::esperanto::name::string is english or generic",tags=_[13]} +_[9]={data="pierre::french::name::string is french",tags=_[12]} +_[8]={data="bob::name::string is english or generic",tags=_[11]} +_[7]={_[10]} +_[6]={_[9]} +_[5]={_[8]} +_[4]={"error","no compatible function found for call to a(number); potential candidates were:\n\9function custom type dispatch error.a(name::string) (at test/tests/function custom type dispatch error.ans:5): argument name is not of expected type string\n\9function custom type dispatch error.a(name:nom::french name) (at test/tests/function custom type dispatch error.ans:8): argument name is not of expected type french::name::string; at test/tests/function custom type dispatch error.ans:17"} +_[3]={"text",_[7]} +_[2]={"text",_[6]} +_[1]={"text",_[5]} +return {_[1],_[2],_[3],_[4]} +--[[ +{ "text", { { + data = "bob::name::string is english or generic", + tags = {} + } } } +{ "text", { { + data = "pierre::french::name::string is french", + tags = {} + } } } +{ "text", { { + data = "idk::esperanto::name::string is english or generic", + tags = {} + } } } +{ "error", "no compatible function found for call to a(number); potential candidates were:\n\tfunction custom type dispatch error.a(name::string) (at test/tests/function custom type dispatch error.ans:5): argument name is not of expected type string\n\tfunction custom type dispatch error.a(name:nom::french name) (at test/tests/function custom type dispatch error.ans:8): argument name is not of expected type french::name::string; at test/tests/function custom type dispatch error.ans:17" } +]]-- \ No newline at end of file diff --git a/test/tests/function custom type dispatch.ans b/test/tests/function custom type dispatch.ans new file mode 100644 index 0000000..d257609 --- /dev/null +++ b/test/tests/function custom type dispatch.ans @@ -0,0 +1,15 @@ +:name="name"::string +:french name="french"::name +:esperanto name="esperanto"::name + +$ a(name) + {name} is english or generic + +$ a(name:nom::french name) + {nom} is french + +~ a("bob"::name) + +~ a("pierre"::french name) + +~ a("idk"::esperanto name) diff --git a/test/tests/function custom type dispatch.lua b/test/tests/function custom type dispatch.lua new file mode 100644 index 0000000..a70d9e1 --- /dev/null +++ b/test/tests/function custom type dispatch.lua @@ -0,0 +1,30 @@ +local _={} +_[13]={} +_[12]={} +_[11]={} +_[10]={data="idk::esperanto::name::string is english or generic",tags=_[13]} +_[9]={data="pierre::french::name::string is french",tags=_[12]} +_[8]={data="bob::name::string is english or generic",tags=_[11]} +_[7]={_[10]} +_[6]={_[9]} +_[5]={_[8]} +_[4]={"return"} +_[3]={"text",_[7]} +_[2]={"text",_[6]} +_[1]={"text",_[5]} +return {_[1],_[2],_[3],_[4]} +--[[ +{ "text", { { + data = "bob::name::string is english or generic", + tags = {} + } } } +{ "text", { { + data = "pierre::french::name::string is french", + tags = {} + } } } +{ "text", { { + data = "idk::esperanto::name::string is english or generic", + tags = {} + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/function decorator scope.lua b/test/tests/function decorator scope.lua index 94aae53..514bf2c 100644 --- a/test/tests/function decorator scope.lua +++ b/test/tests/function decorator scope.lua @@ -1,6 +1,6 @@ local _={} _[5]={} -_[4]={tags=_[5],data="a.\240\159\145\129\239\184\143: 0"} +_[4]={data="a.\240\159\145\129\239\184\143: 0",tags=_[5]} _[3]={_[4]} _[2]={"return"} _[1]={"text",_[3]} diff --git a/test/tests/function name dispatch.ans b/test/tests/function name dispatch.ans new file mode 100644 index 0000000..0f085fe --- /dev/null +++ b/test/tests/function name dispatch.ans @@ -0,0 +1,9 @@ +$ fn(x) + x + +$ fn(a) + a + +~ fn(a=5) + +~ fn(x=5) diff --git a/test/tests/function name dispatch.lua b/test/tests/function name dispatch.lua new file mode 100644 index 0000000..1bea444 --- /dev/null +++ b/test/tests/function name dispatch.lua @@ -0,0 +1,22 @@ +local _={} +_[9]={} +_[8]={} +_[7]={data="x",tags=_[9]} +_[6]={data="a",tags=_[8]} +_[5]={_[7]} +_[4]={_[6]} +_[3]={"return"} +_[2]={"text",_[5]} +_[1]={"text",_[4]} +return {_[1],_[2],_[3]} +--[[ +{ "text", { { + data = "a", + tags = {} + } } } +{ "text", { { + data = "x", + tags = {} + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/function no conflict.ans b/test/tests/function no conflict.ans new file mode 100644 index 0000000..8625326 --- /dev/null +++ b/test/tests/function no conflict.ans @@ -0,0 +1,5 @@ +$ f(a, b) + +$ f(x) + +$ f(u, v) diff --git a/test/tests/function no conflict.lua b/test/tests/function no conflict.lua new file mode 100644 index 0000000..142a8c2 --- /dev/null +++ b/test/tests/function no conflict.lua @@ -0,0 +1,6 @@ +local _={} +_[1]={"return"} +return {_[1]} +--[[ +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/function scope wrong.ans b/test/tests/function scope wrong.ans index 5690400..20a6f65 100644 --- a/test/tests/function scope wrong.ans +++ b/test/tests/function scope wrong.ans @@ -1,4 +1,4 @@ $ a - :5 b + :b = 5 a: {b} diff --git a/test/tests/function scope wrong.lua b/test/tests/function scope wrong.lua index 75f005e..dc19749 100644 --- a/test/tests/function scope wrong.lua +++ b/test/tests/function scope wrong.lua @@ -1,6 +1,6 @@ local _={} -_[1]={"error","unknown identifier \"b\"; at test/tests/function scope wrong.ans:4"} +_[1]={"error","compatible function \"b\" variant not found; at test/tests/function scope wrong.ans:4"} return {_[1]} --[[ -{ "error", 'unknown identifier "b"; at test/tests/function scope wrong.ans:4' } +{ "error", 'compatible function "b" variant not found; at test/tests/function scope wrong.ans:4' } ]]-- \ No newline at end of file diff --git a/test/tests/function scope.ans b/test/tests/function scope.ans index b807156..50350ee 100644 --- a/test/tests/function scope.ans +++ b/test/tests/function scope.ans @@ -1,4 +1,4 @@ $ a - :5 b + :b = 5 a: {a.b} diff --git a/test/tests/function selection.ans b/test/tests/function selection.ans new file mode 100644 index 0000000..2c3c81e --- /dev/null +++ b/test/tests/function selection.ans @@ -0,0 +1,12 @@ +$ a(x::number) + @x + 2 + +$ x + $ a(x::string) + @x + "heh" + + {a("plop")} + + {a(2)} + +~ x diff --git a/test/tests/function selection.lua b/test/tests/function selection.lua new file mode 100644 index 0000000..58f6655 --- /dev/null +++ b/test/tests/function selection.lua @@ -0,0 +1,22 @@ +local _={} +_[9]={} +_[8]={} +_[7]={data="4",tags=_[9]} +_[6]={data="plopheh",tags=_[8]} +_[5]={_[7]} +_[4]={_[6]} +_[3]={"return"} +_[2]={"text",_[5]} +_[1]={"text",_[4]} +return {_[1],_[2],_[3]} +--[[ +{ "text", { { + data = "plopheh", + tags = {} + } } } +{ "text", { { + data = "4", + tags = {} + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/function separate variable from variants.ans b/test/tests/function separate variable from variants.ans new file mode 100644 index 0000000..ff58d35 --- /dev/null +++ b/test/tests/function separate variable from variants.ans @@ -0,0 +1,10 @@ +$ f + :a = 2 + +$ f(x) + :a = 5 + +$ f(b) + :a = 10 + +{f.a} = 2 diff --git a/test/tests/function separate variable from variants.lua b/test/tests/function separate variable from variants.lua new file mode 100644 index 0000000..795296f --- /dev/null +++ b/test/tests/function separate variable from variants.lua @@ -0,0 +1,14 @@ +local _={} +_[5]={} +_[4]={data="2 = 2",tags=_[5]} +_[3]={_[4]} +_[2]={"return"} +_[1]={"text",_[3]} +return {_[1],_[2]} +--[[ +{ "text", { { + data = "2 = 2", + tags = {} + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/function type dispatch ambigous.ans b/test/tests/function type dispatch ambigous.ans new file mode 100644 index 0000000..54eb3b3 --- /dev/null +++ b/test/tests/function type dispatch ambigous.ans @@ -0,0 +1,7 @@ +$ fn(x::number) + x + +$ fn(a::number) + a + +~ fn(5) diff --git a/test/tests/function type dispatch ambigous.lua b/test/tests/function type dispatch ambigous.lua new file mode 100644 index 0000000..8fca88b --- /dev/null +++ b/test/tests/function type dispatch ambigous.lua @@ -0,0 +1,6 @@ +local _={} +_[1]={"error","function call \"fn\" is ambigous; may be at least either:\n\9function type dispatch ambigous.fn(a::number) (at test/tests/function type dispatch ambigous.ans:4)\n\9function type dispatch ambigous.fn(x::number) (at test/tests/function type dispatch ambigous.ans:1); at test/tests/function type dispatch ambigous.ans:7"} +return {_[1]} +--[[ +{ "error", 'function call "fn" is ambigous; may be at least either:\n\tfunction type dispatch ambigous.fn(a::number) (at test/tests/function type dispatch ambigous.ans:4)\n\tfunction type dispatch ambigous.fn(x::number) (at test/tests/function type dispatch ambigous.ans:1); at test/tests/function type dispatch ambigous.ans:7' } +]]-- \ No newline at end of file diff --git a/test/tests/function type dispatch with default.ans b/test/tests/function type dispatch with default.ans new file mode 100644 index 0000000..12762ce --- /dev/null +++ b/test/tests/function type dispatch with default.ans @@ -0,0 +1,26 @@ +$ fn(x::number) + x + +$ fn(a="o"::string) + a + +~ fn("s") + +~ fn(5) + +~ fn() + +$ g(n="s", a=5::number) + @"gn" + +$ g(n="s", a="lol"::string) + @"gs" + +{g(n="k", a="l")} +{g(n="k", a=1)} +{g(n="k", "l")} +{g(n="k", 1)} +{g("k", "l")} +{g("k", 1)} +{g("k", a="l")} +{g("k", a=1)} diff --git a/test/tests/function type dispatch with default.lua b/test/tests/function type dispatch with default.lua new file mode 100644 index 0000000..3c90759 --- /dev/null +++ b/test/tests/function type dispatch with default.lua @@ -0,0 +1,73 @@ +local _={} +_[31]={} +_[30]={} +_[29]={} +_[28]={} +_[27]={} +_[26]={} +_[25]={} +_[24]={} +_[23]={} +_[22]={} +_[21]={} +_[20]={data="gn",tags=_[31]} +_[19]={data="gs",tags=_[30]} +_[18]={data="gn",tags=_[29]} +_[17]={data="gs",tags=_[28]} +_[16]={data="gn",tags=_[27]} +_[15]={data="gs",tags=_[26]} +_[14]={data="gn",tags=_[25]} +_[13]={data="gs",tags=_[24]} +_[12]={data="a",tags=_[23]} +_[11]={data="x",tags=_[22]} +_[10]={data="a",tags=_[21]} +_[9]={_[13],_[14],_[15],_[16],_[17],_[18],_[19],_[20]} +_[8]={_[12]} +_[7]={_[11]} +_[6]={_[10]} +_[5]={"return"} +_[4]={"text",_[9]} +_[3]={"text",_[8]} +_[2]={"text",_[7]} +_[1]={"text",_[6]} +return {_[1],_[2],_[3],_[4],_[5]} +--[[ +{ "text", { { + data = "a", + tags = {} + } } } +{ "text", { { + data = "x", + tags = {} + } } } +{ "text", { { + data = "a", + tags = {} + } } } +{ "text", { { + data = "gs", + tags = {} + }, { + data = "gn", + tags = {} + }, { + data = "gs", + tags = {} + }, { + data = "gn", + tags = {} + }, { + data = "gs", + tags = {} + }, { + data = "gn", + tags = {} + }, { + data = "gs", + tags = {} + }, { + data = "gn", + tags = {} + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/function type dispatch.ans b/test/tests/function type dispatch.ans new file mode 100644 index 0000000..8670a8d --- /dev/null +++ b/test/tests/function type dispatch.ans @@ -0,0 +1,9 @@ +$ fn(x::number) + x + +$ fn(a::string) + a + +~ fn("s") + +~ fn(5) diff --git a/test/tests/function type dispatch.lua b/test/tests/function type dispatch.lua new file mode 100644 index 0000000..1bea444 --- /dev/null +++ b/test/tests/function type dispatch.lua @@ -0,0 +1,22 @@ +local _={} +_[9]={} +_[8]={} +_[7]={data="x",tags=_[9]} +_[6]={data="a",tags=_[8]} +_[5]={_[7]} +_[4]={_[6]} +_[3]={"return"} +_[2]={"text",_[5]} +_[1]={"text",_[4]} +return {_[1],_[2],_[3]} +--[[ +{ "text", { { + data = "a", + tags = {} + } } } +{ "text", { { + data = "x", + tags = {} + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/interrupt callback nested paragraph.ans b/test/tests/interrupt callback nested paragraph.ans index 8f2ced4..05a2002 100644 --- a/test/tests/interrupt callback nested paragraph.ans +++ b/test/tests/interrupt callback nested paragraph.ans @@ -3,7 +3,7 @@ $ oh in interrupt: {bar.var} no $ bar - :5 var + :var = 5 ~ var := 2 diff --git a/test/tests/interrupt callback nested.ans b/test/tests/interrupt callback nested.ans index f2df4fc..cd6f459 100644 --- a/test/tests/interrupt callback nested.ans +++ b/test/tests/interrupt callback nested.ans @@ -4,7 +4,7 @@ $ leave $ oh no $ bar - :5 var + :var = 5 ~ var := 2 diff --git a/test/tests/interrupt callback.ans b/test/tests/interrupt callback.ans index 5f70997..66811e5 100644 --- a/test/tests/interrupt callback.ans +++ b/test/tests/interrupt callback.ans @@ -1,5 +1,5 @@ $ bar - :5 var + :var = 5 ~ var := 2 diff --git a/test/tests/interrupt no callback.ans b/test/tests/interrupt no callback.ans index 6a10192..7bf52f1 100644 --- a/test/tests/interrupt no callback.ans +++ b/test/tests/interrupt no callback.ans @@ -1,5 +1,5 @@ $ bar - :5 var + :var = 5 ~ var := 2 diff --git a/test/tests/lazy boolean operators.ans b/test/tests/lazy boolean operators.ans new file mode 100644 index 0000000..5a945bc --- /dev/null +++ b/test/tests/lazy boolean operators.ans @@ -0,0 +1,23 @@ +$ a + a + @1 + +$ b + b + @0 + +{a & b} = a b 0 + +{b & a} = b 0 + +{a & a} = a a 1 + +{b & b} = b 0 + +{a | b} = a 1 + +{b | a} = b a 1 + +{a | a} = a 1 + +{b | b} = b b 0 diff --git a/test/tests/lazy boolean operators.lua b/test/tests/lazy boolean operators.lua new file mode 100644 index 0000000..cd7d7de --- /dev/null +++ b/test/tests/lazy boolean operators.lua @@ -0,0 +1,130 @@ +local _={} +_[57]={} +_[56]={} +_[55]={} +_[54]={} +_[53]={} +_[52]={} +_[51]={} +_[50]={} +_[49]={} +_[48]={} +_[47]={} +_[46]={} +_[45]={} +_[44]={} +_[43]={} +_[42]={} +_[41]={} +_[40]={} +_[39]={} +_[38]={} +_[37]={data="0 = b b 0",tags=_[57]} +_[36]={data="b",tags=_[56]} +_[35]={data="b",tags=_[55]} +_[34]={data="1 = a 1",tags=_[54]} +_[33]={data="a",tags=_[53]} +_[32]={data="1 = b a 1",tags=_[52]} +_[31]={data="a",tags=_[51]} +_[30]={data="b",tags=_[50]} +_[29]={data="1 = a 1",tags=_[49]} +_[28]={data="a",tags=_[48]} +_[27]={data="0 = b 0",tags=_[47]} +_[26]={data="b",tags=_[46]} +_[25]={data="1 = a a 1",tags=_[45]} +_[24]={data="a",tags=_[44]} +_[23]={data="a",tags=_[43]} +_[22]={data="0 = b 0",tags=_[42]} +_[21]={data="b",tags=_[41]} +_[20]={data="0 = a b 0",tags=_[40]} +_[19]={data="b",tags=_[39]} +_[18]={data="a",tags=_[38]} +_[17]={_[35],_[36],_[37]} +_[16]={_[33],_[34]} +_[15]={_[30],_[31],_[32]} +_[14]={_[28],_[29]} +_[13]={_[26],_[27]} +_[12]={_[23],_[24],_[25]} +_[11]={_[21],_[22]} +_[10]={_[18],_[19],_[20]} +_[9]={"return"} +_[8]={"text",_[17]} +_[7]={"text",_[16]} +_[6]={"text",_[15]} +_[5]={"text",_[14]} +_[4]={"text",_[13]} +_[3]={"text",_[12]} +_[2]={"text",_[11]} +_[1]={"text",_[10]} +return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9]} +--[[ +{ "text", { { + data = "a", + tags = {} + }, { + data = "b", + tags = {} + }, { + data = "0 = a b 0", + tags = {} + } } } +{ "text", { { + data = "b", + tags = {} + }, { + data = "0 = b 0", + tags = {} + } } } +{ "text", { { + data = "a", + tags = {} + }, { + data = "a", + tags = {} + }, { + data = "1 = a a 1", + tags = {} + } } } +{ "text", { { + data = "b", + tags = {} + }, { + data = "0 = b 0", + tags = {} + } } } +{ "text", { { + data = "a", + tags = {} + }, { + data = "1 = a 1", + tags = {} + } } } +{ "text", { { + data = "b", + tags = {} + }, { + data = "a", + tags = {} + }, { + data = "1 = b a 1", + tags = {} + } } } +{ "text", { { + data = "a", + tags = {} + }, { + data = "1 = a 1", + tags = {} + } } } +{ "text", { { + data = "b", + tags = {} + }, { + data = "b", + tags = {} + }, { + data = "0 = b b 0", + tags = {} + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/list assignement.ans b/test/tests/list assignement.ans index 83a202d..c9227b5 100644 --- a/test/tests/list assignement.ans +++ b/test/tests/list assignement.ans @@ -1,4 +1,4 @@ -:[1,2] x +:x = [1,2] {x} diff --git a/test/tests/named arguments.lua b/test/tests/named arguments.lua index c3b4fe8..432acd9 100644 --- a/test/tests/named arguments.lua +++ b/test/tests/named arguments.lua @@ -1,6 +1,6 @@ local _={} _[5]={} -_[4]={tags=_[5],data="abc = abc = abc"} +_[4]={data="abc = abc = abc",tags=_[5]} _[3]={_[4]} _[2]={"return"} _[1]={"text",_[3]} diff --git a/test/tests/named varag.ans b/test/tests/named varag.ans index 51f6a8a..79bab8a 100644 --- a/test/tests/named varag.ans +++ b/test/tests/named varag.ans @@ -1,6 +1,6 @@ $ f(l...) ~ l.len - :0 a + :a = 0 ~ a := l(1) ~ l.remove(1) @a + f(l=l) diff --git a/test/tests/named varag.lua b/test/tests/named varag.lua index ace1c98..4a54948 100644 --- a/test/tests/named varag.lua +++ b/test/tests/named varag.lua @@ -1,6 +1,6 @@ local _={} _[5]={} -_[4]={tags=_[5],data="15"} +_[4]={data="15",tags=_[5]} _[3]={_[4]} _[2]={"return"} _[1]={"text",_[3]} diff --git a/test/tests/optional arguments.lua b/test/tests/optional arguments.lua index c3b4fe8..432acd9 100644 --- a/test/tests/optional arguments.lua +++ b/test/tests/optional arguments.lua @@ -1,6 +1,6 @@ local _={} _[5]={} -_[4]={tags=_[5],data="abc = abc = abc"} +_[4]={data="abc = abc = abc",tags=_[5]} _[3]={_[4]} _[2]={"return"} _[1]={"text",_[3]} diff --git a/test/tests/paragraph alias.lua b/test/tests/paragraph alias.lua index 3adc940..c34ed96 100644 --- a/test/tests/paragraph alias.lua +++ b/test/tests/paragraph alias.lua @@ -1,6 +1,6 @@ local _={} _[5]={} -_[4]={tags=_[5],data="ok = ok"} +_[4]={data="ok = ok",tags=_[5]} _[3]={_[4]} _[2]={"return"} _[1]={"text",_[3]} diff --git a/test/tests/resume from nested paragraph.lua b/test/tests/resume from nested paragraph.lua index a53ccbe..ce5245a 100644 --- a/test/tests/resume from nested paragraph.lua +++ b/test/tests/resume from nested paragraph.lua @@ -17,24 +17,24 @@ _[49]={} _[48]={} _[47]={} _[46]={} -_[45]={tags=_[63],data="c"} -_[44]={tags=_[62],data="a"} -_[43]={tags=_[61],data="Force p checkpoint:"} -_[42]={tags=_[60],data="d"} -_[41]={tags=_[59],data="c"} -_[40]={tags=_[58],data="b"} -_[39]={tags=_[57],data="From q checkpoint again:"} -_[38]={tags=_[56],data="d"} -_[37]={tags=_[55],data="c"} -_[36]={tags=_[54],data="b"} -_[35]={tags=_[53],data="From q checkpoint:"} -_[34]={tags=_[52],data="d"} -_[33]={tags=_[51],data="c"} -_[32]={tags=_[50],data="a"} -_[31]={tags=_[49],data="From p checkpoint:"} -_[30]={tags=_[48],data="d"} -_[29]={tags=_[47],data="x"} -_[28]={tags=_[46],data="From start:"} +_[45]={data="c",tags=_[63]} +_[44]={data="a",tags=_[62]} +_[43]={data="Force p checkpoint:",tags=_[61]} +_[42]={data="d",tags=_[60]} +_[41]={data="c",tags=_[59]} +_[40]={data="b",tags=_[58]} +_[39]={data="From q checkpoint again:",tags=_[57]} +_[38]={data="d",tags=_[56]} +_[37]={data="c",tags=_[55]} +_[36]={data="b",tags=_[54]} +_[35]={data="From q checkpoint:",tags=_[53]} +_[34]={data="d",tags=_[52]} +_[33]={data="c",tags=_[51]} +_[32]={data="a",tags=_[50]} +_[31]={data="From p checkpoint:",tags=_[49]} +_[30]={data="d",tags=_[48]} +_[29]={data="x",tags=_[47]} +_[28]={data="From start:",tags=_[46]} _[27]={_[45]} _[26]={_[43],_[44]} _[25]={_[42]} diff --git a/test/tests/resume from paragraph restore tags.lua b/test/tests/resume from paragraph restore tags.lua index f3540c6..f8b3955 100644 --- a/test/tests/resume from paragraph restore tags.lua +++ b/test/tests/resume from paragraph restore tags.lua @@ -2,18 +2,18 @@ local _={} _[32]={} _[31]={a="a"} _[30]={a="a",b="b"} -_[29]={a="a",c="c",b="b"} +_[29]={a="a",b="b",c="c"} _[28]={} _[27]={a="a",b="b"} _[26]={a="a"} -_[25]={tags=_[32],data="e"} -_[24]={tags=_[31],data="d"} -_[23]={tags=_[30],data="c"} -_[22]={tags=_[29],data="b"} -_[21]={tags=_[28],data="e"} -_[20]={tags=_[26],data="d"} -_[19]={tags=_[27],data="c"} -_[18]={tags=_[26],data="a"} +_[25]={data="e",tags=_[32]} +_[24]={data="d",tags=_[31]} +_[23]={data="c",tags=_[30]} +_[22]={data="b",tags=_[29]} +_[21]={data="e",tags=_[28]} +_[20]={data="d",tags=_[26]} +_[19]={data="c",tags=_[27]} +_[18]={data="a",tags=_[26]} _[17]={_[25]} _[16]={_[24]} _[15]={_[23]} diff --git a/test/tests/resume from paragraph with nested choice.lua b/test/tests/resume from paragraph with nested choice.lua index 5347a8a..8e543e3 100644 --- a/test/tests/resume from paragraph with nested choice.lua +++ b/test/tests/resume from paragraph with nested choice.lua @@ -1,7 +1,4 @@ local _={} -_[121]={} -_[120]={} -_[119]={} _[118]={} _[117]={} _[116]={} @@ -34,15 +31,15 @@ _[90]={} _[89]={} _[88]={} _[87]={} -_[86]={data="c",tags=_[121]} -_[85]={data="b",tags=_[120]} -_[84]={data="a",tags=_[119]} -_[83]={data="-> aa",tags=_[118]} -_[82]={data="ab",tags=_[117]} -_[81]={data="aa",tags=_[116]} -_[80]={data="-> aa",tags=_[115]} -_[79]={data="ab",tags=_[114]} -_[78]={data="aa",tags=_[113]} +_[86]={data="c",tags=_[118]} +_[85]={data="b",tags=_[117]} +_[84]={data="a",tags=_[116]} +_[83]={data="-> aa",tags=_[115]} +_[82]={data="ab",tags=_[114]} +_[81]={data="aa",tags=_[113]} +_[80]={data="-> aa",tags=_[112]} +_[79]={data="ab",tags=_[112]} +_[78]={data="aa",tags=_[112]} _[77]={data="-> a",tags=_[112]} _[76]={data="c",tags=_[111]} _[75]={data="b",tags=_[110]} @@ -222,10 +219,10 @@ return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10],_[11],_[12],_[13],_[1 } } } { "choice", { { data = "aa", - tags = {} + tags = <1>{} }, { data = "ab", - tags = {} + tags =