فهرست منبع

LibIDL+LibWeb: Add support for WebIDL setlike declarations on interfaces

Andrew Kaster 1 سال پیش
والد
کامیت
7077e4d002

+ 203 - 0
Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp

@@ -2643,6 +2643,33 @@ static void generate_prototype_or_global_mixin_declarations(IDL::Interface const
         )~~~");
     }
 
+    if (interface.set_entry_type.has_value()) {
+        auto setlike_generator = generator.fork();
+
+        setlike_generator.append(R"~~~(
+    JS_DECLARE_NATIVE_FUNCTION(get_size);
+    JS_DECLARE_NATIVE_FUNCTION(entries);
+    JS_DECLARE_NATIVE_FUNCTION(values);
+    JS_DECLARE_NATIVE_FUNCTION(for_each);
+    JS_DECLARE_NATIVE_FUNCTION(has);
+)~~~");
+        if (!interface.overload_sets.contains("add"sv) && !interface.is_set_readonly) {
+            setlike_generator.append(R"~~~(
+    JS_DECLARE_NATIVE_FUNCTION(add);
+)~~~");
+        }
+        if (!interface.overload_sets.contains("delete"sv) && !interface.is_set_readonly) {
+            setlike_generator.append(R"~~~(
+    JS_DECLARE_NATIVE_FUNCTION(delete_);
+)~~~");
+        }
+        if (!interface.overload_sets.contains("clear"sv) && !interface.is_set_readonly) {
+            setlike_generator.append(R"~~~(
+    JS_DECLARE_NATIVE_FUNCTION(clear);
+)~~~");
+        }
+    }
+
     for (auto& attribute : interface.attributes) {
         auto attribute_generator = generator.fork();
         attribute_generator.set("attribute.name:snakecase", attribute.name.to_snakecase());
@@ -3172,6 +3199,39 @@ void @class_name@::initialize(JS::Realm& realm)
 )~~~");
     }
 
+    // https://webidl.spec.whatwg.org/#js-setlike
+    if (interface.set_entry_type.has_value()) {
+
+        auto setlike_generator = generator.fork();
+
+        setlike_generator.append(R"~~~(
+    define_native_accessor(realm, vm.names.size, get_size, nullptr, JS::Attribute::Enumerable | JS::Attribute::Configurable);
+    define_native_function(realm, vm.names.entries, entries, 0, default_attributes);
+    // NOTE: Keys intentionally returns values for setlike
+    define_native_function(realm, vm.names.keys, values, 0, default_attributes);
+    define_native_function(realm, vm.names.values, values, 0, default_attributes);
+    define_direct_property(vm.well_known_symbol_iterator(), get_without_side_effects(vm.names.values), JS::Attribute::Configurable | JS::Attribute::Writable);
+    define_native_function(realm, vm.names.forEach, for_each, 1, default_attributes);
+    define_native_function(realm, vm.names.has, has, 1, default_attributes);
+)~~~");
+
+        if (!interface.overload_sets.contains("add"sv) && !interface.is_set_readonly) {
+            setlike_generator.append(R"~~~(
+    define_native_function(realm, vm.names.add, add, 1, default_attributes);
+)~~~");
+        }
+        if (!interface.overload_sets.contains("delete"sv) && !interface.is_set_readonly) {
+            setlike_generator.append(R"~~~(
+    define_native_function(realm, vm.names.delete_, delete_, 1, default_attributes);
+)~~~");
+        }
+        if (!interface.overload_sets.contains("clear"sv) && !interface.is_set_readonly) {
+            setlike_generator.append(R"~~~(
+    define_native_function(realm, vm.names.clear, clear, 0, default_attributes);
+)~~~");
+        }
+    }
+
     if (interface.has_unscopable_member) {
         generator.append(R"~~~(
     define_direct_property(vm.well_known_symbol_unscopables(), unscopable_object, JS::Attribute::Configurable);
@@ -3771,6 +3831,149 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::values)
 }
 )~~~");
     }
