From 4d22a142f7cd3a45debee1cfd0a38de030ef54c5 Mon Sep 17 00:00:00 2001 From: 0xtechnobabble <0xtechnobabble@protonmail.com> Date: Mon, 16 Mar 2020 00:19:41 +0200 Subject: [PATCH] LibJS: Loosen type system This commits makes effort towards tolerating some of javascript's quirks when it comes to its type system, note that the interpreter's way of handling type coercion is still not mature at all, for example, we still have to implement NaN instead of just crashing when trying to parse a string and failing. --- Libraries/LibJS/Cell.cpp | 2 +- Libraries/LibJS/Cell.h | 2 +- Libraries/LibJS/HeapBlock.h | 2 +- Libraries/LibJS/Object.cpp | 30 +++++++++ Libraries/LibJS/Object.h | 13 +++- Libraries/LibJS/ObjectPrototype.cpp | 11 ++++ Libraries/LibJS/StringObject.h | 5 +- Libraries/LibJS/Value.cpp | 98 ++++++++++++++--------------- Libraries/LibJS/Value.h | 1 + 9 files changed, 109 insertions(+), 55 deletions(-) diff --git a/Libraries/LibJS/Cell.cpp b/Libraries/LibJS/Cell.cpp index 540e61a8b8..7a84ef64b5 100644 --- a/Libraries/LibJS/Cell.cpp +++ b/Libraries/LibJS/Cell.cpp @@ -40,7 +40,7 @@ void Cell::Visitor::visit(Value value) visit(value.as_cell()); } -Heap& Cell::heap() +Heap& Cell::heap() const { return HeapBlock::from_cell(this)->heap(); } diff --git a/Libraries/LibJS/Cell.h b/Libraries/LibJS/Cell.h index c4ff075e65..f58ab904f1 100644 --- a/Libraries/LibJS/Cell.h +++ b/Libraries/LibJS/Cell.h @@ -51,7 +51,7 @@ public: virtual void visit_children(Visitor&) {} - Heap& heap(); + Heap& heap() const; Interpreter& interpreter(); private: diff --git a/Libraries/LibJS/HeapBlock.h b/Libraries/LibJS/HeapBlock.h index fbc4ed67ff..2b274a8201 100644 --- a/Libraries/LibJS/HeapBlock.h +++ b/Libraries/LibJS/HeapBlock.h @@ -56,7 +56,7 @@ public: Heap& heap() { return m_heap; } - static HeapBlock* from_cell(Cell* cell) + static HeapBlock* from_cell(const Cell* cell) { return reinterpret_cast((FlatPtr)cell & ~(block_size - 1)); } diff --git a/Libraries/LibJS/Object.cpp b/Libraries/LibJS/Object.cpp index 1b8bdf4ba9..0ea9062548 100644 --- a/Libraries/LibJS/Object.cpp +++ b/Libraries/LibJS/Object.cpp @@ -87,4 +87,34 @@ bool Object::has_own_property(const String& property_name) const return m_properties.get(property_name).has_value(); } +Value Object::to_primitive(PreferredType preferred_type) const +{ + Value result = js_undefined(); + + switch (preferred_type) { + case PreferredType::Default: + case PreferredType::Number: { + result = value_of(); + if (result.is_object()) { + result = to_string(); + } + break; + } + case PreferredType::String: { + result = to_string(); + if (result.is_object()) + result = value_of(); + break; + } + } + + ASSERT(!result.is_object()); + return result; +} + +Value Object::to_string() const +{ + return js_string(heap(), String::format("[object %s]", class_name())); +} + } diff --git a/Libraries/LibJS/Object.h b/Libraries/LibJS/Object.h index 63868d0b84..eea3ee16f9 100644 --- a/Libraries/LibJS/Object.h +++ b/Libraries/LibJS/Object.h @@ -27,9 +27,11 @@ #pragma once #include +#include #include #include -#include +#include +#include namespace JS { @@ -57,6 +59,15 @@ public: void set_prototype(Object* prototype) { m_prototype = prototype; } bool has_own_property(const String& property_name) const; + enum class PreferredType { + Default, + String, + Number, + }; + + virtual Value value_of() const { return Value(const_cast(this)); } + virtual Value to_primitive(PreferredType preferred_type = PreferredType::Default) const; + virtual Value to_string() const; private: HashMap m_properties; diff --git a/Libraries/LibJS/ObjectPrototype.cpp b/Libraries/LibJS/ObjectPrototype.cpp index 0644bb0353..608976f52e 100644 --- a/Libraries/LibJS/ObjectPrototype.cpp +++ b/Libraries/LibJS/ObjectPrototype.cpp @@ -43,6 +43,17 @@ ObjectPrototype::ObjectPrototype() return js_undefined(); return Value(this_object->has_own_property(arguments[0].to_string())); }); + + put_native_function("toString", [](Object* this_object, Vector) -> Value { + ASSERT(this_object); + + return Value(this_object->to_string()); + }); + + put_native_function("valueOf", [](Object* this_object, Vector) -> Value { + ASSERT(this_object); + return this_object->value_of(); + }); } ObjectPrototype::~ObjectPrototype() diff --git a/Libraries/LibJS/StringObject.h b/Libraries/LibJS/StringObject.h index cfeddfb57a..5bfb380226 100644 --- a/Libraries/LibJS/StringObject.h +++ b/Libraries/LibJS/StringObject.h @@ -36,8 +36,11 @@ public: virtual ~StringObject() override; virtual void visit_children(Visitor&) override; - const PrimitiveString* primitive_string() const { return m_string; } + virtual Value value_of() const override + { + return Value(m_string); + } private: virtual const char* class_name() const override { return "StringObject"; } diff --git a/Libraries/LibJS/Value.cpp b/Libraries/LibJS/Value.cpp index 6ba182f6f5..b9ba4ede1f 100644 --- a/Libraries/LibJS/Value.cpp +++ b/Libraries/LibJS/Value.cpp @@ -44,14 +44,12 @@ String Value::to_string() const if (is_undefined()) return "undefined"; - if (is_number()) { + if (is_number()) // FIXME: This needs improvement. return String::number((i32)as_double()); - } - if (is_object()) { - return String::format("{%s}", as_object()->class_name()); - } + if (is_object()) + return as_object()->to_primitive(Object::PreferredType::String).to_string(); if (is_string()) return m_value.as_string->string(); @@ -89,113 +87,113 @@ Value Value::to_object(Heap& heap) const ASSERT_NOT_REACHED(); } -i32 Value::to_i32() const +Value Value::to_number() const { switch (m_type) { case Type::Boolean: - return m_value.as_bool; + return Value(m_value.as_bool ? 1 : 0); case Type::Number: - return static_cast(m_value.as_double); - default: + return Value(m_value.as_double); + case Type::Null: + return Value(0); + case Type::String: { + bool ok; + //FIXME: Parse in a better way + auto parsed_int = as_string()->string().to_int(ok); + if (ok) + return Value(parsed_int); + + //FIXME: Implement 'NaN' ASSERT_NOT_REACHED(); + + break; } + case Type::Undefined: + //FIXME: Implement 'NaN' + ASSERT_NOT_REACHED(); + case Type::Object: + return m_value.as_object->to_primitive(Object::PreferredType::Number).to_number(); + } + + ASSERT_NOT_REACHED(); +} + +i32 Value::to_i32() const +{ + return static_cast(to_number().as_double()); } Value greater_than(Value lhs, Value rhs) { - ASSERT(lhs.is_number()); - ASSERT(rhs.is_number()); - return Value(lhs.as_double() > rhs.as_double()); + return Value(lhs.to_number().as_double() > rhs.to_number().as_double()); } Value greater_than_equals(Value lhs, Value rhs) { - ASSERT(lhs.is_number()); - ASSERT(rhs.is_number()); - return Value(lhs.as_double() >= rhs.as_double()); + return Value(lhs.to_number().as_double() >= rhs.to_number().as_double()); } Value less_than(Value lhs, Value rhs) { - ASSERT(lhs.is_number()); - ASSERT(rhs.is_number()); - return Value(lhs.as_double() < rhs.as_double()); + return Value(lhs.to_number().as_double() < rhs.to_number().as_double()); } Value less_than_equals(Value lhs, Value rhs) { - ASSERT(lhs.is_number()); - ASSERT(rhs.is_number()); - return Value(lhs.as_double() <= rhs.as_double()); + return Value(lhs.to_number().as_double() <= rhs.to_number().as_double()); } Value bitwise_and(Value lhs, Value rhs) { - ASSERT(lhs.is_number()); - ASSERT(rhs.is_number()); - return Value((i32)lhs.as_double() & (i32)rhs.as_double()); + return Value((i32)lhs.to_number().as_double() & (i32)rhs.to_number().as_double()); } Value bitwise_or(Value lhs, Value rhs) { - ASSERT(lhs.is_number()); - ASSERT(rhs.is_number()); - return Value((i32)lhs.as_double() | (i32)rhs.as_double()); + return Value((i32)lhs.to_number().as_double() | (i32)rhs.to_number().as_double()); } Value bitwise_xor(Value lhs, Value rhs) { - ASSERT(lhs.is_number()); - ASSERT(rhs.is_number()); - return Value((i32)lhs.as_double() ^ (i32)rhs.as_double()); + return Value((i32)lhs.to_number().as_double() ^ (i32)rhs.to_number().as_double()); } Value bitwise_not(Value lhs) { - ASSERT(lhs.is_number()); - return Value(~(i32)lhs.as_double()); + return Value(~(i32)lhs.to_number().as_double()); } Value left_shift(Value lhs, Value rhs) { - ASSERT(lhs.is_number()); - ASSERT(rhs.is_number()); - return Value((i32)lhs.as_double() << (i32)rhs.as_double()); + return Value((i32)lhs.to_number().as_double() << (i32)rhs.to_number().as_double()); } Value right_shift(Value lhs, Value rhs) { - ASSERT(lhs.is_number()); - ASSERT(rhs.is_number()); - return Value((i32)lhs.as_double() >> (i32)rhs.as_double()); + return Value((i32)lhs.to_number().as_double() >> (i32)rhs.to_number().as_double()); } Value add(Value lhs, Value rhs) { - ASSERT(lhs.is_number()); - ASSERT(rhs.is_number()); - return Value(lhs.as_double() + rhs.as_double()); + if (lhs.is_string() || rhs.is_string()) + return js_string((lhs.is_string() ? lhs : rhs).as_string()->heap(), String::format("%s%s", lhs.to_string().characters(), rhs.to_string().characters())); + + return Value(lhs.to_number().as_double() + rhs.to_number().as_double()); } Value sub(Value lhs, Value rhs) { - ASSERT(lhs.is_number()); - ASSERT(rhs.is_number()); - return Value(lhs.as_double() - rhs.as_double()); + return Value(lhs.to_number().as_double() - rhs.to_number().as_double()); } Value mul(Value lhs, Value rhs) { - ASSERT(lhs.is_number()); - ASSERT(rhs.is_number()); - return Value(lhs.as_double() * rhs.as_double()); + return Value(lhs.to_number().as_double() * rhs.to_number().as_double()); } Value div(Value lhs, Value rhs) { - ASSERT(lhs.is_number()); - ASSERT(rhs.is_number()); - return Value(lhs.as_double() / rhs.as_double()); + return Value(lhs.to_number().as_double() / rhs.to_number().as_double()); } Value typed_eq(Value lhs, Value rhs) diff --git a/Libraries/LibJS/Value.h b/Libraries/LibJS/Value.h index d98522ced3..3f06b73e66 100644 --- a/Libraries/LibJS/Value.h +++ b/Libraries/LibJS/Value.h @@ -133,6 +133,7 @@ public: String to_string() const; bool to_boolean() const; + Value to_number() const; i32 to_i32() const; Value to_object(Heap&) const;