diff --git a/interpreter/expression.lua b/interpreter/expression.lua index ca442ff..e56d2dd 100644 --- a/interpreter/expression.lua +++ b/interpreter/expression.lua @@ -183,10 +183,23 @@ local function eval(state, exp) value = exp.names } elseif exp.type == "variable reference" then - return { - type = "variable reference", - value = exp.name - } + -- check if variable is already a reference + local v, e = eval(state, exp.expression) + if not v then return nil, e end + if v.type == "function reference" or v.type == "variable reference" then + return v + else + return { type = "variable reference", value = exp.name } + end + elseif exp.type == "implicit call if reference" then + local v, e = eval(state, exp.expression) + if not v then return nil, e end + if v.type == "function reference" or v.type == "variable reference" then + exp.variant.argument.expression.value = v + return eval(state, exp.variant) + else + return v + end -- function elseif exp.type == "function call" then -- eval args: map_brackets @@ -529,6 +542,9 @@ local function eval(state, exp) type = "event buffer", value = l } + -- pass the value along (internal type, used for variable reference implicit calls) + elseif exp.type == "value passthrough" then + return exp.value else return nil, ("unknown expression %q"):format(tostring(exp.type)) end diff --git a/parser/common.lua b/parser/common.lua index 6353615..ab3a6ff 100644 --- a/parser/common.lua +++ b/parser/common.lua @@ -267,6 +267,11 @@ common = { -- returns directly a function expression in case of success -- return nil, err otherwise find_function = function(state, namespace, name, arg, paren_call, implicit_call) + local l = common.find_all(state.aliases, state.functions, namespace, name) + return common.find_function_from_list(state, namespace, name, l, arg, paren_call, implicit_call) + end, + --- same as find_function, but take a list of already found ffqm instead of searching + find_function_from_list = function(state, namespace, name, names, arg, paren_call, implicit_call) local variants = {} local err = ("compatible function %q variant not found"):format(name) local l = common.find_all(state.aliases, state.functions, namespace, name) diff --git a/parser/expression.lua b/parser/expression.lua index 52a6633..0fb52dd 100644 --- a/parser/expression.lua +++ b/parser/expression.lua @@ -1,4 +1,4 @@ -local identifier_pattern, format_identifier, find, escape, find_function, parse_text, find_all, split +local identifier_pattern, format_identifier, find, escape, find_function, parse_text, find_all, split, find_function_from_list --- binop priority local binops_prio = { @@ -14,9 +14,12 @@ local binops_prio = { [10] = { "::" }, [11] = {}, -- unary operators [12] = { "^" }, - [13] = { ".", "!" }, - [14] = {} + [13] = { "!" }, + [14] = {}, + [15] = { "." } } +local call_priority = 13 -- note: higher priority operators will have to deal with potential functions expressions +local implicit_call_priority = 12.5 -- just below call priority so explicit calls automatically take precedence local pair_priority = 5 local implicit_multiply_priority = 9.5 -- just above / so 1/2x gives 1/(2x) -- unop priority @@ -34,7 +37,8 @@ local prefix_unops_prio = { [11] = { "-", "!" }, [12] = {}, [13] = {}, - [14] = { "&" } + [14] = { "&" }, + [15] = {} } local suffix_unops_prio = { [1] = { ";" }, @@ -50,7 +54,8 @@ local suffix_unops_prio = { [11] = {}, [12] = {}, [13] = { "!" }, - [14] = {} + [14] = {}, + [15] = {} } local function get_text_in_litteral(s, start_pos) @@ -185,27 +190,22 @@ local function expression(s, state, namespace, current_priority, operating_on) }) end end - -- function call - local args, paren_call, implicit_call - if r:match("^%b()") then - paren_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 map"):format(err) end + -- functions. This is a temporary expression that will either be transformed into a reference by the &_ operator, or an (implicit) function call otherwise. + for i=#nl, 1, -1 do + local name_prefix = table.concat(nl, ".", 1, i) + local lfnqm = find_all(state.aliases, state.functions, namespace, name_prefix) + if #lfnqm > 0 then + if i < #nl then + r = "."..table.concat(nl, ".", i+1, #nl)..r + end + return expression(r, state, namespace, current_priority, { + type = "potential function", + called_name = name, + names = lfnqm + }) end - else -- implicit call; will be changed if there happens to be a ! after in the suffix operator code - implicit_call = true end - -- find compatible variant - local variant, err = find_function(state, namespace, name, args, paren_call, implicit_call) - if not variant then return variant, err end - return expression(r, state, namespace, current_priority, variant) + return nil, ("can't find function or variable named %q"):format(name) end -- prefix unops for prio, oplist in ipairs(prefix_unops_prio) do @@ -214,38 +214,26 @@ local function expression(s, state, namespace, current_priority, operating_on) if s:match("^"..escaped) then local sright = s:match("^"..escaped.."(.*)$") -- function and variable reference - if op == "&" and sright:match("^"..identifier_pattern) then - local name, r = sright:match("^("..identifier_pattern..")(.-)$") - name = format_identifier(name) - -- get all functions this name can reference - -- try prefixes until we find a valid function or variable name - local nl = split(name) - for i=#nl, 1, -1 do - local name_prefix = table.concat(nl, ".", 1, i) - -- variable ref - local var, vfqm = find(state.aliases, state.variables, namespace, name_prefix) - if var then - if i < #nl then - r = "."..table.concat(nl, ".", i+1, #nl)..r - end - return expression(r, state, namespace, current_priority, { - type = "variable reference", - name = vfqm - }) - end - -- function ref - local lfnqm = find_all(state.aliases, state.functions, namespace, name_prefix) - if #lfnqm > 0 then - if i < #nl then - r = "."..table.concat(nl, ".", i+1, #nl)..r - end - return expression(r, state, namespace, current_priority, { - type = "function reference", - names = lfnqm - }) - end + if op == "&" then + local right, r = expression(sright, state, namespace, prio) + if not right then return nil, ("invalid expression after unop %q: %s"):format(op, r) end + if right.type == "potential function" then + return expression(r, state, namespace, current_priority, { + type = "function reference", + names = right.names + }) + elseif right.type == "variable" then + return expression(r, state, namespace, current_priority, { + type = "variable reference", + name = right.name, + expression = right + }) + else + -- find variant + local variant, err = find_function(state, namespace, op.."_", right, true) + if not variant then return variant, err end + return expression(r, state, namespace, current_priority, variant) end - return nil, ("can't find function %q to reference"):format(name) -- normal prefix unop else local right, r = expression(sright, state, namespace, prio) @@ -260,9 +248,55 @@ local function expression(s, state, namespace, current_priority, operating_on) end return nil, ("no valid expression before %q"):format(s) else + -- transform potential function/variable calls into actual calls automatically + -- need to do this before every other operator, since once the code finds the next operator it won't go back to check if this applied and assume it + -- didn't skip anything since it didn't see any other operator before, even if it's actually higher priority... + -- the problems of an implicit operator I guess + if implicit_call_priority > current_priority then + -- implicit call of a function. Unlike for variables, can't be cancelled since there's not any other value this could return, we don't + -- have first class functions here... + if operating_on.type == "potential function" then + local args, paren_call, implicit_call + local r = s + if r:match("^%b()") then + paren_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 + else -- implicit call; will be changed if there happens to be a ! after in the suffix operator code + implicit_call = true + end + -- find compatible variant + local variant, err = find_function_from_list(state, namespace, operating_on.called_name, operating_on.names, args, paren_call, implicit_call) + if not variant then return variant, err end + return expression(r, state, namespace, current_priority, variant) + -- implicit call on variable reference. Might be canceled afterwards due to finding a higher priority operator. + elseif operating_on.type == "variable" or (operating_on.type == "function call" and operating_on.called_name == "_._") then + local implicit_call_variant, err = find_function(state, namespace, "_!", { type = "value passthrough" }, false, true) + if not implicit_call_variant then return implicit_call_variant, err end + return expression(s, state, namespace, current_priority, { + type = "implicit call if reference", + variant = implicit_call_variant, + expression = operating_on + }) + end + end -- binop for prio, oplist in ipairs(binops_prio) do if prio > current_priority then + -- cancel implicit call operator if we are handling a binop of higher priority + -- see comment a bit above on why the priority handling is stupid for implicit operators + local operating_on = operating_on + if prio > implicit_call_priority and operating_on.type == "implicit call if reference" then + operating_on = operating_on.expression + end for _, op in ipairs(oplist) do local escaped = escape(op) if s:match("^"..escaped) then @@ -337,6 +371,10 @@ local function expression(s, state, namespace, current_priority, operating_on) }) -- special binops elseif op == ":=" or op == "+=" or op == "-=" or op == "//=" or op == "/=" or op == "*=" or op == "%=" or op == "^=" then + -- cancel implicit call on right variable + if operating_on.type == "implicit call if reference" then + operating_on = operating_on.expression + end -- rewrite assignment + arithmetic operators into a normal assignment if op ~= ":=" then local args = { @@ -398,6 +436,12 @@ local function expression(s, state, namespace, current_priority, operating_on) -- suffix unop for prio, oplist in ipairs(suffix_unops_prio) do if prio > current_priority then + -- cancel implit call operator if we are handling an operator of higher priority + -- see comment a bit above on why the priority handling is stupid for implicit operators + local operating_on = operating_on + if prio > implicit_call_priority and operating_on.type == "implicit call if reference" then + operating_on = operating_on.expression + end for _, op in ipairs(oplist) do local escaped = escape(op) if s:match("^"..escaped) then @@ -417,7 +461,10 @@ local function expression(s, state, namespace, current_priority, operating_on) end end -- index / call - if s:match("^%b()") then + if call_priority > current_priority and s:match("^%b()") then + if operating_on.type == "implicit call if reference" then + operating_on = operating_on.expression -- replaced with current call + end local args = operating_on local content, r = s:match("^(%b())(.*)$") content = content:gsub("^%(", ""):gsub("%)$", "") @@ -455,6 +502,6 @@ end package.loaded[...] = expression local common = require((...):gsub("expression$", "common")) -identifier_pattern, format_identifier, find, escape, find_function, parse_text, find_all, split = common.identifier_pattern, common.format_identifier, common.find, common.escape, common.find_function, common.parse_text, common.find_all, common.split +identifier_pattern, format_identifier, find, escape, find_function, parse_text, find_all, split, find_function_from_list = common.identifier_pattern, common.format_identifier, common.find, common.escape, common.find_function, common.parse_text, common.find_all, common.split, common.find_function_from_list return expression diff --git a/stdlib/functions.lua b/stdlib/functions.lua index 8b86900..40d259d 100644 --- a/stdlib/functions.lua +++ b/stdlib/functions.lua @@ -244,6 +244,14 @@ lua_functions = { return get_variable(anselme.running.state, v.value) end }, + ["&_(v::variable reference)"] = { + mode = "unannotated raw", + value = function(v) return v end + }, + ["&_(fn::function reference)"] = { + mode = "unannotated raw", + value = function(v) return v end + }, -- format ["{}(v)"] = { mode = "raw", diff --git a/test/tests/function reference dot operator function.ans b/test/tests/function reference dot operator function.ans index 37b1578..f59f9f3 100644 --- a/test/tests/function reference dot operator function.ans +++ b/test/tests/function reference dot operator function.ans @@ -4,6 +4,8 @@ $ f :x = &f +{&x.a} + {x.a} {x.a!} diff --git a/test/tests/function reference dot operator function.lua b/test/tests/function reference dot operator function.lua index 22c93c5..d051424 100644 --- a/test/tests/function reference dot operator function.lua +++ b/test/tests/function reference dot operator function.lua @@ -1,14 +1,18 @@ local _={} -_[9]={} -_[8]={} -_[7]={tags=_[9],text="12"} -_[6]={tags=_[8],text="&function reference dot operator function.f.a"} -_[5]={_[7]} -_[4]={_[6]} -_[3]={"return"} -_[2]={"text",_[5]} -_[1]={"text",_[4]} -return {_[1],_[2],_[3]} +_[13]={} +_[12]={} +_[11]={} +_[10]={tags=_[13],text="12"} +_[9]={tags=_[12],text="12"} +_[8]={tags=_[11],text="&function reference dot operator function.f.a"} +_[7]={_[10]} +_[6]={_[9]} +_[5]={_[8]} +_[4]={"return"} +_[3]={"text",_[7]} +_[2]={"text",_[6]} +_[1]={"text",_[5]} +return {_[1],_[2],_[3],_[4]} --[[ { "text", { { tags = {}, @@ -18,5 +22,9 @@ return {_[1],_[2],_[3]} tags = {}, text = "12" } } } +{ "text", { { + tags = {}, + text = "12" + } } } { "return" } ]]-- \ No newline at end of file diff --git a/test/tests/function scope wrong.lua b/test/tests/function scope wrong.lua index dc19749..488a4bf 100644 --- a/test/tests/function scope wrong.lua +++ b/test/tests/function scope wrong.lua @@ -1,6 +1,6 @@ local _={} -_[1]={"error","compatible function \"b\" variant not found; at test/tests/function scope wrong.ans:4"} +_[1]={"error","can't find function or variable named \"b\"; at test/tests/function scope wrong.ans:4"} return {_[1]} --[[ -{ "error", 'compatible function "b" variant not found; at test/tests/function scope wrong.ans:4' } +{ "error", "can't find function or variable named \"b\"; at test/tests/function scope wrong.ans:4" } ]]-- \ No newline at end of file diff --git a/test/tests/implicit call of references.ans b/test/tests/implicit call of references.ans new file mode 100644 index 0000000..f6590fb --- /dev/null +++ b/test/tests/implicit call of references.ans @@ -0,0 +1,61 @@ +Function with argument: + +$ f(x) + +{&f} + +Function without argument: + +$ a + lol + $ b + hihi + +a: {a} + +&a: {&a} + +:ref = &a + +&ref: {&ref} + +&&&ref: {&&&ref} + +ref: {ref} + +ref.b: {ref.b} + +ref.b!: {ref.b!} + +ref.b(): {ref.b()} + +&ref.b: {&ref.b} + +&ref.b!: {&ref.b!} + +&ref.b(): {&ref.b()} + +&ref!: {&ref!} + +Objects: + +% A + $ b + KK + @1 + +&A: {&A} + +:ref A = &A + +ref A: {ref A} + +&ref A: {&ref A} + +A.b: {A.b} + +&A.b: {&A.b} + +\(&A).b: {(&A).b} + +&(&A).b: {&(&A).b} diff --git a/test/tests/implicit call of references.lua b/test/tests/implicit call of references.lua new file mode 100644 index 0000000..6e4716c --- /dev/null +++ b/test/tests/implicit call of references.lua @@ -0,0 +1,285 @@ +local _={} +_[131]={} +_[130]={} +_[129]={} +_[128]={} +_[127]={} +_[126]={} +_[125]={} +_[124]={} +_[123]={} +_[122]={} +_[121]={} +_[120]={} +_[119]={} +_[118]={} +_[117]={} +_[116]={} +_[115]={} +_[114]={} +_[113]={} +_[112]={} +_[111]={} +_[110]={} +_[109]={} +_[108]={} +_[107]={} +_[106]={} +_[105]={} +_[104]={} +_[103]={} +_[102]={} +_[101]={} +_[100]={} +_[99]={} +_[98]={} +_[97]={} +_[96]={} +_[95]={} +_[94]={} +_[93]={} +_[92]={} +_[91]={} +_[90]={} +_[89]={tags=_[131],text="&implicit call of references.A.b"} +_[88]={tags=_[130],text="&(&A).b: "} +_[87]={tags=_[129],text="KK"} +_[86]={tags=_[128],text="(&A).b: "} +_[85]={tags=_[127],text="&implicit call of references.A.b"} +_[84]={tags=_[126],text="&A.b: "} +_[83]={tags=_[125],text="KK"} +_[82]={tags=_[124],text="A.b: "} +_[81]={tags=_[123],text="&implicit call of references.A"} +_[80]={tags=_[122],text="&ref A: "} +_[79]={tags=_[121],text="1"} +_[78]={tags=_[120],text="ref A: "} +_[77]={tags=_[119],text="&implicit call of references.A"} +_[76]={tags=_[118],text="&A: "} +_[75]={tags=_[117],text="Objects:"} +_[74]={tags=_[116],text="lol"} +_[73]={tags=_[115],text="&ref!: "} +_[72]={tags=_[114],text="hihi"} +_[71]={tags=_[113],text="&ref.b(): "} +_[70]={tags=_[112],text="hihi"} +_[69]={tags=_[111],text="&ref.b!: "} +_[68]={tags=_[110],text="&implicit call of references.a.b"} +_[67]={tags=_[109],text="&ref.b: "} +_[66]={tags=_[108],text="hihi"} +_[65]={tags=_[107],text="ref.b(): "} +_[64]={tags=_[106],text="hihi"} +_[63]={tags=_[105],text="ref.b!: "} +_[62]={tags=_[104],text="hihi"} +_[61]={tags=_[103],text="ref.b: "} +_[60]={tags=_[102],text="lol"} +_[59]={tags=_[101],text="ref: "} +_[58]={tags=_[100],text="&implicit call of references.a"} +_[57]={tags=_[99],text="&&&ref: "} +_[56]={tags=_[98],text="&implicit call of references.a"} +_[55]={tags=_[97],text="&ref: "} +_[54]={tags=_[96],text="&implicit call of references.a"} +_[53]={tags=_[95],text="&a: "} +_[52]={tags=_[94],text="lol"} +_[51]={tags=_[93],text="a: "} +_[50]={tags=_[92],text="Function without argument:"} +_[49]={tags=_[91],text="&implicit call of references.f"} +_[48]={tags=_[90],text="Function with argument:"} +_[47]={_[88],_[89]} +_[46]={_[86],_[87]} +_[45]={_[84],_[85]} +_[44]={_[82],_[83]} +_[43]={_[80],_[81]} +_[42]={_[78],_[79]} +_[41]={_[76],_[77]} +_[40]={_[75]} +_[39]={_[73],_[74]} +_[38]={_[71],_[72]} +_[37]={_[69],_[70]} +_[36]={_[67],_[68]} +_[35]={_[65],_[66]} +_[34]={_[63],_[64]} +_[33]={_[61],_[62]} +_[32]={_[59],_[60]} +_[31]={_[57],_[58]} +_[30]={_[55],_[56]} +_[29]={_[53],_[54]} +_[28]={_[51],_[52]} +_[27]={_[50]} +_[26]={_[49]} +_[25]={_[48]} +_[24]={"return"} +_[23]={"text",_[47]} +_[22]={"text",_[46]} +_[21]={"text",_[45]} +_[20]={"text",_[44]} +_[19]={"text",_[43]} +_[18]={"text",_[42]} +_[17]={"text",_[41]} +_[16]={"text",_[40]} +_[15]={"text",_[39]} +_[14]={"text",_[38]} +_[13]={"text",_[37]} +_[12]={"text",_[36]} +_[11]={"text",_[35]} +_[10]={"text",_[34]} +_[9]={"text",_[33]} +_[8]={"text",_[32]} +_[7]={"text",_[31]} +_[6]={"text",_[30]} +_[5]={"text",_[29]} +_[4]={"text",_[28]} +_[3]={"text",_[27]} +_[2]={"text",_[26]} +_[1]={"text",_[25]} +return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10],_[11],_[12],_[13],_[14],_[15],_[16],_[17],_[18],_[19],_[20],_[21],_[22],_[23],_[24]} +--[[ +{ "text", { { + tags = {}, + text = "Function with argument:" + } } } +{ "text", { { + tags = {}, + text = "&implicit call of references.f" + } } } +{ "text", { { + tags = {}, + text = "Function without argument:" + } } } +{ "text", { { + tags = {}, + text = "a: " + }, { + tags = {}, + text = "lol" + } } } +{ "text", { { + tags = {}, + text = "&a: " + }, { + tags = {}, + text = "&implicit call of references.a" + } } } +{ "text", { { + tags = {}, + text = "&ref: " + }, { + tags = {}, + text = "&implicit call of references.a" + } } } +{ "text", { { + tags = {}, + text = "&&&ref: " + }, { + tags = {}, + text = "&implicit call of references.a" + } } } +{ "text", { { + tags = {}, + text = "ref: " + }, { + tags = {}, + text = "lol" + } } } +{ "text", { { + tags = {}, + text = "ref.b: " + }, { + tags = {}, + text = "hihi" + } } } +{ "text", { { + tags = {}, + text = "ref.b!: " + }, { + tags = {}, + text = "hihi" + } } } +{ "text", { { + tags = {}, + text = "ref.b(): " + }, { + tags = {}, + text = "hihi" + } } } +{ "text", { { + tags = {}, + text = "&ref.b: " + }, { + tags = {}, + text = "&implicit call of references.a.b" + } } } +{ "text", { { + tags = {}, + text = "&ref.b!: " + }, { + tags = {}, + text = "hihi" + } } } +{ "text", { { + tags = {}, + text = "&ref.b(): " + }, { + tags = {}, + text = "hihi" + } } } +{ "text", { { + tags = {}, + text = "&ref!: " + }, { + tags = {}, + text = "lol" + } } } +{ "text", { { + tags = {}, + text = "Objects:" + } } } +{ "text", { { + tags = {}, + text = "&A: " + }, { + tags = {}, + text = "&implicit call of references.A" + } } } +{ "text", { { + tags = {}, + text = "ref A: " + }, { + tags = {}, + text = "1" + } } } +{ "text", { { + tags = {}, + text = "&ref A: " + }, { + tags = {}, + text = "&implicit call of references.A" + } } } +{ "text", { { + tags = {}, + text = "A.b: " + }, { + tags = {}, + text = "KK" + } } } +{ "text", { { + tags = {}, + text = "&A.b: " + }, { + tags = {}, + text = "&implicit call of references.A.b" + } } } +{ "text", { { + tags = {}, + text = "(&A).b: " + }, { + tags = {}, + text = "KK" + } } } +{ "text", { { + tags = {}, + text = "&(&A).b: " + }, { + tags = {}, + text = "&implicit call of references.A.b" + } } } +{ "return" } +]]-- \ No newline at end of file