mirror of
https://github.com/RGBCube/serenity
synced 2025-05-15 07:34:59 +00:00
LibJS: Replace 'size_t line, size_t column' with 'Optional<Position>'
This is a bit nicer for two reasons: - The absence of line number/column information isn't based on 'values are zero' anymore but on Optional's value - When reporting syntax errors with position information other than the current token's position we had to store line and column ourselves, like this: auto foo_start_line = m_parser_state.m_current_token.line_number(); auto foo_start_column = m_parser_state.m_current_token.line_column(); ... syntax_error("...", foo_start_line, foo_start_column); Which now becomes: auto foo_start= position(); ... syntax_error("...", foo_start); This makes it easier to report correct positions for syntax errors that only emerge a few tokens later :^)
This commit is contained in:
parent
ed5469c1c2
commit
e07a39c816
2 changed files with 34 additions and 29 deletions
|
@ -662,24 +662,22 @@ NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
|
||||||
switch (m_parser_state.m_current_token.type()) {
|
switch (m_parser_state.m_current_token.type()) {
|
||||||
case TokenType::PlusPlus: {
|
case TokenType::PlusPlus: {
|
||||||
consume();
|
consume();
|
||||||
auto rhs_start_line = m_parser_state.m_current_token.line_number();
|
auto rhs_start = position();
|
||||||
auto rhs_start_column = m_parser_state.m_current_token.line_column();
|
|
||||||
auto rhs = parse_expression(precedence, associativity);
|
auto rhs = parse_expression(precedence, associativity);
|
||||||
// FIXME: Apparently for functions this should also not be enforced on a parser level,
|
// FIXME: Apparently for functions this should also not be enforced on a parser level,
|
||||||
// other engines throw ReferenceError for ++foo()
|
// other engines throw ReferenceError for ++foo()
|
||||||
if (!rhs->is_identifier() && !rhs->is_member_expression())
|
if (!rhs->is_identifier() && !rhs->is_member_expression())
|
||||||
syntax_error(String::formatted("Right-hand side of prefix increment operator must be identifier or member expression, got {}", rhs->class_name()), rhs_start_line, rhs_start_column);
|
syntax_error(String::formatted("Right-hand side of prefix increment operator must be identifier or member expression, got {}", rhs->class_name()), rhs_start);
|
||||||
return create_ast_node<UpdateExpression>(UpdateOp::Increment, move(rhs), true);
|
return create_ast_node<UpdateExpression>(UpdateOp::Increment, move(rhs), true);
|
||||||
}
|
}
|
||||||
case TokenType::MinusMinus: {
|
case TokenType::MinusMinus: {
|
||||||
consume();
|
consume();
|
||||||
auto rhs_start_line = m_parser_state.m_current_token.line_number();
|
auto rhs_start = position();
|
||||||
auto rhs_start_column = m_parser_state.m_current_token.line_column();
|
|
||||||
auto rhs = parse_expression(precedence, associativity);
|
auto rhs = parse_expression(precedence, associativity);
|
||||||
// FIXME: Apparently for functions this should also not be enforced on a parser level,
|
// FIXME: Apparently for functions this should also not be enforced on a parser level,
|
||||||
// other engines throw ReferenceError for --foo()
|
// other engines throw ReferenceError for --foo()
|
||||||
if (!rhs->is_identifier() && !rhs->is_member_expression())
|
if (!rhs->is_identifier() && !rhs->is_member_expression())
|
||||||
syntax_error(String::formatted("Right-hand side of prefix decrement operator must be identifier or member expression, got {}", rhs->class_name()), rhs_start_line, rhs_start_column);
|
syntax_error(String::formatted("Right-hand side of prefix decrement operator must be identifier or member expression, got {}", rhs->class_name()), rhs_start);
|
||||||
return create_ast_node<UpdateExpression>(UpdateOp::Decrement, move(rhs), true);
|
return create_ast_node<UpdateExpression>(UpdateOp::Decrement, move(rhs), true);
|
||||||
}
|
}
|
||||||
case TokenType::ExclamationMark:
|
case TokenType::ExclamationMark:
|
||||||
|
@ -777,10 +775,7 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
|
||||||
|
|
||||||
if (property_type == ObjectProperty::Type::Getter || property_type == ObjectProperty::Type::Setter) {
|
if (property_type == ObjectProperty::Type::Getter || property_type == ObjectProperty::Type::Setter) {
|
||||||
if (!match(TokenType::ParenOpen)) {
|
if (!match(TokenType::ParenOpen)) {
|
||||||
syntax_error(
|
syntax_error("Expected '(' for object getter or setter property");
|
||||||
"Expected '(' for object getter or setter property",
|
|
||||||
m_parser_state.m_current_token.line_number(),
|
|
||||||
m_parser_state.m_current_token.line_column());
|
|
||||||
skip_to_next_property();
|
skip_to_next_property();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -867,7 +862,7 @@ NonnullRefPtr<StringLiteral> Parser::parse_string_literal(Token token, bool in_t
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!message.is_empty())
|
if (!message.is_empty())
|
||||||
syntax_error(message, token.line_number(), token.line_column());
|
syntax_error(message, Position { token.line_number(), token.line_column() });
|
||||||
}
|
}
|
||||||
|
|
||||||
auto is_use_strict_directive = !in_template_literal && (token.value() == "'use strict'" || token.value() == "\"use strict\"");
|
auto is_use_strict_directive = !in_template_literal && (token.value() == "'use strict'" || token.value() == "\"use strict\"");
|
||||||
|
@ -1321,7 +1316,7 @@ Vector<FunctionNode::Parameter> Parser::parse_function_parameters(int& function_
|
||||||
else if (has_rest_parameter)
|
else if (has_rest_parameter)
|
||||||
message = String::formatted("Duplicate parameter '{}' not allowed in function with rest parameter", parameter_name);
|
message = String::formatted("Duplicate parameter '{}' not allowed in function with rest parameter", parameter_name);
|
||||||
if (!message.is_empty())
|
if (!message.is_empty())
|
||||||
syntax_error(message, token.line_number(), token.line_column());
|
syntax_error(message, Position { token.line_number(), token.line_column() });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
|
@ -1915,11 +1910,10 @@ Token Parser::consume_and_validate_numeric_literal()
|
||||||
auto is_unprefixed_octal_number = [](const StringView& value) {
|
auto is_unprefixed_octal_number = [](const StringView& value) {
|
||||||
return value.length() > 1 && value[0] == '0' && isdigit(value[1]);
|
return value.length() > 1 && value[0] == '0' && isdigit(value[1]);
|
||||||
};
|
};
|
||||||
auto literal_start_line = m_parser_state.m_current_token.line_number();
|
auto literal_start = position();
|
||||||
auto literal_start_column = m_parser_state.m_current_token.line_column();
|
|
||||||
auto token = consume(TokenType::NumericLiteral);
|
auto token = consume(TokenType::NumericLiteral);
|
||||||
if (m_parser_state.m_strict_mode && is_unprefixed_octal_number(token.value()))
|
if (m_parser_state.m_strict_mode && is_unprefixed_octal_number(token.value()))
|
||||||
syntax_error("Unprefixed octal number not allowed in strict mode", literal_start_line, literal_start_column);
|
syntax_error("Unprefixed octal number not allowed in strict mode", literal_start);
|
||||||
if (match_identifier_name() && m_parser_state.m_current_token.trivia().is_empty())
|
if (match_identifier_name() && m_parser_state.m_current_token.trivia().is_empty())
|
||||||
syntax_error("Numeric literal must not be immediately followed by identifier");
|
syntax_error("Numeric literal must not be immediately followed by identifier");
|
||||||
return token;
|
return token;
|
||||||
|
@ -1933,13 +1927,19 @@ void Parser::expected(const char* what)
|
||||||
syntax_error(message);
|
syntax_error(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::syntax_error(const String& message, size_t line, size_t column)
|
Parser::Position Parser::position() const
|
||||||
{
|
{
|
||||||
if (line == 0 || column == 0) {
|
return {
|
||||||
line = m_parser_state.m_current_token.line_number();
|
m_parser_state.m_current_token.line_number(),
|
||||||
column = m_parser_state.m_current_token.line_column();
|
m_parser_state.m_current_token.line_column()
|
||||||
}
|
};
|
||||||
m_parser_state.m_errors.append({ message, line, column });
|
}
|
||||||
|
|
||||||
|
void Parser::syntax_error(const String& message, Optional<Position> position)
|
||||||
|
{
|
||||||
|
if (!position.has_value())
|
||||||
|
position = this->position();
|
||||||
|
m_parser_state.m_errors.append({ message, position });
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::save_state()
|
void Parser::save_state()
|
||||||
|
|
|
@ -100,21 +100,25 @@ public:
|
||||||
NonnullRefPtr<Expression> parse_property_key();
|
NonnullRefPtr<Expression> parse_property_key();
|
||||||
NonnullRefPtr<AssignmentExpression> parse_assignment_expression(AssignmentOp, NonnullRefPtr<Expression> lhs, int min_precedence, Associativity);
|
NonnullRefPtr<AssignmentExpression> parse_assignment_expression(AssignmentOp, NonnullRefPtr<Expression> lhs, int min_precedence, Associativity);
|
||||||
|
|
||||||
struct Error {
|
struct Position {
|
||||||
String message;
|
|
||||||
size_t line;
|
size_t line;
|
||||||
size_t column;
|
size_t column;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Error {
|
||||||
|
String message;
|
||||||
|
Optional<Position> position;
|
||||||
|
|
||||||
String to_string() const
|
String to_string() const
|
||||||
{
|
{
|
||||||
if (line == 0 || column == 0)
|
if (!position.has_value())
|
||||||
return message;
|
return message;
|
||||||
return String::formatted("{} (line: {}, column: {})", message, line, column);
|
return String::formatted("{} (line: {}, column: {})", message, position.value().line, position.value().column);
|
||||||
}
|
}
|
||||||
|
|
||||||
String source_location_hint(const StringView& source, const char spacer = ' ', const char indicator = '^') const
|
String source_location_hint(const StringView& source, const char spacer = ' ', const char indicator = '^') const
|
||||||
{
|
{
|
||||||
if (line == 0 || column == 0)
|
if (!position.has_value())
|
||||||
return {};
|
return {};
|
||||||
// We need to modify the source to match what the lexer considers one line - normalizing
|
// We need to modify the source to match what the lexer considers one line - normalizing
|
||||||
// line terminators to \n is easier than splitting using all different LT characters.
|
// line terminators to \n is easier than splitting using all different LT characters.
|
||||||
|
@ -124,9 +128,9 @@ public:
|
||||||
source_string.replace(LINE_SEPARATOR, "\n");
|
source_string.replace(LINE_SEPARATOR, "\n");
|
||||||
source_string.replace(PARAGRAPH_SEPARATOR, "\n");
|
source_string.replace(PARAGRAPH_SEPARATOR, "\n");
|
||||||
StringBuilder builder;
|
StringBuilder builder;
|
||||||
builder.append(source_string.split_view('\n', true)[line - 1]);
|
builder.append(source_string.split_view('\n', true)[position.value().line - 1]);
|
||||||
builder.append('\n');
|
builder.append('\n');
|
||||||
for (size_t i = 0; i < column - 1; ++i)
|
for (size_t i = 0; i < position.value().column - 1; ++i)
|
||||||
builder.append(spacer);
|
builder.append(spacer);
|
||||||
builder.append(indicator);
|
builder.append(indicator);
|
||||||
return builder.build();
|
return builder.build();
|
||||||
|
@ -156,13 +160,14 @@ private:
|
||||||
bool match(TokenType type) const;
|
bool match(TokenType type) const;
|
||||||
bool done() const;
|
bool done() const;
|
||||||
void expected(const char* what);
|
void expected(const char* what);
|
||||||
void syntax_error(const String& message, size_t line = 0, size_t column = 0);
|
void syntax_error(const String& message, Optional<Position> = {});
|
||||||
Token consume();
|
Token consume();
|
||||||
Token consume(TokenType type);
|
Token consume(TokenType type);
|
||||||
Token consume_and_validate_numeric_literal();
|
Token consume_and_validate_numeric_literal();
|
||||||
void consume_or_insert_semicolon();
|
void consume_or_insert_semicolon();
|
||||||
void save_state();
|
void save_state();
|
||||||
void load_state();
|
void load_state();
|
||||||
|
Position position() const;
|
||||||
|
|
||||||
struct ParserState {
|
struct ParserState {
|
||||||
Lexer m_lexer;
|
Lexer m_lexer;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue