Skip to content

Commit

Permalink
parser: lookahead function arguments to avoid ambiguity
Browse files Browse the repository at this point in the history
With a 3-token lookahead we can parse function arguments with
multiple returns in the middle of an argument list.

Fixes #780.
  • Loading branch information
hishamhm committed Aug 19, 2024
1 parent ae53d4d commit ddc9f60
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 31 deletions.
9 changes: 2 additions & 7 deletions spec/declaration/global_function_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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<a, b>(f: function(a):b, xs: {a}): {b}
local r = {}
for i, x in ipairs(xs) do
Expand All @@ -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<a,b>(f: function(a):(b), xs: {a}): {b}
Expand Down
11 changes: 3 additions & 8 deletions spec/declaration/local_function_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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<a, b>(f: function(a):b, xs: {a}): {b}
local r = {}
for i, x in ipairs(xs) do
r[i] = f(x)
Expand All @@ -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<a,b>(f: function(a):(b), xs: {a}): {b}
Expand Down
18 changes: 10 additions & 8 deletions tl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
18 changes: 10 additions & 8 deletions tl.tl
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(ps: ParseState, i: integer, list: {T}, parse_item: ParseItem<T>): integer, {T}
local function parse_trying_list<T>(ps: ParseState, i: integer, list: {T}, parse_item: ParseItem<T>, ret_lookahead: boolean): integer, {T}
local try_ps: ParseState = {
filename = ps.filename,
tokens = ps.tokens,
Expand All @@ -1658,12 +1658,14 @@ local function parse_trying_list<T>(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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit ddc9f60

Please sign in to comment.