mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 11:47:45 +00:00
LibJS: Implement parsing and execution of optional chains
This commit is contained in:
parent
4f7e14e0aa
commit
72ddaa31e3
6 changed files with 318 additions and 42 deletions
|
@ -125,44 +125,26 @@ Value ExpressionStatement::execute(Interpreter& interpreter, GlobalObject& globa
|
||||||
return m_expression->execute(interpreter, global_object);
|
return m_expression->execute(interpreter, global_object);
|
||||||
}
|
}
|
||||||
|
|
||||||
CallExpression::ThisAndCallee CallExpression::compute_this_and_callee(Interpreter& interpreter, GlobalObject& global_object) const
|
CallExpression::ThisAndCallee CallExpression::compute_this_and_callee(Interpreter& interpreter, GlobalObject& global_object, Reference const& callee_reference) const
|
||||||
{
|
{
|
||||||
auto& vm = interpreter.vm();
|
auto& vm = interpreter.vm();
|
||||||
|
|
||||||
if (is<MemberExpression>(*m_callee)) {
|
if (callee_reference.is_property_reference()) {
|
||||||
auto& member_expression = static_cast<MemberExpression const&>(*m_callee);
|
auto this_value = callee_reference.get_this_value();
|
||||||
Value callee;
|
auto callee = callee_reference.get_value(global_object);
|
||||||
Value this_value;
|
if (vm.exception())
|
||||||
|
return {};
|
||||||
if (is<SuperExpression>(member_expression.object())) {
|
|
||||||
auto super_base = interpreter.current_function_environment()->get_super_base();
|
|
||||||
if (super_base.is_nullish()) {
|
|
||||||
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeNullOrUndefinedOnSuperPropertyAccess, super_base.to_string_without_side_effects());
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
auto property_name = member_expression.computed_property_name(interpreter, global_object);
|
|
||||||
if (!property_name.is_valid())
|
|
||||||
return {};
|
|
||||||
auto reference = Reference { super_base, move(property_name), super_base, vm.in_strict_mode() };
|
|
||||||
callee = reference.get_value(global_object);
|
|
||||||
if (vm.exception())
|
|
||||||
return {};
|
|
||||||
this_value = &vm.this_value(global_object).as_object();
|
|
||||||
} else {
|
|
||||||
auto reference = member_expression.to_reference(interpreter, global_object);
|
|
||||||
if (vm.exception())
|
|
||||||
return {};
|
|
||||||
callee = reference.get_value(global_object);
|
|
||||||
if (vm.exception())
|
|
||||||
return {};
|
|
||||||
this_value = reference.get_this_value();
|
|
||||||
}
|
|
||||||
|
|
||||||
return { this_value, callee };
|
return { this_value, callee };
|
||||||
}
|
}
|
||||||
|
|
||||||
// [[Call]] will handle that in non-strict mode the this value becomes the global object
|
// [[Call]] will handle that in non-strict mode the this value becomes the global object
|
||||||
return { js_undefined(), m_callee->execute(interpreter, global_object) };
|
return {
|
||||||
|
js_undefined(),
|
||||||
|
callee_reference.is_unresolvable()
|
||||||
|
? m_callee->execute(interpreter, global_object)
|
||||||
|
: callee_reference.get_value(global_object)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 13.3.8.1 Runtime Semantics: ArgumentListEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-argumentlistevaluation
|
// 13.3.8.1 Runtime Semantics: ArgumentListEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-argumentlistevaluation
|
||||||
|
@ -233,7 +215,11 @@ Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_obj
|
||||||
{
|
{
|
||||||
InterpreterNodeScope node_scope { interpreter, *this };
|
InterpreterNodeScope node_scope { interpreter, *this };
|
||||||
auto& vm = interpreter.vm();
|
auto& vm = interpreter.vm();
|
||||||
auto [this_value, callee] = compute_this_and_callee(interpreter, global_object);
|
auto callee_reference = m_callee->to_reference(interpreter, global_object);
|
||||||
|
if (vm.exception())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto [this_value, callee] = compute_this_and_callee(interpreter, global_object, callee_reference);
|
||||||
if (vm.exception())
|
if (vm.exception())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
|
@ -251,7 +237,11 @@ Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_obj
|
||||||
|
|
||||||
auto& function = callee.as_function();
|
auto& function = callee.as_function();
|
||||||
|
|
||||||
if (&function == global_object.eval_function() && is<Identifier>(*m_callee) && static_cast<Identifier const&>(*m_callee).string() == vm.names.eval.as_string()) {
|
if (&function == global_object.eval_function()
|
||||||
|
&& callee_reference.is_environment_reference()
|
||||||
|
&& callee_reference.name().is_string()
|
||||||
|
&& callee_reference.name().as_string() == vm.names.eval.as_string()) {
|
||||||
|
|
||||||
auto script_value = arg_list.size() == 0 ? js_undefined() : arg_list[0];
|
auto script_value = arg_list.size() == 0 ? js_undefined() : arg_list[0];
|
||||||
return perform_eval(script_value, global_object, vm.in_strict_mode() ? CallerMode::Strict : CallerMode::NonStrict, EvalMode::Direct);
|
return perform_eval(script_value, global_object, vm.in_strict_mode() ? CallerMode::Strict : CallerMode::NonStrict, EvalMode::Direct);
|
||||||
}
|
}
|
||||||
|
@ -2011,6 +2001,92 @@ Value MemberExpression::execute(Interpreter& interpreter, GlobalObject& global_o
|
||||||
return reference.get_value(global_object);
|
return reference.get_value(global_object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OptionalChain::dump(int indent) const
|
||||||
|
{
|
||||||
|
print_indent(indent);
|
||||||
|
outln("{}", class_name());
|
||||||
|
m_base->dump(indent + 1);
|
||||||
|
for (auto& reference : m_references) {
|
||||||
|
reference.visit(
|
||||||
|
[&](Call const& call) {
|
||||||
|
print_indent(indent + 1);
|
||||||
|
outln("Call({})", call.mode == Mode::Optional ? "Optional" : "Not Optional");
|
||||||
|
for (auto& argument : call.arguments)
|
||||||
|
argument.value->dump(indent + 2);
|
||||||
|
},
|
||||||
|
[&](ComputedReference const& ref) {
|
||||||
|
print_indent(indent + 1);
|
||||||
|
outln("ComputedReference({})", ref.mode == Mode::Optional ? "Optional" : "Not Optional");
|
||||||
|
ref.expression->dump(indent + 2);
|
||||||
|
},
|
||||||
|
[&](MemberReference const& ref) {
|
||||||
|
print_indent(indent + 1);
|
||||||
|
outln("MemberReference({})", ref.mode == Mode::Optional ? "Optional" : "Not Optional");
|
||||||
|
ref.identifier->dump(indent + 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<OptionalChain::ReferenceAndValue> OptionalChain::to_reference_and_value(JS::Interpreter& interpreter, JS::GlobalObject& global_object) const
|
||||||
|
{
|
||||||
|
// Note: This is wrapped in an optional to allow base_reference = ...
|
||||||
|
Optional<JS::Reference> base_reference = m_base->to_reference(interpreter, global_object);
|
||||||
|
auto base = base_reference->is_unresolvable() ? m_base->execute(interpreter, global_object) : base_reference->get_value(global_object);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
for (auto& reference : m_references) {
|
||||||
|
auto is_optional = reference.visit([](auto& ref) { return ref.mode; }) == Mode::Optional;
|
||||||
|
if (is_optional && base.is_nullish())
|
||||||
|
return ReferenceAndValue { {}, js_undefined() };
|
||||||
|
|
||||||
|
auto expression = reference.visit(
|
||||||
|
[&](Call const& call) -> NonnullRefPtr<Expression> {
|
||||||
|
return create_ast_node<CallExpression>(source_range(),
|
||||||
|
create_ast_node<SyntheticReferenceExpression>(source_range(), *base_reference, base),
|
||||||
|
call.arguments);
|
||||||
|
},
|
||||||
|
[&](ComputedReference const& ref) -> NonnullRefPtr<Expression> {
|
||||||
|
return create_ast_node<MemberExpression>(source_range(),
|
||||||
|
create_ast_node<SyntheticReferenceExpression>(source_range(), *base_reference, base),
|
||||||
|
ref.expression,
|
||||||
|
true);
|
||||||
|
},
|
||||||
|
[&](MemberReference const& ref) -> NonnullRefPtr<Expression> {
|
||||||
|
return create_ast_node<MemberExpression>(source_range(),
|
||||||
|
create_ast_node<SyntheticReferenceExpression>(source_range(), *base_reference, base),
|
||||||
|
ref.identifier,
|
||||||
|
false);
|
||||||
|
});
|
||||||
|
if (is<CallExpression>(*expression)) {
|
||||||
|
base_reference = JS::Reference {};
|
||||||
|
base = expression->execute(interpreter, global_object);
|
||||||
|
} else {
|
||||||
|
base_reference = expression->to_reference(interpreter, global_object);
|
||||||
|
base = base_reference->get_value(global_object);
|
||||||
|
}
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReferenceAndValue { base_reference.release_value(), base };
|
||||||
|
}
|
||||||
|
|
||||||
|
Value OptionalChain::execute(Interpreter& interpreter, GlobalObject& global_object) const
|
||||||
|
{
|
||||||
|
InterpreterNodeScope node_scope { interpreter, *this };
|
||||||
|
if (auto result = to_reference_and_value(interpreter, global_object); result.has_value())
|
||||||
|
return result.release_value().value;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
JS::Reference OptionalChain::to_reference(Interpreter& interpreter, GlobalObject& global_object) const
|
||||||
|
{
|
||||||
|
if (auto result = to_reference_and_value(interpreter, global_object); result.has_value())
|
||||||
|
return result.release_value().reference;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
void MetaProperty::dump(int indent) const
|
void MetaProperty::dump(int indent) const
|
||||||
{
|
{
|
||||||
String name;
|
String name;
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
#include <LibJS/Forward.h>
|
#include <LibJS/Forward.h>
|
||||||
#include <LibJS/Runtime/PropertyName.h>
|
#include <LibJS/Runtime/PropertyName.h>
|
||||||
|
#include <LibJS/Runtime/Reference.h>
|
||||||
#include <LibJS/Runtime/Value.h>
|
#include <LibJS/Runtime/Value.h>
|
||||||
#include <LibJS/SourceRange.h>
|
#include <LibJS/SourceRange.h>
|
||||||
#include <LibRegex/Regex.h>
|
#include <LibRegex/Regex.h>
|
||||||
|
@ -1069,7 +1070,7 @@ private:
|
||||||
Value callee;
|
Value callee;
|
||||||
};
|
};
|
||||||
|
|
||||||
ThisAndCallee compute_this_and_callee(Interpreter&, GlobalObject&) const;
|
ThisAndCallee compute_this_and_callee(Interpreter&, GlobalObject&, Reference const&) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NewExpression final : public CallExpression {
|
class NewExpression final : public CallExpression {
|
||||||
|
@ -1384,6 +1385,50 @@ private:
|
||||||
bool m_computed { false };
|
bool m_computed { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class OptionalChain final : public Expression {
|
||||||
|
public:
|
||||||
|
enum class Mode {
|
||||||
|
Optional,
|
||||||
|
NotOptional,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Call {
|
||||||
|
Vector<CallExpression::Argument> arguments;
|
||||||
|
Mode mode;
|
||||||
|
};
|
||||||
|
struct ComputedReference {
|
||||||
|
NonnullRefPtr<Expression> expression;
|
||||||
|
Mode mode;
|
||||||
|
};
|
||||||
|
struct MemberReference {
|
||||||
|
NonnullRefPtr<Identifier> identifier;
|
||||||
|
Mode mode;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Reference = Variant<Call, ComputedReference, MemberReference>;
|
||||||
|
|
||||||
|
OptionalChain(SourceRange source_range, NonnullRefPtr<Expression> base, Vector<Reference> references)
|
||||||
|
: Expression(source_range)
|
||||||
|
, m_base(move(base))
|
||||||
|
, m_references(move(references))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Value execute(Interpreter& interpreter, GlobalObject& global_object) const override;
|
||||||
|
virtual JS::Reference to_reference(Interpreter& interpreter, GlobalObject& global_object) const override;
|
||||||
|
virtual void dump(int indent) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ReferenceAndValue {
|
||||||
|
JS::Reference reference;
|
||||||
|
Value value;
|
||||||
|
};
|
||||||
|
Optional<ReferenceAndValue> to_reference_and_value(Interpreter&, GlobalObject&) const;
|
||||||
|
|
||||||
|
NonnullRefPtr<Expression> m_base;
|
||||||
|
Vector<Reference> m_references;
|
||||||
|
};
|
||||||
|
|
||||||
class MetaProperty final : public Expression {
|
class MetaProperty final : public Expression {
|
||||||
public:
|
public:
|
||||||
enum class Type {
|
enum class Type {
|
||||||
|
@ -1576,6 +1621,23 @@ public:
|
||||||
virtual void generate_bytecode(Bytecode::Generator&) const override;
|
virtual void generate_bytecode(Bytecode::Generator&) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SyntheticReferenceExpression final : public Expression {
|
||||||
|
public:
|
||||||
|
explicit SyntheticReferenceExpression(SourceRange source_range, Reference reference, Value value)
|
||||||
|
: Expression(source_range)
|
||||||
|
, m_reference(move(reference))
|
||||||
|
, m_value(value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Value execute(Interpreter&, GlobalObject&) const override { return m_value; }
|
||||||
|
virtual Reference to_reference(Interpreter&, GlobalObject&) const override { return m_reference; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Reference m_reference;
|
||||||
|
Value m_value;
|
||||||
|
};
|
||||||
|
|
||||||
template<typename C>
|
template<typename C>
|
||||||
void BindingPattern::for_each_bound_name(C&& callback) const
|
void BindingPattern::for_each_bound_name(C&& callback) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -1521,6 +1521,15 @@ NonnullRefPtr<Expression> Parser::parse_secondary_expression(NonnullRefPtr<Expre
|
||||||
return parse_assignment_expression(AssignmentOp::NullishAssignment, move(lhs), min_precedence, associativity);
|
return parse_assignment_expression(AssignmentOp::NullishAssignment, move(lhs), min_precedence, associativity);
|
||||||
case TokenType::QuestionMark:
|
case TokenType::QuestionMark:
|
||||||
return parse_conditional_expression(move(lhs));
|
return parse_conditional_expression(move(lhs));
|
||||||
|
case TokenType::QuestionMarkPeriod:
|
||||||
|
// FIXME: This should allow `(new Foo)?.bar', but as our parser strips parenthesis,
|
||||||
|
// we can't really tell if `lhs' was parenthesized at this point.
|
||||||
|
if (is<NewExpression>(lhs.ptr())) {
|
||||||
|
syntax_error("'new' cannot be used with optional chaining", position());
|
||||||
|
consume();
|
||||||
|
return lhs;
|
||||||
|
}
|
||||||
|
return parse_optional_chain(move(lhs));
|
||||||
default:
|
default:
|
||||||
expected("secondary expression");
|
expected("secondary expression");
|
||||||
consume();
|
consume();
|
||||||
|
@ -1620,16 +1629,11 @@ NonnullRefPtr<Identifier> Parser::parse_identifier()
|
||||||
token.value());
|
token.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
NonnullRefPtr<Expression> Parser::parse_call_expression(NonnullRefPtr<Expression> lhs)
|
Vector<CallExpression::Argument> Parser::parse_arguments()
|
||||||
{
|
{
|
||||||
auto rule_start = push_start();
|
|
||||||
if (!m_state.allow_super_constructor_call && is<SuperExpression>(*lhs))
|
|
||||||
syntax_error("'super' keyword unexpected here");
|
|
||||||
|
|
||||||
consume(TokenType::ParenOpen);
|
|
||||||
|
|
||||||
Vector<CallExpression::Argument> arguments;
|
Vector<CallExpression::Argument> arguments;
|
||||||
|
|
||||||
|
consume(TokenType::ParenOpen);
|
||||||
while (match_expression() || match(TokenType::TripleDot)) {
|
while (match_expression() || match(TokenType::TripleDot)) {
|
||||||
if (match(TokenType::TripleDot)) {
|
if (match(TokenType::TripleDot)) {
|
||||||
consume();
|
consume();
|
||||||
|
@ -1643,6 +1647,16 @@ NonnullRefPtr<Expression> Parser::parse_call_expression(NonnullRefPtr<Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
consume(TokenType::ParenClose);
|
consume(TokenType::ParenClose);
|
||||||
|
return arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
NonnullRefPtr<Expression> Parser::parse_call_expression(NonnullRefPtr<Expression> lhs)
|
||||||
|
{
|
||||||
|
auto rule_start = push_start();
|
||||||
|
if (!m_state.allow_super_constructor_call && is<SuperExpression>(*lhs))
|
||||||
|
syntax_error("'super' keyword unexpected here");
|
||||||
|
|
||||||
|
auto arguments = parse_arguments();
|
||||||
|
|
||||||
if (is<SuperExpression>(*lhs))
|
if (is<SuperExpression>(*lhs))
|
||||||
return create_ast_node<SuperCall>({ m_state.current_token.filename(), rule_start.position(), position() }, move(arguments));
|
return create_ast_node<SuperCall>({ m_state.current_token.filename(), rule_start.position(), position() }, move(arguments));
|
||||||
|
@ -1655,7 +1669,7 @@ NonnullRefPtr<NewExpression> Parser::parse_new_expression()
|
||||||
auto rule_start = push_start();
|
auto rule_start = push_start();
|
||||||
consume(TokenType::New);
|
consume(TokenType::New);
|
||||||
|
|
||||||
auto callee = parse_expression(g_operator_precedence.get(TokenType::New), Associativity::Right, { TokenType::ParenOpen });
|
auto callee = parse_expression(g_operator_precedence.get(TokenType::New), Associativity::Right, { TokenType::ParenOpen, TokenType::QuestionMarkPeriod });
|
||||||
|
|
||||||
Vector<CallExpression::Argument> arguments;
|
Vector<CallExpression::Argument> arguments;
|
||||||
|
|
||||||
|
@ -2372,6 +2386,80 @@ NonnullRefPtr<ConditionalExpression> Parser::parse_conditional_expression(Nonnul
|
||||||
return create_ast_node<ConditionalExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, move(test), move(consequent), move(alternate));
|
return create_ast_node<ConditionalExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, move(test), move(consequent), move(alternate));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NonnullRefPtr<OptionalChain> Parser::parse_optional_chain(NonnullRefPtr<Expression> base)
|
||||||
|
{
|
||||||
|
auto rule_start = push_start();
|
||||||
|
Vector<OptionalChain::Reference> chain;
|
||||||
|
do {
|
||||||
|
if (match(TokenType::QuestionMarkPeriod)) {
|
||||||
|
consume(TokenType::QuestionMarkPeriod);
|
||||||
|
switch (m_state.current_token.type()) {
|
||||||
|
case TokenType::ParenOpen:
|
||||||
|
chain.append(OptionalChain::Call { parse_arguments(), OptionalChain::Mode::Optional });
|
||||||
|
break;
|
||||||
|
case TokenType::BracketOpen:
|
||||||
|
consume();
|
||||||
|
chain.append(OptionalChain::ComputedReference { parse_expression(0), OptionalChain::Mode::Optional });
|
||||||
|
consume(TokenType::BracketClose);
|
||||||
|
break;
|
||||||
|
case TokenType::TemplateLiteralStart:
|
||||||
|
// 13.3.1.1 - Static Semantics: Early Errors
|
||||||
|
// OptionalChain :
|
||||||
|
// ?. TemplateLiteral
|
||||||
|
// OptionalChain TemplateLiteral
|
||||||
|
// This is a hard error.
|
||||||
|
syntax_error("Invalid tagged template literal after ?.", position());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (match_identifier_name()) {
|
||||||
|
auto start = position();
|
||||||
|
auto identifier = consume();
|
||||||
|
chain.append(OptionalChain::MemberReference {
|
||||||
|
create_ast_node<Identifier>({ m_state.current_token.filename(), start, position() }, identifier.value()),
|
||||||
|
OptionalChain::Mode::Optional,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
syntax_error("Invalid optional chain reference after ?.", position());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (match(TokenType::ParenOpen)) {
|
||||||
|
chain.append(OptionalChain::Call { parse_arguments(), OptionalChain::Mode::NotOptional });
|
||||||
|
} else if (match(TokenType::Period)) {
|
||||||
|
consume();
|
||||||
|
if (match_identifier_name()) {
|
||||||
|
auto start = position();
|
||||||
|
auto identifier = consume();
|
||||||
|
chain.append(OptionalChain::MemberReference {
|
||||||
|
create_ast_node<Identifier>({ m_state.current_token.filename(), start, position() }, identifier.value()),
|
||||||
|
OptionalChain::Mode::NotOptional,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
expected("an identifier");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (match(TokenType::TemplateLiteralStart)) {
|
||||||
|
// 13.3.1.1 - Static Semantics: Early Errors
|
||||||
|
// OptionalChain :
|
||||||
|
// ?. TemplateLiteral
|
||||||
|
// OptionalChain TemplateLiteral
|
||||||
|
syntax_error("Invalid tagged template literal after optional chain", position());
|
||||||
|
break;
|
||||||
|
} else if (match(TokenType::BracketOpen)) {
|
||||||
|
consume();
|
||||||
|
chain.append(OptionalChain::ComputedReference { parse_expression(2), OptionalChain::Mode::NotOptional });
|
||||||
|
consume(TokenType::BracketClose);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (!done());
|
||||||
|
|
||||||
|
return create_ast_node<OptionalChain>(
|
||||||
|
{ m_state.current_token.filename(), rule_start.position(), position() },
|
||||||
|
move(base),
|
||||||
|
move(chain));
|
||||||
|
}
|
||||||
|
|
||||||
NonnullRefPtr<TryStatement> Parser::parse_try_statement()
|
NonnullRefPtr<TryStatement> Parser::parse_try_statement()
|
||||||
{
|
{
|
||||||
auto rule_start = push_start();
|
auto rule_start = push_start();
|
||||||
|
@ -2788,7 +2876,8 @@ bool Parser::match_secondary_expression(const Vector<TokenType>& forbidden) cons
|
||||||
|| type == TokenType::DoublePipe
|
|| type == TokenType::DoublePipe
|
||||||
|| type == TokenType::DoublePipeEquals
|
|| type == TokenType::DoublePipeEquals
|
||||||
|| type == TokenType::DoubleQuestionMark
|
|| type == TokenType::DoubleQuestionMark
|
||||||
|| type == TokenType::DoubleQuestionMarkEquals;
|
|| type == TokenType::DoubleQuestionMarkEquals
|
||||||
|
|| type == TokenType::QuestionMarkPeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Parser::match_statement() const
|
bool Parser::match_statement() const
|
||||||
|
|
|
@ -76,6 +76,7 @@ public:
|
||||||
NonnullRefPtr<WithStatement> parse_with_statement();
|
NonnullRefPtr<WithStatement> parse_with_statement();
|
||||||
NonnullRefPtr<DebuggerStatement> parse_debugger_statement();
|
NonnullRefPtr<DebuggerStatement> parse_debugger_statement();
|
||||||
NonnullRefPtr<ConditionalExpression> parse_conditional_expression(NonnullRefPtr<Expression> test);
|
NonnullRefPtr<ConditionalExpression> parse_conditional_expression(NonnullRefPtr<Expression> test);
|
||||||
|
NonnullRefPtr<OptionalChain> parse_optional_chain(NonnullRefPtr<Expression> base);
|
||||||
NonnullRefPtr<Expression> parse_expression(int min_precedence, Associativity associate = Associativity::Right, const Vector<TokenType>& forbidden = {});
|
NonnullRefPtr<Expression> parse_expression(int min_precedence, Associativity associate = Associativity::Right, const Vector<TokenType>& forbidden = {});
|
||||||
PrimaryExpressionParseResult parse_primary_expression();
|
PrimaryExpressionParseResult parse_primary_expression();
|
||||||
NonnullRefPtr<Expression> parse_unary_prefixed_expression();
|
NonnullRefPtr<Expression> parse_unary_prefixed_expression();
|
||||||
|
@ -100,6 +101,8 @@ public:
|
||||||
RefPtr<Statement> try_parse_labelled_statement(AllowLabelledFunction allow_function);
|
RefPtr<Statement> try_parse_labelled_statement(AllowLabelledFunction allow_function);
|
||||||
RefPtr<MetaProperty> try_parse_new_target_expression();
|
RefPtr<MetaProperty> try_parse_new_target_expression();
|
||||||
|
|
||||||
|
Vector<CallExpression::Argument> parse_arguments();
|
||||||
|
|
||||||
struct Error {
|
struct Error {
|
||||||
String message;
|
String message;
|
||||||
Optional<Position> position;
|
Optional<Position> position;
|
||||||
|
|
|
@ -97,6 +97,12 @@ public:
|
||||||
return !m_this_value.is_empty();
|
return !m_this_value.is_empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: Non-standard helper.
|
||||||
|
bool is_environment_reference() const
|
||||||
|
{
|
||||||
|
return m_base_type == BaseType::Environment;
|
||||||
|
}
|
||||||
|
|
||||||
void put_value(GlobalObject&, Value);
|
void put_value(GlobalObject&, Value);
|
||||||
Value get_value(GlobalObject&, bool throw_if_undefined = true) const;
|
Value get_value(GlobalObject&, bool throw_if_undefined = true) const;
|
||||||
bool delete_(GlobalObject&);
|
bool delete_(GlobalObject&);
|
||||||
|
|
40
Userland/Libraries/LibJS/Tests/syntax/optional-chaining.js
Normal file
40
Userland/Libraries/LibJS/Tests/syntax/optional-chaining.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
test("parse optional-chaining", () => {
|
||||||
|
expect(`a?.b`).toEval();
|
||||||
|
expect(`a?.4:.5`).toEval();
|
||||||
|
expect(`a?.[b]`).toEval();
|
||||||
|
expect(`a?.b[b]`).toEval();
|
||||||
|
expect(`a?.b(c)`).toEval();
|
||||||
|
expect(`a?.b?.(c, d)`).toEval();
|
||||||
|
expect(`a?.b?.()`).toEval();
|
||||||
|
expect("a?.b``").not.toEval();
|
||||||
|
expect("a?.b?.``").not.toEval();
|
||||||
|
expect("new Foo?.bar").not.toEval();
|
||||||
|
expect("new (Foo?.bar)").toEval();
|
||||||
|
// FIXME: This should pass.
|
||||||
|
// expect("(new Foo)?.bar").toEval();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("evaluate optional-chaining", () => {
|
||||||
|
for (let nullishObject of [null, undefined]) {
|
||||||
|
expect((() => nullishObject?.b)()).toBeUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(
|
||||||
|
(() => {
|
||||||
|
let a = {};
|
||||||
|
return a?.foo?.bar?.baz;
|
||||||
|
})()
|
||||||
|
).toBeUndefined();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
(() => {
|
||||||
|
let a = { foo: { bar: () => 42 } };
|
||||||
|
return `${a?.foo?.bar?.()}-${a?.foo?.baz?.()}`;
|
||||||
|
})()
|
||||||
|
).toBe("42-undefined");
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
let a = { foo: { bar: () => 42 } };
|
||||||
|
return a.foo?.baz.nonExistentProperty;
|
||||||
|
}).toThrow();
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue