summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Kling <kling@serenityos.org>2020-03-11 18:58:19 +0100
committerAndreas Kling <kling@serenityos.org>2020-03-11 19:00:26 +0100
commit542108421edd65f9fc5605d17331a7b14efab581 (patch)
tree3e64b8562c508dbb30c2cc58b2def0ad1bb0a1b0
parent6ec6fa1a02d1eba0945b37e86fdbf850446eb97b (diff)
downloadserenity-542108421edd65f9fc5605d17331a7b14efab581.zip
LibJS: Support "hello friends".length
The above snippet is a MemberExpression that necessitates the implicit construction of a StringObject wrapper around a PrimitiveString. We then do a property lookup (a "get") on the StringObject, where we find the "length" property. This is pretty neat! :^)
-rw-r--r--Libraries/LibJS/AST.cpp23
-rw-r--r--Libraries/LibJS/AST.h18
-rw-r--r--Libraries/LibJS/Cell.cpp5
-rw-r--r--Libraries/LibJS/Value.cpp19
-rw-r--r--Libraries/LibJS/Value.h26
-rw-r--r--Userland/js.cpp20
6 files changed, 100 insertions, 11 deletions
diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp
index 44325fb7cd..b60824bcd0 100644
--- a/Libraries/LibJS/AST.cpp
+++ b/Libraries/LibJS/AST.cpp
@@ -387,7 +387,6 @@ Value VariableDeclaration::execute(Interpreter& interpreter) const
return js_undefined();
}
-
void VariableDeclaration::dump(int indent) const
{
ASTNode::dump(indent);
@@ -406,4 +405,26 @@ Value ObjectExpression::execute(Interpreter& interpreter) const
return Value(interpreter.heap().allocate<Object>());
}
+void MemberExpression::dump(int indent) const
+{
+ ASTNode::dump(indent);
+ m_object->dump(indent + 1);
+ m_property->dump(indent + 1);
+}
+
+Value MemberExpression::execute(Interpreter& interpreter) const
+{
+ auto object_result = m_object->execute(interpreter).to_object(interpreter.heap());
+ ASSERT(object_result.is_object());
+
+ String property_name;
+ if (m_property->is_identifier()) {
+ property_name = static_cast<const Identifier&>(*m_property).string();
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+
+ return object_result.as_object()->get(property_name);
+}
+
}
diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h
index 77ec8288c7..30b1827254 100644
--- a/Libraries/LibJS/AST.h
+++ b/Libraries/LibJS/AST.h
@@ -363,4 +363,22 @@ private:
virtual const char* class_name() const override { return "ObjectExpression"; }
};
+class MemberExpression final : public Expression {
+public:
+ MemberExpression(NonnullOwnPtr<Expression> object, NonnullOwnPtr<Expression> property)
+ : m_object(move(object))
+ , m_property(move(property))
+ {
+ }
+
+ virtual Value execute(Interpreter&) const override;
+ virtual void dump(int indent) const override;
+
+private:
+ virtual const char* class_name() const override { return "MemberExpression"; }
+
+ NonnullOwnPtr<Expression> m_object;
+ NonnullOwnPtr<Expression> m_property;
+};
+
}
diff --git a/Libraries/LibJS/Cell.cpp b/Libraries/LibJS/Cell.cpp
index 8e0744ea0b..d07f877e16 100644
--- a/Libraries/LibJS/Cell.cpp
+++ b/Libraries/LibJS/Cell.cpp
@@ -27,14 +27,15 @@
#include <AK/LogStream.h>
#include <LibJS/Cell.h>
#include <LibJS/Object.h>
+#include <LibJS/PrimitiveString.h>
#include <LibJS/Value.h>
namespace JS {
void Cell::Visitor::visit(Value value)
{
- if (value.is_object())
- visit(value.as_object());
+ if (value.is_cell())
+ visit(value.as_cell());
}
const LogStream& operator<<(const LogStream& stream, const Cell* cell)
diff --git a/Libraries/LibJS/Value.cpp b/Libraries/LibJS/Value.cpp
index 530fe31d54..d2e0bfa091 100644
--- a/Libraries/LibJS/Value.cpp
+++ b/Libraries/LibJS/Value.cpp
@@ -25,7 +25,10 @@
*/
#include <AK/String.h>
+#include <LibJS/Heap.h>
#include <LibJS/Object.h>
+#include <LibJS/PrimitiveString.h>
+#include <LibJS/StringObject.h>
#include <LibJS/Value.h>
namespace JS {
@@ -50,6 +53,9 @@ String Value::to_string() const
return String::format("{%s}", as_object()->class_name());
}
+ if (is_string())
+ return m_value.as_string->string();
+
ASSERT_NOT_REACHED();
}
@@ -64,7 +70,7 @@ bool Value::to_boolean() const
case Type::Undefined:
return false;
case Type::String:
- return String(as_string()).is_empty();
+ return !as_string()->string().is_empty();
case Type::Object:
return true;
default:
@@ -72,6 +78,17 @@ bool Value::to_boolean() const
}
}
+Value Value::to_object(Heap& heap) const
+{
+ if (is_object())
+ return Value(as_object());
+
+ if (is_string())
+ return Value(heap.allocate<StringObject>(m_value.as_string));
+
+ ASSERT_NOT_REACHED();
+}
+
Value greater_than(Value lhs, Value rhs)
{
ASSERT(lhs.is_number());
diff --git a/Libraries/LibJS/Value.h b/Libraries/LibJS/Value.h
index 5de0bf8af3..7b232c2a93 100644
--- a/Libraries/LibJS/Value.h
+++ b/Libraries/LibJS/Value.h
@@ -50,6 +50,7 @@ public:
bool is_string() const { return m_type == Type::String; }
bool is_object() const { return m_type == Type::Object; }
bool is_boolean() const { return m_type == Type::Boolean; }
+ bool is_cell() const { return is_string() || is_object(); }
explicit Value(bool value)
: m_type(Type::Boolean)
@@ -75,6 +76,12 @@ public:
m_value.as_object = object;
}
+ explicit Value(PrimitiveString* string)
+ : m_type(Type::String)
+ {
+ m_value.as_string = string;
+ }
+
explicit Value(Type type)
: m_type(type)
{
@@ -106,23 +113,38 @@ public:
return m_value.as_object;
}
- const StringImpl* as_string() const
+ PrimitiveString* as_string()
+ {
+ ASSERT(is_string());
+ return m_value.as_string;
+ }
+
+ const PrimitiveString* as_string() const
{
ASSERT(is_string());
return m_value.as_string;
}
+ Cell* as_cell()
+ {
+ ASSERT(is_cell());
+ return m_value.as_cell;
+ }
+
String to_string() const;
bool to_boolean() const;
+ Value to_object(Heap&) const;
+
private:
Type m_type { Type::Undefined };
union {
bool as_bool;
double as_double;
- StringImpl* as_string;
+ PrimitiveString* as_string;
Object* as_object;
+ Cell* as_cell;
} m_value;
};
diff --git a/Userland/js.cpp b/Userland/js.cpp
index 3017ae30b2..50648637d1 100644
--- a/Userland/js.cpp
+++ b/Userland/js.cpp
@@ -28,21 +28,22 @@
#include <LibJS/AST.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Object.h>
+#include <LibJS/PrimitiveString.h>
#include <LibJS/Value.h>
#include <stdio.h>
-#define PROGRAM 2
+#define PROGRAM 4
-static void build_program(JS::Program&);
+static void build_program(JS::Program&, JS::Heap&);
int main()
{
- auto program = make<JS::Program>();
- build_program(*program);
+ JS::Interpreter interpreter;
+ auto program = make<JS::Program>();
+ build_program(*program, interpreter.heap());
program->dump(0);
- JS::Interpreter interpreter;
auto result = interpreter.run(*program);
dbg() << "Interpreter returned " << result;
@@ -125,4 +126,13 @@ void build_program(JS::Program& program)
program.append<JS::FunctionDeclaration>("foo", move(block));
program.append<JS::CallExpression>("foo");
}
+#elif PROGRAM == 4
+void build_program(JS::Program& program, JS::Heap& heap)
+{
+ // "hello friends".length
+
+ program.append<JS::MemberExpression>(
+ make<JS::Literal>(JS::Value(js_string(heap, "hello friends"))),
+ make<JS::Identifier>("length"));
+}
#endif