1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-17 19:05:07 +00:00

LibJS: Let parser keep track of errors

Rather than printing them to stderr directly the parser now keeps a
Vector<Error>, which allows the "owner" of the parser to consume them
individually after parsing.

The Error struct has a message, line number, column number and a
to_string() helper function to format this information into a meaningful
error message.

The Function() constructor will now include an error message when
throwing a SyntaxError.
This commit is contained in:
Linus Groh 2020-05-14 16:26:01 +01:00 committed by Andreas Kling
parent 00b61a212f
commit 33defef267
7 changed files with 39 additions and 12 deletions

View file

@ -29,7 +29,6 @@
#include <AK/HashMap.h> #include <AK/HashMap.h>
#include <AK/ScopeGuard.h> #include <AK/ScopeGuard.h>
#include <AK/StdLibExtras.h> #include <AK/StdLibExtras.h>
#include <stdio.h>
namespace JS { namespace JS {
@ -1385,12 +1384,11 @@ void Parser::expected(const char* what)
void Parser::syntax_error(const String& message, size_t line, size_t column) void Parser::syntax_error(const String& message, size_t line, size_t column)
{ {
m_parser_state.m_has_errors = true;
if (line == 0 || column == 0) { if (line == 0 || column == 0) {
line = m_parser_state.m_current_token.line_number(); line = m_parser_state.m_current_token.line_number();
column = m_parser_state.m_current_token.line_column(); column = m_parser_state.m_current_token.line_column();
} }
fprintf(stderr, "Syntax Error: %s (line: %zu, column: %zu)\n", message.characters(), line, column); m_parser_state.m_errors.append({ message, line, column });
} }
void Parser::save_state() void Parser::save_state()

View file

@ -29,6 +29,7 @@
#include "AST.h" #include "AST.h"
#include "Lexer.h" #include "Lexer.h"
#include <AK/NonnullRefPtr.h> #include <AK/NonnullRefPtr.h>
#include <stdio.h>
namespace JS { namespace JS {
@ -75,7 +76,26 @@ public:
NonnullRefPtr<NewExpression> parse_new_expression(); NonnullRefPtr<NewExpression> parse_new_expression();
RefPtr<FunctionExpression> try_parse_arrow_function_expression(bool expect_parens); RefPtr<FunctionExpression> try_parse_arrow_function_expression(bool expect_parens);
bool has_errors() const { m_parser_state.m_has_errors; } struct Error {
String message;
size_t line;
size_t column;
String to_string() const
{
if (line == 0 || column == 0)
return message;
return String::format("%s (line: %zu, column: %zu)", message.characters(), line, column);
}
};
bool has_errors() const { return m_parser_state.m_errors.size(); }
const Vector<Error>& errors() const { return m_parser_state.m_errors; }
void print_errors() const
{
for (auto& error : m_parser_state.m_errors)
fprintf(stderr, "SyntaxError: %s\n", error.to_string().characters());
}
private: private:
friend class ScopePusher; friend class ScopePusher;
@ -101,7 +121,7 @@ private:
struct ParserState { struct ParserState {
Lexer m_lexer; Lexer m_lexer;
Token m_current_token; Token m_current_token;
bool m_has_errors = false; Vector<Error> m_errors;
Vector<NonnullRefPtrVector<VariableDeclaration>> m_var_scopes; Vector<NonnullRefPtrVector<VariableDeclaration>> m_var_scopes;
Vector<NonnullRefPtrVector<VariableDeclaration>> m_let_scopes; Vector<NonnullRefPtrVector<VariableDeclaration>> m_let_scopes;

View file

@ -70,8 +70,8 @@ Value FunctionConstructor::construct(Interpreter& interpreter)
auto parser = Parser(Lexer(source)); auto parser = Parser(Lexer(source));
auto function_expression = parser.parse_function_node<FunctionExpression>(); auto function_expression = parser.parse_function_node<FunctionExpression>();
if (parser.has_errors()) { if (parser.has_errors()) {
// FIXME: The parser should expose parsing error strings rather than just fprintf()'ing them auto error = parser.errors()[0];
interpreter.throw_exception<SyntaxError>(""); interpreter.throw_exception<SyntaxError>(error.to_string());
return {}; return {};
} }
return function_expression->execute(interpreter); return function_expression->execute(interpreter);

View file

@ -29,7 +29,11 @@ try {
assertThrowsError(() => { assertThrowsError(() => {
new Function("["); new Function("[");
}, { }, {
error: SyntaxError error: SyntaxError,
// This might be confusing at first but keep in mind it's actually parsing
// function anonymous() { [ }
// This is in line with what other engines are reporting.
message: "Unexpected token CurlyClose. Expected BracketClose (line: 1, column: 26)"
}); });
console.log("PASS"); console.log("PASS");

View file

@ -378,6 +378,7 @@ JS::Value Document::run_javascript(const StringView& source)
auto parser = JS::Parser(JS::Lexer(source)); auto parser = JS::Parser(JS::Lexer(source));
auto program = parser.parse_program(); auto program = parser.parse_program();
if (parser.has_errors()) { if (parser.has_errors()) {
parser.print_errors();
return JS::js_undefined(); return JS::js_undefined();
} }
dbg() << "Document::run_javascript('" << source << "') will run:"; dbg() << "Document::run_javascript('" << source << "') will run:";

View file

@ -61,9 +61,10 @@ void HTMLScriptElement::children_changed()
auto parser = JS::Parser(JS::Lexer(source)); auto parser = JS::Parser(JS::Lexer(source));
auto program = parser.parse_program(); auto program = parser.parse_program();
if (parser.has_errors()) if (parser.has_errors()) {
parser.print_errors();
return; return;
}
document().interpreter().run(*program); document().interpreter().run(*program);
} }
@ -96,9 +97,10 @@ void HTMLScriptElement::inserted_into(Node& new_parent)
auto parser = JS::Parser(JS::Lexer(source)); auto parser = JS::Parser(JS::Lexer(source));
auto program = parser.parse_program(); auto program = parser.parse_program();
if (parser.has_errors()) if (parser.has_errors()) {
parser.print_errors();
return; return;
}
document().interpreter().run(*program); document().interpreter().run(*program);
} }

View file

@ -36,5 +36,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
auto lexer = JS::Lexer(js); auto lexer = JS::Lexer(js);
auto parser = JS::Parser(lexer); auto parser = JS::Parser(lexer);
parser.parse_program(); parser.parse_program();
if (parser.has_errors())
parser.print_errors();
return 0; return 0;
} }