Преглед изворни кода

LibJS: Implement const variable declarations

This also tightens the means of redeclaration of a variable by proxy,
since we now have a way of knowing how a variable was initially
declared, we can check if it was declared using `let` or `const` and
not tolerate redeclaration like we did previously.
0xtechnobabble пре 5 година
родитељ
комит
ee5a49e2fe

+ 8 - 4
Libraries/LibJS/AST.cpp

@@ -456,6 +456,7 @@ Value UpdateExpression::execute(Interpreter& interpreter) const
         break;
     case UpdateOp::Decrement:
         interpreter.set_variable(name, Value(previous_value.as_double() - 1));
+        break;
     }
 
     return previous_value;
@@ -520,19 +521,22 @@ Value VariableDeclaration::execute(Interpreter& interpreter) const
 
 void VariableDeclaration::dump(int indent) const
 {
-    const char* op_string = nullptr;
+    const char* declaration_type_string = nullptr;
     switch (m_declaration_type) {
     case DeclarationType::Let:
-        op_string = "Let";
+        declaration_type_string = "Let";
         break;
     case DeclarationType::Var:
-        op_string = "Var";
+        declaration_type_string = "Var";
+        break;
+    case DeclarationType::Const:
+        declaration_type_string = "Const";
         break;
     }
 
     ASTNode::dump(indent);
     print_indent(indent + 1);
-    printf("%s\n", op_string);
+    printf("%s\n", declaration_type_string);
     m_name->dump(indent + 1);
     if (m_initializer)
         m_initializer->dump(indent + 1);

+ 1 - 0
Libraries/LibJS/AST.h

@@ -470,6 +470,7 @@ private:
 enum class DeclarationType {
     Var,
     Let,
+    Const,
 };
 
 class VariableDeclaration : public Statement {

+ 25 - 8
Libraries/LibJS/Interpreter.cpp

@@ -57,7 +57,12 @@ Value Interpreter::run(const ScopeNode& scope_node, HashMap<String, Value> scope
 
 void Interpreter::enter_scope(const ScopeNode& scope_node, HashMap<String, Value> scope_variables, ScopeType scope_type)
 {
-    m_scope_stack.append({ scope_type, scope_node, move(scope_variables) });
+    HashMap<String, Variable> scope_variables_with_declaration_type;
+    for (String name : scope_variables.keys()) {
+        scope_variables_with_declaration_type.set(name, { scope_variables.get(name).value(), DeclarationType::Var });
+    }
+
+    m_scope_stack.append({ scope_type, scope_node, move(scope_variables_with_declaration_type) });
 }
 
 void Interpreter::exit_scope(const ScopeNode& scope_node)
@@ -78,7 +83,10 @@ void Interpreter::declare_variable(String name, DeclarationType declaration_type
         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());
+                if (scope.variables.get(name).has_value() && scope.variables.get(name).value().declaration_type != DeclarationType::Var)
+                    ASSERT_NOT_REACHED();
+
+                scope.variables.set(move(name), { js_undefined(), declaration_type });
                 return;
             }
         }
@@ -86,7 +94,11 @@ void Interpreter::declare_variable(String name, DeclarationType declaration_type
         global_object().put(move(name), js_undefined());
         break;
     case DeclarationType::Let:
-        m_scope_stack.last().variables.set(move(name), js_undefined());
+    case DeclarationType::Const:
+        if (m_scope_stack.last().variables.get(name).has_value() && m_scope_stack.last().variables.get(name).value().declaration_type != DeclarationType::Var)
+            ASSERT_NOT_REACHED();
+
+        m_scope_stack.last().variables.set(move(name), { js_undefined(), declaration_type });
         break;
     }
 }
@@ -95,8 +107,13 @@ 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));
+
+        auto possible_match = scope.variables.get(name);
+        if (possible_match.has_value()) {
+            if (possible_match.value().declaration_type == DeclarationType::Const)
+                ASSERT_NOT_REACHED();
+
+            scope.variables.set(move(name), { move(value), possible_match.value().declaration_type });
             return;
         }
     }
@@ -110,7 +127,7 @@ Value Interpreter::get_variable(const String& name)
         auto& scope = m_scope_stack.at(i);
         auto value = scope.variables.get(name);
         if (value.has_value())
-            return value.value();
+            return value.value().value;
     }
 
     return global_object().get(name);
@@ -122,8 +139,8 @@ void Interpreter::collect_roots(Badge<Heap>, HashTable<Cell*>& roots)
 
     for (auto& scope : m_scope_stack) {
         for (auto& it : scope.variables) {
-            if (it.value.is_cell())
-                roots.set(it.value.as_cell());
+            if (it.value.value.is_cell())
+                roots.set(it.value.value.as_cell());
         }
     }
 }

+ 7 - 1
Libraries/LibJS/Interpreter.h

@@ -30,6 +30,7 @@
 #include <AK/Vector.h>
 #include <LibJS/Forward.h>
 #include <LibJS/Heap.h>
+#include <LibJS/Value.h>
 
 namespace JS {
 
@@ -38,10 +39,15 @@ enum class ScopeType {
     Block,
 };
 
+struct Variable {
+    Value value;
+    DeclarationType declaration_type;
+};
+
 struct ScopeFrame {
     ScopeType type;
     const ScopeNode& scope_node;
-    HashMap<String, Value> variables;
+    HashMap<String, Variable> variables;
 };
 
 class Interpreter {

+ 5 - 0
Libraries/LibJS/Parser.cpp

@@ -66,6 +66,7 @@ NonnullOwnPtr<Statement> Parser::parse_statement()
         return parse_return_statement();
     case TokenType::Var:
     case TokenType::Let:
+    case TokenType::Const:
         return parse_variable_declaration();
     case TokenType::For:
         return parse_for_statement();
@@ -261,6 +262,10 @@ NonnullOwnPtr<VariableDeclaration> Parser::parse_variable_declaration()
         declaration_type = DeclarationType::Let;
         consume(TokenType::Let);
         break;
+    case TokenType::Const:
+        declaration_type = DeclarationType::Const;
+        consume(TokenType::Const);
+        break;
     default:
         ASSERT_NOT_REACHED();
     }