Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion integration/program.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ class RunPythonProgram : public ::testing::Test

auto lexer = Lexer::create(std::string(program), "_integration_dummy_.py");
parser::Parser p{ lexer };
p.parse();
ASSERT_TRUE(p.parse().is_ok());
p.module()->print_node("");
m_bytecode = compiler::compile(p.module(),
{},
Expand Down
20 changes: 20 additions & 0 deletions integration/run_python_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,24 @@ else
echo $file "... PASSED!"
fi

# A syntax error must exit non-zero and report the line the parser actually gave
# up on -- not line 1 -- with a caret under the offending token.
file=$SCRIPT_DIR/tests/expected_failures/syntax_error_reporting.py
output=$(timeout 10s $PYTHON_EXECUTABLE $file --gc-frequency $GC_FREQUENCY 2>&1)
if [ $? -eq 0 ]; then
echo $file "... FAILED! (expected a non-zero exit code)"
exit_code=1
elif ! echo "$output" | grep -q '", line 4$'; then
echo $file "... FAILED! (expected the error on line 4, got: ${output})"
exit_code=1
elif ! echo "$output" | grep -qF ' ^'; then
echo $file "... FAILED! (expected a caret under the ':', got: ${output})"
exit_code=1
elif ! echo "$output" | grep -q '^SyntaxError: invalid syntax$'; then
echo $file "... FAILED! (expected a SyntaxError, got: ${output})"
exit_code=1
else
echo $file "... PASSED!"
fi

exit $exit_code
5 changes: 5 additions & 0 deletions integration/tests/expected_failures/syntax_error_reporting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
x = 1
y = 2

def foo(:
pass
2 changes: 1 addition & 1 deletion src/ast/optimizers/Optimizers_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ void assert_generates_ast(std::string_view program,
parser::Parser p{ lexer };
const auto spdlog_level = spdlog::get_level();
spdlog::set_level(spdlog::level::debug);
p.parse();
ASSERT(p.parse().is_ok());
spdlog::set_level(spdlog_level);

if (lvl > compiler::OptimizationLevel::None) {
Expand Down
2 changes: 1 addition & 1 deletion src/executable/bytecode/BytecodeProgram_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ std::shared_ptr<BytecodeProgram> generate_bytecode(std::string_view program)
{
auto lexer = Lexer::create(std::string(program), "_bytecode_program_tests_.py");
parser::Parser p{ lexer };
p.parse();
ASSERT(p.parse().is_ok());

auto module = p.module();
ASSERT(module);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ std::shared_ptr<BytecodeProgram> generate_bytecode(std::string_view program)
{
auto lexer = Lexer::create(std::string(program), "_bytecode_generator_tests_.py");
parser::Parser p{ lexer };
p.parse();
ASSERT(p.parse().is_ok());

auto module = p.module();
ASSERT(module);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ VariablesResolver::VisibilityMap generate_resolver(std::string_view program)
{
auto lexer = Lexer::create(std::string(program), "_bytecode_generator_tests_.py");
parser::Parser p{ lexer };
p.parse();
ASSERT(p.parse().is_ok());

auto *module = as<ast::Module>(p.module().get());
ASSERT(module);
Expand Down
2 changes: 1 addition & 1 deletion src/executable/llvm/LLVMGenerator_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ std::shared_ptr<Program> generate_llvm_module(std::string_view program)
{
auto lexer = Lexer::create(std::string(program), "_llvm_backend_tests_.py");
parser::Parser p{ lexer };
p.parse();
ASSERT(p.parse().is_ok());

auto module = as<ast::Module>(p.module());
ASSERT(module);
Expand Down
51 changes: 31 additions & 20 deletions src/parser/Parser.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
module;
#include "core.hpp"
#include "spdlog/spdlog.h"
#include "runtime/SourceManager.hpp"

#include "spdlog/spdlog.h"
#include <gmpxx.h>


Expand Down Expand Up @@ -133,8 +134,8 @@ template<typename Derived> struct PatternV2
if constexpr (seeds_sentinel) {
if (result.has_value()) {
// Safe to hold across grow_lr: memo entries live in a deque.
auto *slot = p.memo_find(start_position, memo_rule_id<Derived>);
ASSERT(slot);
const auto &slot = p.memo_find(start_position, memo_rule_id<Derived>);
ASSERT(slot.has_value());
ASSERT(slot->has_value());
auto &value = *slot;
if (std::holds_alternative<bool>(value->value) && std::get<bool>(value->value)) {
Expand Down Expand Up @@ -212,7 +213,8 @@ template<size_t TypeIdx, typename PatternTuple, typename = void> class PatternMa
if (!t.has_value()) { return {}; }
if constexpr (::detail::has_type<typename ResultTypeHead::value_type,
::detail::ValueTypesTuple>{}) {
if (auto *slot = p.memo_find(original_token_position, memo_rule_id<CurrentType>)) {
if (const auto &slot = p.memo_find(original_token_position, memo_rule_id<CurrentType>);
slot.has_value()) {
if (!slot->has_value()) { return {}; }
auto &value = (*slot)->value;
p.token_position() = (*slot)->position;
Expand Down Expand Up @@ -708,6 +710,7 @@ struct SingleTokenPatternV2 : PatternV2<SingleTokenPatternV2<Patterns...>>

static std::optional<ResultType> matches_impl(Parser &p)
{
p.observe_token(p.token_position());
if (SingleTokenPattern_<ComposedTypes<Patterns...>>::match(p)) {
const auto &t = p.lexer().peek_token(p.token_position());
return t.has_value() ? std::make_optional(ResultType{ *t }) : std::nullopt;
Expand Down Expand Up @@ -7389,33 +7392,41 @@ struct FilePattern : PatternV2<FilePattern>
}
return p.module();
}
size_t idx = 0;
auto t = *p.lexer().peek_token(idx);
auto begin = t.start().pointer_to_program;
auto end = t.end().pointer_to_program;
const size_t row = t.start().row;
while (row == t.start().row) {
end = t.end().pointer_to_program;
idx++;
t = *p.lexer().peek_token(idx);
}
std::string line{ begin, end };
spdlog::error("Syntax error on line {}: '{}'", row + 1, line);
// PARSER_ERROR();
return {};
}
};

namespace parser {
void Parser::parse()
PyResult<std::shared_ptr<ast::Module>> Parser::parse()
{
auto result = PatternMatchV2<FilePattern>::match(*this);
if (result) {
auto [module] = *result;
m_module = std::move(module);
m_module->print_node("");
return Ok(m_module);
}
DEBUG_LOG("Parser return code: {}", result.has_value());
std::size_t index = m_furthest_token;
std::optional<Token> token = m_lexer.peek_token(index);
while (!token.has_value() && index > 0) { token = m_lexer.peek_token(--index); }

const auto &filename = m_lexer.filename();
const auto &program = m_lexer.program();
auto lineno = token.has_value() ? token->start().row + 1 : 1;
auto offset = token.has_value() ? token->start().column + 1 : 1;
const auto line_count =
std::max(static_cast<std::size_t>(std::count(program.begin(), program.end(), '\n'))
+ (program.empty() || program.back() == '\n' ? 0uz : 1uz),
1uz);
if (lineno > line_count) {
lineno = line_count;
offset = SourceManager::the().line(filename, lineno).size() + 1;
}
const auto text = SourceManager::the().line(filename, lineno);
return Err(syntax_error("invalid syntax",
SyntaxErrorLocation{ .filename = filename,
.lineno = lineno,
.offset = offset,
.text = std::string{ text } }));
}

PyResult<std::shared_ptr<ast::Module>> Parser::parse_expression()
Expand Down
22 changes: 16 additions & 6 deletions src/parser/Parser.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Parser
std::shared_ptr<ast::Module> m_module;
Lexer &m_lexer;
std::size_t m_token_position{ 0 };
std::size_t m_furthest_token{ m_token_position };

public:
struct CacheValue
Expand All @@ -25,18 +26,20 @@ class Parser

using MemoSlot = std::optional<CacheValue>;

MemoSlot *memo_find(std::size_t position, std::uint16_t rule)
std::optional<MemoSlot &> memo_find(std::size_t position, std::uint16_t rule)
{
if (position >= m_memo_index.size()) { return nullptr; }
if (position >= m_memo_index.size()) { return std::nullopt; }
for (const auto &[id, slot] : m_memo_index[position]) {
if (id == rule) { return &m_memo_pool[slot]; }
if (id == rule) { return m_memo_pool[slot]; }
}
return nullptr;
return std::nullopt;
}

MemoSlot &memo_insert(std::size_t position, std::uint16_t rule)
{
if (auto *existing = memo_find(position, rule)) { return *existing; }
if (const auto &existing = memo_find(position, rule); existing.has_value()) {
return existing.value();
}
if (position >= m_memo_index.size()) { m_memo_index.resize(position + 1); }
m_memo_pool.emplace_back();
m_memo_index[position].emplace_back(
Expand Down Expand Up @@ -65,8 +68,15 @@ class Parser
const std::size_t &token_position() const { return m_token_position; }
std::size_t &token_position() { return m_token_position; }

std::size_t furthest_token() const { return m_furthest_token; }

void observe_token(std::size_t position)
{
m_furthest_token = std::max(m_furthest_token, position);
}

// parses a file
void parse();
py::PyResult<std::shared_ptr<ast::Module>> parse();

// parses an expression used by the builtin `eval` function
py::PyResult<std::shared_ptr<ast::Module>> parse_expression();
Expand Down
2 changes: 1 addition & 1 deletion src/parser/Parser_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1104,7 +1104,7 @@ void assert_generates_ast(std::string_view program, std::shared_ptr<Module> expe
{
auto lexer = Lexer::create(std::string(program), "_parser_test_.py");
parser::Parser p{ lexer };
p.parse();
ASSERT_TRUE(p.parse().is_ok());
ASSERT_TRUE(p.module());

const auto lvl = spdlog::get_level();
Expand Down
12 changes: 8 additions & 4 deletions src/repl/repl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,20 @@ int run_and_execute_script(size_t argc,
std::cout << std::endl;
}
parser::Parser p{ lexer };
p.parse();
auto module_ = p.parse();
if (module_.is_err()) {
std::cout << module_.unwrap_err()->format_traceback() << std::endl;
return EXIT_FAILURE;
}
if (print_ast) {
const auto lvl = spdlog::get_level();
spdlog::set_level(spdlog::level::debug);
p.module()->print_node("");
module_.unwrap()->print_node("");
spdlog::set_level(lvl);
}

std::shared_ptr<Program> bytecode = compiler::compile(
p.module(), argv_vector, compiler::Backend::MLIR, compiler::OptimizationLevel::None);
module_.unwrap(), argv_vector, compiler::Backend::MLIR, compiler::OptimizationLevel::None);

if (print_bytecode) {
std::cout << "Generated bytecode: \n";
Expand All @@ -108,7 +112,7 @@ int run_and_execute_script(size_t argc,
if (use_llvm) {
#ifdef USE_LLVM
auto llvm_code = codegen::LLVMGenerator::compile(
p.module(), argv_vector, compiler::OptimizationLevel::None);
module_.unwrap(), argv_vector, compiler::OptimizationLevel::None);
if (!llvm_code) {
std::cout << "Could not compile to LLVM IR\n";
} else {
Expand Down
13 changes: 9 additions & 4 deletions src/runtime/BaseException.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ std::string BaseException::to_string() const
std::string BaseException::format_traceback() const
{
std::ostringstream out;
out << "Traceback (most recent call last):\n";
auto *tb = m_traceback;
while (tb) {
if (tb) { out << "Traceback (most recent call last):\n"; }
for (; tb; tb = tb->m_tb_next) {
const auto &filename = tb->m_tb_frame->code()->m_filename;
out << std::format(" File \"{}\", line {}, in {}\n",
filename,
Expand All @@ -118,12 +118,17 @@ std::string BaseException::format_traceback() const
const auto source = SourceManager::the().line(filename, tb->m_tb_lineno);
const auto trimmed = SourceManager::strip_leading_whitespace(source);
if (!trimmed.empty()) { out << " " << trimmed << "\n"; }
tb = tb->m_tb_next;
}
out << type()->name() << ": " << what() << "\n";
out << format_exception_only();
return out.str();
}

std::string BaseException::format_exception_only() const
{
return std::format("{}: {}\n", type()->name(), what());
}


PyResult<PyObject *> BaseException::__repr__() const
{
std::string args_part;
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/BaseException.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ class BaseException : public PyBaseObject
PyType *static_type() const override;
static PyType *class_type();

virtual std::string format_exception_only() const;

void visit_graph(Visitor &) override;
};

Expand Down
Loading
Loading