diff --git a/README.md b/README.md index 479d3e3..f5ed272 100644 --- a/README.md +++ b/README.md @@ -98,16 +98,16 @@ $ main ~ var := 2 - before: {var}=2, because the value has been changed in the current execution context + before: {var}==2, because the value has been changed in the current execution context - (But if we run the script "parallel" in parallel at this point, it will still think var=5) + (But if we run the script "parallel" in parallel at this point, it will still think var==5) § foo But the variable will be merged with the global state on a checkpoint - after: {var}=2, still, as expected + after: {var}==2, still, as expected - (And if we run the script "parallel" in parallel at this point, it will now think var=2) + (And if we run the script "parallel" in parallel at this point, it will now think var==2) $ parallel parallel: {main.var} @@ -150,7 +150,7 @@ There's different types of lines, depending on their first character(s) (after i ~ 0 This is never run. -~~ 1 = 0 +~~ 1 == 0 This neither. ~~ This is. @@ -168,13 +168,14 @@ There's different types of lines, depending on their first character(s) (after i 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. 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 `=`). It is enclosed with paranthesis and contain a comma-separated list of identifiers: ``` -$ f(a, b, c) +$ f(a, b: alias for b, c="default for c", d: alias for d = "default for d") first argument: {a} second argument: {b} third argument: {c} + fourth argument: {d} ``` 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. @@ -274,7 +275,7 @@ $ f @2 (f will return 2 since the choice is run after the @2 line) -~ f = 2 +~ f == 2 Yes. @@ -442,7 +443,7 @@ Var1 in the current namespace = 1: {var1} Var1 in the fn1 namespace = 2: {fn1.var1} (Weird, but valid, and also the reason I'm not talking of scoping:) -~ fn1.var1 = 3 +~ fn1.var1 == 3 ``` #### Aliases @@ -495,7 +496,7 @@ Default types are: * `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`. +* `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. How conversions are handled from Anselme to Lua: @@ -507,7 +508,7 @@ How conversions are handled from Anselme to Lua: * `list` -> `table`. Pair elements in the list will be assigned as a key-value pair in the Lua list and its index skipped in the sequential part, e.g. `[1,2,"key":"value",3]` -> `{1,2,3,key="value"}`. -* `pair` -> `table`, with a signle key-value pair. +* `pair` -> `table`, with a single key-value pair. How conservions are handled from Lua to Anselme: @@ -584,6 +585,49 @@ $ f this is text: {f} ``` +Functions can also have default arguments. Defaults values can be any expression and are re-evaluated each time the function is called: + +``` +$ f(a, b=1) + @a+b + +{f(1)} = 2 + +$ g(a, b=a) + @a+b + +{g(1)} = 2 +{g(2)} = 4 +``` + +Arguments can also be passed by naming them instead of their position. These syntaxes can be mixed: + +``` +$ f(a, b, c) + @a + b + c + +{f(1,2,3)} = {f(c=3,b=2,a=1)} = {f(1,2,c=3)} +``` + +Anselme actually treat argument list are regular lists; named arguments are actually pairs. + +This means that pairs can't be passed directly as arguments to a function (as they will be considered named arguments). If you want to use pairs, always wrap them in a list. + +Functions can have a variable number of arguments. Additional arguments are added in a list: + +``` +$ f(a, b...) + {a} + + {b} + +{f(1, 2, 3, 4, 5)} + +(Will print:) + 1 + [2,3,4,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. @@ -647,7 +691,7 @@ Built-in operators: ##### Comparaison -`a = b`: returns `1` if a and b have the same value (will recursively compare list and pairs), `0` otherwise +`a == b`: returns `1` if a and b have the same value (will recursively compare list and pairs), `0` otherwise `a != b`: returns `1` if a and b do not have the same value, `0` otherwise @@ -685,7 +729,7 @@ 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. +`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. #### Built-in functions diff --git a/anselme.lua b/anselme.lua index ff8360b..3cb3609 100644 --- a/anselme.lua +++ b/anselme.lua @@ -1,7 +1,10 @@ -- anselme module local anselme = { -- version - version = "0.13.1", + -- major.minor.fix + -- saves files are incompatible between major versions + -- scripts files may break between minor versions + version = "0.14.0", --- currently running interpreter running = nil } @@ -399,7 +402,7 @@ return setmetatable(anselme, { functions = { -- [":="] = { -- { - -- arity = {3,42}, type = { [1] = "variable" }, check = function, rewrite = function, vararg = 2, mode = "custom", + -- arity = {3,42}, type = { [1] = "variable" }, check = function, rewrite = function, mode = "custom", -- value = function(state, exp) -- end -- or checkpoint, function, line -- } diff --git a/interpreter/expression.lua b/interpreter/expression.lua index bdbbf0d..1a860af 100644 --- a/interpreter/expression.lua +++ b/interpreter/expression.lua @@ -33,12 +33,13 @@ local function eval(state, exp) elseif exp.type == "parentheses" then return eval(state, exp.expression) -- list parentheses - elseif exp.type == "list_parentheses" then + elseif exp.type == "list_brackets" then if exp.expression then local v, e = eval(state, exp.expression) if not v then return v, e end - if v.type == "list" then + if exp.expression.type == "list" then return v + -- contained a single element, wrap in list manually else return { type = "list", @@ -78,24 +79,12 @@ local function eval(state, exp) if fn.mode == "custom" then return fn.value(state, exp) else - -- eval args: same as list, but only put vararg arguments in a separate list - local l = {} + -- eval args: list_brackets + local args = {} if exp.argument then - local vararg = fn.vararg or math.huge - local i, ast = 1, exp.argument - while ast.type == "list" and i < vararg do - local left, lefte = eval(state, ast.left) - if not left then return left, lefte end - table.insert(l, left) - ast = ast.right - i = i + 1 - end - local right, righte = eval(state, ast) - if not right then return right, righte end - table.insert(l, right) - end - if fn.vararg and #l < fn.vararg then -- empty list vararg - table.insert(l, { type = "list", value = {} }) + local arg, arge = eval(state, exp.argument) + if not arg then return arg, arge end + args = arg.value end -- anselme function if type(fn.value) == "table" then @@ -106,9 +95,40 @@ local function eval(state, exp) return r -- function elseif fn.value.type == "function" then - -- set args + -- 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 + -- get and set args for j, param in ipairs(fn.value.params) do - state.variables[param] = l[j] + local val + -- named + if param.alias and args[param.alias] then + val = args[param.alias] + elseif args[param.name] then + val = args[param.name] + -- vararg + elseif param.vararg then + val = { type = "list", value = {} } + for k=j, #args do + table.insert(val.value, args[k]) + 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 + end + if val then + state.variables[param.full_name] = val + else + return nil, ("missing mandatory argument %q in function %q call"):format(param.name, fn.value.name) + end end -- eval function local r, e @@ -130,12 +150,13 @@ local function eval(state, exp) 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(l)) + return fn.value(unpack(args)) else local l_lua = {} - for _, v in ipairs(l) do + for _, v in ipairs(args) do table.insert(l_lua, to_lua(v)) end local r, e diff --git a/parser/common.lua b/parser/common.lua index 00b4d89..6828cd4 100644 --- a/parser/common.lua +++ b/parser/common.lua @@ -107,6 +107,9 @@ common = { 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 + -- (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 @@ -123,6 +126,7 @@ common = { ok = false end end + -- custom check if ok and variant.check then local s, e = variant.check(state, args) if not s then @@ -131,6 +135,7 @@ common = { 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 @@ -139,6 +144,7 @@ common = { end end end + -- done if ok then if variant.rewrite then local r, e = variant.rewrite(fqm, state, arg, explicit_call) @@ -156,7 +162,11 @@ common = { name = fqm, explicit_call = explicit_call, variant = variant, - argument = arg + 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 + } } end end diff --git a/parser/expression.lua b/parser/expression.lua index 777d5b3..49939b8 100644 --- a/parser/expression.lua +++ b/parser/expression.lua @@ -6,7 +6,7 @@ local binops_prio = { [2] = { ":=", "+=", "-=", "//=", "/=", "*=", "%=", "^=" }, [3] = { "," }, [4] = { "|", "&" }, - [5] = { "!=", "=", ">=", "<=", "<", ">" }, + [5] = { "!=", "==", ">=", "<=", "<", ">" }, [6] = { "+", "-" }, [7] = { "*", "//", "/", "%" }, [8] = {}, -- unary operators @@ -89,7 +89,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn) if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of list parenthesis expression"):format(r_paren) end end return expression(r, state, namespace, currentPriority, { - type = "list_parentheses", + type = "list_brackets", return_type = "list", expression = exp }) @@ -97,6 +97,26 @@ local function expression(s, state, namespace, currentPriority, operatingOn) elseif s:match("^"..identifier_pattern) then local name, r = s:match("^("..identifier_pattern..")(.-)$") name = format_identifier(name) + -- string:value pair shorthand using = + if r:match("^=[^=]") then + local val + val, r = expression(r:match("^=(.*)$"), state, namespace, 9) + 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) + if not variant then return variant, err end + return expression(r, state, namespace, currentPriority, variant) + end -- variables local var, vfqm = find(state.aliases, state.variables, namespace, name) if var then @@ -132,6 +152,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn) 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 end -- find compatible variant @@ -181,6 +202,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn) 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 end -- add first argument diff --git a/parser/postparser.lua b/parser/postparser.lua index 679edad..325dec0 100644 --- a/parser/postparser.lua +++ b/parser/postparser.lua @@ -6,6 +6,21 @@ local parse_text local function parse(state) for _, l in ipairs(state.queued_lines) do local line, namespace = l.line, l.namespace + -- default arguments + if line.type == "function" then + for i, param in ipairs(line.params) do + 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 + end + end + end + end -- expressions if line.expression then local exp, rem = expression(line.expression, state, namespace) diff --git a/parser/preparser.lua b/parser/preparser.lua index 1e20570..c1612c1 100644 --- a/parser/preparser.lua +++ b/parser/preparser.lua @@ -2,6 +2,28 @@ local expression local format_identifier, identifier_pattern local eval +-- try to define an alias using rem, the text that follows the identifier +-- returns true, new_rem, alias_name in case of success +-- returns true, rem in case of no alias and no error +-- returns nil, err in case of alias and error +local function maybe_alias(rem, fqm, namespace, line, state) + local alias + 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 + alias = format_identifier(alias) + -- format alias + local aliasfqm = ("%s%s"):format(namespace, alias) + -- define alias + if state.aliases[aliasfqm] ~= nil and state.aliases[aliasfqm] ~= fqm then + return nil, ("trying to define alias %q for %q, but already exist and refer to %q; at %s"):format(aliasfqm, fqm, state.aliases[aliasfqm], line.source) + end + state.aliases[aliasfqm] = fqm + end + return true, rem, alias +end + -- * ast: if success -- * nil, error: in case of error local function parse_line(line, state, namespace) @@ -9,12 +31,6 @@ local function parse_line(line, state, namespace) local r = { source = line.source } - -- comment - if l:match("^%(") then - r.type = "comment" - r.remove_from_block_ast = true - return r - end -- else-condition & condition if l:match("^~~?") then r.type = l:match("^~~") and "else-condition" or "condition" @@ -42,19 +58,9 @@ local function parse_line(line, state, namespace) -- format identifier local fqm = ("%s%s"):format(namespace, format_identifier(identifier)) -- get alias - if rem:match("^%:") then - local content = rem:sub(2) - local alias - alias, rem = content:match("^("..identifier_pattern..")(.-)$") - if not alias then return nil, ("expected an identifier in alias in checkpoint/function definition line, but got %q; at %s"):format(content, line.source) end - -- format alias - local aliasfqm = ("%s%s"):format(namespace, format_identifier(alias)) - -- define alias - if state.aliases[aliasfqm] ~= nil and state.aliases[aliasfqm] ~= fqm then - return nil, ("trying to define alias %q for checkpoint/function %q, but already exist and refer to %q; at %s"):format(aliasfqm, fqm, state.aliases[aliasfqm], line.source) - end - state.aliases[aliasfqm] = fqm - end + local ok_alias + ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state) + if not ok_alias then return ok_alias, rem end -- get params r.params = {} if r.type == "function" and rem:match("^%b()$") then @@ -63,36 +69,39 @@ local function parse_line(line, state, namespace) -- 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 + param_identifier = format_identifier(param_identifier) -- format identifier - local param_fqm = ("%s.%s"):format(fqm, format_identifier(param_identifier)) + local param_fqm = ("%s.%s"):format(fqm, param_identifier) -- get alias - if param_rem:match("^%:") then - local param_content = param_rem:sub(2) - local alias - alias, param_rem = param_content:match("^("..identifier_pattern..")(.-)$") - if not alias then return nil, ("expected an identifier in alias in parameter, but got %q; at %s"):format(param_content, line.source) end - -- format alias - local aliasfqm = ("%s.%s"):format(fqm, format_identifier(alias)) - -- define alias - if state.aliases[aliasfqm] ~= nil and state.aliases[aliasfqm] ~= param_fqm then - return nil, ("trying to define alias %q for parameter %q, but already exist and refer to %q; at %s"):format(aliasfqm, param_fqm, state.aliases[aliasfqm], line.source) - end - state.aliases[aliasfqm] = param_fqm - end - if param_rem:match("[^%s]") then + local ok_param_alias, param_alias + ok_param_alias, param_rem, param_alias = maybe_alias(param_rem, param_fqm, fqm..".", 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 + 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, param_fqm) + table.insert(r.params, { name = param_identifier, alias = param_alias, full_name = param_fqm, default = default }) end 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 - local arity, vararg = #r.params, nil - if arity > 0 and r.params[arity]:match("%.%.%.$") then -- varargs - r.params[arity] = r.params[arity]:match("^(.*)%.%.%.$") - vararg = arity - arity = { arity-1, math.huge } + -- calculate arity + local minarity, maxarity = #r.params, #r.params + for _, param in ipairs(r.params) do -- params with default values + if param.default then + minarity = minarity - 1 + end + end + if maxarity > 0 and r.params[maxarity].full_name:match("%.%.%.$") then -- varargs + 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 @@ -107,9 +116,8 @@ local function parse_line(line, state, namespace) 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 = arity, + arity = { minarity, maxarity }, types = {}, - vararg = vararg, value = r } -- new function (no overloading yet) @@ -191,13 +199,13 @@ local function parse_line(line, state, namespace) end -- define args and set type check information for i, param in ipairs(r.params) do - if not state.variables[param] then - state.variables[param] = { + if not state.variables[param.full_name] then + state.variables[param.full_name] = { type = "undefined argument", value = { r.variant, i } } - elseif state.variables[param].type ~= "undefined argument" then - r.variant.types[i] = state.variables[param].type + elseif state.variables[param.full_name].type ~= "undefined argument" then + r.variant.types[i] = state.variables[param.full_name].type end end -- definition @@ -208,25 +216,17 @@ local function parse_line(line, state, namespace) 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, rem2 = rem:match("^("..identifier_pattern..")(.-)$") + 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 -- format identifier local fqm = ("%s%s"):format(namespace, format_identifier(identifier)) -- get alias - if rem2:match("^%:") then - local content = rem2:sub(2) - local alias, rem3 = content:match("^("..identifier_pattern..")(.-)$") - if not alias then return nil, ("expected an identifier in alias in definition line, but got %q; at %s"):format(content, line.source) end - if rem3:match("[^%s]") then return nil, ("expected end-of-line after identifier in alias in definition line, but got %q; at %s"):format(rem3, line.source) end - -- format alias - local aliasfqm = ("%s%s"):format(namespace, format_identifier(alias)) - -- define alias - if state.aliases[aliasfqm] ~= nil and state.aliases[aliasfqm] ~= fqm then - return nil, ("trying to define alias %s for variable %s, but already exist and refer to different variable %s; at %s"):format(aliasfqm, fqm, state.aliases[aliasfqm], line.source) - end - state.aliases[aliasfqm] = fqm - elseif rem2:match("[^%s]") then - return nil, ("expected end-of-line after identifier in definition line, but got %q; at %s"):format(rem2, line.source) + 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 -- 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 @@ -297,8 +297,8 @@ local function parse_block(indented, state, namespace, parent_function) -- queue in expression evalution table.insert(state.queued_lines, { namespace = ast.namespace or namespace, line = ast }) - -- indented block (ignore block comments) - if l.children and ast.type ~= "comment" then + -- indented block + if l.children then if not ast.child then return nil, ("line %s (%s) can't have children"):format(ast.source, ast.type) else @@ -317,31 +317,35 @@ local function transform_indented(indented) local i = 1 while i <= #indented do local l = indented[i] - - -- condition decorator - if l.content:match("^.-%s*[^~]%~[^#~$]-$") then - local decorator - l.content, decorator = l.content:match("^(..-)%s*(%~[^#~$]-)$") - indented[i] = { content = decorator, source = l.source, children = { l } } - -- tag decorator - elseif l.content:match("^..-%s*%#[^#~$]-$") then - local decorator - l.content, decorator = l.content:match("^(..-)%s*(%#[^#~$]-)$") - indented[i] = { content = decorator, source = l.source, children = { l } } - -- function decorator - elseif l.content:match("^..-%s*%$[^#~$]-$") then - local name - l.content, name = l.content:match("^(..-)%s*%$([^#~$]-)$") - indented[i] = { content = "~"..name, source = l.source } - table.insert(indented, i+1, { content = "$"..name, source = l.source, children = { l } }) - i = i + 1 -- $ line should not contain any decorator anymore + -- comment + if l.content:match("^%(") then + table.remove(indented, i) else - i = i + 1 -- only increment when no decorator, as there may be several decorators per line - end + -- condition decorator + if l.content:match("^.-%s*[^~]%~[^#~$]-$") then + local decorator + l.content, decorator = l.content:match("^(..-)%s*(%~[^#~$]-)$") + indented[i] = { content = decorator, source = l.source, children = { l } } + -- tag decorator + elseif l.content:match("^..-%s*%#[^#~$]-$") then + local decorator + l.content, decorator = l.content:match("^(..-)%s*(%#[^#~$]-)$") + indented[i] = { content = decorator, source = l.source, children = { l } } + -- function decorator + elseif l.content:match("^..-%s*%$[^#~$]-$") then + local name + l.content, name = l.content:match("^(..-)%s*%$([^#~$]-)$") + indented[i] = { content = "~"..name, source = l.source } + table.insert(indented, i+1, { content = "$"..name, source = l.source, children = { l } }) + i = i + 1 -- $ line should not contain any decorator anymore + else + i = i + 1 -- only increment when no decorator, as there may be several decorators per line + end - -- indented block - if l.children then - transform_indented(l.children) + -- indented block + if l.children then + transform_indented(l.children) + end end end return indented diff --git a/stdlib/functions.lua b/stdlib/functions.lua index f58b3a2..dce5643 100644 --- a/stdlib/functions.lua +++ b/stdlib/functions.lua @@ -53,7 +53,7 @@ functions = { return right.return_type or true end, value = function(state, exp) - local arg = exp.argument + 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 @@ -84,7 +84,7 @@ functions = { { rewrite = rewrite_assignement } }, -- comparaison - ["="] = { + ["=="] = { { arity = 2, return_type = "number", mode = "raw", value = function(a, b) @@ -193,7 +193,7 @@ functions = { { arity = 2, return_type = "number", mode = "custom", value = function(state, exp) - local arg = exp.argument + local arg = exp.argument.expression local left, lefte = eval(state, arg.left) if not left then return left, lefte end if truthy(left) then @@ -217,7 +217,7 @@ functions = { { arity = 2, return_type = "number", mode = "custom", value = function(state, exp) - local arg = exp.argument + local arg = exp.argument.expression local left, lefte = eval(state, arg.left) if not left then return left, lefte end if truthy(left) then @@ -254,6 +254,17 @@ functions = { 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 + end + return { type = "nil", value = nil } + end } }, -- list methods diff --git a/test/tests/argument alias.ans b/test/tests/argument alias.ans new file mode 100644 index 0000000..cebc66b --- /dev/null +++ b/test/tests/argument alias.ans @@ -0,0 +1,4 @@ +$ f(str: foo) + @str + foo + +{f("bi")} = {f(foo="bi")} diff --git a/test/tests/argument alias.lua b/test/tests/argument alias.lua new file mode 100644 index 0000000..482450d --- /dev/null +++ b/test/tests/argument alias.lua @@ -0,0 +1,14 @@ +local _={} +_[5]={} +_[4]={data="bibi = bibi",tags=_[5]} +_[3]={_[4]} +_[2]={"return"} +_[1]={"text",_[3]} +return {_[1],_[2]} +--[[ +{ "text", { { + data = "bibi = bibi", + tags = {} + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/condition else false.ans b/test/tests/condition else false.ans index f077f24..2a1214b 100644 --- a/test/tests/condition else false.ans +++ b/test/tests/condition else false.ans @@ -1,6 +1,6 @@ :5 a -~ a = 2 +~ a == 2 ko ~~ ok diff --git a/test/tests/condition else true.ans b/test/tests/condition else true.ans index 9ce66b1..3a9f0a1 100644 --- a/test/tests/condition else true.ans +++ b/test/tests/condition else true.ans @@ -1,6 +1,6 @@ :5 a -~ a = 5 +~ a == 5 ok ~~ ko diff --git a/test/tests/condition elseif false.ans b/test/tests/condition elseif false.ans index 90935ff..6549d9f 100644 --- a/test/tests/condition elseif false.ans +++ b/test/tests/condition elseif false.ans @@ -1,6 +1,6 @@ :5 a -~ a = 2 +~ a == 2 ko ~~ 0 ko diff --git a/test/tests/condition elseif true.ans b/test/tests/condition elseif true.ans index 21aa5d2..bf2776c 100644 --- a/test/tests/condition elseif true.ans +++ b/test/tests/condition elseif true.ans @@ -1,6 +1,6 @@ :5 a -~ a = 2 +~ a == 2 ko ~~ 1 ok diff --git a/test/tests/condition false.ans b/test/tests/condition false.ans index 99e0fbb..05065b3 100644 --- a/test/tests/condition false.ans +++ b/test/tests/condition false.ans @@ -1,4 +1,4 @@ :5 a -~ a = 2 +~ a == 2 ko diff --git a/test/tests/condition true.ans b/test/tests/condition true.ans index 6db207d..28cb238 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/equality operator.ans b/test/tests/equality operator.ans index dec2d76..9586dca 100644 --- a/test/tests/equality operator.ans +++ b/test/tests/equality operator.ans @@ -1,19 +1,19 @@ :(1:2) a -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 -0 = {b = a} +0 = {b == a} -0 = {b = []} +0 = {b == []} -0 = {b = [3,1,2]} +0 = {b == [3,1,2]} -0 = {b = [1,2,3,4]} +0 = {b == [1,2,3,4]} -1 = {b = [1,2,3]} +1 = {b == [1,2,3]} diff --git a/test/tests/function alias.ans b/test/tests/function alias.ans new file mode 100644 index 0000000..3148d5f --- /dev/null +++ b/test/tests/function alias.ans @@ -0,0 +1,9 @@ +$ f : test + @"ok" + +{f} = {test} + +$ g : bis(a) + @a + +{g("ye")} = {bis("ye")} diff --git a/test/tests/function alias.lua b/test/tests/function alias.lua new file mode 100644 index 0000000..69c1089 --- /dev/null +++ b/test/tests/function alias.lua @@ -0,0 +1,22 @@ +local _={} +_[9]={} +_[8]={} +_[7]={data="ye = ye",tags=_[9]} +_[6]={data="ok = ok",tags=_[8]} +_[5]={_[7]} +_[4]={_[6]} +_[3]={"return"} +_[2]={"text",_[5]} +_[1]={"text",_[4]} +return {_[1],_[2],_[3]} +--[[ +{ "text", { { + data = "ok = ok", + tags = {} + } } } +{ "text", { { + data = "ye = ye", + tags = {} + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/named arguments.ans b/test/tests/named arguments.ans new file mode 100644 index 0000000..d819f40 --- /dev/null +++ b/test/tests/named arguments.ans @@ -0,0 +1,4 @@ +$ f(a, b, c) + @a + b + c + +{f("a", "b", "c")} = {f(a="a", b="b", c="c")} = {f(c="c", a="a", b="b")} diff --git a/test/tests/named arguments.lua b/test/tests/named arguments.lua new file mode 100644 index 0000000..c3b4fe8 --- /dev/null +++ b/test/tests/named arguments.lua @@ -0,0 +1,14 @@ +local _={} +_[5]={} +_[4]={tags=_[5],data="abc = abc = abc"} +_[3]={_[4]} +_[2]={"return"} +_[1]={"text",_[3]} +return {_[1],_[2]} +--[[ +{ "text", { { + data = "abc = abc = abc", + tags = {} + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/optional arguments.ans b/test/tests/optional arguments.ans new file mode 100644 index 0000000..770b906 --- /dev/null +++ b/test/tests/optional arguments.ans @@ -0,0 +1,4 @@ +$ f(a, b, c="c") + @a + b + c + +{f("a", "b")} = {f("a", "b", "c")} = {f(b="b", a="a")} diff --git a/test/tests/optional arguments.lua b/test/tests/optional arguments.lua new file mode 100644 index 0000000..c3b4fe8 --- /dev/null +++ b/test/tests/optional arguments.lua @@ -0,0 +1,14 @@ +local _={} +_[5]={} +_[4]={tags=_[5],data="abc = abc = abc"} +_[3]={_[4]} +_[2]={"return"} +_[1]={"text",_[3]} +return {_[1],_[2]} +--[[ +{ "text", { { + data = "abc = abc = abc", + tags = {} + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/paragraph alias.ans b/test/tests/paragraph alias.ans new file mode 100644 index 0000000..f18958d --- /dev/null +++ b/test/tests/paragraph alias.ans @@ -0,0 +1,4 @@ +§ f : test + @"ok" + +{f} = {test} diff --git a/test/tests/paragraph alias.lua b/test/tests/paragraph alias.lua new file mode 100644 index 0000000..3adc940 --- /dev/null +++ b/test/tests/paragraph alias.lua @@ -0,0 +1,14 @@ +local _={} +_[5]={} +_[4]={tags=_[5],data="ok = ok"} +_[3]={_[4]} +_[2]={"return"} +_[1]={"text",_[3]} +return {_[1],_[2]} +--[[ +{ "text", { { + data = "ok = ok", + tags = {} + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/return in choice.ans b/test/tests/return in choice.ans index 1c8d28c..3aa7b6d 100644 --- a/test/tests/return in choice.ans +++ b/test/tests/return in choice.ans @@ -5,6 +5,6 @@ $ f y @2 -~ f = 2 +~ f == 2 ~ choose(1) Yes. diff --git a/test/tests/variable alias.ans b/test/tests/variable alias.ans new file mode 100644 index 0000000..797383c --- /dev/null +++ b/test/tests/variable alias.ans @@ -0,0 +1,3 @@ +:42 a : b + +{a} = {b} diff --git a/test/tests/variable alias.lua b/test/tests/variable alias.lua new file mode 100644 index 0000000..3a434a7 --- /dev/null +++ b/test/tests/variable alias.lua @@ -0,0 +1,14 @@ +local _={} +_[5]={} +_[4]={data="42 = 42",tags=_[5]} +_[3]={_[4]} +_[2]={"return"} +_[1]={"text",_[3]} +return {_[1],_[2]} +--[[ +{ "text", { { + data = "42 = 42", + tags = {} + } } } +{ "return" } +]]-- \ No newline at end of file