+
+    if (interface.set_entry_type.has_value()) {
+        auto setlike_generator = generator.fork();
+        setlike_generator.set("value_type", interface.set_entry_type.value()->name());
+        setlike_generator.append(R"~~~(
+// https://webidl.spec.whatwg.org/#js-set-size
+JS_DEFINE_NATIVE_FUNCTION(@class_name@::get_size)
+{
+    WebIDL::log_trace(vm, "@class_name@::size");
+    auto* impl = TRY(impl_from(vm));
+
+    JS::NonnullGCPtr<JS::Set> set = impl->set_entries();
+
+    return set->set_size();
+}
+
+// https://webidl.spec.whatwg.org/#js-set-entries
+JS_DEFINE_NATIVE_FUNCTION(@class_name@::entries)
+{
+    WebIDL::log_trace(vm, "@class_name@::entries");
+    auto& realm = *vm.current_realm();
+    auto* impl = TRY(impl_from(vm));
+
+    JS::NonnullGCPtr<JS::Set> set = impl->set_entries();
+
+    return TRY(throw_dom_exception_if_needed(vm, [&] { return JS::SetIterator::create(realm, *set, Object::PropertyKind::KeyAndValue); }));
+}
+
+// https://webidl.spec.whatwg.org/#js-set-values
+JS_DEFINE_NATIVE_FUNCTION(@class_name@::values)
+{
+    WebIDL::log_trace(vm, "@class_name@::values");
+    auto& realm = *vm.current_realm();
+    auto* impl = TRY(impl_from(vm));
+
+    JS::NonnullGCPtr<JS::Set> set = impl->set_entries();
+
+    return TRY(throw_dom_exception_if_needed(vm, [&] { return JS::SetIterator::create(realm, *set, Object::PropertyKind::Value); }));
+}
+
+// https://webidl.spec.whatwg.org/#js-set-forEach
+JS_DEFINE_NATIVE_FUNCTION(@class_name@::for_each)
+{
+    WebIDL::log_trace(vm, "@class_name@::for_each");
+    auto* impl = TRY(impl_from(vm));
+
+    JS::NonnullGCPtr<JS::Set> set = impl->set_entries();
+
+    auto callback = vm.argument(0);
+    if (!callback.is_function())
+        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, callback.to_string_without_side_effects());
+
+    for (auto& entry : *set) {
+        auto value = entry.key;
+        TRY(call(vm, callback.as_function(), vm.argument(1), value, value, impl));
+    }
+
+    return JS::js_undefined();
+}
+
+// https://webidl.spec.whatwg.org/#js-set-has
+JS_DEFINE_NATIVE_FUNCTION(@class_name@::has)
+{
+    WebIDL::log_trace(vm, "@class_name@::has");
+    auto* impl = TRY(impl_from(vm));
+
+    JS::NonnullGCPtr<JS::Set> set = impl->set_entries();
+
+    auto value_arg = vm.argument(0);
+    if (!value_arg.is_object() && !is<@value_type@>(value_arg.as_object())) {
+        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "@value_type@");
+    }
+
+    // FIXME: If value is -0, set value to +0.
+    // What? Which interfaces have a number as their set type?
+
+    return set->set_has(value_arg);
+}
+)~~~");
+
+        if (!interface.overload_sets.contains("add"sv) && !interface.is_set_readonly) {
+            setlike_generator.append(R"~~~(
+// https://webidl.spec.whatwg.org/#js-set-add
+JS_DEFINE_NATIVE_FUNCTION(@class_name@::add)
+{
+    WebIDL::log_trace(vm, "@class_name@::add");
+    auto* impl = TRY(impl_from(vm));
+
+    JS::NonnullGCPtr<JS::Set> set = impl->set_entries();
+
+    auto value_arg = vm.argument(0);
+    if (!value_arg.is_object() && !is<@value_type@>(value_arg.as_object())) {
+        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "@value_type@");
+    }
+
+    // FIXME: If value is -0, set value to +0.
+    // What? Which interfaces have a number as their set type?
+
+    set->set_add(value_arg);
+
+    return impl;
+}
+)~~~");
+        }
+        if (!interface.overload_sets.contains("delete"sv) && !interface.is_set_readonly) {
+            setlike_generator.append(R"~~~(
+// https://webidl.spec.whatwg.org/#js-set-delete
+JS_DEFINE_NATIVE_FUNCTION(@class_name@::delete_)
+{
+    WebIDL::log_trace(vm, "@class_name@::delete_");
+    auto* impl = TRY(impl_from(vm));
+
+    JS::NonnullGCPtr<JS::Set> set = impl->set_entries();
+
+    auto value_arg = vm.argument(0);
+    if (!value_arg.is_object() && !is<@value_type@>(value_arg.as_object())) {
+        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "@value_type@");
+    }
+
+    // FIXME: If value is -0, set value to +0.
+    // What? Which interfaces have a number as their set type?
+
+    return set->set_remove(value_arg);
+}
+)~~~");
+        }
+        if (!interface.overload_sets.contains("clear"sv) && !interface.is_set_readonly) {
+            setlike_generator.append(R"~~~(
+// https://webidl.spec.whatwg.org/#js-set-clear
+JS_DEFINE_NATIVE_FUNCTION(@class_name@::clear)
+{
+    WebIDL::log_trace(vm, "@class_name@::clear");
+    auto* impl = TRY(impl_from(vm));
+
+    JS::NonnullGCPtr<JS::Set> set = impl->set_entries();
+
+    set->set_clear();
+
+    return JS::js_undefined();
+}
+)~~~");
+        }
+    }
 }
 
 void generate_namespace_header(IDL::Interface const& interface, StringBuilder& builder)

