From 0fce3212a3eadb904fecda557b72c16ed822a75c Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Sat, 4 Mar 2023 10:29:52 +0000 Subject: [PATCH] Distinguish between all parsers passing and failing Given an input like f(x), which is both a valid statement and expression, both parsers would accept the whole input. However, this was treated the same as both parsers rejecting the input, resulting in a crash when trying to print the error. We now return immediately when any parser accepts the input. Fixes #1354 --- .../modules/main/cc/internal/syntax/init.lua | 23 +++++++++++++++---- .../modules/cc/internal/syntax/init_spec.lua | 14 +++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/internal/syntax/init.lua b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/internal/syntax/init.lua index 6b52f1ce0..00df37a24 100644 --- a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/internal/syntax/init.lua +++ b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/internal/syntax/init.lua @@ -120,22 +120,35 @@ local function parse_repl(input) assert(coroutine.resume(parsers[i], context, coroutine.yield, start_code)) end + -- Run all parsers together in parallel, feeding them one token at a time. + -- Once all parsers have failed, report the last failure (corresponding to + -- the longest parse). local ok, err = pcall(function() local parsers_n = #parsers while true do local token, start, finish = lexer() - local stop = true + local all_failed = true for i = 1, parsers_n do local parser = parsers[i] - if coroutine.status(parser) ~= "dead" then - stop = false + if parser then local ok, err = coroutine.resume(parser, token, start, finish) - if not ok and err ~= error_sentinel then error(err, 0) end + if ok then + -- This parser accepted our input, succeed immediately. + if coroutine.status(parser) == "dead" then return end + + all_failed = false -- Otherwise continue parsing. + elseif err ~= error_sentinel then + -- An internal error occurred: propagate it. + error(err, 0) + else + -- The parser failed, stub it out so we don't try to continue using it. + parsers[i] = false + end end end - if stop then error(error_sentinel) end + if all_failed then error(error_sentinel) end end end) diff --git a/src/test/resources/test-rom/spec/modules/cc/internal/syntax/init_spec.lua b/src/test/resources/test-rom/spec/modules/cc/internal/syntax/init_spec.lua index 69bddaa89..10ae30f78 100644 --- a/src/test/resources/test-rom/spec/modules/cc/internal/syntax/init_spec.lua +++ b/src/test/resources/test-rom/spec/modules/cc/internal/syntax/init_spec.lua @@ -49,4 +49,18 @@ describe("cc.internal.syntax", function() describe_golden("the lexer", "lexer_spec.md", true) describe_golden("the parser", "parser_spec.md", false) describe_golden("the parser (all states)", "parser_exhaustive_spec.md", false) + + describe("the REPL input parser", function() + it("returns true when accepted by both parsers", function() + helpers.with_window(50, 10, function() + expect(syntax.parse_repl("print(x)")):eq(true) + end) + end) + + it("returns true when accepted by one parser", function() + helpers.with_window(50, 10, function() + expect(syntax.parse_repl("x")):eq(true) + end) + end) + end) end)