Skip to content

Commit 942e386

Browse files
feat(parser): add error handling, keyword matching, and diagnostics
Add comprehensive error handling to the parser with emit_error(), synchronize_statement(), and error collection methods. Introduce keyword matching capability for proper token validation. Add diagnostic collection for parse errors with source line context. Refactor parse_variable_declaration to use StatementNode return type and proper error handling for missing identifiers, colons, types, and literals.
1 parent e82449a commit 942e386

1 file changed

Lines changed: 123 additions & 41 deletions

File tree

wheel_parser/parser.cxx

Lines changed: 123 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
#include "wheel_parser/parser.hxx"
2-
#include "include/wheel_parser/ast/nodes.hxx"
2+
#include "wheel_parser/ast/keywords.hxx"
3+
#include "wheel_parser/ast/nodes.hxx"
4+
#include <utility>
35
#include <wheel_utils/logging.hxx>
46

57
using wheel_memory::Arena;
68
using wheel_parser::WheelParser;
79
using wheel_parser::ast::VariableDeclaration;
10+
using wheel_parser::ast::StatementNode;
11+
using wheel_parser::ast::ErrorStatement;
812
using wheel_parser::ast::LiteralExpression;
913
using wheel_lexer::Lexer;
14+
using wheel_parser::ast::k_keywords;
1015

11-
WheelParser::WheelParser(Lexer &lexer, Arena &arena, StringInterner &interner) noexcept :
12-
m_lexer(lexer), m_arena(arena),
13-
m_interner(interner) {
14-
15-
}
16+
#if defined(WHEEL_EXPERIMENT) && defined(WHEEL_SMALL_VEC)
17+
WheelParser::WheelParser(Lexer &lexer, Arena &arena, StringInterner &interner) noexcept :
18+
m_lexer(lexer), m_arena(arena), m_interner(interner), m_errors(&arena) {}
19+
#else
20+
WheelParser::WheelParser(Lexer &lexer, Arena &arena, StringInterner &interner) noexcept :
21+
m_lexer(lexer), m_arena(arena), m_interner(interner), m_errors() {}
22+
#endif
1623

