LibIDL+Meta: Add support for WebIDL maplike declarations on interfaces
Currently we only support string keys and numeric sequence values. This is heavily based on the implementation of `setlike`.
This commit is contained in:
parent
6294fb1f7a
commit
3f8da74e63
4 changed files with 292 additions and 1 deletions
|
|
@ -349,6 +349,8 @@ void Parser::parse_attribute(HashMap<ByteString, ByteString>& extended_attribute
|
|||
consume_whitespace();
|
||||
else if (lexer.consume_specific("setlike"sv) && !inherit)
|
||||
parse_setlike(interface, readonly);
|
||||
else if (lexer.consume_specific("maplike"sv) && !inherit)
|
||||
parse_maplike(interface, readonly);
|
||||
else
|
||||
report_parsing_error("expected 'attribute'"sv, filename, input, lexer.tell());
|
||||
|
||||
|
|
@ -583,6 +585,9 @@ void Parser::parse_setlike(Interface& interface, bool is_readonly)
|
|||
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());
|
||||
|
||||
if (interface.map_key_type.has_value())
|
||||
report_parsing_error("Interfaces with a setlike declaration must not have a maplike declaration."sv, filename, input, lexer.tell());
|
||||
|
||||
assert_string("setlike"sv);
|
||||
assert_specific('<');
|
||||
|
||||
|
|
@ -593,6 +598,31 @@ void Parser::parse_setlike(Interface& interface, bool is_readonly)
|
|||
assert_specific(';');
|
||||
}
|
||||
|
||||
void Parser::parse_maplike(Interface& interface, bool is_readonly)
|
||||
{
|
||||
if (interface.supports_indexed_properties())
|
||||
report_parsing_error("Interfaces with a maplike declaration must not support 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 maplike declaration must not must not be iterable."sv, filename, input, lexer.tell());
|
||||
|
||||
if (interface.set_entry_type.has_value())
|
||||
report_parsing_error("Interfaces with a maplike declaration must not have a setlike declaration."sv, filename, input, lexer.tell());
|
||||
|
||||
assert_string("maplike"sv);
|
||||
assert_specific('<');
|
||||
|
||||
interface.map_key_type = parse_type();
|
||||
consume_whitespace();
|
||||
assert_specific(',');
|
||||
consume_whitespace();
|
||||
interface.map_value_type = parse_type();
|
||||
interface.is_map_readonly = is_readonly;
|
||||
|
||||
assert_specific('>');
|
||||
assert_specific(';');
|
||||
}
|
||||
|
||||
void Parser::parse_getter(HashMap<ByteString, ByteString>& extended_attributes, Interface& interface)
|
||||
{
|
||||
assert_string("getter"sv);
|
||||
|
|
@ -759,6 +789,12 @@ void Parser::parse_interface(Interface& interface)
|
|||
continue;
|
||||
}
|
||||
|
||||
if (lexer.next_is("maplike"sv)) {
|
||||
bool is_readonly = false;
|
||||
parse_maplike(interface, is_readonly);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lexer.next_is("inherit"sv) || lexer.next_is("readonly"sv) || lexer.next_is("attribute"sv)) {
|
||||
parse_attribute(extended_attributes, interface);
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ private:
|
|||
void parse_iterable(Interface&);
|
||||
void parse_async_iterable(Interface&);
|
||||
void parse_setlike(Interface&, bool is_readonly);
|
||||
void parse_maplike(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();
|
||||
|
|
|
|||
|
|
@ -297,6 +297,10 @@ public:
|
|||
Optional<NonnullRefPtr<Type const>> set_entry_type;
|
||||
bool is_set_readonly { false };
|
||||
|
||||
Optional<NonnullRefPtr<Type const>> map_key_type;
|
||||
Optional<NonnullRefPtr<Type const>> map_value_type;
|
||||
bool is_map_readonly { false };
|
||||
|
||||
Optional<Function> named_property_getter;
|
||||
Optional<Function> named_property_setter;
|
||||
|
||||
|
|
|
|||
|
|
@ -3215,6 +3215,29 @@ static void generate_prototype_or_global_mixin_declarations(IDL::Interface const
|
|||
}
|
||||
}
|
||||
|
||||
if (interface.map_key_type.has_value()) {
|
||||
auto maplike_generator = generator.fork();
|
||||
|
||||
maplike_generator.append(R"~~~(
|
||||
JS_DECLARE_NATIVE_FUNCTION(get_size);
|
||||
JS_DECLARE_NATIVE_FUNCTION(entries);
|
||||
JS_DECLARE_NATIVE_FUNCTION(keys);
|
||||
JS_DECLARE_NATIVE_FUNCTION(values);
|
||||
JS_DECLARE_NATIVE_FUNCTION(for_each);
|
||||
JS_DECLARE_NATIVE_FUNCTION(get);
|
||||
JS_DECLARE_NATIVE_FUNCTION(has);
|
||||
)~~~");
|
||||
|
||||
if (!interface.overload_sets.contains("set"sv) && !interface.is_map_readonly)
|
||||
maplike_generator.appendln(" JS_DECLARE_NATIVE_FUNCTION(set);");
|
||||
|
||||
if (!interface.overload_sets.contains("delete"sv) && !interface.is_map_readonly)
|
||||
maplike_generator.appendln(" JS_DECLARE_NATIVE_FUNCTION(delete_);");
|
||||
|
||||
if (!interface.overload_sets.contains("clear"sv) && !interface.is_map_readonly)
|
||||
maplike_generator.appendln(" JS_DECLARE_NATIVE_FUNCTION(clear);");
|
||||
}
|
||||
|
||||
for (auto& attribute : interface.attributes) {
|
||||
if (attribute.extended_attributes.contains("FIXME"))
|
||||
continue;
|
||||
|
|
@ -3951,6 +3974,30 @@ void @class_name@::initialize(JS::Realm& realm)
|
|||
}
|
||||
}
|
||||
|
||||
if (interface.map_key_type.has_value() && generate_unforgeables == GenerateUnforgeables::No) {
|
||||
auto maplike_generator = generator.fork();
|
||||
|
||||
maplike_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);
|
||||
@define_direct_property@(vm.well_known_symbol_iterator(), get_without_side_effects(vm.names.entries), JS::Attribute::Configurable | JS::Attribute::Writable);
|
||||
@define_native_function@(realm, vm.names.keys, keys, 0, default_attributes);
|
||||
@define_native_function@(realm, vm.names.values, values, 0, default_attributes);
|
||||
@define_native_function@(realm, vm.names.forEach, for_each, 1, default_attributes);
|
||||
@define_native_function@(realm, vm.names.get, get, 1, default_attributes);
|
||||
@define_native_function@(realm, vm.names.has, has, 1, default_attributes);
|
||||
)~~~");
|
||||
|
||||
if (!interface.overload_sets.contains("set"sv) && !interface.is_map_readonly)
|
||||
maplike_generator.appendln(" @define_native_function@(realm, vm.names.set, set, 2, default_attributes);");
|
||||
|
||||
if (!interface.overload_sets.contains("delete"sv) && !interface.is_map_readonly)
|
||||
maplike_generator.appendln(" @define_native_function@(realm, vm.names.delete_, delete_, 1, default_attributes);");
|
||||
|
||||
if (!interface.overload_sets.contains("clear"sv) && !interface.is_map_readonly)
|
||||
maplike_generator.appendln(" @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);
|
||||
|
|
@ -4003,7 +4050,7 @@ static void generate_prototype_or_global_mixin_definitions(IDL::Interface const&
|
|||
generator.set("iterator_name", ByteString::formatted("{}Iterator", interface.name));
|
||||
}
|
||||
|
||||
if (!interface.attributes.is_empty() || !interface.functions.is_empty() || interface.has_stringifier || interface.set_entry_type.has_value()) {
|
||||
if (!interface.attributes.is_empty() || !interface.functions.is_empty() || interface.has_stringifier || interface.set_entry_type.has_value() || interface.map_key_type.has_value()) {
|
||||
generator.append(R"~~~(
|
||||
[[maybe_unused]] static JS::ThrowCompletionOr<@fully_qualified_name@*> impl_from(JS::VM& vm)
|
||||
{
|
||||
|
|
@ -4988,6 +5035,209 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::clear)
|
|||
}
|
||||
}
|
||||
|
||||
if (interface.map_key_type.has_value()) {
|
||||
auto maplike_generator = generator.fork();
|
||||
|
||||
if (interface.map_key_type.value()->is_string()) {
|
||||
maplike_generator.set("key_arg_converted_to_idl_type", "JS::PrimitiveString::create(vm, TRY(key_arg.to_string(vm)));");
|
||||
} else {
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (interface.map_value_type.value()->is_sequence() && interface.map_value_type.value()->as_parameterized().parameters().at(0)->is_numeric()) {
|
||||
// FIXME: We should convert rather than just fail if we have the wrong type.
|
||||
maplike_generator.set("value_arg_converted_to_idl_type", R"~~~([&](){
|
||||
if (!value_arg.is_object() || !is<JS::Array>(value_arg.as_object())) {
|
||||
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "Array");
|
||||
}
|
||||
|
||||
for (auto const& item : as<JS::Array>(value_arg.as_object())) {
|
||||
if (!item.is_numeric()) {
|
||||
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "Number");
|
||||
}
|
||||
}
|
||||
|
||||
return value_arg;
|
||||
}();
|
||||
)~~~");
|
||||
} else {
|
||||
TODO();
|
||||
}
|
||||
|
||||
maplike_generator.append(R"~~~(
|
||||
// https://webidl.spec.whatwg.org/#js-map-size
|
||||
JS_DEFINE_NATIVE_FUNCTION(@class_name@::get_size)
|
||||
{
|
||||
WebIDL::log_trace(vm, "@class_name@::size");
|
||||
auto* impl = TRY(impl_from(vm));
|
||||
|
||||
GC::Ref<JS::Map> map = impl->map_entries();
|
||||
|
||||
return map->map_size();
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#js-map-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));
|
||||
|
||||
GC::Ref<JS::Map> map = impl->map_entries();
|
||||
|
||||
return TRY(throw_dom_exception_if_needed(vm, [&] { return JS::MapIterator::create(realm, *map, Object::PropertyKind::KeyAndValue); }));
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#js-map-keys
|
||||
JS_DEFINE_NATIVE_FUNCTION(@class_name@::keys)
|
||||
{
|
||||
WebIDL::log_trace(vm, "@class_name@::keys");
|
||||
auto& realm = *vm.current_realm();
|
||||
auto* impl = TRY(impl_from(vm));
|
||||
|
||||
GC::Ref<JS::Map> map = impl->map_entries();
|
||||
|
||||
return TRY(throw_dom_exception_if_needed(vm, [&] { return JS::MapIterator::create(realm, *map, Object::PropertyKind::Key); }));
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#js-map-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));
|
||||
|
||||
GC::Ref<JS::Map> map = impl->map_entries();
|
||||
|
||||
return TRY(throw_dom_exception_if_needed(vm, [&] { return JS::MapIterator::create(realm, *map, Object::PropertyKind::Value); }));
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#js-map-forEach
|
||||
JS_DEFINE_NATIVE_FUNCTION(@class_name@::for_each)
|
||||
{
|
||||
WebIDL::log_trace(vm, "@class_name@::for_each");
|
||||
auto* impl = TRY(impl_from(vm));
|
||||
|
||||
GC::Ref<JS::Map> map = impl->map_entries();
|
||||
|
||||
auto callback = vm.argument(0);
|
||||
if (!callback.is_function())
|
||||
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, callback);
|
||||
|
||||
for (auto& entry : *map)
|
||||
TRY(JS::call(vm, callback.as_function(), vm.argument(1), entry.key, entry.value, impl));
|
||||
|
||||
return JS::js_undefined();
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#js-map-get
|
||||
JS_DEFINE_NATIVE_FUNCTION(@class_name@::get)
|
||||
{
|
||||
WebIDL::log_trace(vm, "@class_name@::get");
|
||||
auto* impl = TRY(impl_from(vm));
|
||||
|
||||
GC::Ref<JS::Map> map = impl->map_entries();
|
||||
|
||||
auto key_arg = vm.argument(0);
|
||||
auto key = @key_arg_converted_to_idl_type@
|
||||
|
||||
// FIXME: If key is -0, set key to +0.
|
||||
// What? Which interfaces have a number as their map key type?
|
||||
|
||||
auto result = map->map_get(key);
|
||||
|
||||
if (!result.has_value())
|
||||
return JS::js_undefined();
|
||||
|
||||
return result.release_value();
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#js-map-has
|
||||
JS_DEFINE_NATIVE_FUNCTION(@class_name@::has)
|
||||
{
|
||||
WebIDL::log_trace(vm, "@class_name@::has");
|
||||
auto* impl = TRY(impl_from(vm));
|
||||
|
||||
GC::Ref<JS::Map> map = impl->map_entries();
|
||||
|
||||
auto key_arg = vm.argument(0);
|
||||
auto key = @key_arg_converted_to_idl_type@
|
||||
|
||||
// FIXME: If key is -0, set key to +0.
|
||||
// What? Which interfaces have a number as their map key type?
|
||||
|
||||
return map->map_has(key);
|
||||
}
|
||||
)~~~");
|
||||
|
||||
if (!interface.overload_sets.contains("set"sv) && !interface.is_map_readonly) {
|
||||
maplike_generator.append(R"~~~(
|
||||
// https://webidl.spec.whatwg.org/#js-map-set
|
||||
JS_DEFINE_NATIVE_FUNCTION(@class_name@::set)
|
||||
{
|
||||
WebIDL::log_trace(vm, "@class_name@::set");
|
||||
auto* impl = TRY(impl_from(vm));
|
||||
|
||||
GC::Ref<JS::Map> map = impl->map_entries();
|
||||
|
||||
auto key_arg = vm.argument(0);
|
||||
auto key = @key_arg_converted_to_idl_type@
|
||||
|
||||
// FIXME: If value is -0, set value to +0.
|
||||
// What? Which interfaces have a number as their set type?
|
||||
|
||||
auto value_arg = vm.argument(1);
|
||||
auto value = @value_arg_converted_to_idl_type@
|
||||
|
||||
map->map_set(key, value);
|
||||
impl->on_map_modified_from_js({});
|
||||
|
||||
return impl;
|
||||
}
|
||||
)~~~");
|
||||
}
|
||||
if (!interface.overload_sets.contains("delete"sv) && !interface.is_map_readonly) {
|
||||
maplike_generator.append(R"~~~(
|
||||
// https://webidl.spec.whatwg.org/#js-map-delete
|
||||
JS_DEFINE_NATIVE_FUNCTION(@class_name@::delete_)
|
||||
{
|
||||
WebIDL::log_trace(vm, "@class_name@::delete_");
|
||||
auto* impl = TRY(impl_from(vm));
|
||||
|
||||
GC::Ref<JS::Map> map = impl->map_entries();
|
||||
|
||||
auto key_arg = vm.argument(0);
|
||||
auto key = @key_arg_converted_to_idl_type@
|
||||
|
||||
// FIXME: If key is -0, set key to +0.
|
||||
// What? Which interfaces have a number as their map key type?
|
||||
|
||||
auto result = map->map_remove(key);
|
||||
impl->on_map_modified_from_js({});
|
||||
|
||||
return result;
|
||||
}
|
||||
)~~~");
|
||||
}
|
||||
if (!interface.overload_sets.contains("clear"sv) && !interface.is_map_readonly) {
|
||||
maplike_generator.append(R"~~~(
|
||||
// https://webidl.spec.whatwg.org/#js-map-clear
|
||||
JS_DEFINE_NATIVE_FUNCTION(@class_name@::clear)
|
||||
{
|
||||
WebIDL::log_trace(vm, "@class_name@::clear");
|
||||
auto* impl = TRY(impl_from(vm));
|
||||
|
||||
GC::Ref<JS::Map> map = impl->map_entries();
|
||||
|
||||
map->map_clear();
|
||||
impl->on_map_modified_from_js({});
|
||||
|
||||
return JS::js_undefined();
|
||||
}
|
||||
)~~~");
|
||||
}
|
||||
}
|
||||
|
||||
generate_dictionaries(generator, interface);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue