Skip to content

Commit 0e2d7ea

Browse files
committed
parser: return SyntaxError when parsing fails
1 parent ddc3823 commit 0e2d7ea

18 files changed

Lines changed: 298 additions & 55 deletions

integration/program.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ class RunPythonProgram : public ::testing::Test
110110

111111
auto lexer = Lexer::create(std::string(program), "_integration_dummy_.py");
112112
parser::Parser p{ lexer };
113-
p.parse();
113+
ASSERT_TRUE(p.parse().is_ok());
114114
p.module()->print_node("");
115115
m_bytecode = compiler::compile(p.module(),
116116
{},

integration/run_python_tests.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,24 @@ else
4646
echo $file "... PASSED!"
4747
fi
4848

49+
# A syntax error must exit non-zero and report the line the parser actually gave
50+
# up on -- not line 1 -- with a caret under the offending token.
51+
file=$SCRIPT_DIR/tests/expected_failures/syntax_error_reporting.py
52+
output=$(timeout 10s $PYTHON_EXECUTABLE $file --gc-frequency $GC_FREQUENCY 2>&1)
53+
if [ $? -eq 0 ]; then
54+
echo $file "... FAILED! (expected a non-zero exit code)"
55+
exit_code=1
56+
elif ! echo "$output" | grep -q '", line 4$'; then
57+
echo $file "... FAILED! (expected the error on line 4, got: ${output})"
58+
exit_code=1
59+
elif ! echo "$output" | grep -qF ' ^'; then
60+
echo $file "... FAILED! (expected a caret under the ':', got: ${output})"
61+
exit_code=1
62+
elif ! echo "$output" | grep -q '^SyntaxError: invalid syntax$'; then
63+
echo $file "... FAILED! (expected a SyntaxError, got: ${output})"
64+
exit_code=1
65+
else
66+
echo $file "... PASSED!"
67+
fi
68+
4969
exit $exit_code
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
x = 1
2+
y = 2
3+
4+
def foo(:
5+
pass

src/ast/optimizers/Optimizers_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ void assert_generates_ast(std::string_view program,
466466
parser::Parser p{ lexer };
467467
const auto spdlog_level = spdlog::get_level();
468468
spdlog::set_level(spdlog::level::debug);
469-
p.parse();
469+
ASSERT(p.parse().is_ok());
470470
spdlog::set_level(spdlog_level);
471471

472472
if (lvl > compiler::OptimizationLevel::None) {

src/executable/bytecode/BytecodeProgram_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ std::shared_ptr<BytecodeProgram> generate_bytecode(std::string_view program)
1414
{
1515
auto lexer = Lexer::create(std::string(program), "_bytecode_program_tests_.py");
1616
parser::Parser p{ lexer };
17-
p.parse();
17+
ASSERT(p.parse().is_ok());
1818

1919
auto module = p.module();
2020
ASSERT(module);

src/executable/bytecode/codegen/BytecodeGenerator_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ std::shared_ptr<BytecodeProgram> generate_bytecode(std::string_view program)
1515
{
1616
auto lexer = Lexer::create(std::string(program), "_bytecode_generator_tests_.py");
1717
parser::Parser p{ lexer };
18-
p.parse();
18+
ASSERT(p.parse().is_ok());
1919

2020
auto module = p.module();
2121
ASSERT(module);

src/executable/bytecode/codegen/VariablesResolver_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ VariablesResolver::VisibilityMap generate_resolver(std::string_view program)
1313
{
1414
auto lexer = Lexer::create(std::string(program), "_bytecode_generator_tests_.py");
1515
parser::Parser p{ lexer };
16-
p.parse();
16+
ASSERT(p.parse().is_ok());
1717

1818
auto *module = as<ast::Module>(p.module().get());
1919
ASSERT(module);

src/executable/llvm/LLVMGenerator_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ std::shared_ptr<Program> generate_llvm_module(std::string_view program)
1414
{
1515
auto lexer = Lexer::create(std::string(program), "_llvm_backend_tests_.py");
1616
parser::Parser p{ lexer };
17-
p.parse();
17+
ASSERT(p.parse().is_ok());
1818

1919
auto module = as<ast::Module>(p.module());
2020
ASSERT(module);

src/parser/Parser.cpp

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
module;
22
#include "core.hpp"
3-
#include "spdlog/spdlog.h"
3+
#include "runtime/SourceManager.hpp"
44

5+
#include "spdlog/spdlog.h"
56
#include <gmpxx.h>
67

78

@@ -709,6 +710,7 @@ struct SingleTokenPatternV2 : PatternV2<SingleTokenPatternV2<Patterns...>>
709710

710711
static std::optional<ResultType> matches_impl(Parser &p)
711712
{
713+
p.observe_token(p.token_position());
712714
if (SingleTokenPattern_<ComposedTypes<Patterns...>>::match(p)) {
713715
const auto &t = p.lexer().peek_token(p.token_position());
714716
return t.has_value() ? std::make_optional(ResultType{ *t }) : std::nullopt;
@@ -7390,33 +7392,41 @@ struct FilePattern : PatternV2<FilePattern>
73907392
}
73917393
return p.module();
73927394
}
7393-
size_t idx = 0;
7394-
auto t = *p.lexer().peek_token(idx);
7395-
auto begin = t.start().pointer_to_program;
7396-
auto end = t.end().pointer_to_program;
7397-
const size_t row = t.start().row;
7398-
while (row == t.start().row) {
7399-
end = t.end().pointer_to_program;
7400-
idx++;
7401-
t = *p.lexer().peek_token(idx);
7402-
}
7403-
std::string line{ begin, end };
7404-
spdlog::error("Syntax error on line {}: '{}'", row + 1, line);
7405-
// PARSER_ERROR();
74067395
return {};
74077396
}
74087397
};
74097398

74107399
namespace parser {
7411-
void Parser::parse()
7400+
PyResult<std::shared_ptr<ast::Module>> Parser::parse()
74127401
{
74137402
auto result = PatternMatchV2<FilePattern>::match(*this);
74147403
if (result) {
74157404
auto [module] = *result;
74167405
m_module = std::move(module);
7417-
m_module->print_node("");
7406+
return Ok(m_module);
74187407
}
7419-
DEBUG_LOG("Parser return code: {}", result.has_value());
7408+
std::size_t index = m_furthest_token;
7409+
std::optional<Token> token = m_lexer.peek_token(index);
7410+
while (!token.has_value() && index > 0) { token = m_lexer.peek_token(--index); }
7411+
7412+
const auto &filename = m_lexer.filename();
7413+
const auto &program = m_lexer.program();
7414+
auto lineno = token.has_value() ? token->start().row + 1 : 1;
7415+
auto offset = token.has_value() ? token->start().column + 1 : 1;
7416+
const auto line_count =
7417+
std::max(static_cast<std::size_t>(std::count(program.begin(), program.end(), '\n'))
7418+
+ (program.empty() || program.back() == '\n' ? 0uz : 1uz),
7419+
1uz);
7420+
if (lineno > line_count) {
7421+
lineno = line_count;
7422+
offset = SourceManager::the().line(filename, lineno).size() + 1;
7423+
}
7424+
const auto text = SourceManager::the().line(filename, lineno);
7425+
return Err(syntax_error("invalid syntax",
7426+
SyntaxErrorLocation{ .filename = filename,
7427+
.lineno = lineno,
7428+
.offset = offset,
7429+
.text = std::string{ text } }));
74207430
}
74217431

74227432
PyResult<std::shared_ptr<ast::Module>> Parser::parse_expression()

src/parser/Parser.cppm

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class Parser
1212
std::shared_ptr<ast::Module> m_module;
1313
Lexer &m_lexer;
1414
std::size_t m_token_position{ 0 };
15+
std::size_t m_furthest_token{ m_token_position };
1516

1617
public:
1718
struct CacheValue
@@ -67,8 +68,15 @@ class Parser
6768
const std::size_t &token_position() const { return m_token_position; }
6869
std::size_t &token_position() { return m_token_position; }
6970

71+
std::size_t furthest_token() const { return m_furthest_token; }
72+
73+
void observe_token(std::size_t position)
74+
{
75+
m_furthest_token = std::max(m_furthest_token, position);
76+
}
77+
7078
// parses a file
71-
void parse();
79+
py::PyResult<std::shared_ptr<ast::Module>> parse();
7280

7381
// parses an expression used by the builtin `eval` function
7482
py::PyResult<std::shared_ptr<ast::Module>> parse_expression();

0 commit comments

Comments
 (0)