From ddc9f607f46b01dfcdc2e6226d00ff7895cc0456 Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Mon, 19 Aug 2024 14:23:34 -0300 Subject: [PATCH] parser: lookahead function arguments to avoid ambiguity With a 3-token lookahead we can parse function arguments with multiple returns in the middle of an argument list. Fixes #780. --- spec/declaration/global_function_spec.lua | 9 ++------- spec/declaration/local_function_spec.lua | 11 +++-------- tl.lua | 18 ++++++++++-------- tl.tl | 18 ++++++++++-------- 4 files changed, 25 insertions(+), 31 deletions(-) diff --git a/spec/declaration/global_function_spec.lua b/spec/declaration/global_function_spec.lua index 6496309f3..b5a89be85 100644 --- a/spec/declaration/global_function_spec.lua +++ b/spec/declaration/global_function_spec.lua @@ -131,7 +131,7 @@ describe("global function", function() ]])) describe("with function arguments", function() - it("has ambiguity without parentheses in function type return", mode.check_syntax_error([[ + it("has no ambiguity without parentheses in function type return", mode.check([[ ]] .. mode.fn .. [[ map(f: function(a):b, xs: {a}): {b} local r = {} for i, x in ipairs(xs) do @@ -144,12 +144,7 @@ describe("global function", function() end print(table.concat(map(quoted, {"red", "green", "blue"}), ", ")) - ]], { - { y = 1, x = 47 + #mode.fn, msg = "syntax error" }, - { y = 1 }, - { y = 1 }, - { y = 1 }, - })) + ]])) it("has no ambiguity with parentheses in function type return", mode.check([[ ]] .. mode.fn .. [[ map(f: function(a):(b), xs: {a}): {b} diff --git a/spec/declaration/local_function_spec.lua b/spec/declaration/local_function_spec.lua index 49bda904f..b953747d0 100644 --- a/spec/declaration/local_function_spec.lua +++ b/spec/declaration/local_function_spec.lua @@ -169,8 +169,8 @@ describe("local function", function() ]])) describe("with function arguments", function() - it("has ambiguity without parentheses in function type return", util.check_syntax_error([[ - local function map(f: function(a):b, xs: {a}): {b} + it("has no ambiguity without parentheses in function type return", util.check([[ + local function map(f: function(a):b, xs: {a}): {b} local r = {} for i, x in ipairs(xs) do r[i] = f(x) @@ -182,12 +182,7 @@ describe("local function", function() end print(table.concat(map(quoted, {"red", "green", "blue"}), ", ")) - ]], { - { y = 1, x = 49, msg = "syntax error" }, - { y = 1 }, - { y = 1 }, - { y = 1 }, - })) + ]])) it("has no ambiguity with parentheses in function type return", util.check([[ local function map(f: function(a):(b), xs: {a}): {b} diff --git a/tl.lua b/tl.lua index 5078b2690..3863e8679 100644 --- a/tl.lua +++ b/tl.lua @@ -1642,7 +1642,7 @@ local function parse_table_literal(ps, i) return parse_bracket_list(ps, i, node, "{", "}", "term", parse_table_item) end -local function parse_trying_list(ps, i, list, parse_item) +local function parse_trying_list(ps, i, list, parse_item, ret_lookahead) local try_ps = { filename = ps.filename, tokens = ps.tokens, @@ -1658,12 +1658,14 @@ local function parse_trying_list(ps, i, list, parse_item) end i = tryi table.insert(list, item) - if ps.tokens[i].tk == "," then - while ps.tokens[i].tk == "," do - i = i + 1 - i, item = parse_item(ps, i) - table.insert(list, item) - end + while ps.tokens[i].tk == "," and + (not ret_lookahead or + (not (ps.tokens[i + 1].kind == "identifier" and + ps.tokens[i + 2] and ps.tokens[i + 2].tk == ":"))) do + + i = i + 1 + i, item = parse_item(ps, i) + table.insert(list, item) end return i, list end @@ -1866,7 +1868,7 @@ parse_type_list = function(ps, i, mode) end local prev_i = i - i = parse_trying_list(ps, i, list, parse_type) + i = parse_trying_list(ps, i, list, parse_type, mode == "rets") if i == prev_i and ps.tokens[i].tk ~= ")" then fail(ps, i - 1, "expected a type list") end diff --git a/tl.tl b/tl.tl index ff2fd4cfa..0abe1689b 100644 --- a/tl.tl +++ b/tl.tl @@ -1642,7 +1642,7 @@ local function parse_table_literal(ps: ParseState, i: integer): integer, Node return parse_bracket_list(ps, i, node, "{", "}", "term", parse_table_item) end -local function parse_trying_list(ps: ParseState, i: integer, list: {T}, parse_item: ParseItem): integer, {T} +local function parse_trying_list(ps: ParseState, i: integer, list: {T}, parse_item: ParseItem, ret_lookahead: boolean): integer, {T} local try_ps: ParseState = { filename = ps.filename, tokens = ps.tokens, @@ -1658,12 +1658,14 @@ local function parse_trying_list(ps: ParseState, i: integer, list: {T}, parse end i = tryi table.insert(list, item) - if ps.tokens[i].tk == "," then - while ps.tokens[i].tk == "," do - i = i + 1 - i, item = parse_item(ps, i) - table.insert(list, item) - end + while ps.tokens[i].tk == "," + and (not ret_lookahead + or (not (ps.tokens[i + 1].kind == "identifier" + and ps.tokens[i + 2] and ps.tokens[i + 2].tk == ":"))) + do + i = i + 1 + i, item = parse_item(ps, i) + table.insert(list, item) end return i, list end @@ -1866,7 +1868,7 @@ parse_type_list = function(ps: ParseState, i: integer, mode: ParseTypeListMode): end local prev_i = i - i = parse_trying_list(ps, i, list, parse_type) + i = parse_trying_list(ps, i, list, parse_type, mode == "rets") if i == prev_i and ps.tokens[i].tk ~= ")" then fail(ps, i - 1, "expected a type list") end