mirror of
https://github.com/RGBCube/serenity
synced 2025-05-25 21:25:07 +00:00
LibJS: Allow the choice of a scope of declaration for a variable (#1408)
Previously, we were assuming all declared variables were bound to a block scope, now, with the addition of declaration types, we can bind a variable to a block scope using `let`, or a function scope (the scope of the inner-most enclosing function of a `var` declaration) using `var`.
This commit is contained in:
parent
542108421e
commit
df40c85f80
6 changed files with 85 additions and 16 deletions
|
@ -56,7 +56,7 @@ Value CallExpression::execute(Interpreter& interpreter) const
|
||||||
auto* callee_object = callee.as_object();
|
auto* callee_object = callee.as_object();
|
||||||
ASSERT(callee_object->is_function());
|
ASSERT(callee_object->is_function());
|
||||||
auto& function = static_cast<Function&>(*callee_object);
|
auto& function = static_cast<Function&>(*callee_object);
|
||||||
return interpreter.run(function.body());
|
return interpreter.run(function.body(), ScopeType::Function);
|
||||||
}
|
}
|
||||||
|
|
||||||
Value ReturnStatement::execute(Interpreter& interpreter) const
|
Value ReturnStatement::execute(Interpreter& interpreter) const
|
||||||
|
@ -379,17 +379,30 @@ void AssignmentExpression::dump(int indent) const
|
||||||
|
|
||||||
Value VariableDeclaration::execute(Interpreter& interpreter) const
|
Value VariableDeclaration::execute(Interpreter& interpreter) const
|
||||||
{
|
{
|
||||||
interpreter.declare_variable(name().string());
|
interpreter.declare_variable(name().string(), m_declaration_type);
|
||||||
if (m_initializer) {
|
if (m_initializer) {
|
||||||
auto initalizer_result = m_initializer->execute(interpreter);
|
auto initalizer_result = m_initializer->execute(interpreter);
|
||||||
interpreter.set_variable(name().string(), initalizer_result);
|
interpreter.set_variable(name().string(), initalizer_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return js_undefined();
|
return js_undefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VariableDeclaration::dump(int indent) const
|
void VariableDeclaration::dump(int indent) const
|
||||||
{
|
{
|
||||||
|
const char* op_string = nullptr;
|
||||||
|
switch (m_declaration_type) {
|
||||||
|
case DeclarationType::Let:
|
||||||
|
op_string = "Let";
|
||||||
|
break;
|
||||||
|
case DeclarationType::Var:
|
||||||
|
op_string = "Var";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
ASTNode::dump(indent);
|
ASTNode::dump(indent);
|
||||||
|
print_indent(indent + 1);
|
||||||
|
printf("%s\n", op_string);
|
||||||
m_name->dump(indent + 1);
|
m_name->dump(indent + 1);
|
||||||
if (m_initializer)
|
if (m_initializer)
|
||||||
m_initializer->dump(indent + 1);
|
m_initializer->dump(indent + 1);
|
||||||
|
|
|
@ -332,10 +332,16 @@ private:
|
||||||
NonnullOwnPtr<Expression> m_rhs;
|
NonnullOwnPtr<Expression> m_rhs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class DeclarationType {
|
||||||
|
Var,
|
||||||
|
Let,
|
||||||
|
};
|
||||||
|
|
||||||
class VariableDeclaration : public ASTNode {
|
class VariableDeclaration : public ASTNode {
|
||||||
public:
|
public:
|
||||||
VariableDeclaration(NonnullOwnPtr<Identifier> name, OwnPtr<Expression> initializer)
|
VariableDeclaration(NonnullOwnPtr<Identifier> name, OwnPtr<Expression> initializer, DeclarationType declaration_type)
|
||||||
: m_name(move(name))
|
: m_declaration_type(declaration_type)
|
||||||
|
, m_name(move(name))
|
||||||
, m_initializer(move(initializer))
|
, m_initializer(move(initializer))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -348,6 +354,7 @@ public:
|
||||||
private:
|
private:
|
||||||
virtual const char* class_name() const override { return "VariableDeclaration"; }
|
virtual const char* class_name() const override { return "VariableDeclaration"; }
|
||||||
|
|
||||||
|
DeclarationType m_declaration_type;
|
||||||
NonnullOwnPtr<Identifier> m_name;
|
NonnullOwnPtr<Identifier> m_name;
|
||||||
OwnPtr<Expression> m_initializer;
|
OwnPtr<Expression> m_initializer;
|
||||||
};
|
};
|
||||||
|
|
|
@ -37,5 +37,6 @@ class Object;
|
||||||
class PrimitiveString;
|
class PrimitiveString;
|
||||||
class ScopeNode;
|
class ScopeNode;
|
||||||
class Value;
|
class Value;
|
||||||
|
enum class DeclarationType;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,9 +42,9 @@ Interpreter::~Interpreter()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Value Interpreter::run(const ScopeNode& scope_node)
|
Value Interpreter::run(const ScopeNode& scope_node, ScopeType scope_type)
|
||||||
{
|
{
|
||||||
enter_scope(scope_node);
|
enter_scope(scope_node, scope_type);
|
||||||
|
|
||||||
Value last_value = js_undefined();
|
Value last_value = js_undefined();
|
||||||
for (auto& node : scope_node.children()) {
|
for (auto& node : scope_node.children()) {
|
||||||
|
@ -55,9 +55,9 @@ Value Interpreter::run(const ScopeNode& scope_node)
|
||||||
return last_value;
|
return last_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interpreter::enter_scope(const ScopeNode& scope_node)
|
void Interpreter::enter_scope(const ScopeNode& scope_node, ScopeType scope_type)
|
||||||
{
|
{
|
||||||
m_scope_stack.append({ scope_node, {} });
|
m_scope_stack.append({ scope_type, scope_node, {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interpreter::exit_scope(const ScopeNode& scope_node)
|
void Interpreter::exit_scope(const ScopeNode& scope_node)
|
||||||
|
@ -71,9 +71,24 @@ void Interpreter::do_return()
|
||||||
dbg() << "FIXME: Implement Interpreter::do_return()";
|
dbg() << "FIXME: Implement Interpreter::do_return()";
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interpreter::declare_variable(String name)
|
void Interpreter::declare_variable(String name, DeclarationType declaration_type)
|
||||||
{
|
{
|
||||||
|
switch (declaration_type) {
|
||||||
|
case DeclarationType::Var:
|
||||||
|
for (ssize_t i = m_scope_stack.size() - 1; i >= 0; --i) {
|
||||||
|
auto& scope = m_scope_stack.at(i);
|
||||||
|
if (scope.type == ScopeType::Function) {
|
||||||
|
scope.variables.set(move(name), js_undefined());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global_object().put(move(name), js_undefined());
|
||||||
|
break;
|
||||||
|
case DeclarationType::Let:
|
||||||
m_scope_stack.last().variables.set(move(name), js_undefined());
|
m_scope_stack.last().variables.set(move(name), js_undefined());
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interpreter::set_variable(String name, Value value)
|
void Interpreter::set_variable(String name, Value value)
|
||||||
|
|
|
@ -33,7 +33,13 @@
|
||||||
|
|
||||||
namespace JS {
|
namespace JS {
|
||||||
|
|
||||||
|
enum class ScopeType {
|
||||||
|
Function,
|
||||||
|
Block,
|
||||||
|
};
|
||||||
|
|
||||||
struct ScopeFrame {
|
struct ScopeFrame {
|
||||||
|
ScopeType type;
|
||||||
const ScopeNode& scope_node;
|
const ScopeNode& scope_node;
|
||||||
HashMap<String, Value> variables;
|
HashMap<String, Value> variables;
|
||||||
};
|
};
|
||||||
|
@ -43,7 +49,7 @@ public:
|
||||||
Interpreter();
|
Interpreter();
|
||||||
~Interpreter();
|
~Interpreter();
|
||||||
|
|
||||||
Value run(const ScopeNode&);
|
Value run(const ScopeNode&, ScopeType = ScopeType::Block);
|
||||||
|
|
||||||
Object& global_object() { return *m_global_object; }
|
Object& global_object() { return *m_global_object; }
|
||||||
const Object& global_object() const { return *m_global_object; }
|
const Object& global_object() const { return *m_global_object; }
|
||||||
|
@ -54,12 +60,12 @@ public:
|
||||||
|
|
||||||
Value get_variable(const String& name);
|
Value get_variable(const String& name);
|
||||||
void set_variable(String name, Value);
|
void set_variable(String name, Value);
|
||||||
void declare_variable(String name);
|
void declare_variable(String name, DeclarationType);
|
||||||
|
|
||||||
void collect_roots(Badge<Heap>, HashTable<Cell*>&);
|
void collect_roots(Badge<Heap>, HashTable<Cell*>&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void enter_scope(const ScopeNode&);
|
void enter_scope(const ScopeNode&, ScopeType);
|
||||||
void exit_scope(const ScopeNode&);
|
void exit_scope(const ScopeNode&);
|
||||||
|
|
||||||
Heap m_heap;
|
Heap m_heap;
|
||||||
|
|
|
@ -92,10 +92,12 @@ void build_program(JS::Program& program)
|
||||||
auto block = make<JS::BlockStatement>();
|
auto block = make<JS::BlockStatement>();
|
||||||
block->append<JS::VariableDeclaration>(
|
block->append<JS::VariableDeclaration>(
|
||||||
make<JS::Identifier>("a"),
|
make<JS::Identifier>("a"),
|
||||||
make<JS::Literal>(JS::Value(5)));
|
make<JS::Literal>(JS::Value(5)),
|
||||||
|
JS::DeclarationType::Var);
|
||||||
block->append<JS::VariableDeclaration>(
|
block->append<JS::VariableDeclaration>(
|
||||||
make<JS::Identifier>("b"),
|
make<JS::Identifier>("b"),
|
||||||
make<JS::Literal>(JS::Value(7)));
|
make<JS::Literal>(JS::Value(7)),
|
||||||
|
JS::DeclarationType::Var);
|
||||||
|
|
||||||
block->append<JS::ReturnStatement>(
|
block->append<JS::ReturnStatement>(
|
||||||
make<JS::BinaryExpression>(
|
make<JS::BinaryExpression>(
|
||||||
|
@ -120,13 +122,38 @@ void build_program(JS::Program& program)
|
||||||
auto block = make<JS::BlockStatement>();
|
auto block = make<JS::BlockStatement>();
|
||||||
block->append<JS::VariableDeclaration>(
|
block->append<JS::VariableDeclaration>(
|
||||||
make<JS::Identifier>("x"),
|
make<JS::Identifier>("x"),
|
||||||
make<JS::ObjectExpression>());
|
make<JS::ObjectExpression>(),
|
||||||
|
JS::DeclarationType::Var);
|
||||||
block->append<JS::CallExpression>("$gc");
|
block->append<JS::CallExpression>("$gc");
|
||||||
|
|
||||||
program.append<JS::FunctionDeclaration>("foo", move(block));
|
program.append<JS::FunctionDeclaration>("foo", move(block));
|
||||||
program.append<JS::CallExpression>("foo");
|
program.append<JS::CallExpression>("foo");
|
||||||
}
|
}
|
||||||
#elif PROGRAM == 4
|
#elif PROGRAM == 4
|
||||||
|
void build_program(JS::Program& program)
|
||||||
|
{
|
||||||
|
// function foo() {
|
||||||
|
// function bar() {
|
||||||
|
// var y = 6;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// bar()
|
||||||
|
// return y;
|
||||||
|
// }
|
||||||
|
// foo(); //I should return `undefined` because y is bound to the inner-most enclosing function, i.e the nested one (bar()), therefore, it's undefined in the scope of foo()
|
||||||
|
|
||||||
|
auto block_bar = make<JS::BlockStatement>();
|
||||||
|
block_bar->append<JS::VariableDeclaration>(make<JS::Identifier>("y"), make<JS::Literal>(JS::Value(6)), JS::DeclarationType::Var);
|
||||||
|
|
||||||
|
auto block_foo = make<JS::BlockStatement>();
|
||||||
|
block_foo->append<JS::FunctionDeclaration>("bar", move(block_bar));
|
||||||
|
block_foo->append<JS::CallExpression>("bar");
|
||||||
|
block_foo->append<JS::ReturnStatement>(make<JS::Identifier>("y"));
|
||||||
|
|
||||||
|
program.append<JS::FunctionDeclaration>("foo", move(block_foo));
|
||||||
|
program.append<JS::CallExpression>("foo");
|
||||||
|
}
|
||||||
|
#elif PROGRAM == 5
|
||||||
void build_program(JS::Program& program, JS::Heap& heap)
|
void build_program(JS::Program& program, JS::Heap& heap)
|
||||||
{
|
{
|
||||||
// "hello friends".length
|
// "hello friends".length
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue