diff options
author | Andreas Kling <kling@serenityos.org> | 2020-04-28 14:44:48 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-04-28 15:07:08 +0200 |
commit | 24cce3674beb4c0ab094601cc445da8ceca4791b (patch) | |
tree | 686ebdbde1971ffa90158c8537c08b790d9f626d | |
parent | ee0bf55127dc2add3b271d11c22395f7e29b1860 (diff) | |
download | serenity-24cce3674beb4c0ab094601cc445da8ceca4791b.zip |
LibJS: Support o.f++ :^)
This patch teaches UpdateExpression how to use a Reference. Some other
changes were necessary to keep tests working:
A Reference can now also refer to a local or global variable. This is
not fully aligned with the spec since we don't have a Record concept.
-rw-r--r-- | Libraries/LibJS/AST.cpp | 16 | ||||
-rw-r--r-- | Libraries/LibJS/Interpreter.cpp | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Reference.cpp | 61 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Reference.h | 20 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/update-expression-on-member-expression.js | 14 |
5 files changed, 101 insertions, 12 deletions
diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp index d5399266dc..4095262ebe 100644 --- a/Libraries/LibJS/AST.cpp +++ b/Libraries/LibJS/AST.cpp @@ -411,6 +411,8 @@ Value UnaryExpression::execute(Interpreter& interpreter) const return Value(true); // FIXME: Support deleting locals ASSERT(!reference.is_local_variable()); + if (reference.is_global_variable()) + return interpreter.global_object().delete_property(reference.name()); auto* base_object = reference.base().to_object(interpreter.heap()); if (!base_object) return {}; @@ -844,7 +846,7 @@ Value AssignmentExpression::execute(Interpreter& interpreter) const if (reference.is_unresolvable()) return interpreter.throw_exception<ReferenceError>("Invalid left-hand side in assignment"); - reference.assign(interpreter, rhs_result); + reference.put(interpreter, rhs_result); if (interpreter.exception()) return {}; @@ -853,9 +855,11 @@ Value AssignmentExpression::execute(Interpreter& interpreter) const Value UpdateExpression::execute(Interpreter& interpreter) const { - ASSERT(m_argument->is_identifier()); - auto name = static_cast<const Identifier&>(*m_argument).string(); - auto old_value = m_argument->execute(interpreter); + auto reference = m_argument->to_reference(interpreter); + if (interpreter.exception()) + return {}; + + auto old_value = reference.get(interpreter); if (interpreter.exception()) return {}; old_value = old_value.to_number(); @@ -873,7 +877,9 @@ Value UpdateExpression::execute(Interpreter& interpreter) const } auto new_value = Value(old_value.as_double() + op_result); - interpreter.set_variable(name, new_value); + reference.put(interpreter, new_value); + if (interpreter.exception()) + return {}; return m_prefixed ? new_value : old_value; } diff --git a/Libraries/LibJS/Interpreter.cpp b/Libraries/LibJS/Interpreter.cpp index 57e3b78176..2392f3950b 100644 --- a/Libraries/LibJS/Interpreter.cpp +++ b/Libraries/LibJS/Interpreter.cpp @@ -173,7 +173,7 @@ Reference Interpreter::get_reference(const FlyString& name) return { Reference::LocalVariable, name }; } } - return { &global_object(), PropertyName(name) }; + return { Reference::GlobalVariable, name }; } void Interpreter::gather_roots(Badge<Heap>, HashTable<Cell*>& roots) diff --git a/Libraries/LibJS/Runtime/Reference.cpp b/Libraries/LibJS/Runtime/Reference.cpp index 13402cdc9d..945e671263 100644 --- a/Libraries/LibJS/Runtime/Reference.cpp +++ b/Libraries/LibJS/Runtime/Reference.cpp @@ -24,20 +24,29 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include <AK/StringBuilder.h> #include <LibJS/Interpreter.h> -#include <LibJS/Runtime/Reference.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/Object.h> +#include <LibJS/Runtime/Reference.h> namespace JS { -void Reference::assign(Interpreter& interpreter, Value value) +void Reference::put(Interpreter& interpreter, Value value) { // NOTE: The caller is responsible for doing an exception check after assign(). - ASSERT(!is_unresolvable()); + if (is_unresolvable()) { + throw_reference_error(interpreter); + return; + } - if (is_local_variable()) { - interpreter.set_variable(m_name.to_string(), value); + if (is_local_variable() || is_global_variable()) { + if (is_local_variable()) + interpreter.set_variable(m_name.to_string(), value); + else + interpreter.global_object().put(m_name, value); return; } @@ -48,4 +57,46 @@ void Reference::assign(Interpreter& interpreter, Value value) object->put(m_name, value); } +void Reference::throw_reference_error(Interpreter& interpreter) +{ + auto property_name = m_name.to_string(); + String message; + if (property_name.is_empty()) + message = "Unresolvable reference"; + else + message = String::format("'%s' not known", property_name.characters()); + interpreter.throw_exception<ReferenceError>(message); +} + +Value Reference::get(Interpreter& interpreter) +{ + // NOTE: The caller is responsible for doing an exception check after fetch(). + + if (is_unresolvable()) { + throw_reference_error(interpreter); + return {}; + } + + if (is_local_variable() || is_global_variable()) { + Value value; + if (is_local_variable()) + value = interpreter.get_variable(m_name.to_string()); + else + value = interpreter.global_object().get(m_name); + if (interpreter.exception()) + return {}; + if (value.is_empty()) { + throw_reference_error(interpreter); + return {}; + } + return value; + } + + auto* object = base().to_object(interpreter.heap()); + if (!object) + return {}; + + return object->get(m_name).value_or(js_undefined()); +} + } diff --git a/Libraries/LibJS/Runtime/Reference.h b/Libraries/LibJS/Runtime/Reference.h index 81b1701496..870a4e8f8f 100644 --- a/Libraries/LibJS/Runtime/Reference.h +++ b/Libraries/LibJS/Runtime/Reference.h @@ -51,6 +51,15 @@ public: { } + enum GlobalVariableTag { GlobalVariable }; + Reference(GlobalVariableTag, const String& name, bool strict = false) + : m_base(js_null()) + , m_name(name) + , m_strict(strict) + , m_global_variable(true) + { + } + Value base() const { return m_base; } const PropertyName& name() const { return m_name; } bool is_strict() const { return m_strict; } @@ -71,13 +80,22 @@ public: return m_local_variable; } - void assign(Interpreter&, Value); + bool is_global_variable() const + { + return m_global_variable; + } + + void put(Interpreter&, Value); + Value get(Interpreter&); private: + void throw_reference_error(Interpreter&); + Value m_base { js_undefined() }; PropertyName m_name; bool m_strict { false }; bool m_local_variable { false }; + bool m_global_variable { false }; }; const LogStream& operator<<(const LogStream&, const Value&); diff --git a/Libraries/LibJS/Tests/update-expression-on-member-expression.js b/Libraries/LibJS/Tests/update-expression-on-member-expression.js new file mode 100644 index 0000000000..c3e0a75337 --- /dev/null +++ b/Libraries/LibJS/Tests/update-expression-on-member-expression.js @@ -0,0 +1,14 @@ +load("test-common.js"); + +try { + var o = {}; + o.f = 1; + + assert(o.f++ === 1); + assert(++o.f === 3); + assert(isNaN(++o.missing)); + + console.log("PASS"); +} catch (e) { + console.log("FAIL: " + e); +} |