From 1382dbc5e19cb16b3eee672745d3782188ed5749 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Mon, 9 Mar 2020 21:13:55 +0100 Subject: [PATCH] LibJS: Add basic support for (scoped) variables It's now possible to assign expressions to variables. The variables are put into the current scope of the interpreter. Variable lookup follows the scope chain, ending in the global object. --- Libraries/LibJS/AST.cpp | 60 ++++++++++++++++++++++++++ Libraries/LibJS/AST.h | 70 ++++++++++++++++++++++++++++-- Libraries/LibJS/Interpreter.cpp | 32 +++++++++++++- Libraries/LibJS/Interpreter.h | 6 +++ Userland/js.cpp | 75 ++++++++++++++++++++++++++------- 5 files changed, 224 insertions(+), 19 deletions(-) diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp index 2e6acb947c..acac0b3273 100644 --- a/Libraries/LibJS/AST.cpp +++ b/Libraries/LibJS/AST.cpp @@ -393,4 +393,64 @@ void WhileStatement::dump(int indent) const body().dump(indent + 1); } +Value Identifier::execute(Interpreter& interpreter) const +{ + return interpreter.get_variable(string()); +} + +void Identifier::dump(int indent) const +{ + print_indent(indent); + printf("Identifier \"%s\"\n", m_string.characters()); +} + +Value AssignmentExpression::execute(Interpreter& interpreter) const +{ + ASSERT(m_lhs->is_identifier()); + auto name = static_cast(*m_lhs).string(); + auto rhs_result = m_rhs->execute(interpreter); + + switch (m_op) { + case AssignmentOp::Assign: + interpreter.set_variable(name, rhs_result); + break; + } + return rhs_result; +} + +void AssignmentExpression::dump(int indent) const +{ + const char* op_string = nullptr; + switch (m_op) { + case AssignmentOp::Assign: + op_string = "="; + break; + } + + ASTNode::dump(indent); + print_indent(indent + 1); + printf("%s\n", op_string); + m_lhs->dump(indent + 1); + m_rhs->dump(indent + 1); +} + +Value VariableDeclaration::execute(Interpreter& interpreter) const +{ + interpreter.declare_variable(name().string()); + if (m_initializer) { + auto initalizer_result = m_initializer->execute(interpreter); + interpreter.set_variable(name().string(), initalizer_result); + } + return js_undefined(); +} + + +void VariableDeclaration::dump(int indent) const +{ + ASTNode::dump(indent); + m_name->dump(indent + 1); + if (m_initializer) + m_initializer->dump(indent + 1); +} + } diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h index 23a2dcbb3b..9e51124a30 100644 --- a/Libraries/LibJS/AST.h +++ b/Libraries/LibJS/AST.h @@ -40,6 +40,7 @@ public: virtual const char* class_name() const = 0; virtual Value execute(Interpreter&) const = 0; virtual void dump(int indent) const; + virtual bool is_identifier() const { return false; } protected: ASTNode() {} @@ -188,7 +189,7 @@ enum class BinaryOp { class BinaryExpression : public Expression { public: - BinaryExpression(BinaryOp op, NonnullOwnPtr lhs, NonnullOwnPtr rhs) + BinaryExpression(BinaryOp op, NonnullOwnPtr lhs, NonnullOwnPtr rhs) : m_op(op) , m_lhs(move(lhs)) , m_rhs(move(rhs)) @@ -202,8 +203,8 @@ private: virtual const char* class_name() const override { return "BinaryExpression"; } BinaryOp m_op; - NonnullOwnPtr m_lhs; - NonnullOwnPtr m_rhs; + NonnullOwnPtr m_lhs; + NonnullOwnPtr m_rhs; }; enum class LogicalOp { @@ -270,6 +271,25 @@ private: Value m_value; }; +class Identifier final : public ASTNode { +public: + explicit Identifier(String string) + : m_string(move(string)) + { + } + + const String& string() const { return m_string; } + + virtual Value execute(Interpreter&) const override; + virtual void dump(int indent) const override; + virtual bool is_identifier() const override { return true; } + +private: + virtual const char* class_name() const override { return "Identifier"; } + + String m_string; +}; + class CallExpression : public Expression { public: explicit CallExpression(String name) @@ -288,4 +308,48 @@ private: String m_name; }; +enum class AssignmentOp { + Assign, +}; + +class AssignmentExpression : public Expression { +public: + AssignmentExpression(AssignmentOp op, NonnullOwnPtr lhs, NonnullOwnPtr rhs) + : m_op(op) + , m_lhs(move(lhs)) + , m_rhs(move(rhs)) + { + } + + virtual Value execute(Interpreter&) const override; + virtual void dump(int indent) const override; + +private: + virtual const char* class_name() const override { return "AssignmentExpression"; } + + AssignmentOp m_op; + NonnullOwnPtr m_lhs; + NonnullOwnPtr m_rhs; +}; + +class VariableDeclaration : public ASTNode { +public: + VariableDeclaration(NonnullOwnPtr name, OwnPtr initializer) + : m_name(move(name)) + , m_initializer(move(initializer)) + { + } + + const Identifier& name() const { return *m_name; } + + virtual Value execute(Interpreter&) const override; + virtual void dump(int indent) const override; + +private: + virtual const char* class_name() const override { return "VariableDeclaration"; } + + NonnullOwnPtr m_name; + OwnPtr m_initializer; +}; + } diff --git a/Libraries/LibJS/Interpreter.cpp b/Libraries/LibJS/Interpreter.cpp index 102ee10ed1..3c8f155825 100644 --- a/Libraries/LibJS/Interpreter.cpp +++ b/Libraries/LibJS/Interpreter.cpp @@ -56,7 +56,7 @@ Value Interpreter::run(const ScopeNode& scope_node) void Interpreter::enter_scope(const ScopeNode& scope_node) { - m_scope_stack.append({ scope_node }); + m_scope_stack.append({ scope_node, {} }); } void Interpreter::exit_scope(const ScopeNode& scope_node) @@ -70,4 +70,34 @@ void Interpreter::do_return() dbg() << "FIXME: Implement Interpreter::do_return()"; } +void Interpreter::declare_variable(String name) +{ + m_scope_stack.last().variables.set(move(name), js_undefined()); +} + +void Interpreter::set_variable(String name, Value value) +{ + for (ssize_t i = m_scope_stack.size() - 1; i >= 0; --i) { + auto& scope = m_scope_stack.at(i); + if (scope.variables.contains(name)) { + scope.variables.set(move(name), move(value)); + return; + } + } + + global_object().put(move(name), move(value)); +} + +Value Interpreter::get_variable(const String& name) +{ + for (ssize_t i = m_scope_stack.size() - 1; i >= 0; --i) { + auto& scope = m_scope_stack.at(i); + auto value = scope.variables.get(name); + if (value.has_value()) + return value.value(); + } + + return global_object().get(name); +} + } diff --git a/Libraries/LibJS/Interpreter.h b/Libraries/LibJS/Interpreter.h index 3de8e0a99e..e13dc8e518 100644 --- a/Libraries/LibJS/Interpreter.h +++ b/Libraries/LibJS/Interpreter.h @@ -26,6 +26,7 @@ #pragma once +#include #include #include #include @@ -34,6 +35,7 @@ namespace JS { struct ScopeFrame { const ScopeNode& scope_node; + HashMap variables; }; class Interpreter { @@ -50,6 +52,10 @@ public: void do_return(); + Value get_variable(const String& name); + void set_variable(String name, Value); + void declare_variable(String name); + private: void enter_scope(const ScopeNode&); void exit_scope(const ScopeNode&); diff --git a/Userland/js.cpp b/Userland/js.cpp index 2787ddedd4..419f4b7ced 100644 --- a/Userland/js.cpp +++ b/Userland/js.cpp @@ -31,24 +31,13 @@ #include #include +//static void build_program_1(JS::Program&); +static void build_program_2(JS::Program&); + int main() { - // function foo() { return (1 + 2) + 3; } - // foo(); auto program = make(); - - auto block = make(); - block->append( - make( - JS::BinaryOp::Plus, - make( - JS::BinaryOp::Plus, - make(JS::Value(1)), - make(JS::Value(2))), - make(JS::Value(3)))); - - program->append("foo", move(block)); - program->append("foo"); + build_program_2(*program); program->dump(0); @@ -68,3 +57,59 @@ int main() interpreter.heap().collect_garbage(); return 0; } + +#if 0 +void build_program_1(JS::Program& program) +{ + // function foo() { return (1 + 2) + 3; } + // foo(); + + auto block = make(); + block->append( + make( + JS::BinaryOp::Plus, + make( + JS::BinaryOp::Plus, + make(JS::Value(1)), + make(JS::Value(2))), + make(JS::Value(3)))); + + program.append("foo", move(block)); + program.append("foo"); +} +#endif + +void build_program_2(JS::Program& program) +{ + // c = 1; + // function foo() { + // var a = 5; + // var b = 7; + // return a + b + c; + // } + // foo(); + + program.append( + JS::AssignmentOp::Assign, + make("c"), + make(JS::Value(1))); + + auto block = make(); + block->append( + make("a"), + make(JS::Value(5))); + block->append( + make("b"), + make(JS::Value(7))); + + block->append( + make( + JS::BinaryOp::Plus, + make( + JS::BinaryOp::Plus, + make("a"), + make("b")), + make("c"))); + program.append("foo", move(block)); + program.append("foo"); +}