1724
const Token& WheelParser::consume() noexcept {
1825
const Token& consumed = m_current_token;
@@ -21,12 +28,47 @@ const Token& WheelParser::consume() noexcept {
2128
return consumed;
2229
}
2330

24-
bool WheelParser::str_matches(string_view str) noexcept {
25-
return m_current_token.str == str;
31+
bool WheelParser::token_matches(TokenKind token_kind) const noexcept {
32+
return m_current_token.kind == token_kind;
2633
}
2734

28-
bool WheelParser::token_matches(TokenKind token_kind) noexcept {
29-
return m_current_token.kind == token_kind;
35+
bool WheelParser::keyword_matches(Keyword keyword) const noexcept {
36+
if (m_current_token.kind != TokenKind::IDENT) {
37+
return false;
38+
}
39+
40+
return m_current_token.str == k_keywords[static_cast<size_t>(keyword)].text;
41+
}
42+
43+
void WheelParser::skip_spaces() noexcept {
44+
while (true) {
45+
consume();
46+
if (m_current_token.kind != TokenKind::SPACE && m_current_token.kind != TokenKind::TAB) {
47+
break;
48+
}
49+
}
50+
}
51+
52+
void WheelParser::synchronize_statement() noexcept {
53+
while (m_current_token.kind != TokenKind::EOF_ && m_current_token.kind != TokenKind::NEWLINE) {
54+
consume();
55+
}
56+
}
57+
58+
StatementNode *WheelParser::emit_error(
59+
ParseErrorCode code
60+
) noexcept {
61+
const auto *token = copy_token();
62+
const auto error = make_parse_error(
63+
code,
64+
token,
65+
token->kind,
66+
m_lexer.get_source_location(*token)
67+
);
68+
m_errors.push_back(error);
69+
70+
synchronize_statement();
71+
return m_arena.allocate<ErrorStatement>(token);
3072
}
3173

3274
const Token *WheelParser::copy_token() const noexcept {
@@ -38,49 +80,89 @@ const Token *WheelParser::copy_token() const noexcept {
3880
);
3981
}
4082

41-
// TODO: Implement error reporting, avoid returning `nullptr`
42-
// We also need to improve error handling and avoid assuming everything is literal.
43-
VariableDeclaration *WheelParser::parse_variable_declaration() noexcept {
83+
StatementNode *WheelParser::parse_variable_declaration() noexcept {
4484
consume();
85+
if (!keyword_matches(Keyword::Var)) {
86+
return nullptr;
87+
}
88+
4589
const auto start_token = copy_token();
4690

47-
if (token_matches(TokenKind::IDENT) && str_matches("int")) {
48-
while (true) {
49-
consume();
50-
if (m_current_token.kind != TokenKind::SPACE) break;
51-
}
91+
skip_spaces();
5292

53-
const auto var_type = m_interner.intern("int");
54-
const auto var_name = m_interner.intern(m_current_token.str);
93+
if (!token_matches(TokenKind::IDENT)) {
94+
return emit_error(ParseErrorCode::ExpectedIdentifier);
95+
}
96+
const auto var_name = m_interner.intern(m_current_token.str);
5597

56-
while(true) {
57-
consume();
58-
if (m_current_token.kind != TokenKind::SPACE) break;
59-
}
98+
skip_spaces();
99+
if (!token_matches(TokenKind::COLON)) {
100+
return emit_error(ParseErrorCode::ExpectedColon);
101+
}
60102

61-
const auto var_operator = m_current_token;
62-
if (var_operator.kind != TokenKind::EQUAL) {
63-
return nullptr;
64-
}
103+
skip_spaces();
104+
if (!token_matches(TokenKind::IDENT)) {
105+
return emit_error(ParseErrorCode::ExpectedType);
106+
}
65107

66-
while(true) {
67-
consume();
68-
if (m_current_token.kind != TokenKind::SPACE) break;
69-
}
108+
const auto var_type = m_interner.intern(m_current_token.str);
70109

71-
const auto var_value = m_interner.intern(m_current_token.str);
72-
const auto literal = m_arena.allocate<LiteralExpression>(
73-
&m_current_token
74-
);
110+
skip_spaces();
111+
if (!token_matches(TokenKind::EQUAL)) {
112+
return emit_error(ParseErrorCode::ExpectedEqual);
113+
}
75114

76-
return m_arena.allocate<VariableDeclaration>(
77-
start_token,
78-
var_type, var_name, literal
79-
);
115+
skip_spaces();
116+
switch (m_current_token.kind) {
117+
case TokenKind::INT_LITERAL:
118+
case TokenKind::FLOAT_LITERAL:
119+
case TokenKind::STRING_LITERAL:
120+
case TokenKind::RAW_STRING_LITERAL:
121+
break;
122+
default:
123+
return emit_error(ParseErrorCode::ExpectedLiteral);
80124
}
81125

82-
return nullptr;
126+
const auto literal_token = copy_token();
127+
const auto literal = m_arena.allocate<LiteralExpression>(
128+
literal_token
129+
);
130+
131+
return m_arena.allocate<VariableDeclaration>(
132+
start_token,
133+
var_type, var_name, literal
134+
);
83135
}
84136

85137
void WheelParser::parse() noexcept {
86138
}
139+
140+
size_t WheelParser::error_count() const noexcept {
141+
return m_errors.size();
142+
}
143+
144+
const wheel_parser::ParseError *WheelParser::errors_data() const noexcept {
145+
return m_errors.data();
146+
}
147+
148+
void WheelParser::clear_errors() noexcept {
149+
m_errors.clear();
150+
}
151+
152+
wheel_parser::ParseDiagnosticList WheelParser::collect_diagnostics(std::string_view file_name) const {
153+
#if defined(WHEEL_EXPERIMENT) && defined(WHEEL_SMALL_VEC)
154+
ParseDiagnosticList diagnostics(&m_arena);
155+
#else
156+
ParseDiagnosticList diagnostics;
157+
diagnostics.reserve(m_errors.size());
158+
#endif
159+
160+
const auto *errors = m_errors.data();
161+
162+
for (size_t index = 0; index < m_errors.size(); ++index) {
163+
const auto line_view = m_lexer.get_source_line(errors[index].location.offset);
164+
diagnostics.push_back(build_parse_diagnostic(errors[index], line_view, file_name));
165+
}
166+
167+
return ParseDiagnosticList(std::move(diagnostics));
168+
}

0 commit comments

Comments
 (0)