ladybird/Userland/Libraries/LibJS/AST.h

1986 lines
63 KiB
C
Raw Normal View History

/*
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020-2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2021, David Tuin <davidot@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/FlyString.h>
2020-03-21 00:29:00 +00:00
#include <AK/HashMap.h>
#include <AK/NonnullRefPtrVector.h>
#include <AK/OwnPtr.h>
#include <AK/RefPtr.h>
#include <AK/String.h>
#include <AK/Variant.h>
#include <AK/Vector.h>
#include <LibJS/Forward.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/EnvironmentCoordinate.h>
#include <LibJS/Runtime/FunctionKind.h>
#include <LibJS/Runtime/PropertyKey.h>
#include <LibJS/Runtime/Reference.h>
#include <LibJS/Runtime/Value.h>
#include <LibJS/SourceRange.h>
#include <LibRegex/Regex.h>
namespace JS {
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
class Declaration;
class ClassDeclaration;
class FunctionDeclaration;
class Identifier;
class MemberExpression;
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
class VariableDeclaration;
template<class T, class... Args>
static inline NonnullRefPtr<T>
create_ast_node(SourceRange range, Args&&... args)
{
return adopt_ref(*new T(range, forward<Args>(args)...));
}
class ASTNode : public RefCounted<ASTNode> {
public:
virtual ~ASTNode() { }
virtual Completion execute(Interpreter&, GlobalObject&) const = 0;
virtual void generate_bytecode(Bytecode::Generator&) const;
virtual void dump(int indent) const;
SourceRange const& source_range() const { return m_source_range; }
SourceRange& source_range() { return m_source_range; }
String class_name() const;
template<typename T>
bool fast_is() const = delete;
virtual bool is_new_expression() const { return false; }
virtual bool is_member_expression() const { return false; }
virtual bool is_super_expression() const { return false; }
virtual bool is_function_expression() const { return false; }
virtual bool is_class_expression() const { return false; }
virtual bool is_expression_statement() const { return false; }
virtual bool is_identifier() const { return false; }
virtual bool is_scope_node() const { return false; }
virtual bool is_program() const { return false; }
virtual bool is_function_declaration() const { return false; }
protected:
explicit ASTNode(SourceRange source_range)
: m_source_range(source_range)
{
}
private:
SourceRange m_source_range;
};
class Statement : public ASTNode {
public:
explicit Statement(SourceRange source_range)
: ASTNode(source_range)
{
}
};
// 14.13 Labelled Statements, https://tc39.es/ecma262/#sec-labelled-statements
class LabelledStatement : public Statement {
public:
LabelledStatement(SourceRange source_range, FlyString label, NonnullRefPtr<Statement> labelled_item)
: Statement(source_range)
, m_label(move(label))
, m_labelled_item(move(labelled_item))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
FlyString const& label() const { return m_label; }
FlyString& label() { return m_label; }
NonnullRefPtr<Statement> const& labelled_item() const { return m_labelled_item; }
NonnullRefPtr<Statement>& labelled_item() { return m_labelled_item; }
private:
FlyString m_label;
NonnullRefPtr<Statement> m_labelled_item;
};
class LabelableStatement : public Statement {
public:
using Statement::Statement;
Vector<FlyString> const& labels() const { return m_labels; }
virtual void add_label(FlyString string) { m_labels.append(move(string)); }
protected:
Vector<FlyString> m_labels;
};
class IterationStatement : public Statement {
public:
using Statement::Statement;
virtual Completion loop_evaluation(Interpreter&, GlobalObject&, Vector<FlyString> const&) const = 0;
};
class EmptyStatement final : public Statement {
public:
explicit EmptyStatement(SourceRange source_range)
: Statement(source_range)
{
}
Completion execute(Interpreter&, GlobalObject&) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
};
class ErrorStatement final : public Statement {
public:
explicit ErrorStatement(SourceRange source_range)
: Statement(source_range)
{
}
Completion execute(Interpreter&, GlobalObject&) const override { return {}; }
};
class ExpressionStatement final : public Statement {
public:
ExpressionStatement(SourceRange source_range, NonnullRefPtr<Expression> expression)
: Statement(source_range)
, m_expression(move(expression))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
Expression const& expression() const { return m_expression; };
private:
virtual bool is_expression_statement() const override { return true; }
NonnullRefPtr<Expression> m_expression;
};
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
template<typename... Args>
class IteratorOrVoidFunction : public Function<IterationDecision(Args...)> {
public:
template<typename CallableType>
IteratorOrVoidFunction(CallableType&& callable) requires(VoidFunction<CallableType, Args...>)
: Function<IterationDecision(Args...)>([callable = forward<CallableType>(callable)](Args... args) {
callable(args...);
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
return IterationDecision::Continue;
})
{
}
template<typename CallableType>
IteratorOrVoidFunction(CallableType&& callable) requires(IteratorFunction<CallableType, Args...>)
: Function<IterationDecision(Args...)>(forward<CallableType>(callable))
{
}
};
class ScopeNode : public Statement {
public:
template<typename T, typename... Args>
T& append(SourceRange range, Args&&... args)
{
auto child = create_ast_node<T>(range, forward<Args>(args)...);
m_children.append(move(child));
return static_cast<T&>(m_children.last());
}
void append(NonnullRefPtr<Statement> child)
{
m_children.append(move(child));
}
NonnullRefPtrVector<Statement> const& children() const { return m_children; }
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
Completion evaluate_statements(Interpreter& interpreter, GlobalObject& global_object) const;
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
void add_var_scoped_declaration(NonnullRefPtr<Declaration> variables);
void add_lexical_declaration(NonnullRefPtr<Declaration> variables);
void add_hoisted_function(NonnullRefPtr<FunctionDeclaration> declaration);
[[nodiscard]] bool has_lexical_declarations() const { return !m_lexical_declarations.is_empty(); }
[[nodiscard]] bool has_var_declarations() const { return !m_var_declarations.is_empty(); }
[[nodiscard]] size_t var_declaration_count() const { return m_var_declarations.size(); }
[[nodiscard]] size_t lexical_declaration_count() const { return m_lexical_declarations.size(); }
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
void for_each_lexically_scoped_declaration(IteratorOrVoidFunction<Declaration const&>&& callback) const;
void for_each_lexically_declared_name(IteratorOrVoidFunction<FlyString const&>&& callback) const;
void for_each_var_declared_name(IteratorOrVoidFunction<FlyString const&>&& callback) const;
void for_each_var_function_declaration_in_reverse_order(IteratorOrVoidFunction<FunctionDeclaration const&>&& callback) const;
void for_each_var_scoped_variable_declaration(IteratorOrVoidFunction<VariableDeclaration const&>&& callback) const;
void block_declaration_instantiation(GlobalObject& global_object, Environment* environment) const;
void for_each_function_hoistable_with_annexB_extension(IteratorOrVoidFunction<FunctionDeclaration&>&& callback) const;
protected:
explicit ScopeNode(SourceRange source_range)
: Statement(source_range)
{
}
private:
virtual bool is_scope_node() const final { return true; }
NonnullRefPtrVector<Statement> m_children;
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
NonnullRefPtrVector<Declaration> m_lexical_declarations;
NonnullRefPtrVector<Declaration> m_var_declarations;
NonnullRefPtrVector<FunctionDeclaration> m_functions_hoistable_with_annexB_extension;
};
// 2.9 ModuleRequest Records, https://tc39.es/proposal-import-assertions/#sec-modulerequest-record
struct ModuleRequest {
struct Assertion {
String key;
String value;
};
ModuleRequest() = default;
explicit ModuleRequest(String specifier)
: module_specifier(move(specifier))
{
}
void add_assertion(String key, String value)
{
assertions.empend(move(key), move(value));
}
String module_specifier; // [[Specifier]]
Vector<Assertion> assertions; // [[Assertions]]
};
class ImportStatement final : public Statement {
public:
struct ImportEntry {
String import_name;
String local_name;
};
explicit ImportStatement(SourceRange source_range, ModuleRequest from_module, Vector<ImportEntry> entries = {})
: Statement(source_range)
, m_module_request(move(from_module))
, m_entries(move(entries))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
bool has_bound_name(StringView name) const;
private:
ModuleRequest m_module_request;
Vector<ImportEntry> m_entries;
};
class ExportStatement final : public Statement {
public:
struct ExportEntry {
enum class Kind {
ModuleRequest,
LocalExport
} kind;
// Can always have
String export_name;
// Only if module request
ModuleRequest module_request;
// Has just one of ones below
String local_or_import_name;
ExportEntry(String export_name, String local_name)
: kind(Kind::LocalExport)
, export_name(move(export_name))
, local_or_import_name(move(local_name))
{
}
};
explicit ExportStatement(SourceRange source_range, RefPtr<ASTNode> statement, Vector<ExportEntry> entries)
: Statement(source_range)
, m_statement(move(statement))
, m_entries(move(entries))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
bool has_export(StringView export_name) const;
bool has_statement() const { return m_statement; }
Vector<ExportEntry> const& entries() const { return m_entries; }
private:
RefPtr<ASTNode> m_statement;
Vector<ExportEntry> m_entries;
};
class Program final : public ScopeNode {
public:
enum class Type {
Script,
Module
};
explicit Program(SourceRange source_range, Type program_type)
: ScopeNode(source_range)
, m_type(program_type)
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
bool is_strict_mode() const { return m_is_strict_mode; }
void set_strict_mode() { m_is_strict_mode = true; }
Type type() const { return m_type; }
void append_import(NonnullRefPtr<ImportStatement> import_statement)
{
m_imports.append(import_statement);
append(import_statement);
}
void append_export(NonnullRefPtr<ExportStatement> export_statement)
{
m_exports.append(export_statement);
append(export_statement);
}
NonnullRefPtrVector<ImportStatement> const& imports() const { return m_imports; }
NonnullRefPtrVector<ExportStatement> const& exports() const { return m_exports; }
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
ThrowCompletionOr<void> global_declaration_instantiation(Interpreter& interpreter, GlobalObject& global_object, GlobalEnvironment& global_environment) const;
private:
virtual bool is_program() const override { return true; }
bool m_is_strict_mode { false };
Type m_type { Type::Script };
NonnullRefPtrVector<ImportStatement> m_imports;
NonnullRefPtrVector<ExportStatement> m_exports;
};
class BlockStatement final : public ScopeNode {
public:
explicit BlockStatement(SourceRange source_range)
: ScopeNode(source_range)
{
}
Completion execute(Interpreter& interpreter, GlobalObject& object) const override;
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
};
class FunctionBody final : public ScopeNode {
public:
explicit FunctionBody(SourceRange source_range)
: ScopeNode(source_range)
{
}
void set_strict_mode() { m_in_strict_mode = true; }
bool in_strict_mode() const { return m_in_strict_mode; }
virtual Completion execute(Interpreter&, GlobalObject&) const override;
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
private:
bool m_in_strict_mode { false };
};
class Expression : public ASTNode {
public:
explicit Expression(SourceRange source_range)
: ASTNode(source_range)
{
}
virtual ThrowCompletionOr<Reference> to_reference(Interpreter&, GlobalObject&) const;
};
class Declaration : public Statement {
public:
explicit Declaration(SourceRange source_range)
: Statement(source_range)
{
}
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
virtual void for_each_bound_name(IteratorOrVoidFunction<FlyString const&> callback) const = 0;
// 8.1.3 Static Semantics: IsConstantDeclaration, https://tc39.es/ecma262/#sec-static-semantics-isconstantdeclaration
virtual bool is_constant_declaration() const { return false; }
virtual bool is_lexical_declaration() const { return false; }
};
class ErrorDeclaration final : public Declaration {
public:
explicit ErrorDeclaration(SourceRange source_range)
: Declaration(source_range)
{
}
Completion execute(Interpreter&, GlobalObject&) const override { return {}; }
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
void for_each_bound_name(IteratorOrVoidFunction<FlyString const&>) const override
{
VERIFY_NOT_REACHED();
}
};
struct BindingPattern : RefCounted<BindingPattern> {
// This covers both BindingProperty and BindingElement, hence the more generic name
struct BindingEntry {
// If this entry represents a BindingElement, then name will be Empty
Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<Expression>, Empty> name {};
Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>, NonnullRefPtr<MemberExpression>, Empty> alias {};
RefPtr<Expression> initializer {};
bool is_rest { false };
bool is_elision() const { return name.has<Empty>() && alias.has<Empty>(); }
};
enum class Kind {
Array,
Object,
};
void dump(int indent) const;
template<typename C>
void for_each_bound_name(C&& callback) const;
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
bool contains_expression() const;
Vector<BindingEntry> entries;
Kind kind { Kind::Object };
};
class FunctionNode {
public:
struct Parameter {
Variant<FlyString, NonnullRefPtr<BindingPattern>> binding;
RefPtr<Expression> default_value;
2020-05-04 15:05:13 +00:00
bool is_rest { false };
};
FlyString const& name() const { return m_name; }
Statement const& body() const { return *m_body; }
Vector<Parameter> const& parameters() const { return m_parameters; };
i32 function_length() const { return m_function_length; }
bool is_strict_mode() const { return m_is_strict_mode; }
bool might_need_arguments_object() const { return m_might_need_arguments_object; }
bool contains_direct_call_to_eval() const { return m_contains_direct_call_to_eval; }
bool is_arrow_function() const { return m_is_arrow_function; }
FunctionKind kind() const { return m_kind; }
protected:
FunctionNode(FlyString name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function)
: m_name(move(name))
, m_body(move(body))
, m_parameters(move(parameters))
, m_function_length(function_length)
, m_kind(kind)
, m_is_strict_mode(is_strict_mode)
, m_might_need_arguments_object(might_need_arguments_object)
, m_contains_direct_call_to_eval(contains_direct_call_to_eval)
, m_is_arrow_function(is_arrow_function)
{
if (m_is_arrow_function)
VERIFY(!m_might_need_arguments_object);
}
void dump(int indent, String const& class_name) const;
protected:
void set_name(FlyString name)
{
VERIFY(m_name.is_empty());
m_name = move(name);
}
private:
FlyString m_name;
NonnullRefPtr<Statement> m_body;
Vector<Parameter> const m_parameters;
const i32 m_function_length;
FunctionKind m_kind;
bool m_is_strict_mode { false };
bool m_might_need_arguments_object { false };
bool m_contains_direct_call_to_eval { false };
bool m_is_arrow_function { false };
};
class FunctionDeclaration final
: public Declaration
, public FunctionNode {
public:
static bool must_have_name() { return true; }
FunctionDeclaration(SourceRange source_range, FlyString const& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval)
: Declaration(source_range)
, FunctionNode(name, move(body), move(parameters), function_length, kind, is_strict_mode, might_need_arguments_object, contains_direct_call_to_eval, false)
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
void for_each_bound_name(IteratorOrVoidFunction<FlyString const&> callback) const override;
virtual bool is_function_declaration() const override { return true; }
void set_should_do_additional_annexB_steps() { m_is_hoisted = true; }
private:
bool m_is_hoisted { false };
};
class FunctionExpression final
: public Expression
, public FunctionNode {
public:
static bool must_have_name() { return false; }
FunctionExpression(SourceRange source_range, FlyString const& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function = false)
: Expression(source_range)
, FunctionNode(name, move(body), move(parameters), function_length, kind, is_strict_mode, might_need_arguments_object, contains_direct_call_to_eval, is_arrow_function)
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
bool has_name() const { return !name().is_empty(); }
Value instantiate_ordinary_function_expression(Interpreter& interpreter, GlobalObject& global_object, FlyString given_name) const;
private:
virtual bool is_function_expression() const override { return true; }
};
class ErrorExpression final : public Expression {
public:
explicit ErrorExpression(SourceRange source_range)
: Expression(source_range)
{
}
Completion execute(Interpreter&, GlobalObject&) const override { return {}; }
};
class YieldExpression final : public Expression {
public:
explicit YieldExpression(SourceRange source_range, RefPtr<Expression> argument, bool is_yield_from)
: Expression(source_range)
, m_argument(move(argument))
, m_is_yield_from(is_yield_from)
{
}
Expression const* argument() const { return m_argument; }
bool is_yield_from() const { return m_is_yield_from; }
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
RefPtr<Expression> m_argument;
bool m_is_yield_from { false };
};
class AwaitExpression final : public Expression {
public:
explicit AwaitExpression(SourceRange source_range, NonnullRefPtr<Expression> argument)
: Expression(source_range)
, m_argument(move(argument))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
NonnullRefPtr<Expression> m_argument;
};
class ReturnStatement final : public Statement {
public:
explicit ReturnStatement(SourceRange source_range, RefPtr<Expression> argument)
: Statement(source_range)
, m_argument(move(argument))
{
}
Expression const* argument() const { return m_argument; }
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
RefPtr<Expression> m_argument;
};
class IfStatement final : public Statement {
public:
IfStatement(SourceRange source_range, NonnullRefPtr<Expression> predicate, NonnullRefPtr<Statement> consequent, RefPtr<Statement> alternate)
: Statement(source_range)
, m_predicate(move(predicate))
, m_consequent(move(consequent))
, m_alternate(move(alternate))
{
}
Expression const& predicate() const { return *m_predicate; }
Statement const& consequent() const { return *m_consequent; }
Statement const* alternate() const { return m_alternate; }
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
NonnullRefPtr<Expression> m_predicate;
NonnullRefPtr<Statement> m_consequent;
RefPtr<Statement> m_alternate;
};
class WhileStatement final : public IterationStatement {
2020-03-08 19:22:21 +00:00
public:
WhileStatement(SourceRange source_range, NonnullRefPtr<Expression> test, NonnullRefPtr<Statement> body)
: IterationStatement(source_range)
, m_test(move(test))
2020-03-08 19:22:21 +00:00
, m_body(move(body))
{
}
Expression const& test() const { return *m_test; }
Statement const& body() const { return *m_body; }
2020-03-08 19:22:21 +00:00
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual Completion loop_evaluation(Interpreter&, GlobalObject&, Vector<FlyString> const&) const override;
2020-03-08 19:22:21 +00:00
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
2020-03-08 19:22:21 +00:00
private:
NonnullRefPtr<Expression> m_test;
NonnullRefPtr<Statement> m_body;
2020-03-08 19:22:21 +00:00
};
class DoWhileStatement final : public IterationStatement {
public:
DoWhileStatement(SourceRange source_range, NonnullRefPtr<Expression> test, NonnullRefPtr<Statement> body)
: IterationStatement(source_range)
, m_test(move(test))
, m_body(move(body))
{
}
Expression const& test() const { return *m_test; }
Statement const& body() const { return *m_body; }
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual Completion loop_evaluation(Interpreter&, GlobalObject&, Vector<FlyString> const&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
NonnullRefPtr<Expression> m_test;
NonnullRefPtr<Statement> m_body;
};
2020-11-28 14:05:57 +00:00
class WithStatement final : public Statement {
public:
WithStatement(SourceRange source_range, NonnullRefPtr<Expression> object, NonnullRefPtr<Statement> body)
: Statement(source_range)
, m_object(move(object))
2020-11-28 14:05:57 +00:00
, m_body(move(body))
{
}
Expression const& object() const { return *m_object; }
Statement const& body() const { return *m_body; }
2020-11-28 14:05:57 +00:00
virtual Completion execute(Interpreter&, GlobalObject&) const override;
2020-11-28 14:05:57 +00:00
virtual void dump(int indent) const override;
private:
NonnullRefPtr<Expression> m_object;
NonnullRefPtr<Statement> m_body;
};
class ForStatement final : public IterationStatement {
2020-03-12 12:12:12 +00:00
public:
ForStatement(SourceRange source_range, RefPtr<ASTNode> init, RefPtr<Expression> test, RefPtr<Expression> update, NonnullRefPtr<Statement> body)
: IterationStatement(source_range)
, m_init(move(init))
2020-03-12 12:12:12 +00:00
, m_test(move(test))
, m_update(move(update))
, m_body(move(body))
{
}
ASTNode const* init() const { return m_init; }
Expression const* test() const { return m_test; }
Expression const* update() const { return m_update; }
Statement const& body() const { return *m_body; }
2020-03-12 12:12:12 +00:00
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual Completion loop_evaluation(Interpreter&, GlobalObject&, Vector<FlyString> const&) const override;
2020-03-12 12:12:12 +00:00
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
2020-03-12 12:12:12 +00:00
private:
RefPtr<ASTNode> m_init;
RefPtr<Expression> m_test;
RefPtr<Expression> m_update;
NonnullRefPtr<Statement> m_body;
2020-03-12 12:12:12 +00:00
};
class ForInStatement final : public IterationStatement {
public:
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
ForInStatement(SourceRange source_range, Variant<NonnullRefPtr<ASTNode>, NonnullRefPtr<BindingPattern>> lhs, NonnullRefPtr<Expression> rhs, NonnullRefPtr<Statement> body)
: IterationStatement(source_range)
, m_lhs(move(lhs))
, m_rhs(move(rhs))
, m_body(move(body))
{
}
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
auto const& lhs() const { return m_lhs; }
Expression const& rhs() const { return *m_rhs; }
Statement const& body() const { return *m_body; }
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual Completion loop_evaluation(Interpreter&, GlobalObject&, Vector<FlyString> const&) const override;
virtual void dump(int indent) const override;
private:
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
Variant<NonnullRefPtr<ASTNode>, NonnullRefPtr<BindingPattern>> m_lhs;
NonnullRefPtr<Expression> m_rhs;
NonnullRefPtr<Statement> m_body;
};
class ForOfStatement final : public IterationStatement {
public:
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
ForOfStatement(SourceRange source_range, Variant<NonnullRefPtr<ASTNode>, NonnullRefPtr<BindingPattern>> lhs, NonnullRefPtr<Expression> rhs, NonnullRefPtr<Statement> body)
: IterationStatement(source_range)
, m_lhs(move(lhs))
, m_rhs(move(rhs))
, m_body(move(body))
{
}
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
auto const& lhs() const { return m_lhs; }
Expression const& rhs() const { return *m_rhs; }
Statement const& body() const { return *m_body; }
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual Completion loop_evaluation(Interpreter&, GlobalObject&, Vector<FlyString> const&) const override;
virtual void dump(int indent) const override;
private:
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
Variant<NonnullRefPtr<ASTNode>, NonnullRefPtr<BindingPattern>> m_lhs;
NonnullRefPtr<Expression> m_rhs;
NonnullRefPtr<Statement> m_body;
};
class ForAwaitOfStatement final : public IterationStatement {
public:
ForAwaitOfStatement(SourceRange source_range, Variant<NonnullRefPtr<ASTNode>, NonnullRefPtr<BindingPattern>> lhs, NonnullRefPtr<Expression> rhs, NonnullRefPtr<Statement> body)
: IterationStatement(source_range)
, m_lhs(move(lhs))
, m_rhs(move(rhs))
, m_body(move(body))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual Completion loop_evaluation(Interpreter&, GlobalObject&, Vector<FlyString> const&) const override;
virtual void dump(int indent) const override;
private:
Variant<NonnullRefPtr<ASTNode>, NonnullRefPtr<BindingPattern>> m_lhs;
NonnullRefPtr<Expression> m_rhs;
NonnullRefPtr<Statement> m_body;
};
enum class BinaryOp {
Addition,
Subtraction,
Multiplication,
Division,
Modulo,
Exponentiation,
StrictlyEquals,
StrictlyInequals,
LooselyEquals,
LooselyInequals,
GreaterThan,
GreaterThanEquals,
LessThan,
LessThanEquals,
BitwiseAnd,
BitwiseOr,
BitwiseXor,
LeftShift,
RightShift,
UnsignedRightShift,
2020-04-23 15:06:01 +00:00
In,
InstanceOf,
};
class BinaryExpression final : public Expression {
public:
BinaryExpression(SourceRange source_range, BinaryOp op, NonnullRefPtr<Expression> lhs, NonnullRefPtr<Expression> rhs)
: Expression(source_range)
, m_op(op)
, m_lhs(move(lhs))
, m_rhs(move(rhs))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
BinaryOp m_op;
NonnullRefPtr<Expression> m_lhs;
NonnullRefPtr<Expression> m_rhs;
};
enum class LogicalOp {
And,
Or,
NullishCoalescing,
};
class LogicalExpression final : public Expression {
public:
LogicalExpression(SourceRange source_range, LogicalOp op, NonnullRefPtr<Expression> lhs, NonnullRefPtr<Expression> rhs)
: Expression(source_range)
, m_op(op)
, m_lhs(move(lhs))
, m_rhs(move(rhs))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
LogicalOp m_op;
NonnullRefPtr<Expression> m_lhs;
NonnullRefPtr<Expression> m_rhs;
};
enum class UnaryOp {
BitwiseNot,
Not,
2020-04-02 16:58:39 +00:00
Plus,
Minus,
2020-03-17 19:33:32 +00:00
Typeof,
2020-04-15 16:55:03 +00:00
Void,
Delete,
};
class UnaryExpression final : public Expression {
public:
UnaryExpression(SourceRange source_range, UnaryOp op, NonnullRefPtr<Expression> lhs)
: Expression(source_range)
, m_op(op)
, m_lhs(move(lhs))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
UnaryOp m_op;
NonnullRefPtr<Expression> m_lhs;
};
class SequenceExpression final : public Expression {
public:
SequenceExpression(SourceRange source_range, NonnullRefPtrVector<Expression> expressions)
: Expression(source_range)
, m_expressions(move(expressions))
{
VERIFY(m_expressions.size() >= 2);
}
virtual void dump(int indent) const override;
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
NonnullRefPtrVector<Expression> m_expressions;
};
class Literal : public Expression {
protected:
explicit Literal(SourceRange source_range)
: Expression(source_range)
{
}
};
class BooleanLiteral final : public Literal {
public:
explicit BooleanLiteral(SourceRange source_range, bool value)
: Literal(source_range)
, m_value(value)
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
bool m_value { false };
};
class NumericLiteral final : public Literal {
public:
explicit NumericLiteral(SourceRange source_range, double value)
: Literal(source_range)
, m_value(value)
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
Value m_value;
};
2020-06-06 00:14:10 +00:00
class BigIntLiteral final : public Literal {
public:
explicit BigIntLiteral(SourceRange source_range, String value)
: Literal(source_range)
, m_value(move(value))
2020-06-06 00:14:10 +00:00
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
2020-06-06 00:14:10 +00:00
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
2020-06-06 00:14:10 +00:00
private:
String m_value;
};
class StringLiteral final : public Literal {
public:
explicit StringLiteral(SourceRange source_range, String value, bool is_use_strict_directive = false)
: Literal(source_range)
, m_value(move(value))
, m_is_use_strict_directive(is_use_strict_directive)
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
StringView value() const { return m_value; }
bool is_use_strict_directive() const { return m_is_use_strict_directive; };
private:
String m_value;
bool m_is_use_strict_directive;
};
class NullLiteral final : public Literal {
public:
explicit NullLiteral(SourceRange source_range)
: Literal(source_range)
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
};
class RegExpLiteral final : public Literal {
public:
RegExpLiteral(SourceRange source_range, regex::Parser::Result parsed_regex, String parsed_pattern, regex::RegexOptions<ECMAScriptFlags> parsed_flags, String pattern, String flags)
: Literal(source_range)
, m_parsed_regex(move(parsed_regex))
, m_parsed_pattern(move(parsed_pattern))
, m_parsed_flags(move(parsed_flags))
, m_pattern(move(pattern))
, m_flags(move(flags))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
regex::Parser::Result const& parsed_regex() const { return m_parsed_regex; }
String const& parsed_pattern() const { return m_parsed_pattern; }
regex::RegexOptions<ECMAScriptFlags> const& parsed_flags() const { return m_parsed_flags; }
String const& pattern() const { return m_pattern; }
String const& flags() const { return m_flags; }
private:
regex::Parser::Result m_parsed_regex;
String m_parsed_pattern;
regex::RegexOptions<ECMAScriptFlags> m_parsed_flags;
String m_pattern;
String m_flags;
};
class Identifier final : public Expression {
public:
explicit Identifier(SourceRange source_range, FlyString string)
: Expression(source_range)
, m_string(move(string))
{
}
FlyString const& string() const { return m_string; }
void set_lexically_bound_function_argument_index(size_t index) { m_lexically_bound_function_argument = index; }
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual ThrowCompletionOr<Reference> to_reference(Interpreter&, GlobalObject&) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
virtual bool is_identifier() const override { return true; }
FlyString m_string;
Optional<size_t> m_lexically_bound_function_argument;
mutable Optional<EnvironmentCoordinate> m_cached_environment_coordinate;
};
class PrivateIdentifier final : public Expression {
public:
explicit PrivateIdentifier(SourceRange source_range, FlyString string)
: Expression(source_range)
, m_string(move(string))
{
}
FlyString const& string() const { return m_string; }
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
private:
FlyString m_string;
};
class ClassElement : public ASTNode {
public:
ClassElement(SourceRange source_range, bool is_static)
: ASTNode(source_range)
, m_is_static(is_static)
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
enum class ElementKind {
Method,
Field,
StaticInitializer,
};
virtual ElementKind class_element_kind() const = 0;
bool is_static() const { return m_is_static; }
using ClassElementName = Variant<PropertyKey, PrivateName>;
struct ClassFieldDefinition {
ClassElementName name;
ECMAScriptFunctionObject* initializer { nullptr };
};
// We use the Completion also as a ClassStaticBlockDefinition Record.
using ClassValue = Variant<ClassFieldDefinition, Completion, PrivateElement>;
virtual ThrowCompletionOr<ClassValue> class_element_evaluation(Interpreter&, GlobalObject&, Object& home_object) const = 0;
virtual Optional<FlyString> private_bound_identifier() const { return {}; };
private:
bool m_is_static { false };
};
class ClassMethod final : public ClassElement {
public:
enum class Kind {
Method,
Getter,
Setter,
};
ClassMethod(SourceRange source_range, NonnullRefPtr<Expression> key, NonnullRefPtr<FunctionExpression> function, Kind kind, bool is_static)
: ClassElement(source_range, is_static)
, m_key(move(key))
, m_function(move(function))
, m_kind(kind)
{
}
Expression const& key() const { return *m_key; }
Kind kind() const { return m_kind; }
virtual ElementKind class_element_kind() const override { return ElementKind::Method; }
virtual void dump(int indent) const override;
virtual ThrowCompletionOr<ClassValue> class_element_evaluation(Interpreter&, GlobalObject&, Object& home_object) const override;
virtual Optional<FlyString> private_bound_identifier() const override;
private:
NonnullRefPtr<Expression> m_key;
NonnullRefPtr<FunctionExpression> m_function;
Kind m_kind;
};
class ClassField final : public ClassElement {
public:
ClassField(SourceRange source_range, NonnullRefPtr<Expression> key, RefPtr<Expression> init, bool contains_direct_call_to_eval, bool is_static)
: ClassElement(source_range, is_static)
, m_key(move(key))
, m_initializer(move(init))
, m_contains_direct_call_to_eval(contains_direct_call_to_eval)
{
}
Expression const& key() const { return *m_key; }
RefPtr<Expression> const& initializer() const { return m_initializer; }
RefPtr<Expression>& initializer() { return m_initializer; }
virtual ElementKind class_element_kind() const override { return ElementKind::Field; }
virtual void dump(int indent) const override;
virtual ThrowCompletionOr<ClassValue> class_element_evaluation(Interpreter& interpreter, GlobalObject& object, Object& home_object) const override;
virtual Optional<FlyString> private_bound_identifier() const override;
private:
NonnullRefPtr<Expression> m_key;
RefPtr<Expression> m_initializer;
bool m_contains_direct_call_to_eval { false };
};
class StaticInitializer final : public ClassElement {
public:
StaticInitializer(SourceRange source_range, NonnullRefPtr<FunctionBody> function_body, bool contains_direct_call_to_eval)
: ClassElement(source_range, true)
, m_function_body(move(function_body))
, m_contains_direct_call_to_eval(contains_direct_call_to_eval)
{
}
virtual ElementKind class_element_kind() const override { return ElementKind::StaticInitializer; }
virtual ThrowCompletionOr<ClassValue> class_element_evaluation(Interpreter&, GlobalObject&, Object& home_object) const override;
virtual void dump(int indent) const override;
private:
NonnullRefPtr<FunctionBody> m_function_body;
bool m_contains_direct_call_to_eval { false };
};
class SuperExpression final : public Expression {
public:
explicit SuperExpression(SourceRange source_range)
: Expression(source_range)
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual bool is_super_expression() const override { return true; }
};
class ClassExpression final : public Expression {
public:
ClassExpression(SourceRange source_range, String name, RefPtr<FunctionExpression> constructor, RefPtr<Expression> super_class, NonnullRefPtrVector<ClassElement> elements)
: Expression(source_range)
, m_name(move(name))
, m_constructor(move(constructor))
, m_super_class(move(super_class))
, m_elements(move(elements))
{
}
StringView name() const { return m_name; }
RefPtr<FunctionExpression> constructor() const { return m_constructor; }
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
bool has_name() const { return !m_name.is_empty(); }
ThrowCompletionOr<Value> class_definition_evaluation(Interpreter& interpreter, GlobalObject& global_object, FlyString const& binding_name = {}, FlyString const& class_name = {}) const;
private:
virtual bool is_class_expression() const override { return true; }
String m_name;
RefPtr<FunctionExpression> m_constructor;
RefPtr<Expression> m_super_class;
NonnullRefPtrVector<ClassElement> m_elements;
};
class ClassDeclaration final : public Declaration {
public:
ClassDeclaration(SourceRange source_range, NonnullRefPtr<ClassExpression> class_expression)
: Declaration(source_range)
, m_class_expression(move(class_expression))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
void for_each_bound_name(IteratorOrVoidFunction<FlyString const&> callback) const override;
virtual bool is_lexical_declaration() const override { return true; }
StringView name() const { return m_class_expression->name(); }
ThrowCompletionOr<Value> binding_class_declaration_evaluation(Interpreter& interpreter, GlobalObject& global_object) const;
private:
NonnullRefPtr<ClassExpression> m_class_expression;
};
class SpreadExpression final : public Expression {
public:
explicit SpreadExpression(SourceRange source_range, NonnullRefPtr<Expression> target)
: Expression(source_range)
, m_target(move(target))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
private:
NonnullRefPtr<Expression> m_target;
};
2020-04-12 22:42:14 +00:00
class ThisExpression final : public Expression {
public:
explicit ThisExpression(SourceRange source_range)
: Expression(source_range)
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
2020-04-12 22:42:14 +00:00
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
2020-04-12 22:42:14 +00:00
};
class CallExpression : public Expression {
public:
struct Argument {
NonnullRefPtr<Expression> value;
bool is_spread;
};
CallExpression(SourceRange source_range, NonnullRefPtr<Expression> callee, Vector<Argument> arguments = {})
: Expression(source_range)
, m_callee(move(callee))
, m_arguments(move(arguments))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
Expression const& callee() const { return m_callee; }
protected:
Completion throw_type_error_for_callee(Interpreter&, GlobalObject&, Value callee_value, StringView call_type) const;
NonnullRefPtr<Expression> m_callee;
Vector<Argument> const m_arguments;
private:
struct ThisAndCallee {
Value this_value;
Value callee;
};
ThrowCompletionOr<ThisAndCallee> compute_this_and_callee(Interpreter&, GlobalObject&, Reference const&) const;
};
class NewExpression final : public CallExpression {
public:
NewExpression(SourceRange source_range, NonnullRefPtr<Expression> callee, Vector<Argument> arguments = {})
: CallExpression(source_range, move(callee), move(arguments))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual bool is_new_expression() const override { return true; }
};
class SuperCall final : public Expression {
public:
SuperCall(SourceRange source_range, Vector<CallExpression::Argument> arguments)
: Expression(source_range)
, m_arguments(move(arguments))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
private:
Vector<CallExpression::Argument> const m_arguments;
};
enum class AssignmentOp {
2020-03-12 12:54:56 +00:00
Assignment,
AdditionAssignment,
SubtractionAssignment,
MultiplicationAssignment,
DivisionAssignment,
ModuloAssignment,
ExponentiationAssignment,
BitwiseAndAssignment,
BitwiseOrAssignment,
BitwiseXorAssignment,
LeftShiftAssignment,
RightShiftAssignment,
UnsignedRightShiftAssignment,
AndAssignment,
OrAssignment,
NullishAssignment,
};
class AssignmentExpression final : public Expression {
public:
AssignmentExpression(SourceRange source_range, AssignmentOp op, NonnullRefPtr<Expression> lhs, NonnullRefPtr<Expression> rhs)
: Expression(source_range)
, m_op(op)
, m_lhs(move(lhs))
, m_rhs(move(rhs))
{
}
AssignmentExpression(SourceRange source_range, AssignmentOp op, NonnullRefPtr<BindingPattern> lhs, NonnullRefPtr<Expression> rhs)
: Expression(source_range)
, m_op(op)
, m_lhs(move(lhs))
, m_rhs(move(rhs))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
AssignmentOp m_op;
Variant<NonnullRefPtr<Expression>, NonnullRefPtr<BindingPattern>> m_lhs;
NonnullRefPtr<Expression> m_rhs;
};
enum class UpdateOp {
Increment,
Decrement,
};
class UpdateExpression final : public Expression {
public:
UpdateExpression(SourceRange source_range, UpdateOp op, NonnullRefPtr<Expression> argument, bool prefixed = false)
: Expression(source_range)
, m_op(op)
, m_argument(move(argument))
, m_prefixed(prefixed)
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
UpdateOp m_op;
NonnullRefPtr<Expression> m_argument;
bool m_prefixed;
};
enum class DeclarationKind {
Var,
Let,
Const,
};
class VariableDeclarator final : public ASTNode {
public:
VariableDeclarator(SourceRange source_range, NonnullRefPtr<Identifier> id)
: ASTNode(source_range)
, m_target(move(id))
{
}
VariableDeclarator(SourceRange source_range, NonnullRefPtr<Identifier> target, RefPtr<Expression> init)
: ASTNode(source_range)
, m_target(move(target))
, m_init(move(init))
{
}
VariableDeclarator(SourceRange source_range, Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>> target, RefPtr<Expression> init)
: ASTNode(source_range)
, m_target(move(target))
, m_init(move(init))
{
}
auto& target() const { return m_target; }
Expression const* init() const { return m_init; }
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
private:
Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>> m_target;
RefPtr<Expression> m_init;
};
class VariableDeclaration final : public Declaration {
public:
VariableDeclaration(SourceRange source_range, DeclarationKind declaration_kind, NonnullRefPtrVector<VariableDeclarator> declarations)
: Declaration(source_range)
, m_declaration_kind(declaration_kind)
, m_declarations(move(declarations))
{
}
DeclarationKind declaration_kind() const { return m_declaration_kind; }
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
NonnullRefPtrVector<VariableDeclarator> const& declarations() const { return m_declarations; }
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
virtual void for_each_bound_name(IteratorOrVoidFunction<FlyString const&> callback) const override;
virtual bool is_constant_declaration() const override { return m_declaration_kind == DeclarationKind::Const; };
virtual bool is_lexical_declaration() const override { return m_declaration_kind != DeclarationKind::Var; }
private:
DeclarationKind m_declaration_kind;
NonnullRefPtrVector<VariableDeclarator> m_declarations;
};
class ObjectProperty final : public ASTNode {
public:
enum class Type {
KeyValue,
Getter,
Setter,
Spread,
};
ObjectProperty(SourceRange source_range, NonnullRefPtr<Expression> key, RefPtr<Expression> value, Type property_type, bool is_method)
: ASTNode(source_range)
, m_key(move(key))
, m_value(move(value))
, m_property_type(property_type)
, m_is_method(is_method)
{
}
Expression const& key() const { return m_key; }
Expression const& value() const
{
VERIFY(m_value);
return *m_value;
}
Type type() const { return m_property_type; }
bool is_method() const { return m_is_method; }
virtual void dump(int indent) const override;
virtual Completion execute(Interpreter&, GlobalObject&) const override;
private:
NonnullRefPtr<Expression> m_key;
RefPtr<Expression> m_value;
Type m_property_type;
bool m_is_method { false };
};
class ObjectExpression final : public Expression {
public:
explicit ObjectExpression(SourceRange source_range, NonnullRefPtrVector<ObjectProperty> properties = {}, Optional<SourceRange> first_invalid_property_range = {})
: Expression(source_range)
, m_properties(move(properties))
, m_first_invalid_property_range(move(first_invalid_property_range))
2020-03-21 00:29:00 +00:00
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
Optional<SourceRange> const& invalid_property_range() const { return m_first_invalid_property_range; }
private:
NonnullRefPtrVector<ObjectProperty> m_properties;
Optional<SourceRange> m_first_invalid_property_range;
};
class ArrayExpression final : public Expression {
public:
ArrayExpression(SourceRange source_range, Vector<RefPtr<Expression>> elements)
: Expression(source_range)
, m_elements(move(elements))
{
}
Vector<RefPtr<Expression>> const& elements() const { return m_elements; }
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
Vector<RefPtr<Expression>> m_elements;
};
LibJS: Add template literals Adds fully functioning template literals. Because template literals contain expressions, most of the work has to be done in the Lexer rather than the Parser. And because of the complexity of template literals (expressions, nesting, escapes, etc), the Lexer needs to have some template-related state. When entering a new template literal, a TemplateLiteralStart token is emitted. When inside a literal, all text will be parsed up until a '${' or '`' (or EOF, but that's a syntax error) is seen, and then a TemplateLiteralExprStart token is emitted. At this point, the Lexer proceeds as normal, however it keeps track of the number of opening and closing curly braces it has seen in order to determine the close of the expression. Once it finds a matching curly brace for the '${', a TemplateLiteralExprEnd token is emitted and the state is updated accordingly. When the Lexer is inside of a template literal, but not an expression, and sees a '`', this must be the closing grave: a TemplateLiteralEnd token is emitted. The state required to correctly parse template strings consists of a vector (for nesting) of two pieces of information: whether or not we are in a template expression (as opposed to a template string); and the count of the number of unmatched open curly braces we have seen (only applicable if the Lexer is currently in a template expression). TODO: Add support for template literal newlines in the JS REPL (this will cause a syntax error currently): > `foo > bar` 'foo bar'
2020-05-03 22:41:14 +00:00
class TemplateLiteral final : public Expression {
public:
TemplateLiteral(SourceRange source_range, NonnullRefPtrVector<Expression> expressions)
: Expression(source_range)
, m_expressions(move(expressions))
{
}
TemplateLiteral(SourceRange source_range, NonnullRefPtrVector<Expression> expressions, NonnullRefPtrVector<Expression> raw_strings)
: Expression(source_range)
, m_expressions(move(expressions))
, m_raw_strings(move(raw_strings))
LibJS: Add template literals Adds fully functioning template literals. Because template literals contain expressions, most of the work has to be done in the Lexer rather than the Parser. And because of the complexity of template literals (expressions, nesting, escapes, etc), the Lexer needs to have some template-related state. When entering a new template literal, a TemplateLiteralStart token is emitted. When inside a literal, all text will be parsed up until a '${' or '`' (or EOF, but that's a syntax error) is seen, and then a TemplateLiteralExprStart token is emitted. At this point, the Lexer proceeds as normal, however it keeps track of the number of opening and closing curly braces it has seen in order to determine the close of the expression. Once it finds a matching curly brace for the '${', a TemplateLiteralExprEnd token is emitted and the state is updated accordingly. When the Lexer is inside of a template literal, but not an expression, and sees a '`', this must be the closing grave: a TemplateLiteralEnd token is emitted. The state required to correctly parse template strings consists of a vector (for nesting) of two pieces of information: whether or not we are in a template expression (as opposed to a template string); and the count of the number of unmatched open curly braces we have seen (only applicable if the Lexer is currently in a template expression). TODO: Add support for template literal newlines in the JS REPL (this will cause a syntax error currently): > `foo > bar` 'foo bar'
2020-05-03 22:41:14 +00:00
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
LibJS: Add template literals Adds fully functioning template literals. Because template literals contain expressions, most of the work has to be done in the Lexer rather than the Parser. And because of the complexity of template literals (expressions, nesting, escapes, etc), the Lexer needs to have some template-related state. When entering a new template literal, a TemplateLiteralStart token is emitted. When inside a literal, all text will be parsed up until a '${' or '`' (or EOF, but that's a syntax error) is seen, and then a TemplateLiteralExprStart token is emitted. At this point, the Lexer proceeds as normal, however it keeps track of the number of opening and closing curly braces it has seen in order to determine the close of the expression. Once it finds a matching curly brace for the '${', a TemplateLiteralExprEnd token is emitted and the state is updated accordingly. When the Lexer is inside of a template literal, but not an expression, and sees a '`', this must be the closing grave: a TemplateLiteralEnd token is emitted. The state required to correctly parse template strings consists of a vector (for nesting) of two pieces of information: whether or not we are in a template expression (as opposed to a template string); and the count of the number of unmatched open curly braces we have seen (only applicable if the Lexer is currently in a template expression). TODO: Add support for template literal newlines in the JS REPL (this will cause a syntax error currently): > `foo > bar` 'foo bar'
2020-05-03 22:41:14 +00:00
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
LibJS: Add template literals Adds fully functioning template literals. Because template literals contain expressions, most of the work has to be done in the Lexer rather than the Parser. And because of the complexity of template literals (expressions, nesting, escapes, etc), the Lexer needs to have some template-related state. When entering a new template literal, a TemplateLiteralStart token is emitted. When inside a literal, all text will be parsed up until a '${' or '`' (or EOF, but that's a syntax error) is seen, and then a TemplateLiteralExprStart token is emitted. At this point, the Lexer proceeds as normal, however it keeps track of the number of opening and closing curly braces it has seen in order to determine the close of the expression. Once it finds a matching curly brace for the '${', a TemplateLiteralExprEnd token is emitted and the state is updated accordingly. When the Lexer is inside of a template literal, but not an expression, and sees a '`', this must be the closing grave: a TemplateLiteralEnd token is emitted. The state required to correctly parse template strings consists of a vector (for nesting) of two pieces of information: whether or not we are in a template expression (as opposed to a template string); and the count of the number of unmatched open curly braces we have seen (only applicable if the Lexer is currently in a template expression). TODO: Add support for template literal newlines in the JS REPL (this will cause a syntax error currently): > `foo > bar` 'foo bar'
2020-05-03 22:41:14 +00:00
NonnullRefPtrVector<Expression> const& expressions() const { return m_expressions; }
NonnullRefPtrVector<Expression> const& raw_strings() const { return m_raw_strings; }
LibJS: Add template literals Adds fully functioning template literals. Because template literals contain expressions, most of the work has to be done in the Lexer rather than the Parser. And because of the complexity of template literals (expressions, nesting, escapes, etc), the Lexer needs to have some template-related state. When entering a new template literal, a TemplateLiteralStart token is emitted. When inside a literal, all text will be parsed up until a '${' or '`' (or EOF, but that's a syntax error) is seen, and then a TemplateLiteralExprStart token is emitted. At this point, the Lexer proceeds as normal, however it keeps track of the number of opening and closing curly braces it has seen in order to determine the close of the expression. Once it finds a matching curly brace for the '${', a TemplateLiteralExprEnd token is emitted and the state is updated accordingly. When the Lexer is inside of a template literal, but not an expression, and sees a '`', this must be the closing grave: a TemplateLiteralEnd token is emitted. The state required to correctly parse template strings consists of a vector (for nesting) of two pieces of information: whether or not we are in a template expression (as opposed to a template string); and the count of the number of unmatched open curly braces we have seen (only applicable if the Lexer is currently in a template expression). TODO: Add support for template literal newlines in the JS REPL (this will cause a syntax error currently): > `foo > bar` 'foo bar'
2020-05-03 22:41:14 +00:00
private:
NonnullRefPtrVector<Expression> const m_expressions;
NonnullRefPtrVector<Expression> const m_raw_strings;
LibJS: Add template literals Adds fully functioning template literals. Because template literals contain expressions, most of the work has to be done in the Lexer rather than the Parser. And because of the complexity of template literals (expressions, nesting, escapes, etc), the Lexer needs to have some template-related state. When entering a new template literal, a TemplateLiteralStart token is emitted. When inside a literal, all text will be parsed up until a '${' or '`' (or EOF, but that's a syntax error) is seen, and then a TemplateLiteralExprStart token is emitted. At this point, the Lexer proceeds as normal, however it keeps track of the number of opening and closing curly braces it has seen in order to determine the close of the expression. Once it finds a matching curly brace for the '${', a TemplateLiteralExprEnd token is emitted and the state is updated accordingly. When the Lexer is inside of a template literal, but not an expression, and sees a '`', this must be the closing grave: a TemplateLiteralEnd token is emitted. The state required to correctly parse template strings consists of a vector (for nesting) of two pieces of information: whether or not we are in a template expression (as opposed to a template string); and the count of the number of unmatched open curly braces we have seen (only applicable if the Lexer is currently in a template expression). TODO: Add support for template literal newlines in the JS REPL (this will cause a syntax error currently): > `foo > bar` 'foo bar'
2020-05-03 22:41:14 +00:00
};
class TaggedTemplateLiteral final : public Expression {
public:
TaggedTemplateLiteral(SourceRange source_range, NonnullRefPtr<Expression> tag, NonnullRefPtr<TemplateLiteral> template_literal)
: Expression(source_range)
, m_tag(move(tag))
, m_template_literal(move(template_literal))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
NonnullRefPtr<Expression> const m_tag;
NonnullRefPtr<TemplateLiteral> const m_template_literal;
};
class MemberExpression final : public Expression {
public:
MemberExpression(SourceRange source_range, NonnullRefPtr<Expression> object, NonnullRefPtr<Expression> property, bool computed = false)
: Expression(source_range)
, m_object(move(object))
, m_property(move(property))
, m_computed(computed)
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
virtual ThrowCompletionOr<Reference> to_reference(Interpreter&, GlobalObject&) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
bool is_computed() const { return m_computed; }
Expression const& object() const { return *m_object; }
Expression const& property() const { return *m_property; }
String to_string_approximation() const;
bool ends_in_private_name() const;
private:
virtual bool is_member_expression() const override { return true; }
NonnullRefPtr<Expression> m_object;
NonnullRefPtr<Expression> m_property;
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;
};
struct PrivateMemberReference {
NonnullRefPtr<PrivateIdentifier> private_identifier;
Mode mode;
};
using Reference = Variant<Call, ComputedReference, MemberReference, PrivateMemberReference>;
OptionalChain(SourceRange source_range, NonnullRefPtr<Expression> base, Vector<Reference> references)
: Expression(source_range)
, m_base(move(base))
, m_references(move(references))
{
}
virtual Completion execute(Interpreter& interpreter, GlobalObject& global_object) const override;
virtual ThrowCompletionOr<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;
};
ThrowCompletionOr<ReferenceAndValue> to_reference_and_value(Interpreter&, GlobalObject&) const;
NonnullRefPtr<Expression> m_base;
Vector<Reference> m_references;
};
class MetaProperty final : public Expression {
public:
enum class Type {
NewTarget,
ImportMeta,
};
MetaProperty(SourceRange source_range, Type type)
: Expression(source_range)
, m_type(type)
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
private:
Type m_type;
};
class ImportCall final : public Expression {
public:
ImportCall(SourceRange source_range, NonnullRefPtr<Expression> specifier, RefPtr<Expression> options)
: Expression(source_range)
, m_specifier(move(specifier))
, m_options(move(options))
{
}
virtual void dump(int indent) const override;
virtual Completion execute(Interpreter&, GlobalObject&) const override;
private:
NonnullRefPtr<Expression> m_specifier;
RefPtr<Expression> m_options;
};
class ConditionalExpression final : public Expression {
public:
ConditionalExpression(SourceRange source_range, NonnullRefPtr<Expression> test, NonnullRefPtr<Expression> consequent, NonnullRefPtr<Expression> alternate)
: Expression(source_range)
, m_test(move(test))
, m_consequent(move(consequent))
, m_alternate(move(alternate))
{
}
virtual void dump(int indent) const override;
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
NonnullRefPtr<Expression> m_test;
NonnullRefPtr<Expression> m_consequent;
NonnullRefPtr<Expression> m_alternate;
};
class CatchClause final : public ASTNode {
public:
CatchClause(SourceRange source_range, FlyString parameter, NonnullRefPtr<BlockStatement> body)
: ASTNode(source_range)
, m_parameter(move(parameter))
, m_body(move(body))
{
}
CatchClause(SourceRange source_range, NonnullRefPtr<BindingPattern> parameter, NonnullRefPtr<BlockStatement> body)
: ASTNode(source_range)
, m_parameter(move(parameter))
, m_body(move(body))
{
}
auto& parameter() const { return m_parameter; }
BlockStatement const& body() const { return m_body; }
virtual void dump(int indent) const override;
virtual Completion execute(Interpreter&, GlobalObject&) const override;
private:
Variant<FlyString, NonnullRefPtr<BindingPattern>> m_parameter;
NonnullRefPtr<BlockStatement> m_body;
};
class TryStatement final : public Statement {
public:
TryStatement(SourceRange source_range, NonnullRefPtr<BlockStatement> block, RefPtr<CatchClause> handler, RefPtr<BlockStatement> finalizer)
: Statement(source_range)
, m_block(move(block))
, m_handler(move(handler))
, m_finalizer(move(finalizer))
{
}
BlockStatement const& block() const { return m_block; }
CatchClause const* handler() const { return m_handler; }
BlockStatement const* finalizer() const { return m_finalizer; }
virtual void dump(int indent) const override;
virtual Completion execute(Interpreter&, GlobalObject&) const override;
LibJS: Implement bytecode generation for try..catch..finally EnterUnwindContext pushes an unwind context (exception handler and/or finalizer) onto a stack. LeaveUnwindContext pops the unwind context from that stack. Upon return to the interpreter loop we check whether the VM has an exception pending. If no unwind context is available we return from the loop. If an exception handler is available we clear the VM's exception, put the exception value into the accumulator register, clear the unwind context's handler and jump to the handler. If no handler is available but a finalizer is available we save the exception value + metadata (for later use by ContinuePendingUnwind), clear the VM's exception, pop the unwind context and jump to the finalizer. ContinuePendingUnwind checks whether a saved exception is available. If no saved exception is available it jumps to the resume label. Otherwise it stores the exception into the VM. The Jump after LeaveUnwindContext could be integrated into the LeaveUnwindContext instruction. I've kept them separate for now to make the bytecode more readable. > try { 1; throw "x" } catch (e) { 2 } finally { 3 }; 4 1: [ 0] EnterScope [ 10] EnterUnwindContext handler:@4 finalizer:@3 [ 38] EnterScope [ 48] LoadImmediate 1 [ 60] NewString 1 ("x") [ 70] Throw <for non-terminated blocks: insert LeaveUnwindContext + Jump @3 here> 2: [ 0] LoadImmediate 4 3: [ 0] EnterScope [ 10] LoadImmediate 3 [ 28] ContinuePendingUnwind resume:@2 4: [ 0] SetVariable 0 (e) [ 10] EnterScope [ 20] LoadImmediate 2 [ 38] LeaveUnwindContext [ 3c] Jump @3 String Table: 0: e 1: x
2021-06-10 13:04:38 +00:00
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
NonnullRefPtr<BlockStatement> m_block;
RefPtr<CatchClause> m_handler;
RefPtr<BlockStatement> m_finalizer;
};
class ThrowStatement final : public Statement {
public:
explicit ThrowStatement(SourceRange source_range, NonnullRefPtr<Expression> argument)
: Statement(source_range)
, m_argument(move(argument))
{
}
Expression const& argument() const { return m_argument; }
virtual void dump(int indent) const override;
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
NonnullRefPtr<Expression> m_argument;
};
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
class SwitchCase final : public ScopeNode {
public:
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
SwitchCase(SourceRange source_range, RefPtr<Expression> test)
: ScopeNode(source_range)
, m_test(move(test))
{
}
Expression const* test() const { return m_test; }
virtual void dump(int indent) const override;
virtual Completion execute(Interpreter&, GlobalObject&) const override;
private:
RefPtr<Expression> m_test;
};
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
class SwitchStatement final : public ScopeNode {
public:
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
SwitchStatement(SourceRange source_range, NonnullRefPtr<Expression> discriminant)
: ScopeNode(source_range)
, m_discriminant(move(discriminant))
{
}
virtual void dump(int indent) const override;
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
Completion execute_impl(Interpreter&, GlobalObject&) const;
LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^).
2021-09-22 10:44:56 +00:00
void add_case(NonnullRefPtr<SwitchCase> switch_case) { m_cases.append(move(switch_case)); }
private:
NonnullRefPtr<Expression> m_discriminant;
NonnullRefPtrVector<SwitchCase> m_cases;
};
class BreakStatement final : public Statement {
public:
BreakStatement(SourceRange source_range, FlyString target_label)
: Statement(source_range)
, m_target_label(move(target_label))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
FlyString const& target_label() const { return m_target_label; }
virtual void generate_bytecode(Bytecode::Generator&) const override;
private:
FlyString m_target_label;
};
class ContinueStatement final : public Statement {
public:
ContinueStatement(SourceRange source_range, FlyString target_label)
: Statement(source_range)
, m_target_label(move(target_label))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override;
FlyString const& target_label() const { return m_target_label; }
private:
FlyString m_target_label;
};
class DebuggerStatement final : public Statement {
public:
explicit DebuggerStatement(SourceRange source_range)
: Statement(source_range)
{
}
virtual Completion execute(Interpreter&, GlobalObject&) 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 Completion execute(Interpreter&, GlobalObject&) const override { return m_value; }
virtual ThrowCompletionOr<Reference> to_reference(Interpreter&, GlobalObject&) const override { return m_reference; }
private:
Reference m_reference;
Value m_value;
};
template<typename C>
void BindingPattern::for_each_bound_name(C&& callback) const
{
for (auto& entry : entries) {
auto& alias = entry.alias;
if (alias.has<NonnullRefPtr<Identifier>>()) {
callback(alias.get<NonnullRefPtr<Identifier>>()->string());
} else if (alias.has<NonnullRefPtr<BindingPattern>>()) {
alias.get<NonnullRefPtr<BindingPattern>>()->for_each_bound_name(forward<C>(callback));
} else {
auto& name = entry.name;
if (name.has<NonnullRefPtr<Identifier>>())
callback(name.get<NonnullRefPtr<Identifier>>()->string());
}
}
}
template<>
inline bool ASTNode::fast_is<NewExpression>() const { return is_new_expression(); }
template<>
inline bool ASTNode::fast_is<MemberExpression>() const { return is_member_expression(); }
template<>
inline bool ASTNode::fast_is<SuperExpression>() const { return is_super_expression(); }
template<>
inline bool ASTNode::fast_is<FunctionExpression>() const { return is_function_expression(); }
template<>
inline bool ASTNode::fast_is<ClassExpression>() const { return is_class_expression(); }
template<>
inline bool ASTNode::fast_is<Identifier>() const { return is_identifier(); }
template<>
inline bool ASTNode::fast_is<ExpressionStatement>() const { return is_expression_statement(); }
template<>
inline bool ASTNode::fast_is<ScopeNode>() const { return is_scope_node(); }
template<>
inline bool ASTNode::fast_is<Program>() const { return is_program(); }
template<>
inline bool ASTNode::fast_is<FunctionDeclaration>() const { return is_function_declaration(); }
}