mirror of
https://github.com/RGBCube/serenity
synced 2025-05-28 19:25:10 +00:00
LibJS: Implement basic for..in and for..of loops
This commit is contained in:
parent
c378a1c730
commit
07af2e6b2c
8 changed files with 372 additions and 90 deletions
|
@ -322,6 +322,128 @@ Value ForStatement::execute(Interpreter& interpreter) const
|
||||||
return last_value;
|
return last_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static FlyString variable_from_for_declaration(Interpreter& interpreter, NonnullRefPtr<ASTNode> node, RefPtr<BlockStatement> wrapper)
|
||||||
|
{
|
||||||
|
FlyString variable_name;
|
||||||
|
if (node->is_variable_declaration()) {
|
||||||
|
auto* variable_declaration = static_cast<const VariableDeclaration*>(node.ptr());
|
||||||
|
ASSERT(!variable_declaration->declarations().is_empty());
|
||||||
|
if (variable_declaration->declaration_kind() != DeclarationKind::Var) {
|
||||||
|
wrapper = create_ast_node<BlockStatement>();
|
||||||
|
interpreter.enter_scope(*wrapper, {}, ScopeType::Block);
|
||||||
|
}
|
||||||
|
variable_declaration->execute(interpreter);
|
||||||
|
variable_name = variable_declaration->declarations().first().id().string();
|
||||||
|
} else if (node->is_identifier()) {
|
||||||
|
variable_name = static_cast<const Identifier&>(*node).string();
|
||||||
|
} else {
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
|
}
|
||||||
|
return variable_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value ForInStatement::execute(Interpreter& interpreter) const
|
||||||
|
{
|
||||||
|
if (!m_lhs->is_variable_declaration() && !m_lhs->is_identifier()) {
|
||||||
|
// FIXME: Implement "for (foo.bar in baz)", "for (foo[0] in bar)"
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
|
}
|
||||||
|
RefPtr<BlockStatement> wrapper;
|
||||||
|
auto variable_name = variable_from_for_declaration(interpreter, m_lhs, wrapper);
|
||||||
|
auto wrapper_cleanup = ScopeGuard([&] {
|
||||||
|
if (wrapper)
|
||||||
|
interpreter.exit_scope(*wrapper);
|
||||||
|
});
|
||||||
|
auto last_value = js_undefined();
|
||||||
|
auto rhs_result = m_rhs->execute(interpreter);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
auto* object = rhs_result.to_object(interpreter);
|
||||||
|
while (object) {
|
||||||
|
auto property_names = object->get_own_properties(*object, Object::GetOwnPropertyMode::Key, Attribute::Enumerable);
|
||||||
|
for (auto& property_name : static_cast<Array&>(property_names.as_object()).elements()) {
|
||||||
|
interpreter.set_variable(variable_name, property_name);
|
||||||
|
last_value = interpreter.run(*m_body);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
if (interpreter.should_unwind()) {
|
||||||
|
if (interpreter.should_unwind_until(ScopeType::Continuable)) {
|
||||||
|
interpreter.stop_unwind();
|
||||||
|
} else if (interpreter.should_unwind_until(ScopeType::Breakable)) {
|
||||||
|
interpreter.stop_unwind();
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
return js_undefined();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
object = object->prototype();
|
||||||
|
}
|
||||||
|
return last_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value ForOfStatement::execute(Interpreter& interpreter) const
|
||||||
|
{
|
||||||
|
if (!m_lhs->is_variable_declaration() && !m_lhs->is_identifier()) {
|
||||||
|
// FIXME: Implement "for (foo.bar of baz)", "for (foo[0] of bar)"
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
|
}
|
||||||
|
RefPtr<BlockStatement> wrapper;
|
||||||
|
auto variable_name = variable_from_for_declaration(interpreter, m_lhs, wrapper);
|
||||||
|
auto wrapper_cleanup = ScopeGuard([&] {
|
||||||
|
if (wrapper)
|
||||||
|
interpreter.exit_scope(*wrapper);
|
||||||
|
});
|
||||||
|
auto last_value = js_undefined();
|
||||||
|
auto rhs_result = m_rhs->execute(interpreter);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
// FIXME: We need to properly implement the iterator protocol
|
||||||
|
auto is_iterable = rhs_result.is_array() || rhs_result.is_string() || (rhs_result.is_object() && rhs_result.as_object().is_string_object());
|
||||||
|
if (!is_iterable)
|
||||||
|
return interpreter.throw_exception<TypeError>("for..of right-hand side must be iterable");
|
||||||
|
|
||||||
|
size_t index = 0;
|
||||||
|
auto next = [&]() -> Optional<Value> {
|
||||||
|
if (rhs_result.is_array()) {
|
||||||
|
auto array_elements = static_cast<Array*>(&rhs_result.as_object())->elements();
|
||||||
|
if (index < array_elements.size())
|
||||||
|
return Value(array_elements.at(index));
|
||||||
|
} else if (rhs_result.is_string()) {
|
||||||
|
auto string = rhs_result.as_string().string();
|
||||||
|
if (index < string.length())
|
||||||
|
return js_string(interpreter, string.substring(index, 1));
|
||||||
|
} else if (rhs_result.is_object() && rhs_result.as_object().is_string_object()) {
|
||||||
|
auto string = static_cast<StringObject*>(&rhs_result.as_object())->primitive_string().string();
|
||||||
|
if (index < string.length())
|
||||||
|
return js_string(interpreter, string.substring(index, 1));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
auto next_item = next();
|
||||||
|
if (!next_item.has_value())
|
||||||
|
break;
|
||||||
|
interpreter.set_variable(variable_name, next_item.value());
|
||||||
|
last_value = interpreter.run(*m_body);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
if (interpreter.should_unwind()) {
|
||||||
|
if (interpreter.should_unwind_until(ScopeType::Continuable)) {
|
||||||
|
interpreter.stop_unwind();
|
||||||
|
} else if (interpreter.should_unwind_until(ScopeType::Breakable)) {
|
||||||
|
interpreter.stop_unwind();
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
return js_undefined();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
return last_value;
|
||||||
|
}
|
||||||
|
|
||||||
Value BinaryExpression::execute(Interpreter& interpreter) const
|
Value BinaryExpression::execute(Interpreter& interpreter) const
|
||||||
{
|
{
|
||||||
auto lhs_result = m_lhs->execute(interpreter);
|
auto lhs_result = m_lhs->execute(interpreter);
|
||||||
|
@ -801,6 +923,28 @@ void ForStatement::dump(int indent) const
|
||||||
body().dump(indent + 1);
|
body().dump(indent + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ForInStatement::dump(int indent) const
|
||||||
|
{
|
||||||
|
ASTNode::dump(indent);
|
||||||
|
|
||||||
|
print_indent(indent);
|
||||||
|
printf("ForIn\n");
|
||||||
|
lhs().dump(indent + 1);
|
||||||
|
rhs().dump(indent + 1);
|
||||||
|
body().dump(indent + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForOfStatement::dump(int indent) const
|
||||||
|
{
|
||||||
|
ASTNode::dump(indent);
|
||||||
|
|
||||||
|
print_indent(indent);
|
||||||
|
printf("ForOf\n");
|
||||||
|
lhs().dump(indent + 1);
|
||||||
|
rhs().dump(indent + 1);
|
||||||
|
body().dump(indent + 1);
|
||||||
|
}
|
||||||
|
|
||||||
Value Identifier::execute(Interpreter& interpreter) const
|
Value Identifier::execute(Interpreter& interpreter) const
|
||||||
{
|
{
|
||||||
auto value = interpreter.get_variable(string());
|
auto value = interpreter.get_variable(string());
|
||||||
|
|
|
@ -341,6 +341,54 @@ private:
|
||||||
NonnullRefPtr<Statement> m_body;
|
NonnullRefPtr<Statement> m_body;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ForInStatement : public Statement {
|
||||||
|
public:
|
||||||
|
ForInStatement(NonnullRefPtr<ASTNode> lhs, NonnullRefPtr<Expression> rhs, NonnullRefPtr<Statement> body)
|
||||||
|
: m_lhs(move(lhs))
|
||||||
|
, m_rhs(move(rhs))
|
||||||
|
, m_body(move(body))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const ASTNode& lhs() const { return *m_lhs; }
|
||||||
|
const Expression& rhs() const { return *m_rhs; }
|
||||||
|
const Statement& body() const { return *m_body; }
|
||||||
|
|
||||||
|
virtual Value execute(Interpreter&) const override;
|
||||||
|
virtual void dump(int indent) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual const char* class_name() const override { return "ForInStatement"; }
|
||||||
|
|
||||||
|
NonnullRefPtr<ASTNode> m_lhs;
|
||||||
|
NonnullRefPtr<Expression> m_rhs;
|
||||||
|
NonnullRefPtr<Statement> m_body;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ForOfStatement : public Statement {
|
||||||
|
public:
|
||||||
|
ForOfStatement(NonnullRefPtr<ASTNode> lhs, NonnullRefPtr<Expression> rhs, NonnullRefPtr<Statement> body)
|
||||||
|
: m_lhs(move(lhs))
|
||||||
|
, m_rhs(move(rhs))
|
||||||
|
, m_body(move(body))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const ASTNode& lhs() const { return *m_lhs; }
|
||||||
|
const Expression& rhs() const { return *m_rhs; }
|
||||||
|
const Statement& body() const { return *m_body; }
|
||||||
|
|
||||||
|
virtual Value execute(Interpreter&) const override;
|
||||||
|
virtual void dump(int indent) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual const char* class_name() const override { return "ForOfStatement"; }
|
||||||
|
|
||||||
|
NonnullRefPtr<ASTNode> m_lhs;
|
||||||
|
NonnullRefPtr<Expression> m_rhs;
|
||||||
|
NonnullRefPtr<Statement> m_body;
|
||||||
|
};
|
||||||
|
|
||||||
enum class BinaryOp {
|
enum class BinaryOp {
|
||||||
Addition,
|
Addition,
|
||||||
Subtraction,
|
Subtraction,
|
||||||
|
@ -678,6 +726,11 @@ enum class DeclarationKind {
|
||||||
|
|
||||||
class VariableDeclarator final : public ASTNode {
|
class VariableDeclarator final : public ASTNode {
|
||||||
public:
|
public:
|
||||||
|
VariableDeclarator(NonnullRefPtr<Identifier> id)
|
||||||
|
: m_id(move(id))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
VariableDeclarator(NonnullRefPtr<Identifier> id, RefPtr<Expression> init)
|
VariableDeclarator(NonnullRefPtr<Identifier> id, RefPtr<Expression> init)
|
||||||
: m_id(move(id))
|
: m_id(move(id))
|
||||||
, m_init(move(init))
|
, m_init(move(init))
|
||||||
|
|
|
@ -592,8 +592,7 @@ NonnullRefPtr<StringLiteral> Parser::parse_string_literal(Token token)
|
||||||
syntax_error(
|
syntax_error(
|
||||||
message,
|
message,
|
||||||
m_parser_state.m_current_token.line_number(),
|
m_parser_state.m_current_token.line_number(),
|
||||||
m_parser_state.m_current_token.line_column()
|
m_parser_state.m_current_token.line_column());
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return create_ast_node<StringLiteral>(string);
|
return create_ast_node<StringLiteral>(string);
|
||||||
}
|
}
|
||||||
|
@ -651,14 +650,14 @@ NonnullRefPtr<TemplateLiteral> Parser::parse_template_literal(bool is_tagged)
|
||||||
return create_ast_node<TemplateLiteral>(expressions);
|
return create_ast_node<TemplateLiteral>(expressions);
|
||||||
}
|
}
|
||||||
|
|
||||||
NonnullRefPtr<Expression> Parser::parse_expression(int min_precedence, Associativity associativity)
|
NonnullRefPtr<Expression> Parser::parse_expression(int min_precedence, Associativity associativity, Vector<TokenType> forbidden)
|
||||||
{
|
{
|
||||||
auto expression = parse_primary_expression();
|
auto expression = parse_primary_expression();
|
||||||
while (match(TokenType::TemplateLiteralStart)) {
|
while (match(TokenType::TemplateLiteralStart)) {
|
||||||
auto template_literal = parse_template_literal(true);
|
auto template_literal = parse_template_literal(true);
|
||||||
expression = create_ast_node<TaggedTemplateLiteral>(move(expression), move(template_literal));
|
expression = create_ast_node<TaggedTemplateLiteral>(move(expression), move(template_literal));
|
||||||
}
|
}
|
||||||
while (match_secondary_expression()) {
|
while (match_secondary_expression(forbidden)) {
|
||||||
int new_precedence = operator_precedence(m_parser_state.m_current_token.type());
|
int new_precedence = operator_precedence(m_parser_state.m_current_token.type());
|
||||||
if (new_precedence < min_precedence)
|
if (new_precedence < min_precedence)
|
||||||
break;
|
break;
|
||||||
|
@ -974,7 +973,7 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(bool needs_function_
|
||||||
return create_ast_node<FunctionNodeType>(name, move(body), move(parameters), function_length, NonnullRefPtrVector<VariableDeclaration>());
|
return create_ast_node<FunctionNodeType>(name, move(body), move(parameters), function_length, NonnullRefPtrVector<VariableDeclaration>());
|
||||||
}
|
}
|
||||||
|
|
||||||
NonnullRefPtr<VariableDeclaration> Parser::parse_variable_declaration()
|
NonnullRefPtr<VariableDeclaration> Parser::parse_variable_declaration(bool with_semicolon)
|
||||||
{
|
{
|
||||||
DeclarationKind declaration_kind;
|
DeclarationKind declaration_kind;
|
||||||
|
|
||||||
|
@ -1010,10 +1009,11 @@ NonnullRefPtr<VariableDeclaration> Parser::parse_variable_declaration()
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
consume_or_insert_semicolon();
|
if (with_semicolon)
|
||||||
|
consume_or_insert_semicolon();
|
||||||
|
|
||||||
auto declaration = create_ast_node<VariableDeclaration>(declaration_kind, move(declarations));
|
auto declaration = create_ast_node<VariableDeclaration>(declaration_kind, move(declarations));
|
||||||
if (declaration->declaration_kind() == DeclarationKind::Var)
|
if (declaration_kind == DeclarationKind::Var)
|
||||||
m_parser_state.m_var_scopes.last().append(declaration);
|
m_parser_state.m_var_scopes.last().append(declaration);
|
||||||
else
|
else
|
||||||
m_parser_state.m_let_scopes.last().append(declaration);
|
m_parser_state.m_let_scopes.last().append(declaration);
|
||||||
|
@ -1177,57 +1177,46 @@ NonnullRefPtr<IfStatement> Parser::parse_if_statement()
|
||||||
return create_ast_node<IfStatement>(move(predicate), move(consequent), move(alternate));
|
return create_ast_node<IfStatement>(move(predicate), move(consequent), move(alternate));
|
||||||
}
|
}
|
||||||
|
|
||||||
NonnullRefPtr<ForStatement> Parser::parse_for_statement()
|
NonnullRefPtr<Statement> Parser::parse_for_statement()
|
||||||
{
|
{
|
||||||
|
auto match_for_in_of = [&]() {
|
||||||
|
return match(TokenType::In) || (match(TokenType::Identifier) && m_parser_state.m_current_token.value() == "of");
|
||||||
|
};
|
||||||
|
|
||||||
consume(TokenType::For);
|
consume(TokenType::For);
|
||||||
|
|
||||||
consume(TokenType::ParenOpen);
|
consume(TokenType::ParenOpen);
|
||||||
|
|
||||||
bool first_semicolon_consumed = false;
|
|
||||||
bool in_scope = false;
|
bool in_scope = false;
|
||||||
RefPtr<ASTNode> init;
|
RefPtr<ASTNode> init;
|
||||||
switch (m_parser_state.m_current_token.type()) {
|
if (!match(TokenType::Semicolon)) {
|
||||||
case TokenType::Semicolon:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (match_expression()) {
|
if (match_expression()) {
|
||||||
init = parse_expression(0);
|
init = parse_expression(0, Associativity::Right, { TokenType::In });
|
||||||
|
if (match_for_in_of())
|
||||||
|
return parse_for_in_of_statement(*init);
|
||||||
} else if (match_variable_declaration()) {
|
} else if (match_variable_declaration()) {
|
||||||
if (m_parser_state.m_current_token.type() != TokenType::Var) {
|
if (!match(TokenType::Var)) {
|
||||||
m_parser_state.m_let_scopes.append(NonnullRefPtrVector<VariableDeclaration>());
|
m_parser_state.m_let_scopes.append(NonnullRefPtrVector<VariableDeclaration>());
|
||||||
in_scope = true;
|
in_scope = true;
|
||||||
}
|
}
|
||||||
|
init = parse_variable_declaration(false);
|
||||||
init = parse_variable_declaration();
|
if (match_for_in_of())
|
||||||
first_semicolon_consumed = true;
|
return parse_for_in_of_statement(*init);
|
||||||
} else {
|
} else {
|
||||||
ASSERT_NOT_REACHED();
|
ASSERT_NOT_REACHED();
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
consume(TokenType::Semicolon);
|
||||||
if (!first_semicolon_consumed)
|
|
||||||
consume(TokenType::Semicolon);
|
|
||||||
|
|
||||||
RefPtr<Expression> test;
|
RefPtr<Expression> test;
|
||||||
switch (m_parser_state.m_current_token.type()) {
|
if (!match(TokenType::Semicolon))
|
||||||
case TokenType::Semicolon:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
test = parse_expression(0);
|
test = parse_expression(0);
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
consume(TokenType::Semicolon);
|
consume(TokenType::Semicolon);
|
||||||
|
|
||||||
RefPtr<Expression> update;
|
RefPtr<Expression> update;
|
||||||
switch (m_parser_state.m_current_token.type()) {
|
if (!match(TokenType::ParenClose))
|
||||||
case TokenType::ParenClose:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
update = parse_expression(0);
|
update = parse_expression(0);
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
consume(TokenType::ParenClose);
|
consume(TokenType::ParenClose);
|
||||||
|
|
||||||
|
@ -1240,6 +1229,28 @@ NonnullRefPtr<ForStatement> Parser::parse_for_statement()
|
||||||
return create_ast_node<ForStatement>(move(init), move(test), move(update), move(body));
|
return create_ast_node<ForStatement>(move(init), move(test), move(update), move(body));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NonnullRefPtr<Statement> Parser::parse_for_in_of_statement(NonnullRefPtr<ASTNode> lhs)
|
||||||
|
{
|
||||||
|
if (lhs->is_variable_declaration()) {
|
||||||
|
auto declarations = static_cast<VariableDeclaration*>(lhs.ptr())->declarations();
|
||||||
|
if (declarations.size() > 1) {
|
||||||
|
syntax_error("multiple declarations not allowed in for..in/of");
|
||||||
|
lhs = create_ast_node<ErrorExpression>();
|
||||||
|
}
|
||||||
|
if (declarations.first().init() != nullptr) {
|
||||||
|
syntax_error("variable initializer not allowed in for..in/of");
|
||||||
|
lhs = create_ast_node<ErrorExpression>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto in_or_of = consume();
|
||||||
|
auto rhs = parse_expression(0);
|
||||||
|
consume(TokenType::ParenClose);
|
||||||
|
auto body = parse_statement();
|
||||||
|
if (in_or_of.type() == TokenType::In)
|
||||||
|
return create_ast_node<ForInStatement>(move(lhs), move(rhs), move(body));
|
||||||
|
return create_ast_node<ForOfStatement>(move(lhs), move(rhs), move(body));
|
||||||
|
}
|
||||||
|
|
||||||
NonnullRefPtr<DebuggerStatement> Parser::parse_debugger_statement()
|
NonnullRefPtr<DebuggerStatement> Parser::parse_debugger_statement()
|
||||||
{
|
{
|
||||||
consume(TokenType::Debugger);
|
consume(TokenType::Debugger);
|
||||||
|
@ -1296,9 +1307,11 @@ bool Parser::match_unary_prefixed_expression() const
|
||||||
|| type == TokenType::Delete;
|
|| type == TokenType::Delete;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Parser::match_secondary_expression() const
|
bool Parser::match_secondary_expression(Vector<TokenType> forbidden) const
|
||||||
{
|
{
|
||||||
auto type = m_parser_state.m_current_token.type();
|
auto type = m_parser_state.m_current_token.type();
|
||||||
|
if (forbidden.contains_slow(type))
|
||||||
|
return false;
|
||||||
return type == TokenType::Plus
|
return type == TokenType::Plus
|
||||||
|| type == TokenType::PlusEquals
|
|| type == TokenType::PlusEquals
|
||||||
|| type == TokenType::Minus
|
|| type == TokenType::Minus
|
||||||
|
@ -1410,7 +1423,7 @@ void Parser::consume_or_insert_semicolon()
|
||||||
|
|
||||||
Token Parser::consume(TokenType expected_type)
|
Token Parser::consume(TokenType expected_type)
|
||||||
{
|
{
|
||||||
if (m_parser_state.m_current_token.type() != expected_type) {
|
if (!match(expected_type)) {
|
||||||
expected(Token::name(expected_type));
|
expected(Token::name(expected_type));
|
||||||
}
|
}
|
||||||
return consume();
|
return consume();
|
||||||
|
|
|
@ -50,8 +50,9 @@ public:
|
||||||
NonnullRefPtr<Statement> parse_statement();
|
NonnullRefPtr<Statement> parse_statement();
|
||||||
NonnullRefPtr<BlockStatement> parse_block_statement();
|
NonnullRefPtr<BlockStatement> parse_block_statement();
|
||||||
NonnullRefPtr<ReturnStatement> parse_return_statement();
|
NonnullRefPtr<ReturnStatement> parse_return_statement();
|
||||||
NonnullRefPtr<VariableDeclaration> parse_variable_declaration();
|
NonnullRefPtr<VariableDeclaration> parse_variable_declaration(bool with_semicolon = true);
|
||||||
NonnullRefPtr<ForStatement> parse_for_statement();
|
NonnullRefPtr<Statement> parse_for_statement();
|
||||||
|
NonnullRefPtr<Statement> parse_for_in_of_statement(NonnullRefPtr<ASTNode> lhs);
|
||||||
NonnullRefPtr<IfStatement> parse_if_statement();
|
NonnullRefPtr<IfStatement> parse_if_statement();
|
||||||
NonnullRefPtr<ThrowStatement> parse_throw_statement();
|
NonnullRefPtr<ThrowStatement> parse_throw_statement();
|
||||||
NonnullRefPtr<TryStatement> parse_try_statement();
|
NonnullRefPtr<TryStatement> parse_try_statement();
|
||||||
|
@ -65,7 +66,7 @@ public:
|
||||||
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<Expression> parse_expression(int min_precedence, Associativity associate = Associativity::Right);
|
NonnullRefPtr<Expression> parse_expression(int min_precedence, Associativity associate = Associativity::Right, Vector<TokenType> forbidden = {});
|
||||||
NonnullRefPtr<Expression> parse_primary_expression();
|
NonnullRefPtr<Expression> parse_primary_expression();
|
||||||
NonnullRefPtr<Expression> parse_unary_prefixed_expression();
|
NonnullRefPtr<Expression> parse_unary_prefixed_expression();
|
||||||
NonnullRefPtr<ObjectExpression> parse_object_expression();
|
NonnullRefPtr<ObjectExpression> parse_object_expression();
|
||||||
|
@ -105,7 +106,7 @@ private:
|
||||||
Associativity operator_associativity(TokenType) const;
|
Associativity operator_associativity(TokenType) const;
|
||||||
bool match_expression() const;
|
bool match_expression() const;
|
||||||
bool match_unary_prefixed_expression() const;
|
bool match_unary_prefixed_expression() const;
|
||||||
bool match_secondary_expression() const;
|
bool match_secondary_expression(Vector<TokenType> forbidden = {}) const;
|
||||||
bool match_statement() const;
|
bool match_statement() const;
|
||||||
bool match_variable_declaration() const;
|
bool match_variable_declaration() const;
|
||||||
bool match_identifier_name() const;
|
bool match_identifier_name() const;
|
||||||
|
|
|
@ -73,67 +73,47 @@ try {
|
||||||
const o = { length: 5, 0: "foo", 1: "bar", 3: "baz" };
|
const o = { length: 5, 0: "foo", 1: "bar", 3: "baz" };
|
||||||
|
|
||||||
{
|
{
|
||||||
const visited = [];
|
assertVisitsAll(visit => {
|
||||||
Array.prototype.every.call(o, function (value) {
|
Array.prototype.every.call(o, function (value) {
|
||||||
visited.push(value);
|
visit(value);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
assert(visited.length === 3);
|
}, ["foo", "bar", "baz"]);
|
||||||
assert(visited[0] === "foo");
|
|
||||||
assert(visited[1] === "bar");
|
|
||||||
assert(visited[2] === "baz");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
["find", "findIndex"].forEach(name => {
|
["find", "findIndex"].forEach(name => {
|
||||||
const visited = [];
|
assertVisitsAll(visit => {
|
||||||
Array.prototype[name].call(o, function (value) {
|
Array.prototype[name].call(o, function (value) {
|
||||||
visited.push(value);
|
visit(value);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
assert(visited.length === 5);
|
}, ["foo", "bar", undefined, "baz", undefined]);
|
||||||
assert(visited[0] === "foo");
|
|
||||||
assert(visited[1] === "bar");
|
|
||||||
assert(visited[2] === undefined);
|
|
||||||
assert(visited[3] === "baz");
|
|
||||||
assert(visited[4] === undefined);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
["filter", "forEach", "map", "some"].forEach(name => {
|
["filter", "forEach", "map", "some"].forEach(name => {
|
||||||
const visited = [];
|
assertVisitsAll(visit => {
|
||||||
Array.prototype[name].call(o, function (value) {
|
Array.prototype[name].call(o, function (value) {
|
||||||
visited.push(value);
|
visit(value);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
assert(visited.length === 3);
|
}, ["foo", "bar", "baz"]);
|
||||||
assert(visited[0] === "foo");
|
|
||||||
assert(visited[1] === "bar");
|
|
||||||
assert(visited[2] === "baz");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
const visited = [];
|
assertVisitsAll(visit => {
|
||||||
Array.prototype.reduce.call(o, function (_, value) {
|
Array.prototype.reduce.call(o, function (_, value) {
|
||||||
visited.push(value);
|
visit(value);
|
||||||
return false;
|
return false;
|
||||||
}, "initial");
|
}, "initial");
|
||||||
|
}, ["foo", "bar", "baz"]);
|
||||||
assert(visited.length === 3);
|
|
||||||
assert(visited[0] === "foo");
|
|
||||||
assert(visited[1] === "bar");
|
|
||||||
assert(visited[2] === "baz");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const visited = [];
|
assertVisitsAll(visit => {
|
||||||
Array.prototype.reduceRight.call(o, function (_, value) {
|
Array.prototype.reduceRight.call(o, function (_, value) {
|
||||||
visited.push(value);
|
visit(value);
|
||||||
return false;
|
return false;
|
||||||
}, "initial");
|
}, "initial");
|
||||||
|
}, ["baz", "bar", "foo"]);
|
||||||
assert(visited.length === 3);
|
|
||||||
assert(visited[2] === "foo");
|
|
||||||
assert(visited[1] === "bar");
|
|
||||||
assert(visited[0] === "baz");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("PASS");
|
console.log("PASS");
|
||||||
|
|
41
Libraries/LibJS/Tests/for-in-basic.js
Normal file
41
Libraries/LibJS/Tests/for-in-basic.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
load("test-common.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
assertVisitsAll(visit => {
|
||||||
|
for (const property in "") {
|
||||||
|
visit(property);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
assertVisitsAll(visit => {
|
||||||
|
for (const property in 123) {
|
||||||
|
visit(property);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
assertVisitsAll(visit => {
|
||||||
|
for (const property in {}) {
|
||||||
|
visit(property);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
assertVisitsAll(visit => {
|
||||||
|
for (const property in "hello") {
|
||||||
|
visit(property);
|
||||||
|
}
|
||||||
|
}, ["0", "1", "2", "3", "4"]);
|
||||||
|
|
||||||
|
assertVisitsAll(visit => {
|
||||||
|
for (const property in {a: 1, b: 2, c: 2}) {
|
||||||
|
visit(property);
|
||||||
|
}
|
||||||
|
}, ["a", "b", "c"]);
|
||||||
|
|
||||||
|
var property;
|
||||||
|
for (property in "abc");
|
||||||
|
assert(property === "2");
|
||||||
|
|
||||||
|
console.log("PASS");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("FAIL: " + e);
|
||||||
|
}
|
43
Libraries/LibJS/Tests/for-of-basic.js
Normal file
43
Libraries/LibJS/Tests/for-of-basic.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
load("test-common.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
assertThrowsError(() => {
|
||||||
|
for (const _ of 123) {}
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "for..of right-hand side must be iterable"
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
for (const _ of {foo: 1, bar: 2}) {}
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "for..of right-hand side must be iterable"
|
||||||
|
});
|
||||||
|
|
||||||
|
assertVisitsAll(visit => {
|
||||||
|
for (const num of [1, 2, 3]) {
|
||||||
|
visit(num);
|
||||||
|
}
|
||||||
|
}, [1, 2, 3]);
|
||||||
|
|
||||||
|
assertVisitsAll(visit => {
|
||||||
|
for (const char of "hello") {
|
||||||
|
visit(char);
|
||||||
|
}
|
||||||
|
}, ["h", "e", "l", "l", "o"]);
|
||||||
|
|
||||||
|
assertVisitsAll(visit => {
|
||||||
|
for (const char of new String("hello")) {
|
||||||
|
visit(char);
|
||||||
|
}
|
||||||
|
}, ["h", "e", "l", "l", "o"]);
|
||||||
|
|
||||||
|
var char;
|
||||||
|
for (char of "abc");
|
||||||
|
assert(char === "c");
|
||||||
|
|
||||||
|
console.log("PASS");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("FAIL: " + e);
|
||||||
|
}
|
|
@ -50,6 +50,13 @@ function assertThrowsError(testFunction, options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const assertVisitsAll = (testFunction, expectedOutput) => {
|
||||||
|
const visited = [];
|
||||||
|
testFunction(value => visited.push(value));
|
||||||
|
assert(visited.length === expectedOutput.length);
|
||||||
|
expectedOutput.forEach((value, i) => assert(visited[i] === value));
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the difference between two numbers is less than 0.000001.
|
* Check whether the difference between two numbers is less than 0.000001.
|
||||||
* @param {Number} a First number
|
* @param {Number} a First number
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue