From 4ced12670453df331632c980e7479159ef5ddf83 Mon Sep 17 00:00:00 2001 From: mattco98 Date: Wed, 29 Apr 2020 23:25:21 -0700 Subject: [PATCH] LibJS: Add symbol objects This commit adds the following classes: SymbolObject, SymbolConstructor, SymbolPrototype, and Symbol. This commit does not introduce any new functionality to the Object class, so they cannot be used as property keys in objects. --- Libraries/LibJS/AST.cpp | 2 + Libraries/LibJS/CMakeLists.txt | 4 + Libraries/LibJS/Forward.h | 4 +- Libraries/LibJS/Interpreter.cpp | 3 + Libraries/LibJS/Runtime/GlobalObject.cpp | 3 + Libraries/LibJS/Runtime/Object.h | 1 + Libraries/LibJS/Runtime/Symbol.cpp | 53 ++++++++ Libraries/LibJS/Runtime/Symbol.h | 53 ++++++++ Libraries/LibJS/Runtime/SymbolConstructor.cpp | 105 +++++++++++++++ Libraries/LibJS/Runtime/SymbolConstructor.h | 49 +++++++ Libraries/LibJS/Runtime/SymbolObject.cpp | 122 ++++++++++++++++++ Libraries/LibJS/Runtime/SymbolObject.h | 95 ++++++++++++++ Libraries/LibJS/Runtime/SymbolPrototype.cpp | 98 ++++++++++++++ Libraries/LibJS/Runtime/SymbolPrototype.h | 48 +++++++ Libraries/LibJS/Runtime/Value.cpp | 23 +++- Libraries/LibJS/Runtime/Value.h | 22 ++++ Libraries/LibJS/Tests/Symbol.for.js | 30 +++++ Libraries/LibJS/Tests/Symbol.js | 26 ++++ Libraries/LibJS/Tests/Symbol.keyFor.js | 31 +++++ .../LibJS/Tests/Symbol.prototype.toString.js | 28 ++++ .../LibJS/Tests/Symbol.prototype.valueOf.js | 22 ++++ 21 files changed, 819 insertions(+), 3 deletions(-) create mode 100644 Libraries/LibJS/Runtime/Symbol.cpp create mode 100644 Libraries/LibJS/Runtime/Symbol.h create mode 100644 Libraries/LibJS/Runtime/SymbolConstructor.cpp create mode 100644 Libraries/LibJS/Runtime/SymbolConstructor.h create mode 100644 Libraries/LibJS/Runtime/SymbolObject.cpp create mode 100644 Libraries/LibJS/Runtime/SymbolObject.h create mode 100644 Libraries/LibJS/Runtime/SymbolPrototype.cpp create mode 100644 Libraries/LibJS/Runtime/SymbolPrototype.h create mode 100644 Libraries/LibJS/Tests/Symbol.for.js create mode 100644 Libraries/LibJS/Tests/Symbol.js create mode 100644 Libraries/LibJS/Tests/Symbol.keyFor.js create mode 100644 Libraries/LibJS/Tests/Symbol.prototype.toString.js create mode 100644 Libraries/LibJS/Tests/Symbol.prototype.valueOf.js diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp index 1362a450b5..35175f38ae 100644 --- a/Libraries/LibJS/AST.cpp +++ b/Libraries/LibJS/AST.cpp @@ -491,6 +491,8 @@ Value UnaryExpression::execute(Interpreter& interpreter) const return js_string(interpreter, "object"); case Value::Type::Boolean: return js_string(interpreter, "boolean"); + case Value::Type::Symbol: + return js_string(interpreter, "symbol"); default: ASSERT_NOT_REACHED(); } diff --git a/Libraries/LibJS/CMakeLists.txt b/Libraries/LibJS/CMakeLists.txt index 11c4608282..d9928f9188 100644 --- a/Libraries/LibJS/CMakeLists.txt +++ b/Libraries/LibJS/CMakeLists.txt @@ -46,6 +46,10 @@ set(SOURCES Runtime/StringConstructor.cpp Runtime/StringObject.cpp Runtime/StringPrototype.cpp + Runtime/Symbol.cpp + Runtime/SymbolConstructor.cpp + Runtime/SymbolObject.cpp + Runtime/SymbolPrototype.cpp Runtime/Uint8ClampedArray.cpp Runtime/Value.cpp Token.cpp diff --git a/Libraries/LibJS/Forward.h b/Libraries/LibJS/Forward.h index 7c75f169d7..1c7623bc84 100644 --- a/Libraries/LibJS/Forward.h +++ b/Libraries/LibJS/Forward.h @@ -34,7 +34,8 @@ __JS_ENUMERATE(Function, function, FunctionPrototype, FunctionConstructor) \ __JS_ENUMERATE(NumberObject, number, NumberPrototype, NumberConstructor) \ __JS_ENUMERATE(Object, object, ObjectPrototype, ObjectConstructor) \ - __JS_ENUMERATE(StringObject, string, StringPrototype, StringConstructor) + __JS_ENUMERATE(StringObject, string, StringPrototype, StringConstructor) \ + __JS_ENUMERATE(SymbolObject, symbol, SymbolPrototype, SymbolConstructor) #define JS_ENUMERATE_ERROR_SUBCLASSES \ __JS_ENUMERATE(EvalError, eval_error, EvalErrorPrototype, EvalErrorConstructor) \ @@ -70,6 +71,7 @@ class Reference; class ScopeNode; class Shape; class Statement; +class Symbol; class Uint8ClampedArray; class Value; enum class DeclarationKind; diff --git a/Libraries/LibJS/Interpreter.cpp b/Libraries/LibJS/Interpreter.cpp index 853b06d2d5..311743a60f 100644 --- a/Libraries/LibJS/Interpreter.cpp +++ b/Libraries/LibJS/Interpreter.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include namespace JS { @@ -195,6 +196,8 @@ void Interpreter::gather_roots(Badge, HashTable& roots) } roots.set(call_frame.environment); } + + SymbolObject::gather_symbol_roots(roots); } Value Interpreter::call(Function& function, Value this_value, Optional arguments) diff --git a/Libraries/LibJS/Runtime/GlobalObject.cpp b/Libraries/LibJS/Runtime/GlobalObject.cpp index e81179168b..e45e65d7c9 100644 --- a/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -54,6 +54,8 @@ #include #include #include +#include +#include #include namespace JS { @@ -102,6 +104,7 @@ void GlobalObject::initialize() add_constructor("Number", m_number_constructor, *m_number_prototype); add_constructor("Object", m_object_constructor, *m_object_prototype); add_constructor("String", m_string_constructor, *m_string_prototype); + add_constructor("Symbol", m_symbol_constructor, *m_symbol_prototype); #define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName) \ add_constructor(#ClassName, m_##snake_name##_constructor, *m_##snake_name##_prototype); diff --git a/Libraries/LibJS/Runtime/Object.h b/Libraries/LibJS/Runtime/Object.h index df7c712dc8..f3e2b2fbe5 100644 --- a/Libraries/LibJS/Runtime/Object.h +++ b/Libraries/LibJS/Runtime/Object.h @@ -89,6 +89,7 @@ public: virtual bool is_bound_function() const { return false; } virtual bool is_native_property() const { return false; } virtual bool is_string_object() const { return false; } + virtual bool is_symbol_object() const { return false; } virtual const char* class_name() const override { return "Object"; } virtual void visit_children(Cell::Visitor&) override; diff --git a/Libraries/LibJS/Runtime/Symbol.cpp b/Libraries/LibJS/Runtime/Symbol.cpp new file mode 100644 index 0000000000..2a35aa8d99 --- /dev/null +++ b/Libraries/LibJS/Runtime/Symbol.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020, Matthew Olsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +namespace JS { + +Symbol::Symbol(String description, bool is_global) + : m_description(move(description)) + , m_is_global(is_global) +{ +} + +Symbol::~Symbol() +{ +} + +Symbol* js_symbol(Heap& heap, String description, bool is_global) +{ + return heap.allocate(move(description), is_global); +} + +Symbol* js_symbol(Interpreter& interpreter, String description, bool is_global) +{ + return js_symbol(interpreter.heap(), description, is_global); +} + +} diff --git a/Libraries/LibJS/Runtime/Symbol.h b/Libraries/LibJS/Runtime/Symbol.h new file mode 100644 index 0000000000..3f911e2b4f --- /dev/null +++ b/Libraries/LibJS/Runtime/Symbol.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020, Matthew Olsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include + +namespace JS { + +class Symbol final : public Cell { +public: + Symbol(String, bool); + virtual ~Symbol(); + + const String& description() const { return m_description; } + bool is_global() const { return m_is_global; } + const String to_string() const { return String::format("Symbol(%s)", description().characters()); } + +private: + virtual const char* class_name() const override { return "Symbol"; } + + const String m_description; + const bool m_is_global; +}; + +Symbol* js_symbol(Heap&, String description, bool is_global); +Symbol* js_symbol(Interpreter&, String description, bool is_global); + +} diff --git a/Libraries/LibJS/Runtime/SymbolConstructor.cpp b/Libraries/LibJS/Runtime/SymbolConstructor.cpp new file mode 100644 index 0000000000..c8d36823ba --- /dev/null +++ b/Libraries/LibJS/Runtime/SymbolConstructor.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2020, Matthew Olsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +namespace JS { + +SymbolConstructor::SymbolConstructor() + : NativeFunction("Symbol", *interpreter().global_object().function_prototype()) +{ + put("prototype", interpreter().global_object().symbol_prototype(), 0); + put("length", Value(0), Attribute::Configurable); + + put_native_function("for", for_, 1, Attribute::Writable | Attribute::Configurable); + put_native_function("keyFor", key_for, 1, Attribute::Writable | Attribute::Configurable); + + SymbolObject::initialize_well_known_symbols(interpreter()); + + put("iterator", SymbolObject::well_known_iterator(), 0); + put("asyncIterator", SymbolObject::well_known_async_terator(), 0); + put("match", SymbolObject::well_known_match(), 0); + put("matchAll", SymbolObject::well_known_match_all(), 0); + put("replace", SymbolObject::well_known_replace(), 0); + put("search", SymbolObject::well_known_search(), 0); + put("split", SymbolObject::well_known_split(), 0); + put("hasInstance", SymbolObject::well_known_has_instance(), 0); + put("isConcatSpreadable", SymbolObject::well_known_is_concat_spreadable(), 0); + put("unscopables", SymbolObject::well_known_unscopables(), 0); + put("species", SymbolObject::well_known_species(), 0); + put("toPrimitive", SymbolObject::well_known_to_primtive(), 0); + put("toStringTag", SymbolObject::well_known_to_string_tag(), 0); +} + +SymbolConstructor::~SymbolConstructor() +{ +} + +Value SymbolConstructor::call(Interpreter& interpreter) +{ + if (!interpreter.argument_count()) + return js_symbol(interpreter, "", false); + return js_symbol(interpreter, interpreter.argument(0).to_string(interpreter), false); +} + +Value SymbolConstructor::construct(Interpreter& interpreter) +{ + interpreter.throw_exception("Symbol is not a constructor"); + return {}; +} + +Value SymbolConstructor::for_(Interpreter& interpreter) +{ + String description; + if (!interpreter.argument_count()) { + description = "undefined"; + } else { + description = interpreter.argument(0).to_string(interpreter); + } + + return SymbolObject::get_global(interpreter, description); +} + +Value SymbolConstructor::key_for(Interpreter& interpreter) +{ + auto argument = interpreter.argument(0); + if (!argument.is_symbol()) { + interpreter.throw_exception(String::format("%s is not a symbol", argument.to_string_without_side_effects().characters())); + return {}; + } + + auto& symbol = argument.as_symbol(); + if (symbol.is_global()) + return js_string(interpreter, symbol.description()); + + return js_undefined(); +} + +} diff --git a/Libraries/LibJS/Runtime/SymbolConstructor.h b/Libraries/LibJS/Runtime/SymbolConstructor.h new file mode 100644 index 0000000000..ced41de12e --- /dev/null +++ b/Libraries/LibJS/Runtime/SymbolConstructor.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020, Matthew Olsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include + +namespace JS { + +class SymbolConstructor final : public NativeFunction { +public: + SymbolConstructor(); + virtual ~SymbolConstructor() override; + + virtual Value call(Interpreter&) override; + virtual Value construct(Interpreter&) override; + +private: + virtual bool has_constructor() const override { return true; } + virtual const char* class_name() const override { return "SymbolConstructor"; } + + static Value for_(Interpreter&); + static Value key_for(Interpreter&); +}; + +} diff --git a/Libraries/LibJS/Runtime/SymbolObject.cpp b/Libraries/LibJS/Runtime/SymbolObject.cpp new file mode 100644 index 0000000000..63c4df93bb --- /dev/null +++ b/Libraries/LibJS/Runtime/SymbolObject.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2020, Matthew Olsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace JS { + +HashMap SymbolObject::s_global_symbol_map; + +Value SymbolObject::s_well_known_iterator; +Value SymbolObject::s_well_known_async_terator; +Value SymbolObject::s_well_known_match; +Value SymbolObject::s_well_known_match_all; +Value SymbolObject::s_well_known_replace; +Value SymbolObject::s_well_known_search; +Value SymbolObject::s_well_known_split; +Value SymbolObject::s_well_known_has_instance; +Value SymbolObject::s_well_known_is_concat_spreadable; +Value SymbolObject::s_well_known_unscopables; +Value SymbolObject::s_well_known_species; +Value SymbolObject::s_well_known_to_primtive; +Value SymbolObject::s_well_known_to_string_tag; + +SymbolObject* SymbolObject::create(GlobalObject& global_object, Symbol& primitive_symbol) +{ + return global_object.heap().allocate(primitive_symbol, *global_object.symbol_prototype()); +} + +SymbolObject::SymbolObject(Symbol& symbol, Object& prototype) + : Object(&prototype) + , m_symbol(symbol) +{ +} + +SymbolObject::~SymbolObject() +{ +} + +Value SymbolObject::get_global(Interpreter& interpreter, String description) +{ + auto global_symbol = s_global_symbol_map.get(description); + if (global_symbol.has_value()) + return global_symbol.value(); + + auto symbol = js_symbol(interpreter, description, true); + s_global_symbol_map.set(description, symbol); + return Value(symbol); +} + +void SymbolObject::initialize_well_known_symbols(Interpreter& interpreter) +{ + SymbolObject::s_well_known_iterator = Value(js_symbol(interpreter, "Symbol.iterator", false)); + SymbolObject::s_well_known_async_terator = Value(js_symbol(interpreter, "Symbol.asyncIterator", false)); + SymbolObject::s_well_known_match = Value(js_symbol(interpreter, "Symbol.match", false)); + SymbolObject::s_well_known_match_all = Value(js_symbol(interpreter, "Symbol.matchAll", false)); + SymbolObject::s_well_known_replace = Value(js_symbol(interpreter, "Symbol.replace", false)); + SymbolObject::s_well_known_search = Value(js_symbol(interpreter, "Symbol.search", false)); + SymbolObject::s_well_known_split = Value(js_symbol(interpreter, "Symbol.split", false)); + SymbolObject::s_well_known_has_instance = Value(js_symbol(interpreter, "Symbol.hasInstance", false)); + SymbolObject::s_well_known_is_concat_spreadable = Value(js_symbol(interpreter, "Symbol.isConcatSpreadable", false)); + SymbolObject::s_well_known_unscopables = Value(js_symbol(interpreter, "Symbol.unscopables", false)); + SymbolObject::s_well_known_species = Value(js_symbol(interpreter, "Symbol.species", false)); + SymbolObject::s_well_known_to_primtive = Value(js_symbol(interpreter, "Symbol.toPrimitive", false)); + SymbolObject::s_well_known_to_string_tag = Value(js_symbol(interpreter, "Symbol.toStringTag", false)); +} + +void SymbolObject::gather_symbol_roots(HashTable& roots) +{ + for (auto& global_symbol : s_global_symbol_map) + roots.set(&global_symbol.value.as_symbol()); + + roots.set(&s_well_known_iterator.as_symbol()); + roots.set(&s_well_known_async_terator.as_symbol()); + roots.set(&s_well_known_match.as_symbol()); + roots.set(&s_well_known_match_all.as_symbol()); + roots.set(&s_well_known_replace.as_symbol()); + roots.set(&s_well_known_search.as_symbol()); + roots.set(&s_well_known_split.as_symbol()); + roots.set(&s_well_known_has_instance.as_symbol()); + roots.set(&s_well_known_is_concat_spreadable.as_symbol()); + roots.set(&s_well_known_unscopables.as_symbol()); + roots.set(&s_well_known_species.as_symbol()); + roots.set(&s_well_known_to_primtive.as_symbol()); + roots.set(&s_well_known_to_string_tag.as_symbol()); +} + +void SymbolObject::visit_children(Cell::Visitor& visitor) +{ + Object::visit_children(visitor); + visitor.visit(&m_symbol); +} + +} diff --git a/Libraries/LibJS/Runtime/SymbolObject.h b/Libraries/LibJS/Runtime/SymbolObject.h new file mode 100644 index 0000000000..33c726cde0 --- /dev/null +++ b/Libraries/LibJS/Runtime/SymbolObject.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020, Matthew Olsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include + +namespace JS { + +class SymbolObject : public Object { +public: + static SymbolObject* create(GlobalObject&, Symbol&); + + SymbolObject(Symbol&, Object& prototype); + virtual ~SymbolObject() override; + + Symbol& primitive_symbol() { return m_symbol; } + const Symbol& primitive_symbol() const { return m_symbol; } + + const String& description() const { return m_symbol.description(); } + bool is_global() const { return m_symbol.is_global(); } + + static Value get_global(Interpreter&, String description); + + virtual Value value_of() const override + { + return Value(&m_symbol); + } + + static void initialize_well_known_symbols(Interpreter&); + static void gather_symbol_roots(HashTable& roots); + + static Value well_known_iterator() { return s_well_known_iterator; }; + static Value well_known_async_terator() { return s_well_known_async_terator; }; + static Value well_known_match() { return s_well_known_match; }; + static Value well_known_match_all() { return s_well_known_match_all; }; + static Value well_known_replace() { return s_well_known_replace; }; + static Value well_known_search() { return s_well_known_search; }; + static Value well_known_split() { return s_well_known_split; }; + static Value well_known_has_instance() { return s_well_known_has_instance; }; + static Value well_known_is_concat_spreadable() { return s_well_known_is_concat_spreadable; }; + static Value well_known_unscopables() { return s_well_known_unscopables; }; + static Value well_known_species() { return s_well_known_species; }; + static Value well_known_to_primtive() { return s_well_known_to_primtive; }; + static Value well_known_to_string_tag() { return s_well_known_to_string_tag; }; + +private: + virtual void visit_children(Visitor&) override; + virtual const char* class_name() const override { return "SymbolObject"; } + virtual bool is_symbol_object() const override { return true; } + + Symbol& m_symbol; + + static HashMap s_global_symbol_map; + + static Value s_well_known_iterator; + static Value s_well_known_async_terator; + static Value s_well_known_match; + static Value s_well_known_match_all; + static Value s_well_known_replace; + static Value s_well_known_search; + static Value s_well_known_split; + static Value s_well_known_has_instance; + static Value s_well_known_is_concat_spreadable; + static Value s_well_known_unscopables; + static Value s_well_known_species; + static Value s_well_known_to_primtive; + static Value s_well_known_to_string_tag; +}; + +} diff --git a/Libraries/LibJS/Runtime/SymbolPrototype.cpp b/Libraries/LibJS/Runtime/SymbolPrototype.cpp new file mode 100644 index 0000000000..b00dfbedd2 --- /dev/null +++ b/Libraries/LibJS/Runtime/SymbolPrototype.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2020, Matthew Olsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace JS { + +SymbolPrototype::SymbolPrototype() + : Object(interpreter().global_object().object_prototype()) +{ + // FIXME: description has no setter, eventually remove + put_native_property("description", description_getter, description_setter, Attribute::Configurable); + + put_native_function("toString", to_string, 0, Attribute::Writable | Attribute::Configurable); + put_native_function("valueOf", value_of, 0, Attribute::Writable | Attribute::Configurable); +} + +SymbolPrototype::~SymbolPrototype() +{ +} + +static SymbolObject* this_symbol_from_interpreter(Interpreter& interpreter) +{ + auto* this_object = interpreter.this_value().to_object(interpreter.heap()); + if (!this_object) + return nullptr; + if (!this_object->is_symbol_object()) { + interpreter.throw_exception("object must be of type Symbol"); + return nullptr; + } + return static_cast(this_object); +} + +Value SymbolPrototype::description_getter(Interpreter& interpreter) +{ + auto* this_object = this_symbol_from_interpreter(interpreter); + if (!this_object) + return {}; + return js_string(interpreter, this_object->description()); +} + +void SymbolPrototype::description_setter(Interpreter&, Value) +{ + // No-op, remove eventually +} + +Value SymbolPrototype::to_string(Interpreter& interpreter) +{ + auto* this_object = this_symbol_from_interpreter(interpreter); + if (!this_object) + return {}; + auto string = this_object->primitive_symbol().to_string(); + return js_string(interpreter, move(string)); +} + +Value SymbolPrototype::value_of(Interpreter& interpreter) +{ + auto* this_object = this_symbol_from_interpreter(interpreter); + if (!this_object) + return {}; + return this_object->value_of(); +} + +} diff --git a/Libraries/LibJS/Runtime/SymbolPrototype.h b/Libraries/LibJS/Runtime/SymbolPrototype.h new file mode 100644 index 0000000000..4ccd5df3e1 --- /dev/null +++ b/Libraries/LibJS/Runtime/SymbolPrototype.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020, Matthew Olsson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include + +namespace JS { + +class SymbolPrototype final : public Object { +public: + SymbolPrototype(); + virtual ~SymbolPrototype() override; + +private: + virtual const char* class_name() const override { return "SymbolPrototype"; } + + static Value description_getter(Interpreter&); + static void description_setter(Interpreter&, Value); + + static Value to_string(Interpreter&); + static Value value_of(Interpreter&); +}; + +} diff --git a/Libraries/LibJS/Runtime/Value.cpp b/Libraries/LibJS/Runtime/Value.cpp index afd5c14c4c..8055331c02 100644 --- a/Libraries/LibJS/Runtime/Value.cpp +++ b/Libraries/LibJS/Runtime/Value.cpp @@ -36,7 +36,9 @@ #include #include #include +#include #include +#include #include #include @@ -87,6 +89,9 @@ String Value::to_string_without_side_effects() const if (is_string()) return m_value.as_string->string(); + if (is_symbol()) + return as_symbol().to_string(); + ASSERT(is_object()); return String::format("[object %s]", as_object().class_name()); } @@ -124,6 +129,11 @@ String Value::to_string(Interpreter& interpreter) const return String::format("%.4f", as_double()); } + if (is_symbol()) { + interpreter.throw_exception("Can't convert symbol to string"); + return {}; + } + if (is_object()) { auto primitive_value = as_object().to_primitive(Object::PreferredType::String); // FIXME: Maybe we should pass in the Interpreter& and call interpreter.exception() instead? @@ -152,6 +162,7 @@ bool Value::to_boolean() const case Type::String: return !as_string().string().is_empty(); case Type::Object: + case Type::Symbol: return true; default: ASSERT_NOT_REACHED(); @@ -173,6 +184,9 @@ Object* Value::to_object(Heap& heap) const if (is_string()) return StringObject::create(heap.interpreter().global_object(), *m_value.as_string); + if (is_symbol()) + return SymbolObject::create(heap.interpreter().global_object(), *m_value.as_symbol); + if (is_number()) return NumberObject::create(heap.interpreter().global_object(), m_value.as_double); @@ -216,6 +230,9 @@ Value Value::to_number() const return js_nan(); return Value(parsed_double); } + case Type::Symbol: + // FIXME: Get access to the interpreter and throw a TypeError + ASSERT_NOT_REACHED(); case Type::Object: return m_value.as_object->to_primitive(Object::PreferredType::Number).to_number(); } @@ -473,6 +490,8 @@ bool same_value_non_numeric(Interpreter&, Value lhs, Value rhs) return true; case Value::Type::String: return lhs.as_string().string() == rhs.as_string().string(); + case Value::Type::Symbol: + return &lhs.as_symbol() == &rhs.as_symbol(); case Value::Type::Boolean: return lhs.as_bool() == rhs.as_bool(); case Value::Type::Object: @@ -520,10 +539,10 @@ bool abstract_eq(Interpreter& interpreter, Value lhs, Value rhs) if (rhs.is_boolean()) return abstract_eq(interpreter, lhs, rhs.to_number()); - if ((lhs.is_string() || lhs.is_number()) && rhs.is_object()) + if ((lhs.is_string() || lhs.is_number() || lhs.is_symbol()) && rhs.is_object()) return abstract_eq(interpreter, lhs, rhs.to_primitive(interpreter)); - if (lhs.is_object() && (rhs.is_string() || rhs.is_number())) + if (lhs.is_object() && (rhs.is_string() || rhs.is_number() || rhs.is_symbol())) return abstract_eq(interpreter, lhs.to_primitive(interpreter), rhs); return false; diff --git a/Libraries/LibJS/Runtime/Value.h b/Libraries/LibJS/Runtime/Value.h index b946de209c..e798f2646e 100644 --- a/Libraries/LibJS/Runtime/Value.h +++ b/Libraries/LibJS/Runtime/Value.h @@ -30,6 +30,7 @@ #include #include #include +#include namespace JS { @@ -43,6 +44,7 @@ public: String, Object, Boolean, + Symbol, }; bool is_empty() const { return m_type == Type::Empty; } @@ -52,6 +54,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_symbol() const { return m_type == Type::Symbol; } bool is_cell() const { return is_string() || is_object(); } bool is_array() const; bool is_function() const; @@ -110,6 +113,12 @@ public: m_value.as_string = string; } + Value(Symbol* symbol) + : m_type(Type::Symbol) + { + m_value.as_symbol = symbol; + } + explicit Value(Type type) : m_type(type) { @@ -153,6 +162,18 @@ public: return *m_value.as_string; } + Symbol& as_symbol() + { + ASSERT(is_symbol()); + return *m_value.as_symbol; + } + + const Symbol& as_symbol() const + { + ASSERT(is_symbol()); + return *m_value.as_symbol; + } + Cell* as_cell() { ASSERT(is_cell()); @@ -188,6 +209,7 @@ private: bool as_bool; double as_double; PrimitiveString* as_string; + Symbol* as_symbol; Object* as_object; Cell* as_cell; } m_value; diff --git a/Libraries/LibJS/Tests/Symbol.for.js b/Libraries/LibJS/Tests/Symbol.for.js new file mode 100644 index 0000000000..9423f215d2 --- /dev/null +++ b/Libraries/LibJS/Tests/Symbol.for.js @@ -0,0 +1,30 @@ +load("test-common.js") + +try { + const localSym = Symbol("foo"); + const globalSym = Symbol.for("foo"); + + assert(localSym !== globalSym); + assert(localSym !== Symbol("foo")); + assert(globalSym !== Symbol("foo")); + assert(globalSym === Symbol.for("foo")); + assert(localSym.toString() === "Symbol(foo)"); + assert(globalSym.toString() === "Symbol(foo)"); + + assert(Symbol.for(1).description === "1"); + assert(Symbol.for(true).description === "true"); + assert(Symbol.for({}).description === "[object Object]"); + assert(Symbol.for().description === "undefined"); + assert(Symbol.for(null).description === "null"); + + assertThrowsError(() => { + Symbol.for(Symbol()); + }, { + error: TypeError, + message: "Can't convert symbol to string", + }); + + console.log("PASS"); +} catch (e) { + console.log("FAIL: " + e); +} diff --git a/Libraries/LibJS/Tests/Symbol.js b/Libraries/LibJS/Tests/Symbol.js new file mode 100644 index 0000000000..47dd9f5228 --- /dev/null +++ b/Libraries/LibJS/Tests/Symbol.js @@ -0,0 +1,26 @@ +load("test-common.js") + +try { + const s1 = Symbol("foo"); + const s2 = Symbol("foo"); + + assert(s1 !== s2); + assert(s1.description === "foo"); + assert(s2.description === "foo"); + + s1.description = "bar"; + assert(s1.description === "foo"); + + assert(typeof s1 === "symbol"); + + assertThrowsError(() => { + Symbol(Symbol('foo')); + }, { + error: TypeError, + message: "Can't convert symbol to string" + }) + + console.log("PASS"); +} catch (e) { + console.log("FAIL: " + e); +} diff --git a/Libraries/LibJS/Tests/Symbol.keyFor.js b/Libraries/LibJS/Tests/Symbol.keyFor.js new file mode 100644 index 0000000000..56e3ed9d81 --- /dev/null +++ b/Libraries/LibJS/Tests/Symbol.keyFor.js @@ -0,0 +1,31 @@ +load("test-common.js") + +try { + const localSym = Symbol("foo"); + const globalSym = Symbol.for("foo"); + + assert(Symbol.keyFor(localSym) === undefined); + assert(Symbol.keyFor(globalSym) === "foo"); + + const testThrows = (value, str) => { + assertThrowsError(() => { + Symbol.keyFor(value); + }, { + error: TypeError, + message: str + " is not a symbol", + }); + }; + + testThrows(1, "1"); + testThrows(null, "null"); + testThrows(undefined, "undefined"); + testThrows([], "[object Array]"); + testThrows({}, "[object Object]"); + testThrows(true, "true"); + testThrows("foobar", "foobar"); + testThrows(function(){}, "[object ScriptFunction]"); // FIXME: Better function stringification + + console.log("PASS"); +} catch (e) { + console.log("FAIL: " + e); +} diff --git a/Libraries/LibJS/Tests/Symbol.prototype.toString.js b/Libraries/LibJS/Tests/Symbol.prototype.toString.js new file mode 100644 index 0000000000..cdbcae073e --- /dev/null +++ b/Libraries/LibJS/Tests/Symbol.prototype.toString.js @@ -0,0 +1,28 @@ +load("test-common.js") + +try { + const s1 = Symbol("foo"); + const s2 = Symbol.for("bar"); + + assert(s1.toString() === "Symbol(foo)"); + assert(s2.toString() === "Symbol(bar)"); + + assertThrowsError(() => { + s1 + ""; + }, { + error: TypeError, + message: "Can't convert symbol to string", + }); + + // FIXME: Uncomment when this doesn't assert + // assertThrowsError(() => { + // s1 + 1; + // }, { + // error: TypeError, + // message: "Can't convert symbol to number", + // }); + + console.log("PASS"); +} catch (e) { + console.log("FAIL: " + e); +} diff --git a/Libraries/LibJS/Tests/Symbol.prototype.valueOf.js b/Libraries/LibJS/Tests/Symbol.prototype.valueOf.js new file mode 100644 index 0000000000..33c171c5b0 --- /dev/null +++ b/Libraries/LibJS/Tests/Symbol.prototype.valueOf.js @@ -0,0 +1,22 @@ +load("test-common.js"); + +try { + let local = Symbol('foo'); + let global = Symbol.for('foo'); + assert(local.valueOf() === local); + assert(global.valueOf() === global); + + assert(Symbol.prototype.valueOf.call(local) === local); + assert(Symbol.prototype.valueOf.call(global) === global); + + assertThrowsError(() => { + Symbol.prototype.valueOf.call("foo"); + }, { + error: TypeError, + message: "object must be of type Symbol" + }); + + console.log("PASS"); +} catch (err) { + console.log("FAIL: " + err); +}