+ 31 - 0
Userland/Libraries/LibIDL/IDLParser.cpp

@@ -292,8 +292,11 @@ void Parser::parse_attribute(HashMap<ByteString, ByteString>& extended_attribute
     if (readonly)
         consume_whitespace();
 
+    // FIXME: Should we parse 'readonly setlike<T>' differently than this?
     if (lexer.consume_specific("attribute"sv))
         consume_whitespace();
+    else if (lexer.consume_specific("setlike"sv) && !inherit)
+        parse_setlike(interface, readonly);
     else
         report_parsing_error("expected 'attribute'"sv, filename, input, lexer.tell());
 
@@ -466,6 +469,28 @@ void Parser::parse_iterable(Interface& interface)
 
         interface.value_iterator_type = move(first_type);
     }
+
+    if (interface.set_entry_type.has_value())
+        report_parsing_error("Interfaces with an iterable declaration must not have a setlike declaration."sv, filename, input, lexer.tell());
+
+    assert_specific('>');
+    assert_specific(';');
+}
+
+void Parser::parse_setlike(Interface& interface, bool is_readonly)
+{
+    if (interface.supports_indexed_properties())
+        report_parsing_error("Interfaces with a setlike declaration must not supported indexed properties."sv, filename, input, lexer.tell());
+
+    if (interface.value_iterator_type.has_value() || interface.pair_iterator_types.has_value())
+        report_parsing_error("Interfaces with a setlike declaration must not must not be iterable."sv, filename, input, lexer.tell());
+
+    assert_string("setlike"sv);
+    assert_specific('<');
+
+    interface.set_entry_type = parse_type();
+    interface.is_set_readonly = is_readonly;
+
     assert_specific('>');
     assert_specific(';');
 }
@@ -625,6 +650,12 @@ void Parser::parse_interface(Interface& interface)
             continue;
         }
 
+        if (lexer.next_is("setlike")) {
+            bool is_readonly = false;
+            parse_setlike(interface, is_readonly);
+            continue;
+        }
+
         if (lexer.next_is("inherit") || lexer.next_is("readonly") || lexer.next_is("attribute")) {
             parse_attribute(extended_attributes, interface);
             continue;

+ 1 - 0
Userland/Libraries/LibIDL/IDLParser.h

@@ -58,6 +58,7 @@ private:
     void parse_deleter(HashMap<ByteString, ByteString>& extended_attributes, Interface&);
     void parse_stringifier(HashMap<ByteString, ByteString>& extended_attributes, Interface&);
     void parse_iterable(Interface&);
+    void parse_setlike(Interface&, bool is_readonly);
     Function parse_function(HashMap<ByteString, ByteString>& extended_attributes, Interface&, IsStatic is_static = IsStatic::No, IsSpecialOperation is_special_operation = IsSpecialOperation::No);
     Vector<Parameter> parse_parameters();
     NonnullRefPtr<Type const> parse_type();

+ 2 - 0
Userland/Libraries/LibIDL/Types.h

@@ -281,6 +281,8 @@ public:
 
     Optional<NonnullRefPtr<Type const>> value_iterator_type;
     Optional<Tuple<NonnullRefPtr<Type const>, NonnullRefPtr<Type const>>> pair_iterator_types;
+    Optional<NonnullRefPtr<Type const>> set_entry_type;
+    bool is_set_readonly { false };
 
     Optional<Function> named_property_getter;
     Optional<Function> named_property_setter;