mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 07:30:19 +00:00
LibJS: Implement 'new.target'
This adds a new MetaProperty AST node which will be used for 'new.target' and 'import.meta' meta properties. The parser now distinguishes between "in function context" and "in arrow function context" (which is required for this). When encountering TokenType::New we will attempt to parse it as meta property and resort to regular new expression parsing if that fails, much like the parsing of labelled statements.
This commit is contained in:
parent
e07a39c816
commit
39a1c9d827
Notes:
sideshowbarker
2024-07-19 01:34:39 +09:00
Author: https://github.com/linusg Commit: https://github.com/SerenityOS/serenity/commit/39a1c9d8276 Pull-request: https://github.com/SerenityOS/serenity/pull/3923 Reviewed-by: https://github.com/awesomekling
6 changed files with 109 additions and 10 deletions
|
@ -1655,6 +1655,28 @@ Value MemberExpression::execute(Interpreter& interpreter, GlobalObject& global_o
|
|||
return object_result->get(property_name).value_or(js_undefined());
|
||||
}
|
||||
|
||||
void MetaProperty::dump(int indent) const
|
||||
{
|
||||
String name;
|
||||
if (m_type == MetaProperty::Type::NewTarget)
|
||||
name = "new.target";
|
||||
else if (m_type == MetaProperty::Type::ImportMeta)
|
||||
name = "import.meta";
|
||||
else
|
||||
ASSERT_NOT_REACHED();
|
||||
print_indent(indent);
|
||||
printf("%s %s\n", class_name(), name.characters());
|
||||
}
|
||||
|
||||
Value MetaProperty::execute(Interpreter& interpreter, GlobalObject&) const
|
||||
{
|
||||
if (m_type == MetaProperty::Type::NewTarget)
|
||||
return interpreter.vm().get_new_target().value_or(js_undefined());
|
||||
if (m_type == MetaProperty::Type::ImportMeta)
|
||||
TODO();
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
Value StringLiteral::execute(Interpreter& interpreter, GlobalObject&) const
|
||||
{
|
||||
return js_string(interpreter.heap(), m_value);
|
||||
|
|
|
@ -1084,6 +1084,27 @@ private:
|
|||
bool m_computed { false };
|
||||
};
|
||||
|
||||
class MetaProperty final : public Expression {
|
||||
public:
|
||||
enum class Type {
|
||||
NewTarget,
|
||||
ImportMeta,
|
||||
};
|
||||
|
||||
MetaProperty(Type type)
|
||||
: m_type(type)
|
||||
{
|
||||
}
|
||||
|
||||
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||
virtual void dump(int indent) const override;
|
||||
|
||||
private:
|
||||
virtual const char* class_name() const override { return "MetaProperty"; }
|
||||
|
||||
Type m_type;
|
||||
};
|
||||
|
||||
class ConditionalExpression final : public Expression {
|
||||
public:
|
||||
ConditionalExpression(NonnullRefPtr<Expression> test, NonnullRefPtr<Expression> consequent, NonnullRefPtr<Expression> alternate)
|
||||
|
|
|
@ -401,7 +401,7 @@ RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expe
|
|||
bool is_strict = false;
|
||||
|
||||
auto function_body_result = [&]() -> RefPtr<BlockStatement> {
|
||||
TemporaryChange change(m_parser_state.m_in_function_context, true);
|
||||
TemporaryChange change(m_parser_state.m_in_arrow_function_context, true);
|
||||
if (match(TokenType::CurlyOpen)) {
|
||||
// Parse a function body with statements
|
||||
return parse_block_statement(is_strict);
|
||||
|
@ -454,6 +454,26 @@ RefPtr<Statement> Parser::try_parse_labelled_statement()
|
|||
return statement;
|
||||
}
|
||||
|
||||
RefPtr<MetaProperty> Parser::try_parse_new_target_expression()
|
||||
{
|
||||
save_state();
|
||||
ArmedScopeGuard state_rollback_guard = [&] {
|
||||
load_state();
|
||||
};
|
||||
|
||||
consume(TokenType::New);
|
||||
if (!match(TokenType::Period))
|
||||
return {};
|
||||
consume();
|
||||
if (!match(TokenType::Identifier))
|
||||
return {};
|
||||
if (consume().value() != "target")
|
||||
return {};
|
||||
|
||||
state_rollback_guard.disarm();
|
||||
return create_ast_node<MetaProperty>(MetaProperty::Type::NewTarget);
|
||||
}
|
||||
|
||||
NonnullRefPtr<ClassDeclaration> Parser::parse_class_declaration()
|
||||
{
|
||||
return create_ast_node<ClassDeclaration>(parse_class_expression(true));
|
||||
|
@ -593,9 +613,8 @@ NonnullRefPtr<Expression> Parser::parse_primary_expression()
|
|||
consume(TokenType::ParenOpen);
|
||||
if (match(TokenType::ParenClose) || match(TokenType::Identifier) || match(TokenType::TripleDot)) {
|
||||
auto arrow_function_result = try_parse_arrow_function_expression(true);
|
||||
if (!arrow_function_result.is_null()) {
|
||||
if (!arrow_function_result.is_null())
|
||||
return arrow_function_result.release_nonnull();
|
||||
}
|
||||
}
|
||||
auto expression = parse_expression(0);
|
||||
consume(TokenType::ParenClose);
|
||||
|
@ -613,9 +632,8 @@ NonnullRefPtr<Expression> Parser::parse_primary_expression()
|
|||
return create_ast_node<SuperExpression>();
|
||||
case TokenType::Identifier: {
|
||||
auto arrow_function_result = try_parse_arrow_function_expression(false);
|
||||
if (!arrow_function_result.is_null()) {
|
||||
if (!arrow_function_result.is_null())
|
||||
return arrow_function_result.release_nonnull();
|
||||
}
|
||||
return create_ast_node<Identifier>(consume().value());
|
||||
}
|
||||
case TokenType::NumericLiteral:
|
||||
|
@ -639,8 +657,16 @@ NonnullRefPtr<Expression> Parser::parse_primary_expression()
|
|||
return parse_regexp_literal();
|
||||
case TokenType::TemplateLiteralStart:
|
||||
return parse_template_literal(false);
|
||||
case TokenType::New:
|
||||
case TokenType::New: {
|
||||
auto new_start = position();
|
||||
auto new_target_result = try_parse_new_target_expression();
|
||||
if (!new_target_result.is_null()) {
|
||||
if (!m_parser_state.m_in_function_context)
|
||||
syntax_error("'new.target' not allowed outside of a function", new_start);
|
||||
return new_target_result.release_nonnull();
|
||||
}
|
||||
return parse_new_expression();
|
||||
}
|
||||
default:
|
||||
expected("primary expression");
|
||||
consume();
|
||||
|
@ -1191,7 +1217,7 @@ NonnullRefPtr<NewExpression> Parser::parse_new_expression()
|
|||
|
||||
NonnullRefPtr<ReturnStatement> Parser::parse_return_statement()
|
||||
{
|
||||
if (!m_parser_state.m_in_function_context)
|
||||
if (!m_parser_state.m_in_function_context && !m_parser_state.m_in_arrow_function_context)
|
||||
syntax_error("'return' not allowed outside of a function");
|
||||
|
||||
consume(TokenType::Return);
|
||||
|
|
|
@ -81,7 +81,6 @@ public:
|
|||
NonnullRefPtr<WhileStatement> parse_while_statement();
|
||||
NonnullRefPtr<DebuggerStatement> parse_debugger_statement();
|
||||
NonnullRefPtr<ConditionalExpression> parse_conditional_expression(NonnullRefPtr<Expression> test);
|
||||
|
||||
NonnullRefPtr<Expression> parse_expression(int min_precedence, Associativity associate = Associativity::Right, Vector<TokenType> forbidden = {});
|
||||
NonnullRefPtr<Expression> parse_primary_expression();
|
||||
NonnullRefPtr<Expression> parse_unary_prefixed_expression();
|
||||
|
@ -93,13 +92,15 @@ public:
|
|||
NonnullRefPtr<Expression> parse_secondary_expression(NonnullRefPtr<Expression>, int min_precedence, Associativity associate = Associativity::Right);
|
||||
NonnullRefPtr<CallExpression> parse_call_expression(NonnullRefPtr<Expression>);
|
||||
NonnullRefPtr<NewExpression> parse_new_expression();
|
||||
RefPtr<FunctionExpression> try_parse_arrow_function_expression(bool expect_parens);
|
||||
RefPtr<Statement> try_parse_labelled_statement();
|
||||
NonnullRefPtr<ClassDeclaration> parse_class_declaration();
|
||||
NonnullRefPtr<ClassExpression> parse_class_expression(bool expect_class_name);
|
||||
NonnullRefPtr<Expression> parse_property_key();
|
||||
NonnullRefPtr<AssignmentExpression> parse_assignment_expression(AssignmentOp, NonnullRefPtr<Expression> lhs, int min_precedence, Associativity);
|
||||
|
||||
RefPtr<FunctionExpression> try_parse_arrow_function_expression(bool expect_parens);
|
||||
RefPtr<Statement> try_parse_labelled_statement();
|
||||
RefPtr<MetaProperty> try_parse_new_target_expression();
|
||||
|
||||
struct Position {
|
||||
size_t line;
|
||||
size_t column;
|
||||
|
@ -181,6 +182,7 @@ private:
|
|||
bool m_allow_super_property_lookup { false };
|
||||
bool m_allow_super_constructor_call { false };
|
||||
bool m_in_function_context { false };
|
||||
bool m_in_arrow_function_context { false };
|
||||
bool m_in_break_context { false };
|
||||
bool m_in_continue_context { false };
|
||||
bool m_string_legacy_octal_escape_sequence_in_scope { false };
|
||||
|
|
|
@ -105,6 +105,8 @@ LexicalEnvironment* ScriptFunction::create_environment()
|
|||
auto* environment = heap().allocate<LexicalEnvironment>(global_object(), move(variables), m_parent_environment, LexicalEnvironment::EnvironmentRecordType::Function);
|
||||
environment->set_home_object(home_object());
|
||||
environment->set_current_function(*this);
|
||||
if (m_is_arrow_function)
|
||||
environment->set_new_target(m_parent_environment->new_target());
|
||||
return environment;
|
||||
}
|
||||
|
||||
|
|
26
Libraries/LibJS/Tests/functions/function-new-target.js
Normal file
26
Libraries/LibJS/Tests/functions/function-new-target.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
test("basic functionality", () => {
|
||||
function foo() {
|
||||
return new.target;
|
||||
}
|
||||
expect(foo()).toBeUndefined();
|
||||
expect(new foo()).toEqual(foo);
|
||||
|
||||
function bar() {
|
||||
const baz = () => new.target;
|
||||
return baz();
|
||||
}
|
||||
expect(bar()).toBeUndefined();
|
||||
expect(new bar()).toEqual(bar);
|
||||
|
||||
class baz {
|
||||
constructor() {
|
||||
this.newTarget = new.target;
|
||||
}
|
||||
}
|
||||
expect(new baz().newTarget).toEqual(baz);
|
||||
});
|
||||
|
||||
// FIXME: This does not work as expected as toEval() places the code inside a function :|
|
||||
test.skip("syntax error outside of function", () => {
|
||||
expect("new.target").not.toEval();
|
||||
});
|
Loading…
Reference in a new issue