Skip to content

Commit fb85656

Browse files
feat(parser): add parse error handling header
1 parent 942e386 commit fb85656

1 file changed

Lines changed: 139 additions & 0 deletions

File tree

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#if !defined(PARSER_ERROR_HXX)
2+
#define PARSER_ERROR_HXX
3+
4+
#include <array>
5+
#include <cstdint>
6+
#include <format>
7+
#include <string>
8+
#include <string_view>
9+
10+
#if !defined (WHEEL_EXPERIMENT) && !defined (WHEEL_SMALL_VEC)
11+
#include <vector>
12+
#endif
13+
14+
#include <wheel_lexer/kind.hxx>
15+
#include <wheel_lexer/token.hxx>
16+
#include <wheel_memory/vec.hxx>
17+
#include "config.hxx"
18+
19+
using wheel_lexer::SourceLocation;
20+
using wheel_lexer::Token;
21+
using wheel_lexer::TokenKind;
22+
23+
WHEEL_PARSER_NAMESPACE
24+
enum class ParseErrorCode : uint16_t {
25+
ExpectedIdentifier = 1001,
26+
ExpectedColon = 1002,
27+
ExpectedType = 1003,
28+
ExpectedEqual = 1004,
29+
ExpectedLiteral = 1005
30+
};
31+
32+
struct ParseErrorSpec {
33+
ParseErrorCode code;
34+
TokenKind expected;
35+
const char *message;
36+
};
37+
38+
inline constexpr std::array<ParseErrorSpec, 5> k_parse_error_specs = {{
39+
{ParseErrorCode::ExpectedIdentifier, TokenKind::IDENT, "expected variable name after 'var'"},
40+
{ParseErrorCode::ExpectedColon, TokenKind::COLON, "expected ':' after variable name"},
41+
{ParseErrorCode::ExpectedType, TokenKind::IDENT, "expected type name after ':'"},
42+
{ParseErrorCode::ExpectedEqual, TokenKind::EQUAL, "expected '=' after type"},
43+
{ParseErrorCode::ExpectedLiteral, TokenKind::INT_LITERAL, "expected literal initializer"}
44+
}};
45+
46+
struct ParseError {
47+
ParseErrorCode code;
48+
const Token *token;
49+
TokenKind expected;
50+
TokenKind actual;
51+
SourceLocation location;
52+
const char *message;
53+
};
54+
55+
struct ParseDiagnostic {
56+
ParseError error;
57+
std::string full_message;
58+
std::string source_line;
59+
std::string marker;
60+
};
61+
62+
#if defined(WHEEL_EXPERIMENT) && defined(WHEEL_SMALL_VEC)
63+
using ParseDiagnosticList = wheel_memory::SmallVec<ParseDiagnostic>;
64+
#else
65+
using ParseDiagnosticList = std::vector<ParseDiagnostic>;
66+
#endif
67+
68+
[[nodiscard]] constexpr const ParseErrorSpec& parse_error_spec(ParseErrorCode code) noexcept {
69+
switch (code) {
70+
case ParseErrorCode::ExpectedIdentifier: return k_parse_error_specs[0];
71+
case ParseErrorCode::ExpectedColon: return k_parse_error_specs[1];
72+
case ParseErrorCode::ExpectedType: return k_parse_error_specs[2];
73+
case ParseErrorCode::ExpectedEqual: return k_parse_error_specs[3];
74+
case ParseErrorCode::ExpectedLiteral: return k_parse_error_specs[4];
75+
}
76+
77+
return k_parse_error_specs[0];
78+
}
79+
80+
[[nodiscard]] inline ParseError make_parse_error(
81+
ParseErrorCode code,
82+
const Token *token,
83+
TokenKind actual,
84+
SourceLocation location
85+
) noexcept {
86+
const auto &spec = parse_error_spec(code);
87+
return ParseError {
88+
code,
89+
token,
90+
spec.expected,
91+
actual,
92+
location,
93+
spec.message
94+
};
95+
}
96+
97+
[[nodiscard]] inline std::string format_parse_error(
98+
const ParseError &error,
99+
std::string_view file_name = {}
100+
) {
101+
return std::format(
102+
"[P{:04}] [{}:{}:{}] {} (expected: {}, got: {})",
103+
static_cast<uint16_t>(error.code),
104+
file_name.empty() ? "unknown" : file_name,
105+
error.location.line,
106+
error.location.column,
107+
error.message,
108+
wheel_lexer::to_string(error.expected),
109+
wheel_lexer::to_string(error.actual)
110+
);
111+
}
112+
113+
[[nodiscard]] inline ParseDiagnostic build_parse_diagnostic(
114+
const ParseError &error,
115+
std::string_view source_line,
116+
std::string_view file_name = {}
117+
) {
118+
const size_t marker_spaces = (error.location.column > 0)
119+
? static_cast<size_t>(error.location.column - 1)
120+
: 0;
121+
122+
size_t marker_length = 1;
123+
if (error.token != nullptr) {
124+
const size_t token_len = static_cast<size_t>(error.token->end - error.token->start);
125+
marker_length = (token_len > 0) ? token_len : 1;
126+
}
127+
128+
ParseDiagnostic diagnostic;
129+
diagnostic.error = error;
130+
diagnostic.full_message = format_parse_error(error, file_name);
131+
diagnostic.source_line = std::string(source_line);
132+
diagnostic.marker = std::string(marker_spaces, ' ') + std::string(marker_length, '^');
133+
134+
return diagnostic;
135+
}
136+
137+
WHEEL_PARSER_END_NAMESPACE
138+
139+
#endif // PARSER_ERROR_HXX

0 commit comments

Comments
 (0)