mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 07:30:19 +00:00
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.
This commit is contained in:
parent
ac3c19b91c
commit
1382dbc5e1
Notes:
sideshowbarker
2024-07-19 08:48:32 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/1382dbc5e19
5 changed files with 224 additions and 19 deletions
|
@ -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<const Identifier&>(*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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<Expression> lhs, NonnullOwnPtr<Expression> rhs)
|
||||
BinaryExpression(BinaryOp op, NonnullOwnPtr<ASTNode> lhs, NonnullOwnPtr<ASTNode> 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<Expression> m_lhs;
|
||||
NonnullOwnPtr<Expression> m_rhs;
|
||||
NonnullOwnPtr<ASTNode> m_lhs;
|
||||
NonnullOwnPtr<ASTNode> 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<ASTNode> lhs, NonnullOwnPtr<ASTNode> 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<ASTNode> m_lhs;
|
||||
NonnullOwnPtr<ASTNode> m_rhs;
|
||||
};
|
||||
|
||||
class VariableDeclaration : public ASTNode {
|
||||
public:
|
||||
VariableDeclaration(NonnullOwnPtr<Identifier> name, OwnPtr<ASTNode> 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<Identifier> m_name;
|
||||
OwnPtr<ASTNode> m_initializer;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/Heap.h>
|
||||
|
@ -34,6 +35,7 @@ namespace JS {
|
|||
|
||||
struct ScopeFrame {
|
||||
const ScopeNode& scope_node;
|
||||
HashMap<String, Value> 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&);
|
||||
|
|
|
@ -31,24 +31,13 @@
|
|||
#include <LibJS/Value.h>
|
||||
#include <stdio.h>
|
||||
|
||||
//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<JS::Program>();
|
||||
|
||||
auto block = make<JS::BlockStatement>();
|
||||
block->append<JS::ReturnStatement>(
|
||||
make<JS::BinaryExpression>(
|
||||
JS::BinaryOp::Plus,
|
||||
make<JS::BinaryExpression>(
|
||||
JS::BinaryOp::Plus,
|
||||
make<JS::Literal>(JS::Value(1)),
|
||||
make<JS::Literal>(JS::Value(2))),
|
||||
make<JS::Literal>(JS::Value(3))));
|
||||
|
||||
program->append<JS::FunctionDeclaration>("foo", move(block));
|
||||
program->append<JS::CallExpression>("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<JS::BlockStatement>();
|
||||
block->append<JS::ReturnStatement>(
|
||||
make<JS::BinaryExpression>(
|
||||
JS::BinaryOp::Plus,
|
||||
make<JS::BinaryExpression>(
|
||||
JS::BinaryOp::Plus,
|
||||
make<JS::Literal>(JS::Value(1)),
|
||||
make<JS::Literal>(JS::Value(2))),
|
||||
make<JS::Literal>(JS::Value(3))));
|
||||
|
||||
program.append<JS::FunctionDeclaration>("foo", move(block));
|
||||
program.append<JS::CallExpression>("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::AssignmentExpression>(
|
||||
JS::AssignmentOp::Assign,
|
||||
make<JS::Identifier>("c"),
|
||||
make<JS::Literal>(JS::Value(1)));
|
||||
|
||||
auto block = make<JS::BlockStatement>();
|
||||
block->append<JS::VariableDeclaration>(
|
||||
make<JS::Identifier>("a"),
|
||||
make<JS::Literal>(JS::Value(5)));
|
||||
block->append<JS::VariableDeclaration>(
|
||||
make<JS::Identifier>("b"),
|
||||
make<JS::Literal>(JS::Value(7)));
|
||||
|
||||
block->append<JS::ReturnStatement>(
|
||||
make<JS::BinaryExpression>(
|
||||
JS::BinaryOp::Plus,
|
||||
make<JS::BinaryExpression>(
|
||||
JS::BinaryOp::Plus,
|
||||
make<JS::Identifier>("a"),
|
||||
make<JS::Identifier>("b")),
|
||||
make<JS::Identifier>("c")));
|
||||
program.append<JS::FunctionDeclaration>("foo", move(block));
|
||||
program.append<JS::CallExpression>("foo");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue