summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Kling <kling@serenityos.org>2020-04-28 14:44:48 +0200
committerAndreas Kling <kling@serenityos.org>2020-04-28 15:07:08 +0200
commit24cce3674beb4c0ab094601cc445da8ceca4791b (patch)
tree686ebdbde1971ffa90158c8537c08b790d9f626d
parentee0bf55127dc2add3b271d11c22395f7e29b1860 (diff)
downloadserenity-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.cpp16
-rw-r--r--Libraries/LibJS/Interpreter.cpp2
-rw-r--r--Libraries/LibJS/Runtime/Reference.cpp61
-rw-r--r--Libraries/LibJS/Runtime/Reference.h20
-rw-r--r--Libraries/LibJS/Tests/update-expression-on-member-expression.js14
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);
+}