diff options
Diffstat (limited to 'Userland/Libraries/LibJS/Runtime')
149 files changed, 19183 insertions, 0 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/Accessor.h b/Userland/Libraries/LibJS/Runtime/Accessor.h new file mode 100644 index 0000000000..ce2d9efb57 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Accessor.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/Function.h> +#include <LibJS/Runtime/VM.h> + +namespace JS { + +class Accessor final : public Cell { +public: + static Accessor* create(VM& vm, Function* getter, Function* setter) + { + return vm.heap().allocate_without_global_object<Accessor>(getter, setter); + } + + Accessor(Function* getter, Function* setter) + : m_getter(getter) + , m_setter(setter) + { + } + + Function* getter() const { return m_getter; } + void set_getter(Function* getter) { m_getter = getter; } + + Function* setter() const { return m_setter; } + void set_setter(Function* setter) { m_setter = setter; } + + Value call_getter(Value this_value) + { + if (!m_getter) + return js_undefined(); + return vm().call(*m_getter, this_value); + } + + void call_setter(Value this_value, Value setter_value) + { + if (!m_setter) + return; + // FIXME: It might be nice if we had a way to communicate to our caller if an exception happened after this. + [[maybe_unused]] auto rc = vm().call(*m_setter, this_value, setter_value); + } + + void visit_edges(Cell::Visitor& visitor) override + { + visitor.visit(m_getter); + visitor.visit(m_setter); + } + +private: + const char* class_name() const override { return "Accessor"; }; + + Function* m_getter { nullptr }; + Function* m_setter { nullptr }; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Array.cpp b/Userland/Libraries/LibJS/Runtime/Array.cpp new file mode 100644 index 0000000000..6e1f5f5300 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Array.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/Function.h> +#include <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/ArrayPrototype.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace JS { + +Array* Array::create(GlobalObject& global_object) +{ + return global_object.heap().allocate<Array>(global_object, *global_object.array_prototype()); +} + +Array::Array(Object& prototype) + : Object(prototype) +{ + auto& vm = this->vm(); + define_native_property(vm.names.length, length_getter, length_setter, Attribute::Writable); +} + +Array::~Array() +{ +} + +Array* Array::typed_this(VM& vm, GlobalObject& global_object) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + if (!this_object->is_array()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAn, "Array"); + return nullptr; + } + return static_cast<Array*>(this_object); +} + +JS_DEFINE_NATIVE_GETTER(Array::length_getter) +{ + auto* array = typed_this(vm, global_object); + if (!array) + return {}; + return Value(static_cast<i32>(array->indexed_properties().array_like_size())); +} + +JS_DEFINE_NATIVE_SETTER(Array::length_setter) +{ + auto* array = typed_this(vm, global_object); + if (!array) + return; + auto length = value.to_number(global_object); + if (vm.exception()) + return; + if (length.is_nan() || length.is_infinity() || length.as_double() < 0) { + vm.throw_exception<RangeError>(global_object, ErrorType::InvalidLength, "array"); + return; + } + array->indexed_properties().set_array_like_size(length.as_double()); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Array.h b/Userland/Libraries/LibJS/Runtime/Array.h new file mode 100644 index 0000000000..2ee4727c35 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Array.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class Array final : public Object { + JS_OBJECT(Array, Object); + +public: + static Array* create(GlobalObject&); + + explicit Array(Object& prototype); + virtual ~Array() override; + + static Array* typed_this(VM&, GlobalObject&); + +private: + virtual bool is_array() const override { return true; } + + JS_DECLARE_NATIVE_GETTER(length_getter); + JS_DECLARE_NATIVE_SETTER(length_setter); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp new file mode 100644 index 0000000000..007e61c0fa --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/ArrayBuffer.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace JS { + +ArrayBuffer* ArrayBuffer::create(GlobalObject& global_object, size_t byte_size) +{ + return global_object.heap().allocate<ArrayBuffer>(global_object, byte_size, *global_object.array_buffer_prototype()); +} + +ArrayBuffer::ArrayBuffer(size_t byte_size, Object& prototype) + : Object(prototype) + , m_buffer(ByteBuffer::create_zeroed(byte_size)) +{ +} + +ArrayBuffer::~ArrayBuffer() +{ +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h new file mode 100644 index 0000000000..d2724cd96e --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <AK/ByteBuffer.h> +#include <LibJS/Runtime/Object.h> + +namespace JS { + +class ArrayBuffer : public Object { + JS_OBJECT(ArrayBuffer, Object); + +public: + static ArrayBuffer* create(GlobalObject&, size_t); + + ArrayBuffer(size_t, Object& prototype); + virtual ~ArrayBuffer() override; + + size_t byte_length() const { return m_buffer.size(); } + ByteBuffer& buffer() { return m_buffer; } + const ByteBuffer& buffer() const { return m_buffer; } + +private: + ByteBuffer m_buffer; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBufferConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ArrayBufferConstructor.cpp new file mode 100644 index 0000000000..b869dcd5f3 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ArrayBufferConstructor.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/ArrayBuffer.h> +#include <LibJS/Runtime/ArrayBufferConstructor.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/TypedArray.h> + +namespace JS { + +ArrayBufferConstructor::ArrayBufferConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.ArrayBuffer, *global_object.function_prototype()) +{ +} + +void ArrayBufferConstructor::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + NativeFunction::initialize(global_object); + u8 attr = Attribute::Writable | Attribute::Configurable; + define_property(vm.names.prototype, global_object.array_buffer_prototype(), 0); + define_property(vm.names.length, Value(1), Attribute::Configurable); + define_native_function(vm.names.isView, is_view, 1, attr); +} + +ArrayBufferConstructor::~ArrayBufferConstructor() +{ +} + +Value ArrayBufferConstructor::call() +{ + auto& vm = this->vm(); + vm.throw_exception<TypeError>(global_object(), ErrorType::ConstructorWithoutNew, vm.names.ArrayBuffer); + return {}; +} + +Value ArrayBufferConstructor::construct(Function&) +{ + auto& vm = this->vm(); + auto byte_length = vm.argument(0).to_index(global_object()); + if (vm.exception()) { + // Re-throw more specific RangeError + vm.clear_exception(); + vm.throw_exception<RangeError>(global_object(), ErrorType::InvalidLength, "array buffer"); + return {}; + } + return ArrayBuffer::create(global_object(), byte_length); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayBufferConstructor::is_view) +{ + auto arg = vm.argument(0); + if (!arg.is_object()) + return Value(false); + if (arg.as_object().is_typed_array()) + return Value(true); + // FIXME: Check for DataView as well + return Value(false); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBufferConstructor.h b/Userland/Libraries/LibJS/Runtime/ArrayBufferConstructor.h new file mode 100644 index 0000000000..455fa7e7e2 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ArrayBufferConstructor.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/NativeFunction.h> + +namespace JS { + +class ArrayBufferConstructor final : public NativeFunction { + JS_OBJECT(ArrayBufferConstructor, NativeFunction); + +public: + explicit ArrayBufferConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~ArrayBufferConstructor() override; + + virtual Value call() override; + virtual Value construct(Function& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } + + JS_DECLARE_NATIVE_FUNCTION(is_view); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp new file mode 100644 index 0000000000..846acbd562 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <AK/Function.h> +#include <LibJS/Runtime/ArrayBuffer.h> +#include <LibJS/Runtime/ArrayBufferPrototype.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace JS { + +ArrayBufferPrototype::ArrayBufferPrototype(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void ArrayBufferPrototype::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.slice, slice, 2, attr); + // FIXME: This should be an accessor property + define_native_property(vm.names.byteLength, byte_length_getter, {}, Attribute::Configurable); + + define_property(vm.well_known_symbol_to_string_tag(), js_string(vm.heap(), "ArrayBuffer"), Attribute::Configurable); +} + +ArrayBufferPrototype::~ArrayBufferPrototype() +{ +} + +static ArrayBuffer* array_buffer_object_from(VM& vm, GlobalObject& global_object) +{ + // ArrayBuffer.prototype.* deliberately don't coerce |this| value to object. + auto this_value = vm.this_value(global_object); + if (!this_value.is_object()) + return nullptr; + auto& this_object = this_value.as_object(); + if (!is<ArrayBuffer>(this_object)) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAn, "ArrayBuffer"); + return nullptr; + } + return static_cast<ArrayBuffer*>(&this_object); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::slice) +{ + auto array_buffer_object = array_buffer_object_from(vm, global_object); + if (!array_buffer_object) + return {}; + TODO(); +} + +JS_DEFINE_NATIVE_GETTER(ArrayBufferPrototype::byte_length_getter) +{ + auto array_buffer_object = array_buffer_object_from(vm, global_object); + if (!array_buffer_object) + return {}; + // FIXME: Check for shared buffer + // FIXME: Check for detached buffer + return Value((double)array_buffer_object->byte_length()); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.h b/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.h new file mode 100644 index 0000000000..96ed852830 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class ArrayBufferPrototype final : public Object { + JS_OBJECT(ArrayBufferPrototype, Object); + +public: + explicit ArrayBufferPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~ArrayBufferPrototype() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(slice); + JS_DECLARE_NATIVE_GETTER(byte_length_getter); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp new file mode 100644 index 0000000000..153e2a4fe2 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <AK/Function.h> +#include <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/ArrayConstructor.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/IteratorOperations.h> +#include <LibJS/Runtime/Shape.h> + +namespace JS { + +ArrayConstructor::ArrayConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.Array, *global_object.function_prototype()) +{ +} + +ArrayConstructor::~ArrayConstructor() +{ +} + +void ArrayConstructor::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + NativeFunction::initialize(global_object); + + define_property(vm.names.prototype, global_object.array_prototype(), 0); + define_property(vm.names.length, Value(1), Attribute::Configurable); + + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.from, from, 1, attr); + define_native_function(vm.names.isArray, is_array, 1, attr); + define_native_function(vm.names.of, of, 0, attr); +} + +Value ArrayConstructor::call() +{ + if (vm().argument_count() <= 0) + return Array::create(global_object()); + + if (vm().argument_count() == 1 && vm().argument(0).is_number()) { + auto array_length_value = vm().argument(0); + if (!array_length_value.is_integer() || array_length_value.as_i32() < 0) { + vm().throw_exception<RangeError>(global_object(), ErrorType::InvalidLength, "array"); + return {}; + } + auto* array = Array::create(global_object()); + array->indexed_properties().set_array_like_size(array_length_value.as_i32()); + return array; + } + + auto* array = Array::create(global_object()); + for (size_t i = 0; i < vm().argument_count(); ++i) + array->indexed_properties().append(vm().argument(i)); + return array; +} + +Value ArrayConstructor::construct(Function&) +{ + return call(); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from) +{ + auto value = vm.argument(0); + auto object = value.to_object(global_object); + if (!object) + return {}; + + auto* array = Array::create(global_object); + + // Array.from() lets you create Arrays from: + if (auto size = object->indexed_properties().array_like_size()) { + // * array-like objects (objects with a length property and indexed elements) + MarkedValueList elements(vm.heap()); + elements.ensure_capacity(size); + for (size_t i = 0; i < size; ++i) { + elements.append(object->get(i)); + if (vm.exception()) + return {}; + } + array->set_indexed_property_elements(move(elements)); + } else { + // * iterable objects + get_iterator_values(global_object, value, [&](Value element) { + if (vm.exception()) + return IterationDecision::Break; + array->indexed_properties().append(element); + return IterationDecision::Continue; + }); + if (vm.exception()) + return {}; + } + + // FIXME: if interpreter.argument_count() >= 2: mapFn + // FIXME: if interpreter.argument_count() >= 3: thisArg + + return array; +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::is_array) +{ + auto value = vm.argument(0); + return Value(value.is_array()); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::of) +{ + auto* array = Array::create(global_object); + for (size_t i = 0; i < vm.argument_count(); ++i) + array->indexed_properties().append(vm.argument(i)); + return array; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/ArrayConstructor.h b/Userland/Libraries/LibJS/Runtime/ArrayConstructor.h new file mode 100644 index 0000000000..5c85dfc04b --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ArrayConstructor.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/NativeFunction.h> + +namespace JS { + +class ArrayConstructor final : public NativeFunction { + JS_OBJECT(ArrayConstructor, NativeFunction); + +public: + explicit ArrayConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~ArrayConstructor() override; + + virtual Value call() override; + virtual Value construct(Function& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } + + JS_DECLARE_NATIVE_FUNCTION(from); + JS_DECLARE_NATIVE_FUNCTION(is_array); + JS_DECLARE_NATIVE_FUNCTION(of); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/ArrayIterator.cpp b/Userland/Libraries/LibJS/Runtime/ArrayIterator.cpp new file mode 100644 index 0000000000..338ec0a0bc --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ArrayIterator.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/ArrayIterator.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace JS { + +ArrayIterator* ArrayIterator::create(GlobalObject& global_object, Value array, Object::PropertyKind iteration_kind) +{ + return global_object.heap().allocate<ArrayIterator>(global_object, *global_object.array_iterator_prototype(), array, iteration_kind); +} + +ArrayIterator::ArrayIterator(Object& prototype, Value array, Object::PropertyKind iteration_kind) + : Object(prototype) + , m_array(array) + , m_iteration_kind(iteration_kind) +{ +} + +ArrayIterator::~ArrayIterator() +{ +} + +void ArrayIterator::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_array); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/ArrayIterator.h b/Userland/Libraries/LibJS/Runtime/ArrayIterator.h new file mode 100644 index 0000000000..30ec25b70f --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ArrayIterator.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class ArrayIterator final : public Object { + JS_OBJECT(ArrayIterator, Object); + +public: + static ArrayIterator* create(GlobalObject&, Value array, Object::PropertyKind iteration_kind); + + explicit ArrayIterator(Object& prototype, Value array, Object::PropertyKind iteration_kind); + virtual ~ArrayIterator() override; + + Value array() const { return m_array; } + Object::PropertyKind iteration_kind() const { return m_iteration_kind; } + size_t index() const { return m_index; } + +private: + friend class ArrayIteratorPrototype; + + virtual void visit_edges(Cell::Visitor&) override; + + Value m_array; + Object::PropertyKind m_iteration_kind; + size_t m_index { 0 }; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp new file mode 100644 index 0000000000..c3cbd179b0 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/ArrayIterator.h> +#include <LibJS/Runtime/ArrayIteratorPrototype.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/IteratorOperations.h> + +namespace JS { + +ArrayIteratorPrototype::ArrayIteratorPrototype(GlobalObject& global_object) + : Object(*global_object.iterator_prototype()) +{ +} + +void ArrayIteratorPrototype::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + + define_native_function(vm.names.next, next, 0, Attribute::Configurable | Attribute::Writable); + define_property(global_object.vm().well_known_symbol_to_string_tag(), js_string(global_object.heap(), "Array Iterator"), Attribute::Configurable); +} + +ArrayIteratorPrototype::~ArrayIteratorPrototype() +{ +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayIteratorPrototype::next) +{ + auto this_value = vm.this_value(global_object); + if (!this_value.is_object() || !is<ArrayIterator>(this_value.as_object())) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAn, "Array Iterator"); + return {}; + } + auto& this_object = this_value.as_object(); + auto& iterator = static_cast<ArrayIterator&>(this_object); + auto target_array = iterator.array(); + if (target_array.is_undefined()) + return create_iterator_result_object(global_object, js_undefined(), true); + ASSERT(target_array.is_object()); + auto& array = target_array.as_object(); + + auto index = iterator.index(); + auto iteration_kind = iterator.iteration_kind(); + // FIXME: Typed array check + auto length = array.indexed_properties().array_like_size(); + + if (index >= length) { + iterator.m_array = js_undefined(); + return create_iterator_result_object(global_object, js_undefined(), true); + } + + iterator.m_index++; + if (iteration_kind == Object::PropertyKind::Key) + return create_iterator_result_object(global_object, Value(static_cast<i32>(index)), false); + + auto value = array.get(index); + if (vm.exception()) + return {}; + if (iteration_kind == Object::PropertyKind::Value) + return create_iterator_result_object(global_object, value, false); + + auto* entry_array = Array::create(global_object); + entry_array->define_property(0, Value(static_cast<i32>(index))); + entry_array->define_property(1, value); + return create_iterator_result_object(global_object, entry_array, false); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/ArrayIteratorPrototype.h b/Userland/Libraries/LibJS/Runtime/ArrayIteratorPrototype.h new file mode 100644 index 0000000000..0e79a55ca5 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ArrayIteratorPrototype.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class ArrayIteratorPrototype final : public Object { + JS_OBJECT(ArrayIteratorPrototype, Object) + +public: + ArrayIteratorPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~ArrayIteratorPrototype() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(next); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp new file mode 100644 index 0000000000..8b155e9a95 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp @@ -0,0 +1,1037 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * Copyright (c) 2020, Marcin Gasperowicz <xnooga@gmail.com> + * 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 <AK/Function.h> +#include <AK/HashTable.h> +#include <AK/ScopeGuard.h> +#include <AK/StringBuilder.h> +#include <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/ArrayIterator.h> +#include <LibJS/Runtime/ArrayPrototype.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/Function.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/ObjectPrototype.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +static HashTable<Object*> s_array_join_seen_objects; + +ArrayPrototype::ArrayPrototype(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void ArrayPrototype::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + u8 attr = Attribute::Writable | Attribute::Configurable; + + define_native_function(vm.names.filter, filter, 1, attr); + define_native_function(vm.names.forEach, for_each, 1, attr); + define_native_function(vm.names.map, map, 1, attr); + define_native_function(vm.names.pop, pop, 0, attr); + define_native_function(vm.names.push, push, 1, attr); + define_native_function(vm.names.shift, shift, 0, attr); + define_native_function(vm.names.toString, to_string, 0, attr); + define_native_function(vm.names.toLocaleString, to_locale_string, 0, attr); + define_native_function(vm.names.unshift, unshift, 1, attr); + define_native_function(vm.names.join, join, 1, attr); + define_native_function(vm.names.concat, concat, 1, attr); + define_native_function(vm.names.slice, slice, 2, attr); + define_native_function(vm.names.indexOf, index_of, 1, attr); + define_native_function(vm.names.reduce, reduce, 1, attr); + define_native_function(vm.names.reduceRight, reduce_right, 1, attr); + define_native_function(vm.names.reverse, reverse, 0, attr); + define_native_function(vm.names.sort, sort, 1, attr); + define_native_function(vm.names.lastIndexOf, last_index_of, 1, attr); + define_native_function(vm.names.includes, includes, 1, attr); + define_native_function(vm.names.find, find, 1, attr); + define_native_function(vm.names.findIndex, find_index, 1, attr); + define_native_function(vm.names.some, some, 1, attr); + define_native_function(vm.names.every, every, 1, attr); + define_native_function(vm.names.splice, splice, 2, attr); + define_native_function(vm.names.fill, fill, 1, attr); + define_native_function(vm.names.values, values, 0, attr); + define_property(vm.names.length, Value(0), Attribute::Configurable); + + // Use define_property here instead of define_native_function so that + // Object.is(Array.prototype[Symbol.iterator], Array.prototype.values) + // evaluates to true + define_property(vm.well_known_symbol_iterator(), get(vm.names.values), attr); +} + +ArrayPrototype::~ArrayPrototype() +{ +} + +static Function* callback_from_args(GlobalObject& global_object, const String& name) +{ + auto& vm = global_object.vm(); + if (vm.argument_count() < 1) { + vm.throw_exception<TypeError>(global_object, ErrorType::ArrayPrototypeOneArg, name); + return nullptr; + } + auto callback = vm.argument(0); + if (!callback.is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback.to_string_without_side_effects()); + return nullptr; + } + return &callback.as_function(); +} + +static void for_each_item(VM& vm, GlobalObject& global_object, const String& name, AK::Function<IterationDecision(size_t index, Value value, Value callback_result)> callback, bool skip_empty = true) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return; + + auto initial_length = length_of_array_like(global_object, *this_object); + if (vm.exception()) + return; + + auto* callback_function = callback_from_args(global_object, name); + if (!callback_function) + return; + + auto this_value = vm.argument(1); + + for (size_t i = 0; i < initial_length; ++i) { + auto value = this_object->get(i); + if (vm.exception()) + return; + if (value.is_empty()) { + if (skip_empty) + continue; + value = js_undefined(); + } + + auto callback_result = vm.call(*callback_function, this_value, value, Value((i32)i), this_object); + if (vm.exception()) + return; + + if (callback(i, value, callback_result) == IterationDecision::Break) + break; + } +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::filter) +{ + auto* new_array = Array::create(global_object); + for_each_item(vm, global_object, "filter", [&](auto, auto value, auto callback_result) { + if (callback_result.to_boolean()) + new_array->indexed_properties().append(value); + return IterationDecision::Continue; + }); + return Value(new_array); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::for_each) +{ + for_each_item(vm, global_object, "forEach", [](auto, auto, auto) { + return IterationDecision::Continue; + }); + return js_undefined(); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::map) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + auto initial_length = length_of_array_like(global_object, *this_object); + if (vm.exception()) + return {}; + auto* new_array = Array::create(global_object); + new_array->indexed_properties().set_array_like_size(initial_length); + for_each_item(vm, global_object, "map", [&](auto index, auto, auto callback_result) { + if (vm.exception()) + return IterationDecision::Break; + new_array->define_property(index, callback_result); + return IterationDecision::Continue; + }); + return Value(new_array); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::push) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + if (this_object->is_array()) { + auto* array = static_cast<Array*>(this_object); + for (size_t i = 0; i < vm.argument_count(); ++i) + array->indexed_properties().append(vm.argument(i)); + return Value(static_cast<i32>(array->indexed_properties().array_like_size())); + } + auto length = length_of_array_like(global_object, *this_object); + if (vm.exception()) + return {}; + auto argument_count = vm.argument_count(); + auto new_length = length + argument_count; + if (new_length > MAX_ARRAY_LIKE_INDEX) { + vm.throw_exception<TypeError>(global_object, ErrorType::ArrayMaxSize); + return {}; + } + for (size_t i = 0; i < argument_count; ++i) { + this_object->put(length + i, vm.argument(i)); + if (vm.exception()) + return {}; + } + auto new_length_value = Value((i32)new_length); + this_object->put(vm.names.length, new_length_value); + if (vm.exception()) + return {}; + return new_length_value; +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::unshift) +{ + auto* array = Array::typed_this(vm, global_object); + if (!array) + return {}; + for (size_t i = 0; i < vm.argument_count(); ++i) + array->indexed_properties().insert(i, vm.argument(i)); + return Value(static_cast<i32>(array->indexed_properties().array_like_size())); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::pop) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + if (this_object->is_array()) { + auto* array = static_cast<Array*>(this_object); + if (array->indexed_properties().is_empty()) + return js_undefined(); + return array->indexed_properties().take_last(array).value.value_or(js_undefined()); + } + auto length = length_of_array_like(global_object, *this_object); + if (vm.exception()) + return {}; + if (length == 0) { + this_object->put(vm.names.length, Value(0)); + return js_undefined(); + } + auto index = length - 1; + auto element = this_object->get(index).value_or(js_undefined()); + if (vm.exception()) + return {}; + this_object->delete_property(index); + if (vm.exception()) + return {}; + this_object->put(vm.names.length, Value((i32)index)); + if (vm.exception()) + return {}; + return element; +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::shift) +{ + auto* array = Array::typed_this(vm, global_object); + if (!array) + return {}; + if (array->indexed_properties().is_empty()) + return js_undefined(); + auto result = array->indexed_properties().take_first(array); + if (vm.exception()) + return {}; + return result.value.value_or(js_undefined()); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_string) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + auto join_function = this_object->get(vm.names.join); + if (vm.exception()) + return {}; + if (!join_function.is_function()) + return ObjectPrototype::to_string(vm, global_object); + return vm.call(join_function.as_function(), this_object); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_locale_string) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + + if (s_array_join_seen_objects.contains(this_object)) + return js_string(vm, ""); + s_array_join_seen_objects.set(this_object); + ArmedScopeGuard unsee_object_guard = [&] { + s_array_join_seen_objects.remove(this_object); + }; + + auto length = length_of_array_like(global_object, *this_object); + if (vm.exception()) + return {}; + + String separator = ","; // NOTE: This is implementation-specific. + StringBuilder builder; + for (size_t i = 0; i < length; ++i) { + if (i > 0) + builder.append(separator); + auto value = this_object->get(i).value_or(js_undefined()); + if (vm.exception()) + return {}; + if (value.is_nullish()) + continue; + auto* value_object = value.to_object(global_object); + if (!value_object) + return {}; + auto locale_string_result = value_object->invoke("toLocaleString"); + if (vm.exception()) + return {}; + auto string = locale_string_result.to_string(global_object); + if (vm.exception()) + return {}; + builder.append(string); + } + return js_string(vm, builder.to_string()); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::join) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + + // This is not part of the spec, but all major engines do some kind of circular reference checks. + // FWIW: engine262, a "100% spec compliant" ECMA-262 impl, aborts with "too much recursion". + // Same applies to Array.prototype.toLocaleString(). + if (s_array_join_seen_objects.contains(this_object)) + return js_string(vm, ""); + s_array_join_seen_objects.set(this_object); + ArmedScopeGuard unsee_object_guard = [&] { + s_array_join_seen_objects.remove(this_object); + }; + + auto length = length_of_array_like(global_object, *this_object); + if (vm.exception()) + return {}; + String separator = ","; + if (!vm.argument(0).is_undefined()) { + separator = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + } + StringBuilder builder; + for (size_t i = 0; i < length; ++i) { + if (i > 0) + builder.append(separator); + auto value = this_object->get(i).value_or(js_undefined()); + if (vm.exception()) + return {}; + if (value.is_nullish()) + continue; + auto string = value.to_string(global_object); + if (vm.exception()) + return {}; + builder.append(string); + } + + return js_string(vm, builder.to_string()); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::concat) +{ + auto* array = Array::typed_this(vm, global_object); + if (!array) + return {}; + + auto* new_array = Array::create(global_object); + new_array->indexed_properties().append_all(array, array->indexed_properties()); + if (vm.exception()) + return {}; + + for (size_t i = 0; i < vm.argument_count(); ++i) { + auto argument = vm.argument(i); + if (argument.is_array()) { + auto& argument_object = argument.as_object(); + new_array->indexed_properties().append_all(&argument_object, argument_object.indexed_properties()); + if (vm.exception()) + return {}; + } else { + new_array->indexed_properties().append(argument); + } + } + + return Value(new_array); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::slice) +{ + auto* array = Array::typed_this(vm, global_object); + if (!array) + return {}; + + auto* new_array = Array::create(global_object); + if (vm.argument_count() == 0) { + new_array->indexed_properties().append_all(array, array->indexed_properties()); + if (vm.exception()) + return {}; + return new_array; + } + + ssize_t array_size = static_cast<ssize_t>(array->indexed_properties().array_like_size()); + auto start_slice = vm.argument(0).to_i32(global_object); + if (vm.exception()) + return {}; + auto end_slice = array_size; + + if (start_slice > array_size) + return new_array; + + if (start_slice < 0) + start_slice = end_slice + start_slice; + + if (vm.argument_count() >= 2) { + end_slice = vm.argument(1).to_i32(global_object); + if (vm.exception()) + return {}; + if (end_slice < 0) + end_slice = array_size + end_slice; + else if (end_slice > array_size) + end_slice = array_size; + } + + for (ssize_t i = start_slice; i < end_slice; ++i) { + new_array->indexed_properties().append(array->get(i)); + if (vm.exception()) + return {}; + } + + return new_array; +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::index_of) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + i32 length = length_of_array_like(global_object, *this_object); + if (vm.exception()) + return {}; + if (length == 0) + return Value(-1); + i32 from_index = 0; + if (vm.argument_count() >= 2) { + from_index = vm.argument(1).to_i32(global_object); + if (vm.exception()) + return {}; + if (from_index >= length) + return Value(-1); + if (from_index < 0) + from_index = max(length + from_index, 0); + } + auto search_element = vm.argument(0); + for (i32 i = from_index; i < length; ++i) { + auto element = this_object->get(i); + if (vm.exception()) + return {}; + if (strict_eq(element, search_element)) + return Value(i); + } + return Value(-1); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::reduce) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + + auto initial_length = length_of_array_like(global_object, *this_object); + if (vm.exception()) + return {}; + + auto* callback_function = callback_from_args(global_object, "reduce"); + if (!callback_function) + return {}; + + size_t start = 0; + + auto accumulator = js_undefined(); + if (vm.argument_count() > 1) { + accumulator = vm.argument(1); + } else { + bool start_found = false; + while (!start_found && start < initial_length) { + auto value = this_object->get(start); + if (vm.exception()) + return {}; + start_found = !value.is_empty(); + if (start_found) + accumulator = value; + start += 1; + } + if (!start_found) { + vm.throw_exception<TypeError>(global_object, ErrorType::ReduceNoInitial); + return {}; + } + } + + auto this_value = js_undefined(); + + for (size_t i = start; i < initial_length; ++i) { + auto value = this_object->get(i); + if (vm.exception()) + return {}; + if (value.is_empty()) + continue; + + accumulator = vm.call(*callback_function, this_value, accumulator, value, Value((i32)i), this_object); + if (vm.exception()) + return {}; + } + + return accumulator; +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::reduce_right) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + + auto initial_length = length_of_array_like(global_object, *this_object); + if (vm.exception()) + return {}; + + auto* callback_function = callback_from_args(global_object, "reduceRight"); + if (!callback_function) + return {}; + + int start = initial_length - 1; + + auto accumulator = js_undefined(); + if (vm.argument_count() > 1) { + accumulator = vm.argument(1); + } else { + bool start_found = false; + while (!start_found && start >= 0) { + auto value = this_object->get(start); + if (vm.exception()) + return {}; + start_found = !value.is_empty(); + if (start_found) + accumulator = value; + start -= 1; + } + if (!start_found) { + vm.throw_exception<TypeError>(global_object, ErrorType::ReduceNoInitial); + return {}; + } + } + + auto this_value = js_undefined(); + + for (int i = start; i >= 0; --i) { + auto value = this_object->get(i); + if (vm.exception()) + return {}; + if (value.is_empty()) + continue; + + accumulator = vm.call(*callback_function, this_value, accumulator, value, Value((i32)i), this_object); + if (vm.exception()) + return {}; + } + + return accumulator; +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::reverse) +{ + auto* array = Array::typed_this(vm, global_object); + if (!array) + return {}; + + if (array->indexed_properties().is_empty()) + return array; + + MarkedValueList array_reverse(vm.heap()); + auto size = array->indexed_properties().array_like_size(); + array_reverse.ensure_capacity(size); + + for (ssize_t i = size - 1; i >= 0; --i) { + array_reverse.append(array->get(i)); + if (vm.exception()) + return {}; + } + + array->set_indexed_property_elements(move(array_reverse)); + + return array; +} + +static void array_merge_sort(VM& vm, GlobalObject& global_object, Function* compare_func, MarkedValueList& arr_to_sort) +{ + // FIXME: it would probably be better to switch to insertion sort for small arrays for + // better performance + if (arr_to_sort.size() <= 1) + return; + + MarkedValueList left(vm.heap()); + MarkedValueList right(vm.heap()); + + left.ensure_capacity(arr_to_sort.size() / 2); + right.ensure_capacity(arr_to_sort.size() / 2 + (arr_to_sort.size() & 1)); + + for (size_t i = 0; i < arr_to_sort.size(); ++i) { + if (i < arr_to_sort.size() / 2) { + left.append(arr_to_sort[i]); + } else { + right.append(arr_to_sort[i]); + } + } + + array_merge_sort(vm, global_object, compare_func, left); + if (vm.exception()) + return; + array_merge_sort(vm, global_object, compare_func, right); + if (vm.exception()) + return; + + arr_to_sort.clear(); + + size_t left_index = 0, right_index = 0; + + while (left_index < left.size() && right_index < right.size()) { + auto x = left[left_index]; + auto y = right[right_index]; + + double comparison_result; + + if (x.is_undefined() && y.is_undefined()) { + comparison_result = 0; + } else if (x.is_undefined()) { + comparison_result = 1; + } else if (y.is_undefined()) { + comparison_result = -1; + } else if (compare_func) { + auto call_result = vm.call(*compare_func, js_undefined(), left[left_index], right[right_index]); + if (vm.exception()) + return; + + if (call_result.is_nan()) { + comparison_result = 0; + } else { + comparison_result = call_result.to_double(global_object); + if (vm.exception()) + return; + } + } else { + // FIXME: It would probably be much better to be smarter about this and implement + // the Abstract Relational Comparison in line once iterating over code points, rather + // than calling it twice after creating two primitive strings. + + auto x_string = x.to_primitive_string(global_object); + if (vm.exception()) + return; + auto y_string = y.to_primitive_string(global_object); + if (vm.exception()) + return; + + auto x_string_value = Value(x_string); + auto y_string_value = Value(y_string); + + // Because they are called with primitive strings, these abstract_relation calls + // should never result in a VM exception. + auto x_lt_y_relation = abstract_relation(global_object, true, x_string_value, y_string_value); + ASSERT(x_lt_y_relation != TriState::Unknown); + auto y_lt_x_relation = abstract_relation(global_object, true, y_string_value, x_string_value); + ASSERT(y_lt_x_relation != TriState::Unknown); + + if (x_lt_y_relation == TriState::True) { + comparison_result = -1; + } else if (y_lt_x_relation == TriState::True) { + comparison_result = 1; + } else { + comparison_result = 0; + } + } + + if (comparison_result <= 0) { + arr_to_sort.append(left[left_index]); + left_index++; + } else { + arr_to_sort.append(right[right_index]); + right_index++; + } + } + + while (left_index < left.size()) { + arr_to_sort.append(left[left_index]); + left_index++; + } + + while (right_index < right.size()) { + arr_to_sort.append(right[right_index]); + right_index++; + } +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::sort) +{ + auto* array = vm.this_value(global_object).to_object(global_object); + if (vm.exception()) + return {}; + + auto callback = vm.argument(0); + if (!callback.is_undefined() && !callback.is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback.to_string_without_side_effects()); + return {}; + } + + auto original_length = length_of_array_like(global_object, *array); + if (vm.exception()) + return {}; + + MarkedValueList values_to_sort(vm.heap()); + + for (size_t i = 0; i < original_length; ++i) { + auto element_val = array->get(i); + if (vm.exception()) + return {}; + + if (!element_val.is_empty()) + values_to_sort.append(element_val); + } + + // Perform sorting by merge sort. This isn't as efficient compared to quick sort, but + // quicksort can't be used in all cases because the spec requires Array.prototype.sort() + // to be stable. FIXME: when initially scanning through the array, maintain a flag + // for if an unstable sort would be indistinguishable from a stable sort (such as just + // just strings or numbers), and in that case use quick sort instead for better performance. + array_merge_sort(vm, global_object, callback.is_undefined() ? nullptr : &callback.as_function(), values_to_sort); + if (vm.exception()) + return {}; + + for (size_t i = 0; i < values_to_sort.size(); ++i) { + array->put(i, values_to_sort[i]); + if (vm.exception()) + return {}; + } + + // The empty parts of the array are always sorted to the end, regardless of the + // compare function. FIXME: For performance, a similar process could be used + // for undefined, which are sorted to right before the empty values. + for (size_t i = values_to_sort.size(); i < original_length; ++i) { + array->delete_property(i); + if (vm.exception()) + return {}; + } + + return array; +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::last_index_of) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + i32 length = length_of_array_like(global_object, *this_object); + if (vm.exception()) + return {}; + if (length == 0) + return Value(-1); + i32 from_index = length - 1; + if (vm.argument_count() >= 2) { + from_index = vm.argument(1).to_i32(global_object); + if (vm.exception()) + return {}; + if (from_index >= 0) + from_index = min(from_index, length - 1); + else + from_index = length + from_index; + } + auto search_element = vm.argument(0); + for (i32 i = from_index; i >= 0; --i) { + auto element = this_object->get(i); + if (vm.exception()) + return {}; + if (strict_eq(element, search_element)) + return Value(i); + } + return Value(-1); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::includes) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + i32 length = length_of_array_like(global_object, *this_object); + if (vm.exception()) + return {}; + if (length == 0) + return Value(false); + i32 from_index = 0; + if (vm.argument_count() >= 2) { + from_index = vm.argument(1).to_i32(global_object); + if (vm.exception()) + return {}; + if (from_index >= length) + return Value(false); + if (from_index < 0) + from_index = max(length + from_index, 0); + } + auto value_to_find = vm.argument(0); + for (i32 i = from_index; i < length; ++i) { + auto element = this_object->get(i).value_or(js_undefined()); + if (vm.exception()) + return {}; + if (same_value_zero(element, value_to_find)) + return Value(true); + } + return Value(false); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::find) +{ + auto result = js_undefined(); + for_each_item( + vm, global_object, "find", [&](auto, auto value, auto callback_result) { + if (callback_result.to_boolean()) { + result = value; + return IterationDecision::Break; + } + return IterationDecision::Continue; + }, + false); + return result; +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::find_index) +{ + auto result_index = -1; + for_each_item( + vm, global_object, "findIndex", [&](auto index, auto, auto callback_result) { + if (callback_result.to_boolean()) { + result_index = index; + return IterationDecision::Break; + } + return IterationDecision::Continue; + }, + false); + return Value(result_index); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::some) +{ + auto result = false; + for_each_item(vm, global_object, "some", [&](auto, auto, auto callback_result) { + if (callback_result.to_boolean()) { + result = true; + return IterationDecision::Break; + } + return IterationDecision::Continue; + }); + return Value(result); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::every) +{ + auto result = true; + for_each_item(vm, global_object, "every", [&](auto, auto, auto callback_result) { + if (!callback_result.to_boolean()) { + result = false; + return IterationDecision::Break; + } + return IterationDecision::Continue; + }); + return Value(result); +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::splice) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + + auto initial_length = length_of_array_like(global_object, *this_object); + if (vm.exception()) + return {}; + + auto relative_start = vm.argument(0).to_i32(global_object); + if (vm.exception()) + return {}; + + size_t actual_start; + + if (relative_start < 0) + actual_start = max((ssize_t)initial_length + relative_start, (ssize_t)0); + else + actual_start = min((size_t)relative_start, initial_length); + + size_t insert_count = 0; + size_t actual_delete_count = 0; + + if (vm.argument_count() == 1) { + actual_delete_count = initial_length - actual_start; + } else if (vm.argument_count() >= 2) { + insert_count = vm.argument_count() - 2; + i32 delete_count = vm.argument(1).to_i32(global_object); + if (vm.exception()) + return {}; + + actual_delete_count = min((size_t)max(delete_count, 0), initial_length - actual_start); + } + + size_t new_length = initial_length + insert_count - actual_delete_count; + + if (new_length > MAX_ARRAY_LIKE_INDEX) { + vm.throw_exception<TypeError>(global_object, ErrorType::ArrayMaxSize); + return {}; + } + + auto removed_elements = Array::create(global_object); + + for (size_t i = 0; i < actual_delete_count; ++i) { + auto value = this_object->get(actual_start + i); + if (vm.exception()) + return {}; + + removed_elements->indexed_properties().append(value); + } + + if (insert_count < actual_delete_count) { + for (size_t i = actual_start; i < initial_length - actual_delete_count; ++i) { + auto from = this_object->get(i + actual_delete_count); + if (vm.exception()) + return {}; + + auto to = i + insert_count; + + if (!from.is_empty()) { + this_object->put(to, from); + } else { + this_object->delete_property(to); + } + if (vm.exception()) + return {}; + } + + for (size_t i = initial_length; i > new_length; --i) { + this_object->delete_property(i - 1); + if (vm.exception()) + return {}; + } + } else if (insert_count > actual_delete_count) { + for (size_t i = initial_length - actual_delete_count; i > actual_start; --i) { + auto from = this_object->get(i + actual_delete_count - 1); + if (vm.exception()) + return {}; + + auto to = i + insert_count - 1; + + if (!from.is_empty()) { + this_object->put(to, from); + } else { + this_object->delete_property(to); + } + if (vm.exception()) + return {}; + } + } + + for (size_t i = 0; i < insert_count; ++i) { + this_object->put(actual_start + i, vm.argument(i + 2)); + if (vm.exception()) + return {}; + } + + this_object->put(vm.names.length, Value((i32)new_length)); + if (vm.exception()) + return {}; + + return removed_elements; +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::fill) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + + ssize_t length = length_of_array_like(global_object, *this_object); + if (vm.exception()) + return {}; + + ssize_t relative_start = 0; + ssize_t relative_end = length; + + if (vm.argument_count() >= 2) { + relative_start = vm.argument(1).to_i32(global_object); + if (vm.exception()) + return {}; + } + + if (vm.argument_count() >= 3) { + relative_end = vm.argument(2).to_i32(global_object); + if (vm.exception()) + return {}; + } + + size_t from, to; + + if (relative_start < 0) + from = max(length + relative_start, 0L); + else + from = min(relative_start, length); + + if (relative_end < 0) + to = max(length + relative_end, 0L); + else + to = min(relative_end, length); + + for (size_t i = from; i < to; i++) { + this_object->put(i, vm.argument(0)); + if (vm.exception()) + return {}; + } + + return this_object; +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::values) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + + return ArrayIterator::create(global_object, this_object, Object::PropertyKind::Value); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h new file mode 100644 index 0000000000..bbdb82903e --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class ArrayPrototype final : public Object { + JS_OBJECT(ArrayPrototype, Object); + +public: + ArrayPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~ArrayPrototype() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(filter); + JS_DECLARE_NATIVE_FUNCTION(for_each); + JS_DECLARE_NATIVE_FUNCTION(map); + JS_DECLARE_NATIVE_FUNCTION(pop); + JS_DECLARE_NATIVE_FUNCTION(push); + JS_DECLARE_NATIVE_FUNCTION(shift); + JS_DECLARE_NATIVE_FUNCTION(to_string); + JS_DECLARE_NATIVE_FUNCTION(to_locale_string); + JS_DECLARE_NATIVE_FUNCTION(unshift); + JS_DECLARE_NATIVE_FUNCTION(join); + JS_DECLARE_NATIVE_FUNCTION(concat); + JS_DECLARE_NATIVE_FUNCTION(slice); + JS_DECLARE_NATIVE_FUNCTION(index_of); + JS_DECLARE_NATIVE_FUNCTION(reduce); + JS_DECLARE_NATIVE_FUNCTION(reduce_right); + JS_DECLARE_NATIVE_FUNCTION(reverse); + JS_DECLARE_NATIVE_FUNCTION(sort); + JS_DECLARE_NATIVE_FUNCTION(last_index_of); + JS_DECLARE_NATIVE_FUNCTION(includes); + JS_DECLARE_NATIVE_FUNCTION(find); + JS_DECLARE_NATIVE_FUNCTION(find_index); + JS_DECLARE_NATIVE_FUNCTION(some); + JS_DECLARE_NATIVE_FUNCTION(every); + JS_DECLARE_NATIVE_FUNCTION(splice); + JS_DECLARE_NATIVE_FUNCTION(fill); + JS_DECLARE_NATIVE_FUNCTION(values); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/BigInt.cpp b/Userland/Libraries/LibJS/Runtime/BigInt.cpp new file mode 100644 index 0000000000..59a0951ff7 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/BigInt.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibCrypto/BigInt/SignedBigInteger.h> +#include <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/BigInt.h> + +namespace JS { + +BigInt::BigInt(Crypto::SignedBigInteger big_integer) + : m_big_integer(move(big_integer)) +{ + ASSERT(!m_big_integer.is_invalid()); +} + +BigInt::~BigInt() +{ +} + +BigInt* js_bigint(Heap& heap, Crypto::SignedBigInteger big_integer) +{ + return heap.allocate_without_global_object<BigInt>(move(big_integer)); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/BigInt.h b/Userland/Libraries/LibJS/Runtime/BigInt.h new file mode 100644 index 0000000000..09a700c4b0 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/BigInt.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibCrypto/BigInt/SignedBigInteger.h> +#include <LibJS/Runtime/Cell.h> + +namespace JS { + +class BigInt final : public Cell { +public: + BigInt(Crypto::SignedBigInteger); + virtual ~BigInt(); + + const Crypto::SignedBigInteger& big_integer() const { return m_big_integer; } + const String to_string() const { return String::formatted("{}n", m_big_integer.to_base10()); } + +private: + virtual const char* class_name() const override { return "BigInt"; } + + Crypto::SignedBigInteger m_big_integer; +}; + +BigInt* js_bigint(Heap&, Crypto::SignedBigInteger); + +} diff --git a/Userland/Libraries/LibJS/Runtime/BigIntConstructor.cpp b/Userland/Libraries/LibJS/Runtime/BigIntConstructor.cpp new file mode 100644 index 0000000000..5eec127b63 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/BigIntConstructor.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <AK/String.h> +#include <LibCrypto/BigInt/SignedBigInteger.h> +#include <LibJS/Runtime/BigIntConstructor.h> +#include <LibJS/Runtime/BigIntObject.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/VM.h> + +namespace JS { + +BigIntConstructor::BigIntConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.BigInt, *global_object.function_prototype()) +{ +} + +void BigIntConstructor::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + NativeFunction::initialize(global_object); + define_property(vm.names.prototype, global_object.bigint_prototype(), 0); + define_property(vm.names.length, Value(1), Attribute::Configurable); + + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.asIntN, as_int_n, 2, attr); + define_native_function(vm.names.asUintN, as_uint_n, 2, attr); +} + +BigIntConstructor::~BigIntConstructor() +{ +} + +Value BigIntConstructor::call() +{ + auto primitive = vm().argument(0).to_primitive(Value::PreferredType::Number); + if (vm().exception()) + return {}; + if (primitive.is_number()) { + if (!primitive.is_integer()) { + vm().throw_exception<RangeError>(global_object(), ErrorType::BigIntIntArgument); + return {}; + } + return js_bigint(heap(), Crypto::SignedBigInteger { primitive.as_i32() }); + } + auto* bigint = vm().argument(0).to_bigint(global_object()); + if (vm().exception()) + return {}; + return bigint; +} + +Value BigIntConstructor::construct(Function&) +{ + vm().throw_exception<TypeError>(global_object(), ErrorType::NotAConstructor, "BigInt"); + return {}; +} + +JS_DEFINE_NATIVE_FUNCTION(BigIntConstructor::as_int_n) +{ + TODO(); +} + +JS_DEFINE_NATIVE_FUNCTION(BigIntConstructor::as_uint_n) +{ + TODO(); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/BigIntConstructor.h b/Userland/Libraries/LibJS/Runtime/BigIntConstructor.h new file mode 100644 index 0000000000..5a4fc6bef7 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/BigIntConstructor.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/NativeFunction.h> + +namespace JS { + +class BigIntConstructor final : public NativeFunction { + JS_OBJECT(BigIntConstructor, NativeFunction); + +public: + explicit BigIntConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~BigIntConstructor() override; + + virtual Value call() override; + virtual Value construct(Function& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } + + JS_DECLARE_NATIVE_FUNCTION(as_int_n); + JS_DECLARE_NATIVE_FUNCTION(as_uint_n); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/BigIntObject.cpp b/Userland/Libraries/LibJS/Runtime/BigIntObject.cpp new file mode 100644 index 0000000000..eea70af64e --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/BigIntObject.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/BigIntObject.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace JS { + +BigIntObject* BigIntObject::create(GlobalObject& global_object, BigInt& bigint) +{ + return global_object.heap().allocate<BigIntObject>(global_object, bigint, *global_object.bigint_prototype()); +} + +BigIntObject::BigIntObject(BigInt& bigint, Object& prototype) + : Object(prototype) + , m_bigint(bigint) +{ +} + +BigIntObject::~BigIntObject() +{ +} + +void BigIntObject::visit_edges(Cell::Visitor& visitor) +{ + Object::visit_edges(visitor); + visitor.visit(&m_bigint); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/BigIntObject.h b/Userland/Libraries/LibJS/Runtime/BigIntObject.h new file mode 100644 index 0000000000..f58183e4c3 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/BigIntObject.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/BigInt.h> +#include <LibJS/Runtime/Object.h> + +namespace JS { + +class BigIntObject final : public Object { + JS_OBJECT(BigIntObject, Object); + +public: + static BigIntObject* create(GlobalObject&, BigInt&); + + BigIntObject(BigInt&, Object& prototype); + virtual ~BigIntObject(); + + const BigInt& bigint() const { return m_bigint; } + virtual Value value_of() const override + { + return Value(&m_bigint); + } + +private: + virtual void visit_edges(Visitor&) override; + + BigInt& m_bigint; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/BigIntPrototype.cpp b/Userland/Libraries/LibJS/Runtime/BigIntPrototype.cpp new file mode 100644 index 0000000000..84b6617fe5 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/BigIntPrototype.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <AK/Function.h> +#include <LibJS/Runtime/BigIntObject.h> +#include <LibJS/Runtime/BigIntPrototype.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace JS { + +BigIntPrototype::BigIntPrototype(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void BigIntPrototype::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.toString, to_string, 0, attr); + define_native_function(vm.names.toLocaleString, to_locale_string, 0, attr); + define_native_function(vm.names.valueOf, value_of, 0, attr); + + define_property(vm.well_known_symbol_to_string_tag(), js_string(global_object.heap(), "BigInt"), Attribute::Configurable); +} + +BigIntPrototype::~BigIntPrototype() +{ +} + +static BigIntObject* bigint_object_from(VM& vm, GlobalObject& global_object) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return nullptr; + if (!is<BigIntObject>(this_object)) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "BigInt"); + return nullptr; + } + return static_cast<BigIntObject*>(this_object); +} + +JS_DEFINE_NATIVE_FUNCTION(BigIntPrototype::to_string) +{ + auto* bigint_object = bigint_object_from(vm, global_object); + if (!bigint_object) + return {}; + return js_string(vm, bigint_object->bigint().big_integer().to_base10()); +} + +JS_DEFINE_NATIVE_FUNCTION(BigIntPrototype::to_locale_string) +{ + return to_string(vm, global_object); +} + +JS_DEFINE_NATIVE_FUNCTION(BigIntPrototype::value_of) +{ + auto* bigint_object = bigint_object_from(vm, global_object); + if (!bigint_object) + return {}; + return bigint_object->value_of(); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/BigIntPrototype.h b/Userland/Libraries/LibJS/Runtime/BigIntPrototype.h new file mode 100644 index 0000000000..c0e54a7c2a --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/BigIntPrototype.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class BigIntPrototype final : public Object { + JS_OBJECT(BigIntPrototype, Object); + +public: + explicit BigIntPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~BigIntPrototype() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(to_string); + JS_DECLARE_NATIVE_FUNCTION(to_locale_string); + JS_DECLARE_NATIVE_FUNCTION(value_of); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/BooleanConstructor.cpp b/Userland/Libraries/LibJS/Runtime/BooleanConstructor.cpp new file mode 100644 index 0000000000..33e71c238d --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/BooleanConstructor.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020, Jack Karamanian <karamanian.jack@gmail.com> + * 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 <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/BooleanConstructor.h> +#include <LibJS/Runtime/BooleanObject.h> +#include <LibJS/Runtime/BooleanPrototype.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace JS { + +BooleanConstructor::BooleanConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.Boolean, *global_object.function_prototype()) +{ +} + +void BooleanConstructor::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + NativeFunction::initialize(global_object); + define_property(vm.names.prototype, Value(global_object.boolean_prototype()), 0); + define_property(vm.names.length, Value(1), Attribute::Configurable); +} + +BooleanConstructor::~BooleanConstructor() +{ +} + +Value BooleanConstructor::call() +{ + return Value(vm().argument(0).to_boolean()); +} + +Value BooleanConstructor::construct(Function&) +{ + return BooleanObject::create(global_object(), vm().argument(0).to_boolean()); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/BooleanConstructor.h b/Userland/Libraries/LibJS/Runtime/BooleanConstructor.h new file mode 100644 index 0000000000..1c68814b21 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/BooleanConstructor.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020, Jack Karamanian <karamanian.jack@gmail.com> + * 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 <LibJS/Runtime/NativeFunction.h> + +namespace JS { + +class BooleanConstructor final : public NativeFunction { + JS_OBJECT(BooleanConstructor, NativeFunction); + +public: + explicit BooleanConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~BooleanConstructor() override; + + virtual Value call() override; + virtual Value construct(Function& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/BooleanObject.cpp b/Userland/Libraries/LibJS/Runtime/BooleanObject.cpp new file mode 100644 index 0000000000..b6a4cef400 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/BooleanObject.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020, Jack Karamanian <karamanian.jack@gmail.com> + * 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 <LibJS/Runtime/BooleanObject.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace JS { + +BooleanObject* BooleanObject::create(GlobalObject& global_object, bool value) +{ + return global_object.heap().allocate<BooleanObject>(global_object, value, *global_object.boolean_prototype()); +} + +BooleanObject::BooleanObject(bool value, Object& prototype) + : Object(prototype) + , m_value(value) +{ +} + +BooleanObject::~BooleanObject() +{ +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/BooleanObject.h b/Userland/Libraries/LibJS/Runtime/BooleanObject.h new file mode 100644 index 0000000000..4b9775205a --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/BooleanObject.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020, Jack Karamanian <karamanian.jack@gmail.com> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { +class BooleanObject : public Object { + JS_OBJECT(BooleanObject, Object); + +public: + static BooleanObject* create(GlobalObject&, bool); + + BooleanObject(bool, Object& prototype); + virtual ~BooleanObject() override; + + virtual Value value_of() const override + { + return Value(m_value); + } + +private: + bool m_value { false }; +}; +} diff --git a/Userland/Libraries/LibJS/Runtime/BooleanPrototype.cpp b/Userland/Libraries/LibJS/Runtime/BooleanPrototype.cpp new file mode 100644 index 0000000000..dd69ff57dc --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/BooleanPrototype.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020, Jack Karamanian <karamanian.jack@gmail.com> + * 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 <AK/Function.h> +#include <LibJS/Runtime/BooleanPrototype.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace JS { + +BooleanPrototype::BooleanPrototype(GlobalObject& global_object) + : BooleanObject(false, *global_object.object_prototype()) +{ +} + +void BooleanPrototype::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + BooleanObject::initialize(global_object); + define_native_function(vm.names.toString, to_string, 0, Attribute::Writable | Attribute::Configurable); + define_native_function(vm.names.valueOf, value_of, 0, Attribute::Writable | Attribute::Configurable); +} + +BooleanPrototype::~BooleanPrototype() +{ +} + +JS_DEFINE_NATIVE_FUNCTION(BooleanPrototype::to_string) +{ + auto this_value = vm.this_value(global_object); + if (this_value.is_boolean()) + return js_string(vm, this_value.as_bool() ? "true" : "false"); + if (!this_value.is_object() || !is<BooleanObject>(this_value.as_object())) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Boolean"); + return {}; + } + + bool bool_value = static_cast<const BooleanObject&>(this_value.as_object()).value_of().as_bool(); + return js_string(vm, bool_value ? "true" : "false"); +} + +JS_DEFINE_NATIVE_FUNCTION(BooleanPrototype::value_of) +{ + auto this_value = vm.this_value(global_object); + if (this_value.is_boolean()) + return this_value; + if (!this_value.is_object() || !is<BooleanObject>(this_value.as_object())) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Boolean"); + return {}; + } + + return static_cast<const BooleanObject&>(this_value.as_object()).value_of(); +} +} diff --git a/Userland/Libraries/LibJS/Runtime/BooleanPrototype.h b/Userland/Libraries/LibJS/Runtime/BooleanPrototype.h new file mode 100644 index 0000000000..10b46f4efb --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/BooleanPrototype.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020, Jack Karamanian <karamanian.jack@gmail.com> + * 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 <LibJS/Runtime/BooleanObject.h> + +namespace JS { + +class BooleanPrototype final : public BooleanObject { + JS_OBJECT(BooleanPrototype, BooleanObject); + +public: + explicit BooleanPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~BooleanPrototype() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(to_string); + JS_DECLARE_NATIVE_FUNCTION(value_of); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp b/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp new file mode 100644 index 0000000000..891eebc57b --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2020, Jack Karamanian <karamanian.jack@gmail.com> + * 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 <LibJS/Runtime/BoundFunction.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace JS { + +BoundFunction::BoundFunction(GlobalObject& global_object, Function& target_function, Value bound_this, Vector<Value> arguments, i32 length, Object* constructor_prototype) + : Function::Function(*global_object.function_prototype(), bound_this, move(arguments)) + , m_target_function(&target_function) + , m_constructor_prototype(constructor_prototype) + , m_name(String::formatted("bound {}", target_function.name())) + , m_length(length) +{ +} + +void BoundFunction::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Function::initialize(global_object); + define_property(vm.names.length, Value(m_length), Attribute::Configurable); +} + +BoundFunction::~BoundFunction() +{ +} + +Value BoundFunction::call() +{ + return m_target_function->call(); +} + +Value BoundFunction::construct(Function& new_target) +{ + if (auto this_value = vm().this_value(global_object()); m_constructor_prototype && this_value.is_object()) { + this_value.as_object().set_prototype(m_constructor_prototype); + if (vm().exception()) + return {}; + } + return m_target_function->construct(new_target); +} + +LexicalEnvironment* BoundFunction::create_environment() +{ + return m_target_function->create_environment(); +} + +void BoundFunction::visit_edges(Visitor& visitor) +{ + Function::visit_edges(visitor); + visitor.visit(m_target_function); + visitor.visit(m_constructor_prototype); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/BoundFunction.h b/Userland/Libraries/LibJS/Runtime/BoundFunction.h new file mode 100644 index 0000000000..8d4c3a443c --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/BoundFunction.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020, Jack Karamanian <karamanian.jack@gmail.com> + * 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 <LibJS/Runtime/Function.h> + +namespace JS { + +class BoundFunction final : public Function { + JS_OBJECT(BoundFunction, Function); + +public: + BoundFunction(GlobalObject&, Function& target_function, Value bound_this, Vector<Value> arguments, i32 length, Object* constructor_prototype); + virtual void initialize(GlobalObject&) override; + virtual ~BoundFunction(); + + virtual Value call() override; + + virtual Value construct(Function& new_target) override; + + virtual LexicalEnvironment* create_environment() override; + + virtual void visit_edges(Visitor&) override; + + virtual const FlyString& name() const override + { + return m_name; + } + + Function& target_function() const + { + return *m_target_function; + } + + virtual bool is_strict_mode() const override { return m_target_function->is_strict_mode(); } + +private: + Function* m_target_function = nullptr; + Object* m_constructor_prototype = nullptr; + FlyString m_name; + i32 m_length { 0 }; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Cell.cpp b/Userland/Libraries/LibJS/Runtime/Cell.cpp new file mode 100644 index 0000000000..22a9979a35 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Cell.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Heap/Heap.h> +#include <LibJS/Heap/HeapBlock.h> +#include <LibJS/Runtime/Cell.h> +#include <LibJS/Runtime/Object.h> +#include <LibJS/Runtime/PrimitiveString.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +void Cell::Visitor::visit(Cell* cell) +{ + if (cell) + visit_impl(cell); +} + +void Cell::Visitor::visit(Value value) +{ + if (value.is_cell()) + visit_impl(value.as_cell()); +} + +Heap& Cell::heap() const +{ + return HeapBlock::from_cell(this)->heap(); +} + +VM& Cell::vm() const +{ + return heap().vm(); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Cell.h b/Userland/Libraries/LibJS/Runtime/Cell.h new file mode 100644 index 0000000000..df4a19f19a --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Cell.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/Format.h> +#include <AK/Forward.h> +#include <AK/Noncopyable.h> +#include <AK/String.h> +#include <AK/TypeCasts.h> +#include <LibJS/Forward.h> + +namespace JS { + +class Cell { + AK_MAKE_NONCOPYABLE(Cell); + AK_MAKE_NONMOVABLE(Cell); + +public: + virtual void initialize(GlobalObject&) { } + virtual ~Cell() { } + + bool is_marked() const { return m_mark; } + void set_marked(bool b) { m_mark = b; } + + bool is_live() const { return m_live; } + void set_live(bool b) { m_live = b; } + + virtual const char* class_name() const = 0; + + class Visitor { + public: + void visit(Cell*); + void visit(Value); + + protected: + virtual void visit_impl(Cell*) = 0; + }; + + virtual void visit_edges(Visitor&) { } + + Heap& heap() const; + VM& vm() const; + +protected: + Cell() { } + +private: + bool m_mark { false }; + bool m_live { true }; +}; + +} + +template<> +struct AK::Formatter<JS::Cell> : AK::Formatter<FormatString> { + void format(FormatBuilder& builder, const JS::Cell* cell) + { + if (!cell) + Formatter<FormatString>::format(builder, "Cell{nullptr}"); + else + Formatter<FormatString>::format(builder, "{}({})", cell->class_name(), cell); + } +}; diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h new file mode 100644 index 0000000000..2d1d4d1d2e --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/FlyString.h> +#include <LibJS/Forward.h> + +namespace JS { + +#define ENUMERATE_STANDARD_PROPERTY_NAMES(P) \ + P(BYTES_PER_ELEMENT) \ + P(BigInt) \ + P(Boolean) \ + P(E) \ + P(EPSILON) \ + P(Infinity) \ + P(JSON) \ + P(LN10) \ + P(LN2) \ + P(LOG10E) \ + P(LOG2E) \ + P(MAX_SAFE_INTEGER) \ + P(MIN_SAFE_INTEGER) \ + P(Math) \ + P(NEGATIVE_INFINITY) \ + P(NaN) \ + P(Number) \ + P(PI) \ + P(POSITIVE_INFINITY) \ + P(Proxy) \ + P(Reflect) \ + P(RegExp) \ + P(SQRT1_2) \ + P(SQRT2) \ + P(String) \ + P(Symbol) \ + P(UTC) \ + P(abs) \ + P(acos) \ + P(acosh) \ + P(apply) \ + P(arguments) \ + P(asIntN) \ + P(asUintN) \ + P(asin) \ + P(asinh) \ + P(atan) \ + P(atan2) \ + P(atanh) \ + P(bind) \ + P(byteLength) \ + P(call) \ + P(cbrt) \ + P(ceil) \ + P(charAt) \ + P(charCodeAt) \ + P(clear) \ + P(clz32) \ + P(concat) \ + P(configurable) \ + P(console) \ + P(construct) \ + P(constructor) \ + P(cos) \ + P(cosh) \ + P(count) \ + P(countReset) \ + P(debug) \ + P(defineProperty) \ + P(deleteProperty) \ + P(description) \ + P(done) \ + P(dotAll) \ + P(endsWith) \ + P(entries) \ + P(enumerable) \ + P(error) \ + P(every) \ + P(exec) \ + P(exp) \ + P(expm1) \ + P(fill) \ + P(filter) \ + P(find) \ + P(findIndex) \ + P(flags) \ + P(floor) \ + P(forEach) \ + P(from) \ + P(fromCharCode) \ + P(fround) \ + P(gc) \ + P(get) \ + P(getDate) \ + P(getDay) \ + P(getFullYear) \ + P(getHours) \ + P(getMilliseconds) \ + P(getMinutes) \ + P(getMonth) \ + P(getOwnPropertyDescriptor) \ + P(getOwnPropertyNames) \ + P(getPrototypeOf) \ + P(getSeconds) \ + P(getTime) \ + P(getUTCDate) \ + P(getUTCDay) \ + P(getUTCFullYear) \ + P(getUTCHours) \ + P(getUTCMilliseconds) \ + P(getUTCMinutes) \ + P(getUTCMonth) \ + P(getUTCSeconds) \ + P(global) \ + P(globalThis) \ + P(groups) \ + P(has) \ + P(hasOwnProperty) \ + P(hypot) \ + P(ignoreCase) \ + P(imul) \ + P(includes) \ + P(index) \ + P(indexOf) \ + P(info) \ + P(input) \ + P(is) \ + P(isArray) \ + P(isExtensible) \ + P(isFinite) \ + P(isInteger) \ + P(isNaN) \ + P(isPrototypeOf) \ + P(isSafeInteger) \ + P(isView) \ + P(join) \ + P(keyFor) \ + P(keys) \ + P(lastIndex) \ + P(lastIndexOf) \ + P(length) \ + P(log) \ + P(log1p) \ + P(log2) \ + P(log10) \ + P(map) \ + P(max) \ + P(message) \ + P(min) \ + P(multiline) \ + P(name) \ + P(next) \ + P(now) \ + P(of) \ + P(ownKeys) \ + P(padEnd) \ + P(padStart) \ + P(parse) \ + P(parseFloat) \ + P(parseInt) \ + P(pop) \ + P(pow) \ + P(preventExtensions) \ + P(propertyIsEnumerable) \ + P(prototype) \ + P(push) \ + P(random) \ + P(raw) \ + P(reduce) \ + P(reduceRight) \ + P(repeat) \ + P(reverse) \ + P(round) \ + P(set) \ + P(setPrototypeOf) \ + P(shift) \ + P(sign) \ + P(sin) \ + P(sinh) \ + P(slice) \ + P(some) \ + P(sort) \ + P(source) \ + P(splice) \ + P(sqrt) \ + P(startsWith) \ + P(sticky) \ + P(stringify) \ + P(substr) \ + P(substring) \ + P(tan) \ + P(tanh) \ + P(test) \ + P(toDateString) \ + P(toISOString) \ + P(toJSON) \ + P(toLocaleDateString) \ + P(toLocaleString) \ + P(toLocaleTimeString) \ + P(toLowerCase) \ + P(toString) \ + P(toTimeString) \ + P(toUpperCase) \ + P(trace) \ + P(trim) \ + P(trimEnd) \ + P(trimStart) \ + P(trunc) \ + P(undefined) \ + P(unicode) \ + P(unshift) \ + P(value) \ + P(valueOf) \ + P(values) \ + P(warn) \ + P(writable) + +struct CommonPropertyNames { + FlyString for_ { "for" }; +#define __ENUMERATE(x) FlyString x { #x }; + ENUMERATE_STANDARD_PROPERTY_NAMES(__ENUMERATE) +#undef __ENUMERATE +#define __JS_ENUMERATE(x, a, b, c, t) FlyString x { #x }; + JS_ENUMERATE_BUILTIN_TYPES +#undef __JS_ENUMERATE +#define __JS_ENUMERATE(x, a) FlyString x { #x }; + JS_ENUMERATE_WELL_KNOWN_SYMBOLS +#undef __JS_ENUMERATE +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/ConsoleObject.cpp b/Userland/Libraries/LibJS/Runtime/ConsoleObject.cpp new file mode 100644 index 0000000000..f87b29c0a1 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ConsoleObject.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * Copyright (c) 2020, Emanuele Torre <torreemanuele6@gmail.com> + * 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 <AK/FlyString.h> +#include <AK/Function.h> +#include <LibJS/Console.h> +#include <LibJS/Runtime/ConsoleObject.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace JS { + +ConsoleObject::ConsoleObject(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void ConsoleObject::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + define_native_function(vm.names.log, log); + define_native_function(vm.names.debug, debug); + define_native_function(vm.names.info, info); + define_native_function(vm.names.warn, warn); + define_native_function(vm.names.error, error); + define_native_function(vm.names.trace, trace); + define_native_function(vm.names.count, count); + define_native_function(vm.names.countReset, count_reset); + define_native_function(vm.names.clear, clear); +} + +ConsoleObject::~ConsoleObject() +{ +} + +JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::log) +{ + return global_object.console().log(); +} + +JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::debug) +{ + return global_object.console().debug(); +} + +JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::info) +{ + return global_object.console().info(); +} + +JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::warn) +{ + return global_object.console().warn(); +} + +JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::error) +{ + return global_object.console().error(); +} + +JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::trace) +{ + return global_object.console().trace(); +} + +JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::count) +{ + return global_object.console().count(); +} + +JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::count_reset) +{ + return global_object.console().count_reset(); +} + +JS_DEFINE_NATIVE_FUNCTION(ConsoleObject::clear) +{ + return global_object.console().clear(); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/ConsoleObject.h b/Userland/Libraries/LibJS/Runtime/ConsoleObject.h new file mode 100644 index 0000000000..07848aa44f --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ConsoleObject.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class ConsoleObject final : public Object { + JS_OBJECT(ConsoleObject, Object); + +public: + explicit ConsoleObject(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~ConsoleObject() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(log); + JS_DECLARE_NATIVE_FUNCTION(debug); + JS_DECLARE_NATIVE_FUNCTION(info); + JS_DECLARE_NATIVE_FUNCTION(warn); + JS_DECLARE_NATIVE_FUNCTION(error); + JS_DECLARE_NATIVE_FUNCTION(trace); + JS_DECLARE_NATIVE_FUNCTION(count); + JS_DECLARE_NATIVE_FUNCTION(count_reset); + JS_DECLARE_NATIVE_FUNCTION(clear); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Date.cpp b/Userland/Libraries/LibJS/Runtime/Date.cpp new file mode 100644 index 0000000000..ced1a58009 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Date.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <AK/StringBuilder.h> +#include <LibCore/DateTime.h> +#include <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/Date.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace JS { + +Date* Date::create(GlobalObject& global_object, Core::DateTime datetime, u16 milliseconds) +{ + return global_object.heap().allocate<Date>(global_object, datetime, milliseconds, *global_object.date_prototype()); +} + +Date::Date(Core::DateTime datetime, u16 milliseconds, Object& prototype) + : Object(prototype) + , m_datetime(datetime) + , m_milliseconds(milliseconds) +{ +} + +Date::~Date() +{ +} + +tm Date::to_utc_tm() const +{ + time_t timestamp = m_datetime.timestamp(); + struct tm tm; + gmtime_r(×tamp, &tm); + return tm; +} + +int Date::utc_date() const +{ + return to_utc_tm().tm_mday; +} + +int Date::utc_day() const +{ + return to_utc_tm().tm_wday; +} + +int Date::utc_full_year() const +{ + return to_utc_tm().tm_year + 1900; +} + +int Date::utc_hours() const +{ + return to_utc_tm().tm_hour; +} + +int Date::utc_minutes() const +{ + return to_utc_tm().tm_min; +} + +int Date::utc_month() const +{ + return to_utc_tm().tm_mon; +} + +int Date::utc_seconds() const +{ + return to_utc_tm().tm_sec; +} + +String Date::iso_date_string() const +{ + auto tm = to_utc_tm(); + int year = tm.tm_year + 1900; + int month = tm.tm_mon + 1; + + StringBuilder builder; + if (year < 0) + builder.appendf("-%06d", -year); + else if (year > 9999) + builder.appendf("+%06d", year); + else + builder.appendf("%04d", year); + builder.append('-'); + builder.appendf("%02d", month); + builder.append('-'); + builder.appendf("%02d", tm.tm_mday); + builder.append('T'); + builder.appendf("%02d", tm.tm_hour); + builder.append(':'); + builder.appendf("%02d", tm.tm_min); + builder.append(':'); + builder.appendf("%02d", tm.tm_sec); + builder.append('.'); + builder.appendf("%03d", m_milliseconds); + builder.append('Z'); + + return builder.build(); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Date.h b/Userland/Libraries/LibJS/Runtime/Date.h new file mode 100644 index 0000000000..368cc91bc2 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Date.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibCore/DateTime.h> +#include <LibJS/Runtime/Object.h> + +namespace JS { + +class Date final : public Object { + JS_OBJECT(Date, Object); + +public: + static Date* create(GlobalObject&, Core::DateTime, u16 milliseconds); + + Date(Core::DateTime datetime, u16 milliseconds, Object& prototype); + virtual ~Date() override; + + Core::DateTime& datetime() { return m_datetime; } + const Core::DateTime& datetime() const { return m_datetime; } + + int date() const { return datetime().day(); } + int day() const { return datetime().weekday(); } + int full_year() const { return datetime().year(); } + int hours() const { return datetime().hour(); } + u16 milliseconds() const { return m_milliseconds; } + int minutes() const { return datetime().minute(); } + int month() const { return datetime().month() - 1; } + int seconds() const { return datetime().second(); } + double time() const { return datetime().timestamp() * 1000.0 + milliseconds(); } + int year() const { return datetime().day(); } + + int utc_date() const; + int utc_day() const; + int utc_full_year() const; + int utc_hours() const; + int utc_milliseconds() const { return milliseconds(); } + int utc_minutes() const; + int utc_month() const; + int utc_seconds() const; + + String date_string() const { return m_datetime.to_string("%a %b %d %Y"); } + // FIXME: Deal with timezones once SerenityOS has a working tzset(3) + String time_string() const { return m_datetime.to_string("%T GMT+0000 (UTC)"); } + String string() const + { + return String::formatted("{} {}", date_string(), time_string()); + } + + String iso_date_string() const; + + // FIXME: One day, implement real locale support. Until then, everyone gets what the Clock MenuApplet displays. + String locale_date_string() const { return m_datetime.to_string("%Y-%m-%d"); } + String locale_string() const { return m_datetime.to_string(); } + String locale_time_string() const { return m_datetime.to_string("%H:%M:%S"); } + + virtual Value value_of() const override + { + return Value(static_cast<double>(m_datetime.timestamp() * 1000 + m_milliseconds)); + } + +private: + tm to_utc_tm() const; + + Core::DateTime m_datetime; + u16 m_milliseconds; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/DateConstructor.cpp b/Userland/Libraries/LibJS/Runtime/DateConstructor.cpp new file mode 100644 index 0000000000..37b4d87865 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/DateConstructor.cpp @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * Copyright (c) 2020, Nico Weber <thakis@chromium.org> + * 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 <AK/GenericLexer.h> +#include <LibCore/DateTime.h> +#include <LibJS/Runtime/Date.h> +#include <LibJS/Runtime/DateConstructor.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/VM.h> +#include <ctype.h> +#include <sys/time.h> +#include <time.h> + +namespace JS { + +static Value parse_simplified_iso8601(const String& iso_8601) +{ + // Date.parse() is allowed to accept many formats. We strictly only accept things matching + // http://www.ecma-international.org/ecma-262/#sec-date-time-string-format + GenericLexer lexer(iso_8601); + auto lex_n_digits = [&](size_t n, int& out) { + if (lexer.tell_remaining() < n) + return false; + int r = 0; + for (size_t i = 0; i < n; ++i) { + char ch = lexer.consume(); + if (!isdigit(ch)) + return false; + r = 10 * r + ch - '0'; + } + out = r; + return true; + }; + + int year = -1, month = -1, day = -1; + int hours = -1, minutes = -1, seconds = -1, milliseconds = -1; + char timezone = -1; + int timezone_hours = -1, timezone_minutes = -1; + auto lex_year = [&]() { + if (lexer.consume_specific('+')) + return lex_n_digits(6, year); + if (lexer.consume_specific('-')) { + int absolute_year; + if (!lex_n_digits(6, absolute_year)) + return false; + year = -absolute_year; + return true; + } + return lex_n_digits(4, year); + }; + auto lex_month = [&]() { return lex_n_digits(2, month) && month >= 1 && month <= 12; }; + auto lex_day = [&]() { return lex_n_digits(2, day) && day >= 1 && day <= 31; }; + auto lex_date = [&]() { return lex_year() && (!lexer.consume_specific('-') || (lex_month() && (!lexer.consume_specific('-') || lex_day()))); }; + + auto lex_hours_minutes = [&](int& out_h, int& out_m) { + int h, m; + if (lex_n_digits(2, h) && h >= 0 && h <= 24 && lexer.consume_specific(':') && lex_n_digits(2, m) && m >= 0 && m <= 59) { + out_h = h; + out_m = m; + return true; + } + return false; + }; + auto lex_seconds = [&]() { return lex_n_digits(2, seconds) && seconds >= 0 && seconds <= 59; }; + auto lex_milliseconds = [&]() { return lex_n_digits(3, milliseconds); }; + auto lex_seconds_milliseconds = [&]() { return lex_seconds() && (!lexer.consume_specific('.') || lex_milliseconds()); }; + auto lex_timezone = [&]() { + if (lexer.consume_specific('+')) { + timezone = '+'; + return lex_hours_minutes(timezone_hours, timezone_minutes); + } + if (lexer.consume_specific('-')) { + timezone = '-'; + return lex_hours_minutes(timezone_hours, timezone_minutes); + } + if (lexer.consume_specific('Z')) + timezone = 'Z'; + return true; + }; + auto lex_time = [&]() { return lex_hours_minutes(hours, minutes) && (!lexer.consume_specific(':') || lex_seconds_milliseconds()) && lex_timezone(); }; + + if (!lex_date() || (lexer.consume_specific('T') && !lex_time()) || !lexer.is_eof()) { + return js_nan(); + } + + // We parsed a valid date simplified ISO 8601 string. Values not present in the string are -1. + ASSERT(year != -1); // A valid date string always has at least a year. + struct tm tm = {}; + tm.tm_year = year - 1900; + tm.tm_mon = month == -1 ? 0 : month - 1; + tm.tm_mday = day == -1 ? 1 : day; + tm.tm_hour = hours == -1 ? 0 : hours; + tm.tm_min = minutes == -1 ? 0 : minutes; + tm.tm_sec = seconds == -1 ? 0 : seconds; + + // http://www.ecma-international.org/ecma-262/#sec-date.parse: + // "When the UTC offset representation is absent, date-only forms are interpreted as a UTC time and date-time forms are interpreted as a local time." + time_t timestamp; + if (timezone != -1 || hours == -1) + timestamp = timegm(&tm); + else + timestamp = mktime(&tm); + + if (timezone == '-') + timestamp += (timezone_hours * 60 + timezone_minutes) * 60; + else if (timezone == '+') + timestamp -= (timezone_hours * 60 + timezone_minutes) * 60; + + // FIXME: reject timestamp if resulting value wouldn't fit in a double + + if (milliseconds == -1) + milliseconds = 0; + return Value(1000.0 * timestamp + milliseconds); +} + +DateConstructor::DateConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.Date, *global_object.function_prototype()) +{ +} + +void DateConstructor::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + NativeFunction::initialize(global_object); + define_property(vm.names.prototype, global_object.date_prototype(), 0); + define_property(vm.names.length, Value(7), Attribute::Configurable); + + define_native_function(vm.names.now, now, 0, Attribute::Writable | Attribute::Configurable); + define_native_function(vm.names.parse, parse, 1, Attribute::Writable | Attribute::Configurable); + define_native_function(vm.names.UTC, utc, 1, Attribute::Writable | Attribute::Configurable); +} + +DateConstructor::~DateConstructor() +{ +} + +Value DateConstructor::call() +{ + auto date = construct(*this); + if (!date.is_object()) + return {}; + return js_string(heap(), static_cast<Date&>(date.as_object()).string()); +} + +Value DateConstructor::construct(Function&) +{ + if (vm().argument_count() == 0) { + struct timeval tv; + gettimeofday(&tv, nullptr); + auto datetime = Core::DateTime::now(); + auto milliseconds = static_cast<u16>(tv.tv_usec / 1000); + return Date::create(global_object(), datetime, milliseconds); + } + if (vm().argument_count() == 1) { + auto value = vm().argument(0); + if (value.is_string()) + value = parse_simplified_iso8601(value.as_string().string()); + // A timestamp since the epoch, in UTC. + // FIXME: Date() probably should use a double as internal representation, so that NaN arguments and larger offsets are handled correctly. + double value_as_double = value.to_double(global_object()); + auto datetime = Core::DateTime::from_timestamp(static_cast<time_t>(value_as_double / 1000)); + auto milliseconds = static_cast<u16>(fmod(value_as_double, 1000)); + return Date::create(global_object(), datetime, milliseconds); + } + // A date/time in components, in local time. + // FIXME: This doesn't construct an "Invalid Date" object if one of the parameters is NaN. + auto arg_or = [this](size_t i, i32 fallback) { return vm().argument_count() > i ? vm().argument(i).to_i32(global_object()) : fallback; }; + int year = vm().argument(0).to_i32(global_object()); + int month_index = vm().argument(1).to_i32(global_object()); + int day = arg_or(2, 1); + int hours = arg_or(3, 0); + int minutes = arg_or(4, 0); + int seconds = arg_or(5, 0); + int milliseconds = arg_or(6, 0); + + seconds += milliseconds / 1000; + milliseconds %= 1000; + if (milliseconds < 0) { + seconds -= 1; + milliseconds += 1000; + } + + if (year >= 0 && year <= 99) + year += 1900; + int month = month_index + 1; + auto datetime = Core::DateTime::create(year, month, day, hours, minutes, seconds); + return Date::create(global_object(), datetime, milliseconds); +} + +JS_DEFINE_NATIVE_FUNCTION(DateConstructor::now) +{ + struct timeval tv; + gettimeofday(&tv, nullptr); + return Value(tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0); +} + +JS_DEFINE_NATIVE_FUNCTION(DateConstructor::parse) +{ + if (!vm.argument_count()) + return js_nan(); + + auto iso_8601 = vm.argument(0).to_string(global_object); + if (vm.exception()) + return js_nan(); + + return parse_simplified_iso8601(iso_8601); +} + +JS_DEFINE_NATIVE_FUNCTION(DateConstructor::utc) +{ + auto arg_or = [&vm, &global_object](size_t i, i32 fallback) { return vm.argument_count() > i ? vm.argument(i).to_i32(global_object) : fallback; }; + int year = vm.argument(0).to_i32(global_object); + if (year >= 0 && year <= 99) + year += 1900; + + struct tm tm = {}; + tm.tm_year = year - 1900; + tm.tm_mon = arg_or(1, 0); // 0-based in both tm and JavaScript + tm.tm_mday = arg_or(2, 1); + tm.tm_hour = arg_or(3, 0); + tm.tm_min = arg_or(4, 0); + tm.tm_sec = arg_or(5, 0); + // timegm() doesn't read tm.tm_wday and tm.tm_yday, no need to fill them in. + + int milliseconds = arg_or(6, 0); + return Value(1000.0 * timegm(&tm) + milliseconds); +} +} diff --git a/Userland/Libraries/LibJS/Runtime/DateConstructor.h b/Userland/Libraries/LibJS/Runtime/DateConstructor.h new file mode 100644 index 0000000000..cc16ee8e4a --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/DateConstructor.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/NativeFunction.h> + +namespace JS { + +class DateConstructor final : public NativeFunction { + JS_OBJECT(DateConstructor, NativeFunction); + +public: + explicit DateConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~DateConstructor() override; + + virtual Value call() override; + virtual Value construct(Function& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } + + JS_DECLARE_NATIVE_FUNCTION(now); + JS_DECLARE_NATIVE_FUNCTION(parse); + JS_DECLARE_NATIVE_FUNCTION(utc); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/DatePrototype.cpp b/Userland/Libraries/LibJS/Runtime/DatePrototype.cpp new file mode 100644 index 0000000000..72fea9ca25 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/DatePrototype.cpp @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <AK/Function.h> +#include <AK/String.h> +#include <LibCore/DateTime.h> +#include <LibJS/Runtime/Date.h> +#include <LibJS/Runtime/DatePrototype.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +static Date* typed_this(VM& vm, GlobalObject& global_object) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return nullptr; + if (!is<Date>(this_object)) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Date"); + return nullptr; + } + return static_cast<Date*>(this_object); +} + +DatePrototype::DatePrototype(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void DatePrototype::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.getDate, get_date, 0, attr); + define_native_function(vm.names.getDay, get_day, 0, attr); + define_native_function(vm.names.getFullYear, get_full_year, 0, attr); + define_native_function(vm.names.getHours, get_hours, 0, attr); + define_native_function(vm.names.getMilliseconds, get_milliseconds, 0, attr); + define_native_function(vm.names.getMinutes, get_minutes, 0, attr); + define_native_function(vm.names.getMonth, get_month, 0, attr); + define_native_function(vm.names.getSeconds, get_seconds, 0, attr); + define_native_function(vm.names.getTime, get_time, 0, attr); + define_native_function(vm.names.getUTCDate, get_utc_date, 0, attr); + define_native_function(vm.names.getUTCDay, get_utc_day, 0, attr); + define_native_function(vm.names.getUTCFullYear, get_utc_full_year, 0, attr); + define_native_function(vm.names.getUTCHours, get_utc_hours, 0, attr); + define_native_function(vm.names.getUTCMilliseconds, get_utc_milliseconds, 0, attr); + define_native_function(vm.names.getUTCMinutes, get_utc_minutes, 0, attr); + define_native_function(vm.names.getUTCMonth, get_utc_month, 0, attr); + define_native_function(vm.names.getUTCSeconds, get_utc_seconds, 0, attr); + define_native_function(vm.names.toDateString, to_date_string, 0, attr); + define_native_function(vm.names.toISOString, to_iso_string, 0, attr); + define_native_function(vm.names.toLocaleDateString, to_locale_date_string, 0, attr); + define_native_function(vm.names.toLocaleString, to_locale_string, 0, attr); + define_native_function(vm.names.toLocaleTimeString, to_locale_time_string, 0, attr); + define_native_function(vm.names.toTimeString, to_time_string, 0, attr); + define_native_function(vm.names.toString, to_string, 0, attr); + + // Aliases. + define_native_function(vm.names.valueOf, get_time, 0, attr); + // toJSON() isn't quite an alias for toISOString(): + // - it returns null instead of throwing RangeError + // - its .length is 1, not 0 + // - it can be transferred to other prototypes +} + +DatePrototype::~DatePrototype() +{ +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_date) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return Value(static_cast<double>(this_object->date())); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_day) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return Value(static_cast<double>(this_object->day())); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_full_year) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return Value(static_cast<double>(this_object->full_year())); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_hours) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return Value(static_cast<double>(this_object->hours())); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_milliseconds) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return Value(static_cast<double>(this_object->milliseconds())); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_minutes) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return Value(static_cast<double>(this_object->minutes())); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_month) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return Value(static_cast<double>(this_object->month())); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_seconds) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return Value(static_cast<double>(this_object->seconds())); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_time) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return Value(this_object->time()); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_date) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return Value(static_cast<double>(this_object->utc_date())); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_day) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return Value(static_cast<double>(this_object->utc_day())); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_full_year) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return Value(static_cast<double>(this_object->utc_full_year())); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_hours) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return Value(static_cast<double>(this_object->utc_hours())); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_milliseconds) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return Value(static_cast<double>(this_object->utc_milliseconds())); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_month) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return Value(static_cast<double>(this_object->utc_month())); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_minutes) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return Value(static_cast<double>(this_object->utc_minutes())); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::get_utc_seconds) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return Value(static_cast<double>(this_object->utc_seconds())); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_date_string) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + auto string = this_object->date_string(); + return js_string(vm, move(string)); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_iso_string) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + auto string = this_object->iso_date_string(); + return js_string(vm, move(string)); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_locale_date_string) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + // FIXME: Optional locales, options params. + auto string = this_object->locale_date_string(); + return js_string(vm, move(string)); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_locale_string) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + // FIXME: Optional locales, options params. + auto string = this_object->locale_string(); + return js_string(vm, move(string)); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_locale_time_string) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + // FIXME: Optional locales, options params. + auto string = this_object->locale_time_string(); + return js_string(vm, move(string)); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_time_string) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + auto string = this_object->time_string(); + return js_string(vm, move(string)); +} + +JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_string) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + auto string = this_object->string(); + return js_string(vm, move(string)); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/DatePrototype.h b/Userland/Libraries/LibJS/Runtime/DatePrototype.h new file mode 100644 index 0000000000..5c8c8bebd4 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/DatePrototype.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class DatePrototype final : public Object { + JS_OBJECT(DatePrototype, Object); + +public: + explicit DatePrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~DatePrototype() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(get_date); + JS_DECLARE_NATIVE_FUNCTION(get_day); + JS_DECLARE_NATIVE_FUNCTION(get_full_year); + JS_DECLARE_NATIVE_FUNCTION(get_hours); + JS_DECLARE_NATIVE_FUNCTION(get_milliseconds); + JS_DECLARE_NATIVE_FUNCTION(get_minutes); + JS_DECLARE_NATIVE_FUNCTION(get_month); + JS_DECLARE_NATIVE_FUNCTION(get_seconds); + JS_DECLARE_NATIVE_FUNCTION(get_time); + JS_DECLARE_NATIVE_FUNCTION(get_utc_date); + JS_DECLARE_NATIVE_FUNCTION(get_utc_day); + JS_DECLARE_NATIVE_FUNCTION(get_utc_full_year); + JS_DECLARE_NATIVE_FUNCTION(get_utc_hours); + JS_DECLARE_NATIVE_FUNCTION(get_utc_milliseconds); + JS_DECLARE_NATIVE_FUNCTION(get_utc_minutes); + JS_DECLARE_NATIVE_FUNCTION(get_utc_month); + JS_DECLARE_NATIVE_FUNCTION(get_utc_seconds); + JS_DECLARE_NATIVE_FUNCTION(to_date_string); + JS_DECLARE_NATIVE_FUNCTION(to_iso_string); + JS_DECLARE_NATIVE_FUNCTION(to_locale_date_string); + JS_DECLARE_NATIVE_FUNCTION(to_locale_string); + JS_DECLARE_NATIVE_FUNCTION(to_locale_time_string); + JS_DECLARE_NATIVE_FUNCTION(to_time_string); + JS_DECLARE_NATIVE_FUNCTION(to_string); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Error.cpp b/Userland/Libraries/LibJS/Runtime/Error.cpp new file mode 100644 index 0000000000..7f30ab872c --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Error.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace JS { + +Error* Error::create(GlobalObject& global_object, const FlyString& name, const String& message) +{ + return global_object.heap().allocate<Error>(global_object, name, message, *global_object.error_prototype()); +} + +Error::Error(const FlyString& name, const String& message, Object& prototype) + : Object(prototype) + , m_name(name) + , m_message(message) +{ +} + +Error::~Error() +{ +} + +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + ClassName* ClassName::create(GlobalObject& global_object, const String& message) \ + { \ + return global_object.heap().allocate<ClassName>(global_object, message, *global_object.snake_name##_prototype()); \ + } \ + ClassName::ClassName(const String& message, Object& prototype) \ + : Error(vm().names.ClassName, message, prototype) \ + { \ + } \ + ClassName::~ClassName() { } + +JS_ENUMERATE_ERROR_SUBCLASSES +#undef __JS_ENUMERATE + +} diff --git a/Userland/Libraries/LibJS/Runtime/Error.h b/Userland/Libraries/LibJS/Runtime/Error.h new file mode 100644 index 0000000000..3605825f1d --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Error.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/FlyString.h> +#include <LibJS/Runtime/Object.h> + +namespace JS { + +class Error : public Object { + JS_OBJECT(Error, Object); + +public: + static Error* create(GlobalObject&, const FlyString& name, const String& message); + + Error(const FlyString& name, const String& message, Object& prototype); + virtual ~Error() override; + + const FlyString& name() const { return m_name; } + const String& message() const { return m_message; } + + void set_name(const FlyString& name) { m_name = name; } + +private: + FlyString m_name; + String m_message; +}; + +#define DECLARE_ERROR_SUBCLASS(ClassName, snake_name, PrototypeName, ConstructorName) \ + class ClassName final : public Error { \ + JS_OBJECT(ClassName, Error); \ + \ + public: \ + static ClassName* create(GlobalObject&, const String& message); \ + \ + ClassName(const String& message, Object& prototype); \ + virtual ~ClassName() override; \ + }; + +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + DECLARE_ERROR_SUBCLASS(ClassName, snake_name, PrototypeName, ConstructorName) +JS_ENUMERATE_ERROR_SUBCLASSES +#undef __JS_ENUMERATE +} diff --git a/Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp new file mode 100644 index 0000000000..549c74855c --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/ErrorConstructor.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace JS { + +ErrorConstructor::ErrorConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.Error, *global_object.function_prototype()) +{ +} + +void ErrorConstructor::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + NativeFunction::initialize(global_object); + define_property(vm.names.prototype, global_object.error_prototype(), 0); + define_property(vm.names.length, Value(1), Attribute::Configurable); +} + +ErrorConstructor::~ErrorConstructor() +{ +} + +Value ErrorConstructor::call() +{ + return construct(*this); +} + +Value ErrorConstructor::construct(Function&) +{ + auto& vm = this->vm(); + String message = ""; + if (!vm.call_frame().arguments.is_empty() && !vm.call_frame().arguments[0].is_undefined()) { + message = vm.call_frame().arguments[0].to_string(global_object()); + if (vm.exception()) + return {}; + } + return Error::create(global_object(), vm.names.Error, message); +} + +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + ConstructorName::ConstructorName(GlobalObject& global_object) \ + : NativeFunction(*global_object.function_prototype()) \ + { \ + } \ + void ConstructorName::initialize(GlobalObject& global_object) \ + { \ + auto& vm = this->vm(); \ + NativeFunction::initialize(global_object); \ + define_property(vm.names.prototype, global_object.snake_name##_prototype(), 0); \ + define_property(vm.names.length, Value(1), Attribute::Configurable); \ + } \ + ConstructorName::~ConstructorName() { } \ + Value ConstructorName::call() \ + { \ + return construct(*this); \ + } \ + Value ConstructorName::construct(Function&) \ + { \ + String message = ""; \ + if (!vm().call_frame().arguments.is_empty() && !vm().call_frame().arguments[0].is_undefined()) { \ + message = vm().call_frame().arguments[0].to_string(global_object()); \ + if (vm().exception()) \ + return {}; \ + } \ + return ClassName::create(global_object(), message); \ + } + +JS_ENUMERATE_ERROR_SUBCLASSES +#undef __JS_ENUMERATE + +} diff --git a/Userland/Libraries/LibJS/Runtime/ErrorConstructor.h b/Userland/Libraries/LibJS/Runtime/ErrorConstructor.h new file mode 100644 index 0000000000..2626623aaa --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ErrorConstructor.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/NativeFunction.h> + +namespace JS { + +class ErrorConstructor final : public NativeFunction { + JS_OBJECT(ErrorConstructor, NativeFunction); + +public: + explicit ErrorConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~ErrorConstructor() override; + + virtual Value call() override; + virtual Value construct(Function& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } +}; + +#define DECLARE_ERROR_SUBCLASS_CONSTRUCTOR(ClassName, snake_name, PrototypeName, ConstructorName) \ + class ConstructorName final : public NativeFunction { \ + JS_OBJECT(ConstructorName, NativeFunction); \ + \ + public: \ + explicit ConstructorName(GlobalObject&); \ + virtual void initialize(GlobalObject&) override; \ + virtual ~ConstructorName() override; \ + virtual Value call() override; \ + virtual Value construct(Function& new_target) override; \ + \ + private: \ + virtual bool has_constructor() const override { return true; } \ + }; + +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + DECLARE_ERROR_SUBCLASS_CONSTRUCTOR(ClassName, snake_name, PrototypeName, ConstructorName) +JS_ENUMERATE_ERROR_SUBCLASSES +#undef __JS_ENUMERATE + +} diff --git a/Userland/Libraries/LibJS/Runtime/ErrorPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ErrorPrototype.cpp new file mode 100644 index 0000000000..68affb5745 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ErrorPrototype.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/Function.h> +#include <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/ErrorPrototype.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/PrimitiveString.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +ErrorPrototype::ErrorPrototype(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void ErrorPrototype::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_property(vm.names.name, name_getter, name_setter, attr); + define_native_property(vm.names.message, message_getter, {}, attr); + define_native_function(vm.names.toString, to_string, 0, attr); +} + +ErrorPrototype::~ErrorPrototype() +{ +} + +JS_DEFINE_NATIVE_GETTER(ErrorPrototype::name_getter) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + if (!is<Error>(this_object)) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAn, "Error"); + return {}; + } + return js_string(vm, static_cast<const Error*>(this_object)->name()); +} + +JS_DEFINE_NATIVE_SETTER(ErrorPrototype::name_setter) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return; + if (!is<Error>(this_object)) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAn, "Error"); + return; + } + auto name = value.to_string(global_object); + if (vm.exception()) + return; + static_cast<Error*>(this_object)->set_name(name); +} + +JS_DEFINE_NATIVE_GETTER(ErrorPrototype::message_getter) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + if (!is<Error>(this_object)) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAn, "Error"); + return {}; + } + return js_string(vm, static_cast<const Error*>(this_object)->message()); +} + +JS_DEFINE_NATIVE_FUNCTION(ErrorPrototype::to_string) +{ + if (!vm.this_value(global_object).is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, vm.this_value(global_object).to_string_without_side_effects()); + return {}; + } + auto& this_object = vm.this_value(global_object).as_object(); + + String name = "Error"; + auto name_property = this_object.get(vm.names.name); + if (vm.exception()) + return {}; + if (!name_property.is_empty() && !name_property.is_undefined()) { + name = name_property.to_string(global_object); + if (vm.exception()) + return {}; + } + + String message = ""; + auto message_property = this_object.get(vm.names.message); + if (vm.exception()) + return {}; + if (!message_property.is_empty() && !message_property.is_undefined()) { + message = message_property.to_string(global_object); + if (vm.exception()) + return {}; + } + + if (name.length() == 0) + return js_string(vm, message); + if (message.length() == 0) + return js_string(vm, name); + return js_string(vm, String::formatted("{}: {}", name, message)); +} + +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + PrototypeName::PrototypeName(GlobalObject& global_object) \ + : Object(*global_object.error_prototype()) \ + { \ + } \ + PrototypeName::~PrototypeName() { } + +JS_ENUMERATE_ERROR_SUBCLASSES +#undef __JS_ENUMERATE + +} diff --git a/Userland/Libraries/LibJS/Runtime/ErrorPrototype.h b/Userland/Libraries/LibJS/Runtime/ErrorPrototype.h new file mode 100644 index 0000000000..3b461942ab --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ErrorPrototype.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/Error.h> + +namespace JS { + +class ErrorPrototype final : public Object { + JS_OBJECT(ErrorPrototype, Object); + +public: + explicit ErrorPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~ErrorPrototype() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(to_string); + + JS_DECLARE_NATIVE_GETTER(name_getter); + JS_DECLARE_NATIVE_SETTER(name_setter); + + JS_DECLARE_NATIVE_GETTER(message_getter); +}; + +#define DECLARE_ERROR_SUBCLASS_PROTOTYPE(ClassName, snake_name, PrototypeName, ConstructorName) \ + class PrototypeName final : public Object { \ + JS_OBJECT(PrototypeName, Object); \ + \ + public: \ + explicit PrototypeName(GlobalObject&); \ + virtual void initialize(GlobalObject&) override { } \ + virtual ~PrototypeName() override; \ + }; + +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + DECLARE_ERROR_SUBCLASS_PROTOTYPE(ClassName, snake_name, PrototypeName, ConstructorName) +JS_ENUMERATE_ERROR_SUBCLASSES +#undef __JS_ENUMERATE + +} diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.cpp b/Userland/Libraries/LibJS/Runtime/ErrorTypes.cpp new file mode 100644 index 0000000000..f71172b68a --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/ErrorTypes.h> + +namespace JS { + +#define __ENUMERATE_JS_ERROR(name, message) \ + const ErrorType ErrorType::name = ErrorType(message); +JS_ENUMERATE_ERROR_TYPES(__ENUMERATE_JS_ERROR) +#undef __ENUMERATE_JS_ERROR + +} diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h new file mode 100644 index 0000000000..e406d4377f --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 + +#define JS_ENUMERATE_ERROR_TYPES(M) \ + M(ArrayMaxSize, "Maximum array size exceeded") \ + M(ArrayPrototypeOneArg, "Array.prototype.{}() requires at least one argument") \ + M(AccessorBadField, "Accessor descriptor's '{}' field must be a function or undefined") \ + M(AccessorValueOrWritable, "Accessor property descriptor cannot specify a value or writable key") \ + M(BigIntBadOperator, "Cannot use {} operator with BigInt") \ + M(BigIntBadOperatorOtherType, "Cannot use {} operator with BigInt and other type") \ + M(BigIntIntArgument, "BigInt argument must be an integer") \ + M(BigIntInvalidValue, "Invalid value for BigInt: {}") \ + M(ClassConstructorWithoutNew, "Class constructor {} must be called with 'new'") \ + M(ClassIsAbstract, "Abstract class {} cannot be constructed directly") \ + M(ClassDoesNotExtendAConstructorOrNull, "Class extends value {} is not a constructor or null") \ + M(ConstructorWithoutNew, "{} constructor must be called with 'new'") \ + M(Convert, "Cannot convert {} to {}") \ + M(ConvertUndefinedToObject, "Cannot convert undefined to object") \ + M(DescChangeNonConfigurable, "Cannot change attributes of non-configurable property '{}'") \ + M(FunctionArgsNotObject, "Argument array must be an object") \ + M(InOperatorWithObject, "'in' operator must be used on an object") \ + M(InstanceOfOperatorBadPrototype, "'prototype' property of {} is not an object") \ + M(InvalidAssignToConst, "Invalid assignment to const variable") \ + M(InvalidIndex, "Index must be a positive integer") \ + M(InvalidLeftHandAssignment, "Invalid left-hand side in assignment") \ + M(InvalidLength, "Invalid {} length") \ + M(InvalidRadix, "Radix must be an integer no less than 2, and no greater than 36") \ + M(IsNotA, "{} is not a {}") \ + M(IsNotAEvaluatedFrom, "{} is not a {} (evaluated from '{}')") \ + M(IterableNextBadReturn, "iterator.next() returned a non-object value") \ + M(IterableNextNotAFunction, "'next' property on returned object from Symbol.iterator method is " \ + "not a function") \ + M(JsonBigInt, "Cannot serialize BigInt value to JSON") \ + M(JsonCircular, "Cannot stringify circular object") \ + M(JsonMalformed, "Malformed JSON string") \ + M(NotA, "Not a {} object") \ + M(NotAConstructor, "{} is not a constructor") \ + M(NotAFunction, "{} is not a function") \ + M(NotAFunctionNoParam, "Not a function") \ + M(NotAn, "Not an {} object") \ + M(NotAnObject, "{} is not an object") \ + M(NotASymbol, "{} is not a symbol") \ + M(NotIterable, "{} is not iterable") \ + M(NonExtensibleDefine, "Cannot define property {} on non-extensible object") \ + M(NumberIncompatibleThis, "Number.prototype.{} method called with incompatible this target") \ + M(ObjectDefinePropertyReturnedFalse, "Object's [[DefineProperty]] method returned false") \ + M(ObjectSetPrototypeOfReturnedFalse, "Object's [[SetPrototypeOf]] method returned false") \ + M(ObjectSetPrototypeOfTwoArgs, "Object.setPrototypeOf requires at least two arguments") \ + M(ObjectPreventExtensionsReturnedFalse, "Object's [[PreventExtensions]] method returned false") \ + M(ObjectPrototypeNullOrUndefinedOnSuperPropertyAccess, \ + "Object prototype must not be {} on a super property access") \ + M(ObjectPrototypeWrongType, "Prototype must be an object or null") \ + M(ProxyConstructBadReturnType, "Proxy handler's construct trap violates invariant: must return " \ + "an object") \ + M(ProxyConstructorBadType, "Expected {} argument of Proxy constructor to be object, got {}") \ + M(ProxyDefinePropExistingConfigurable, "Proxy handler's defineProperty trap violates " \ + "invariant: a property cannot be defined as non-configurable if it already exists on the " \ + "target object as a configurable property") \ + M(ProxyDefinePropIncompatibleDescriptor, "Proxy handler's defineProperty trap violates " \ + "invariant: the new descriptor is not compatible with the existing descriptor of the " \ + "property on the target") \ + M(ProxyDefinePropNonConfigurableNonExisting, "Proxy handler's defineProperty trap " \ + "violates invariant: a property cannot be defined as non-configurable if it does not " \ + "already exist on the target object") \ + M(ProxyDefinePropNonExtensible, "Proxy handler's defineProperty trap violates invariant: " \ + "a property cannot be reported as being defined if the property does not exist on " \ + "the target and the target is non-extensible") \ + M(ProxyDeleteNonConfigurable, "Proxy handler's deleteProperty trap violates invariant: " \ + "cannot report a non-configurable own property of the target as deleted") \ + M(ProxyGetImmutableDataProperty, "Proxy handler's get trap violates invariant: the " \ + "returned value must match the value on the target if the property exists on the " \ + "target as a non-writable, non-configurable own data property") \ + M(ProxyGetNonConfigurableAccessor, "Proxy handler's get trap violates invariant: the " \ + "returned value must be undefined if the property exists on the target as a " \ + "non-configurable accessor property with an undefined get attribute") \ + M(ProxyGetOwnDescriptorExistingConfigurable, "Proxy handler's getOwnPropertyDescriptor " \ + "trap violates invariant: a property cannot be defined as non-configurable if it " \ + "already exists on the target object as a configurable property") \ + M(ProxyGetOwnDescriptorInvalidDescriptor, "Proxy handler's getOwnPropertyDescriptor trap " \ + "violates invariant: invalid property descriptor for existing property on the target") \ + M(ProxyGetOwnDescriptorInvalidNonConfig, "Proxy handler's getOwnPropertyDescriptor trap " \ + "violates invariant: cannot report target's property as non-configurable if the " \ + "property does not exist, or if it is configurable") \ + M(ProxyGetOwnDescriptorNonConfigurable, "Proxy handler's getOwnPropertyDescriptor trap " \ + "violates invariant: cannot return undefined for a property on the target which is " \ + "a non-configurable property") \ + M(ProxyGetOwnDescriptorReturn, "Proxy handler's getOwnPropertyDescriptor trap violates " \ + "invariant: must return an object or undefined") \ + M(ProxyGetOwnDescriptorUndefReturn, "Proxy handler's getOwnPropertyDescriptor trap " \ + "violates invariant: cannot report a property as being undefined if it exists as an " \ + "own property of the target and the target is non-extensible") \ + M(ProxyGetPrototypeOfNonExtensible, "Proxy handler's getPrototypeOf trap violates " \ + "invariant: cannot return a different prototype object for a non-extensible target") \ + M(ProxyGetPrototypeOfReturn, "Proxy handler's getPrototypeOf trap violates invariant: " \ + "must return an object or null") \ + M(ProxyHasExistingNonConfigurable, "Proxy handler's has trap violates invariant: a " \ + "property cannot be reported as non-existent if it exists on the target as a " \ + "non-configurable property") \ + M(ProxyHasExistingNonExtensible, "Proxy handler's has trap violates invariant: a property " \ + "cannot be reported as non-existent if it exists on the target and the target is " \ + "non-extensible") \ + M(ProxyInvalidTrap, "Proxy handler's {} trap wasn't undefined, null, or callable") \ + M(ProxyIsExtensibleReturn, "Proxy handler's isExtensible trap violates invariant: " \ + "return value must match the target's extensibility") \ + M(ProxyPreventExtensionsReturn, "Proxy handler's preventExtensions trap violates " \ + "invariant: cannot return true if the target object is extensible") \ + M(ProxyRevoked, "An operation was performed on a revoked Proxy object") \ + M(ProxySetImmutableDataProperty, "Proxy handler's set trap violates invariant: cannot " \ + "return true for a property on the target which is a non-configurable, non-writable " \ + "own data property") \ + M(ProxySetNonConfigurableAccessor, "Proxy handler's set trap violates invariant: cannot " \ + "return true for a property on the target which is a non-configurable own accessor " \ + "property with an undefined set attribute") \ + M(ProxySetPrototypeOfNonExtensible, "Proxy handler's setPrototypeOf trap violates " \ + "invariant: the argument must match the prototype of the target if the " \ + "target is non-extensible") \ + M(ProxyTwoArguments, "Proxy constructor requires at least two arguments") \ + M(ReduceNoInitial, "Reduce of empty array with no initial value") \ + M(ReferencePrimitiveAssignment, "Cannot assign property {} to primitive value") \ + M(ReferenceUnresolvable, "Unresolvable reference") \ + M(ReflectArgumentMustBeAFunction, "First argument of Reflect.{}() must be a function") \ + M(ReflectArgumentMustBeAnObject, "First argument of Reflect.{}() must be an object") \ + M(ReflectBadArgumentsList, "Arguments list must be an object") \ + M(ReflectBadNewTarget, "Optional third argument of Reflect.construct() must be a constructor") \ + M(ReflectBadDescriptorArgument, "Descriptor argument is not an object") \ + M(RegExpCompileError, "RegExp compile error: {}") \ + M(RegExpObjectBadFlag, "Invalid RegExp flag '{}'") \ + M(RegExpObjectRepeatedFlag, "Repeated RegExp flag '{}'") \ + M(StringRawCannotConvert, "Cannot convert property 'raw' to object from {}") \ + M(StringRepeatCountMustBe, "repeat count must be a {} number") \ + M(ThisHasNotBeenInitialized, "|this| has not been initialized") \ + M(ThisIsAlreadyInitialized, "|this| is already initialized") \ + M(ToObjectNullOrUndef, "ToObject on null or undefined") \ + M(TypedArrayInvalidBufferLength, "Invalid buffer length for {}: must be a multiple of {}, got {}") \ + M(TypedArrayInvalidByteOffset, "Invalid byte offset for {}: must be a multiple of {}, got {}") \ + M(TypedArrayOutOfRangeByteOffset, "Typed array byte offset {} is out of range for buffer with length {}") \ + M(TypedArrayOutOfRangeByteOffsetOrLength, "Typed array range {}:{} is out of range for buffer with length {}") \ + M(UnknownIdentifier, "'{}' is not defined") \ + /* LibWeb bindings */ \ + M(NotAByteString, "Argument to {}() must be a byte string") \ + M(BadArgCountOne, "{}() needs one argument") \ + M(BadArgCountAtLeastOne, "{}() needs at least one argument") \ + M(BadArgCountMany, "{}() needs {} arguments") + +namespace JS { + +class ErrorType { +public: +#define __ENUMERATE_JS_ERROR(name, message) \ + static const ErrorType name; + JS_ENUMERATE_ERROR_TYPES(__ENUMERATE_JS_ERROR) +#undef __ENUMERATE_JS_ERROR + + const char* message() const + { + return m_message; + } + +private: + explicit ErrorType(const char* message) + : m_message(message) + { + } + + const char* m_message; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Exception.cpp b/Userland/Libraries/LibJS/Runtime/Exception.cpp new file mode 100644 index 0000000000..08c6f6bde9 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Exception.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/String.h> +#include <LibJS/AST.h> +#include <LibJS/Runtime/Exception.h> +#include <LibJS/Runtime/VM.h> + +namespace JS { + +Exception::Exception(Value value) + : m_value(value) +{ + auto& call_stack = vm().call_stack(); + for (ssize_t i = call_stack.size() - 1; i >= 0; --i) { + String function_name = call_stack[i]->function_name; + if (function_name.is_empty()) + function_name = "<anonymous>"; + m_trace.append(function_name); + } + + auto& node_stack = vm().node_stack(); + for (ssize_t i = node_stack.size() - 1; i >= 0; --i) { + auto* node = node_stack[i]; + ASSERT(node); + m_source_ranges.append(node->source_range()); + } +} + +Exception::~Exception() +{ +} + +void Exception::visit_edges(Visitor& visitor) +{ + Cell::visit_edges(visitor); + visitor.visit(m_value); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Exception.h b/Userland/Libraries/LibJS/Runtime/Exception.h new file mode 100644 index 0000000000..2d26447c89 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Exception.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/Vector.h> +#include <LibJS/Runtime/Cell.h> +#include <LibJS/Runtime/Value.h> +#include <LibJS/SourceRange.h> + +namespace JS { + +class Exception : public Cell { +public: + explicit Exception(Value); + virtual ~Exception() override; + + Value value() const { return m_value; } + const Vector<String>& trace() const { return m_trace; } + const Vector<SourceRange>& source_ranges() const { return m_source_ranges; } + +private: + virtual const char* class_name() const override { return "Exception"; } + virtual void visit_edges(Visitor&) override; + + Value m_value; + Vector<String> m_trace; + Vector<SourceRange> m_source_ranges; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Function.cpp b/Userland/Libraries/LibJS/Runtime/Function.cpp new file mode 100644 index 0000000000..43b7d6e7e1 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Function.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Interpreter.h> +#include <LibJS/Runtime/BoundFunction.h> +#include <LibJS/Runtime/Function.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace JS { + +Function::Function(Object& prototype) + : Function(prototype, {}, {}) +{ +} + +Function::Function(Object& prototype, Value bound_this, Vector<Value> bound_arguments) + : Object(prototype) + , m_bound_this(bound_this) + , m_bound_arguments(move(bound_arguments)) +{ +} + +Function::~Function() +{ +} + +BoundFunction* Function::bind(Value bound_this_value, Vector<Value> arguments) +{ + auto& vm = this->vm(); + Function& target_function = is<BoundFunction>(*this) ? static_cast<BoundFunction&>(*this).target_function() : *this; + + auto bound_this_object = [&vm, bound_this_value, this]() -> Value { + if (!m_bound_this.is_empty()) + return m_bound_this; + switch (bound_this_value.type()) { + case Value::Type::Undefined: + case Value::Type::Null: + if (vm.in_strict_mode()) + return bound_this_value; + return &global_object(); + default: + return bound_this_value.to_object(global_object()); + } + }(); + + i32 computed_length = 0; + auto length_property = get(vm.names.length); + if (vm.exception()) + return nullptr; + if (length_property.is_number()) + computed_length = max(0, length_property.as_i32() - static_cast<i32>(arguments.size())); + + Object* constructor_prototype = nullptr; + auto prototype_property = target_function.get(vm.names.prototype); + if (vm.exception()) + return nullptr; + if (prototype_property.is_object()) + constructor_prototype = &prototype_property.as_object(); + + auto all_bound_arguments = bound_arguments(); + all_bound_arguments.append(move(arguments)); + + return heap().allocate<BoundFunction>(global_object(), global_object(), target_function, bound_this_object, move(all_bound_arguments), computed_length, constructor_prototype); +} + +void Function::visit_edges(Visitor& visitor) +{ + Object::visit_edges(visitor); + + visitor.visit(m_bound_this); + + for (auto argument : m_bound_arguments) + visitor.visit(argument); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Function.h b/Userland/Libraries/LibJS/Runtime/Function.h new file mode 100644 index 0000000000..d54f059ec2 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Function.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/String.h> +#include <LibJS/Runtime/Object.h> + +namespace JS { + +class Function : public Object { + JS_OBJECT(Function, Object); + +public: + enum class ConstructorKind { + Base, + Derived, + }; + + virtual ~Function(); + virtual void initialize(GlobalObject&) override { } + + virtual Value call() = 0; + virtual Value construct(Function& new_target) = 0; + virtual const FlyString& name() const = 0; + virtual LexicalEnvironment* create_environment() = 0; + + virtual void visit_edges(Visitor&) override; + + BoundFunction* bind(Value bound_this_value, Vector<Value> arguments); + + Value bound_this() const { return m_bound_this; } + + const Vector<Value>& bound_arguments() const { return m_bound_arguments; } + + Value home_object() const { return m_home_object; } + void set_home_object(Value home_object) { m_home_object = home_object; } + + ConstructorKind constructor_kind() const { return m_constructor_kind; }; + void set_constructor_kind(ConstructorKind constructor_kind) { m_constructor_kind = constructor_kind; } + + virtual bool is_strict_mode() const { return false; } + +protected: + explicit Function(Object& prototype); + Function(Object& prototype, Value bound_this, Vector<Value> bound_arguments); + +private: + virtual bool is_function() const override { return true; } + Value m_bound_this; + Vector<Value> m_bound_arguments; + Value m_home_object; + ConstructorKind m_constructor_kind = ConstructorKind::Base; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp b/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp new file mode 100644 index 0000000000..b8664907f2 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <AK/StringBuilder.h> +#include <LibJS/AST.h> +#include <LibJS/Interpreter.h> +#include <LibJS/Parser.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/FunctionConstructor.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/ScriptFunction.h> + +namespace JS { + +FunctionConstructor::FunctionConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.Function, *global_object.function_prototype()) +{ +} + +void FunctionConstructor::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + NativeFunction::initialize(global_object); + define_property(vm.names.prototype, global_object.function_prototype(), 0); + define_property(vm.names.length, Value(1), Attribute::Configurable); +} + +FunctionConstructor::~FunctionConstructor() +{ +} + +Value FunctionConstructor::call() +{ + return construct(*this); +} + +Value FunctionConstructor::construct(Function&) +{ + auto& vm = this->vm(); + String parameters_source = ""; + String body_source = ""; + if (vm.argument_count() == 1) { + body_source = vm.argument(0).to_string(global_object()); + if (vm.exception()) + return {}; + } + if (vm.argument_count() > 1) { + Vector<String> parameters; + for (size_t i = 0; i < vm.argument_count() - 1; ++i) { + parameters.append(vm.argument(i).to_string(global_object())); + if (vm.exception()) + return {}; + } + StringBuilder parameters_builder; + parameters_builder.join(',', parameters); + parameters_source = parameters_builder.build(); + body_source = vm.argument(vm.argument_count() - 1).to_string(global_object()); + if (vm.exception()) + return {}; + } + auto source = String::formatted("function anonymous({}\n) {{\n{}\n}}", parameters_source, body_source); + auto parser = Parser(Lexer(source)); + auto function_expression = parser.parse_function_node<FunctionExpression>(); + if (parser.has_errors()) { + auto error = parser.errors()[0]; + vm.throw_exception<SyntaxError>(global_object(), error.to_string()); + return {}; + } + + OwnPtr<Interpreter> local_interpreter; + Interpreter* interpreter = vm.interpreter_if_exists(); + + if (!interpreter) { + local_interpreter = Interpreter::create_with_existing_global_object(global_object()); + interpreter = local_interpreter.ptr(); + } + + VM::InterpreterExecutionScope scope(*interpreter); + return function_expression->execute(*interpreter, global_object()); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/FunctionConstructor.h b/Userland/Libraries/LibJS/Runtime/FunctionConstructor.h new file mode 100644 index 0000000000..70405d43e4 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/FunctionConstructor.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/NativeFunction.h> + +namespace JS { + +class FunctionConstructor final : public NativeFunction { + JS_OBJECT(FunctionConstructor, NativeFunction); + +public: + explicit FunctionConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~FunctionConstructor() override; + + virtual Value call() override; + virtual Value construct(Function& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp b/Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp new file mode 100644 index 0000000000..972c5021ea --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <AK/Function.h> +#include <AK/StringBuilder.h> +#include <LibJS/AST.h> +#include <LibJS/Interpreter.h> +#include <LibJS/Runtime/BoundFunction.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/Function.h> +#include <LibJS/Runtime/FunctionPrototype.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/MarkedValueList.h> +#include <LibJS/Runtime/NativeFunction.h> +#include <LibJS/Runtime/ScriptFunction.h> + +namespace JS { + +FunctionPrototype::FunctionPrototype(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void FunctionPrototype::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.apply, apply, 2, attr); + define_native_function(vm.names.bind, bind, 1, attr); + define_native_function(vm.names.call, call, 1, attr); + define_native_function(vm.names.toString, to_string, 0, attr); + define_native_function(vm.well_known_symbol_has_instance(), symbol_has_instance, 1, 0); + define_property(vm.names.length, Value(0), Attribute::Configurable); + define_property(vm.names.name, js_string(heap(), ""), Attribute::Configurable); +} + +FunctionPrototype::~FunctionPrototype() +{ +} + +JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::apply) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + if (!this_object->is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Function"); + return {}; + } + auto& function = static_cast<Function&>(*this_object); + auto this_arg = vm.argument(0); + auto arg_array = vm.argument(1); + if (arg_array.is_nullish()) + return vm.call(function, this_arg); + if (!arg_array.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::FunctionArgsNotObject); + return {}; + } + auto length = length_of_array_like(global_object, arg_array.as_object()); + if (vm.exception()) + return {}; + MarkedValueList arguments(vm.heap()); + for (size_t i = 0; i < length; ++i) { + auto element = arg_array.as_object().get(i); + if (vm.exception()) + return {}; + arguments.append(element.value_or(js_undefined())); + } + return vm.call(function, this_arg, move(arguments)); +} + +JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::bind) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + if (!this_object->is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Function"); + return {}; + } + auto& this_function = static_cast<Function&>(*this_object); + auto bound_this_arg = vm.argument(0); + + Vector<Value> arguments; + if (vm.argument_count() > 1) { + arguments = vm.call_frame().arguments; + arguments.remove(0); + } + + return this_function.bind(bound_this_arg, move(arguments)); +} + +JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::call) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + if (!this_object->is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Function"); + return {}; + } + auto& function = static_cast<Function&>(*this_object); + auto this_arg = vm.argument(0); + MarkedValueList arguments(vm.heap()); + if (vm.argument_count() > 1) { + for (size_t i = 1; i < vm.argument_count(); ++i) + arguments.append(vm.argument(i)); + } + return vm.call(function, this_arg, move(arguments)); +} + +JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::to_string) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + if (!this_object->is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Function"); + return {}; + } + String function_name = static_cast<Function*>(this_object)->name(); + String function_parameters = ""; + String function_body; + + if (is<NativeFunction>(this_object) || is<BoundFunction>(this_object)) { + function_body = String::formatted(" [{}]", this_object->class_name()); + } else { + StringBuilder parameters_builder; + auto first = true; + for (auto& parameter : static_cast<ScriptFunction*>(this_object)->parameters()) { + if (!first) + parameters_builder.append(", "); + first = false; + parameters_builder.append(parameter.name); + if (parameter.default_value) { + // FIXME: See note below + parameters_builder.append(" = TODO"); + } + } + function_parameters = parameters_builder.build(); + // FIXME: ASTNodes should be able to dump themselves to source strings - something like this: + // auto& body = static_cast<ScriptFunction*>(this_object)->body(); + // function_body = body.to_source(); + function_body = " ???"; + } + + auto function_source = String::formatted( + "function {}({}) {{\n{}\n}}", + function_name.is_null() ? "" : function_name, function_parameters, function_body); + return js_string(vm, function_source); +} + +JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::symbol_has_instance) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + if (!this_object->is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Function"); + return {}; + } + return ordinary_has_instance(global_object, vm.argument(0), this_object); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/FunctionPrototype.h b/Userland/Libraries/LibJS/Runtime/FunctionPrototype.h new file mode 100644 index 0000000000..2cb74504ad --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/FunctionPrototype.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class FunctionPrototype final : public Object { + JS_OBJECT(FunctionPrototype, Object); + +public: + explicit FunctionPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~FunctionPrototype() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(apply); + JS_DECLARE_NATIVE_FUNCTION(bind); + JS_DECLARE_NATIVE_FUNCTION(call); + JS_DECLARE_NATIVE_FUNCTION(to_string); + JS_DECLARE_NATIVE_FUNCTION(symbol_has_instance); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp new file mode 100644 index 0000000000..37135f907c --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <AK/Utf8View.h> +#include <LibJS/Console.h> +#include <LibJS/Heap/DeferGC.h> +#include <LibJS/Runtime/ArrayBufferConstructor.h> +#include <LibJS/Runtime/ArrayBufferPrototype.h> +#include <LibJS/Runtime/ArrayConstructor.h> +#include <LibJS/Runtime/ArrayIteratorPrototype.h> +#include <LibJS/Runtime/ArrayPrototype.h> +#include <LibJS/Runtime/BigIntConstructor.h> +#include <LibJS/Runtime/BigIntPrototype.h> +#include <LibJS/Runtime/BooleanConstructor.h> +#include <LibJS/Runtime/BooleanPrototype.h> +#include <LibJS/Runtime/ConsoleObject.h> +#include <LibJS/Runtime/DateConstructor.h> +#include <LibJS/Runtime/DatePrototype.h> +#include <LibJS/Runtime/ErrorConstructor.h> +#include <LibJS/Runtime/ErrorPrototype.h> +#include <LibJS/Runtime/FunctionConstructor.h> +#include <LibJS/Runtime/FunctionPrototype.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/IteratorPrototype.h> +#include <LibJS/Runtime/JSONObject.h> +#include <LibJS/Runtime/MathObject.h> +#include <LibJS/Runtime/NativeFunction.h> +#include <LibJS/Runtime/NumberConstructor.h> +#include <LibJS/Runtime/NumberPrototype.h> +#include <LibJS/Runtime/Object.h> +#include <LibJS/Runtime/ObjectConstructor.h> +#include <LibJS/Runtime/ObjectPrototype.h> +#include <LibJS/Runtime/ProxyConstructor.h> +#include <LibJS/Runtime/ReflectObject.h> +#include <LibJS/Runtime/RegExpConstructor.h> +#include <LibJS/Runtime/RegExpPrototype.h> +#include <LibJS/Runtime/Shape.h> +#include <LibJS/Runtime/StringConstructor.h> +#include <LibJS/Runtime/StringIteratorPrototype.h> +#include <LibJS/Runtime/StringPrototype.h> +#include <LibJS/Runtime/SymbolConstructor.h> +#include <LibJS/Runtime/SymbolPrototype.h> +#include <LibJS/Runtime/TypedArray.h> +#include <LibJS/Runtime/TypedArrayConstructor.h> +#include <LibJS/Runtime/TypedArrayPrototype.h> +#include <LibJS/Runtime/Value.h> +#include <ctype.h> + +namespace JS { + +GlobalObject::GlobalObject() + : ScopeObject(GlobalObjectTag::Tag) + , m_console(make<Console>(*this)) +{ +} + +void GlobalObject::initialize() +{ + auto& vm = this->vm(); + + ensure_shape_is_unique(); + + // These are done first since other prototypes depend on their presence. + m_empty_object_shape = heap().allocate_without_global_object<Shape>(*this); + m_object_prototype = heap().allocate_without_global_object<ObjectPrototype>(*this); + m_function_prototype = heap().allocate_without_global_object<FunctionPrototype>(*this); + + m_new_object_shape = vm.heap().allocate_without_global_object<Shape>(*this); + m_new_object_shape->set_prototype_without_transition(m_object_prototype); + + m_new_script_function_prototype_object_shape = vm.heap().allocate_without_global_object<Shape>(*this); + m_new_script_function_prototype_object_shape->set_prototype_without_transition(m_object_prototype); + m_new_script_function_prototype_object_shape->add_property_without_transition(vm.names.constructor, Attribute::Writable | Attribute::Configurable); + + static_cast<FunctionPrototype*>(m_function_prototype)->initialize(*this); + static_cast<ObjectPrototype*>(m_object_prototype)->initialize(*this); + + set_prototype(m_object_prototype); + +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + if (!m_##snake_name##_prototype) \ + m_##snake_name##_prototype = heap().allocate<PrototypeName>(*this, *this); + JS_ENUMERATE_BUILTIN_TYPES +#undef __JS_ENUMERATE + +#define __JS_ENUMERATE(ClassName, snake_name) \ + if (!m_##snake_name##_prototype) \ + m_##snake_name##_prototype = heap().allocate<ClassName##Prototype>(*this, *this); + JS_ENUMERATE_ITERATOR_PROTOTYPES +#undef __JS_ENUMERATE + + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.gc, gc, 0, attr); + define_native_function(vm.names.isNaN, is_nan, 1, attr); + define_native_function(vm.names.isFinite, is_finite, 1, attr); + define_native_function(vm.names.parseFloat, parse_float, 1, attr); + define_native_function(vm.names.parseInt, parse_int, 1, attr); + + define_property(vm.names.NaN, js_nan(), 0); + define_property(vm.names.Infinity, js_infinity(), 0); + define_property(vm.names.undefined, js_undefined(), 0); + + define_property(vm.names.globalThis, this, attr); + define_property(vm.names.console, heap().allocate<ConsoleObject>(*this, *this), attr); + define_property(vm.names.Math, heap().allocate<MathObject>(*this, *this), attr); + define_property(vm.names.JSON, heap().allocate<JSONObject>(*this, *this), attr); + define_property(vm.names.Reflect, heap().allocate<ReflectObject>(*this, *this), attr); + + add_constructor(vm.names.Array, m_array_constructor, m_array_prototype); + add_constructor(vm.names.ArrayBuffer, m_array_buffer_constructor, m_array_buffer_prototype); + add_constructor(vm.names.BigInt, m_bigint_constructor, m_bigint_prototype); + add_constructor(vm.names.Boolean, m_boolean_constructor, m_boolean_prototype); + add_constructor(vm.names.Date, m_date_constructor, m_date_prototype); + add_constructor(vm.names.Error, m_error_constructor, m_error_prototype); + add_constructor(vm.names.Function, m_function_constructor, m_function_prototype); + add_constructor(vm.names.Number, m_number_constructor, m_number_prototype); + add_constructor(vm.names.Object, m_object_constructor, m_object_prototype); + add_constructor(vm.names.Proxy, m_proxy_constructor, nullptr); + add_constructor(vm.names.RegExp, m_regexp_constructor, m_regexp_prototype); + add_constructor(vm.names.String, m_string_constructor, m_string_prototype); + add_constructor(vm.names.Symbol, m_symbol_constructor, m_symbol_prototype); + + initialize_constructor(vm.names.TypedArray, m_typed_array_constructor, m_typed_array_prototype); + +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + add_constructor(vm.names.ClassName, m_##snake_name##_constructor, m_##snake_name##_prototype); + JS_ENUMERATE_ERROR_SUBCLASSES + JS_ENUMERATE_TYPED_ARRAYS +#undef __JS_ENUMERATE +} + +GlobalObject::~GlobalObject() +{ +} + +void GlobalObject::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + + visitor.visit(m_empty_object_shape); + visitor.visit(m_new_object_shape); + visitor.visit(m_new_script_function_prototype_object_shape); + visitor.visit(m_proxy_constructor); + +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + visitor.visit(m_##snake_name##_constructor); + JS_ENUMERATE_ERROR_SUBCLASSES +#undef __JS_ENUMERATE + +#define __JS_ENUMERATE(ClassName, snake_name) \ + visitor.visit(m_##snake_name##_prototype); + JS_ENUMERATE_ITERATOR_PROTOTYPES +#undef __JS_ENUMERATE +} + +JS_DEFINE_NATIVE_FUNCTION(GlobalObject::gc) +{ + dbgln("Forced garbage collection requested!"); + vm.heap().collect_garbage(); + return js_undefined(); +} + +JS_DEFINE_NATIVE_FUNCTION(GlobalObject::is_nan) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + return Value(number.is_nan()); +} + +JS_DEFINE_NATIVE_FUNCTION(GlobalObject::is_finite) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + return Value(number.is_finite_number()); +} + +JS_DEFINE_NATIVE_FUNCTION(GlobalObject::parse_float) +{ + if (vm.argument(0).is_number()) + return vm.argument(0); + auto string = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + for (size_t length = string.length(); length > 0; --length) { + // This can't throw, so no exception check is fine. + auto number = Value(js_string(vm, string.substring(0, length))).to_number(global_object); + if (!number.is_nan()) + return number; + } + return js_nan(); +} + +JS_DEFINE_NATIVE_FUNCTION(GlobalObject::parse_int) +{ + // 18.2.5 parseInt ( string, radix ) + auto input_string = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + + // FIXME: There's a bunch of unnecessary string copying here. + double sign = 1; + auto s = input_string.trim_whitespace(TrimMode::Left); + if (!s.is_empty() && s[0] == '-') + sign = -1; + if (!s.is_empty() && (s[0] == '+' || s[0] == '-')) + s = s.substring(1, s.length() - 1); + + auto radix = vm.argument(1).to_i32(global_object); + if (vm.exception()) + return {}; + + bool strip_prefix = true; + if (radix != 0) { + if (radix < 2 || radix > 36) + return js_nan(); + if (radix != 16) + strip_prefix = false; + } else { + radix = 10; + } + + if (strip_prefix) { + if (s.length() >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + s = s.substring(2, s.length() - 2); + radix = 16; + } + } + + auto parse_digit = [&](u32 codepoint, i32 radix) -> Optional<i32> { + i32 digit = -1; + + if (isdigit(codepoint)) + digit = codepoint - '0'; + else if (islower(codepoint)) + digit = 10 + (codepoint - 'a'); + else if (isupper(codepoint)) + digit = 10 + (codepoint - 'A'); + + if (digit == -1 || digit >= radix) + return {}; + return digit; + }; + + bool had_digits = false; + double number = 0; + for (auto codepoint : Utf8View(s)) { + auto digit = parse_digit(codepoint, radix); + if (!digit.has_value()) + break; + had_digits = true; + number *= radix; + number += digit.value(); + } + + if (!had_digits) + return js_nan(); + + return Value(sign * number); +} + +Optional<Variable> GlobalObject::get_from_scope(const FlyString& name) const +{ + auto value = get(name); + if (value.is_empty()) + return {}; + return Variable { value, DeclarationKind::Var }; +} + +void GlobalObject::put_to_scope(const FlyString& name, Variable variable) +{ + put(name, variable.value); +} + +bool GlobalObject::has_this_binding() const +{ + return true; +} + +Value GlobalObject::get_this_binding(GlobalObject&) const +{ + return Value(this); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.h b/Userland/Libraries/LibJS/Runtime/GlobalObject.h new file mode 100644 index 0000000000..d3b7041e43 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/ScopeObject.h> +#include <LibJS/Runtime/VM.h> + +namespace JS { + +class GlobalObject : public ScopeObject { + JS_OBJECT(GlobalObject, ScopeObject); + +public: + explicit GlobalObject(); + virtual void initialize(); + + virtual ~GlobalObject() override; + + virtual Optional<Variable> get_from_scope(const FlyString&) const override; + virtual void put_to_scope(const FlyString&, Variable) override; + virtual bool has_this_binding() const override; + virtual Value get_this_binding(GlobalObject&) const override; + + Console& console() { return *m_console; } + + Shape* empty_object_shape() { return m_empty_object_shape; } + + Shape* new_object_shape() { return m_new_object_shape; } + Shape* new_script_function_prototype_object_shape() { return m_new_script_function_prototype_object_shape; } + + // Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype + ProxyConstructor* proxy_constructor() { return m_proxy_constructor; } + +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + ConstructorName* snake_name##_constructor() { return m_##snake_name##_constructor; } \ + Object* snake_name##_prototype() { return m_##snake_name##_prototype; } + JS_ENUMERATE_BUILTIN_TYPES +#undef __JS_ENUMERATE + +#define __JS_ENUMERATE(ClassName, snake_name) \ + Object* snake_name##_prototype() { return m_##snake_name##_prototype; } + JS_ENUMERATE_ITERATOR_PROTOTYPES +#undef __JS_ENUMERATE + +protected: + virtual void visit_edges(Visitor&) override; + + template<typename ConstructorType> + void initialize_constructor(const FlyString& property_name, ConstructorType*&, Object* prototype); + template<typename ConstructorType> + void add_constructor(const FlyString& property_name, ConstructorType*&, Object* prototype); + +private: + JS_DECLARE_NATIVE_FUNCTION(gc); + JS_DECLARE_NATIVE_FUNCTION(is_nan); + JS_DECLARE_NATIVE_FUNCTION(is_finite); + JS_DECLARE_NATIVE_FUNCTION(parse_float); + JS_DECLARE_NATIVE_FUNCTION(parse_int); + + NonnullOwnPtr<Console> m_console; + + Shape* m_empty_object_shape { nullptr }; + Shape* m_new_object_shape { nullptr }; + Shape* m_new_script_function_prototype_object_shape { nullptr }; + + // Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype + ProxyConstructor* m_proxy_constructor { nullptr }; + +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + ConstructorName* m_##snake_name##_constructor { nullptr }; \ + Object* m_##snake_name##_prototype { nullptr }; + JS_ENUMERATE_BUILTIN_TYPES +#undef __JS_ENUMERATE + +#define __JS_ENUMERATE(ClassName, snake_name) \ + Object* m_##snake_name##_prototype { nullptr }; + JS_ENUMERATE_ITERATOR_PROTOTYPES +#undef __JS_ENUMERATE +}; + +template<typename ConstructorType> +inline void GlobalObject::initialize_constructor(const FlyString& property_name, ConstructorType*& constructor, Object* prototype) +{ + auto& vm = this->vm(); + constructor = heap().allocate<ConstructorType>(*this, *this); + constructor->define_property(vm.names.name, js_string(heap(), property_name), Attribute::Configurable); + if (vm.exception()) + return; + if (prototype) { + prototype->define_property(vm.names.constructor, constructor, Attribute::Writable | Attribute::Configurable); + if (vm.exception()) + return; + } +} + +template<typename ConstructorType> +inline void GlobalObject::add_constructor(const FlyString& property_name, ConstructorType*& constructor, Object* prototype) +{ + initialize_constructor(property_name, constructor, prototype); + define_property(property_name, constructor, Attribute::Writable | Attribute::Configurable); +} + +inline GlobalObject* Shape::global_object() const +{ + return static_cast<GlobalObject*>(m_global_object); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/IndexedProperties.cpp b/Userland/Libraries/LibJS/Runtime/IndexedProperties.cpp new file mode 100644 index 0000000000..7dc1b4b9a3 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/IndexedProperties.cpp @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <AK/QuickSort.h> +#include <LibJS/Runtime/Accessor.h> +#include <LibJS/Runtime/IndexedProperties.h> + +namespace JS { + +SimpleIndexedPropertyStorage::SimpleIndexedPropertyStorage(Vector<Value>&& initial_values) + : m_array_size(initial_values.size()) + , m_packed_elements(move(initial_values)) +{ +} + +bool SimpleIndexedPropertyStorage::has_index(u32 index) const +{ + return index < m_array_size && !m_packed_elements[index].is_empty(); +} + +Optional<ValueAndAttributes> SimpleIndexedPropertyStorage::get(u32 index) const +{ + if (index >= m_array_size) + return {}; + return ValueAndAttributes { m_packed_elements[index], default_attributes }; +} + +void SimpleIndexedPropertyStorage::put(u32 index, Value value, PropertyAttributes attributes) +{ + ASSERT(attributes == default_attributes); + ASSERT(index < SPARSE_ARRAY_THRESHOLD); + + if (index >= m_array_size) { + m_array_size = index + 1; + if (index >= m_packed_elements.size()) + m_packed_elements.resize(index + MIN_PACKED_RESIZE_AMOUNT >= SPARSE_ARRAY_THRESHOLD ? SPARSE_ARRAY_THRESHOLD : index + MIN_PACKED_RESIZE_AMOUNT); + } + m_packed_elements[index] = value; +} + +void SimpleIndexedPropertyStorage::remove(u32 index) +{ + if (index < m_array_size) + m_packed_elements[index] = {}; +} + +void SimpleIndexedPropertyStorage::insert(u32 index, Value value, PropertyAttributes attributes) +{ + ASSERT(attributes == default_attributes); + ASSERT(index < SPARSE_ARRAY_THRESHOLD); + m_array_size++; + ASSERT(m_array_size <= SPARSE_ARRAY_THRESHOLD); + m_packed_elements.insert(index, value); +} + +ValueAndAttributes SimpleIndexedPropertyStorage::take_first() +{ + m_array_size--; + return { m_packed_elements.take_first(), default_attributes }; +} + +ValueAndAttributes SimpleIndexedPropertyStorage::take_last() +{ + m_array_size--; + auto last_element = m_packed_elements[m_array_size]; + m_packed_elements[m_array_size] = {}; + return { last_element, default_attributes }; +} + +void SimpleIndexedPropertyStorage::set_array_like_size(size_t new_size) +{ + ASSERT(new_size <= SPARSE_ARRAY_THRESHOLD); + m_array_size = new_size; + m_packed_elements.resize(new_size); +} + +GenericIndexedPropertyStorage::GenericIndexedPropertyStorage(SimpleIndexedPropertyStorage&& storage) +{ + m_array_size = storage.array_like_size(); + for (auto& element : move(storage.m_packed_elements)) + m_packed_elements.append({ element, default_attributes }); +} + +bool GenericIndexedPropertyStorage::has_index(u32 index) const +{ + if (index < SPARSE_ARRAY_THRESHOLD) + return index < m_packed_elements.size() && !m_packed_elements[index].value.is_empty(); + return m_sparse_elements.contains(index); +} + +Optional<ValueAndAttributes> GenericIndexedPropertyStorage::get(u32 index) const +{ + if (index >= m_array_size) + return {}; + if (index < SPARSE_ARRAY_THRESHOLD) { + if (index >= m_packed_elements.size()) + return {}; + return m_packed_elements[index]; + } + return m_sparse_elements.get(index); +} + +void GenericIndexedPropertyStorage::put(u32 index, Value value, PropertyAttributes attributes) +{ + if (index >= m_array_size) + m_array_size = index + 1; + if (index < SPARSE_ARRAY_THRESHOLD) { + if (index >= m_packed_elements.size()) + m_packed_elements.resize(index + MIN_PACKED_RESIZE_AMOUNT >= SPARSE_ARRAY_THRESHOLD ? SPARSE_ARRAY_THRESHOLD : index + MIN_PACKED_RESIZE_AMOUNT); + m_packed_elements[index] = { value, attributes }; + } else { + m_sparse_elements.set(index, { value, attributes }); + } +} + +void GenericIndexedPropertyStorage::remove(u32 index) +{ + if (index >= m_array_size) + return; + if (index + 1 == m_array_size) { + take_last(); + return; + } + if (index < SPARSE_ARRAY_THRESHOLD) { + if (index < m_packed_elements.size()) + m_packed_elements[index] = {}; + } else { + m_sparse_elements.remove(index); + } +} + +void GenericIndexedPropertyStorage::insert(u32 index, Value value, PropertyAttributes attributes) +{ + if (index >= m_array_size) { + put(index, value, attributes); + return; + } + + m_array_size++; + + if (!m_sparse_elements.is_empty()) { + HashMap<u32, ValueAndAttributes> new_sparse_elements; + for (auto& entry : m_sparse_elements) + new_sparse_elements.set(entry.key >= index ? entry.key + 1 : entry.key, entry.value); + m_sparse_elements = move(new_sparse_elements); + } + + if (index < SPARSE_ARRAY_THRESHOLD) { + m_packed_elements.insert(index, { value, attributes }); + } else { + m_sparse_elements.set(index, { value, attributes }); + } +} + +ValueAndAttributes GenericIndexedPropertyStorage::take_first() +{ + ASSERT(m_array_size > 0); + m_array_size--; + + if (!m_sparse_elements.is_empty()) { + HashMap<u32, ValueAndAttributes> new_sparse_elements; + for (auto& entry : m_sparse_elements) + new_sparse_elements.set(entry.key - 1, entry.value); + m_sparse_elements = move(new_sparse_elements); + } + + return m_packed_elements.take_first(); +} + +ValueAndAttributes GenericIndexedPropertyStorage::take_last() +{ + ASSERT(m_array_size > 0); + m_array_size--; + + if (m_array_size <= SPARSE_ARRAY_THRESHOLD) { + auto last_element = m_packed_elements[m_array_size]; + m_packed_elements[m_array_size] = {}; + return last_element; + } else { + auto result = m_sparse_elements.get(m_array_size); + m_sparse_elements.remove(m_array_size); + ASSERT(result.has_value()); + return result.value(); + } +} + +void GenericIndexedPropertyStorage::set_array_like_size(size_t new_size) +{ + m_array_size = new_size; + if (new_size < SPARSE_ARRAY_THRESHOLD) { + m_packed_elements.resize(new_size); + m_sparse_elements.clear(); + } else { + m_packed_elements.resize(SPARSE_ARRAY_THRESHOLD); + + HashMap<u32, ValueAndAttributes> new_sparse_elements; + for (auto& entry : m_sparse_elements) { + if (entry.key < new_size) + new_sparse_elements.set(entry.key, entry.value); + } + m_sparse_elements = move(new_sparse_elements); + } +} + +IndexedPropertyIterator::IndexedPropertyIterator(const IndexedProperties& indexed_properties, u32 staring_index, bool skip_empty) + : m_indexed_properties(indexed_properties) + , m_index(staring_index) + , m_skip_empty(skip_empty) +{ + if (m_skip_empty) + skip_empty_indices(); +} + +IndexedPropertyIterator& IndexedPropertyIterator::operator++() +{ + m_index++; + + if (m_skip_empty) + skip_empty_indices(); + + return *this; +} + +IndexedPropertyIterator& IndexedPropertyIterator::operator*() +{ + return *this; +} + +bool IndexedPropertyIterator::operator!=(const IndexedPropertyIterator& other) const +{ + return m_index != other.m_index; +} + +ValueAndAttributes IndexedPropertyIterator::value_and_attributes(Object* this_object, bool evaluate_accessors) +{ + if (m_index < m_indexed_properties.array_like_size()) + return m_indexed_properties.get(this_object, m_index, evaluate_accessors).value_or({}); + return {}; +} + +void IndexedPropertyIterator::skip_empty_indices() +{ + auto indices = m_indexed_properties.indices(); + for (auto i : indices) { + if (i < m_index) + continue; + m_index = i; + return; + } + m_index = m_indexed_properties.array_like_size(); +} + +Optional<ValueAndAttributes> IndexedProperties::get(Object* this_object, u32 index, bool evaluate_accessors) const +{ + auto result = m_storage->get(index); + if (!evaluate_accessors) + return result; + if (!result.has_value()) + return {}; + auto& value = result.value(); + if (value.value.is_accessor()) { + ASSERT(this_object); + auto& accessor = value.value.as_accessor(); + return ValueAndAttributes { accessor.call_getter(this_object), value.attributes }; + } + return result; +} + +void IndexedProperties::put(Object* this_object, u32 index, Value value, PropertyAttributes attributes, bool evaluate_accessors) +{ + if (m_storage->is_simple_storage() && (index >= SPARSE_ARRAY_THRESHOLD || attributes != default_attributes)) + switch_to_generic_storage(); + if (m_storage->is_simple_storage() || !evaluate_accessors) { + m_storage->put(index, value, attributes); + return; + } + + auto value_here = m_storage->get(index); + if (value_here.has_value() && value_here.value().value.is_accessor()) { + ASSERT(this_object); + value_here.value().value.as_accessor().call_setter(this_object, value); + } else { + m_storage->put(index, value, attributes); + } +} + +bool IndexedProperties::remove(u32 index) +{ + auto result = m_storage->get(index); + if (!result.has_value()) + return true; + if (!result.value().attributes.is_configurable()) + return false; + m_storage->remove(index); + return true; +} + +void IndexedProperties::insert(u32 index, Value value, PropertyAttributes attributes) +{ + if (m_storage->is_simple_storage() && (index >= SPARSE_ARRAY_THRESHOLD || attributes != default_attributes || array_like_size() == SPARSE_ARRAY_THRESHOLD)) + switch_to_generic_storage(); + m_storage->insert(index, move(value), attributes); +} + +ValueAndAttributes IndexedProperties::take_first(Object* this_object) +{ + auto first = m_storage->take_first(); + if (first.value.is_accessor()) + return { first.value.as_accessor().call_getter(this_object), first.attributes }; + return first; +} + +ValueAndAttributes IndexedProperties::take_last(Object* this_object) +{ + auto last = m_storage->take_last(); + if (last.value.is_accessor()) + return { last.value.as_accessor().call_getter(this_object), last.attributes }; + return last; +} + +void IndexedProperties::append_all(Object* this_object, const IndexedProperties& properties, bool evaluate_accessors) +{ + if (m_storage->is_simple_storage() && !properties.m_storage->is_simple_storage()) + switch_to_generic_storage(); + + for (auto it = properties.begin(false); it != properties.end(); ++it) { + const auto& element = it.value_and_attributes(this_object, evaluate_accessors); + if (this_object && this_object->vm().exception()) + return; + m_storage->put(m_storage->array_like_size(), element.value, element.attributes); + } +} + +void IndexedProperties::set_array_like_size(size_t new_size) +{ + if (m_storage->is_simple_storage() && new_size > SPARSE_ARRAY_THRESHOLD) + switch_to_generic_storage(); + m_storage->set_array_like_size(new_size); +} + +Vector<u32> IndexedProperties::indices() const +{ + Vector<u32> indices; + if (m_storage->is_simple_storage()) { + const auto& storage = static_cast<const SimpleIndexedPropertyStorage&>(*m_storage); + const auto& elements = storage.elements(); + indices.ensure_capacity(storage.array_like_size()); + for (size_t i = 0; i < elements.size(); ++i) { + if (!elements.at(i).is_empty()) + indices.unchecked_append(i); + } + } else { + const auto& storage = static_cast<const GenericIndexedPropertyStorage&>(*m_storage); + const auto packed_elements = storage.packed_elements(); + indices.ensure_capacity(storage.array_like_size()); + for (size_t i = 0; i < packed_elements.size(); ++i) { + if (!packed_elements.at(i).value.is_empty()) + indices.unchecked_append(i); + } + auto sparse_elements_keys = storage.sparse_elements().keys(); + quick_sort(sparse_elements_keys); + indices.append(move(sparse_elements_keys)); + } + return indices; +} + +void IndexedProperties::switch_to_generic_storage() +{ + auto& storage = static_cast<SimpleIndexedPropertyStorage&>(*m_storage); + m_storage = make<GenericIndexedPropertyStorage>(move(storage)); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/IndexedProperties.h b/Userland/Libraries/LibJS/Runtime/IndexedProperties.h new file mode 100644 index 0000000000..984c3cef4c --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/IndexedProperties.h @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <AK/NonnullOwnPtr.h> +#include <LibJS/Runtime/Shape.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +const u32 SPARSE_ARRAY_THRESHOLD = 200; +const u32 MIN_PACKED_RESIZE_AMOUNT = 20; + +struct ValueAndAttributes { + Value value; + PropertyAttributes attributes { default_attributes }; +}; + +class IndexedProperties; +class IndexedPropertyIterator; +class GenericIndexedPropertyStorage; + +class IndexedPropertyStorage { +public: + virtual ~IndexedPropertyStorage() {}; + + virtual bool has_index(u32 index) const = 0; + virtual Optional<ValueAndAttributes> get(u32 index) const = 0; + virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) = 0; + virtual void remove(u32 index) = 0; + + virtual void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes) = 0; + virtual ValueAndAttributes take_first() = 0; + virtual ValueAndAttributes take_last() = 0; + + virtual size_t size() const = 0; + virtual size_t array_like_size() const = 0; + virtual void set_array_like_size(size_t new_size) = 0; + + virtual bool is_simple_storage() const { return false; } +}; + +class SimpleIndexedPropertyStorage final : public IndexedPropertyStorage { +public: + SimpleIndexedPropertyStorage() = default; + explicit SimpleIndexedPropertyStorage(Vector<Value>&& initial_values); + + virtual bool has_index(u32 index) const override; + virtual Optional<ValueAndAttributes> get(u32 index) const override; + virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) override; + virtual void remove(u32 index) override; + + virtual void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes) override; + virtual ValueAndAttributes take_first() override; + virtual ValueAndAttributes take_last() override; + + virtual size_t size() const override { return m_packed_elements.size(); } + virtual size_t array_like_size() const override { return m_array_size; } + virtual void set_array_like_size(size_t new_size) override; + + virtual bool is_simple_storage() const override { return true; } + const Vector<Value>& elements() const { return m_packed_elements; } + +private: + friend GenericIndexedPropertyStorage; + + size_t m_array_size { 0 }; + Vector<Value> m_packed_elements; +}; + +class GenericIndexedPropertyStorage final : public IndexedPropertyStorage { +public: + explicit GenericIndexedPropertyStorage(SimpleIndexedPropertyStorage&&); + + virtual bool has_index(u32 index) const override; + virtual Optional<ValueAndAttributes> get(u32 index) const override; + virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) override; + virtual void remove(u32 index) override; + + virtual void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes) override; + virtual ValueAndAttributes take_first() override; + virtual ValueAndAttributes take_last() override; + + virtual size_t size() const override { return m_packed_elements.size() + m_sparse_elements.size(); } + virtual size_t array_like_size() const override { return m_array_size; } + virtual void set_array_like_size(size_t new_size) override; + + const Vector<ValueAndAttributes>& packed_elements() const { return m_packed_elements; } + const HashMap<u32, ValueAndAttributes>& sparse_elements() const { return m_sparse_elements; } + +private: + size_t m_array_size { 0 }; + Vector<ValueAndAttributes> m_packed_elements; + HashMap<u32, ValueAndAttributes> m_sparse_elements; +}; + +class IndexedPropertyIterator { +public: + IndexedPropertyIterator(const IndexedProperties&, u32 starting_index, bool skip_empty); + + IndexedPropertyIterator& operator++(); + IndexedPropertyIterator& operator*(); + bool operator!=(const IndexedPropertyIterator&) const; + + u32 index() const { return m_index; }; + ValueAndAttributes value_and_attributes(Object* this_object, bool evaluate_accessors = true); + +private: + void skip_empty_indices(); + + const IndexedProperties& m_indexed_properties; + u32 m_index; + bool m_skip_empty; +}; + +class IndexedProperties { +public: + IndexedProperties() = default; + + IndexedProperties(Vector<Value>&& values) + : m_storage(make<SimpleIndexedPropertyStorage>(move(values))) + { + } + + bool has_index(u32 index) const { return m_storage->has_index(index); } + Optional<ValueAndAttributes> get(Object* this_object, u32 index, bool evaluate_accessors = true) const; + void put(Object* this_object, u32 index, Value value, PropertyAttributes attributes = default_attributes, bool evaluate_accessors = true); + bool remove(u32 index); + + void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes); + ValueAndAttributes take_first(Object* this_object); + ValueAndAttributes take_last(Object* this_object); + + void append(Value value, PropertyAttributes attributes = default_attributes) { put(nullptr, array_like_size(), value, attributes, false); } + void append_all(Object* this_object, const IndexedProperties& properties, bool evaluate_accessors = true); + + IndexedPropertyIterator begin(bool skip_empty = true) const { return IndexedPropertyIterator(*this, 0, skip_empty); }; + IndexedPropertyIterator end() const { return IndexedPropertyIterator(*this, array_like_size(), false); }; + + bool is_empty() const { return array_like_size() == 0; } + size_t array_like_size() const { return m_storage->array_like_size(); } + void set_array_like_size(size_t); + + Vector<u32> indices() const; + + template<typename Callback> + void for_each_value(Callback callback) + { + if (m_storage->is_simple_storage()) { + for (auto& value : static_cast<SimpleIndexedPropertyStorage&>(*m_storage).elements()) + callback(value); + } else { + for (auto& element : static_cast<const GenericIndexedPropertyStorage&>(*m_storage).packed_elements()) + callback(element.value); + for (auto& element : static_cast<const GenericIndexedPropertyStorage&>(*m_storage).sparse_elements()) + callback(element.value.value); + } + } + +private: + void switch_to_generic_storage(); + + NonnullOwnPtr<IndexedPropertyStorage> m_storage { make<SimpleIndexedPropertyStorage>() }; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/IteratorOperations.cpp b/Userland/Libraries/LibJS/Runtime/IteratorOperations.cpp new file mode 100644 index 0000000000..ee6a8ed057 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/IteratorOperations.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/IteratorOperations.h> + +namespace JS { + +Object* get_iterator(GlobalObject& global_object, Value value, String hint, Value method) +{ + auto& vm = global_object.vm(); + ASSERT(hint == "sync" || hint == "async"); + if (method.is_empty()) { + if (hint == "async") + TODO(); + auto object = value.to_object(global_object); + if (!object) + return {}; + method = object->get(global_object.vm().well_known_symbol_iterator()); + if (vm.exception()) + return {}; + } + if (!method.is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotIterable, value.to_string_without_side_effects()); + return nullptr; + } + auto iterator = vm.call(method.as_function(), value); + if (vm.exception()) + return {}; + if (!iterator.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotIterable, value.to_string_without_side_effects()); + return nullptr; + } + return &iterator.as_object(); +} + +Object* iterator_next(Object& iterator, Value value) +{ + auto& vm = iterator.vm(); + auto& global_object = iterator.global_object(); + auto next_method = iterator.get(vm.names.next); + if (vm.exception()) + return {}; + + if (!next_method.is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::IterableNextNotAFunction); + return nullptr; + } + + Value result; + if (value.is_empty()) + result = vm.call(next_method.as_function(), &iterator); + else + result = vm.call(next_method.as_function(), &iterator, value); + + if (vm.exception()) + return {}; + if (!result.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::IterableNextBadReturn); + return nullptr; + } + + return &result.as_object(); +} + +void iterator_close([[maybe_unused]] Object& iterator) +{ + TODO(); +} + +Value create_iterator_result_object(GlobalObject& global_object, Value value, bool done) +{ + auto& vm = global_object.vm(); + auto* object = Object::create_empty(global_object); + object->define_property(vm.names.value, value); + object->define_property(vm.names.done, Value(done)); + return object; +} + +void get_iterator_values(GlobalObject& global_object, Value value, AK::Function<IterationDecision(Value)> callback) +{ + auto& vm = global_object.vm(); + + auto iterator = get_iterator(global_object, value); + if (!iterator) + return; + + while (true) { + auto next_object = iterator_next(*iterator); + if (!next_object) + return; + + auto done_property = next_object->get(vm.names.done); + if (vm.exception()) + return; + + if (!done_property.is_empty() && done_property.to_boolean()) + return; + + auto next_value = next_object->get(vm.names.value); + if (vm.exception()) + return; + + auto result = callback(next_value); + if (result == IterationDecision::Break) + return; + ASSERT(result == IterationDecision::Continue); + } +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/IteratorOperations.h b/Userland/Libraries/LibJS/Runtime/IteratorOperations.h new file mode 100644 index 0000000000..db113eec97 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/IteratorOperations.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <AK/Function.h> +#include <LibJS/Runtime/Object.h> + +namespace JS { + +// Common iterator operations defined in ECMA262 7.4 +// https://tc39.es/ecma262/#sec-operations-on-iterator-objects + +Object* get_iterator(GlobalObject&, Value value, String hint = "sync", Value method = {}); +bool is_iterator_complete(Object& iterator_result); +Value create_iterator_result_object(GlobalObject&, Value value, bool done); + +Object* iterator_next(Object& iterator, Value value = {}); +void iterator_close(Object& iterator); + +void get_iterator_values(GlobalObject&, Value value, AK::Function<IterationDecision(Value)> callback); + +} diff --git a/Userland/Libraries/LibJS/Runtime/IteratorPrototype.cpp b/Userland/Libraries/LibJS/Runtime/IteratorPrototype.cpp new file mode 100644 index 0000000000..32eef36315 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/IteratorPrototype.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <AK/Function.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/IteratorPrototype.h> + +namespace JS { + +IteratorPrototype::IteratorPrototype(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void IteratorPrototype::initialize(GlobalObject& global_object) +{ + Object::initialize(global_object); + define_native_function(global_object.vm().well_known_symbol_iterator(), symbol_iterator, 0, Attribute::Writable | Attribute::Enumerable); +} + +IteratorPrototype::~IteratorPrototype() +{ +} + +JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::symbol_iterator) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + return this_object; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/IteratorPrototype.h b/Userland/Libraries/LibJS/Runtime/IteratorPrototype.h new file mode 100644 index 0000000000..72d37b2540 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/IteratorPrototype.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class IteratorPrototype : public Object { + JS_OBJECT(IteratorPrototype, Object) + +public: + IteratorPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~IteratorPrototype() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(symbol_iterator); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/JSONObject.cpp b/Userland/Libraries/LibJS/Runtime/JSONObject.cpp new file mode 100644 index 0000000000..dddd82bfa0 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/JSONObject.cpp @@ -0,0 +1,507 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <AK/Function.h> +#include <AK/JsonArray.h> +#include <AK/JsonObject.h> +#include <AK/JsonParser.h> +#include <AK/StringBuilder.h> +#include <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/BigIntObject.h> +#include <LibJS/Runtime/BooleanObject.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/JSONObject.h> +#include <LibJS/Runtime/NumberObject.h> +#include <LibJS/Runtime/Object.h> +#include <LibJS/Runtime/StringObject.h> + +namespace JS { + +JSONObject::JSONObject(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void JSONObject::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.stringify, stringify, 3, attr); + define_native_function(vm.names.parse, parse, 2, attr); + define_property(vm.well_known_symbol_to_string_tag(), js_string(global_object.heap(), "JSON"), Attribute::Configurable); +} + +JSONObject::~JSONObject() +{ +} + +String JSONObject::stringify_impl(GlobalObject& global_object, Value value, Value replacer, Value space) +{ + auto& vm = global_object.vm(); + StringifyState state; + + if (replacer.is_object()) { + if (replacer.as_object().is_function()) { + state.replacer_function = &replacer.as_function(); + } else if (replacer.is_array()) { + auto& replacer_object = replacer.as_object(); + auto replacer_length = length_of_array_like(global_object, replacer_object); + if (vm.exception()) + return {}; + Vector<String> list; + for (size_t i = 0; i < replacer_length; ++i) { + auto replacer_value = replacer_object.get(i); + if (vm.exception()) + return {}; + String item; + if (replacer_value.is_string() || replacer_value.is_number()) { + item = replacer_value.to_string(global_object); + if (vm.exception()) + return {}; + } else if (replacer_value.is_object()) { + auto& value_object = replacer_value.as_object(); + if (is<StringObject>(value_object) || is<NumberObject>(value_object)) { + item = value_object.value_of().to_string(global_object); + if (vm.exception()) + return {}; + } + } + if (!item.is_null() && !list.contains_slow(item)) { + list.append(item); + } + } + state.property_list = list; + } + } + + if (space.is_object()) { + auto& space_obj = space.as_object(); + if (is<StringObject>(space_obj) || is<NumberObject>(space_obj)) + space = space_obj.value_of(); + } + + if (space.is_number()) { + StringBuilder gap_builder; + auto gap_size = min(10, space.as_i32()); + for (auto i = 0; i < gap_size; ++i) + gap_builder.append(' '); + state.gap = gap_builder.to_string(); + } else if (space.is_string()) { + auto string = space.as_string().string(); + if (string.length() <= 10) { + state.gap = string; + } else { + state.gap = string.substring(0, 10); + } + } else { + state.gap = String::empty(); + } + + auto* wrapper = Object::create_empty(global_object); + wrapper->define_property(String::empty(), value); + if (vm.exception()) + return {}; + auto result = serialize_json_property(global_object, state, String::empty(), wrapper); + if (vm.exception()) + return {}; + if (result.is_null()) + return {}; + + return result; +} + +JS_DEFINE_NATIVE_FUNCTION(JSONObject::stringify) +{ + if (!vm.argument_count()) + return js_undefined(); + + auto value = vm.argument(0); + auto replacer = vm.argument(1); + auto space = vm.argument(2); + + auto string = stringify_impl(global_object, value, replacer, space); + if (string.is_null()) + return js_undefined(); + + return js_string(vm, string); +} + +String JSONObject::serialize_json_property(GlobalObject& global_object, StringifyState& state, const PropertyName& key, Object* holder) +{ + auto& vm = global_object.vm(); + auto value = holder->get(key); + if (vm.exception()) + return {}; + if (value.is_object()) { + auto to_json = value.as_object().get(vm.names.toJSON); + if (vm.exception()) + return {}; + if (to_json.is_function()) { + value = vm.call(to_json.as_function(), value, js_string(vm, key.to_string())); + if (vm.exception()) + return {}; + } + } + + if (state.replacer_function) { + value = vm.call(*state.replacer_function, holder, js_string(vm, key.to_string()), value); + if (vm.exception()) + return {}; + } + + if (value.is_object()) { + auto& value_object = value.as_object(); + if (is<NumberObject>(value_object) || is<BooleanObject>(value_object) || is<StringObject>(value_object) || is<BigIntObject>(value_object)) + value = value_object.value_of(); + } + + if (value.is_null()) + return "null"; + if (value.is_boolean()) + return value.as_bool() ? "true" : "false"; + if (value.is_string()) + return quote_json_string(value.as_string().string()); + if (value.is_number()) { + if (value.is_finite_number()) + return value.to_string(global_object); + return "null"; + } + if (value.is_object() && !value.is_function()) { + if (value.is_array()) + return serialize_json_array(global_object, state, static_cast<Array&>(value.as_object())); + return serialize_json_object(global_object, state, value.as_object()); + } + if (value.is_bigint()) + vm.throw_exception<TypeError>(global_object, ErrorType::JsonBigInt); + return {}; +} + +String JSONObject::serialize_json_object(GlobalObject& global_object, StringifyState& state, Object& object) +{ + auto& vm = global_object.vm(); + if (state.seen_objects.contains(&object)) { + vm.throw_exception<TypeError>(global_object, ErrorType::JsonCircular); + return {}; + } + + state.seen_objects.set(&object); + String previous_indent = state.indent; + state.indent = String::formatted("{}{}", state.indent, state.gap); + Vector<String> property_strings; + + auto process_property = [&](const PropertyName& key) { + auto serialized_property_string = serialize_json_property(global_object, state, key, &object); + if (vm.exception()) + return; + if (!serialized_property_string.is_null()) { + property_strings.append(String::formatted( + "{}:{}{}", + quote_json_string(key.to_string()), + state.gap.is_empty() ? "" : " ", + serialized_property_string)); + } + }; + + if (state.property_list.has_value()) { + auto property_list = state.property_list.value(); + for (auto& property : property_list) { + process_property(property); + if (vm.exception()) + return {}; + } + } else { + for (auto& entry : object.indexed_properties()) { + auto value_and_attributes = entry.value_and_attributes(&object); + if (!value_and_attributes.attributes.is_enumerable()) + continue; + process_property(entry.index()); + if (vm.exception()) + return {}; + } + for (auto& [key, metadata] : object.shape().property_table_ordered()) { + if (!metadata.attributes.is_enumerable()) + continue; + process_property(key); + if (vm.exception()) + return {}; + } + } + StringBuilder builder; + if (property_strings.is_empty()) { + builder.append("{}"); + } else { + bool first = true; + builder.append('{'); + if (state.gap.is_empty()) { + for (auto& property_string : property_strings) { + if (!first) + builder.append(','); + first = false; + builder.append(property_string); + } + } else { + builder.append('\n'); + builder.append(state.indent); + auto separator = String::formatted(",\n{}", state.indent); + for (auto& property_string : property_strings) { + if (!first) + builder.append(separator); + first = false; + builder.append(property_string); + } + builder.append('\n'); + builder.append(previous_indent); + } + builder.append('}'); + } + + state.seen_objects.remove(&object); + state.indent = previous_indent; + return builder.to_string(); +} + +String JSONObject::serialize_json_array(GlobalObject& global_object, StringifyState& state, Object& object) +{ + auto& vm = global_object.vm(); + if (state.seen_objects.contains(&object)) { + vm.throw_exception<TypeError>(global_object, ErrorType::JsonCircular); + return {}; + } + + state.seen_objects.set(&object); + String previous_indent = state.indent; + state.indent = String::formatted("{}{}", state.indent, state.gap); + Vector<String> property_strings; + + auto length = length_of_array_like(global_object, object); + if (vm.exception()) + return {}; + for (size_t i = 0; i < length; ++i) { + if (vm.exception()) + return {}; + auto serialized_property_string = serialize_json_property(global_object, state, i, &object); + if (vm.exception()) + return {}; + if (serialized_property_string.is_null()) { + property_strings.append("null"); + } else { + property_strings.append(serialized_property_string); + } + } + + StringBuilder builder; + if (property_strings.is_empty()) { + builder.append("[]"); + } else { + if (state.gap.is_empty()) { + builder.append('['); + bool first = true; + for (auto& property_string : property_strings) { + if (!first) + builder.append(','); + first = false; + builder.append(property_string); + } + builder.append(']'); + } else { + builder.append("[\n"); + builder.append(state.indent); + auto separator = String::formatted(",\n{}", state.indent); + bool first = true; + for (auto& property_string : property_strings) { + if (!first) + builder.append(separator); + first = false; + builder.append(property_string); + } + builder.append('\n'); + builder.append(previous_indent); + builder.append(']'); + } + } + + state.seen_objects.remove(&object); + state.indent = previous_indent; + return builder.to_string(); +} + +String JSONObject::quote_json_string(String string) +{ + // FIXME: Handle UTF16 + StringBuilder builder; + builder.append('"'); + for (auto& ch : string) { + switch (ch) { + case '\b': + builder.append("\\b"); + break; + case '\t': + builder.append("\\t"); + break; + case '\n': + builder.append("\\n"); + break; + case '\f': + builder.append("\\f"); + break; + case '\r': + builder.append("\\r"); + break; + case '"': + builder.append("\\\""); + break; + case '\\': + builder.append("\\\\"); + break; + default: + if (ch < 0x20) { + builder.append("\\u%#08x", ch); + } else { + builder.append(ch); + } + } + } + builder.append('"'); + return builder.to_string(); +} + +JS_DEFINE_NATIVE_FUNCTION(JSONObject::parse) +{ + if (!vm.argument_count()) + return js_undefined(); + auto string = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + auto reviver = vm.argument(1); + + auto json = JsonValue::from_string(string); + if (!json.has_value()) { + vm.throw_exception<SyntaxError>(global_object, ErrorType::JsonMalformed); + return {}; + } + Value result = parse_json_value(global_object, json.value()); + if (reviver.is_function()) { + auto* holder_object = Object::create_empty(global_object); + holder_object->define_property(String::empty(), result); + if (vm.exception()) + return {}; + return internalize_json_property(global_object, holder_object, String::empty(), reviver.as_function()); + } + return result; +} + +Value JSONObject::parse_json_value(GlobalObject& global_object, const JsonValue& value) +{ + if (value.is_object()) + return Value(parse_json_object(global_object, value.as_object())); + if (value.is_array()) + return Value(parse_json_array(global_object, value.as_array())); + if (value.is_null()) + return js_null(); +#if !defined(KERNEL) + if (value.is_double()) + return Value(value.as_double()); +#endif + if (value.is_number()) + return Value(value.to_i32(0)); + if (value.is_string()) + return js_string(global_object.heap(), value.to_string()); + if (value.is_bool()) + return Value(static_cast<bool>(value.as_bool())); + ASSERT_NOT_REACHED(); +} + +Object* JSONObject::parse_json_object(GlobalObject& global_object, const JsonObject& json_object) +{ + auto* object = Object::create_empty(global_object); + json_object.for_each_member([&](auto& key, auto& value) { + object->define_property(key, parse_json_value(global_object, value)); + }); + return object; +} + +Array* JSONObject::parse_json_array(GlobalObject& global_object, const JsonArray& json_array) +{ + auto* array = Array::create(global_object); + size_t index = 0; + json_array.for_each([&](auto& value) { + array->define_property(index++, parse_json_value(global_object, value)); + }); + return array; +} + +Value JSONObject::internalize_json_property(GlobalObject& global_object, Object* holder, const PropertyName& name, Function& reviver) +{ + auto& vm = global_object.vm(); + auto value = holder->get(name); + if (vm.exception()) + return {}; + if (value.is_object()) { + auto& value_object = value.as_object(); + + auto process_property = [&](const PropertyName& key) { + auto element = internalize_json_property(global_object, &value_object, key, reviver); + if (vm.exception()) + return; + if (element.is_undefined()) { + value_object.delete_property(key); + } else { + value_object.define_property(key, element, default_attributes, false); + } + }; + + if (value_object.is_array()) { + auto length = length_of_array_like(global_object, value_object); + for (size_t i = 0; i < length; ++i) { + process_property(i); + if (vm.exception()) + return {}; + } + } else { + for (auto& entry : value_object.indexed_properties()) { + auto value_and_attributes = entry.value_and_attributes(&value_object); + if (!value_and_attributes.attributes.is_enumerable()) + continue; + process_property(entry.index()); + if (vm.exception()) + return {}; + } + for (auto& [key, metadata] : value_object.shape().property_table_ordered()) { + if (!metadata.attributes.is_enumerable()) + continue; + process_property(key); + if (vm.exception()) + return {}; + } + } + } + + return vm.call(reviver, Value(holder), js_string(vm, name.to_string()), value); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/JSONObject.h b/Userland/Libraries/LibJS/Runtime/JSONObject.h new file mode 100644 index 0000000000..23fdff2473 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/JSONObject.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class JSONObject final : public Object { + JS_OBJECT(JSONObject, Object); + +public: + explicit JSONObject(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~JSONObject() override; + + // The base implementation of stringify is exposed because it is used by + // test-js to communicate between the JS tests and the C++ test runner. + static String stringify_impl(GlobalObject&, Value value, Value replacer, Value space); + +private: + struct StringifyState { + Function* replacer_function { nullptr }; + HashTable<Object*> seen_objects; + String indent { String::empty() }; + String gap; + Optional<Vector<String>> property_list; + }; + + // Stringify helpers + static String serialize_json_property(GlobalObject&, StringifyState&, const PropertyName& key, Object* holder); + static String serialize_json_object(GlobalObject&, StringifyState&, Object&); + static String serialize_json_array(GlobalObject&, StringifyState&, Object&); + static String quote_json_string(String); + + // Parse helpers + static Object* parse_json_object(GlobalObject&, const JsonObject&); + static Array* parse_json_array(GlobalObject&, const JsonArray&); + static Value parse_json_value(GlobalObject&, const JsonValue&); + static Value internalize_json_property(GlobalObject&, Object* holder, const PropertyName& name, Function& reviver); + + JS_DECLARE_NATIVE_FUNCTION(stringify); + JS_DECLARE_NATIVE_FUNCTION(parse); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/LexicalEnvironment.cpp b/Userland/Libraries/LibJS/Runtime/LexicalEnvironment.cpp new file mode 100644 index 0000000000..2275b6402b --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/LexicalEnvironment.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Interpreter.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/Function.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/LexicalEnvironment.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +LexicalEnvironment::LexicalEnvironment() + : ScopeObject(nullptr) +{ +} + +LexicalEnvironment::LexicalEnvironment(EnvironmentRecordType environment_record_type) + : ScopeObject(nullptr) + , m_environment_record_type(environment_record_type) +{ +} + +LexicalEnvironment::LexicalEnvironment(HashMap<FlyString, Variable> variables, ScopeObject* parent_scope) + : ScopeObject(parent_scope) + , m_variables(move(variables)) +{ +} + +LexicalEnvironment::LexicalEnvironment(HashMap<FlyString, Variable> variables, ScopeObject* parent_scope, EnvironmentRecordType environment_record_type) + : ScopeObject(parent_scope) + , m_environment_record_type(environment_record_type) + , m_variables(move(variables)) +{ +} + +LexicalEnvironment::~LexicalEnvironment() +{ +} + +void LexicalEnvironment::visit_edges(Visitor& visitor) +{ + Cell::visit_edges(visitor); + visitor.visit(m_this_value); + visitor.visit(m_home_object); + visitor.visit(m_new_target); + visitor.visit(m_current_function); + for (auto& it : m_variables) + visitor.visit(it.value.value); +} + +Optional<Variable> LexicalEnvironment::get_from_scope(const FlyString& name) const +{ + return m_variables.get(name); +} + +void LexicalEnvironment::put_to_scope(const FlyString& name, Variable variable) +{ + m_variables.set(name, variable); +} + +bool LexicalEnvironment::has_super_binding() const +{ + return m_environment_record_type == EnvironmentRecordType::Function && this_binding_status() != ThisBindingStatus::Lexical && m_home_object.is_object(); +} + +Value LexicalEnvironment::get_super_base() +{ + ASSERT(has_super_binding()); + if (m_home_object.is_object()) + return m_home_object.as_object().prototype(); + return {}; +} + +bool LexicalEnvironment::has_this_binding() const +{ + // More like "is_capable_of_having_a_this_binding". + switch (m_environment_record_type) { + case EnvironmentRecordType::Declarative: + case EnvironmentRecordType::Object: + return false; + case EnvironmentRecordType::Function: + return this_binding_status() != ThisBindingStatus::Lexical; + case EnvironmentRecordType::Module: + return true; + } + ASSERT_NOT_REACHED(); +} + +Value LexicalEnvironment::get_this_binding(GlobalObject& global_object) const +{ + ASSERT(has_this_binding()); + if (this_binding_status() == ThisBindingStatus::Uninitialized) { + vm().throw_exception<ReferenceError>(global_object, ErrorType::ThisHasNotBeenInitialized); + return {}; + } + return m_this_value; +} + +void LexicalEnvironment::bind_this_value(GlobalObject& global_object, Value this_value) +{ + ASSERT(has_this_binding()); + if (m_this_binding_status == ThisBindingStatus::Initialized) { + vm().throw_exception<ReferenceError>(global_object, ErrorType::ThisIsAlreadyInitialized); + return; + } + m_this_value = this_value; + m_this_binding_status = ThisBindingStatus::Initialized; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/LexicalEnvironment.h b/Userland/Libraries/LibJS/Runtime/LexicalEnvironment.h new file mode 100644 index 0000000000..1e4b4723d1 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/LexicalEnvironment.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/FlyString.h> +#include <AK/HashMap.h> +#include <LibJS/Runtime/ScopeObject.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +class LexicalEnvironment final : public ScopeObject { + JS_OBJECT(LexicalEnvironment, ScopeObject); + +public: + enum class ThisBindingStatus { + Lexical, + Initialized, + Uninitialized, + }; + + enum class EnvironmentRecordType { + Declarative, + Function, + Object, + Module, + }; + + LexicalEnvironment(); + LexicalEnvironment(EnvironmentRecordType); + LexicalEnvironment(HashMap<FlyString, Variable> variables, ScopeObject* parent_scope); + LexicalEnvironment(HashMap<FlyString, Variable> variables, ScopeObject* parent_scope, EnvironmentRecordType); + virtual ~LexicalEnvironment() override; + + // ^ScopeObject + virtual Optional<Variable> get_from_scope(const FlyString&) const override; + virtual void put_to_scope(const FlyString&, Variable) override; + virtual bool has_this_binding() const override; + virtual Value get_this_binding(GlobalObject&) const override; + + void clear(); + + const HashMap<FlyString, Variable>& variables() const { return m_variables; } + + void set_home_object(Value object) { m_home_object = object; } + bool has_super_binding() const; + Value get_super_base(); + + ThisBindingStatus this_binding_status() const { return m_this_binding_status; } + void bind_this_value(GlobalObject&, Value this_value); + + // Not a standard operation. + void replace_this_binding(Value this_value) { m_this_value = this_value; } + + Value new_target() const { return m_new_target; }; + void set_new_target(Value new_target) { m_new_target = new_target; } + + Function* current_function() const { return m_current_function; } + void set_current_function(Function& function) { m_current_function = &function; } + + EnvironmentRecordType type() const { return m_environment_record_type; } + +private: + virtual void visit_edges(Visitor&) override; + + EnvironmentRecordType m_environment_record_type : 8 { EnvironmentRecordType::Declarative }; + ThisBindingStatus m_this_binding_status : 8 { ThisBindingStatus::Uninitialized }; + HashMap<FlyString, Variable> m_variables; + Value m_home_object; + Value m_this_value; + Value m_new_target; + // Corresponds to [[FunctionObject]] + Function* m_current_function { nullptr }; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/MarkedValueList.cpp b/Userland/Libraries/LibJS/Runtime/MarkedValueList.cpp new file mode 100644 index 0000000000..f268a21c6b --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/MarkedValueList.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/MarkedValueList.h> + +namespace JS { + +MarkedValueList::MarkedValueList(Heap& heap) + : m_heap(heap) +{ + m_heap.did_create_marked_value_list({}, *this); +} + +MarkedValueList::MarkedValueList(MarkedValueList&& other) + : AK::Vector<Value, 32>(move(static_cast<Vector<Value, 32>&>(other))) + , m_heap(other.m_heap) +{ + m_heap.did_create_marked_value_list({}, *this); +} + +MarkedValueList::~MarkedValueList() +{ + m_heap.did_destroy_marked_value_list({}, *this); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/MarkedValueList.h b/Userland/Libraries/LibJS/Runtime/MarkedValueList.h new file mode 100644 index 0000000000..e311972181 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/MarkedValueList.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/Noncopyable.h> +#include <AK/Vector.h> +#include <LibJS/Forward.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +class MarkedValueList : public AK::Vector<Value, 32> { + AK_MAKE_NONCOPYABLE(MarkedValueList); + +public: + explicit MarkedValueList(Heap&); + MarkedValueList(MarkedValueList&&); + ~MarkedValueList(); + + MarkedValueList& operator=(MarkedValueList&&) = delete; + + Vector<Value, 32>& values() { return *this; } + + MarkedValueList copy() const + { + MarkedValueList copy { m_heap }; + copy.append(*this); + return copy; + } + +private: + Heap& m_heap; +}; +} diff --git a/Userland/Libraries/LibJS/Runtime/MathObject.cpp b/Userland/Libraries/LibJS/Runtime/MathObject.cpp new file mode 100644 index 0000000000..bdd3ec6e17 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/MathObject.cpp @@ -0,0 +1,504 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <AK/FlyString.h> +#include <AK/Function.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/MathObject.h> +#include <math.h> + +namespace JS { + +MathObject::MathObject(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void MathObject::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.abs, abs, 1, attr); + define_native_function(vm.names.random, random, 0, attr); + define_native_function(vm.names.sqrt, sqrt, 1, attr); + define_native_function(vm.names.floor, floor, 1, attr); + define_native_function(vm.names.ceil, ceil, 1, attr); + define_native_function(vm.names.round, round, 1, attr); + define_native_function(vm.names.max, max, 2, attr); + define_native_function(vm.names.min, min, 2, attr); + define_native_function(vm.names.trunc, trunc, 1, attr); + define_native_function(vm.names.sin, sin, 1, attr); + define_native_function(vm.names.cos, cos, 1, attr); + define_native_function(vm.names.tan, tan, 1, attr); + define_native_function(vm.names.pow, pow, 2, attr); + define_native_function(vm.names.exp, exp, 1, attr); + define_native_function(vm.names.expm1, expm1, 1, attr); + define_native_function(vm.names.sign, sign, 1, attr); + define_native_function(vm.names.clz32, clz32, 1, attr); + define_native_function(vm.names.acos, acos, 1, attr); + define_native_function(vm.names.acosh, acosh, 1, attr); + define_native_function(vm.names.asin, asin, 1, attr); + define_native_function(vm.names.asinh, asinh, 1, attr); + define_native_function(vm.names.atan, atan, 1, attr); + define_native_function(vm.names.atanh, atanh, 1, attr); + define_native_function(vm.names.log1p, log1p, 1, attr); + define_native_function(vm.names.cbrt, cbrt, 1, attr); + define_native_function(vm.names.atan2, atan2, 2, attr); + define_native_function(vm.names.fround, fround, 1, attr); + define_native_function(vm.names.hypot, hypot, 2, attr); + define_native_function(vm.names.log, log, 1, attr); + define_native_function(vm.names.log2, log2, 1, attr); + define_native_function(vm.names.log10, log10, 1, attr); + define_native_function(vm.names.sinh, sinh, 1, attr); + define_native_function(vm.names.cosh, cosh, 1, attr); + define_native_function(vm.names.tanh, tanh, 1, attr); + + define_property(vm.names.E, Value(M_E), 0); + define_property(vm.names.LN2, Value(M_LN2), 0); + define_property(vm.names.LN10, Value(M_LN10), 0); + define_property(vm.names.LOG2E, Value(::log2(M_E)), 0); + define_property(vm.names.LOG10E, Value(::log10(M_E)), 0); + define_property(vm.names.PI, Value(M_PI), 0); + define_property(vm.names.SQRT1_2, Value(M_SQRT1_2), 0); + define_property(vm.names.SQRT2, Value(M_SQRT2), 0); + + define_property(vm.well_known_symbol_to_string_tag(), js_string(vm.heap(), "Math"), Attribute::Configurable); +} + +MathObject::~MathObject() +{ +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::abs) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_nan()) + return js_nan(); + return Value(number.as_double() >= 0 ? number.as_double() : -number.as_double()); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::random) +{ +#ifdef __serenity__ + double r = (double)arc4random() / (double)UINT32_MAX; +#else + double r = (double)rand() / (double)RAND_MAX; +#endif + return Value(r); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::sqrt) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_nan()) + return js_nan(); + return Value(::sqrt(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::floor) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_nan()) + return js_nan(); + return Value(::floor(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::ceil) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_nan()) + return js_nan(); + auto number_double = number.as_double(); + if (number_double < 0 && number_double > -1) + return Value(-0.f); + return Value(::ceil(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::round) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_nan()) + return js_nan(); + return Value(::round(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::max) +{ + if (!vm.argument_count()) + return js_negative_infinity(); + + auto max = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + for (size_t i = 1; i < vm.argument_count(); ++i) { + auto cur = vm.argument(i).to_number(global_object); + if (vm.exception()) + return {}; + max = Value(cur.as_double() > max.as_double() ? cur : max); + } + return max; +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::min) +{ + if (!vm.argument_count()) + return js_infinity(); + + auto min = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + for (size_t i = 1; i < vm.argument_count(); ++i) { + auto cur = vm.argument(i).to_number(global_object); + if (vm.exception()) + return {}; + min = Value(cur.as_double() < min.as_double() ? cur : min); + } + return min; +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::trunc) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_nan()) + return js_nan(); + if (number.as_double() < 0) + return MathObject::ceil(vm, global_object); + return MathObject::floor(vm, global_object); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::sin) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_nan()) + return js_nan(); + return Value(::sin(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::cos) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_nan()) + return js_nan(); + return Value(::cos(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::tan) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_nan()) + return js_nan(); + return Value(::tan(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::pow) +{ + return JS::exp(global_object, vm.argument(0), vm.argument(1)); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::exp) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_nan()) + return js_nan(); + return Value(::exp(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::expm1) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_nan()) + return js_nan(); + return Value(::expm1(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::sign) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_positive_zero()) + return Value(0); + if (number.is_negative_zero()) + return Value(-0.0); + if (number.as_double() > 0) + return Value(1); + if (number.as_double() < 0) + return Value(-1); + return js_nan(); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::clz32) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (!number.is_finite_number() || (unsigned)number.as_double() == 0) + return Value(32); + return Value(__builtin_clz((unsigned)number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::acos) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_nan() || number.as_double() > 1 || number.as_double() < -1) + return js_nan(); + if (number.as_double() == 1) + return Value(0); + return Value(::acos(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::acosh) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.as_double() < 1) + return js_nan(); + return Value(::acosh(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::asin) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_nan() || number.is_positive_zero() || number.is_negative_zero()) + return number; + return Value(::asin(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::asinh) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + return Value(::asinh(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::atan) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_nan() || number.is_positive_zero() || number.is_negative_zero()) + return number; + if (number.is_positive_infinity()) + return Value(M_PI_2); + if (number.is_negative_infinity()) + return Value(-M_PI_2); + return Value(::atan(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::atanh) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.as_double() > 1 || number.as_double() < -1) + return js_nan(); + return Value(::atanh(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::log1p) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.as_double() < -1) + return js_nan(); + return Value(::log1p(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::cbrt) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + return Value(::cbrt(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::atan2) +{ + auto y = vm.argument(0).to_number(global_object), x = vm.argument(1).to_number(global_object); + auto pi_4 = M_PI_2 / 2; + auto three_pi_4 = pi_4 + M_PI_2; + if (vm.exception()) + return {}; + if (x.is_positive_zero()) { + if (y.is_positive_zero() || y.is_negative_zero()) + return y; + else + return (y.as_double() > 0) ? Value(M_PI_2) : Value(-M_PI_2); + } + if (x.is_negative_zero()) { + if (y.is_positive_zero()) + return Value(M_PI); + else if (y.is_negative_zero()) + return Value(-M_PI); + else + return (y.as_double() > 0) ? Value(M_PI_2) : Value(-M_PI_2); + } + if (x.is_positive_infinity()) { + if (y.is_infinity()) + return (y.is_positive_infinity()) ? Value(pi_4) : Value(-pi_4); + else + return (y.as_double() > 0) ? Value(+0.0) : Value(-0.0); + } + if (x.is_negative_infinity()) { + if (y.is_infinity()) + return (y.is_positive_infinity()) ? Value(three_pi_4) : Value(-three_pi_4); + else + return (y.as_double() > 0) ? Value(M_PI) : Value(-M_PI); + } + if (y.is_infinity()) + return (y.is_positive_infinity()) ? Value(M_PI_2) : Value(-M_PI_2); + if (y.is_positive_zero()) + return (x.as_double() > 0) ? Value(+0.0) : Value(M_PI); + if (y.is_negative_zero()) + return (x.as_double() > 0) ? Value(-0.0) : Value(-M_PI); + + return Value(::atan2(y.as_double(), x.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::fround) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_nan()) + return js_nan(); + return Value((float)number.as_double()); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::hypot) +{ + if (!vm.argument_count()) + return Value(0); + + auto hypot = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + hypot = Value(hypot.as_double() * hypot.as_double()); + for (size_t i = 1; i < vm.argument_count(); ++i) { + auto cur = vm.argument(i).to_number(global_object); + if (vm.exception()) + return {}; + hypot = Value(hypot.as_double() + cur.as_double() * cur.as_double()); + } + return Value(::sqrt(hypot.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::log) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.as_double() < 0) + return js_nan(); + return Value(::log(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::log2) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.as_double() < 0) + return js_nan(); + return Value(::log2(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::log10) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.as_double() < 0) + return js_nan(); + return Value(::log10(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::sinh) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_nan()) + return js_nan(); + return Value(::sinh(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::cosh) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_nan()) + return js_nan(); + return Value(::cosh(number.as_double())); +} + +JS_DEFINE_NATIVE_FUNCTION(MathObject::tanh) +{ + auto number = vm.argument(0).to_number(global_object); + if (vm.exception()) + return {}; + if (number.is_nan()) + return js_nan(); + if (number.is_positive_infinity()) + return Value(1); + if (number.is_negative_infinity()) + return Value(-1); + return Value(::tanh(number.as_double())); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/MathObject.h b/Userland/Libraries/LibJS/Runtime/MathObject.h new file mode 100644 index 0000000000..06f09c2e4a --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/MathObject.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class MathObject final : public Object { + JS_OBJECT(MathObject, Object); + +public: + explicit MathObject(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~MathObject() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(abs); + JS_DECLARE_NATIVE_FUNCTION(random); + JS_DECLARE_NATIVE_FUNCTION(sqrt); + JS_DECLARE_NATIVE_FUNCTION(floor); + JS_DECLARE_NATIVE_FUNCTION(ceil); + JS_DECLARE_NATIVE_FUNCTION(round); + JS_DECLARE_NATIVE_FUNCTION(max); + JS_DECLARE_NATIVE_FUNCTION(min); + JS_DECLARE_NATIVE_FUNCTION(trunc); + JS_DECLARE_NATIVE_FUNCTION(sin); + JS_DECLARE_NATIVE_FUNCTION(cos); + JS_DECLARE_NATIVE_FUNCTION(tan); + JS_DECLARE_NATIVE_FUNCTION(pow); + JS_DECLARE_NATIVE_FUNCTION(exp); + JS_DECLARE_NATIVE_FUNCTION(expm1); + JS_DECLARE_NATIVE_FUNCTION(sign); + JS_DECLARE_NATIVE_FUNCTION(clz32); + JS_DECLARE_NATIVE_FUNCTION(acos); + JS_DECLARE_NATIVE_FUNCTION(acosh); + JS_DECLARE_NATIVE_FUNCTION(asin); + JS_DECLARE_NATIVE_FUNCTION(asinh); + JS_DECLARE_NATIVE_FUNCTION(atan); + JS_DECLARE_NATIVE_FUNCTION(atanh); + JS_DECLARE_NATIVE_FUNCTION(log1p); + JS_DECLARE_NATIVE_FUNCTION(cbrt); + JS_DECLARE_NATIVE_FUNCTION(atan2); + JS_DECLARE_NATIVE_FUNCTION(fround); + JS_DECLARE_NATIVE_FUNCTION(hypot); + JS_DECLARE_NATIVE_FUNCTION(log); + JS_DECLARE_NATIVE_FUNCTION(log2); + JS_DECLARE_NATIVE_FUNCTION(log10); + JS_DECLARE_NATIVE_FUNCTION(sinh); + JS_DECLARE_NATIVE_FUNCTION(cosh); + JS_DECLARE_NATIVE_FUNCTION(tanh); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/NativeFunction.cpp b/Userland/Libraries/LibJS/Runtime/NativeFunction.cpp new file mode 100644 index 0000000000..8453724414 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/NativeFunction.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/LexicalEnvironment.h> +#include <LibJS/Runtime/NativeFunction.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +NativeFunction* NativeFunction::create(GlobalObject& global_object, const FlyString& name, AK::Function<Value(VM&, GlobalObject&)> function) +{ + return global_object.heap().allocate<NativeFunction>(global_object, name, move(function), *global_object.function_prototype()); +} + +NativeFunction::NativeFunction(Object& prototype) + : Function(prototype) +{ +} + +NativeFunction::NativeFunction(const FlyString& name, AK::Function<Value(VM&, GlobalObject&)> native_function, Object& prototype) + : Function(prototype) + , m_name(name) + , m_native_function(move(native_function)) +{ +} + +NativeFunction::NativeFunction(const FlyString& name, Object& prototype) + : Function(prototype) + , m_name(name) +{ +} + +NativeFunction::~NativeFunction() +{ +} + +Value NativeFunction::call() +{ + return m_native_function(vm(), global_object()); +} + +Value NativeFunction::construct(Function&) +{ + return {}; +} + +LexicalEnvironment* NativeFunction::create_environment() +{ + return heap().allocate<LexicalEnvironment>(global_object(), LexicalEnvironment::EnvironmentRecordType::Function); +} + +bool NativeFunction::is_strict_mode() const +{ + return vm().in_strict_mode(); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/NativeFunction.h b/Userland/Libraries/LibJS/Runtime/NativeFunction.h new file mode 100644 index 0000000000..a37336329f --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/NativeFunction.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/Function.h> +#include <LibJS/Runtime/Function.h> + +namespace JS { + +class NativeFunction : public Function { + JS_OBJECT(NativeFunction, Function); + +public: + static NativeFunction* create(GlobalObject&, const FlyString& name, AK::Function<Value(VM&, GlobalObject&)>); + + explicit NativeFunction(const FlyString& name, AK::Function<Value(VM&, GlobalObject&)>, Object& prototype); + virtual void initialize(GlobalObject&) override { } + virtual ~NativeFunction() override; + + virtual Value call() override; + virtual Value construct(Function& new_target) override; + + virtual const FlyString& name() const override { return m_name; }; + virtual bool has_constructor() const { return false; } + + virtual bool is_strict_mode() const override; + +protected: + NativeFunction(const FlyString& name, Object& prototype); + explicit NativeFunction(Object& prototype); + +private: + virtual LexicalEnvironment* create_environment() override final; + + FlyString m_name; + AK::Function<Value(VM&, GlobalObject&)> m_native_function; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/NativeProperty.cpp b/Userland/Libraries/LibJS/Runtime/NativeProperty.cpp new file mode 100644 index 0000000000..d52e7995fa --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/NativeProperty.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/NativeProperty.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +NativeProperty::NativeProperty(AK::Function<Value(VM&, GlobalObject&)> getter, AK::Function<void(VM&, GlobalObject&, Value)> setter) + : m_getter(move(getter)) + , m_setter(move(setter)) +{ +} + +NativeProperty::~NativeProperty() +{ +} + +Value NativeProperty::get(VM& vm, GlobalObject& global_object) const +{ + if (!m_getter) + return js_undefined(); + return m_getter(vm, global_object); +} + +void NativeProperty::set(VM& vm, GlobalObject& global_object, Value value) +{ + if (!m_setter) + return; + m_setter(vm, global_object, move(value)); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/NativeProperty.h b/Userland/Libraries/LibJS/Runtime/NativeProperty.h new file mode 100644 index 0000000000..e0bb4140b7 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/NativeProperty.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/Function.h> +#include <LibJS/Runtime/Object.h> + +namespace JS { + +class NativeProperty final : public Cell { +public: + NativeProperty(AK::Function<Value(VM&, GlobalObject&)> getter, AK::Function<void(VM&, GlobalObject&, Value)> setter); + virtual ~NativeProperty() override; + + Value get(VM&, GlobalObject&) const; + void set(VM&, GlobalObject&, Value); + +private: + virtual const char* class_name() const override { return "NativeProperty"; } + + AK::Function<Value(VM&, GlobalObject&)> m_getter; + AK::Function<void(VM&, GlobalObject&, Value)> m_setter; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/NumberConstructor.cpp b/Userland/Libraries/LibJS/Runtime/NumberConstructor.cpp new file mode 100644 index 0000000000..c95441166e --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/NumberConstructor.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/NumberConstructor.h> +#include <LibJS/Runtime/NumberObject.h> +#include <math.h> + +#ifdef __clang__ +# define EPSILON_VALUE pow(2, -52) +# define MAX_SAFE_INTEGER_VALUE pow(2, 53) - 1 +# define MIN_SAFE_INTEGER_VALUE -(pow(2, 53) - 1) +#else +constexpr const double EPSILON_VALUE { __builtin_pow(2, -52) }; +constexpr const double MAX_SAFE_INTEGER_VALUE { __builtin_pow(2, 53) - 1 }; +constexpr const double MIN_SAFE_INTEGER_VALUE { -(__builtin_pow(2, 53) - 1) }; +#endif + +namespace JS { + +NumberConstructor::NumberConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.Number, *global_object.function_prototype()) +{ +} + +void NumberConstructor::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + NativeFunction::initialize(global_object); + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.isFinite, is_finite, 1, attr); + define_native_function(vm.names.isInteger, is_integer, 1, attr); + define_native_function(vm.names.isNaN, is_nan, 1, attr); + define_native_function(vm.names.isSafeInteger, is_safe_integer, 1, attr); + define_property(vm.names.parseFloat, global_object.get(vm.names.parseFloat)); + define_property(vm.names.prototype, global_object.number_prototype(), 0); + define_property(vm.names.length, Value(1), Attribute::Configurable); + define_property(vm.names.EPSILON, Value(EPSILON_VALUE), 0); + define_property(vm.names.MAX_SAFE_INTEGER, Value(MAX_SAFE_INTEGER_VALUE), 0); + define_property(vm.names.MIN_SAFE_INTEGER, Value(MIN_SAFE_INTEGER_VALUE), 0); + define_property(vm.names.NEGATIVE_INFINITY, js_negative_infinity(), 0); + define_property(vm.names.POSITIVE_INFINITY, js_infinity(), 0); + define_property(vm.names.NaN, js_nan(), 0); +} + +NumberConstructor::~NumberConstructor() +{ +} + +Value NumberConstructor::call() +{ + if (!vm().argument_count()) + return Value(0); + return vm().argument(0).to_number(global_object()); +} + +Value NumberConstructor::construct(Function&) +{ + double number = 0; + if (vm().argument_count()) { + number = vm().argument(0).to_double(global_object()); + if (vm().exception()) + return {}; + } + return NumberObject::create(global_object(), number); +} + +JS_DEFINE_NATIVE_FUNCTION(NumberConstructor::is_finite) +{ + return Value(vm.argument(0).is_finite_number()); +} + +JS_DEFINE_NATIVE_FUNCTION(NumberConstructor::is_integer) +{ + return Value(vm.argument(0).is_integer()); +} + +JS_DEFINE_NATIVE_FUNCTION(NumberConstructor::is_nan) +{ + return Value(vm.argument(0).is_nan()); +} + +JS_DEFINE_NATIVE_FUNCTION(NumberConstructor::is_safe_integer) +{ + if (!vm.argument(0).is_number()) + return Value(false); + auto value = vm.argument(0).as_double(); + return Value((int64_t)value == value && value >= MIN_SAFE_INTEGER_VALUE && value <= MAX_SAFE_INTEGER_VALUE); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/NumberConstructor.h b/Userland/Libraries/LibJS/Runtime/NumberConstructor.h new file mode 100644 index 0000000000..0ed58b70cb --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/NumberConstructor.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/NativeFunction.h> + +namespace JS { + +class NumberConstructor final : public NativeFunction { + JS_OBJECT(NumberConstructor, NativeFunction); + +public: + explicit NumberConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~NumberConstructor() override; + + virtual Value call() override; + virtual Value construct(Function& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } + + JS_DECLARE_NATIVE_FUNCTION(is_finite); + JS_DECLARE_NATIVE_FUNCTION(is_integer); + JS_DECLARE_NATIVE_FUNCTION(is_nan); + JS_DECLARE_NATIVE_FUNCTION(is_safe_integer); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/NumberObject.cpp b/Userland/Libraries/LibJS/Runtime/NumberObject.cpp new file mode 100644 index 0000000000..1376be1571 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/NumberObject.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/NumberObject.h> +#include <LibJS/Runtime/NumberPrototype.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +NumberObject* NumberObject::create(GlobalObject& global_object, double value) +{ + return global_object.heap().allocate<NumberObject>(global_object, value, *global_object.number_prototype()); +} + +NumberObject::NumberObject(double value, Object& prototype) + : Object(prototype) + , m_value(value) +{ +} + +NumberObject::~NumberObject() +{ +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/NumberObject.h b/Userland/Libraries/LibJS/Runtime/NumberObject.h new file mode 100644 index 0000000000..7ae5e71451 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/NumberObject.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class NumberObject : public Object { + JS_OBJECT(NumberObject, Object); + +public: + static NumberObject* create(GlobalObject&, double); + + NumberObject(double, Object& prototype); + virtual ~NumberObject() override; + + virtual Value value_of() const override { return Value(m_value); } + + double number() const { return m_value; } + +private: + double m_value { 0 }; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp b/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp new file mode 100644 index 0000000000..94add9a146 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/Function.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/NumberObject.h> +#include <LibJS/Runtime/NumberPrototype.h> + +namespace JS { + +static const u8 max_precision_for_radix[37] = { + // clang-format off + 0, 0, 52, 32, 26, 22, 20, 18, 17, 16, + 15, 15, 14, 14, 13, 13, 13, 12, 12, 12, + 12, 11, 11, 11, 11, 11, 11, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, + // clang-format on +}; + +static char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; + +NumberPrototype::NumberPrototype(GlobalObject& global_object) + : NumberObject(0, *global_object.object_prototype()) +{ +} + +void NumberPrototype::initialize(GlobalObject& object) +{ + auto& vm = this->vm(); + Object::initialize(object); + define_native_function(vm.names.toString, to_string, 1, Attribute::Configurable | Attribute::Writable); +} + +NumberPrototype::~NumberPrototype() +{ +} + +JS_DEFINE_NATIVE_FUNCTION(NumberPrototype::to_string) +{ + Value number_value; + + auto this_value = vm.this_value(global_object); + if (this_value.is_number()) { + number_value = this_value; + } else if (this_value.is_object() && is<NumberObject>(this_value.as_object())) { + number_value = static_cast<NumberObject&>(this_value.as_object()).value_of(); + } else { + vm.throw_exception<TypeError>(global_object, ErrorType::NumberIncompatibleThis, "toString"); + return {}; + } + + int radix; + auto argument = vm.argument(0); + if (argument.is_undefined()) { + radix = 10; + } else { + radix = argument.to_i32(global_object); + } + + if (vm.exception() || radix < 2 || radix > 36) { + vm.throw_exception<RangeError>(global_object, ErrorType::InvalidRadix); + return {}; + } + + if (number_value.is_positive_infinity()) + return js_string(vm, "Infinity"); + if (number_value.is_negative_infinity()) + return js_string(vm, "-Infinity"); + if (number_value.is_nan()) + return js_string(vm, "NaN"); + if (number_value.is_positive_zero() || number_value.is_negative_zero()) + return js_string(vm, "0"); + + double number = number_value.as_double(); + bool negative = number < 0; + if (negative) + number *= -1; + + int int_part = floor(number); + double decimal_part = number - int_part; + + Vector<char> backwards_characters; + + if (int_part == 0) { + backwards_characters.append('0'); + } else { + while (int_part > 0) { + backwards_characters.append(digits[int_part % radix]); + int_part /= radix; + } + } + + Vector<char> characters; + if (negative) + characters.append('-'); + + // Reverse characters; + for (ssize_t i = backwards_characters.size() - 1; i >= 0; --i) { + characters.append(backwards_characters[i]); + } + + // decimal part + if (decimal_part != 0.0) { + characters.append('.'); + + int precision = max_precision_for_radix[radix]; + + for (int i = 0; i < precision; ++i) { + decimal_part *= radix; + int integral = floor(decimal_part); + characters.append(digits[integral]); + decimal_part -= integral; + } + + while (characters.last() == '0') + characters.take_last(); + } + + return js_string(vm, String(characters.data(), characters.size())); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/NumberPrototype.h b/Userland/Libraries/LibJS/Runtime/NumberPrototype.h new file mode 100644 index 0000000000..a2b94143f1 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/NumberPrototype.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/NumberObject.h> + +namespace JS { + +class NumberPrototype final : public NumberObject { + JS_OBJECT(NumberPrototype, NumberObject); + +public: + explicit NumberPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~NumberPrototype() override; + + JS_DECLARE_NATIVE_FUNCTION(to_string); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Object.cpp b/Userland/Libraries/LibJS/Runtime/Object.cpp new file mode 100644 index 0000000000..87a9d75dd5 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Object.cpp @@ -0,0 +1,927 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/String.h> +#include <AK/TemporaryChange.h> +#include <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/Accessor.h> +#include <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/NativeFunction.h> +#include <LibJS/Runtime/NativeProperty.h> +#include <LibJS/Runtime/Object.h> +#include <LibJS/Runtime/Shape.h> +#include <LibJS/Runtime/StringObject.h> +#include <LibJS/Runtime/Value.h> + +//#define OBJECT_DEBUG + +namespace JS { + +PropertyDescriptor PropertyDescriptor::from_dictionary(VM& vm, const Object& object) +{ + PropertyAttributes attributes; + if (object.has_property(vm.names.configurable)) { + attributes.set_has_configurable(); + if (object.get(vm.names.configurable).value_or(Value(false)).to_boolean()) + attributes.set_configurable(); + if (vm.exception()) + return {}; + } + if (object.has_property(vm.names.enumerable)) { + attributes.set_has_enumerable(); + if (object.get(vm.names.enumerable).value_or(Value(false)).to_boolean()) + attributes.set_enumerable(); + if (vm.exception()) + return {}; + } + if (object.has_property(vm.names.writable)) { + attributes.set_has_writable(); + if (object.get(vm.names.writable).value_or(Value(false)).to_boolean()) + attributes.set_writable(); + if (vm.exception()) + return {}; + } + PropertyDescriptor descriptor { attributes, object.get(vm.names.value), nullptr, nullptr }; + if (vm.exception()) + return {}; + auto getter = object.get(vm.names.get); + if (vm.exception()) + return {}; + if (getter.is_function()) + descriptor.getter = &getter.as_function(); + auto setter = object.get(vm.names.set); + if (vm.exception()) + return {}; + if (setter.is_function()) + descriptor.setter = &setter.as_function(); + return descriptor; +} + +Object* Object::create_empty(GlobalObject& global_object) +{ + return global_object.heap().allocate<Object>(global_object, *global_object.new_object_shape()); +} + +Object::Object(GlobalObjectTag) +{ + // This is the global object + m_shape = heap().allocate_without_global_object<Shape>(*this); +} + +Object::Object(ConstructWithoutPrototypeTag, GlobalObject& global_object) +{ + m_shape = heap().allocate_without_global_object<Shape>(global_object); +} + +Object::Object(Object& prototype) +{ + m_shape = prototype.global_object().empty_object_shape(); + set_prototype(&prototype); +} + +Object::Object(Shape& shape) + : m_shape(&shape) +{ + m_storage.resize(shape.property_count()); +} + +void Object::initialize(GlobalObject&) +{ +} + +Object::~Object() +{ +} + +Object* Object::prototype() +{ + return shape().prototype(); +} + +const Object* Object::prototype() const +{ + return shape().prototype(); +} + +bool Object::set_prototype(Object* new_prototype) +{ + if (prototype() == new_prototype) + return true; + if (!m_is_extensible) + return false; + if (shape().is_unique()) { + shape().set_prototype_without_transition(new_prototype); + return true; + } + m_shape = m_shape->create_prototype_transition(new_prototype); + return true; +} + +bool Object::has_prototype(const Object* prototype) const +{ + for (auto* object = this->prototype(); object; object = object->prototype()) { + if (vm().exception()) + return false; + if (object == prototype) + return true; + } + return false; +} + +bool Object::prevent_extensions() +{ + m_is_extensible = false; + return true; +} + +Value Object::get_own_property(const PropertyName& property_name, Value receiver) const +{ + ASSERT(property_name.is_valid()); + ASSERT(!receiver.is_empty()); + + Value value_here; + + if (property_name.is_number()) { + auto existing_property = m_indexed_properties.get(nullptr, property_name.as_number(), false); + if (!existing_property.has_value()) + return {}; + value_here = existing_property.value().value.value_or(js_undefined()); + } else { + auto metadata = shape().lookup(property_name.to_string_or_symbol()); + if (!metadata.has_value()) + return {}; + value_here = m_storage[metadata.value().offset].value_or(js_undefined()); + } + + ASSERT(!value_here.is_empty()); + if (value_here.is_accessor()) + return value_here.as_accessor().call_getter(receiver); + if (value_here.is_native_property()) + return call_native_property_getter(value_here.as_native_property(), receiver); + return value_here; +} + +Value Object::get_own_properties(const Object& this_object, PropertyKind kind, bool only_enumerable_properties, GetOwnPropertyReturnType return_type) const +{ + auto* properties_array = Array::create(global_object()); + + // FIXME: Support generic iterables + if (is<StringObject>(this_object)) { + auto str = static_cast<const StringObject&>(this_object).primitive_string().string(); + + for (size_t i = 0; i < str.length(); ++i) { + if (kind == PropertyKind::Key) { + properties_array->define_property(i, js_string(vm(), String::number(i))); + } else if (kind == PropertyKind::Value) { + properties_array->define_property(i, js_string(vm(), String::formatted("{:c}", str[i]))); + } else { + auto* entry_array = Array::create(global_object()); + entry_array->define_property(0, js_string(vm(), String::number(i))); + entry_array->define_property(1, js_string(vm(), String::formatted("{:c}", str[i]))); + properties_array->define_property(i, entry_array); + } + if (vm().exception()) + return {}; + } + + return properties_array; + } + + size_t property_index = 0; + for (auto& entry : m_indexed_properties) { + auto value_and_attributes = entry.value_and_attributes(const_cast<Object*>(&this_object)); + if (only_enumerable_properties && !value_and_attributes.attributes.is_enumerable()) + continue; + + if (kind == PropertyKind::Key) { + properties_array->define_property(property_index, js_string(vm(), String::number(entry.index()))); + } else if (kind == PropertyKind::Value) { + properties_array->define_property(property_index, value_and_attributes.value); + } else { + auto* entry_array = Array::create(global_object()); + entry_array->define_property(0, js_string(vm(), String::number(entry.index()))); + entry_array->define_property(1, value_and_attributes.value); + properties_array->define_property(property_index, entry_array); + } + if (vm().exception()) + return {}; + + ++property_index; + } + + for (auto& it : this_object.shape().property_table_ordered()) { + if (only_enumerable_properties && !it.value.attributes.is_enumerable()) + continue; + + if (return_type == GetOwnPropertyReturnType::StringOnly && it.key.is_symbol()) + continue; + if (return_type == GetOwnPropertyReturnType::SymbolOnly && it.key.is_string()) + continue; + + if (kind == PropertyKind::Key) { + properties_array->define_property(property_index, it.key.to_value(vm())); + } else if (kind == PropertyKind::Value) { + properties_array->define_property(property_index, this_object.get(it.key)); + } else { + auto* entry_array = Array::create(global_object()); + entry_array->define_property(0, it.key.to_value(vm())); + entry_array->define_property(1, this_object.get(it.key)); + properties_array->define_property(property_index, entry_array); + } + if (vm().exception()) + return {}; + + ++property_index; + } + + return properties_array; +} + +Optional<PropertyDescriptor> Object::get_own_property_descriptor(const PropertyName& property_name) const +{ + ASSERT(property_name.is_valid()); + + Value value; + PropertyAttributes attributes; + + if (property_name.is_number()) { + auto existing_value = m_indexed_properties.get(nullptr, property_name.as_number(), false); + if (!existing_value.has_value()) + return {}; + value = existing_value.value().value; + attributes = existing_value.value().attributes; + attributes = default_attributes; + } else { + auto metadata = shape().lookup(property_name.to_string_or_symbol()); + if (!metadata.has_value()) + return {}; + value = m_storage[metadata.value().offset]; + if (vm().exception()) + return {}; + attributes = metadata.value().attributes; + } + + PropertyDescriptor descriptor { attributes, {}, nullptr, nullptr }; + if (value.is_native_property()) { + auto result = call_native_property_getter(value.as_native_property(), const_cast<Object*>(this)); + descriptor.value = result.value_or(js_undefined()); + } else if (value.is_accessor()) { + auto& pair = value.as_accessor(); + if (pair.getter()) + descriptor.getter = pair.getter(); + if (pair.setter()) + descriptor.setter = pair.setter(); + } else { + descriptor.value = value.value_or(js_undefined()); + } + + return descriptor; +} + +Value Object::get_own_property_descriptor_object(const PropertyName& property_name) const +{ + ASSERT(property_name.is_valid()); + + auto& vm = this->vm(); + auto descriptor_opt = get_own_property_descriptor(property_name); + if (!descriptor_opt.has_value()) + return js_undefined(); + auto descriptor = descriptor_opt.value(); + + auto* descriptor_object = Object::create_empty(global_object()); + descriptor_object->define_property(vm.names.enumerable, Value(descriptor.attributes.is_enumerable())); + if (vm.exception()) + return {}; + descriptor_object->define_property(vm.names.configurable, Value(descriptor.attributes.is_configurable())); + if (vm.exception()) + return {}; + if (descriptor.is_data_descriptor()) { + descriptor_object->define_property(vm.names.value, descriptor.value.value_or(js_undefined())); + if (vm.exception()) + return {}; + descriptor_object->define_property(vm.names.writable, Value(descriptor.attributes.is_writable())); + if (vm.exception()) + return {}; + } else if (descriptor.is_accessor_descriptor()) { + if (descriptor.getter) { + descriptor_object->define_property(vm.names.get, Value(descriptor.getter)); + if (vm.exception()) + return {}; + } + if (descriptor.setter) { + descriptor_object->define_property(vm.names.set, Value(descriptor.setter)); + if (vm.exception()) + return {}; + } + } + return descriptor_object; +} + +void Object::set_shape(Shape& new_shape) +{ + m_storage.resize(new_shape.property_count()); + m_shape = &new_shape; +} + +bool Object::define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions) +{ + auto& vm = this->vm(); + bool is_accessor_property = descriptor.has_property(vm.names.get) || descriptor.has_property(vm.names.set); + PropertyAttributes attributes; + if (descriptor.has_property(vm.names.configurable)) { + attributes.set_has_configurable(); + if (descriptor.get(vm.names.configurable).value_or(Value(false)).to_boolean()) + attributes.set_configurable(); + if (vm.exception()) + return false; + } + if (descriptor.has_property(vm.names.enumerable)) { + attributes.set_has_enumerable(); + if (descriptor.get(vm.names.enumerable).value_or(Value(false)).to_boolean()) + attributes.set_enumerable(); + if (vm.exception()) + return false; + } + + if (is_accessor_property) { + if (descriptor.has_property(vm.names.value) || descriptor.has_property(vm.names.writable)) { + if (throw_exceptions) + vm.throw_exception<TypeError>(global_object(), ErrorType::AccessorValueOrWritable); + return false; + } + + auto getter = descriptor.get(vm.names.get).value_or(js_undefined()); + if (vm.exception()) + return {}; + auto setter = descriptor.get(vm.names.set).value_or(js_undefined()); + if (vm.exception()) + return {}; + + Function* getter_function { nullptr }; + Function* setter_function { nullptr }; + + if (getter.is_function()) { + getter_function = &getter.as_function(); + } else if (!getter.is_undefined()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::AccessorBadField, "get"); + return false; + } + + if (setter.is_function()) { + setter_function = &setter.as_function(); + } else if (!setter.is_undefined()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::AccessorBadField, "set"); + return false; + } + +#ifdef OBJECT_DEBUG + dbgln("Defining new property {} with accessor descriptor {{ attributes={}, getter={}, setter={} }}", property_name.to_display_string(), attributes, getter, setter); +#endif + + return define_property(property_name, Accessor::create(vm, getter_function, setter_function), attributes, throw_exceptions); + } + + auto value = descriptor.get(vm.names.value); + if (vm.exception()) + return {}; + if (descriptor.has_property(vm.names.writable)) { + attributes.set_has_writable(); + if (descriptor.get(vm.names.writable).value_or(Value(false)).to_boolean()) + attributes.set_writable(); + if (vm.exception()) + return false; + } + if (vm.exception()) + return {}; + +#ifdef OBJECT_DEBUG + dbgln("Defining new property {} with data descriptor {{ attributes={}, value={} }}", property_name.to_display_string(), attributes, value); +#endif + + return define_property(property_name, value, attributes, throw_exceptions); +} + +bool Object::define_property_without_transition(const PropertyName& property_name, Value value, PropertyAttributes attributes, bool throw_exceptions) +{ + TemporaryChange change(m_transitions_enabled, false); + return define_property(property_name, value, attributes, throw_exceptions); +} + +bool Object::define_property(const PropertyName& property_name, Value value, PropertyAttributes attributes, bool throw_exceptions) +{ + ASSERT(property_name.is_valid()); + + if (property_name.is_number()) + return put_own_property_by_index(*this, property_name.as_number(), value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions); + + if (property_name.is_string()) { + i32 property_index = property_name.as_string().to_int().value_or(-1); + if (property_index >= 0) + return put_own_property_by_index(*this, property_index, value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions); + } + return put_own_property(*this, property_name.to_string_or_symbol(), value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions); +} + +bool Object::define_accessor(const PropertyName& property_name, Function& getter_or_setter, bool is_getter, PropertyAttributes attributes, bool throw_exceptions) +{ + ASSERT(property_name.is_valid()); + + Accessor* accessor { nullptr }; + auto property_metadata = shape().lookup(property_name.to_string_or_symbol()); + if (property_metadata.has_value()) { + auto existing_property = get_direct(property_metadata.value().offset); + if (existing_property.is_accessor()) + accessor = &existing_property.as_accessor(); + } + if (!accessor) { + accessor = Accessor::create(vm(), nullptr, nullptr); + bool definition_success = define_property(property_name, accessor, attributes, throw_exceptions); + if (vm().exception()) + return {}; + if (!definition_success) + return false; + } + if (is_getter) + accessor->set_getter(&getter_or_setter); + else + accessor->set_setter(&getter_or_setter); + + return true; +} + +bool Object::put_own_property(Object& this_object, const StringOrSymbol& property_name, Value value, PropertyAttributes attributes, PutOwnPropertyMode mode, bool throw_exceptions) +{ + ASSERT(!(mode == PutOwnPropertyMode::Put && value.is_accessor())); + + if (value.is_accessor()) { + auto& accessor = value.as_accessor(); + if (accessor.getter()) + attributes.set_has_getter(); + if (accessor.setter()) + attributes.set_has_setter(); + } + + // NOTE: We disable transitions during initialize(), this makes building common runtime objects significantly faster. + // Transitions are primarily interesting when scripts add properties to objects. + if (!m_transitions_enabled && !m_shape->is_unique()) { + m_shape->add_property_without_transition(property_name, attributes); + m_storage.resize(m_shape->property_count()); + m_storage[m_shape->property_count() - 1] = value; + return true; + } + + auto metadata = shape().lookup(property_name); + bool new_property = !metadata.has_value(); + + if (!is_extensible() && new_property) { +#ifdef OBJECT_DEBUG + dbgln("Disallow define_property of non-extensible object"); +#endif + if (throw_exceptions && vm().in_strict_mode()) + vm().throw_exception<TypeError>(global_object(), ErrorType::NonExtensibleDefine, property_name.to_display_string()); + return false; + } + + if (new_property) { + if (!m_shape->is_unique() && shape().property_count() > 100) { + // If you add more than 100 properties to an object, let's stop doing + // transitions to avoid filling up the heap with shapes. + ensure_shape_is_unique(); + } + + if (m_shape->is_unique()) { + m_shape->add_property_to_unique_shape(property_name, attributes); + m_storage.resize(m_shape->property_count()); + } else if (m_transitions_enabled) { + set_shape(*m_shape->create_put_transition(property_name, attributes)); + } else { + m_shape->add_property_without_transition(property_name, attributes); + m_storage.resize(m_shape->property_count()); + } + metadata = shape().lookup(property_name); + ASSERT(metadata.has_value()); + } + + if (!new_property && mode == PutOwnPropertyMode::DefineProperty && !metadata.value().attributes.is_configurable() && attributes != metadata.value().attributes) { +#ifdef OBJECT_DEBUG + dbgln("Disallow reconfig of non-configurable property"); +#endif + if (throw_exceptions) + vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_name.to_display_string()); + return false; + } + + if (mode == PutOwnPropertyMode::DefineProperty && attributes != metadata.value().attributes) { + if (m_shape->is_unique()) { + m_shape->reconfigure_property_in_unique_shape(property_name, attributes); + } else { + set_shape(*m_shape->create_configure_transition(property_name, attributes)); + } + metadata = shape().lookup(property_name); + +#ifdef OBJECT_DEBUG + dbgln("Reconfigured property {}, new shape says offset is {} and my storage capacity is {}", property_name.to_display_string(), metadata.value().offset, m_storage.size()); +#endif + } + + auto value_here = m_storage[metadata.value().offset]; + if (!new_property && mode == PutOwnPropertyMode::Put && !value_here.is_accessor() && !metadata.value().attributes.is_writable()) { +#ifdef OBJECT_DEBUG + dbgln("Disallow write to non-writable property"); +#endif + return false; + } + + if (value.is_empty()) + return true; + + if (value_here.is_native_property()) { + call_native_property_setter(value_here.as_native_property(), &this_object, value); + } else { + m_storage[metadata.value().offset] = value; + } + return true; +} + +bool Object::put_own_property_by_index(Object& this_object, u32 property_index, Value value, PropertyAttributes attributes, PutOwnPropertyMode mode, bool throw_exceptions) +{ + ASSERT(!(mode == PutOwnPropertyMode::Put && value.is_accessor())); + + auto existing_property = m_indexed_properties.get(nullptr, property_index, false); + auto new_property = !existing_property.has_value(); + + if (!is_extensible() && new_property) { +#ifdef OBJECT_DEBUG + dbgln("Disallow define_property of non-extensible object"); +#endif + if (throw_exceptions && vm().in_strict_mode()) + vm().throw_exception<TypeError>(global_object(), ErrorType::NonExtensibleDefine, property_index); + return false; + } + + if (value.is_accessor()) { + auto& accessor = value.as_accessor(); + if (accessor.getter()) + attributes.set_has_getter(); + if (accessor.setter()) + attributes.set_has_setter(); + } + + PropertyAttributes existing_attributes = new_property ? 0 : existing_property.value().attributes; + + if (!new_property && mode == PutOwnPropertyMode::DefineProperty && !existing_attributes.is_configurable() && attributes != existing_attributes) { +#ifdef OBJECT_DEBUG + dbgln("Disallow reconfig of non-configurable property"); +#endif + if (throw_exceptions) + vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_index); + return false; + } + + auto value_here = new_property ? Value() : existing_property.value().value; + if (!new_property && mode == PutOwnPropertyMode::Put && !value_here.is_accessor() && !existing_attributes.is_writable()) { +#ifdef OBJECT_DEBUG + dbgln("Disallow write to non-writable property"); +#endif + return false; + } + + if (value.is_empty()) + return true; + + if (value_here.is_native_property()) { + call_native_property_setter(value_here.as_native_property(), &this_object, value); + } else { + m_indexed_properties.put(&this_object, property_index, value, attributes, mode == PutOwnPropertyMode::Put); + } + return true; +} + +Value Object::delete_property(const PropertyName& property_name) +{ + ASSERT(property_name.is_valid()); + + if (property_name.is_number()) + return Value(m_indexed_properties.remove(property_name.as_number())); + + if (property_name.is_string()) { + i32 property_index = property_name.as_string().to_int().value_or(-1); + if (property_index >= 0) + return Value(m_indexed_properties.remove(property_index)); + } + + auto metadata = shape().lookup(property_name.to_string_or_symbol()); + if (!metadata.has_value()) + return Value(true); + if (!metadata.value().attributes.is_configurable()) + return Value(false); + + size_t deleted_offset = metadata.value().offset; + + ensure_shape_is_unique(); + + shape().remove_property_from_unique_shape(property_name.to_string_or_symbol(), deleted_offset); + m_storage.remove(deleted_offset); + return Value(true); +} + +void Object::ensure_shape_is_unique() +{ + if (shape().is_unique()) + return; + + m_shape = m_shape->create_unique_clone(); +} + +Value Object::get_by_index(u32 property_index) const +{ + const Object* object = this; + while (object) { + if (is<StringObject>(*this)) { + auto& string = static_cast<const StringObject*>(this)->primitive_string().string(); + if (property_index < string.length()) + return js_string(heap(), string.substring(property_index, 1)); + return js_undefined(); + } + if (static_cast<size_t>(property_index) < object->m_indexed_properties.array_like_size()) { + auto result = object->m_indexed_properties.get(const_cast<Object*>(this), property_index); + if (vm().exception()) + return {}; + if (result.has_value() && !result.value().value.is_empty()) + return result.value().value; + return {}; + } + object = object->prototype(); + if (vm().exception()) + return {}; + } + return {}; +} + +Value Object::get(const PropertyName& property_name, Value receiver) const +{ + ASSERT(property_name.is_valid()); + + if (property_name.is_number()) + return get_by_index(property_name.as_number()); + + if (property_name.is_string()) { + auto property_string = property_name.to_string(); + i32 property_index = property_string.to_int().value_or(-1); + if (property_index >= 0) + return get_by_index(property_index); + } + + const Object* object = this; + while (object) { + if (receiver.is_empty()) + receiver = Value(const_cast<Object*>(this)); + auto value = object->get_own_property(property_name, receiver); + if (vm().exception()) + return {}; + if (!value.is_empty()) + return value; + object = object->prototype(); + if (vm().exception()) + return {}; + } + return {}; +} + +bool Object::put_by_index(u32 property_index, Value value) +{ + ASSERT(!value.is_empty()); + + // If there's a setter in the prototype chain, we go to the setter. + // Otherwise, it goes in the own property storage. + Object* object = this; + while (object) { + auto existing_value = object->m_indexed_properties.get(nullptr, property_index, false); + if (existing_value.has_value()) { + auto value_here = existing_value.value(); + if (value_here.value.is_accessor()) { + value_here.value.as_accessor().call_setter(object, value); + return true; + } + if (value_here.value.is_native_property()) { + // FIXME: Why doesn't put_by_index() receive the receiver value from put()?! + auto receiver = this; + call_native_property_setter(value_here.value.as_native_property(), receiver, value); + return true; + } + } + object = object->prototype(); + if (vm().exception()) + return {}; + } + return put_own_property_by_index(*this, property_index, value, default_attributes, PutOwnPropertyMode::Put); +} + +bool Object::put(const PropertyName& property_name, Value value, Value receiver) +{ + ASSERT(property_name.is_valid()); + + if (property_name.is_number()) + return put_by_index(property_name.as_number(), value); + + ASSERT(!value.is_empty()); + + if (property_name.is_string()) { + auto& property_string = property_name.as_string(); + i32 property_index = property_string.to_int().value_or(-1); + if (property_index >= 0) + return put_by_index(property_index, value); + } + + auto string_or_symbol = property_name.to_string_or_symbol(); + + if (receiver.is_empty()) + receiver = Value(this); + + // If there's a setter in the prototype chain, we go to the setter. + // Otherwise, it goes in the own property storage. + Object* object = this; + while (object) { + auto metadata = object->shape().lookup(string_or_symbol); + if (metadata.has_value()) { + auto value_here = object->m_storage[metadata.value().offset]; + if (value_here.is_accessor()) { + value_here.as_accessor().call_setter(receiver, value); + return true; + } + if (value_here.is_native_property()) { + call_native_property_setter(value_here.as_native_property(), receiver, value); + return true; + } + } + object = object->prototype(); + if (vm().exception()) + return false; + } + return put_own_property(*this, string_or_symbol, value, default_attributes, PutOwnPropertyMode::Put); +} + +bool Object::define_native_function(const StringOrSymbol& property_name, AK::Function<Value(VM&, GlobalObject&)> native_function, i32 length, PropertyAttributes attribute) +{ + auto& vm = this->vm(); + String function_name; + if (property_name.is_string()) { + function_name = property_name.as_string(); + } else { + function_name = String::formatted("[{}]", property_name.as_symbol()->description()); + } + auto* function = NativeFunction::create(global_object(), function_name, move(native_function)); + function->define_property_without_transition(vm.names.length, Value(length), Attribute::Configurable); + if (vm.exception()) + return {}; + function->define_property_without_transition(vm.names.name, js_string(vm.heap(), function_name), Attribute::Configurable); + if (vm.exception()) + return {}; + return define_property(property_name, function, attribute); +} + +bool Object::define_native_property(const StringOrSymbol& property_name, AK::Function<Value(VM&, GlobalObject&)> getter, AK::Function<void(VM&, GlobalObject&, Value)> setter, PropertyAttributes attribute) +{ + return define_property(property_name, heap().allocate_without_global_object<NativeProperty>(move(getter), move(setter)), attribute); +} + +void Object::visit_edges(Cell::Visitor& visitor) +{ + Cell::visit_edges(visitor); + visitor.visit(m_shape); + + for (auto& value : m_storage) + visitor.visit(value); + + m_indexed_properties.for_each_value([&visitor](auto& value) { + visitor.visit(value); + }); +} + +bool Object::has_property(const PropertyName& property_name) const +{ + const Object* object = this; + while (object) { + if (object->has_own_property(property_name)) + return true; + object = object->prototype(); + if (vm().exception()) + return false; + } + return false; +} + +bool Object::has_own_property(const PropertyName& property_name) const +{ + ASSERT(property_name.is_valid()); + + auto has_indexed_property = [&](u32 index) -> bool { + if (is<StringObject>(*this)) + return index < static_cast<const StringObject*>(this)->primitive_string().string().length(); + return m_indexed_properties.has_index(index); + }; + + if (property_name.is_number()) + return has_indexed_property(property_name.as_number()); + + if (property_name.is_string()) { + i32 property_index = property_name.as_string().to_int().value_or(-1); + if (property_index >= 0) + return has_indexed_property(property_index); + } + + return shape().lookup(property_name.to_string_or_symbol()).has_value(); +} + +Value Object::ordinary_to_primitive(Value::PreferredType preferred_type) const +{ + ASSERT(preferred_type == Value::PreferredType::String || preferred_type == Value::PreferredType::Number); + + auto& vm = this->vm(); + + Vector<FlyString, 2> method_names; + if (preferred_type == Value::PreferredType::String) + method_names = { vm.names.toString, vm.names.valueOf }; + else + method_names = { vm.names.valueOf, vm.names.toString }; + + for (auto& method_name : method_names) { + auto method = get(method_name); + if (vm.exception()) + return {}; + if (method.is_function()) { + auto result = vm.call(method.as_function(), const_cast<Object*>(this)); + if (!result.is_object()) + return result; + } + } + vm.throw_exception<TypeError>(global_object(), ErrorType::Convert, "object", preferred_type == Value::PreferredType::String ? "string" : "number"); + return {}; +} + +Value Object::invoke(const StringOrSymbol& property_name, Optional<MarkedValueList> arguments) +{ + auto& vm = this->vm(); + auto property = get(property_name).value_or(js_undefined()); + if (vm.exception()) + return {}; + if (!property.is_function()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::NotAFunction, property.to_string_without_side_effects()); + return {}; + } + return vm.call(property.as_function(), this, move(arguments)); +} + +Value Object::call_native_property_getter(NativeProperty& property, Value this_value) const +{ + auto& vm = this->vm(); + CallFrame call_frame; + call_frame.is_strict_mode = vm.in_strict_mode(); + call_frame.this_value = this_value; + vm.push_call_frame(call_frame, global_object()); + if (vm.exception()) + return {}; + auto result = property.get(vm, global_object()); + vm.pop_call_frame(); + return result; +} + +void Object::call_native_property_setter(NativeProperty& property, Value this_value, Value setter_value) const +{ + auto& vm = this->vm(); + CallFrame call_frame; + call_frame.is_strict_mode = vm.in_strict_mode(); + call_frame.this_value = this_value; + vm.push_call_frame(call_frame, global_object()); + if (vm.exception()) + return; + property.set(vm, global_object(), setter_value); + vm.pop_call_frame(); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Object.h b/Userland/Libraries/LibJS/Runtime/Object.h new file mode 100644 index 0000000000..e869aee748 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Object.h @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/HashMap.h> +#include <AK/String.h> +#include <LibJS/Forward.h> +#include <LibJS/Runtime/Cell.h> +#include <LibJS/Runtime/IndexedProperties.h> +#include <LibJS/Runtime/MarkedValueList.h> +#include <LibJS/Runtime/PrimitiveString.h> +#include <LibJS/Runtime/PropertyName.h> +#include <LibJS/Runtime/Shape.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +#define JS_OBJECT(class_, base_class) \ +public: \ + using Base = base_class; \ + virtual const char* class_name() const override { return #class_; } + +struct PropertyDescriptor { + PropertyAttributes attributes; + Value value; + Function* getter { nullptr }; + Function* setter { nullptr }; + + static PropertyDescriptor from_dictionary(VM&, const Object&); + + bool is_accessor_descriptor() const { return getter || setter; } + bool is_data_descriptor() const { return !(value.is_empty() && !attributes.has_writable()); } + bool is_generic_descriptor() const { return !is_accessor_descriptor() && !is_data_descriptor(); } +}; + +class Object : public Cell { +public: + static Object* create_empty(GlobalObject&); + + explicit Object(Object& prototype); + explicit Object(Shape&); + virtual void initialize(GlobalObject&) override; + virtual ~Object(); + + enum class PropertyKind { + Key, + Value, + KeyAndValue, + }; + + enum class GetOwnPropertyReturnType { + StringOnly, + SymbolOnly, + }; + + enum class PutOwnPropertyMode { + Put, + DefineProperty, + }; + + Shape& shape() { return *m_shape; } + const Shape& shape() const { return *m_shape; } + + GlobalObject& global_object() const { return *shape().global_object(); } + + virtual Value get(const PropertyName&, Value receiver = {}) const; + + virtual bool has_property(const PropertyName&) const; + bool has_own_property(const PropertyName&) const; + + virtual bool put(const PropertyName&, Value, Value receiver = {}); + + Value get_own_property(const PropertyName&, Value receiver) const; + Value get_own_properties(const Object& this_object, PropertyKind, bool only_enumerable_properties = false, GetOwnPropertyReturnType = GetOwnPropertyReturnType::StringOnly) const; + virtual Optional<PropertyDescriptor> get_own_property_descriptor(const PropertyName&) const; + Value get_own_property_descriptor_object(const PropertyName&) const; + + virtual bool define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions = true); + bool define_property(const PropertyName&, Value value, PropertyAttributes attributes = default_attributes, bool throw_exceptions = true); + bool define_property_without_transition(const PropertyName&, Value value, PropertyAttributes attributes = default_attributes, bool throw_exceptions = true); + bool define_accessor(const PropertyName&, Function& getter_or_setter, bool is_getter, PropertyAttributes attributes = default_attributes, bool throw_exceptions = true); + + bool define_native_function(const StringOrSymbol& property_name, AK::Function<Value(VM&, GlobalObject&)>, i32 length = 0, PropertyAttributes attributes = default_attributes); + bool define_native_property(const StringOrSymbol& property_name, AK::Function<Value(VM&, GlobalObject&)> getter, AK::Function<void(VM&, GlobalObject&, Value)> setter, PropertyAttributes attributes = default_attributes); + + virtual Value delete_property(const PropertyName&); + + virtual bool is_array() const { return false; } + virtual bool is_function() const { return false; } + virtual bool is_typed_array() const { return false; } + + virtual const char* class_name() const override { return "Object"; } + virtual void visit_edges(Cell::Visitor&) override; + + virtual Object* prototype(); + virtual const Object* prototype() const; + virtual bool set_prototype(Object* prototype); + bool has_prototype(const Object* prototype) const; + + virtual bool is_extensible() const { return m_is_extensible; } + virtual bool prevent_extensions(); + + virtual Value value_of() const { return Value(const_cast<Object*>(this)); } + virtual Value ordinary_to_primitive(Value::PreferredType preferred_type) const; + + Value get_direct(size_t index) const { return m_storage[index]; } + + const IndexedProperties& indexed_properties() const { return m_indexed_properties; } + IndexedProperties& indexed_properties() { return m_indexed_properties; } + void set_indexed_property_elements(Vector<Value>&& values) { m_indexed_properties = IndexedProperties(move(values)); } + + Value invoke(const StringOrSymbol& property_name, Optional<MarkedValueList> arguments = {}); + + void ensure_shape_is_unique(); + + void enable_transitions() { m_transitions_enabled = true; } + void disable_transitions() { m_transitions_enabled = false; } + +protected: + enum class GlobalObjectTag { Tag }; + enum class ConstructWithoutPrototypeTag { Tag }; + explicit Object(GlobalObjectTag); + Object(ConstructWithoutPrototypeTag, GlobalObject&); + + virtual Value get_by_index(u32 property_index) const; + virtual bool put_by_index(u32 property_index, Value); + +private: + bool put_own_property(Object& this_object, const StringOrSymbol& property_name, Value, PropertyAttributes attributes, PutOwnPropertyMode = PutOwnPropertyMode::Put, bool throw_exceptions = true); + bool put_own_property_by_index(Object& this_object, u32 property_index, Value, PropertyAttributes attributes, PutOwnPropertyMode = PutOwnPropertyMode::Put, bool throw_exceptions = true); + + Value call_native_property_getter(NativeProperty& property, Value this_value) const; + void call_native_property_setter(NativeProperty& property, Value this_value, Value) const; + + void set_shape(Shape&); + + bool m_is_extensible { true }; + bool m_transitions_enabled { true }; + Shape* m_shape { nullptr }; + Vector<Value> m_storage; + IndexedProperties m_indexed_properties; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp new file mode 100644 index 0000000000..a5601d9753 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/Function.h> +#include <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/ObjectConstructor.h> +#include <LibJS/Runtime/ProxyObject.h> +#include <LibJS/Runtime/Shape.h> + +namespace JS { + +ObjectConstructor::ObjectConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.Object, *global_object.function_prototype()) +{ +} + +void ObjectConstructor::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + NativeFunction::initialize(global_object); + define_property(vm.names.prototype, global_object.object_prototype(), 0); + define_property(vm.names.length, Value(1), Attribute::Configurable); + + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.defineProperty, define_property_, 3, attr); + define_native_function(vm.names.is, is, 2, attr); + define_native_function(vm.names.getOwnPropertyDescriptor, get_own_property_descriptor, 2, attr); + define_native_function(vm.names.getOwnPropertyNames, get_own_property_names, 1, attr); + define_native_function(vm.names.getPrototypeOf, get_prototype_of, 1, attr); + define_native_function(vm.names.setPrototypeOf, set_prototype_of, 2, attr); + define_native_function(vm.names.isExtensible, is_extensible, 1, attr); + define_native_function(vm.names.preventExtensions, prevent_extensions, 1, attr); + define_native_function(vm.names.keys, keys, 1, attr); + define_native_function(vm.names.values, values, 1, attr); + define_native_function(vm.names.entries, entries, 1, attr); +} + +ObjectConstructor::~ObjectConstructor() +{ +} + +Value ObjectConstructor::call() +{ + auto value = vm().argument(0); + if (value.is_nullish()) + return Object::create_empty(global_object()); + return value.to_object(global_object()); +} + +Value ObjectConstructor::construct(Function&) +{ + return call(); +} + +JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_names) +{ + if (!vm.argument_count()) + return {}; + auto* object = vm.argument(0).to_object(global_object); + if (vm.exception()) + return {}; + auto* result = Array::create(global_object); + for (auto& entry : object->indexed_properties()) + result->indexed_properties().append(js_string(vm, String::number(entry.index()))); + for (auto& it : object->shape().property_table_ordered()) { + if (!it.key.is_string()) + continue; + result->indexed_properties().append(js_string(vm, it.key.as_string())); + } + + return result; +} + +JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_prototype_of) +{ + if (!vm.argument_count()) + return {}; + auto* object = vm.argument(0).to_object(global_object); + if (vm.exception()) + return {}; + return object->prototype(); +} + +JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::set_prototype_of) +{ + if (vm.argument_count() < 2) { + vm.throw_exception<TypeError>(global_object, ErrorType::ObjectSetPrototypeOfTwoArgs); + return {}; + } + auto* object = vm.argument(0).to_object(global_object); + if (vm.exception()) + return {}; + auto prototype_value = vm.argument(1); + Object* prototype; + if (prototype_value.is_null()) { + prototype = nullptr; + } else if (prototype_value.is_object()) { + prototype = &prototype_value.as_object(); + } else { + vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeWrongType); + return {}; + } + if (!object->set_prototype(prototype)) { + if (!vm.exception()) + vm.throw_exception<TypeError>(global_object, ErrorType::ObjectSetPrototypeOfReturnedFalse); + return {}; + } + return object; +} + +JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::is_extensible) +{ + auto argument = vm.argument(0); + if (!argument.is_object()) + return Value(false); + return Value(argument.as_object().is_extensible()); +} + +JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::prevent_extensions) +{ + auto argument = vm.argument(0); + if (!argument.is_object()) + return argument; + if (!argument.as_object().prevent_extensions()) { + if (!vm.exception()) + vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPreventExtensionsReturnedFalse); + return {}; + } + return argument; +} + +JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_descriptor) +{ + auto* object = vm.argument(0).to_object(global_object); + if (vm.exception()) + return {}; + auto property_key = PropertyName::from_value(global_object, vm.argument(1)); + if (vm.exception()) + return {}; + return object->get_own_property_descriptor_object(property_key); +} + +JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::define_property_) +{ + if (!vm.argument(0).is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Object argument"); + return {}; + } + if (!vm.argument(2).is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Descriptor argument"); + return {}; + } + auto& object = vm.argument(0).as_object(); + auto property_key = StringOrSymbol::from_value(global_object, vm.argument(1)); + if (vm.exception()) + return {}; + auto& descriptor = vm.argument(2).as_object(); + if (!object.define_property(property_key, descriptor)) { + if (!vm.exception()) { + if (AK::is<ProxyObject>(object)) { + vm.throw_exception<TypeError>(global_object, ErrorType::ObjectDefinePropertyReturnedFalse); + } else { + vm.throw_exception<TypeError>(global_object, ErrorType::NonExtensibleDefine, property_key.to_display_string()); + } + } + return {}; + } + return &object; +} + +JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::is) +{ + return Value(same_value(vm.argument(0), vm.argument(1))); +} + +JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::keys) +{ + if (!vm.argument_count()) { + vm.throw_exception<TypeError>(global_object, ErrorType::ConvertUndefinedToObject); + return {}; + } + + auto* obj_arg = vm.argument(0).to_object(global_object); + if (vm.exception()) + return {}; + + return obj_arg->get_own_properties(*obj_arg, PropertyKind::Key, true); +} + +JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::values) +{ + if (!vm.argument_count()) { + vm.throw_exception<TypeError>(global_object, ErrorType::ConvertUndefinedToObject); + return {}; + } + auto* obj_arg = vm.argument(0).to_object(global_object); + if (vm.exception()) + return {}; + + return obj_arg->get_own_properties(*obj_arg, PropertyKind::Value, true); +} + +JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::entries) +{ + if (!vm.argument_count()) { + vm.throw_exception<TypeError>(global_object, ErrorType::ConvertUndefinedToObject); + return {}; + } + auto* obj_arg = vm.argument(0).to_object(global_object); + if (vm.exception()) + return {}; + + return obj_arg->get_own_properties(*obj_arg, PropertyKind::KeyAndValue, true); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/ObjectConstructor.h b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.h new file mode 100644 index 0000000000..717539651c --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/NativeFunction.h> + +namespace JS { + +class ObjectConstructor final : public NativeFunction { + JS_OBJECT(ObjectConstructor, NativeFunction); + +public: + explicit ObjectConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~ObjectConstructor() override; + + virtual Value call() override; + virtual Value construct(Function& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } + + JS_DECLARE_NATIVE_FUNCTION(define_property_); + JS_DECLARE_NATIVE_FUNCTION(is); + JS_DECLARE_NATIVE_FUNCTION(get_own_property_descriptor); + JS_DECLARE_NATIVE_FUNCTION(get_own_property_names); + JS_DECLARE_NATIVE_FUNCTION(get_prototype_of); + JS_DECLARE_NATIVE_FUNCTION(set_prototype_of); + JS_DECLARE_NATIVE_FUNCTION(is_extensible); + JS_DECLARE_NATIVE_FUNCTION(prevent_extensions); + JS_DECLARE_NATIVE_FUNCTION(keys); + JS_DECLARE_NATIVE_FUNCTION(values); + JS_DECLARE_NATIVE_FUNCTION(entries); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/ObjectPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ObjectPrototype.cpp new file mode 100644 index 0000000000..cfc31c6b91 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ObjectPrototype.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/Function.h> +#include <AK/String.h> +#include <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/BooleanObject.h> +#include <LibJS/Runtime/Date.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/NumberObject.h> +#include <LibJS/Runtime/ObjectPrototype.h> +#include <LibJS/Runtime/RegExpObject.h> +#include <LibJS/Runtime/StringObject.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +ObjectPrototype::ObjectPrototype(GlobalObject& global_object) + : Object(Object::ConstructWithoutPrototypeTag::Tag, global_object) +{ +} + +void ObjectPrototype::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + // This must be called after the constructor has returned, so that the below code + // can find the ObjectPrototype through normal paths. + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.hasOwnProperty, has_own_property, 1, attr); + define_native_function(vm.names.toString, to_string, 0, attr); + define_native_function(vm.names.toLocaleString, to_locale_string, 0, attr); + define_native_function(vm.names.valueOf, value_of, 0, attr); + define_native_function(vm.names.propertyIsEnumerable, property_is_enumerable, 1, attr); + define_native_function(vm.names.isPrototypeOf, is_prototype_of, 1, attr); +} + +ObjectPrototype::~ObjectPrototype() +{ +} + +JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::has_own_property) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + auto name = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + return Value(this_object->has_own_property(name)); +} + +JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::to_string) +{ + auto this_value = vm.this_value(global_object); + + if (this_value.is_undefined()) + return js_string(vm, "[object Undefined]"); + if (this_value.is_null()) + return js_string(vm, "[object Null]"); + + auto* this_object = this_value.to_object(global_object); + if (!this_object) + return {}; + + String tag; + auto to_string_tag = this_object->get(global_object.vm().well_known_symbol_to_string_tag()); + + if (to_string_tag.is_string()) { + tag = to_string_tag.as_string().string(); + } else if (this_object->is_array()) { + tag = "Array"; + } else if (this_object->is_function()) { + tag = "Function"; + } else if (is<Error>(this_object)) { + tag = "Error"; + } else if (is<BooleanObject>(this_object)) { + tag = "Boolean"; + } else if (is<NumberObject>(this_object)) { + tag = "Number"; + } else if (is<StringObject>(this_object)) { + tag = "String"; + } else if (is<Date>(this_object)) { + tag = "Date"; + } else if (is<RegExpObject>(this_object)) { + tag = "RegExp"; + } else { + tag = "Object"; + } + + return js_string(vm, String::formatted("[object {}]", tag)); +} + +JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::to_locale_string) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + return this_object->invoke("toString"); +} + +JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::value_of) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + return this_object->value_of(); +} + +JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::property_is_enumerable) +{ + auto name = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + auto property_descriptor = this_object->get_own_property_descriptor(name); + if (!property_descriptor.has_value()) + return Value(false); + return Value(property_descriptor.value().attributes.is_enumerable()); +} + +JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::is_prototype_of) +{ + auto object_argument = vm.argument(0); + if (!object_argument.is_object()) + return Value(false); + auto* object = &object_argument.as_object(); + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + + for (;;) { + object = object->prototype(); + if (!object) + return Value(false); + if (same_value(this_object, object)) + return Value(true); + } +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/ObjectPrototype.h b/Userland/Libraries/LibJS/Runtime/ObjectPrototype.h new file mode 100644 index 0000000000..a68857e8ae --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ObjectPrototype.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class ObjectPrototype final : public Object { + JS_OBJECT(ObjectPrototype, Object); + +public: + explicit ObjectPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~ObjectPrototype() override; + + // public to serve as intrinsic function %Object.prototype.toString% + JS_DECLARE_NATIVE_FUNCTION(to_string); + +private: + JS_DECLARE_NATIVE_FUNCTION(has_own_property); + JS_DECLARE_NATIVE_FUNCTION(to_locale_string); + JS_DECLARE_NATIVE_FUNCTION(value_of); + JS_DECLARE_NATIVE_FUNCTION(property_is_enumerable); + JS_DECLARE_NATIVE_FUNCTION(is_prototype_of); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/PrimitiveString.cpp b/Userland/Libraries/LibJS/Runtime/PrimitiveString.cpp new file mode 100644 index 0000000000..b62377925d --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/PrimitiveString.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/PrimitiveString.h> +#include <LibJS/Runtime/VM.h> + +namespace JS { + +PrimitiveString::PrimitiveString(String string) + : m_string(move(string)) +{ +} + +PrimitiveString::~PrimitiveString() +{ +} + +PrimitiveString* js_string(Heap& heap, String string) +{ + if (string.is_empty()) + return &heap.vm().empty_string(); + + if (string.length() == 1 && (u8)string.characters()[0] < 0x80) + return &heap.vm().single_ascii_character_string(string.characters()[0]); + + return heap.allocate_without_global_object<PrimitiveString>(move(string)); +} + +PrimitiveString* js_string(VM& vm, String string) +{ + return js_string(vm.heap(), move(string)); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/PrimitiveString.h b/Userland/Libraries/LibJS/Runtime/PrimitiveString.h new file mode 100644 index 0000000000..7b1a9d9e4d --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/PrimitiveString.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/String.h> +#include <LibJS/Runtime/Cell.h> + +namespace JS { + +class PrimitiveString final : public Cell { +public: + explicit PrimitiveString(String); + virtual ~PrimitiveString(); + + const String& string() const { return m_string; } + +private: + virtual const char* class_name() const override { return "PrimitiveString"; } + + String m_string; +}; + +PrimitiveString* js_string(Heap&, String); +PrimitiveString* js_string(VM&, String); + +} diff --git a/Userland/Libraries/LibJS/Runtime/PropertyAttributes.h b/Userland/Libraries/LibJS/Runtime/PropertyAttributes.h new file mode 100644 index 0000000000..b3788bd897 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/PropertyAttributes.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <AK/Format.h> +#include <AK/Types.h> + +namespace JS { + +struct Attribute { + enum { + Configurable = 1 << 0, + Enumerable = 1 << 1, + Writable = 1 << 2, + HasGetter = 1 << 3, + HasSetter = 1 << 4, + HasConfigurable = 1 << 5, + HasEnumerable = 1 << 6, + HasWritable = 1 << 7, + }; +}; + +class PropertyAttributes { +public: + PropertyAttributes(u8 bits = 0) + { + m_bits = bits; + if (bits & Attribute::Configurable) + m_bits |= Attribute::HasConfigurable; + if (bits & Attribute::Enumerable) + m_bits |= Attribute::HasEnumerable; + if (bits & Attribute::Writable) + m_bits |= Attribute::HasWritable; + } + + bool is_empty() const { return !m_bits; } + + bool has_configurable() const { return m_bits & Attribute::HasConfigurable; } + bool has_enumerable() const { return m_bits & Attribute::HasEnumerable; } + bool has_writable() const { return m_bits & Attribute::HasWritable; } + bool has_getter() const { return m_bits & Attribute::HasGetter; } + bool has_setter() const { return m_bits & Attribute::HasSetter; } + + bool is_configurable() const { return m_bits & Attribute::Configurable; } + bool is_enumerable() const { return m_bits & Attribute::Enumerable; } + bool is_writable() const { return m_bits & Attribute::Writable; } + + void set_has_configurable() { m_bits |= Attribute::HasConfigurable; } + void set_has_enumerable() { m_bits |= Attribute::HasEnumerable; } + void set_has_writable() { m_bits |= Attribute::HasWritable; } + void set_configurable() { m_bits |= Attribute::Configurable; } + void set_enumerable() { m_bits |= Attribute::Enumerable; } + void set_writable() { m_bits |= Attribute::Writable; } + void set_has_getter() { m_bits |= Attribute::HasGetter; } + void set_has_setter() { m_bits |= Attribute::HasSetter; } + + bool operator==(const PropertyAttributes& other) const { return m_bits == other.m_bits; } + bool operator!=(const PropertyAttributes& other) const { return m_bits != other.m_bits; } + + u8 bits() const { return m_bits; } + +private: + u8 m_bits; +}; + +const PropertyAttributes default_attributes = Attribute::Configurable | Attribute::Writable | Attribute::Enumerable; + +} + +namespace AK { + +template<> +struct Formatter<JS::PropertyAttributes> : Formatter<u8> { + void format(FormatBuilder& builder, const JS::PropertyAttributes& attributes) + { + Formatter<u8>::format(builder, attributes.bits()); + } +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/PropertyName.h b/Userland/Libraries/LibJS/Runtime/PropertyName.h new file mode 100644 index 0000000000..bbd6a0c640 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/PropertyName.h @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/FlyString.h> +#include <LibJS/Runtime/StringOrSymbol.h> + +namespace JS { + +class PropertyName { +public: + enum class Type { + Invalid, + Number, + String, + Symbol, + }; + + static PropertyName from_value(GlobalObject& global_object, Value value) + { + if (value.is_empty()) + return {}; + if (value.is_symbol()) + return &value.as_symbol(); + if (value.is_integer() && value.as_i32() >= 0) + return value.as_i32(); + auto string = value.to_string(global_object); + if (string.is_null()) + return {}; + return string; + } + + PropertyName() { } + + PropertyName(i32 index) + : m_type(Type::Number) + , m_number(index) + { + ASSERT(index >= 0); + } + + PropertyName(const char* chars) + : m_type(Type::String) + , m_string(FlyString(chars)) + { + } + + PropertyName(const String& string) + : m_type(Type::String) + , m_string(FlyString(string)) + { + ASSERT(!string.is_null()); + } + + PropertyName(const FlyString& string) + : m_type(Type::String) + , m_string(string) + { + ASSERT(!string.is_null()); + } + + PropertyName(Symbol* symbol) + : m_type(Type::Symbol) + , m_symbol(symbol) + { + ASSERT(symbol); + } + + PropertyName(const StringOrSymbol& string_or_symbol) + { + if (string_or_symbol.is_string()) { + m_string = string_or_symbol.as_string(); + m_type = Type::String; + } else if (string_or_symbol.is_symbol()) { + m_symbol = const_cast<Symbol*>(string_or_symbol.as_symbol()); + m_type = Type::Symbol; + } + } + + bool is_valid() const { return m_type != Type::Invalid; } + bool is_number() const { return m_type == Type::Number; } + bool is_string() const { return m_type == Type::String; } + bool is_symbol() const { return m_type == Type::Symbol; } + + i32 as_number() const + { + ASSERT(is_number()); + return m_number; + } + + const FlyString& as_string() const + { + ASSERT(is_string()); + return m_string; + } + + const Symbol* as_symbol() const + { + ASSERT(is_symbol()); + return m_symbol; + } + + String to_string() const + { + ASSERT(is_valid()); + ASSERT(!is_symbol()); + if (is_string()) + return as_string(); + return String::number(as_number()); + } + + StringOrSymbol to_string_or_symbol() const + { + ASSERT(is_valid()); + ASSERT(!is_number()); + if (is_string()) + return StringOrSymbol(as_string()); + return StringOrSymbol(as_symbol()); + } + + Value to_value(VM& vm) const + { + if (is_string()) + return js_string(vm, m_string); + if (is_number()) + return Value(m_number); + if (is_symbol()) + return m_symbol; + return js_undefined(); + } + +private: + Type m_type { Type::Invalid }; + FlyString m_string; + Symbol* m_symbol { nullptr }; + u32 m_number { 0 }; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp new file mode 100644 index 0000000000..f83c5f7727 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/ProxyConstructor.h> +#include <LibJS/Runtime/ProxyObject.h> + +namespace JS { + +ProxyConstructor::ProxyConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.Proxy, *global_object.function_prototype()) +{ +} + +void ProxyConstructor::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + NativeFunction::initialize(global_object); + define_property(vm.names.length, Value(2), Attribute::Configurable); +} + +ProxyConstructor::~ProxyConstructor() +{ +} + +Value ProxyConstructor::call() +{ + auto& vm = this->vm(); + vm.throw_exception<TypeError>(global_object(), ErrorType::ConstructorWithoutNew, vm.names.Proxy); + return {}; +} + +Value ProxyConstructor::construct(Function&) +{ + auto& vm = this->vm(); + if (vm.argument_count() < 2) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyTwoArguments); + return {}; + } + + auto target = vm.argument(0); + auto handler = vm.argument(1); + + if (!target.is_object()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyConstructorBadType, "target", target.to_string_without_side_effects()); + return {}; + } + if (!handler.is_object()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyConstructorBadType, "handler", handler.to_string_without_side_effects()); + return {}; + } + return ProxyObject::create(global_object(), target.as_object(), handler.as_object()); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/ProxyConstructor.h b/Userland/Libraries/LibJS/Runtime/ProxyConstructor.h new file mode 100644 index 0000000000..69d23b847a --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ProxyConstructor.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/NativeFunction.h> + +namespace JS { + +class ProxyConstructor final : public NativeFunction { + JS_OBJECT(ProxyConstructor, NativeFunction); + +public: + explicit ProxyConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~ProxyConstructor() override; + + virtual Value call() override; + virtual Value construct(Function& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp b/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp new file mode 100644 index 0000000000..85a0d9db1b --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp @@ -0,0 +1,559 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/Accessor.h> +#include <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/ProxyObject.h> + +namespace JS { + +bool static is_compatible_property_descriptor(bool is_extensible, PropertyDescriptor new_descriptor, Optional<PropertyDescriptor> current_descriptor_optional) +{ + if (!current_descriptor_optional.has_value()) + return is_extensible; + auto current_descriptor = current_descriptor_optional.value(); + if (new_descriptor.attributes.is_empty() && new_descriptor.value.is_empty() && !new_descriptor.getter && !new_descriptor.setter) + return true; + if (!current_descriptor.attributes.is_configurable()) { + if (new_descriptor.attributes.is_configurable()) + return false; + if (new_descriptor.attributes.has_enumerable() && new_descriptor.attributes.is_enumerable() != current_descriptor.attributes.is_enumerable()) + return false; + } + if (new_descriptor.is_generic_descriptor()) + return true; + if (current_descriptor.is_data_descriptor() != new_descriptor.is_data_descriptor() && !current_descriptor.attributes.is_configurable()) + return false; + if (current_descriptor.is_data_descriptor() && new_descriptor.is_data_descriptor() && !current_descriptor.attributes.is_configurable() && !current_descriptor.attributes.is_writable()) { + if (new_descriptor.attributes.is_writable()) + return false; + return new_descriptor.value.is_empty() && same_value(new_descriptor.value, current_descriptor.value); + } + return true; +} + +ProxyObject* ProxyObject::create(GlobalObject& global_object, Object& target, Object& handler) +{ + return global_object.heap().allocate<ProxyObject>(global_object, target, handler, *global_object.object_prototype()); +} + +ProxyObject::ProxyObject(Object& target, Object& handler, Object& prototype) + : Function(prototype) + , m_target(target) + , m_handler(handler) +{ +} + +ProxyObject::~ProxyObject() +{ +} + +Object* ProxyObject::prototype() +{ + auto& vm = this->vm(); + if (m_is_revoked) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); + return nullptr; + } + auto trap = m_handler.get(vm.names.getPrototypeOf); + if (vm.exception()) + return nullptr; + if (trap.is_empty() || trap.is_nullish()) + return m_target.prototype(); + if (!trap.is_function()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "getPrototypeOf"); + return nullptr; + } + + auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target)); + if (vm.exception()) + return nullptr; + if (!trap_result.is_object() && !trap_result.is_null()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetPrototypeOfReturn); + return nullptr; + } + if (m_target.is_extensible()) { + if (vm.exception()) + return nullptr; + if (trap_result.is_null()) + return nullptr; + return &trap_result.as_object(); + } + auto target_proto = m_target.prototype(); + if (vm.exception()) + return nullptr; + if (!same_value(trap_result, Value(target_proto))) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetPrototypeOfNonExtensible); + return nullptr; + } + return &trap_result.as_object(); +} + +const Object* ProxyObject::prototype() const +{ + auto& vm = this->vm(); + if (m_is_revoked) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); + return nullptr; + } + return const_cast<const Object*>(const_cast<ProxyObject*>(this)->prototype()); +} + +bool ProxyObject::set_prototype(Object* object) +{ + auto& vm = this->vm(); + if (m_is_revoked) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); + return false; + } + auto trap = m_handler.get(vm.names.setPrototypeOf); + if (vm.exception()) + return false; + if (trap.is_empty() || trap.is_nullish()) + return m_target.set_prototype(object); + if (!trap.is_function()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "setPrototypeOf"); + return false; + } + + auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target), Value(object)); + if (vm.exception() || !trap_result.to_boolean()) + return false; + if (m_target.is_extensible()) + return true; + auto* target_proto = m_target.prototype(); + if (vm.exception()) + return false; + if (!same_value(Value(object), Value(target_proto))) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxySetPrototypeOfNonExtensible); + return false; + } + return true; +} + +bool ProxyObject::is_extensible() const +{ + auto& vm = this->vm(); + if (m_is_revoked) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); + return false; + } + auto trap = m_handler.get(vm.names.isExtensible); + if (vm.exception()) + return false; + if (trap.is_empty() || trap.is_nullish()) + return m_target.is_extensible(); + if (!trap.is_function()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "isExtensible"); + return {}; + } + + auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target)); + if (vm.exception()) + return false; + if (trap_result.to_boolean() != m_target.is_extensible()) { + if (!vm.exception()) + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyIsExtensibleReturn); + return false; + } + return trap_result.to_boolean(); +} + +bool ProxyObject::prevent_extensions() +{ + auto& vm = this->vm(); + if (m_is_revoked) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); + return false; + } + auto trap = m_handler.get(vm.names.preventExtensions); + if (vm.exception()) + return false; + if (trap.is_empty() || trap.is_nullish()) + return m_target.prevent_extensions(); + if (!trap.is_function()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "preventExtensions"); + return {}; + } + + auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target)); + if (vm.exception()) + return false; + if (trap_result.to_boolean() && m_target.is_extensible()) { + if (!vm.exception()) + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyPreventExtensionsReturn); + return false; + } + return trap_result.to_boolean(); +} + +Optional<PropertyDescriptor> ProxyObject::get_own_property_descriptor(const PropertyName& name) const +{ + auto& vm = this->vm(); + if (m_is_revoked) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); + return {}; + } + auto trap = m_handler.get(vm.names.getOwnPropertyDescriptor); + if (vm.exception()) + return {}; + if (trap.is_empty() || trap.is_nullish()) + return m_target.get_own_property_descriptor(name); + if (!trap.is_function()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "getOwnPropertyDescriptor"); + return {}; + } + + auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target), name.to_value(vm)); + if (vm.exception()) + return {}; + if (!trap_result.is_object() && !trap_result.is_undefined()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorReturn); + return {}; + } + auto target_desc = m_target.get_own_property_descriptor(name); + if (vm.exception()) + return {}; + if (trap_result.is_undefined()) { + if (!target_desc.has_value()) + return {}; + if (!target_desc.value().attributes.is_configurable()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorNonConfigurable); + return {}; + } + if (!m_target.is_extensible()) { + if (!vm.exception()) + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorUndefReturn); + return {}; + } + return {}; + } + auto result_desc = PropertyDescriptor::from_dictionary(vm, trap_result.as_object()); + if (vm.exception()) + return {}; + if (!is_compatible_property_descriptor(m_target.is_extensible(), result_desc, target_desc)) { + if (!vm.exception()) + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorInvalidDescriptor); + return {}; + } + if (!result_desc.attributes.is_configurable() && (!target_desc.has_value() || target_desc.value().attributes.is_configurable())) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorInvalidNonConfig); + return {}; + } + return result_desc; +} + +bool ProxyObject::define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions) +{ + auto& vm = this->vm(); + if (m_is_revoked) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); + return false; + } + auto trap = m_handler.get(vm.names.defineProperty); + if (vm.exception()) + return false; + if (trap.is_empty() || trap.is_nullish()) + return m_target.define_property(property_name, descriptor, throw_exceptions); + if (!trap.is_function()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "defineProperty"); + return false; + } + + auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target), property_name.to_value(vm), Value(const_cast<Object*>(&descriptor))); + if (vm.exception() || !trap_result.to_boolean()) + return false; + auto target_desc = m_target.get_own_property_descriptor(property_name); + if (vm.exception()) + return false; + bool setting_config_false = false; + if (descriptor.has_property(vm.names.configurable) && !descriptor.get(vm.names.configurable).to_boolean()) + setting_config_false = true; + if (vm.exception()) + return false; + if (!target_desc.has_value()) { + if (!m_target.is_extensible()) { + if (!vm.exception()) + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropNonExtensible); + return false; + } + if (setting_config_false) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropNonConfigurableNonExisting); + return false; + } + } else { + if (!is_compatible_property_descriptor(m_target.is_extensible(), PropertyDescriptor::from_dictionary(vm, descriptor), target_desc)) { + if (!vm.exception()) + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropIncompatibleDescriptor); + return false; + } + if (setting_config_false && target_desc.value().attributes.is_configurable()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropExistingConfigurable); + return false; + } + } + return true; +} + +bool ProxyObject::has_property(const PropertyName& name) const +{ + auto& vm = this->vm(); + if (m_is_revoked) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); + return false; + } + auto trap = m_handler.get(vm.names.has); + if (vm.exception()) + return false; + if (trap.is_empty() || trap.is_nullish()) + return m_target.has_property(name); + if (!trap.is_function()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "has"); + return false; + } + + auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target), name.to_value(vm)); + if (vm.exception()) + return false; + if (!trap_result.to_boolean()) { + auto target_desc = m_target.get_own_property_descriptor(name); + if (vm.exception()) + return false; + if (target_desc.has_value()) { + if (!target_desc.value().attributes.is_configurable()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyHasExistingNonConfigurable); + return false; + } + if (!m_target.is_extensible()) { + if (!vm.exception()) + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyHasExistingNonExtensible); + return false; + } + } + } + return trap_result.to_boolean(); +} + +Value ProxyObject::get(const PropertyName& name, Value receiver) const +{ + auto& vm = this->vm(); + if (m_is_revoked) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); + return {}; + } + if (receiver.is_empty()) + receiver = Value(const_cast<ProxyObject*>(this)); + auto trap = m_handler.get(vm.names.get); + if (vm.exception()) + return {}; + if (trap.is_empty() || trap.is_nullish()) + return m_target.get(name, receiver); + if (!trap.is_function()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "get"); + return {}; + } + + auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target), name.to_value(vm), receiver); + if (vm.exception()) + return {}; + auto target_desc = m_target.get_own_property_descriptor(name); + if (target_desc.has_value()) { + if (vm.exception()) + return {}; + if (target_desc.value().is_data_descriptor() && !target_desc.value().attributes.is_writable() && !same_value(trap_result, target_desc.value().value)) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetImmutableDataProperty); + return {}; + } + if (target_desc.value().is_accessor_descriptor() && target_desc.value().getter == nullptr && !trap_result.is_undefined()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetNonConfigurableAccessor); + return {}; + } + } + return trap_result; +} + +bool ProxyObject::put(const PropertyName& name, Value value, Value receiver) +{ + auto& vm = this->vm(); + if (m_is_revoked) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); + return false; + } + if (receiver.is_empty()) + receiver = Value(const_cast<ProxyObject*>(this)); + auto trap = m_handler.get(vm.names.set); + if (vm.exception()) + return false; + if (trap.is_empty() || trap.is_nullish()) + return m_target.put(name, value, receiver); + if (!trap.is_function()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "set"); + return false; + } + auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target), name.to_value(vm), value, receiver); + if (vm.exception() || !trap_result.to_boolean()) + return false; + auto target_desc = m_target.get_own_property_descriptor(name); + if (vm.exception()) + return false; + if (target_desc.has_value() && !target_desc.value().attributes.is_configurable()) { + if (target_desc.value().is_data_descriptor() && !target_desc.value().attributes.is_writable() && !same_value(value, target_desc.value().value)) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxySetImmutableDataProperty); + return false; + } + if (target_desc.value().is_accessor_descriptor() && !target_desc.value().setter) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxySetNonConfigurableAccessor); + } + } + return true; +} + +Value ProxyObject::delete_property(const PropertyName& name) +{ + auto& vm = this->vm(); + if (m_is_revoked) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); + return {}; + } + auto trap = m_handler.get(vm.names.deleteProperty); + if (vm.exception()) + return {}; + if (trap.is_empty() || trap.is_nullish()) + return m_target.delete_property(name); + if (!trap.is_function()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "deleteProperty"); + return {}; + } + + auto trap_result = vm.call(trap.as_function(), Value(&m_handler), Value(&m_target), name.to_value(vm)); + if (vm.exception()) + return {}; + if (!trap_result.to_boolean()) + return Value(false); + auto target_desc = m_target.get_own_property_descriptor(name); + if (vm.exception()) + return {}; + if (!target_desc.has_value()) + return Value(true); + if (!target_desc.value().attributes.is_configurable()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDeleteNonConfigurable); + return {}; + } + return Value(true); +} + +void ProxyObject::visit_edges(Cell::Visitor& visitor) +{ + Function::visit_edges(visitor); + visitor.visit(&m_target); + visitor.visit(&m_handler); +} + +Value ProxyObject::call() +{ + auto& vm = this->vm(); + if (!is_function()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::NotAFunction, Value(this).to_string_without_side_effects()); + return {}; + } + if (m_is_revoked) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); + return {}; + } + auto trap = m_handler.get(vm.names.apply); + if (vm.exception()) + return {}; + if (trap.is_empty() || trap.is_nullish()) + return static_cast<Function&>(m_target).call(); + if (!trap.is_function()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "apply"); + return {}; + } + MarkedValueList arguments(heap()); + arguments.append(Value(&m_target)); + arguments.append(Value(&m_handler)); + // FIXME: Pass global object + auto arguments_array = Array::create(global_object()); + vm.for_each_argument([&](auto& argument) { + arguments_array->indexed_properties().append(argument); + }); + arguments.append(arguments_array); + + return vm.call(trap.as_function(), Value(&m_handler), move(arguments)); +} + +Value ProxyObject::construct(Function& new_target) +{ + auto& vm = this->vm(); + if (!is_function()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::NotAConstructor, Value(this).to_string_without_side_effects()); + return {}; + } + if (m_is_revoked) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); + return {}; + } + auto trap = m_handler.get(vm.names.construct); + if (vm.exception()) + return {}; + if (trap.is_empty() || trap.is_nullish()) + return static_cast<Function&>(m_target).construct(new_target); + if (!trap.is_function()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyInvalidTrap, "construct"); + return {}; + } + + MarkedValueList arguments(vm.heap()); + arguments.append(Value(&m_target)); + auto arguments_array = Array::create(global_object()); + vm.for_each_argument([&](auto& argument) { + arguments_array->indexed_properties().append(argument); + }); + arguments.append(arguments_array); + arguments.append(Value(&new_target)); + auto result = vm.call(trap.as_function(), Value(&m_handler), move(arguments)); + if (!result.is_object()) { + vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyConstructBadReturnType); + return {}; + } + return result; +} + +const FlyString& ProxyObject::name() const +{ + ASSERT(is_function()); + return static_cast<Function&>(m_target).name(); +} + +LexicalEnvironment* ProxyObject::create_environment() +{ + ASSERT(is_function()); + return static_cast<Function&>(m_target).create_environment(); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/ProxyObject.h b/Userland/Libraries/LibJS/Runtime/ProxyObject.h new file mode 100644 index 0000000000..70f30fac2c --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ProxyObject.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/Function.h> + +namespace JS { + +class ProxyObject final : public Function { + JS_OBJECT(ProxyObject, Function); + +public: + static ProxyObject* create(GlobalObject&, Object& target, Object& handler); + + ProxyObject(Object& target, Object& handler, Object& prototype); + virtual ~ProxyObject() override; + + virtual Value call() override; + virtual Value construct(Function& new_target) override; + virtual const FlyString& name() const override; + virtual LexicalEnvironment* create_environment() override; + + const Object& target() const { return m_target; } + const Object& handler() const { return m_handler; } + + virtual Object* prototype() override; + virtual const Object* prototype() const override; + virtual bool set_prototype(Object* object) override; + virtual bool is_extensible() const override; + virtual bool prevent_extensions() override; + virtual Optional<PropertyDescriptor> get_own_property_descriptor(const PropertyName&) const override; + virtual bool define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions = true) override; + virtual bool has_property(const PropertyName& name) const override; + virtual Value get(const PropertyName& name, Value receiver) const override; + virtual bool put(const PropertyName& name, Value value, Value receiver) override; + virtual Value delete_property(const PropertyName& name) override; + + void revoke() { m_is_revoked = true; } + +private: + virtual void visit_edges(Visitor&) override; + + virtual bool is_function() const override { return m_target.is_function(); } + virtual bool is_array() const override { return m_target.is_array(); }; + + Object& m_target; + Object& m_handler; + bool m_is_revoked { false }; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Reference.cpp b/Userland/Libraries/LibJS/Runtime/Reference.cpp new file mode 100644 index 0000000000..6b6b06f80f --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Reference.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/StringBuilder.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::put(GlobalObject& global_object, Value value) +{ + auto& vm = global_object.vm(); + + if (is_unresolvable()) { + throw_reference_error(global_object); + return; + } + + if (is_local_variable() || is_global_variable()) { + if (is_local_variable()) + vm.set_variable(m_name.to_string(), value, global_object); + else + global_object.put(m_name, value); + return; + } + + if (!base().is_object() && vm.in_strict_mode()) { + vm.throw_exception<TypeError>(global_object, ErrorType::ReferencePrimitiveAssignment, m_name.to_value(global_object.vm()).to_string_without_side_effects()); + return; + } + + auto* object = base().to_object(global_object); + if (!object) + return; + + object->put(m_name, value); +} + +void Reference::throw_reference_error(GlobalObject& global_object) +{ + auto property_name = m_name.to_string(); + String message; + if (property_name.is_empty()) { + global_object.vm().throw_exception<ReferenceError>(global_object, ErrorType::ReferenceUnresolvable); + } else { + global_object.vm().throw_exception<ReferenceError>(global_object, ErrorType::UnknownIdentifier, property_name); + } +} + +Value Reference::get(GlobalObject& global_object) +{ + auto& vm = global_object.vm(); + + if (is_unresolvable()) { + throw_reference_error(global_object); + return {}; + } + + if (is_local_variable() || is_global_variable()) { + Value value; + if (is_local_variable()) + value = vm.get_variable(m_name.to_string(), global_object); + else + value = global_object.get(m_name); + if (vm.exception()) + return {}; + if (value.is_empty()) { + throw_reference_error(global_object); + return {}; + } + return value; + } + + auto* object = base().to_object(global_object); + if (!object) + return {}; + + return object->get(m_name).value_or(js_undefined()); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Reference.h b/Userland/Libraries/LibJS/Runtime/Reference.h new file mode 100644 index 0000000000..c81d964440 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Reference.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/String.h> +#include <LibJS/Runtime/PropertyName.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +class Reference { +public: + Reference() { } + Reference(Value base, const PropertyName& name, bool strict = false) + : m_base(base) + , m_name(name) + , m_strict(strict) + { + } + + enum LocalVariableTag { LocalVariable }; + Reference(LocalVariableTag, const String& name, bool strict = false) + : m_base(js_null()) + , m_name(name) + , m_strict(strict) + , m_local_variable(true) + { + } + + 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; } + + bool is_unresolvable() const { return m_base.is_undefined(); } + bool is_property() const + { + return m_base.is_object() || has_primitive_base(); + } + + bool has_primitive_base() const + { + return m_base.is_boolean() || m_base.is_string() || m_base.is_number(); + } + + bool is_local_variable() const + { + return m_local_variable; + } + + bool is_global_variable() const + { + return m_global_variable; + } + + void put(GlobalObject&, Value); + Value get(GlobalObject&); + +private: + void throw_reference_error(GlobalObject&); + + Value m_base { js_undefined() }; + PropertyName m_name; + bool m_strict { false }; + bool m_local_variable { false }; + bool m_global_variable { false }; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/ReflectObject.cpp b/Userland/Libraries/LibJS/Runtime/ReflectObject.cpp new file mode 100644 index 0000000000..1b7865cc8e --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ReflectObject.cpp @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <AK/Function.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/Function.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/NativeFunction.h> +#include <LibJS/Runtime/ReflectObject.h> + +namespace JS { + +static Object* get_target_object_from(GlobalObject& global_object, const String& name) +{ + auto& vm = global_object.vm(); + auto target = vm.argument(0); + if (!target.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::ReflectArgumentMustBeAnObject, name); + return nullptr; + } + return static_cast<Object*>(&target.as_object()); +} + +static Function* get_target_function_from(GlobalObject& global_object, const String& name) +{ + auto& vm = global_object.vm(); + auto target = vm.argument(0); + if (!target.is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::ReflectArgumentMustBeAFunction, name); + return nullptr; + } + return &target.as_function(); +} + +static void prepare_arguments_list(GlobalObject& global_object, Value value, MarkedValueList* arguments) +{ + auto& vm = global_object.vm(); + if (!value.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::ReflectBadArgumentsList); + return; + } + auto& arguments_list = value.as_object(); + auto length = length_of_array_like(global_object, arguments_list); + if (vm.exception()) + return; + for (size_t i = 0; i < length; ++i) { + auto element = arguments_list.get(String::number(i)); + if (vm.exception()) + return; + arguments->append(element.value_or(js_undefined())); + } +} + +ReflectObject::ReflectObject(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void ReflectObject::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.apply, apply, 3, attr); + define_native_function(vm.names.construct, construct, 2, attr); + define_native_function(vm.names.defineProperty, define_property, 3, attr); + define_native_function(vm.names.deleteProperty, delete_property, 2, attr); + define_native_function(vm.names.get, get, 2, attr); + define_native_function(vm.names.getOwnPropertyDescriptor, get_own_property_descriptor, 2, attr); + define_native_function(vm.names.getPrototypeOf, get_prototype_of, 1, attr); + define_native_function(vm.names.has, has, 2, attr); + define_native_function(vm.names.isExtensible, is_extensible, 1, attr); + define_native_function(vm.names.ownKeys, own_keys, 1, attr); + define_native_function(vm.names.preventExtensions, prevent_extensions, 1, attr); + define_native_function(vm.names.set, set, 3, attr); + define_native_function(vm.names.setPrototypeOf, set_prototype_of, 2, attr); +} + +ReflectObject::~ReflectObject() +{ +} + +JS_DEFINE_NATIVE_FUNCTION(ReflectObject::apply) +{ + auto* target = get_target_function_from(global_object, "apply"); + if (!target) + return {}; + auto this_arg = vm.argument(1); + MarkedValueList arguments(vm.heap()); + prepare_arguments_list(global_object, vm.argument(2), &arguments); + if (vm.exception()) + return {}; + return vm.call(*target, this_arg, move(arguments)); +} + +JS_DEFINE_NATIVE_FUNCTION(ReflectObject::construct) +{ + auto* target = get_target_function_from(global_object, "construct"); + if (!target) + return {}; + MarkedValueList arguments(vm.heap()); + prepare_arguments_list(global_object, vm.argument(1), &arguments); + if (vm.exception()) + return {}; + auto* new_target = target; + if (vm.argument_count() > 2) { + auto new_target_value = vm.argument(2); + if (!new_target_value.is_function() + || (is<NativeFunction>(new_target_value.as_object()) && !static_cast<NativeFunction&>(new_target_value.as_object()).has_constructor())) { + vm.throw_exception<TypeError>(global_object, ErrorType::ReflectBadNewTarget); + return {}; + } + new_target = &new_target_value.as_function(); + } + return vm.construct(*target, *new_target, move(arguments), global_object); +} + +JS_DEFINE_NATIVE_FUNCTION(ReflectObject::define_property) +{ + auto* target = get_target_object_from(global_object, "defineProperty"); + if (!target) + return {}; + if (!vm.argument(2).is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::ReflectBadDescriptorArgument); + return {}; + } + auto property_key = StringOrSymbol::from_value(global_object, vm.argument(1)); + if (vm.exception()) + return {}; + auto& descriptor = vm.argument(2).as_object(); + auto success = target->define_property(property_key, descriptor, false); + if (vm.exception()) + return {}; + return Value(success); +} + +JS_DEFINE_NATIVE_FUNCTION(ReflectObject::delete_property) +{ + auto* target = get_target_object_from(global_object, "deleteProperty"); + if (!target) + return {}; + + auto property_key = vm.argument(1); + auto property_name = PropertyName::from_value(global_object, property_key); + if (vm.exception()) + return {}; + auto property_key_number = property_key.to_number(global_object); + if (vm.exception()) + return {}; + if (property_key_number.is_finite_number()) { + auto property_key_as_double = property_key_number.as_double(); + if (property_key_as_double >= 0 && (i32)property_key_as_double == property_key_as_double) + property_name = PropertyName(property_key_as_double); + } + return target->delete_property(property_name); +} + +JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get) +{ + auto* target = get_target_object_from(global_object, "get"); + if (!target) + return {}; + auto property_key = PropertyName::from_value(global_object, vm.argument(1)); + if (vm.exception()) + return {}; + Value receiver = {}; + if (vm.argument_count() > 2) + receiver = vm.argument(2); + return target->get(property_key, receiver).value_or(js_undefined()); +} + +JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get_own_property_descriptor) +{ + auto* target = get_target_object_from(global_object, "getOwnPropertyDescriptor"); + if (!target) + return {}; + auto property_key = PropertyName::from_value(global_object, vm.argument(1)); + if (vm.exception()) + return {}; + return target->get_own_property_descriptor_object(property_key); +} + +JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get_prototype_of) +{ + auto* target = get_target_object_from(global_object, "getPrototypeOf"); + if (!target) + return {}; + return target->prototype(); +} + +JS_DEFINE_NATIVE_FUNCTION(ReflectObject::has) +{ + auto* target = get_target_object_from(global_object, "has"); + if (!target) + return {}; + auto property_key = PropertyName::from_value(global_object, vm.argument(1)); + if (vm.exception()) + return {}; + return Value(target->has_property(property_key)); +} + +JS_DEFINE_NATIVE_FUNCTION(ReflectObject::is_extensible) +{ + auto* target = get_target_object_from(global_object, "isExtensible"); + if (!target) + return {}; + return Value(target->is_extensible()); +} + +JS_DEFINE_NATIVE_FUNCTION(ReflectObject::own_keys) +{ + auto* target = get_target_object_from(global_object, "ownKeys"); + if (!target) + return {}; + return target->get_own_properties(*target, PropertyKind::Key); +} + +JS_DEFINE_NATIVE_FUNCTION(ReflectObject::prevent_extensions) +{ + auto* target = get_target_object_from(global_object, "preventExtensions"); + if (!target) + return {}; + return Value(target->prevent_extensions()); +} + +JS_DEFINE_NATIVE_FUNCTION(ReflectObject::set) +{ + auto* target = get_target_object_from(global_object, "set"); + if (!target) + return {}; + auto property_key = vm.argument(1).to_string(global_object); + if (vm.exception()) + return {}; + auto value = vm.argument(2); + Value receiver = {}; + if (vm.argument_count() > 3) + receiver = vm.argument(3); + return Value(target->put(property_key, value, receiver)); +} + +JS_DEFINE_NATIVE_FUNCTION(ReflectObject::set_prototype_of) +{ + auto* target = get_target_object_from(global_object, "setPrototypeOf"); + if (!target) + return {}; + auto prototype_value = vm.argument(1); + if (!prototype_value.is_object() && !prototype_value.is_null()) { + vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeWrongType); + return {}; + } + Object* prototype = nullptr; + if (!prototype_value.is_null()) + prototype = const_cast<Object*>(&prototype_value.as_object()); + return Value(target->set_prototype(prototype)); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/ReflectObject.h b/Userland/Libraries/LibJS/Runtime/ReflectObject.h new file mode 100644 index 0000000000..68f2fd497e --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ReflectObject.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class ReflectObject final : public Object { + JS_OBJECT(ReflectObject, Object); + +public: + explicit ReflectObject(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~ReflectObject() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(apply); + JS_DECLARE_NATIVE_FUNCTION(construct); + JS_DECLARE_NATIVE_FUNCTION(define_property); + JS_DECLARE_NATIVE_FUNCTION(delete_property); + JS_DECLARE_NATIVE_FUNCTION(get); + JS_DECLARE_NATIVE_FUNCTION(get_own_property_descriptor); + JS_DECLARE_NATIVE_FUNCTION(get_prototype_of); + JS_DECLARE_NATIVE_FUNCTION(has); + JS_DECLARE_NATIVE_FUNCTION(is_extensible); + JS_DECLARE_NATIVE_FUNCTION(own_keys); + JS_DECLARE_NATIVE_FUNCTION(prevent_extensions); + JS_DECLARE_NATIVE_FUNCTION(set); + JS_DECLARE_NATIVE_FUNCTION(set_prototype_of); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/RegExpConstructor.cpp b/Userland/Libraries/LibJS/Runtime/RegExpConstructor.cpp new file mode 100644 index 0000000000..477261b664 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/RegExpConstructor.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/RegExpConstructor.h> +#include <LibJS/Runtime/RegExpObject.h> + +namespace JS { + +RegExpConstructor::RegExpConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.RegExp, *global_object.function_prototype()) +{ +} + +void RegExpConstructor::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + NativeFunction::initialize(global_object); + define_property(vm.names.prototype, global_object.regexp_prototype(), 0); + define_property(vm.names.length, Value(2), Attribute::Configurable); +} + +RegExpConstructor::~RegExpConstructor() +{ +} + +Value RegExpConstructor::call() +{ + return construct(*this); +} + +Value RegExpConstructor::construct(Function&) +{ + auto& vm = this->vm(); + String pattern = ""; + String flags = ""; + if (!vm.argument(0).is_undefined()) { + pattern = vm.argument(0).to_string(global_object()); + if (vm.exception()) + return {}; + } + if (!vm.argument(1).is_undefined()) { + flags = vm.argument(1).to_string(global_object()); + if (vm.exception()) + return {}; + } + return RegExpObject::create(global_object(), pattern, flags); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/RegExpConstructor.h b/Userland/Libraries/LibJS/Runtime/RegExpConstructor.h new file mode 100644 index 0000000000..0c3d055636 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/RegExpConstructor.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/NativeFunction.h> + +namespace JS { + +class RegExpConstructor final : public NativeFunction { + JS_OBJECT(RegExpConstructor, NativeFunction); + +public: + explicit RegExpConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~RegExpConstructor() override; + + virtual Value call() override; + virtual Value construct(Function& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/RegExpObject.cpp b/Userland/Libraries/LibJS/Runtime/RegExpObject.cpp new file mode 100644 index 0000000000..cfe0364497 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/RegExpObject.cpp @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <AK/Function.h> +#include <AK/StringBuilder.h> +#include <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/PrimitiveString.h> +#include <LibJS/Runtime/RegExpObject.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +static Flags options_from(const String& flags, VM& vm, GlobalObject& global_object) +{ + bool g = false, i = false, m = false, s = false, u = false, y = false; + Flags options { + { (regex::ECMAScriptFlags)regex::AllFlags::Global }, // JS regexps are all 'global' by default as per our definition, but the "global" flag enables "stateful". + {}, + }; + + for (auto ch : flags) { + switch (ch) { + case 'g': + if (g) + vm.throw_exception<SyntaxError>(global_object, ErrorType::RegExpObjectRepeatedFlag, ch); + g = true; + options.effective_flags |= regex::ECMAScriptFlags::Global; + options.declared_flags |= regex::ECMAScriptFlags::Global; + break; + case 'i': + if (i) + vm.throw_exception<SyntaxError>(global_object, ErrorType::RegExpObjectRepeatedFlag, ch); + i = true; + options.effective_flags |= regex::ECMAScriptFlags::Insensitive; + options.declared_flags |= regex::ECMAScriptFlags::Insensitive; + break; + case 'm': + if (m) + vm.throw_exception<SyntaxError>(global_object, ErrorType::RegExpObjectRepeatedFlag, ch); + m = true; + options.effective_flags |= regex::ECMAScriptFlags::Multiline; + options.declared_flags |= regex::ECMAScriptFlags::Multiline; + break; + case 's': + if (s) + vm.throw_exception<SyntaxError>(global_object, ErrorType::RegExpObjectRepeatedFlag, ch); + s = true; + options.effective_flags |= regex::ECMAScriptFlags::SingleLine; + options.declared_flags |= regex::ECMAScriptFlags::SingleLine; + break; + case 'u': + if (u) + vm.throw_exception<SyntaxError>(global_object, ErrorType::RegExpObjectRepeatedFlag, ch); + u = true; + options.effective_flags |= regex::ECMAScriptFlags::Unicode; + options.declared_flags |= regex::ECMAScriptFlags::Unicode; + break; + case 'y': + if (y) + vm.throw_exception<SyntaxError>(global_object, ErrorType::RegExpObjectRepeatedFlag, ch); + y = true; + // Now for the more interesting flag, 'sticky' actually unsets 'global', part of which is the default. + options.effective_flags.reset_flag(regex::ECMAScriptFlags::Global); + // "What's the difference between sticky and global, then", that's simple. + // all the other flags imply 'global', and the "global" flag implies 'stateful'; + // however, the "sticky" flag does *not* imply 'global', only 'stateful'. + options.effective_flags |= (regex::ECMAScriptFlags)regex::AllFlags::Internal_Stateful; + options.effective_flags |= regex::ECMAScriptFlags::Sticky; + options.declared_flags |= regex::ECMAScriptFlags::Sticky; + break; + default: + vm.throw_exception<SyntaxError>(global_object, ErrorType::RegExpObjectBadFlag, ch); + return options; + } + } + + return options; +} + +RegExpObject* RegExpObject::create(GlobalObject& global_object, String pattern, String flags) +{ + return global_object.heap().allocate<RegExpObject>(global_object, pattern, flags, *global_object.regexp_prototype()); +} + +RegExpObject::RegExpObject(String pattern, String flags, Object& prototype) + : Object(prototype) + , m_pattern(pattern) + , m_flags(flags) + , m_active_flags(options_from(m_flags, this->vm(), this->global_object())) + , m_regex(pattern, m_active_flags.effective_flags) +{ + if (m_regex.parser_result.error != regex::Error::NoError) { + vm().throw_exception<SyntaxError>(global_object(), ErrorType::RegExpCompileError, m_regex.error_string()); + } +} + +void RegExpObject::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + + define_native_property(vm.names.lastIndex, last_index, set_last_index, Attribute::Writable); +} + +RegExpObject::~RegExpObject() +{ +} + +static RegExpObject* regexp_object_from(VM& vm, GlobalObject& global_object) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return nullptr; + if (!is<RegExpObject>(this_object)) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "RegExp"); + return nullptr; + } + return static_cast<RegExpObject*>(this_object); +} + +JS_DEFINE_NATIVE_GETTER(RegExpObject::last_index) +{ + auto regexp_object = regexp_object_from(vm, global_object); + if (!regexp_object) + return {}; + + return Value((unsigned)regexp_object->regex().start_offset); +} + +JS_DEFINE_NATIVE_SETTER(RegExpObject::set_last_index) +{ + auto regexp_object = regexp_object_from(vm, global_object); + if (!regexp_object) + return; + + auto index = value.to_i32(global_object); + if (vm.exception()) + return; + + if (index < 0) + index = 0; + + regexp_object->regex().start_offset = index; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/RegExpObject.h b/Userland/Libraries/LibJS/Runtime/RegExpObject.h new file mode 100644 index 0000000000..7be6b96069 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/RegExpObject.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/AST.h> +#include <LibJS/Runtime/Object.h> +#include <LibRegex/Regex.h> + +struct Flags { + regex::RegexOptions<ECMAScriptFlags> effective_flags; + regex::RegexOptions<ECMAScriptFlags> declared_flags; +}; + +namespace JS { + +class RegExpObject : public Object { + JS_OBJECT(RegExpObject, Object); + +public: + static RegExpObject* create(GlobalObject&, String pattern, String flags); + + RegExpObject(String pattern, String flags, Object& prototype); + virtual void initialize(GlobalObject&) override; + virtual ~RegExpObject() override; + + const String& pattern() const { return m_pattern; } + const String& flags() const { return m_flags; } + const regex::RegexOptions<ECMAScriptFlags>& declared_options() { return m_active_flags.declared_flags; } + const Regex<ECMA262>& regex() { return m_regex; } + const Regex<ECMA262>& regex() const { return m_regex; } + +private: + JS_DECLARE_NATIVE_GETTER(last_index); + JS_DECLARE_NATIVE_SETTER(set_last_index); + + String m_pattern; + String m_flags; + Flags m_active_flags; + Regex<ECMA262> m_regex; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp new file mode 100644 index 0000000000..8540b7bc06 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <AK/Function.h> +#include <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/RegExpObject.h> +#include <LibJS/Runtime/RegExpPrototype.h> +#include <LibJS/Token.h> + +namespace JS { + +RegExpPrototype::RegExpPrototype(GlobalObject& global_object) + : RegExpObject({}, {}, *global_object.object_prototype()) +{ +} + +void RegExpPrototype::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.toString, to_string, 0, attr); + define_native_function(vm.names.test, test, 1, attr); + define_native_function(vm.names.exec, exec, 1, attr); + + u8 readable_attr = Attribute::Configurable; + define_native_property(vm.names.flags, flags, {}, readable_attr); + define_native_property(vm.names.source, source, {}, readable_attr); + +#define __JS_ENUMERATE(flagName, flag_name, flag_char, ECMAScriptFlagName) \ + define_native_property(vm.names.flagName, flag_name, {}, readable_attr); + JS_ENUMERATE_REGEXP_FLAGS +#undef __JS_ENUMERATE +} + +RegExpPrototype::~RegExpPrototype() +{ +} + +static Object* this_object_from(VM& vm, GlobalObject& global_object) +{ + auto this_value = vm.this_value(global_object); + if (!this_value.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, this_value.to_string_without_side_effects()); + return {}; + } + return &this_value.as_object(); +} + +static RegExpObject* regexp_object_from(VM& vm, GlobalObject& global_object) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return nullptr; + if (!is<RegExpObject>(this_object)) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "RegExp"); + return nullptr; + } + return static_cast<RegExpObject*>(this_object); +} + +static String escape_regexp_pattern(const RegExpObject& regexp_object) +{ + auto pattern = regexp_object.pattern(); + if (pattern.is_empty()) + return "(?:)"; + // FIXME: Check u flag and escape accordingly + pattern.replace("\n", "\\n", true); + pattern.replace("\r", "\\r", true); + pattern.replace(LINE_SEPARATOR, "\\u2028", true); + pattern.replace(PARAGRAPH_SEPARATOR, "\\u2029", true); + pattern.replace("/", "\\/", true); + return pattern; +} + +#define __JS_ENUMERATE(flagName, flag_name, flag_char, ECMAScriptFlagName) \ + JS_DEFINE_NATIVE_GETTER(RegExpPrototype::flag_name) \ + { \ + auto regexp_object = regexp_object_from(vm, global_object); \ + if (!regexp_object) \ + return {}; \ + \ + return Value(regexp_object->declared_options().has_flag_set(ECMAScriptFlags::ECMAScriptFlagName)); \ + } +JS_ENUMERATE_REGEXP_FLAGS +#undef __JS_ENUMERATE + +JS_DEFINE_NATIVE_GETTER(RegExpPrototype::flags) +{ + auto this_object = this_object_from(vm, global_object); + if (!this_object) + return {}; + + StringBuilder builder(8); + +#define __JS_ENUMERATE(flagName, flag_name, flag_char, ECMAScriptFlagName) \ + auto flag_##flag_name = this_object->get(vm.names.flagName).value_or(js_undefined()); \ + if (vm.exception()) \ + return {}; \ + if (flag_##flag_name.to_boolean()) \ + builder.append(#flag_char); + JS_ENUMERATE_REGEXP_FLAGS +#undef __JS_ENUMERATE + + return js_string(vm, builder.to_string()); +} + +JS_DEFINE_NATIVE_GETTER(RegExpPrototype::source) +{ + auto this_object = this_object_from(vm, global_object); + if (!this_object) + return {}; + + // FIXME: This is obnoxious - we should have an easier way of looking up %RegExp.prototype%. + auto& regexp_prototype = global_object.get(vm.names.RegExp).as_object().get(vm.names.prototype).as_object(); + if (this_object == ®exp_prototype) + return js_string(vm, "(?:)"); + + auto regexp_object = regexp_object_from(vm, global_object); + if (!regexp_object) + return {}; + + return js_string(vm, escape_regexp_pattern(*regexp_object)); +} + +RegexResult RegExpPrototype::do_match(const Regex<ECMA262>& re, const StringView& subject) +{ + auto result = re.match(subject); + // The 'lastIndex' property is reset on failing tests (if 'global') + if (!result.success && re.options().has_flag_set(ECMAScriptFlags::Global)) + re.start_offset = 0; + + return result; +} + +JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::exec) +{ + // FIXME: This should try using dynamic properties for 'lastIndex', + // and internal slots [[RegExpMatcher]], [[OriginalFlags]], etc. + auto regexp_object = regexp_object_from(vm, global_object); + if (!regexp_object) + return {}; + + auto str = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + + StringView str_to_match = str; + + // RegExps without "global" and "sticky" always start at offset 0. + if (!regexp_object->regex().options().has_flag_set((ECMAScriptFlags)regex::AllFlags::Internal_Stateful)) + regexp_object->regex().start_offset = 0; + + auto result = do_match(regexp_object->regex(), str_to_match); + if (!result.success) + return js_null(); + + auto& match = result.matches[0]; + + // FIXME: Do code point index correction if the Unicode flag is set. + auto* array = Array::create(global_object); + array->indexed_properties().set_array_like_size(result.n_capture_groups + 1); + array->define_property(vm.names.index, Value((i32)match.column)); + array->define_property(vm.names.input, js_string(vm, str)); + array->indexed_properties().put(array, 0, js_string(vm, match.view.to_string())); + + for (size_t i = 0; i < result.n_capture_groups; ++i) { + auto& capture = result.capture_group_matches[0][i]; + array->indexed_properties().put(array, i + 1, js_string(vm, capture.view.to_string())); + } + + Value groups = js_undefined(); + if (result.n_named_capture_groups > 0) { + auto groups_object = create_empty(global_object); + for (auto& entry : result.named_capture_group_matches[0]) + groups_object->define_property(entry.key, js_string(vm, entry.value.view.to_string())); + groups = move(groups_object); + } + + array->define_property(vm.names.groups, groups); + + return array; +} + +JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::test) +{ + // FIXME: This should try using dynamic properties for 'exec' first, + // before falling back to builtin_exec. + auto regexp_object = regexp_object_from(vm, global_object); + if (!regexp_object) + return {}; + + auto str = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + + // RegExps without "global" and "sticky" always start at offset 0. + if (!regexp_object->regex().options().has_flag_set((ECMAScriptFlags)regex::AllFlags::Internal_Stateful)) + regexp_object->regex().start_offset = 0; + + auto result = do_match(regexp_object->regex(), str); + return Value(result.success); +} + +JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::to_string) +{ + auto this_object = this_object_from(vm, global_object); + if (!this_object) + return {}; + + auto source_attr = this_object->get(vm.names.source).value_or(js_undefined()); + if (vm.exception()) + return {}; + auto pattern = source_attr.to_string(global_object); + if (vm.exception()) + return {}; + + auto flags_attr = this_object->get(vm.names.flags).value_or(js_undefined()); + if (vm.exception()) + return {}; + auto flags = flags_attr.to_string(global_object); + if (vm.exception()) + return {}; + + return js_string(vm, String::formatted("/{}/{}", pattern, flags)); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.h b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.h new file mode 100644 index 0000000000..69b0708ce1 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/RegExpObject.h> + +namespace JS { + +class RegExpPrototype final : public RegExpObject { + JS_OBJECT(RegExpPrototype, RegExpObject); + +public: + explicit RegExpPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~RegExpPrototype() override; + +private: + static RegexResult do_match(const Regex<ECMA262>&, const StringView&); + + JS_DECLARE_NATIVE_GETTER(flags); + JS_DECLARE_NATIVE_GETTER(source); + + JS_DECLARE_NATIVE_FUNCTION(exec); + JS_DECLARE_NATIVE_FUNCTION(test); + JS_DECLARE_NATIVE_FUNCTION(to_string); + +#define __JS_ENUMERATE(_, flag_name, ...) \ + JS_DECLARE_NATIVE_GETTER(flag_name); + JS_ENUMERATE_REGEXP_FLAGS +#undef __JS_ENUMERATE +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/ScopeObject.cpp b/Userland/Libraries/LibJS/Runtime/ScopeObject.cpp new file mode 100644 index 0000000000..5a67011a55 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ScopeObject.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/ScopeObject.h> +#include <LibJS/Runtime/VM.h> + +namespace JS { + +ScopeObject::ScopeObject(ScopeObject* parent) + : Object(vm().scope_object_shape()) + , m_parent(parent) +{ +} + +ScopeObject::ScopeObject(GlobalObjectTag tag) + : Object(tag) +{ +} + +void ScopeObject::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_parent); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/ScopeObject.h b/Userland/Libraries/LibJS/Runtime/ScopeObject.h new file mode 100644 index 0000000000..ac29c11503 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ScopeObject.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +struct Variable { + Value value; + DeclarationKind declaration_kind; +}; + +class ScopeObject : public Object { + JS_OBJECT(ScopeObject, Object); + +public: + virtual Optional<Variable> get_from_scope(const FlyString&) const = 0; + virtual void put_to_scope(const FlyString&, Variable) = 0; + virtual bool has_this_binding() const = 0; + virtual Value get_this_binding(GlobalObject&) const = 0; + + ScopeObject* parent() { return m_parent; } + const ScopeObject* parent() const { return m_parent; } + +protected: + explicit ScopeObject(ScopeObject* parent); + explicit ScopeObject(GlobalObjectTag); + + virtual void visit_edges(Visitor&) override; + +private: + ScopeObject* m_parent { nullptr }; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp b/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp new file mode 100644 index 0000000000..56ce94edd2 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@gmx.de> + * 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 <AK/Function.h> +#include <LibJS/AST.h> +#include <LibJS/Interpreter.h> +#include <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/ScriptFunction.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +static ScriptFunction* typed_this(VM& vm, GlobalObject& global_object) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return nullptr; + if (!this_object->is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunctionNoParam); + return nullptr; + } + return static_cast<ScriptFunction*>(this_object); +} + +ScriptFunction* ScriptFunction::create(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, bool is_strict, bool is_arrow_function) +{ + return global_object.heap().allocate<ScriptFunction>(global_object, global_object, name, body, move(parameters), m_function_length, parent_scope, *global_object.function_prototype(), is_strict, is_arrow_function); +} + +ScriptFunction::ScriptFunction(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, Object& prototype, bool is_strict, bool is_arrow_function) + : Function(prototype, is_arrow_function ? vm().this_value(global_object) : Value(), {}) + , m_name(name) + , m_body(body) + , m_parameters(move(parameters)) + , m_parent_scope(parent_scope) + , m_function_length(m_function_length) + , m_is_strict(is_strict) + , m_is_arrow_function(is_arrow_function) +{ +} + +void ScriptFunction::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Function::initialize(global_object); + if (!m_is_arrow_function) { + Object* prototype = vm.heap().allocate<Object>(global_object, *global_object.new_script_function_prototype_object_shape()); + prototype->define_property(vm.names.constructor, this, Attribute::Writable | Attribute::Configurable); + define_property(vm.names.prototype, prototype, Attribute::Writable); + } + define_native_property(vm.names.length, length_getter, {}, Attribute::Configurable); + define_native_property(vm.names.name, name_getter, {}, Attribute::Configurable); +} + +ScriptFunction::~ScriptFunction() +{ +} + +void ScriptFunction::visit_edges(Visitor& visitor) +{ + Function::visit_edges(visitor); + visitor.visit(m_parent_scope); +} + +LexicalEnvironment* ScriptFunction::create_environment() +{ + HashMap<FlyString, Variable> variables; + for (auto& parameter : m_parameters) { + variables.set(parameter.name, { js_undefined(), DeclarationKind::Var }); + } + + if (is<ScopeNode>(body())) { + for (auto& declaration : static_cast<const ScopeNode&>(body()).variables()) { + for (auto& declarator : declaration.declarations()) { + variables.set(declarator.id().string(), { js_undefined(), DeclarationKind::Var }); + } + } + } + + auto* environment = heap().allocate<LexicalEnvironment>(global_object(), move(variables), m_parent_scope, LexicalEnvironment::EnvironmentRecordType::Function); + environment->set_home_object(home_object()); + environment->set_current_function(*this); + if (m_is_arrow_function) { + if (is<LexicalEnvironment>(m_parent_scope)) + environment->set_new_target(static_cast<LexicalEnvironment*>(m_parent_scope)->new_target()); + } + return environment; +} + +Value ScriptFunction::execute_function_body() +{ + auto& vm = this->vm(); + + OwnPtr<Interpreter> local_interpreter; + Interpreter* interpreter = vm.interpreter_if_exists(); + + if (!interpreter) { + local_interpreter = Interpreter::create_with_existing_global_object(global_object()); + interpreter = local_interpreter.ptr(); + } + + VM::InterpreterExecutionScope scope(*interpreter); + + auto& call_frame_args = vm.call_frame().arguments; + for (size_t i = 0; i < m_parameters.size(); ++i) { + auto parameter = m_parameters[i]; + Value argument_value; + if (parameter.is_rest) { + auto* array = Array::create(global_object()); + for (size_t rest_index = i; rest_index < call_frame_args.size(); ++rest_index) + array->indexed_properties().append(call_frame_args[rest_index]); + argument_value = move(array); + } else if (i < call_frame_args.size() && !call_frame_args[i].is_undefined()) { + argument_value = call_frame_args[i]; + } else if (parameter.default_value) { + argument_value = parameter.default_value->execute(*interpreter, global_object()); + if (vm.exception()) + return {}; + } else { + argument_value = js_undefined(); + } + vm.current_scope()->put_to_scope(parameter.name, { argument_value, DeclarationKind::Var }); + } + + return interpreter->execute_statement(global_object(), m_body, ScopeType::Function); +} + +Value ScriptFunction::call() +{ + if (m_is_class_constructor) { + vm().throw_exception<TypeError>(global_object(), ErrorType::ClassConstructorWithoutNew, m_name); + return {}; + } + return execute_function_body(); +} + +Value ScriptFunction::construct(Function&) +{ + if (m_is_arrow_function) { + vm().throw_exception<TypeError>(global_object(), ErrorType::NotAConstructor, m_name); + return {}; + } + return execute_function_body(); +} + +JS_DEFINE_NATIVE_GETTER(ScriptFunction::length_getter) +{ + auto* function = typed_this(vm, global_object); + if (!function) + return {}; + return Value(static_cast<i32>(function->m_function_length)); +} + +JS_DEFINE_NATIVE_GETTER(ScriptFunction::name_getter) +{ + auto* function = typed_this(vm, global_object); + if (!function) + return {}; + return js_string(vm, function->name().is_null() ? "" : function->name()); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/ScriptFunction.h b/Userland/Libraries/LibJS/Runtime/ScriptFunction.h new file mode 100644 index 0000000000..e3765509ab --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ScriptFunction.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/AST.h> +#include <LibJS/Runtime/Function.h> + +namespace JS { + +class ScriptFunction final : public Function { + JS_OBJECT(ScriptFunction, Function); + +public: + static ScriptFunction* create(GlobalObject&, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, bool is_strict, bool is_arrow_function = false); + + ScriptFunction(GlobalObject&, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, Object& prototype, bool is_strict, bool is_arrow_function = false); + virtual void initialize(GlobalObject&) override; + virtual ~ScriptFunction(); + + const Statement& body() const { return m_body; } + const Vector<FunctionNode::Parameter>& parameters() const { return m_parameters; }; + + virtual Value call() override; + virtual Value construct(Function& new_target) override; + + virtual const FlyString& name() const override { return m_name; }; + void set_name(const FlyString& name) { m_name = name; }; + + void set_is_class_constructor() { m_is_class_constructor = true; }; + +protected: + virtual bool is_strict_mode() const final { return m_is_strict; } + +private: + virtual LexicalEnvironment* create_environment() override; + virtual void visit_edges(Visitor&) override; + + Value execute_function_body(); + + JS_DECLARE_NATIVE_GETTER(length_getter); + JS_DECLARE_NATIVE_GETTER(name_getter); + + FlyString m_name; + NonnullRefPtr<Statement> m_body; + const Vector<FunctionNode::Parameter> m_parameters; + ScopeObject* m_parent_scope { nullptr }; + i32 m_function_length { 0 }; + bool m_is_strict { false }; + bool m_is_arrow_function { false }; + bool m_is_class_constructor { false }; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Shape.cpp b/Userland/Libraries/LibJS/Runtime/Shape.cpp new file mode 100644 index 0000000000..1f74b14471 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Shape.cpp @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Heap/DeferGC.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/Shape.h> + +namespace JS { + +Shape* Shape::create_unique_clone() const +{ + ASSERT(m_global_object); + auto* new_shape = heap().allocate_without_global_object<Shape>(*m_global_object); + new_shape->m_unique = true; + new_shape->m_prototype = m_prototype; + ensure_property_table(); + new_shape->ensure_property_table(); + (*new_shape->m_property_table) = *m_property_table; + new_shape->m_property_count = new_shape->m_property_table->size(); + return new_shape; +} + +Shape* Shape::create_put_transition(const StringOrSymbol& property_name, PropertyAttributes attributes) +{ + TransitionKey key { property_name, attributes }; + if (auto* existing_shape = m_forward_transitions.get(key).value_or(nullptr)) + return existing_shape; + auto* new_shape = heap().allocate_without_global_object<Shape>(*this, property_name, attributes, TransitionType::Put); + m_forward_transitions.set(key, new_shape); + return new_shape; +} + +Shape* Shape::create_configure_transition(const StringOrSymbol& property_name, PropertyAttributes attributes) +{ + TransitionKey key { property_name, attributes }; + if (auto* existing_shape = m_forward_transitions.get(key).value_or(nullptr)) + return existing_shape; + auto* new_shape = heap().allocate_without_global_object<Shape>(*this, property_name, attributes, TransitionType::Configure); + m_forward_transitions.set(key, new_shape); + return new_shape; +} + +Shape* Shape::create_prototype_transition(Object* new_prototype) +{ + return heap().allocate_without_global_object<Shape>(*this, new_prototype); +} + +Shape::Shape(ShapeWithoutGlobalObjectTag) +{ +} + +Shape::Shape(Object& global_object) + : m_global_object(&global_object) +{ +} + +Shape::Shape(Shape& previous_shape, const StringOrSymbol& property_name, PropertyAttributes attributes, TransitionType transition_type) + : m_attributes(attributes) + , m_transition_type(transition_type) + , m_global_object(previous_shape.m_global_object) + , m_previous(&previous_shape) + , m_property_name(property_name) + , m_prototype(previous_shape.m_prototype) + , m_property_count(transition_type == TransitionType::Put ? previous_shape.m_property_count + 1 : previous_shape.m_property_count) +{ +} + +Shape::Shape(Shape& previous_shape, Object* new_prototype) + : m_transition_type(TransitionType::Prototype) + , m_global_object(previous_shape.m_global_object) + , m_previous(&previous_shape) + , m_prototype(new_prototype) + , m_property_count(previous_shape.m_property_count) +{ +} + +Shape::~Shape() +{ +} + +void Shape::visit_edges(Cell::Visitor& visitor) +{ + Cell::visit_edges(visitor); + visitor.visit(m_global_object); + visitor.visit(m_prototype); + visitor.visit(m_previous); + m_property_name.visit_edges(visitor); + for (auto& it : m_forward_transitions) + visitor.visit(it.value); + + if (m_property_table) { + for (auto& it : *m_property_table) + it.key.visit_edges(visitor); + } +} + +Optional<PropertyMetadata> Shape::lookup(const StringOrSymbol& property_name) const +{ + if (m_property_count == 0) + return {}; + auto property = property_table().get(property_name); + if (!property.has_value()) + return {}; + return property; +} + +const HashMap<StringOrSymbol, PropertyMetadata>& Shape::property_table() const +{ + ensure_property_table(); + return *m_property_table; +} + +size_t Shape::property_count() const +{ + return m_property_count; +} + +Vector<Shape::Property> Shape::property_table_ordered() const +{ + auto vec = Vector<Shape::Property>(); + vec.resize(property_count()); + + for (auto& it : property_table()) { + vec[it.value.offset] = { it.key, it.value }; + } + + return vec; +} + +void Shape::ensure_property_table() const +{ + if (m_property_table) + return; + m_property_table = make<HashMap<StringOrSymbol, PropertyMetadata>>(); + + u32 next_offset = 0; + + Vector<const Shape*, 64> transition_chain; + for (auto* shape = m_previous; shape; shape = shape->m_previous) { + if (shape->m_property_table) { + *m_property_table = *shape->m_property_table; + next_offset = shape->m_property_count; + break; + } + transition_chain.append(shape); + } + transition_chain.append(this); + + for (ssize_t i = transition_chain.size() - 1; i >= 0; --i) { + auto* shape = transition_chain[i]; + if (!shape->m_property_name.is_valid()) { + // Ignore prototype transitions as they don't affect the key map. + continue; + } + if (shape->m_transition_type == TransitionType::Put) { + m_property_table->set(shape->m_property_name, { next_offset++, shape->m_attributes }); + } else if (shape->m_transition_type == TransitionType::Configure) { + auto it = m_property_table->find(shape->m_property_name); + ASSERT(it != m_property_table->end()); + it->value.attributes = shape->m_attributes; + } + } +} + +void Shape::add_property_to_unique_shape(const StringOrSymbol& property_name, PropertyAttributes attributes) +{ + ASSERT(is_unique()); + ASSERT(m_property_table); + ASSERT(!m_property_table->contains(property_name)); + m_property_table->set(property_name, { m_property_table->size(), attributes }); + ++m_property_count; +} + +void Shape::reconfigure_property_in_unique_shape(const StringOrSymbol& property_name, PropertyAttributes attributes) +{ + ASSERT(is_unique()); + ASSERT(m_property_table); + auto it = m_property_table->find(property_name); + ASSERT(it != m_property_table->end()); + it->value.attributes = attributes; + m_property_table->set(property_name, it->value); +} + +void Shape::remove_property_from_unique_shape(const StringOrSymbol& property_name, size_t offset) +{ + ASSERT(is_unique()); + ASSERT(m_property_table); + if (m_property_table->remove(property_name)) + --m_property_count; + for (auto& it : *m_property_table) { + ASSERT(it.value.offset != offset); + if (it.value.offset > offset) + --it.value.offset; + } +} + +void Shape::add_property_without_transition(const StringOrSymbol& property_name, PropertyAttributes attributes) +{ + ensure_property_table(); + if (m_property_table->set(property_name, { m_property_count, attributes }) == AK::HashSetResult::InsertedNewEntry) + ++m_property_count; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Shape.h b/Userland/Libraries/LibJS/Runtime/Shape.h new file mode 100644 index 0000000000..1d2bd51037 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Shape.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/HashMap.h> +#include <AK/OwnPtr.h> +#include <LibJS/Forward.h> +#include <LibJS/Runtime/Cell.h> +#include <LibJS/Runtime/PropertyAttributes.h> +#include <LibJS/Runtime/StringOrSymbol.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +struct PropertyMetadata { + size_t offset { 0 }; + PropertyAttributes attributes { 0 }; +}; + +struct TransitionKey { + StringOrSymbol property_name; + PropertyAttributes attributes { 0 }; + + bool operator==(const TransitionKey& other) const + { + return property_name == other.property_name && attributes == other.attributes; + } +}; + +class Shape final : public Cell { +public: + virtual ~Shape() override; + + enum class TransitionType { + Invalid, + Put, + Configure, + Prototype, + }; + + enum class ShapeWithoutGlobalObjectTag { Tag }; + + explicit Shape(ShapeWithoutGlobalObjectTag); + explicit Shape(Object& global_object); + Shape(Shape& previous_shape, const StringOrSymbol& property_name, PropertyAttributes attributes, TransitionType); + Shape(Shape& previous_shape, Object* new_prototype); + + Shape* create_put_transition(const StringOrSymbol&, PropertyAttributes attributes); + Shape* create_configure_transition(const StringOrSymbol&, PropertyAttributes attributes); + Shape* create_prototype_transition(Object* new_prototype); + + void add_property_without_transition(const StringOrSymbol&, PropertyAttributes); + + bool is_unique() const { return m_unique; } + Shape* create_unique_clone() const; + + GlobalObject* global_object() const; + + Object* prototype() { return m_prototype; } + const Object* prototype() const { return m_prototype; } + + Optional<PropertyMetadata> lookup(const StringOrSymbol&) const; + const HashMap<StringOrSymbol, PropertyMetadata>& property_table() const; + size_t property_count() const; + + struct Property { + StringOrSymbol key; + PropertyMetadata value; + }; + + Vector<Property> property_table_ordered() const; + + void set_prototype_without_transition(Object* new_prototype) { m_prototype = new_prototype; } + + void remove_property_from_unique_shape(const StringOrSymbol&, size_t offset); + void add_property_to_unique_shape(const StringOrSymbol&, PropertyAttributes attributes); + void reconfigure_property_in_unique_shape(const StringOrSymbol& property_name, PropertyAttributes attributes); + +private: + virtual const char* class_name() const override { return "Shape"; } + virtual void visit_edges(Visitor&) override; + + void ensure_property_table() const; + + PropertyAttributes m_attributes { 0 }; + TransitionType m_transition_type : 6 { TransitionType::Invalid }; + bool m_unique : 1 { false }; + + Object* m_global_object { nullptr }; + + mutable OwnPtr<HashMap<StringOrSymbol, PropertyMetadata>> m_property_table; + + HashMap<TransitionKey, Shape*> m_forward_transitions; + Shape* m_previous { nullptr }; + StringOrSymbol m_property_name; + Object* m_prototype { nullptr }; + size_t m_property_count { 0 }; +}; + +} + +template<> +struct AK::Traits<JS::TransitionKey> : public GenericTraits<JS::TransitionKey> { + static unsigned hash(const JS::TransitionKey& key) + { + return pair_int_hash(key.attributes.bits(), Traits<JS::StringOrSymbol>::hash(key.property_name)); + } +}; diff --git a/Userland/Libraries/LibJS/Runtime/StringConstructor.cpp b/Userland/Libraries/LibJS/Runtime/StringConstructor.cpp new file mode 100644 index 0000000000..50872f2387 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/StringConstructor.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/StringBuilder.h> +#include <AK/Utf32View.h> +#include <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/StringConstructor.h> +#include <LibJS/Runtime/StringObject.h> + +namespace JS { + +StringConstructor::StringConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.String, *global_object.function_prototype()) +{ +} + +void StringConstructor::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + NativeFunction::initialize(global_object); + define_property(vm.names.prototype, global_object.string_prototype(), 0); + define_property(vm.names.length, Value(1), Attribute::Configurable); + + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.raw, raw, 1, attr); + define_native_function(vm.names.fromCharCode, from_char_code, 1, attr); +} + +StringConstructor::~StringConstructor() +{ +} + +Value StringConstructor::call() +{ + if (!vm().argument_count()) + return js_string(heap(), ""); + if (vm().argument(0).is_symbol()) + return js_string(heap(), vm().argument(0).as_symbol().to_string()); + auto* string = vm().argument(0).to_primitive_string(global_object()); + if (vm().exception()) + return {}; + return string; +} + +Value StringConstructor::construct(Function&) +{ + PrimitiveString* primitive_string = nullptr; + if (!vm().argument_count()) + primitive_string = js_string(vm(), ""); + else + primitive_string = vm().argument(0).to_primitive_string(global_object()); + if (!primitive_string) + return {}; + return StringObject::create(global_object(), *primitive_string); +} + +JS_DEFINE_NATIVE_FUNCTION(StringConstructor::raw) +{ + auto* template_object = vm.argument(0).to_object(global_object); + if (vm.exception()) + return {}; + + auto raw = template_object->get(vm.names.raw); + if (vm.exception()) + return {}; + if (raw.is_empty() || raw.is_nullish()) { + vm.throw_exception<TypeError>(global_object, ErrorType::StringRawCannotConvert, raw.is_null() ? "null" : "undefined"); + return {}; + } + if (!raw.is_array()) + return js_string(vm, ""); + + auto* array = static_cast<Array*>(raw.to_object(global_object)); + auto& raw_array_elements = array->indexed_properties(); + StringBuilder builder; + + for (size_t i = 0; i < raw_array_elements.array_like_size(); ++i) { + auto result = raw_array_elements.get(array, i); + if (vm.exception()) + return {}; + if (!result.has_value()) + continue; + builder.append(result.value().value.to_string(global_object)); + if (vm.exception()) + return {}; + if (i + 1 < vm.argument_count() && i < raw_array_elements.array_like_size() - 1) { + builder.append(vm.argument(i + 1).to_string(global_object)); + if (vm.exception()) + return {}; + } + } + + return js_string(vm, builder.build()); +} + +JS_DEFINE_NATIVE_FUNCTION(StringConstructor::from_char_code) +{ + StringBuilder builder; + for (size_t i = 0; i < vm.argument_count(); ++i) { + auto char_code = vm.argument(i).to_i32(global_object); + if (vm.exception()) + return {}; + auto truncated = char_code & 0xffff; + // FIXME: We need an Utf16View :^) + builder.append(Utf32View((u32*)&truncated, 1)); + } + return js_string(vm, builder.build()); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/StringConstructor.h b/Userland/Libraries/LibJS/Runtime/StringConstructor.h new file mode 100644 index 0000000000..239d9e7596 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/StringConstructor.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/NativeFunction.h> + +namespace JS { + +class StringConstructor final : public NativeFunction { + JS_OBJECT(StringConstructor, NativeFunction); + +public: + explicit StringConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~StringConstructor() override; + + virtual Value call() override; + virtual Value construct(Function& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } + + JS_DECLARE_NATIVE_FUNCTION(raw); + JS_DECLARE_NATIVE_FUNCTION(from_char_code); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/StringIterator.cpp b/Userland/Libraries/LibJS/Runtime/StringIterator.cpp new file mode 100644 index 0000000000..4c0b9a3355 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/StringIterator.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <AK/Utf8View.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/StringIterator.h> + +namespace JS { + +StringIterator* StringIterator::create(GlobalObject& global_object, String string) +{ + return global_object.heap().allocate<StringIterator>(global_object, *global_object.string_iterator_prototype(), move(string)); +} + +StringIterator::StringIterator(Object& prototype, String string) + : Object(prototype) + , m_string(move(string)) + , m_iterator(Utf8View(m_string).begin()) +{ +} + +StringIterator::~StringIterator() +{ +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/StringIterator.h b/Userland/Libraries/LibJS/Runtime/StringIterator.h new file mode 100644 index 0000000000..c5754cf147 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/StringIterator.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <AK/Utf8View.h> +#include <LibJS/Runtime/Object.h> + +namespace JS { + +class StringIterator final : public Object { + JS_OBJECT(StringIterator, Object); + +public: + static StringIterator* create(GlobalObject&, String string); + + explicit StringIterator(Object& prototype, String string); + virtual ~StringIterator() override; + + Utf8CodepointIterator& iterator() { return m_iterator; } + bool done() const { return m_done; } + +private: + friend class StringIteratorPrototype; + + String m_string; + Utf8CodepointIterator m_iterator; + bool m_done { false }; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/StringIteratorPrototype.cpp b/Userland/Libraries/LibJS/Runtime/StringIteratorPrototype.cpp new file mode 100644 index 0000000000..2259ae7b26 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/StringIteratorPrototype.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <AK/StringBuilder.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/IteratorOperations.h> +#include <LibJS/Runtime/StringIterator.h> +#include <LibJS/Runtime/StringIteratorPrototype.h> + +namespace JS { + +StringIteratorPrototype::StringIteratorPrototype(GlobalObject& global_object) + : Object(*global_object.iterator_prototype()) +{ +} + +void StringIteratorPrototype::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + define_native_function(vm.names.next, next, 0, Attribute::Configurable | Attribute::Writable); + define_property(vm.well_known_symbol_to_string_tag(), js_string(global_object.heap(), "String Iterator"), Attribute::Configurable); +} + +StringIteratorPrototype::~StringIteratorPrototype() +{ +} + +JS_DEFINE_NATIVE_FUNCTION(StringIteratorPrototype::next) +{ + auto this_value = vm.this_value(global_object); + if (!this_value.is_object() || !is<StringIterator>(this_value.as_object())) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "String Iterator"); + return {}; + } + + auto& this_object = this_value.as_object(); + auto& iterator = static_cast<StringIterator&>(this_object); + if (iterator.done()) + return create_iterator_result_object(global_object, js_undefined(), true); + + auto& utf8_iterator = iterator.iterator(); + + if (utf8_iterator.done()) { + iterator.m_done = true; + return create_iterator_result_object(global_object, js_undefined(), true); + } + + StringBuilder builder; + builder.append_code_point(*utf8_iterator); + ++utf8_iterator; + + return create_iterator_result_object(global_object, js_string(vm, builder.to_string()), false); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/StringIteratorPrototype.h b/Userland/Libraries/LibJS/Runtime/StringIteratorPrototype.h new file mode 100644 index 0000000000..a17e2507eb --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/StringIteratorPrototype.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class StringIteratorPrototype final : public Object { + JS_OBJECT(StringIteratorPrototype, Object) + +public: + StringIteratorPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~StringIteratorPrototype() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(next); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/StringObject.cpp b/Userland/Libraries/LibJS/Runtime/StringObject.cpp new file mode 100644 index 0000000000..cfe1fd6fd8 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/StringObject.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/PrimitiveString.h> +#include <LibJS/Runtime/StringObject.h> +#include <LibJS/Runtime/StringPrototype.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +StringObject* StringObject::create(GlobalObject& global_object, PrimitiveString& primitive_string) +{ + return global_object.heap().allocate<StringObject>(global_object, primitive_string, *global_object.string_prototype()); +} + +StringObject::StringObject(PrimitiveString& string, Object& prototype) + : Object(prototype) + , m_string(string) +{ +} + +StringObject::~StringObject() +{ +} + +void StringObject::visit_edges(Cell::Visitor& visitor) +{ + Object::visit_edges(visitor); + visitor.visit(&m_string); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/StringObject.h b/Userland/Libraries/LibJS/Runtime/StringObject.h new file mode 100644 index 0000000000..7fb32ebc63 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/StringObject.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class StringObject : public Object { + JS_OBJECT(StringObject, Object); + +public: + static StringObject* create(GlobalObject&, PrimitiveString&); + + StringObject(PrimitiveString&, Object& prototype); + virtual ~StringObject() override; + + const PrimitiveString& primitive_string() const { return m_string; } + virtual Value value_of() const override + { + return Value(&m_string); + } + +private: + virtual void visit_edges(Visitor&) override; + + PrimitiveString& m_string; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/StringOrSymbol.h b/Userland/Libraries/LibJS/Runtime/StringOrSymbol.h new file mode 100644 index 0000000000..38311b61b2 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/StringOrSymbol.h @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <AK/FlyString.h> +#include <LibJS/Runtime/PrimitiveString.h> +#include <LibJS/Runtime/Symbol.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +class StringOrSymbol { +public: + static StringOrSymbol from_value(GlobalObject& global_object, Value value) + { + if (value.is_empty()) + return {}; + if (value.is_symbol()) + return &value.as_symbol(); + auto string = value.to_string(global_object); + if (string.is_null()) + return {}; + return string; + } + + StringOrSymbol() = default; + + StringOrSymbol(const char* chars) + : m_ptr(StringImpl::create(chars).leak_ref()) + { + } + + StringOrSymbol(const String& string) + : m_ptr(string.impl()) + { + ASSERT(!string.is_null()); + as_string_impl().ref(); + } + + StringOrSymbol(const FlyString& string) + : m_ptr(string.impl()) + { + ASSERT(!string.is_null()); + as_string_impl().ref(); + } + + ~StringOrSymbol() + { + if (is_string()) + as_string_impl().unref(); + } + + StringOrSymbol(const Symbol* symbol) + : m_ptr(symbol) + { + set_symbol_flag(); + } + + StringOrSymbol(const StringOrSymbol& other) + { + m_ptr = other.m_ptr; + if (is_string()) + as_string_impl().ref(); + } + + StringOrSymbol(StringOrSymbol&& other) + { + m_ptr = exchange(other.m_ptr, nullptr); + } + + ALWAYS_INLINE bool is_valid() const { return m_ptr != nullptr; } + ALWAYS_INLINE bool is_symbol() const { return is_valid() && (bits() & 1ul); } + ALWAYS_INLINE bool is_string() const { return is_valid() && !(bits() & 1ul); } + + ALWAYS_INLINE String as_string() const + { + ASSERT(is_string()); + return as_string_impl(); + } + + ALWAYS_INLINE const Symbol* as_symbol() const + { + ASSERT(is_symbol()); + return reinterpret_cast<const Symbol*>(bits() & ~1ul); + } + + String to_display_string() const + { + if (is_string()) + return as_string(); + if (is_symbol()) + return as_symbol()->to_string(); + ASSERT_NOT_REACHED(); + } + + Value to_value(VM& vm) const + { + if (is_string()) + return js_string(vm, as_string()); + if (is_symbol()) + return const_cast<Symbol*>(as_symbol()); + return {}; + } + + void visit_edges(Cell::Visitor& visitor) + { + if (is_symbol()) + visitor.visit(const_cast<Symbol*>(as_symbol())); + } + + ALWAYS_INLINE bool operator==(const StringOrSymbol& other) const + { + if (is_string()) + return other.is_string() && as_string_impl() == other.as_string_impl(); + if (is_symbol()) + return other.is_symbol() && as_symbol() == other.as_symbol(); + return true; + } + + StringOrSymbol& operator=(const StringOrSymbol& other) + { + if (this == &other) + return *this; + m_ptr = other.m_ptr; + if (is_string()) + as_string_impl().ref(); + return *this; + } + + StringOrSymbol& operator=(StringOrSymbol&& other) + { + if (this != &other) + m_ptr = exchange(other.m_ptr, nullptr); + return *this; + } + + unsigned hash() const + { + if (is_string()) + return as_string_impl().hash(); + return ptr_hash(as_symbol()); + } + +private: + ALWAYS_INLINE u64 bits() const + { + return reinterpret_cast<uintptr_t>(m_ptr); + } + + ALWAYS_INLINE void set_symbol_flag() + { + m_ptr = reinterpret_cast<const void*>(bits() | 1ul); + } + + ALWAYS_INLINE const StringImpl& as_string_impl() const + { + ASSERT(is_string()); + return *reinterpret_cast<const StringImpl*>(m_ptr); + } + + const void* m_ptr { nullptr }; +}; + +} + +template<> +struct AK::Traits<JS::StringOrSymbol> : public GenericTraits<JS::StringOrSymbol> { + static unsigned hash(const JS::StringOrSymbol& key) + { + return key.hash(); + } +}; diff --git a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp new file mode 100644 index 0000000000..d0f4d495ff --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp @@ -0,0 +1,629 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <AK/Function.h> +#include <AK/StringBuilder.h> +#include <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/PrimitiveString.h> +#include <LibJS/Runtime/StringIterator.h> +#include <LibJS/Runtime/StringObject.h> +#include <LibJS/Runtime/StringPrototype.h> +#include <LibJS/Runtime/Value.h> +#include <string.h> + +namespace JS { + +static StringObject* typed_this(VM& vm, GlobalObject& global_object) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return nullptr; + if (!is<StringObject>(this_object)) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "String"); + return nullptr; + } + return static_cast<StringObject*>(this_object); +} + +static String ak_string_from(VM& vm, GlobalObject& global_object) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + return Value(this_object).to_string(global_object); +} + +static Optional<size_t> split_match(const String& haystack, size_t start, const String& needle) +{ + auto r = needle.length(); + auto s = haystack.length(); + if (start + r > s) + return {}; + if (!haystack.substring_view(start).starts_with(needle)) + return {}; + return start + r; +} + +StringPrototype::StringPrototype(GlobalObject& global_object) + : StringObject(*js_string(global_object.heap(), String::empty()), *global_object.object_prototype()) +{ +} + +void StringPrototype::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + StringObject::initialize(global_object); + u8 attr = Attribute::Writable | Attribute::Configurable; + + define_native_property(vm.names.length, length_getter, {}, 0); + define_native_function(vm.names.charAt, char_at, 1, attr); + define_native_function(vm.names.charCodeAt, char_code_at, 1, attr); + define_native_function(vm.names.repeat, repeat, 1, attr); + define_native_function(vm.names.startsWith, starts_with, 1, attr); + define_native_function(vm.names.endsWith, ends_with, 1, attr); + define_native_function(vm.names.indexOf, index_of, 1, attr); + define_native_function(vm.names.toLowerCase, to_lowercase, 0, attr); + define_native_function(vm.names.toUpperCase, to_uppercase, 0, attr); + define_native_function(vm.names.toString, to_string, 0, attr); + define_native_function(vm.names.padStart, pad_start, 1, attr); + define_native_function(vm.names.padEnd, pad_end, 1, attr); + define_native_function(vm.names.trim, trim, 0, attr); + define_native_function(vm.names.trimStart, trim_start, 0, attr); + define_native_function(vm.names.trimEnd, trim_end, 0, attr); + define_native_function(vm.names.concat, concat, 1, attr); + define_native_function(vm.names.substr, substr, 2, attr); + define_native_function(vm.names.substring, substring, 2, attr); + define_native_function(vm.names.includes, includes, 1, attr); + define_native_function(vm.names.slice, slice, 2, attr); + define_native_function(vm.names.split, split, 2, attr); + define_native_function(vm.names.lastIndexOf, last_index_of, 1, attr); + define_native_function(vm.well_known_symbol_iterator(), symbol_iterator, 0, attr); +} + +StringPrototype::~StringPrototype() +{ +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::char_at) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + i32 index = 0; + if (vm.argument_count()) { + index = vm.argument(0).to_i32(global_object); + if (vm.exception()) + return {}; + } + if (index < 0 || index >= static_cast<i32>(string.length())) + return js_string(vm, String::empty()); + // FIXME: This should return a character corresponding to the i'th UTF-16 code point. + return js_string(vm, string.substring(index, 1)); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::char_code_at) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + i32 index = 0; + if (vm.argument_count()) { + index = vm.argument(0).to_i32(global_object); + if (vm.exception()) + return {}; + } + if (index < 0 || index >= static_cast<i32>(string.length())) + return js_nan(); + // FIXME: This should return the i'th UTF-16 code point. + return Value((i32)string[index]); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::repeat) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + if (!vm.argument_count()) + return js_string(vm, String::empty()); + auto count = vm.argument(0).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + if (count < 0) { + vm.throw_exception<RangeError>(global_object, ErrorType::StringRepeatCountMustBe, "positive"); + return {}; + } + if (Value(count).is_infinity()) { + vm.throw_exception<RangeError>(global_object, ErrorType::StringRepeatCountMustBe, "finite"); + return {}; + } + StringBuilder builder; + for (size_t i = 0; i < count; ++i) + builder.append(string); + return js_string(vm, builder.to_string()); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::starts_with) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + if (!vm.argument_count()) + return Value(false); + auto search_string = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + auto string_length = string.length(); + auto search_string_length = search_string.length(); + size_t start = 0; + if (!vm.argument(1).is_undefined()) { + auto position = vm.argument(1).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + start = clamp(position, static_cast<double>(0), static_cast<double>(string_length)); + } + if (start + search_string_length > string_length) + return Value(false); + if (search_string_length == 0) + return Value(true); + return Value(string.substring(start, search_string_length) == search_string); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::ends_with) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + + auto search_string_value = vm.argument(0); + + bool search_is_regexp = search_string_value.is_regexp(global_object); + if (vm.exception()) + return {}; + if (search_is_regexp) { + vm.throw_exception<TypeError>(global_object, ErrorType::IsNotA, "searchString", "string, but a regular expression"); + return {}; + } + + auto search_string = search_string_value.to_string(global_object); + if (vm.exception()) + return {}; + + auto string_length = string.length(); + auto search_string_length = search_string.length(); + + size_t pos = string_length; + + auto end_position_value = vm.argument(1); + if (!end_position_value.is_undefined()) { + double pos_as_double = end_position_value.to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + pos = clamp(pos_as_double, static_cast<double>(0), static_cast<double>(string_length)); + } + + if (search_string_length == 0) + return Value(true); + if (pos < search_string_length) + return Value(false); + + auto start = pos - search_string_length; + return Value(string.substring(start, search_string_length) == search_string); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::index_of) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + auto needle = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + return Value((i32)string.index_of(needle).value_or(-1)); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_lowercase) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + return js_string(vm, string.to_lowercase()); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_uppercase) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + return js_string(vm, string.to_uppercase()); +} + +JS_DEFINE_NATIVE_GETTER(StringPrototype::length_getter) +{ + auto* string_object = typed_this(vm, global_object); + if (!string_object) + return {}; + return Value((i32)string_object->primitive_string().string().length()); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_string) +{ + auto* string_object = typed_this(vm, global_object); + if (!string_object) + return {}; + return js_string(vm, string_object->primitive_string().string()); +} + +enum class PadPlacement { + Start, + End, +}; + +static Value pad_string(GlobalObject& global_object, const String& string, PadPlacement placement) +{ + auto& vm = global_object.vm(); + auto max_length = vm.argument(0).to_length(global_object); + if (vm.exception()) + return {}; + if (max_length <= string.length()) + return js_string(vm, string); + + String fill_string = " "; + if (!vm.argument(1).is_undefined()) { + fill_string = vm.argument(1).to_string(global_object); + if (vm.exception()) + return {}; + if (fill_string.is_empty()) + return js_string(vm, string); + } + + auto fill_length = max_length - string.length(); + + StringBuilder filler_builder; + while (filler_builder.length() < fill_length) + filler_builder.append(fill_string); + auto filler = filler_builder.build().substring(0, fill_length); + + auto formatted = placement == PadPlacement::Start + ? String::formatted("{}{}", filler, string) + : String::formatted("{}{}", string, filler); + return js_string(vm, formatted); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::pad_start) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + return pad_string(global_object, string, PadPlacement::Start); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::pad_end) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + return pad_string(global_object, string, PadPlacement::End); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::trim) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + return js_string(vm, string.trim_whitespace(TrimMode::Both)); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::trim_start) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + return js_string(vm, string.trim_whitespace(TrimMode::Left)); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::trim_end) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + return js_string(vm, string.trim_whitespace(TrimMode::Right)); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::concat) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + StringBuilder builder; + builder.append(string); + for (size_t i = 0; i < vm.argument_count(); ++i) { + auto string_argument = vm.argument(i).to_string(global_object); + if (vm.exception()) + return {}; + builder.append(string_argument); + } + return js_string(vm, builder.to_string()); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::substring) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + if (vm.argument_count() == 0) + return js_string(vm, string); + + // FIXME: index_start and index_end should index a UTF-16 code_point view of the string. + auto string_length = string.length(); + auto start = vm.argument(0).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + auto end = (double)string_length; + if (!vm.argument(1).is_undefined()) { + end = vm.argument(1).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + } + size_t index_start = clamp(start, static_cast<double>(0), static_cast<double>(string_length)); + size_t index_end = clamp(end, static_cast<double>(0), static_cast<double>(string_length)); + + if (index_start == index_end) + return js_string(vm, String("")); + + if (index_start > index_end) { + if (vm.argument_count() == 1) + return js_string(vm, String("")); + auto temp_index_start = index_start; + index_start = index_end; + index_end = temp_index_start; + } + + auto part_length = index_end - index_start; + auto string_part = string.substring(index_start, part_length); + return js_string(vm, string_part); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::substr) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + if (vm.argument_count() == 0) + return js_string(vm, string); + + // FIXME: this should index a UTF-16 code_point view of the string. + auto string_length = (i32)string.length(); + + auto start_argument = vm.argument(0).to_i32(global_object); + if (vm.exception()) + return {}; + + auto start = start_argument < 0 ? (string_length - -start_argument) : start_argument; + + auto length = string_length - start; + if (vm.argument_count() >= 2) { + auto length_argument = vm.argument(1).to_i32(global_object); + if (vm.exception()) + return {}; + length = max(0, min(length_argument, length)); + if (vm.exception()) + return {}; + } + + if (length == 0) + return js_string(vm, String("")); + + auto string_part = string.substring(start, length); + return js_string(vm, string_part); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::includes) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + auto search_string = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + auto string_length = string.length(); + // FIXME: start should index a UTF-16 code_point view of the string. + size_t start = 0; + if (!vm.argument(1).is_undefined()) { + auto position = vm.argument(1).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + start = clamp(position, static_cast<double>(0), static_cast<double>(string_length)); + } + if (start == 0) + return Value(string.contains(search_string)); + auto substring_length = string_length - start; + auto substring_search = string.substring(start, substring_length); + return Value(substring_search.contains(search_string)); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::slice) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + + if (vm.argument_count() == 0) + return js_string(vm, string); + + // FIXME: index_start and index_end should index a UTF-16 code_point view of the string. + auto string_length = static_cast<i32>(string.length()); + auto index_start = vm.argument(0).to_i32(global_object); + if (vm.exception()) + return {}; + auto index_end = string_length; + + auto negative_min_index = -(string_length - 1); + if (index_start < negative_min_index) + index_start = 0; + else if (index_start < 0) + index_start = string_length + index_start; + + if (vm.argument_count() >= 2) { + index_end = vm.argument(1).to_i32(global_object); + if (vm.exception()) + return {}; + + if (index_end < negative_min_index) + return js_string(vm, String::empty()); + + if (index_end > string_length) + index_end = string_length; + else if (index_end < 0) + index_end = string_length + index_end; + } + + if (index_start >= index_end) + return js_string(vm, String::empty()); + + auto part_length = index_end - index_start; + auto string_part = string.substring(index_start, part_length); + return js_string(vm, string_part); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split) +{ + // FIXME Implement the @@split part + + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + + auto* result = Array::create(global_object); + size_t result_len = 0; + + auto limit = static_cast<u32>(MAX_U32); + if (!vm.argument(1).is_undefined()) { + limit = vm.argument(1).to_u32(global_object); + if (vm.exception()) + return {}; + } + + auto separator = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + + if (limit == 0) + return result; + + if (vm.argument(0).is_undefined()) { + result->define_property(0, js_string(vm, string)); + return result; + } + + auto len = string.length(); + auto separator_len = separator.length(); + if (len == 0) { + if (separator_len > 0) + result->define_property(0, js_string(vm, string)); + return result; + } + + size_t start = 0; + auto pos = start; + if (separator_len == 0) { + for (pos = 0; pos < len; pos++) + result->define_property(pos, js_string(vm, string.substring(pos, 1))); + return result; + } + + while (pos != len) { + auto e = split_match(string, pos, separator); + if (!e.has_value()) { + pos += 1; + continue; + } + + auto segment = string.substring_view(start, pos - start); + result->define_property(result_len, js_string(vm, segment)); + result_len++; + if (result_len == limit) + return result; + start = e.value(); + pos = start; + } + + auto rest = string.substring(start, len - start); + result->define_property(result_len, js_string(vm, rest)); + + return result; +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::last_index_of) +{ + auto string = ak_string_from(vm, global_object); + if (string.is_null()) + return {}; + auto search_string = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + auto position = vm.argument(1).to_number(global_object); + if (vm.exception()) + return {}; + if (search_string.length() > string.length()) + return Value(-1); + auto max_index = string.length() - search_string.length(); + auto from_index = max_index; + if (!position.is_nan()) { + // FIXME: from_index should index a UTF-16 code_point view of the string. + auto p = position.to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + from_index = clamp(p, static_cast<double>(0), static_cast<double>(max_index)); + } + + for (i32 i = from_index; i >= 0; --i) { + auto part_view = string.substring_view(i, search_string.length()); + if (part_view == search_string) + return Value(i); + } + + return Value(-1); +} + +JS_DEFINE_NATIVE_FUNCTION(StringPrototype::symbol_iterator) +{ + auto this_object = vm.this_value(global_object); + if (this_object.is_nullish()) { + vm.throw_exception<TypeError>(global_object, ErrorType::ToObjectNullOrUndef); + return {}; + } + + auto string = this_object.to_string(global_object); + if (vm.exception()) + return {}; + return StringIterator::create(global_object, string); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/StringPrototype.h b/Userland/Libraries/LibJS/Runtime/StringPrototype.h new file mode 100644 index 0000000000..5847ea2117 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/StringPrototype.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/StringObject.h> + +namespace JS { + +class StringPrototype final : public StringObject { + JS_OBJECT(StringPrototype, StringObject); + +public: + explicit StringPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~StringPrototype() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(char_at); + JS_DECLARE_NATIVE_FUNCTION(char_code_at); + JS_DECLARE_NATIVE_FUNCTION(repeat); + JS_DECLARE_NATIVE_FUNCTION(starts_with); + JS_DECLARE_NATIVE_FUNCTION(ends_with); + JS_DECLARE_NATIVE_FUNCTION(index_of); + JS_DECLARE_NATIVE_FUNCTION(to_lowercase); + JS_DECLARE_NATIVE_FUNCTION(to_uppercase); + JS_DECLARE_NATIVE_FUNCTION(to_string); + JS_DECLARE_NATIVE_FUNCTION(pad_start); + JS_DECLARE_NATIVE_FUNCTION(pad_end); + JS_DECLARE_NATIVE_FUNCTION(substring); + JS_DECLARE_NATIVE_FUNCTION(substr); + + JS_DECLARE_NATIVE_GETTER(length_getter); + + JS_DECLARE_NATIVE_FUNCTION(trim); + JS_DECLARE_NATIVE_FUNCTION(trim_start); + JS_DECLARE_NATIVE_FUNCTION(trim_end); + JS_DECLARE_NATIVE_FUNCTION(concat); + JS_DECLARE_NATIVE_FUNCTION(includes); + JS_DECLARE_NATIVE_FUNCTION(slice); + JS_DECLARE_NATIVE_FUNCTION(split); + JS_DECLARE_NATIVE_FUNCTION(last_index_of); + + JS_DECLARE_NATIVE_FUNCTION(symbol_iterator); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Symbol.cpp b/Userland/Libraries/LibJS/Runtime/Symbol.cpp new file mode 100644 index 0000000000..c25ebf2fce --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Symbol.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/Symbol.h> +#include <LibJS/Runtime/VM.h> + +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_without_global_object<Symbol>(move(description), is_global); +} + +Symbol* js_symbol(VM& vm, String description, bool is_global) +{ + return js_symbol(vm.heap(), move(description), is_global); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Symbol.h b/Userland/Libraries/LibJS/Runtime/Symbol.h new file mode 100644 index 0000000000..d01179add7 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Symbol.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <AK/String.h> +#include <LibJS/Runtime/Cell.h> + +namespace JS { + +class Symbol final : public Cell { + AK_MAKE_NONCOPYABLE(Symbol); + AK_MAKE_NONMOVABLE(Symbol); + +public: + Symbol(String, bool); + virtual ~Symbol(); + + const String& description() const { return m_description; } + bool is_global() const { return m_is_global; } + String to_string() const { return String::formatted("Symbol({})", description()); } + +private: + virtual const char* class_name() const override { return "Symbol"; } + + String m_description; + bool m_is_global; +}; + +Symbol* js_symbol(Heap&, String description, bool is_global); +Symbol* js_symbol(VM&, String description, bool is_global); + +} diff --git a/Userland/Libraries/LibJS/Runtime/SymbolConstructor.cpp b/Userland/Libraries/LibJS/Runtime/SymbolConstructor.cpp new file mode 100644 index 0000000000..0697ad98e6 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/SymbolConstructor.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/SymbolConstructor.h> +#include <LibJS/Runtime/SymbolObject.h> + +namespace JS { + +SymbolConstructor::SymbolConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.Symbol, *global_object.function_prototype()) +{ +} + +void SymbolConstructor::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + NativeFunction::initialize(global_object); + define_property(vm.names.prototype, global_object.symbol_prototype(), 0); + define_property(vm.names.length, Value(0), Attribute::Configurable); + + define_native_function(vm.names.for_, for_, 1, Attribute::Writable | Attribute::Configurable); + define_native_function(vm.names.keyFor, key_for, 1, Attribute::Writable | Attribute::Configurable); + +#define __JS_ENUMERATE(SymbolName, snake_name) \ + define_property(vm.names.SymbolName, vm.well_known_symbol_##snake_name(), 0); + JS_ENUMERATE_WELL_KNOWN_SYMBOLS +#undef __JS_ENUMERATE +} + +SymbolConstructor::~SymbolConstructor() +{ +} + +Value SymbolConstructor::call() +{ + if (!vm().argument_count()) + return js_symbol(heap(), "", false); + return js_symbol(heap(), vm().argument(0).to_string(global_object()), false); +} + +Value SymbolConstructor::construct(Function&) +{ + vm().throw_exception<TypeError>(global_object(), ErrorType::NotAConstructor, "Symbol"); + return {}; +} + +JS_DEFINE_NATIVE_FUNCTION(SymbolConstructor::for_) +{ + String description; + if (!vm.argument_count()) { + description = "undefined"; + } else { + description = vm.argument(0).to_string(global_object); + } + + return global_object.vm().get_global_symbol(description); +} + +JS_DEFINE_NATIVE_FUNCTION(SymbolConstructor::key_for) +{ + auto argument = vm.argument(0); + if (!argument.is_symbol()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotASymbol, argument.to_string_without_side_effects()); + return {}; + } + + auto& symbol = argument.as_symbol(); + if (symbol.is_global()) + return js_string(vm, symbol.description()); + + return js_undefined(); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/SymbolConstructor.h b/Userland/Libraries/LibJS/Runtime/SymbolConstructor.h new file mode 100644 index 0000000000..8afba45fb5 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/SymbolConstructor.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/NativeFunction.h> + +namespace JS { + +class SymbolConstructor final : public NativeFunction { + JS_OBJECT(SymbolConstructor, NativeFunction); + +public: + explicit SymbolConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~SymbolConstructor() override; + + virtual Value call() override; + virtual Value construct(Function& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } + + JS_DECLARE_NATIVE_FUNCTION(for_); + JS_DECLARE_NATIVE_FUNCTION(key_for); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/SymbolObject.cpp b/Userland/Libraries/LibJS/Runtime/SymbolObject.cpp new file mode 100644 index 0000000000..deb1170928 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/SymbolObject.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/Symbol.h> +#include <LibJS/Runtime/SymbolObject.h> +#include <LibJS/Runtime/SymbolPrototype.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +SymbolObject* SymbolObject::create(GlobalObject& global_object, Symbol& primitive_symbol) +{ + return global_object.heap().allocate<SymbolObject>(global_object, primitive_symbol, *global_object.symbol_prototype()); +} + +SymbolObject::SymbolObject(Symbol& symbol, Object& prototype) + : Object(prototype) + , m_symbol(symbol) +{ +} + +SymbolObject::~SymbolObject() +{ +} + +void SymbolObject::visit_edges(Cell::Visitor& visitor) +{ + Object::visit_edges(visitor); + visitor.visit(&m_symbol); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/SymbolObject.h b/Userland/Libraries/LibJS/Runtime/SymbolObject.h new file mode 100644 index 0000000000..0ff212ffb5 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/SymbolObject.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/Object.h> +#include <LibJS/Runtime/Symbol.h> + +namespace JS { + +class SymbolObject : public Object { + JS_OBJECT(SymbolObject, 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(); } + + virtual Value value_of() const override + { + return Value(&m_symbol); + } + +private: + virtual void visit_edges(Visitor&) override; + + Symbol& m_symbol; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/SymbolPrototype.cpp b/Userland/Libraries/LibJS/Runtime/SymbolPrototype.cpp new file mode 100644 index 0000000000..0a36ab4a20 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/SymbolPrototype.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <AK/Function.h> +#include <AK/StringBuilder.h> +#include <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/Object.h> +#include <LibJS/Runtime/PrimitiveString.h> +#include <LibJS/Runtime/SymbolObject.h> +#include <LibJS/Runtime/SymbolPrototype.h> +#include <LibJS/Runtime/Value.h> +#include <string.h> + +namespace JS { + +SymbolPrototype::SymbolPrototype(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void SymbolPrototype::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + define_native_property(vm.names.description, description_getter, {}, Attribute::Configurable); + define_native_function(vm.names.toString, to_string, 0, Attribute::Writable | Attribute::Configurable); + define_native_function(vm.names.valueOf, value_of, 0, Attribute::Writable | Attribute::Configurable); + + define_property(global_object.vm().well_known_symbol_to_string_tag(), js_string(global_object.heap(), "Symbol"), Attribute::Configurable); +} + +SymbolPrototype::~SymbolPrototype() +{ +} + +static SymbolObject* typed_this(VM& vm, GlobalObject& global_object) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return nullptr; + if (!is<SymbolObject>(this_object)) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Symbol"); + return nullptr; + } + return static_cast<SymbolObject*>(this_object); +} + +JS_DEFINE_NATIVE_GETTER(SymbolPrototype::description_getter) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return js_string(vm, this_object->description()); +} + +JS_DEFINE_NATIVE_FUNCTION(SymbolPrototype::to_string) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + auto string = this_object->primitive_symbol().to_string(); + return js_string(vm, move(string)); +} + +JS_DEFINE_NATIVE_FUNCTION(SymbolPrototype::value_of) +{ + auto* this_object = typed_this(vm, global_object); + if (!this_object) + return {}; + return this_object->value_of(); +} +} diff --git a/Userland/Libraries/LibJS/Runtime/SymbolPrototype.h b/Userland/Libraries/LibJS/Runtime/SymbolPrototype.h new file mode 100644 index 0000000000..589b46e7a7 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/SymbolPrototype.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class SymbolPrototype final : public Object { + JS_OBJECT(SymbolPrototype, Object); + +public: + explicit SymbolPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~SymbolPrototype() override; + +private: + JS_DECLARE_NATIVE_GETTER(description_getter); + + JS_DECLARE_NATIVE_FUNCTION(to_string); + JS_DECLARE_NATIVE_FUNCTION(value_of); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/TypedArray.cpp b/Userland/Libraries/LibJS/Runtime/TypedArray.cpp new file mode 100644 index 0000000000..d5bc286e41 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/TypedArray.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/ArrayBuffer.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/TypedArray.h> +#include <LibJS/Runtime/TypedArrayConstructor.h> + +namespace JS { + +static void initialize_typed_array_from_array_buffer(GlobalObject& global_object, TypedArrayBase& typed_array, ArrayBuffer& array_buffer, Value byte_offset, Value length) +{ + // 22.2.5.1.3 InitializeTypedArrayFromArrayBuffer, https://tc39.es/ecma262/#sec-initializetypedarrayfromarraybuffer + + auto& vm = global_object.vm(); + auto element_size = typed_array.element_size(); + auto offset = byte_offset.to_index(global_object); + if (vm.exception()) + return; + if (offset % element_size != 0) { + vm.throw_exception<RangeError>(global_object, ErrorType::TypedArrayInvalidByteOffset, typed_array.class_name(), element_size, offset); + return; + } + size_t new_length { 0 }; + if (!length.is_undefined()) { + new_length = length.to_index(global_object); + if (vm.exception()) + return; + } + // FIXME: 8. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + auto buffer_byte_length = array_buffer.byte_length(); + size_t new_byte_length; + if (length.is_undefined()) { + if (buffer_byte_length % element_size != 0) { + vm.throw_exception<RangeError>(global_object, ErrorType::TypedArrayInvalidBufferLength, typed_array.class_name(), element_size, buffer_byte_length); + return; + } + if (offset > buffer_byte_length) { + vm.throw_exception<RangeError>(global_object, ErrorType::TypedArrayOutOfRangeByteOffset, offset, buffer_byte_length); + return; + } + new_byte_length = buffer_byte_length - offset; + } else { + new_byte_length = new_length * element_size; + if (offset + new_byte_length > buffer_byte_length) { + vm.throw_exception<RangeError>(global_object, ErrorType::TypedArrayOutOfRangeByteOffsetOrLength, offset, offset + new_byte_length, buffer_byte_length); + return; + } + } + typed_array.set_viewed_array_buffer(&array_buffer); + typed_array.set_byte_length(new_byte_length); + typed_array.set_byte_offset(offset); + typed_array.set_array_length(new_byte_length / element_size); +} + +void TypedArrayBase::visit_edges(Visitor& visitor) +{ + Object::visit_edges(visitor); + visitor.visit(m_viewed_array_buffer); +} + +#define JS_DEFINE_TYPED_ARRAY(ClassName, snake_name, PrototypeName, ConstructorName, Type) \ + ClassName* ClassName::create(GlobalObject& global_object, u32 length) \ + { \ + return global_object.heap().allocate<ClassName>(global_object, length, *global_object.snake_name##_prototype()); \ + } \ + \ + ClassName::ClassName(u32 length, Object& prototype) \ + : TypedArray(length, prototype) \ + { \ + } \ + ClassName::~ClassName() { } \ + \ + PrototypeName::PrototypeName(GlobalObject& global_object) \ + : Object(*global_object.typed_array_prototype()) \ + { \ + } \ + PrototypeName::~PrototypeName() { } \ + \ + ConstructorName::ConstructorName(GlobalObject& global_object) \ + : TypedArrayConstructor(vm().names.ClassName, *global_object.typed_array_constructor()) \ + { \ + } \ + ConstructorName::~ConstructorName() { } \ + void ConstructorName::initialize(GlobalObject& global_object) \ + { \ + auto& vm = this->vm(); \ + NativeFunction::initialize(global_object); \ + define_property(vm.names.prototype, global_object.snake_name##_prototype(), 0); \ + define_property(vm.names.length, Value(1), Attribute::Configurable); \ + define_property(vm.names.BYTES_PER_ELEMENT, Value((i32)sizeof(Type)), 0); \ + } \ + Value ConstructorName::call() \ + { \ + auto& vm = this->vm(); \ + vm.throw_exception<TypeError>(global_object(), ErrorType::ConstructorWithoutNew, vm.names.ClassName); \ + return {}; \ + } \ + Value ConstructorName::construct(Function&) \ + { \ + auto& vm = this->vm(); \ + if (vm.argument_count() == 0) \ + return ClassName::create(global_object(), 0); \ + \ + auto first_argument = vm.argument(0); \ + if (first_argument.is_object()) { \ + auto* typed_array = ClassName::create(global_object(), 0); \ + if (first_argument.as_object().is_typed_array()) { \ + /* FIXME: Initialize from TypedArray */ \ + TODO(); \ + } else if (is<ArrayBuffer>(first_argument.as_object())) { \ + auto& array_buffer = static_cast<ArrayBuffer&>(first_argument.as_object()); \ + initialize_typed_array_from_array_buffer(global_object(), *typed_array, array_buffer, vm.argument(1), vm.argument(2)); \ + if (vm.exception()) \ + return {}; \ + } else { \ + /* FIXME: Initialize from Iterator or Array-like object */ \ + TODO(); \ + } \ + return typed_array; \ + } \ + \ + auto array_length = first_argument.to_index(global_object()); \ + if (vm.exception()) { \ + /* Re-throw more specific RangeError */ \ + vm.clear_exception(); \ + vm.throw_exception<RangeError>(global_object(), ErrorType::InvalidLength, "typed array"); \ + return {}; \ + } \ + return ClassName::create(global_object(), array_length); \ + } + +#undef __JS_ENUMERATE +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + JS_DEFINE_TYPED_ARRAY(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType); +JS_ENUMERATE_TYPED_ARRAYS +#undef __JS_ENUMERATE + +} diff --git a/Userland/Libraries/LibJS/Runtime/TypedArray.h b/Userland/Libraries/LibJS/Runtime/TypedArray.h new file mode 100644 index 0000000000..1a10a1a93f --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/TypedArray.h @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/ArrayBuffer.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/TypedArrayConstructor.h> +#include <LibJS/Runtime/VM.h> + +namespace JS { + +class TypedArrayBase : public Object { + JS_OBJECT(TypedArrayBase, Object); + +public: + u32 array_length() const { return m_array_length; } + u32 byte_length() const { return m_byte_length; } + u32 byte_offset() const { return m_byte_offset; } + ArrayBuffer* viewed_array_buffer() const { return m_viewed_array_buffer; } + + void set_array_length(u32 length) { m_array_length = length; } + void set_byte_length(u32 length) { m_byte_length = length; } + void set_byte_offset(u32 offset) { m_byte_offset = offset; } + void set_viewed_array_buffer(ArrayBuffer* array_buffer) { m_viewed_array_buffer = array_buffer; } + + virtual size_t element_size() const = 0; + +protected: + TypedArrayBase(Object& prototype) + : Object(prototype) + { + } + + u32 m_array_length { 0 }; + u32 m_byte_length { 0 }; + u32 m_byte_offset { 0 }; + ArrayBuffer* m_viewed_array_buffer { nullptr }; + +private: + virtual void visit_edges(Visitor&) override; +}; + +template<typename T> +class TypedArray : public TypedArrayBase { + JS_OBJECT(TypedArray, TypedArrayBase); + +public: + virtual bool put_by_index(u32 property_index, Value value) override + { + property_index += m_byte_offset / sizeof(T); + if (property_index >= m_array_length) + return Base::put_by_index(property_index, value); + + if constexpr (sizeof(T) < 4) { + auto number = value.to_i32(global_object()); + if (vm().exception()) + return {}; + data()[property_index] = number; + } else if constexpr (sizeof(T) == 4 || sizeof(T) == 8) { + auto number = value.to_double(global_object()); + if (vm().exception()) + return {}; + data()[property_index] = number; + } else { + static_assert(DependentFalse<T>, "TypedArray::put_by_index with unhandled type size"); + } + return true; + } + + virtual Value get_by_index(u32 property_index) const override + { + property_index += m_byte_offset / sizeof(T); + if (property_index >= m_array_length) + return Base::get_by_index(property_index); + + if constexpr (sizeof(T) < 4) { + return Value((i32)data()[property_index]); + } else if constexpr (sizeof(T) == 4 || sizeof(T) == 8) { + auto value = data()[property_index]; + if constexpr (IsFloatingPoint<T>::value) { + return Value((double)value); + } else if constexpr (NumericLimits<T>::is_signed()) { + if (value > NumericLimits<i32>::max() || value < NumericLimits<i32>::min()) + return Value((double)value); + } else { + if (value > NumericLimits<i32>::max()) + return Value((double)value); + } + return Value((i32)value); + } else { + static_assert(DependentFalse<T>, "TypedArray::get_by_index with unhandled type size"); + } + } + + T* data() const { return reinterpret_cast<T*>(m_viewed_array_buffer->buffer().data()); } + + virtual size_t element_size() const override { return sizeof(T); }; + +protected: + TypedArray(ArrayBuffer& array_buffer, u32 array_length, Object& prototype) + : TypedArrayBase(prototype) + { + m_viewed_array_buffer = &array_buffer; + m_array_length = array_length; + m_byte_length = m_viewed_array_buffer->byte_length(); + } + + TypedArray(u32 array_length, Object& prototype) + : TypedArrayBase(prototype) + { + m_viewed_array_buffer = ArrayBuffer::create(global_object(), array_length * sizeof(T)); + m_array_length = array_length; + m_byte_length = m_viewed_array_buffer->byte_length(); + } + +private: + virtual bool is_typed_array() const final { return true; } +}; + +#define JS_DECLARE_TYPED_ARRAY(ClassName, snake_name, PrototypeName, ConstructorName, Type) \ + class ClassName : public TypedArray<Type> { \ + JS_OBJECT(ClassName, TypedArray); \ + \ + public: \ + virtual ~ClassName(); \ + static ClassName* create(GlobalObject&, u32 length); \ + ClassName(u32 length, Object& prototype); \ + }; \ + class PrototypeName final : public Object { \ + JS_OBJECT(PrototypeName, Object); \ + \ + public: \ + PrototypeName(GlobalObject&); \ + virtual ~PrototypeName() override; \ + }; \ + class ConstructorName final : public TypedArrayConstructor { \ + JS_OBJECT(ConstructorName, TypedArrayConstructor); \ + \ + public: \ + explicit ConstructorName(GlobalObject&); \ + virtual void initialize(GlobalObject&) override; \ + virtual ~ConstructorName() override; \ + \ + virtual Value call() override; \ + virtual Value construct(Function& new_target) override; \ + \ + private: \ + virtual bool has_constructor() const override { return true; } \ + }; + +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, Type) \ + JS_DECLARE_TYPED_ARRAY(ClassName, snake_name, PrototypeName, ConstructorName, Type); +JS_ENUMERATE_TYPED_ARRAYS +#undef __JS_ENUMERATE + +} diff --git a/Userland/Libraries/LibJS/Runtime/TypedArrayConstructor.cpp b/Userland/Libraries/LibJS/Runtime/TypedArrayConstructor.cpp new file mode 100644 index 0000000000..d7d57cb50b --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/TypedArrayConstructor.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/TypedArrayConstructor.h> + +namespace JS { + +TypedArrayConstructor::TypedArrayConstructor(const FlyString& name, Object& prototype) + : NativeFunction(name, prototype) +{ +} + +TypedArrayConstructor::TypedArrayConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.TypedArray, *global_object.function_prototype()) +{ +} + +void TypedArrayConstructor::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + NativeFunction::initialize(global_object); + define_property(vm.names.prototype, global_object.typed_array_prototype(), 0); + define_property(vm.names.length, Value(0), Attribute::Configurable); +} + +TypedArrayConstructor::~TypedArrayConstructor() +{ +} + +Value TypedArrayConstructor::call() +{ + return construct(*this); +} + +Value TypedArrayConstructor::construct(Function&) +{ + vm().throw_exception<TypeError>(global_object(), ErrorType::ClassIsAbstract, "TypedArray"); + return {}; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/TypedArrayConstructor.h b/Userland/Libraries/LibJS/Runtime/TypedArrayConstructor.h new file mode 100644 index 0000000000..b99a957ae4 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/TypedArrayConstructor.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/NativeFunction.h> + +namespace JS { + +class TypedArrayConstructor : public NativeFunction { + JS_OBJECT(TypedArrayConstructor, NativeFunction); + +public: + TypedArrayConstructor(const FlyString& name, Object& prototype); + explicit TypedArrayConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~TypedArrayConstructor() override; + + virtual Value call() override; + virtual Value construct(Function& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp b/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp new file mode 100644 index 0000000000..da0c43faf1 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/TypedArray.h> +#include <LibJS/Runtime/TypedArrayPrototype.h> + +namespace JS { + +TypedArrayPrototype::TypedArrayPrototype(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void TypedArrayPrototype::initialize(GlobalObject& object) +{ + auto& vm = this->vm(); + Object::initialize(object); + // FIXME: This should be an accessor property + define_native_property(vm.names.length, length_getter, {}, Attribute::Configurable); +} + +TypedArrayPrototype::~TypedArrayPrototype() +{ +} + +static TypedArrayBase* typed_array_from(VM& vm, GlobalObject& global_object) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return nullptr; + if (!this_object->is_typed_array()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "TypedArray"); + return nullptr; + } + return static_cast<TypedArrayBase*>(this_object); +} + +JS_DEFINE_NATIVE_GETTER(TypedArrayPrototype::length_getter) +{ + auto typed_array = typed_array_from(vm, global_object); + if (!typed_array) + return {}; + return Value(typed_array->array_length()); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.h b/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.h new file mode 100644 index 0000000000..8c39f31c31 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class TypedArrayPrototype final : public Object { + JS_OBJECT(TypedArrayPrototype, Object); + +public: + explicit TypedArrayPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~TypedArrayPrototype() override; + +private: + JS_DECLARE_NATIVE_GETTER(length_getter); +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Uint8ClampedArray.cpp b/Userland/Libraries/LibJS/Runtime/Uint8ClampedArray.cpp new file mode 100644 index 0000000000..c558f41119 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Uint8ClampedArray.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/Function.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/Uint8ClampedArray.h> +#include <string.h> + +namespace JS { + +Uint8ClampedArray* Uint8ClampedArray::create(GlobalObject& global_object, u32 length) +{ + return global_object.heap().allocate<Uint8ClampedArray>(global_object, length, *global_object.array_prototype()); +} + +Uint8ClampedArray::Uint8ClampedArray(u32 length, Object& prototype) + : Object(prototype) + , m_length(length) +{ + auto& vm = this->vm(); + define_native_property(vm.names.length, length_getter, {}); + m_data = (u8*)calloc(m_length, 1); +} + +Uint8ClampedArray::~Uint8ClampedArray() +{ + ASSERT(m_data); + free(m_data); + m_data = nullptr; +} + +JS_DEFINE_NATIVE_GETTER(Uint8ClampedArray::length_getter) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + if (StringView(this_object->class_name()) != "Uint8ClampedArray") { + vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Uint8ClampedArray"); + return {}; + } + return Value(static_cast<const Uint8ClampedArray*>(this_object)->length()); +} + +bool Uint8ClampedArray::put_by_index(u32 property_index, Value value) +{ + if (property_index >= m_length) + return Base::put_by_index(property_index, value); + auto number = value.to_i32(global_object()); + if (vm().exception()) + return {}; + m_data[property_index] = clamp(number, 0, 255); + return true; +} + +Value Uint8ClampedArray::get_by_index(u32 property_index) const +{ + if (property_index >= m_length) + return Base::get_by_index(property_index); + return Value((i32)m_data[property_index]); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Uint8ClampedArray.h b/Userland/Libraries/LibJS/Runtime/Uint8ClampedArray.h new file mode 100644 index 0000000000..056d490eab --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Uint8ClampedArray.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/Object.h> + +namespace JS { + +class Uint8ClampedArray final : public Object { + JS_OBJECT(Uint8ClampedArray, Object); + +public: + static Uint8ClampedArray* create(GlobalObject&, u32 length); + + Uint8ClampedArray(u32 length, Object& prototype); + virtual ~Uint8ClampedArray() override; + + i32 length() const { return m_length; } + + virtual bool put_by_index(u32 property_index, Value value) override; + virtual Value get_by_index(u32 property_index) const override; + + u8* data() { return m_data; } + const u8* data() const { return m_data; } + +private: + JS_DECLARE_NATIVE_GETTER(length_getter); + + u8* m_data { nullptr }; + u32 m_length { 0 }; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/VM.cpp b/Userland/Libraries/LibJS/Runtime/VM.cpp new file mode 100644 index 0000000000..73842974c7 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/VM.cpp @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/ScopeGuard.h> +#include <AK/StringBuilder.h> +#include <LibJS/Interpreter.h> +#include <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/Reference.h> +#include <LibJS/Runtime/ScriptFunction.h> +#include <LibJS/Runtime/Symbol.h> +#include <LibJS/Runtime/VM.h> + +namespace JS { + +NonnullRefPtr<VM> VM::create() +{ + return adopt(*new VM); +} + +VM::VM() + : m_heap(*this) +{ + m_empty_string = m_heap.allocate_without_global_object<PrimitiveString>(String::empty()); + for (size_t i = 0; i < 128; ++i) { + m_single_ascii_character_strings[i] = m_heap.allocate_without_global_object<PrimitiveString>(String::formatted("{:c}", i)); + } + + m_scope_object_shape = m_heap.allocate_without_global_object<Shape>(Shape::ShapeWithoutGlobalObjectTag::Tag); + +#define __JS_ENUMERATE(SymbolName, snake_name) \ + m_well_known_symbol_##snake_name = js_symbol(*this, "Symbol." #SymbolName, false); + JS_ENUMERATE_WELL_KNOWN_SYMBOLS +#undef __JS_ENUMERATE +} + +VM::~VM() +{ +} + +Interpreter& VM::interpreter() +{ + ASSERT(!m_interpreters.is_empty()); + return *m_interpreters.last(); +} + +Interpreter* VM::interpreter_if_exists() +{ + if (m_interpreters.is_empty()) + return nullptr; + return m_interpreters.last(); +} + +void VM::push_interpreter(Interpreter& interpreter) +{ + m_interpreters.append(&interpreter); +} + +void VM::pop_interpreter(Interpreter& interpreter) +{ + ASSERT(!m_interpreters.is_empty()); + auto* popped_interpreter = m_interpreters.take_last(); + ASSERT(popped_interpreter == &interpreter); +} + +VM::InterpreterExecutionScope::InterpreterExecutionScope(Interpreter& interpreter) + : m_interpreter(interpreter) +{ + m_interpreter.vm().push_interpreter(m_interpreter); +} + +VM::InterpreterExecutionScope::~InterpreterExecutionScope() +{ + m_interpreter.vm().pop_interpreter(m_interpreter); +} + +void VM::gather_roots(HashTable<Cell*>& roots) +{ + roots.set(m_empty_string); + for (auto* string : m_single_ascii_character_strings) + roots.set(string); + + roots.set(m_scope_object_shape); + roots.set(m_exception); + + if (m_last_value.is_cell()) + roots.set(m_last_value.as_cell()); + + for (auto& call_frame : m_call_stack) { + if (call_frame->this_value.is_cell()) + roots.set(call_frame->this_value.as_cell()); + roots.set(call_frame->arguments_object); + for (auto& argument : call_frame->arguments) { + if (argument.is_cell()) + roots.set(argument.as_cell()); + } + roots.set(call_frame->scope); + } + +#define __JS_ENUMERATE(SymbolName, snake_name) \ + roots.set(well_known_symbol_##snake_name()); + JS_ENUMERATE_WELL_KNOWN_SYMBOLS +#undef __JS_ENUMERATE + + for (auto& symbol : m_global_symbol_map) + roots.set(symbol.value); +} + +Symbol* VM::get_global_symbol(const String& description) +{ + auto result = m_global_symbol_map.get(description); + if (result.has_value()) + return result.value(); + + auto new_global_symbol = js_symbol(*this, description, true); + m_global_symbol_map.set(description, new_global_symbol); + return new_global_symbol; +} + +void VM::set_variable(const FlyString& name, Value value, GlobalObject& global_object, bool first_assignment) +{ + if (m_call_stack.size()) { + for (auto* scope = current_scope(); scope; scope = scope->parent()) { + auto possible_match = scope->get_from_scope(name); + if (possible_match.has_value()) { + if (!first_assignment && possible_match.value().declaration_kind == DeclarationKind::Const) { + throw_exception<TypeError>(global_object, ErrorType::InvalidAssignToConst); + return; + } + + scope->put_to_scope(name, { value, possible_match.value().declaration_kind }); + return; + } + } + } + + global_object.put(move(name), move(value)); +} + +Value VM::get_variable(const FlyString& name, GlobalObject& global_object) +{ + if (m_call_stack.size()) { + if (name == names.arguments) { + // HACK: Special handling for the name "arguments": + // If the name "arguments" is defined in the current scope, for example via + // a function parameter, or by a local var declaration, we use that. + // Otherwise, we return a lazily constructed Array with all the argument values. + // FIXME: Do something much more spec-compliant. + auto possible_match = current_scope()->get_from_scope(name); + if (possible_match.has_value()) + return possible_match.value().value; + if (!call_frame().arguments_object) { + call_frame().arguments_object = Array::create(global_object); + for (auto argument : call_frame().arguments) { + call_frame().arguments_object->indexed_properties().append(argument); + } + } + return call_frame().arguments_object; + } + + for (auto* scope = current_scope(); scope; scope = scope->parent()) { + auto possible_match = scope->get_from_scope(name); + if (possible_match.has_value()) + return possible_match.value().value; + } + } + auto value = global_object.get(name); + if (m_underscore_is_last_value && name == "_" && value.is_empty()) + return m_last_value; + return value; +} + +Reference VM::get_reference(const FlyString& name) +{ + if (m_call_stack.size()) { + for (auto* scope = current_scope(); scope; scope = scope->parent()) { + if (is<GlobalObject>(scope)) + break; + auto possible_match = scope->get_from_scope(name); + if (possible_match.has_value()) + return { Reference::LocalVariable, name }; + } + } + return { Reference::GlobalVariable, name }; +} + +Value VM::construct(Function& function, Function& new_target, Optional<MarkedValueList> arguments, GlobalObject& global_object) +{ + CallFrame call_frame; + call_frame.is_strict_mode = function.is_strict_mode(); + + push_call_frame(call_frame, function.global_object()); + if (exception()) + return {}; + ArmedScopeGuard call_frame_popper = [&] { + pop_call_frame(); + }; + + call_frame.function_name = function.name(); + call_frame.arguments = function.bound_arguments(); + if (arguments.has_value()) + call_frame.arguments.append(arguments.value().values()); + auto* environment = function.create_environment(); + call_frame.scope = environment; + environment->set_new_target(&new_target); + + Object* new_object = nullptr; + if (function.constructor_kind() == Function::ConstructorKind::Base) { + new_object = Object::create_empty(global_object); + environment->bind_this_value(global_object, new_object); + if (exception()) + return {}; + auto prototype = new_target.get(names.prototype); + if (exception()) + return {}; + if (prototype.is_object()) { + new_object->set_prototype(&prototype.as_object()); + if (exception()) + return {}; + } + } + + // If we are a Derived constructor, |this| has not been constructed before super is called. + Value this_value = function.constructor_kind() == Function::ConstructorKind::Base ? new_object : Value {}; + call_frame.this_value = this_value; + auto result = function.construct(new_target); + + this_value = call_frame.scope->get_this_binding(global_object); + pop_call_frame(); + call_frame_popper.disarm(); + + // If we are constructing an instance of a derived class, + // set the prototype on objects created by constructors that return an object (i.e. NativeFunction subclasses). + if (function.constructor_kind() == Function::ConstructorKind::Base && new_target.constructor_kind() == Function::ConstructorKind::Derived && result.is_object()) { + ASSERT(is<LexicalEnvironment>(current_scope())); + static_cast<LexicalEnvironment*>(current_scope())->replace_this_binding(result); + auto prototype = new_target.get(names.prototype); + if (exception()) + return {}; + if (prototype.is_object()) { + result.as_object().set_prototype(&prototype.as_object()); + if (exception()) + return {}; + } + return result; + } + + if (exception()) + return {}; + + if (result.is_object()) + return result; + + return this_value; +} + +void VM::throw_exception(Exception* exception) +{ + if (should_log_exceptions() && exception->value().is_object() && is<Error>(exception->value().as_object())) { + auto& error = static_cast<Error&>(exception->value().as_object()); + dbgln("Throwing JavaScript Error: {}, {}", error.name(), error.message()); + + for (ssize_t i = m_call_stack.size() - 1; i >= 0; --i) { + auto function_name = m_call_stack[i]->function_name; + if (function_name.is_empty()) + function_name = "<anonymous>"; + dbgln(" {}", function_name); + } + } + + m_exception = exception; + unwind(ScopeType::Try); +} + +String VM::join_arguments() const +{ + StringBuilder joined_arguments; + for (size_t i = 0; i < argument_count(); ++i) { + joined_arguments.append(argument(i).to_string_without_side_effects().characters()); + if (i != argument_count() - 1) + joined_arguments.append(' '); + } + return joined_arguments.build(); +} + +Value VM::resolve_this_binding(GlobalObject& global_object) const +{ + return find_this_scope()->get_this_binding(global_object); +} + +const ScopeObject* VM::find_this_scope() const +{ + // We will always return because the Global environment will always be reached, which has a |this| binding. + for (auto* scope = current_scope(); scope; scope = scope->parent()) { + if (scope->has_this_binding()) + return scope; + } + ASSERT_NOT_REACHED(); +} + +Value VM::get_new_target() const +{ + ASSERT(is<LexicalEnvironment>(find_this_scope())); + return static_cast<const LexicalEnvironment*>(find_this_scope())->new_target(); +} + +Value VM::call_internal(Function& function, Value this_value, Optional<MarkedValueList> arguments) +{ + ASSERT(!exception()); + + CallFrame call_frame; + call_frame.is_strict_mode = function.is_strict_mode(); + call_frame.function_name = function.name(); + call_frame.this_value = function.bound_this().value_or(this_value); + call_frame.arguments = function.bound_arguments(); + if (arguments.has_value()) + call_frame.arguments.append(move(arguments.release_value().values())); + auto* environment = function.create_environment(); + call_frame.scope = environment; + + ASSERT(environment->this_binding_status() == LexicalEnvironment::ThisBindingStatus::Uninitialized); + environment->bind_this_value(function.global_object(), call_frame.this_value); + if (exception()) + return {}; + + push_call_frame(call_frame, function.global_object()); + if (exception()) + return {}; + auto result = function.call(); + pop_call_frame(); + return result; +} + +bool VM::in_strict_mode() const +{ + if (call_stack().is_empty()) + return false; + return call_frame().is_strict_mode; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/VM.h b/Userland/Libraries/LibJS/Runtime/VM.h new file mode 100644 index 0000000000..320ea53405 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/VM.h @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/FlyString.h> +#include <AK/HashMap.h> +#include <AK/RefCounted.h> +#include <AK/StackInfo.h> +#include <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/CommonPropertyNames.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/ErrorTypes.h> +#include <LibJS/Runtime/Exception.h> +#include <LibJS/Runtime/MarkedValueList.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +enum class ScopeType { + None, + Function, + Block, + Try, + Breakable, + Continuable, +}; + +struct ScopeFrame { + ScopeType type; + NonnullRefPtr<ScopeNode> scope_node; + bool pushed_environment { false }; +}; + +struct CallFrame { + FlyString function_name; + Value this_value; + Vector<Value> arguments; + Array* arguments_object { nullptr }; + ScopeObject* scope { nullptr }; + bool is_strict_mode { false }; +}; + +class VM : public RefCounted<VM> { +public: + static NonnullRefPtr<VM> create(); + ~VM(); + + bool should_log_exceptions() const { return m_should_log_exceptions; } + void set_should_log_exceptions(bool b) { m_should_log_exceptions = b; } + + Heap& heap() { return m_heap; } + const Heap& heap() const { return m_heap; } + + Interpreter& interpreter(); + Interpreter* interpreter_if_exists(); + + void push_interpreter(Interpreter&); + void pop_interpreter(Interpreter&); + + Exception* exception() + { + return m_exception; + } + + void clear_exception() { m_exception = nullptr; } + + class InterpreterExecutionScope { + public: + InterpreterExecutionScope(Interpreter&); + ~InterpreterExecutionScope(); + + private: + Interpreter& m_interpreter; + }; + + void gather_roots(HashTable<Cell*>&); + +#define __JS_ENUMERATE(SymbolName, snake_name) \ + Symbol* well_known_symbol_##snake_name() const { return m_well_known_symbol_##snake_name; } + JS_ENUMERATE_WELL_KNOWN_SYMBOLS +#undef __JS_ENUMERATE + + Symbol* get_global_symbol(const String& description); + + PrimitiveString& empty_string() { return *m_empty_string; } + PrimitiveString& single_ascii_character_string(u8 character) + { + ASSERT(character < 0x80); + return *m_single_ascii_character_strings[character]; + } + + void push_call_frame(CallFrame& call_frame, GlobalObject& global_object) + { + ASSERT(!exception()); + // Ensure we got some stack space left, so the next function call doesn't kill us. + // This value is merely a guess and might need tweaking at a later point. + if (m_stack_info.size_free() < 16 * KiB) + throw_exception<Error>(global_object, "RuntimeError", "Call stack size limit exceeded"); + else + m_call_stack.append(&call_frame); + } + + void pop_call_frame() { m_call_stack.take_last(); } + + void push_ast_node(const ASTNode& node) { m_ast_nodes.append(&node); } + void pop_ast_node() { m_ast_nodes.take_last(); } + + CallFrame& call_frame() { return *m_call_stack.last(); } + const CallFrame& call_frame() const { return *m_call_stack.last(); } + const Vector<CallFrame*>& call_stack() const { return m_call_stack; } + Vector<CallFrame*>& call_stack() { return m_call_stack; } + const Vector<const ASTNode*>& node_stack() const { return m_ast_nodes; } + + const ScopeObject* current_scope() const { return call_frame().scope; } + ScopeObject* current_scope() { return call_frame().scope; } + + bool in_strict_mode() const; + + template<typename Callback> + void for_each_argument(Callback callback) + { + if (m_call_stack.is_empty()) + return; + for (auto& value : call_frame().arguments) + callback(value); + } + + size_t argument_count() const + { + if (m_call_stack.is_empty()) + return 0; + return call_frame().arguments.size(); + } + + Value argument(size_t index) const + { + if (m_call_stack.is_empty()) + return {}; + auto& arguments = call_frame().arguments; + return index < arguments.size() ? arguments[index] : js_undefined(); + } + + Value this_value(Object& global_object) const + { + if (m_call_stack.is_empty()) + return &global_object; + return call_frame().this_value; + } + + Value last_value() const { return m_last_value; } + void set_last_value(Badge<Interpreter>, Value value) { m_last_value = value; } + + const StackInfo& stack_info() const { return m_stack_info; }; + + bool underscore_is_last_value() const { return m_underscore_is_last_value; } + void set_underscore_is_last_value(bool b) { m_underscore_is_last_value = b; } + + void unwind(ScopeType type, FlyString label = {}) + { + m_unwind_until = type; + m_unwind_until_label = label; + } + void stop_unwind() { m_unwind_until = ScopeType::None; } + bool should_unwind_until(ScopeType type, FlyString label = {}) const + { + if (m_unwind_until_label.is_null()) + return m_unwind_until == type; + return m_unwind_until == type && m_unwind_until_label == label; + } + bool should_unwind() const { return m_unwind_until != ScopeType::None; } + + ScopeType unwind_until() const { return m_unwind_until; } + + Value get_variable(const FlyString& name, GlobalObject&); + void set_variable(const FlyString& name, Value, GlobalObject&, bool first_assignment = false); + + Reference get_reference(const FlyString& name); + + template<typename T, typename... Args> + void throw_exception(GlobalObject& global_object, Args&&... args) + { + return throw_exception(global_object, T::create(global_object, forward<Args>(args)...)); + } + + void throw_exception(Exception*); + void throw_exception(GlobalObject& global_object, Value value) + { + return throw_exception(heap().allocate<Exception>(global_object, value)); + } + + template<typename T, typename... Args> + void throw_exception(GlobalObject& global_object, ErrorType type, Args&&... args) + { + return throw_exception(global_object, T::create(global_object, String::formatted(type.message(), forward<Args>(args)...))); + } + + Value construct(Function&, Function& new_target, Optional<MarkedValueList> arguments, GlobalObject&); + + String join_arguments() const; + + Value resolve_this_binding(GlobalObject&) const; + const ScopeObject* find_this_scope() const; + Value get_new_target() const; + + template<typename... Args> + [[nodiscard]] ALWAYS_INLINE Value call(Function& function, Value this_value, Args... args) + { + if constexpr (sizeof...(Args) > 0) { + MarkedValueList arglist { heap() }; + (..., arglist.append(move(args))); + return call(function, this_value, move(arglist)); + } + + return call(function, this_value); + } + + CommonPropertyNames names; + + Shape& scope_object_shape() { return *m_scope_object_shape; } + +private: + VM(); + + [[nodiscard]] Value call_internal(Function&, Value this_value, Optional<MarkedValueList> arguments); + + Exception* m_exception { nullptr }; + + Heap m_heap; + Vector<Interpreter*> m_interpreters; + + Vector<CallFrame*> m_call_stack; + Vector<const ASTNode*> m_ast_nodes; + + Value m_last_value; + ScopeType m_unwind_until { ScopeType::None }; + FlyString m_unwind_until_label; + + StackInfo m_stack_info; + + bool m_underscore_is_last_value { false }; + + HashMap<String, Symbol*> m_global_symbol_map; + + PrimitiveString* m_empty_string { nullptr }; + PrimitiveString* m_single_ascii_character_strings[128] {}; + +#define __JS_ENUMERATE(SymbolName, snake_name) \ + Symbol* m_well_known_symbol_##snake_name { nullptr }; + JS_ENUMERATE_WELL_KNOWN_SYMBOLS +#undef __JS_ENUMERATE + + Shape* m_scope_object_shape { nullptr }; + + bool m_should_log_exceptions { false }; +}; + +template<> +[[nodiscard]] ALWAYS_INLINE Value VM::call(Function& function, Value this_value, MarkedValueList arguments) { return call_internal(function, this_value, move(arguments)); } + +template<> +[[nodiscard]] ALWAYS_INLINE Value VM::call(Function& function, Value this_value, Optional<MarkedValueList> arguments) { return call_internal(function, this_value, move(arguments)); } + +template<> +[[nodiscard]] ALWAYS_INLINE Value VM::call(Function& function, Value this_value) { return call(function, this_value, Optional<MarkedValueList> {}); } + +} diff --git a/Userland/Libraries/LibJS/Runtime/Value.cpp b/Userland/Libraries/LibJS/Runtime/Value.cpp new file mode 100644 index 0000000000..1d397c9536 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Value.cpp @@ -0,0 +1,1221 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2020, Linus Groh <mail@linusgroh.de> + * 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 <AK/FlyString.h> +#include <AK/String.h> +#include <AK/StringBuilder.h> +#include <AK/Utf8View.h> +#include <LibCrypto/BigInt/SignedBigInteger.h> +#include <LibCrypto/NumberTheory/ModularFunctions.h> +#include <LibJS/Heap/Heap.h> +#include <LibJS/Runtime/Accessor.h> +#include <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/BigInt.h> +#include <LibJS/Runtime/BigIntObject.h> +#include <LibJS/Runtime/BooleanObject.h> +#include <LibJS/Runtime/BoundFunction.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/Function.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/NumberObject.h> +#include <LibJS/Runtime/Object.h> +#include <LibJS/Runtime/PrimitiveString.h> +#include <LibJS/Runtime/RegExpObject.h> +#include <LibJS/Runtime/StringObject.h> +#include <LibJS/Runtime/Symbol.h> +#include <LibJS/Runtime/SymbolObject.h> +#include <LibJS/Runtime/Value.h> +#include <ctype.h> +#include <math.h> + +namespace JS { + +// Used in various abstract operations to make it obvious when a non-optional return value must be discarded. +static const double INVALID { 0 }; + +static const Crypto::SignedBigInteger BIGINT_ZERO { 0 }; + +static bool is_valid_bigint_value(String string) +{ + string = string.trim_whitespace(); + if (string.length() > 1 && (string[0] == '-' || string[0] == '+')) + string = string.substring_view(1, string.length() - 1); + for (auto& ch : string) { + if (!isdigit(ch)) + return false; + } + return true; +} + +ALWAYS_INLINE bool both_number(const Value& lhs, const Value& rhs) +{ + return lhs.is_number() && rhs.is_number(); +} + +ALWAYS_INLINE bool both_bigint(const Value& lhs, const Value& rhs) +{ + return lhs.is_bigint() && rhs.is_bigint(); +} + +static String double_to_string(double d) +{ + // https://tc39.es/ecma262/#sec-numeric-types-number-tostring + if (isnan(d)) + return "NaN"; + if (d == +0.0 || d == -0.0) + return "0"; + if (d < +0.0) { + StringBuilder builder; + builder.append('-'); + builder.append(double_to_string(-d)); + return builder.to_string(); + } + if (d == INFINITY) + return "Infinity"; + + StringBuilder number_string_builder; + + size_t start_index = 0; + size_t end_index = 0; + size_t intpart_end = 0; + + // generate integer part (reversed) + double intPart; + double frac_part; + frac_part = modf(d, &intPart); + while (intPart > 0) { + number_string_builder.append('0' + (int)fmod(intPart, 10)); + end_index++; + intPart = floor(intPart / 10); + } + + auto reversed_integer_part = number_string_builder.to_string().reverse(); + number_string_builder.clear(); + number_string_builder.append(reversed_integer_part); + + intpart_end = end_index; + + int exponent = 0; + + // generate fractional part + while (frac_part > 0) { + double old_frac_part = frac_part; + frac_part *= 10; + frac_part = modf(frac_part, &intPart); + if (old_frac_part == frac_part) + break; + number_string_builder.append('0' + (int)intPart); + end_index++; + exponent--; + } + + auto number_string = number_string_builder.to_string(); + + // FIXME: Remove this hack. + // FIXME: Instead find the shortest round-trippable representation. + // Remove decimals after the 15th position + if (end_index > intpart_end + 15) { + exponent += end_index - intpart_end - 15; + end_index = intpart_end + 15; + } + + // remove leading zeroes + while (start_index < end_index && number_string[start_index] == '0') { + start_index++; + } + + // remove trailing zeroes + while (end_index > 0 && number_string[end_index - 1] == '0') { + end_index--; + exponent++; + } + + if (end_index <= start_index) + return "0"; + + auto digits = number_string.substring_view(start_index, end_index - start_index); + + int number_of_digits = end_index - start_index; + + exponent += number_of_digits; + + StringBuilder builder; + + if (number_of_digits <= exponent && exponent <= 21) { + builder.append(digits); + builder.append(String::repeated('0', exponent - number_of_digits)); + return builder.to_string(); + } + if (0 < exponent && exponent <= 21) { + builder.append(digits.substring_view(0, exponent)); + builder.append('.'); + builder.append(digits.substring_view(exponent)); + return builder.to_string(); + } + if (-6 < exponent && exponent <= 0) { + builder.append("0."); + builder.append(String::repeated('0', -exponent)); + builder.append(digits); + return builder.to_string(); + } + if (number_of_digits == 1) { + builder.append(digits); + builder.append('e'); + + if (exponent - 1 > 0) + builder.append('+'); + else + builder.append('-'); + + builder.append(String::format("%d", abs(exponent - 1))); + return builder.to_string(); + } + + builder.append(digits[0]); + builder.append('.'); + builder.append(digits.substring_view(1)); + builder.append('e'); + + if (exponent - 1 > 0) + builder.append('+'); + else + builder.append('-'); + + builder.append(String::format("%d", abs(exponent - 1))); + return builder.to_string(); +} + +bool Value::is_array() const +{ + return is_object() && as_object().is_array(); +} + +Array& Value::as_array() +{ + ASSERT(is_array()); + return static_cast<Array&>(*m_value.as_object); +} + +bool Value::is_function() const +{ + return is_object() && as_object().is_function(); +} + +Function& Value::as_function() +{ + ASSERT(is_function()); + return static_cast<Function&>(as_object()); +} + +bool Value::is_regexp(GlobalObject& global_object) const +{ + // 7.2.8 IsRegExp, https://tc39.es/ecma262/#sec-isregexp + + if (!is_object()) + return false; + + auto matcher = as_object().get(global_object.vm().well_known_symbol_match()); + if (global_object.vm().exception()) + return false; + if (!matcher.is_empty() && !matcher.is_undefined()) + return matcher.to_boolean(); + + return is<RegExpObject>(as_object()); +} + +String Value::to_string_without_side_effects() const +{ + switch (m_type) { + case Type::Undefined: + return "undefined"; + case Type::Null: + return "null"; + case Type::Boolean: + return m_value.as_bool ? "true" : "false"; + case Type::Number: + return double_to_string(m_value.as_double); + case Type::String: + return m_value.as_string->string(); + case Type::Symbol: + return m_value.as_symbol->to_string(); + case Type::BigInt: + return m_value.as_bigint->to_string(); + case Type::Object: + return String::formatted("[object {}]", as_object().class_name()); + case Type::Accessor: + return "<accessor>"; + case Type::NativeProperty: + return "<native-property>"; + default: + ASSERT_NOT_REACHED(); + } +} + +PrimitiveString* Value::to_primitive_string(GlobalObject& global_object) +{ + if (is_string()) + return &as_string(); + auto string = to_string(global_object); + if (global_object.vm().exception()) + return nullptr; + return js_string(global_object.heap(), string); +} + +String Value::to_string(GlobalObject& global_object, bool legacy_null_to_empty_string) const +{ + switch (m_type) { + case Type::Undefined: + return "undefined"; + case Type::Null: + return !legacy_null_to_empty_string ? "null" : String::empty(); + case Type::Boolean: + return m_value.as_bool ? "true" : "false"; + case Type::Number: + return double_to_string(m_value.as_double); + case Type::String: + return m_value.as_string->string(); + case Type::Symbol: + global_object.vm().throw_exception<TypeError>(global_object, ErrorType::Convert, "symbol", "string"); + return {}; + case Type::BigInt: + return m_value.as_bigint->big_integer().to_base10(); + case Type::Object: { + auto primitive_value = to_primitive(PreferredType::String); + if (global_object.vm().exception()) + return {}; + return primitive_value.to_string(global_object); + } + default: + ASSERT_NOT_REACHED(); + } +} + +bool Value::to_boolean() const +{ + switch (m_type) { + case Type::Undefined: + case Type::Null: + return false; + case Type::Boolean: + return m_value.as_bool; + case Type::Number: + if (is_nan()) + return false; + return m_value.as_double != 0; + case Type::String: + return !m_value.as_string->string().is_empty(); + case Type::Symbol: + return true; + case Type::BigInt: + return m_value.as_bigint->big_integer() != BIGINT_ZERO; + case Type::Object: + return true; + default: + ASSERT_NOT_REACHED(); + } +} + +Value Value::to_primitive(PreferredType preferred_type) const +{ + if (is_object()) { + // FIXME: Also support @@toPrimitive + if (preferred_type == PreferredType::Default) + preferred_type = PreferredType::Number; + return as_object().ordinary_to_primitive(preferred_type); + } + return *this; +} + +Object* Value::to_object(GlobalObject& global_object) const +{ + switch (m_type) { + case Type::Undefined: + case Type::Null: + global_object.vm().throw_exception<TypeError>(global_object, ErrorType::ToObjectNullOrUndef); + return nullptr; + case Type::Boolean: + return BooleanObject::create(global_object, m_value.as_bool); + case Type::Number: + return NumberObject::create(global_object, m_value.as_double); + case Type::String: + return StringObject::create(global_object, *m_value.as_string); + case Type::Symbol: + return SymbolObject::create(global_object, *m_value.as_symbol); + case Type::BigInt: + return BigIntObject::create(global_object, *m_value.as_bigint); + case Type::Object: + return &const_cast<Object&>(as_object()); + default: + dbgln("Dying because I can't to_object() on {}", *this); + ASSERT_NOT_REACHED(); + } +} + +Value Value::to_numeric(GlobalObject& global_object) const +{ + auto primitive = to_primitive(Value::PreferredType::Number); + if (global_object.vm().exception()) + return {}; + if (primitive.is_bigint()) + return primitive; + return primitive.to_number(global_object); +} + +Value Value::to_number(GlobalObject& global_object) const +{ + switch (m_type) { + case Type::Undefined: + return js_nan(); + case Type::Null: + return Value(0); + case Type::Boolean: + return Value(m_value.as_bool ? 1 : 0); + case Type::Number: + return Value(m_value.as_double); + case Type::String: { + auto string = as_string().string().trim_whitespace(); + if (string.is_empty()) + return Value(0); + if (string == "Infinity" || string == "+Infinity") + return js_infinity(); + if (string == "-Infinity") + return js_negative_infinity(); + char* endptr; + auto parsed_double = strtod(string.characters(), &endptr); + if (*endptr) + return js_nan(); + return Value(parsed_double); + } + case Type::Symbol: + global_object.vm().throw_exception<TypeError>(global_object, ErrorType::Convert, "symbol", "number"); + return {}; + case Type::BigInt: + global_object.vm().throw_exception<TypeError>(global_object, ErrorType::Convert, "BigInt", "number"); + return {}; + case Type::Object: { + auto primitive = to_primitive(PreferredType::Number); + if (global_object.vm().exception()) + return {}; + return primitive.to_number(global_object); + } + default: + ASSERT_NOT_REACHED(); + } +} + +BigInt* Value::to_bigint(GlobalObject& global_object) const +{ + auto& vm = global_object.vm(); + auto primitive = to_primitive(PreferredType::Number); + if (vm.exception()) + return nullptr; + switch (primitive.type()) { + case Type::Undefined: + vm.throw_exception<TypeError>(global_object, ErrorType::Convert, "undefined", "BigInt"); + return nullptr; + case Type::Null: + vm.throw_exception<TypeError>(global_object, ErrorType::Convert, "null", "BigInt"); + return nullptr; + case Type::Boolean: { + auto value = primitive.as_bool() ? 1 : 0; + return js_bigint(vm.heap(), Crypto::SignedBigInteger { value }); + } + case Type::BigInt: + return &primitive.as_bigint(); + case Type::Number: + vm.throw_exception<TypeError>(global_object, ErrorType::Convert, "number", "BigInt"); + return {}; + case Type::String: { + auto& string = primitive.as_string().string(); + if (!is_valid_bigint_value(string)) { + vm.throw_exception<SyntaxError>(global_object, ErrorType::BigIntInvalidValue, string); + return {}; + } + return js_bigint(vm.heap(), Crypto::SignedBigInteger::from_base10(string.trim_whitespace())); + } + case Type::Symbol: + vm.throw_exception<TypeError>(global_object, ErrorType::Convert, "symbol", "BigInt"); + return {}; + default: + ASSERT_NOT_REACHED(); + } +} + +i32 Value::as_i32() const +{ + return static_cast<i32>(as_double()); +} + +u32 Value::as_u32() const +{ + ASSERT(as_double() >= 0); + return min((double)as_i32(), MAX_U32); +} + +size_t Value::as_size_t() const +{ + ASSERT(as_double() >= 0); + return min((double)as_i32(), MAX_ARRAY_LIKE_INDEX); +} + +double Value::to_double(GlobalObject& global_object) const +{ + auto number = to_number(global_object); + if (global_object.vm().exception()) + return INVALID; + return number.as_double(); +} + +i32 Value::to_i32(GlobalObject& global_object) const +{ + auto number = to_number(global_object); + if (global_object.vm().exception()) + return INVALID; + if (number.is_nan() || number.is_infinity()) + return 0; + return number.as_i32(); +} + +u32 Value::to_u32(GlobalObject& global_object) const +{ + // 7.1.7 ToUint32, https://tc39.es/ecma262/#sec-touint32 + auto number = to_number(global_object); + if (global_object.vm().exception()) + return INVALID; + if (number.is_nan() || number.is_infinity()) + return 0; + if (number.as_double() <= 0) + return 0; + return number.as_u32(); +} + +size_t Value::to_length(GlobalObject& global_object) const +{ + // 7.1.20 ToLength, https://tc39.es/ecma262/#sec-tolength + + auto& vm = global_object.vm(); + + auto len = to_integer_or_infinity(global_object); + if (vm.exception()) + return INVALID; + if (len <= 0) + return 0; + return min(len, MAX_ARRAY_LIKE_INDEX); +} + +size_t Value::to_index(GlobalObject& global_object) const +{ + // 7.1.22 ToIndex, https://tc39.es/ecma262/#sec-toindex + + auto& vm = global_object.vm(); + + if (is_undefined()) + return 0; + auto integer_index = to_integer_or_infinity(global_object); + if (vm.exception()) + return INVALID; + if (integer_index < 0) { + vm.throw_exception<RangeError>(global_object, ErrorType::InvalidIndex); + return INVALID; + } + auto index = Value(integer_index).to_length(global_object); + ASSERT(!vm.exception()); + if (integer_index != index) { + vm.throw_exception<RangeError>(global_object, ErrorType::InvalidIndex); + return INVALID; + } + return index; +} + +double Value::to_integer_or_infinity(GlobalObject& global_object) const +{ + // 7.1.5 ToIntegerOrInfinity, https://tc39.es/ecma262/#sec-tointegerorinfinity + + auto& vm = global_object.vm(); + + auto number = to_number(global_object); + if (vm.exception()) + return INVALID; + if (number.is_nan() || number.as_double() == 0) + return 0; + if (number.is_infinity()) + return number.as_double(); + auto integer = floor(abs(number.as_double())); + if (number.as_double() < 0) + integer = -integer; + return integer; +} + +Value greater_than(GlobalObject& global_object, Value lhs, Value rhs) +{ + TriState relation = abstract_relation(global_object, false, lhs, rhs); + if (relation == TriState::Unknown) + return Value(false); + return Value(relation == TriState::True); +} + +Value greater_than_equals(GlobalObject& global_object, Value lhs, Value rhs) +{ + TriState relation = abstract_relation(global_object, true, lhs, rhs); + if (relation == TriState::Unknown || relation == TriState::True) + return Value(false); + return Value(true); +} + +Value less_than(GlobalObject& global_object, Value lhs, Value rhs) +{ + TriState relation = abstract_relation(global_object, true, lhs, rhs); + if (relation == TriState::Unknown) + return Value(false); + return Value(relation == TriState::True); +} + +Value less_than_equals(GlobalObject& global_object, Value lhs, Value rhs) +{ + TriState relation = abstract_relation(global_object, false, lhs, rhs); + if (relation == TriState::Unknown || relation == TriState::True) + return Value(false); + return Value(true); +} + +Value bitwise_and(GlobalObject& global_object, Value lhs, Value rhs) +{ + auto lhs_numeric = lhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + auto rhs_numeric = rhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + if (both_number(lhs_numeric, rhs_numeric)) { + if (!lhs_numeric.is_finite_number() || !rhs_numeric.is_finite_number()) + return Value(0); + return Value((i32)lhs_numeric.as_double() & (i32)rhs_numeric.as_double()); + } + if (both_bigint(lhs_numeric, rhs_numeric)) + return js_bigint(global_object.heap(), lhs_numeric.as_bigint().big_integer().bitwise_and(rhs_numeric.as_bigint().big_integer())); + global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "bitwise AND"); + return {}; +} + +Value bitwise_or(GlobalObject& global_object, Value lhs, Value rhs) +{ + auto lhs_numeric = lhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + auto rhs_numeric = rhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + if (both_number(lhs_numeric, rhs_numeric)) { + if (!lhs_numeric.is_finite_number() && !rhs_numeric.is_finite_number()) + return Value(0); + if (!lhs_numeric.is_finite_number()) + return rhs_numeric; + if (!rhs_numeric.is_finite_number()) + return lhs_numeric; + return Value((i32)lhs_numeric.as_double() | (i32)rhs_numeric.as_double()); + } + if (both_bigint(lhs_numeric, rhs_numeric)) + return js_bigint(global_object.heap(), lhs_numeric.as_bigint().big_integer().bitwise_or(rhs_numeric.as_bigint().big_integer())); + global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "bitwise OR"); + return {}; +} + +Value bitwise_xor(GlobalObject& global_object, Value lhs, Value rhs) +{ + auto lhs_numeric = lhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + auto rhs_numeric = rhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + if (both_number(lhs_numeric, rhs_numeric)) { + if (!lhs_numeric.is_finite_number() && !rhs_numeric.is_finite_number()) + return Value(0); + if (!lhs_numeric.is_finite_number()) + return rhs_numeric; + if (!rhs_numeric.is_finite_number()) + return lhs_numeric; + return Value((i32)lhs_numeric.as_double() ^ (i32)rhs_numeric.as_double()); + } + if (both_bigint(lhs_numeric, rhs_numeric)) + return js_bigint(global_object.heap(), lhs_numeric.as_bigint().big_integer().bitwise_xor(rhs_numeric.as_bigint().big_integer())); + global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "bitwise XOR"); + return {}; +} + +Value bitwise_not(GlobalObject& global_object, Value lhs) +{ + auto lhs_numeric = lhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + if (lhs_numeric.is_number()) + return Value(~lhs_numeric.to_i32(global_object)); + auto big_integer_bitwise_not = lhs_numeric.as_bigint().big_integer(); + big_integer_bitwise_not = big_integer_bitwise_not.plus(Crypto::SignedBigInteger { 1 }); + big_integer_bitwise_not.negate(); + return js_bigint(global_object.heap(), big_integer_bitwise_not); +} + +Value unary_plus(GlobalObject& global_object, Value lhs) +{ + return lhs.to_number(global_object.global_object()); +} + +Value unary_minus(GlobalObject& global_object, Value lhs) +{ + auto lhs_numeric = lhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + if (lhs_numeric.is_number()) { + if (lhs_numeric.is_nan()) + return js_nan(); + return Value(-lhs_numeric.as_double()); + } + if (lhs_numeric.as_bigint().big_integer() == BIGINT_ZERO) + return js_bigint(global_object.heap(), BIGINT_ZERO); + auto big_integer_negated = lhs_numeric.as_bigint().big_integer(); + big_integer_negated.negate(); + return js_bigint(global_object.heap(), big_integer_negated); +} + +Value left_shift(GlobalObject& global_object, Value lhs, Value rhs) +{ + auto lhs_numeric = lhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + auto rhs_numeric = rhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + if (both_number(lhs_numeric, rhs_numeric)) { + if (!lhs_numeric.is_finite_number()) + return Value(0); + if (!rhs_numeric.is_finite_number()) + return lhs_numeric; + return Value((i32)lhs_numeric.as_double() << (i32)rhs_numeric.as_double()); + } + if (both_bigint(lhs_numeric, rhs_numeric)) + TODO(); + global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "left-shift"); + return {}; +} + +Value right_shift(GlobalObject& global_object, Value lhs, Value rhs) +{ + auto lhs_numeric = lhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + auto rhs_numeric = rhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + if (both_number(lhs_numeric, rhs_numeric)) { + if (!lhs_numeric.is_finite_number()) + return Value(0); + if (!rhs_numeric.is_finite_number()) + return lhs_numeric; + return Value((i32)lhs_numeric.as_double() >> (i32)rhs_numeric.as_double()); + } + if (both_bigint(lhs_numeric, rhs_numeric)) + TODO(); + global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "right-shift"); + return {}; +} + +Value unsigned_right_shift(GlobalObject& global_object, Value lhs, Value rhs) +{ + auto lhs_numeric = lhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + auto rhs_numeric = rhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + if (both_number(lhs_numeric, rhs_numeric)) { + if (!lhs_numeric.is_finite_number()) + return Value(0); + if (!rhs_numeric.is_finite_number()) + return lhs_numeric; + return Value((unsigned)lhs_numeric.as_double() >> (i32)rhs_numeric.as_double()); + } + global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperator, "unsigned right-shift"); + return {}; +} + +Value add(GlobalObject& global_object, Value lhs, Value rhs) +{ + auto lhs_primitive = lhs.to_primitive(); + if (global_object.vm().exception()) + return {}; + auto rhs_primitive = rhs.to_primitive(); + if (global_object.vm().exception()) + return {}; + + if (lhs_primitive.is_string() || rhs_primitive.is_string()) { + auto lhs_string = lhs_primitive.to_string(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + auto rhs_string = rhs_primitive.to_string(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + StringBuilder builder(lhs_string.length() + rhs_string.length()); + builder.append(lhs_string); + builder.append(rhs_string); + return js_string(global_object.heap(), builder.to_string()); + } + + auto lhs_numeric = lhs_primitive.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + auto rhs_numeric = rhs_primitive.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + if (both_number(lhs_numeric, rhs_numeric)) + return Value(lhs_numeric.as_double() + rhs_numeric.as_double()); + if (both_bigint(lhs_numeric, rhs_numeric)) + return js_bigint(global_object.heap(), lhs_numeric.as_bigint().big_integer().plus(rhs_numeric.as_bigint().big_integer())); + global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "addition"); + return {}; +} + +Value sub(GlobalObject& global_object, Value lhs, Value rhs) +{ + auto lhs_numeric = lhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + auto rhs_numeric = rhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + if (both_number(lhs_numeric, rhs_numeric)) + return Value(lhs_numeric.as_double() - rhs_numeric.as_double()); + if (both_bigint(lhs_numeric, rhs_numeric)) + return js_bigint(global_object.heap(), lhs_numeric.as_bigint().big_integer().minus(rhs_numeric.as_bigint().big_integer())); + global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "subtraction"); + return {}; +} + +Value mul(GlobalObject& global_object, Value lhs, Value rhs) +{ + auto lhs_numeric = lhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + auto rhs_numeric = rhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + if (both_number(lhs_numeric, rhs_numeric)) + return Value(lhs_numeric.as_double() * rhs_numeric.as_double()); + if (both_bigint(lhs_numeric, rhs_numeric)) + return js_bigint(global_object.heap(), lhs_numeric.as_bigint().big_integer().multiplied_by(rhs_numeric.as_bigint().big_integer())); + global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "multiplication"); + return {}; +} + +Value div(GlobalObject& global_object, Value lhs, Value rhs) +{ + auto lhs_numeric = lhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + auto rhs_numeric = rhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + if (both_number(lhs_numeric, rhs_numeric)) + return Value(lhs_numeric.as_double() / rhs_numeric.as_double()); + if (both_bigint(lhs_numeric, rhs_numeric)) + return js_bigint(global_object.heap(), lhs_numeric.as_bigint().big_integer().divided_by(rhs_numeric.as_bigint().big_integer()).quotient); + global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "division"); + return {}; +} + +Value mod(GlobalObject& global_object, Value lhs, Value rhs) +{ + auto lhs_numeric = lhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + auto rhs_numeric = rhs.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + if (both_number(lhs_numeric, rhs_numeric)) { + if (lhs_numeric.is_nan() || rhs_numeric.is_nan()) + return js_nan(); + auto index = lhs_numeric.as_double(); + auto period = rhs_numeric.as_double(); + auto trunc = (double)(i32)(index / period); + return Value(index - trunc * period); + } + if (both_bigint(lhs_numeric, rhs_numeric)) + return js_bigint(global_object.heap(), lhs_numeric.as_bigint().big_integer().divided_by(rhs_numeric.as_bigint().big_integer()).remainder); + global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::BigIntBadOperatorOtherType, "modulo"); + return {}; +} + +Value exp(GlobalObject& global_object, Value lhs, Value rhs) +{ + auto& vm = global_object.vm(); + auto lhs_numeric = lhs.to_numeric(global_object); + if (vm.exception()) + return {}; + auto rhs_numeric = rhs.to_numeric(global_object); + if (vm.exception()) + return {}; + if (both_number(lhs_numeric, rhs_numeric)) + return Value(pow(lhs_numeric.as_double(), rhs_numeric.as_double())); + if (both_bigint(lhs_numeric, rhs_numeric)) + return js_bigint(vm.heap(), Crypto::NumberTheory::Power(lhs_numeric.as_bigint().big_integer(), rhs_numeric.as_bigint().big_integer())); + vm.throw_exception<TypeError>(global_object, ErrorType::BigIntBadOperatorOtherType, "exponentiation"); + return {}; +} + +Value in(GlobalObject& global_object, Value lhs, Value rhs) +{ + if (!rhs.is_object()) { + global_object.vm().throw_exception<TypeError>(global_object.global_object(), ErrorType::InOperatorWithObject); + return {}; + } + auto lhs_string = lhs.to_string(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + return Value(rhs.as_object().has_property(lhs_string)); +} + +Value instance_of(GlobalObject& global_object, Value lhs, Value rhs) +{ + auto& vm = global_object.vm(); + if (!rhs.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, rhs.to_string_without_side_effects()); + return {}; + } + auto has_instance_method = rhs.as_object().get(vm.well_known_symbol_has_instance()); + if (!has_instance_method.is_empty()) { + if (!has_instance_method.is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, has_instance_method.to_string_without_side_effects()); + return {}; + } + auto has_instance_result = vm.call(has_instance_method.as_function(), rhs, lhs); + if (vm.exception()) + return {}; + return Value(has_instance_result.to_boolean()); + } + + if (!rhs.is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, rhs.to_string_without_side_effects()); + return {}; + } + return ordinary_has_instance(global_object, lhs, rhs); +} + +Value ordinary_has_instance(GlobalObject& global_object, Value lhs, Value rhs) +{ + auto& vm = global_object.vm(); + if (!rhs.is_function()) + return Value(false); + auto& rhs_function = rhs.as_function(); + + if (is<BoundFunction>(rhs_function)) { + auto& bound_target = static_cast<const BoundFunction&>(rhs_function); + return instance_of(global_object, lhs, Value(&bound_target.target_function())); + } + + if (!lhs.is_object()) + return Value(false); + + Object* lhs_object = &lhs.as_object(); + auto rhs_prototype = rhs_function.get(vm.names.prototype); + if (vm.exception()) + return {}; + + if (!rhs_prototype.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::InstanceOfOperatorBadPrototype, rhs.to_string_without_side_effects()); + return {}; + } + while (true) { + lhs_object = lhs_object->prototype(); + if (vm.exception()) + return {}; + if (!lhs_object) + return Value(false); + if (same_value(rhs_prototype, lhs_object)) + return Value(true); + } +} + +bool same_value(Value lhs, Value rhs) +{ + if (lhs.type() != rhs.type()) + return false; + + if (lhs.is_number()) { + if (lhs.is_nan() && rhs.is_nan()) + return true; + if (lhs.is_positive_zero() && rhs.is_negative_zero()) + return false; + if (lhs.is_negative_zero() && rhs.is_positive_zero()) + return false; + return lhs.as_double() == rhs.as_double(); + } + + if (lhs.is_bigint()) { + auto lhs_big_integer = lhs.as_bigint().big_integer(); + auto rhs_big_integer = rhs.as_bigint().big_integer(); + if (lhs_big_integer == BIGINT_ZERO && rhs_big_integer == BIGINT_ZERO && lhs_big_integer.is_negative() != rhs_big_integer.is_negative()) + return false; + return lhs_big_integer == rhs_big_integer; + } + + return same_value_non_numeric(lhs, rhs); +} + +bool same_value_zero(Value lhs, Value rhs) +{ + if (lhs.type() != rhs.type()) + return false; + + if (lhs.is_number()) { + if (lhs.is_nan() && rhs.is_nan()) + return true; + return lhs.as_double() == rhs.as_double(); + } + + if (lhs.is_bigint()) + return lhs.as_bigint().big_integer() == rhs.as_bigint().big_integer(); + + return same_value_non_numeric(lhs, rhs); +} + +bool same_value_non_numeric(Value lhs, Value rhs) +{ + ASSERT(!lhs.is_number() && !lhs.is_bigint()); + ASSERT(lhs.type() == rhs.type()); + + switch (lhs.type()) { + case Value::Type::Undefined: + case Value::Type::Null: + 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: + return &lhs.as_object() == &rhs.as_object(); + default: + ASSERT_NOT_REACHED(); + } +} + +bool strict_eq(Value lhs, Value rhs) +{ + if (lhs.type() != rhs.type()) + return false; + + if (lhs.is_number()) { + if (lhs.is_nan() || rhs.is_nan()) + return false; + if (lhs.as_double() == rhs.as_double()) + return true; + return false; + } + + if (lhs.is_bigint()) + return lhs.as_bigint().big_integer() == rhs.as_bigint().big_integer(); + + return same_value_non_numeric(lhs, rhs); +} + +bool abstract_eq(GlobalObject& global_object, Value lhs, Value rhs) +{ + if (lhs.type() == rhs.type()) + return strict_eq(lhs, rhs); + + if (lhs.is_nullish() && rhs.is_nullish()) + return true; + + if (lhs.is_number() && rhs.is_string()) + return abstract_eq(global_object, lhs, rhs.to_number(global_object.global_object())); + + if (lhs.is_string() && rhs.is_number()) + return abstract_eq(global_object, lhs.to_number(global_object.global_object()), rhs); + + if (lhs.is_bigint() && rhs.is_string()) { + auto& rhs_string = rhs.as_string().string(); + if (!is_valid_bigint_value(rhs_string)) + return false; + return abstract_eq(global_object, lhs, js_bigint(global_object.heap(), Crypto::SignedBigInteger::from_base10(rhs_string))); + } + + if (lhs.is_string() && rhs.is_bigint()) + return abstract_eq(global_object, rhs, lhs); + + if (lhs.is_boolean()) + return abstract_eq(global_object, lhs.to_number(global_object.global_object()), rhs); + + if (rhs.is_boolean()) + return abstract_eq(global_object, lhs, rhs.to_number(global_object.global_object())); + + if ((lhs.is_string() || lhs.is_number() || lhs.is_bigint() || lhs.is_symbol()) && rhs.is_object()) + return abstract_eq(global_object, lhs, rhs.to_primitive()); + + if (lhs.is_object() && (rhs.is_string() || rhs.is_number() || lhs.is_bigint() || rhs.is_symbol())) + return abstract_eq(global_object, lhs.to_primitive(), rhs); + + if ((lhs.is_bigint() && rhs.is_number()) || (lhs.is_number() && rhs.is_bigint())) { + if (lhs.is_nan() || lhs.is_infinity() || rhs.is_nan() || rhs.is_infinity()) + return false; + if ((lhs.is_number() && !lhs.is_integer()) || (rhs.is_number() && !rhs.is_integer())) + return false; + if (lhs.is_number()) + return Crypto::SignedBigInteger { lhs.as_i32() } == rhs.as_bigint().big_integer(); + else + return Crypto::SignedBigInteger { rhs.as_i32() } == lhs.as_bigint().big_integer(); + } + + return false; +} + +TriState abstract_relation(GlobalObject& global_object, bool left_first, Value lhs, Value rhs) +{ + Value x_primitive; + Value y_primitive; + + if (left_first) { + x_primitive = lhs.to_primitive(Value::PreferredType::Number); + if (global_object.vm().exception()) + return {}; + y_primitive = rhs.to_primitive(Value::PreferredType::Number); + if (global_object.vm().exception()) + return {}; + } else { + y_primitive = lhs.to_primitive(Value::PreferredType::Number); + if (global_object.vm().exception()) + return {}; + x_primitive = rhs.to_primitive(Value::PreferredType::Number); + if (global_object.vm().exception()) + return {}; + } + + if (x_primitive.is_string() && y_primitive.is_string()) { + auto x_string = x_primitive.as_string().string(); + auto y_string = y_primitive.as_string().string(); + + if (x_string.starts_with(y_string)) + return TriState::False; + if (y_string.starts_with(x_string)) + return TriState::True; + + Utf8View x_code_points { x_string }; + Utf8View y_code_points { y_string }; + for (auto k = x_code_points.begin(), l = y_code_points.begin(); + k != x_code_points.end() && l != y_code_points.end(); + ++k, ++l) { + if (*k != *l) { + if (*k < *l) { + return TriState::True; + } else { + return TriState::False; + } + } + } + ASSERT_NOT_REACHED(); + } + + if (x_primitive.is_bigint() && y_primitive.is_string()) { + auto& y_string = y_primitive.as_string().string(); + if (!is_valid_bigint_value(y_string)) + return TriState::Unknown; + if (x_primitive.as_bigint().big_integer() < Crypto::SignedBigInteger::from_base10(y_string)) + return TriState::True; + else + return TriState::False; + } + + if (x_primitive.is_string() && y_primitive.is_bigint()) { + auto& x_string = x_primitive.as_string().string(); + if (!is_valid_bigint_value(x_string)) + return TriState::Unknown; + if (Crypto::SignedBigInteger::from_base10(x_string) < y_primitive.as_bigint().big_integer()) + return TriState::True; + else + return TriState::False; + } + + auto x_numeric = x_primitive.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + auto y_numeric = y_primitive.to_numeric(global_object.global_object()); + if (global_object.vm().exception()) + return {}; + + if (x_numeric.is_nan() || y_numeric.is_nan()) + return TriState::Unknown; + + if (x_numeric.is_positive_infinity() || y_numeric.is_negative_infinity()) + return TriState::False; + + if (x_numeric.is_negative_infinity() || y_numeric.is_positive_infinity()) + return TriState::True; + + if (x_numeric.is_number() && y_numeric.is_number()) { + if (x_numeric.as_double() < y_numeric.as_double()) + return TriState::True; + else + return TriState::False; + } + + if (x_numeric.is_bigint() && y_numeric.is_bigint()) { + if (x_numeric.as_bigint().big_integer() < y_numeric.as_bigint().big_integer()) + return TriState::True; + else + return TriState::False; + } + + ASSERT((x_numeric.is_number() && y_numeric.is_bigint()) || (x_numeric.is_bigint() && y_numeric.is_number())); + + bool x_lower_than_y; + if (x_numeric.is_number()) { + x_lower_than_y = x_numeric.is_integer() + ? Crypto::SignedBigInteger { x_numeric.as_i32() } < y_numeric.as_bigint().big_integer() + : (Crypto::SignedBigInteger { x_numeric.as_i32() } < y_numeric.as_bigint().big_integer() || Crypto::SignedBigInteger { x_numeric.as_i32() + 1 } < y_numeric.as_bigint().big_integer()); + } else { + x_lower_than_y = y_numeric.is_integer() + ? x_numeric.as_bigint().big_integer() < Crypto::SignedBigInteger { y_numeric.as_i32() } + : (x_numeric.as_bigint().big_integer() < Crypto::SignedBigInteger { y_numeric.as_i32() } || x_numeric.as_bigint().big_integer() < Crypto::SignedBigInteger { y_numeric.as_i32() + 1 }); + } + if (x_lower_than_y) + return TriState::True; + else + return TriState::False; +} + +size_t length_of_array_like(GlobalObject& global_object, const Object& object) +{ + // 7.3.18 LengthOfArrayLike, https://tc39.es/ecma262/#sec-lengthofarraylike + + auto& vm = global_object.vm(); + auto result = object.get(vm.names.length).value_or(js_undefined()); + if (vm.exception()) + return INVALID; + return result.to_length(global_object); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Value.h b/Userland/Libraries/LibJS/Runtime/Value.h new file mode 100644 index 0000000000..a24db80057 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Value.h @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <AK/Assertions.h> +#include <AK/Format.h> +#include <AK/Forward.h> +#include <AK/String.h> +#include <AK/Types.h> +#include <LibJS/Forward.h> +#include <math.h> + +// 2 ** 53 - 1 +static constexpr double MAX_ARRAY_LIKE_INDEX = 9007199254740991.0; +// 2 ** 32 - 1 +static constexpr double MAX_U32 = 4294967295.0; + +namespace JS { + +class Value { +public: + enum class Type { + Empty, + Undefined, + Null, + Number, + String, + Object, + Boolean, + Symbol, + Accessor, + BigInt, + NativeProperty, + }; + + enum class PreferredType { + Default, + String, + Number, + }; + + bool is_empty() const { return m_type == Type::Empty; } + bool is_undefined() const { return m_type == Type::Undefined; } + bool is_null() const { return m_type == Type::Null; } + bool is_number() const { return m_type == Type::Number; } + 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_accessor() const { return m_type == Type::Accessor; }; + bool is_bigint() const { return m_type == Type::BigInt; }; + bool is_native_property() const { return m_type == Type::NativeProperty; } + bool is_nullish() const { return is_null() || is_undefined(); } + bool is_cell() const { return is_string() || is_accessor() || is_object() || is_bigint() || is_symbol() || is_native_property(); } + bool is_array() const; + bool is_function() const; + bool is_regexp(GlobalObject& global_object) const; + + bool is_nan() const { return is_number() && __builtin_isnan(as_double()); } + bool is_infinity() const { return is_number() && __builtin_isinf(as_double()); } + bool is_positive_infinity() const { return is_number() && __builtin_isinf_sign(as_double()) > 0; } + bool is_negative_infinity() const { return is_number() && __builtin_isinf_sign(as_double()) < 0; } + bool is_positive_zero() const { return is_number() && 1.0 / as_double() == INFINITY; } + bool is_negative_zero() const { return is_number() && 1.0 / as_double() == -INFINITY; } + bool is_integer() const { return is_finite_number() && (i32)as_double() == as_double(); } + bool is_finite_number() const + { + if (!is_number()) + return false; + auto number = as_double(); + return !__builtin_isnan(number) && !__builtin_isinf(number); + } + + Value() + : m_type(Type::Empty) + { + } + + explicit Value(bool value) + : m_type(Type::Boolean) + { + m_value.as_bool = value; + } + + explicit Value(double value) + : m_type(Type::Number) + { + m_value.as_double = value; + } + + explicit Value(unsigned value) + : m_type(Type::Number) + { + m_value.as_double = static_cast<double>(value); + } + + explicit Value(i32 value) + : m_type(Type::Number) + { + m_value.as_double = value; + } + + Value(const Object* object) + : m_type(object ? Type::Object : Type::Null) + { + m_value.as_object = const_cast<Object*>(object); + } + + Value(const PrimitiveString* string) + : m_type(Type::String) + { + m_value.as_string = const_cast<PrimitiveString*>(string); + } + + Value(const Symbol* symbol) + : m_type(Type::Symbol) + { + m_value.as_symbol = const_cast<Symbol*>(symbol); + } + + Value(const Accessor* accessor) + : m_type(Type::Accessor) + { + m_value.as_accessor = const_cast<Accessor*>(accessor); + } + + Value(const BigInt* bigint) + : m_type(Type::BigInt) + { + m_value.as_bigint = const_cast<BigInt*>(bigint); + } + + Value(const NativeProperty* native_property) + : m_type(Type::NativeProperty) + { + m_value.as_native_property = const_cast<NativeProperty*>(native_property); + } + + explicit Value(Type type) + : m_type(type) + { + } + + Type type() const { return m_type; } + + double as_double() const + { + ASSERT(type() == Type::Number); + return m_value.as_double; + } + + bool as_bool() const + { + ASSERT(type() == Type::Boolean); + return m_value.as_bool; + } + + Object& as_object() + { + ASSERT(type() == Type::Object); + return *m_value.as_object; + } + + const Object& as_object() const + { + ASSERT(type() == Type::Object); + return *m_value.as_object; + } + + PrimitiveString& as_string() + { + ASSERT(is_string()); + return *m_value.as_string; + } + + const PrimitiveString& as_string() const + { + ASSERT(is_string()); + 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()); + return m_value.as_cell; + } + + Accessor& as_accessor() + { + ASSERT(is_accessor()); + return *m_value.as_accessor; + } + + BigInt& as_bigint() + { + ASSERT(is_bigint()); + return *m_value.as_bigint; + } + + NativeProperty& as_native_property() + { + ASSERT(is_native_property()); + return *m_value.as_native_property; + } + + Array& as_array(); + Function& as_function(); + + i32 as_i32() const; + u32 as_u32() const; + size_t as_size_t() const; + + String to_string(GlobalObject&, bool legacy_null_to_empty_string = false) const; + PrimitiveString* to_primitive_string(GlobalObject&); + Value to_primitive(PreferredType preferred_type = PreferredType::Default) const; + Object* to_object(GlobalObject&) const; + Value to_numeric(GlobalObject&) const; + Value to_number(GlobalObject&) const; + BigInt* to_bigint(GlobalObject&) const; + double to_double(GlobalObject&) const; + i32 to_i32(GlobalObject&) const; + u32 to_u32(GlobalObject&) const; + size_t to_length(GlobalObject&) const; + size_t to_index(GlobalObject&) const; + double to_integer_or_infinity(GlobalObject&) const; + bool to_boolean() const; + + String to_string_without_side_effects() const; + + Value value_or(Value fallback) const + { + if (is_empty()) + return fallback; + return *this; + } + +private: + Type m_type { Type::Empty }; + + union { + bool as_bool; + double as_double; + PrimitiveString* as_string; + Symbol* as_symbol; + Object* as_object; + Cell* as_cell; + Accessor* as_accessor; + BigInt* as_bigint; + NativeProperty* as_native_property; + } m_value; +}; + +inline Value js_undefined() +{ + return Value(Value::Type::Undefined); +} + +inline Value js_null() +{ + return Value(Value::Type::Null); +} + +inline Value js_nan() +{ + return Value(NAN); +} + +inline Value js_infinity() +{ + return Value(INFINITY); +} + +inline Value js_negative_infinity() +{ + return Value(-INFINITY); +} + +Value greater_than(GlobalObject&, Value lhs, Value rhs); +Value greater_than_equals(GlobalObject&, Value lhs, Value rhs); +Value less_than(GlobalObject&, Value lhs, Value rhs); +Value less_than_equals(GlobalObject&, Value lhs, Value rhs); +Value bitwise_and(GlobalObject&, Value lhs, Value rhs); +Value bitwise_or(GlobalObject&, Value lhs, Value rhs); +Value bitwise_xor(GlobalObject&, Value lhs, Value rhs); +Value bitwise_not(GlobalObject&, Value); +Value unary_plus(GlobalObject&, Value); +Value unary_minus(GlobalObject&, Value); +Value left_shift(GlobalObject&, Value lhs, Value rhs); +Value right_shift(GlobalObject&, Value lhs, Value rhs); +Value unsigned_right_shift(GlobalObject&, Value lhs, Value rhs); +Value add(GlobalObject&, Value lhs, Value rhs); +Value sub(GlobalObject&, Value lhs, Value rhs); +Value mul(GlobalObject&, Value lhs, Value rhs); +Value div(GlobalObject&, Value lhs, Value rhs); +Value mod(GlobalObject&, Value lhs, Value rhs); +Value exp(GlobalObject&, Value lhs, Value rhs); +Value in(GlobalObject&, Value lhs, Value rhs); +Value instance_of(GlobalObject&, Value lhs, Value rhs); +Value ordinary_has_instance(GlobalObject&, Value lhs, Value rhs); + +bool abstract_eq(GlobalObject&, Value lhs, Value rhs); +bool strict_eq(Value lhs, Value rhs); +bool same_value(Value lhs, Value rhs); +bool same_value_zero(Value lhs, Value rhs); +bool same_value_non_numeric(Value lhs, Value rhs); +TriState abstract_relation(GlobalObject&, bool left_first, Value lhs, Value rhs); +size_t length_of_array_like(GlobalObject&, const Object&); + +} + +namespace AK { + +template<> +struct Formatter<JS::Value> : Formatter<StringView> { + void format(FormatBuilder& builder, const JS::Value& value) + { + Formatter<StringView>::format(builder, value.is_empty() ? "<empty>" : value.to_string_without_side_effects()); + } +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/WithScope.cpp b/Userland/Libraries/LibJS/Runtime/WithScope.cpp new file mode 100644 index 0000000000..8afb608bf3 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/WithScope.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/AST.h> +#include <LibJS/Runtime/WithScope.h> + +namespace JS { + +WithScope::WithScope(Object& object, ScopeObject* parent_scope) + : ScopeObject(parent_scope) + , m_object(object) +{ +} + +void WithScope::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(&m_object); +} + +Optional<Variable> WithScope::get_from_scope(const FlyString& name) const +{ + auto value = m_object.get(name); + if (value.is_empty()) + return {}; + return Variable { value, DeclarationKind::Var }; +} + +void WithScope::put_to_scope(const FlyString& name, Variable variable) +{ + m_object.put(name, variable.value); +} + +bool WithScope::has_this_binding() const +{ + return parent()->has_this_binding(); +} + +Value WithScope::get_this_binding(GlobalObject& global_object) const +{ + return parent()->get_this_binding(global_object); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/WithScope.h b/Userland/Libraries/LibJS/Runtime/WithScope.h new file mode 100644 index 0000000000..b74542cb64 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/WithScope.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * 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 <LibJS/Runtime/ScopeObject.h> + +namespace JS { + +class WithScope : public ScopeObject { + JS_OBJECT(WithScope, ScopeObject); + +public: + WithScope(Object&, ScopeObject* parent_scope); + + virtual Optional<Variable> get_from_scope(const FlyString&) const override; + virtual void put_to_scope(const FlyString&, Variable) override; + virtual bool has_this_binding() const override; + virtual Value get_this_binding(GlobalObject&) const override; + +private: + virtual void visit_edges(Visitor&) override; + + Object& m_object; +}; + +} |