Meta: Add support for enumerations to the IDL compiler

This commit is contained in:
Ali Mohammad Pur 2022-02-15 13:10:08 +03:30 committed by Ali Mohammad Pur
parent 4f2d898a51
commit ce6adf25e5
Notes: sideshowbarker 2024-07-17 18:42:30 +09:00
2 changed files with 287 additions and 112 deletions

View file

@ -33,6 +33,33 @@ static String make_input_acceptable_cpp(String const& input)
return input.replace("-", "_"); return input.replace("-", "_");
} }
static String convert_enumeration_value_to_cpp_enum_member(String const& value, HashTable<String>& names_already_seen)
{
StringBuilder builder;
GenericLexer lexer { value };
while (!lexer.is_eof()) {
lexer.ignore_while([](auto c) { return isspace(c) || c == '-' || c == '_'; });
auto word = lexer.consume_while([](auto c) { return isalnum(c); });
if (!word.is_empty()) {
builder.append(word.to_titlecase_string());
} else {
auto non_alnum_string = lexer.consume_while([](auto c) { return !isalnum(c); });
if (!non_alnum_string.is_empty())
builder.append("_");
}
}
if (builder.is_empty())
builder.append("Empty");
while (names_already_seen.contains(builder.string_view()))
builder.append('_');
names_already_seen.set(builder.string_view());
return builder.build();
}
[[noreturn]] static void report_parsing_error(StringView message, StringView filename, StringView input, size_t offset) [[noreturn]] static void report_parsing_error(StringView message, StringView filename, StringView input, size_t offset)
{ {
// FIXME: Spaghetti code ahead. // FIXME: Spaghetti code ahead.
@ -291,6 +318,15 @@ struct Dictionary {
Vector<DictionaryMember> members; Vector<DictionaryMember> members;
}; };
struct Enumeration {
HashTable<String> values;
HashMap<String, String> translated_cpp_names;
String first_member;
bool is_original_definition { true };
};
struct Interface;
struct ParameterizedType : public Type { struct ParameterizedType : public Type {
ParameterizedType() = default; ParameterizedType() = default;
@ -304,7 +340,7 @@ struct ParameterizedType : public Type {
NonnullRefPtrVector<Type> parameters; NonnullRefPtrVector<Type> parameters;
void generate_sequence_from_iterable(SourceGenerator& generator, String const& cpp_name, String const& iterable_cpp_name, String const& iterator_method_cpp_name, HashMap<String, Dictionary> const& dictionaries, size_t recursion_depth) const; void generate_sequence_from_iterable(SourceGenerator& generator, String const& cpp_name, String const& iterable_cpp_name, String const& iterator_method_cpp_name, IDL::Interface const&, size_t recursion_depth) const;
}; };
struct Interface { struct Interface {
@ -334,6 +370,7 @@ struct Interface {
Optional<Function> named_property_deleter; Optional<Function> named_property_deleter;
HashMap<String, Dictionary> dictionaries; HashMap<String, Dictionary> dictionaries;
HashMap<String, Enumeration> enumerations;
// Added for convenience after parsing // Added for convenience after parsing
String wrapper_class; String wrapper_class;
@ -760,6 +797,141 @@ static NonnullOwnPtr<Interface> parse_interface(StringView filename, StringView
} }
}; };
auto parse_non_interface_entities = [&](bool allow_interface) {
auto parse_dictionary = [&] {
assert_string("dictionary");
consume_whitespace();
Dictionary dictionary {};
auto name = lexer.consume_until([](auto ch) { return isspace(ch); });
consume_whitespace();
if (lexer.consume_specific(':')) {
consume_whitespace();
dictionary.parent_name = lexer.consume_until([](auto ch) { return isspace(ch); });
consume_whitespace();
}
assert_specific('{');
for (;;) {
consume_whitespace();
if (lexer.consume_specific('}')) {
consume_whitespace();
assert_specific(';');
break;
}
bool required = false;
HashMap<String, String> extended_attributes;
if (lexer.consume_specific("required")) {
required = true;
consume_whitespace();
if (lexer.consume_specific('['))
extended_attributes = parse_extended_attributes();
}
auto type = parse_type();
consume_whitespace();
auto name = lexer.consume_until([](auto ch) { return isspace(ch) || ch == ';'; });
consume_whitespace();
Optional<StringView> default_value;
if (lexer.consume_specific('=')) {
VERIFY(!required);
consume_whitespace();
default_value = lexer.consume_until([](auto ch) { return isspace(ch) || ch == ';'; });
consume_whitespace();
}
assert_specific(';');
DictionaryMember member {
required,
move(type),
name,
move(extended_attributes),
Optional<String>(move(default_value)),
};
dictionary.members.append(move(member));
}
// dictionary members need to be evaluated in lexicographical order
quick_sort(dictionary.members, [&](auto& one, auto& two) {
return one.name < two.name;
});
interface->dictionaries.set(name, move(dictionary));
consume_whitespace();
};
auto parse_enumeration = [&] {
assert_string("enum");
consume_whitespace();
Enumeration enumeration {};
auto name = lexer.consume_until([](auto ch) { return isspace(ch); });
consume_whitespace();
assert_specific('{');
bool first = true;
for (; !lexer.is_eof();) {
consume_whitespace();
if (lexer.next_is('}'))
break;
if (!first) {
assert_specific(',');
consume_whitespace();
}
assert_specific('"');
auto string = lexer.consume_until('"');
assert_specific('"');
consume_whitespace();
if (enumeration.values.contains(string))
report_parsing_error(String::formatted("Enumeration {} contains duplicate member '{}'", name, string), filename, input, lexer.tell());
else
enumeration.values.set(string);
if (first)
enumeration.first_member = move(string);
first = false;
}
consume_whitespace();
assert_specific('}');
assert_specific(';');
HashTable<String> names_already_seen;
for (auto& entry : enumeration.values)
enumeration.translated_cpp_names.set(entry, convert_enumeration_value_to_cpp_enum_member(entry, names_already_seen));
interface->enumerations.set(name, move(enumeration));
consume_whitespace();
};
while (!lexer.is_eof()) {
if (lexer.next_is("dictionary"))
parse_dictionary();
else if (lexer.next_is("enum"))
parse_enumeration();
else if ((allow_interface && !lexer.next_is("interface")) || !allow_interface)
report_parsing_error("expected 'enum' or 'dictionary'", filename, input, lexer.tell());
else
break;
}
};
parse_non_interface_entities(true);
if (lexer.consume_specific("interface")) { if (lexer.consume_specific("interface")) {
consume_whitespace(); consume_whitespace();
interface->name = lexer.consume_until([](auto ch) { return isspace(ch); }); interface->name = lexer.consume_until([](auto ch) { return isspace(ch); });
@ -839,80 +1011,18 @@ static NonnullOwnPtr<Interface> parse_interface(StringView filename, StringView
consume_whitespace(); consume_whitespace();
} }
while (!lexer.is_eof()) { parse_non_interface_entities(false);
assert_string("dictionary");
consume_whitespace();
Dictionary dictionary {}; for (auto& import : imports) {
// FIXME: Instead of copying every imported entity into the current interface, query imports directly
auto name = lexer.consume_until([](auto ch) { return isspace(ch); });
consume_whitespace();
if (lexer.consume_specific(':')) {
consume_whitespace();
dictionary.parent_name = lexer.consume_until([](auto ch) { return isspace(ch); });
consume_whitespace();
}
assert_specific('{');
for (;;) {
consume_whitespace();
if (lexer.consume_specific('}')) {
consume_whitespace();
assert_specific(';');
break;
}
bool required = false;
HashMap<String, String> extended_attributes;
if (lexer.consume_specific("required")) {
required = true;
consume_whitespace();
if (lexer.consume_specific('['))
extended_attributes = parse_extended_attributes();
}
auto type = parse_type();
consume_whitespace();
auto name = lexer.consume_until([](auto ch) { return isspace(ch) || ch == ';'; });
consume_whitespace();
Optional<StringView> default_value;
if (lexer.consume_specific('=')) {
VERIFY(!required);
consume_whitespace();
default_value = lexer.consume_until([](auto ch) { return isspace(ch) || ch == ';'; });
consume_whitespace();
}
assert_specific(';');
DictionaryMember member {
required,
move(type),
name,
move(extended_attributes),
Optional<String>(move(default_value)),
};
dictionary.members.append(move(member));
}
// dictionary members need to be evaluated in lexicographical order
quick_sort(dictionary.members, [&](auto& one, auto& two) {
return one.name < two.name;
});
interface->dictionaries.set(name, move(dictionary));
consume_whitespace();
}
for (auto& import : imports) { // FIXME: Instead of copying every imported dictionary into the current interface, query imports directly
for (auto& dictionary : import.dictionaries) for (auto& dictionary : import.dictionaries)
interface->dictionaries.set(dictionary.key, dictionary.value); interface->dictionaries.set(dictionary.key, dictionary.value);
for (auto& enumeration : import.enumerations) {
auto enumeration_copy = enumeration.value;
enumeration_copy.is_original_definition = false;
interface->enumerations.set(enumeration.key, move(enumeration_copy));
}
} }
return interface; return interface;
@ -1187,7 +1297,7 @@ static bool should_emit_wrapper_factory(IDL::Interface const& interface)
} }
template<typename ParameterType> template<typename ParameterType>
static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter, String const& js_name, String const& js_suffix, String const& cpp_name, HashMap<String, IDL::Dictionary> const& dictionaries, bool legacy_null_to_empty_string = false, bool optional = false, Optional<String> optional_default_value = {}, bool variadic = false, size_t recursion_depth = 0) static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter, String const& js_name, String const& js_suffix, String const& cpp_name, IDL::Interface const& interface, bool legacy_null_to_empty_string = false, bool optional = false, Optional<String> optional_default_value = {}, bool variadic = false, size_t recursion_depth = 0, bool used_as_argument = false)
{ {
auto scoped_generator = generator.fork(); auto scoped_generator = generator.fork();
auto acceptable_cpp_name = make_input_acceptable_cpp(cpp_name); auto acceptable_cpp_name = make_input_acceptable_cpp(cpp_name);
@ -1426,7 +1536,35 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
} }
} }
} }
} else if (dictionaries.contains(parameter.type->name)) { } else if (interface.enumerations.contains(parameter.type->name)) {
auto enum_generator = scoped_generator.fork();
auto& enumeration = interface.enumerations.find(parameter.type->name)->value;
enum_generator.set("enum.default.cpp_value", *enumeration.translated_cpp_names.get(optional_default_value.value_or(enumeration.first_member)));
enum_generator.set("js_name.as_string", String::formatted("{}{}_string", enum_generator.get("js_name"), enum_generator.get("js_suffix")));
enum_generator.append(R"~~~(
@parameter.type.name@ @cpp_name@ { @parameter.type.name@::@enum.default.cpp_value@ };
auto @js_name.as_string@ = TRY(@js_name@@js_suffix@.to_string(global_object));
)~~~");
auto first = true;
for (auto& it : enumeration.translated_cpp_names) {
enum_generator.set("enum.alt.name", it.key);
enum_generator.set("enum.alt.value", it.value);
enum_generator.set("else", first ? "" : "else ");
first = false;
enum_generator.append(R"~~~(
@else@if (@js_name.as_string@ == "@enum.alt.name@"sv)
@cpp_name@ = @parameter.type.name@::@enum.alt.value@;
)~~~");
}
if (used_as_argument) {
enum_generator.append(R"~~~(
@else@
return vm.throw_completion<JS::TypeError>(global_object, JS::ErrorType::InvalidEnumerationValue, @js_name.as_string@, "@parameter.type.name@");
)~~~");
}
} else if (interface.dictionaries.contains(parameter.type->name)) {
if (optional_default_value.has_value() && optional_default_value != "{}") if (optional_default_value.has_value() && optional_default_value != "{}")
TODO(); TODO();
auto dictionary_generator = scoped_generator.fork(); auto dictionary_generator = scoped_generator.fork();
@ -1436,7 +1574,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
@parameter.type.name@ @cpp_name@ {}; @parameter.type.name@ @cpp_name@ {};
)~~~"); )~~~");
auto* current_dictionary = &dictionaries.find(parameter.type->name)->value; auto* current_dictionary = &interface.dictionaries.find(parameter.type->name)->value;
while (true) { while (true) {
for (auto& member : current_dictionary->members) { for (auto& member : current_dictionary->members) {
dictionary_generator.set("member_key", member.name); dictionary_generator.set("member_key", member.name);
@ -1459,15 +1597,15 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
auto member_value_name = String::formatted("{}_value", member_js_name); auto member_value_name = String::formatted("{}_value", member_js_name);
dictionary_generator.set("member_value_name", member_value_name); dictionary_generator.set("member_value_name", member_value_name);
generate_to_cpp(dictionary_generator, member, member_js_name, "", member_value_name, dictionaries, member.extended_attributes.contains("LegacyNullToEmptyString"), !member.required, member.default_value); generate_to_cpp(dictionary_generator, member, member_js_name, "", member_value_name, interface, member.extended_attributes.contains("LegacyNullToEmptyString"), !member.required, member.default_value);
dictionary_generator.append(R"~~~( dictionary_generator.append(R"~~~(
@cpp_name@.@member_name@ = @member_value_name@; @cpp_name@.@member_name@ = @member_value_name@;
)~~~"); )~~~");
} }
if (current_dictionary->parent_name.is_null()) if (current_dictionary->parent_name.is_null())
break; break;
VERIFY(dictionaries.contains(current_dictionary->parent_name)); VERIFY(interface.dictionaries.contains(current_dictionary->parent_name));
current_dictionary = &dictionaries.find(current_dictionary->parent_name)->value; current_dictionary = &interface.dictionaries.find(current_dictionary->parent_name)->value;
} }
} else if (parameter.type->name == "sequence") { } else if (parameter.type->name == "sequence") {
// https://webidl.spec.whatwg.org/#es-sequence // https://webidl.spec.whatwg.org/#es-sequence
@ -1491,7 +1629,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
return vm.throw_completion<JS::TypeError>(global_object, JS::ErrorType::NotIterable, @js_name@@js_suffix@.to_string_without_side_effects()); return vm.throw_completion<JS::TypeError>(global_object, JS::ErrorType::NotIterable, @js_name@@js_suffix@.to_string_without_side_effects());
)~~~"); )~~~");
parameterized_type.generate_sequence_from_iterable(sequence_generator, acceptable_cpp_name, String::formatted("{}{}", js_name, js_suffix), String::formatted("iterator_method{}", recursion_depth), dictionaries, recursion_depth + 1); parameterized_type.generate_sequence_from_iterable(sequence_generator, acceptable_cpp_name, String::formatted("{}{}", js_name, js_suffix), String::formatted("iterator_method{}", recursion_depth), interface, recursion_depth + 1);
} else if (parameter.type->name == "record") { } else if (parameter.type->name == "record") {
// https://webidl.spec.whatwg.org/#es-record // https://webidl.spec.whatwg.org/#es-record
@ -1547,7 +1685,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
)~~~"); )~~~");
IDL::Parameter key_parameter { .type = parameterized_type.parameters[0], .name = acceptable_cpp_name, .optional_default_value = {}, .extended_attributes = {} }; IDL::Parameter key_parameter { .type = parameterized_type.parameters[0], .name = acceptable_cpp_name, .optional_default_value = {}, .extended_attributes = {} };
generate_to_cpp(record_generator, key_parameter, "key", String::number(recursion_depth), String::formatted("typed_key{}", recursion_depth), dictionaries, false, false, {}, false, recursion_depth + 1); generate_to_cpp(record_generator, key_parameter, "key", String::number(recursion_depth), String::formatted("typed_key{}", recursion_depth), interface, false, false, {}, false, recursion_depth + 1);
record_generator.append(R"~~~( record_generator.append(R"~~~(
auto value@recursion_depth@ = TRY(@js_name@@js_suffix@_object.get(property_key@recursion_depth@)); auto value@recursion_depth@ = TRY(@js_name@@js_suffix@_object.get(property_key@recursion_depth@));
@ -1555,7 +1693,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
// FIXME: Record value types should be TypeWithExtendedAttributes, which would allow us to get [LegacyNullToEmptyString] here. // FIXME: Record value types should be TypeWithExtendedAttributes, which would allow us to get [LegacyNullToEmptyString] here.
IDL::Parameter value_parameter { .type = parameterized_type.parameters[1], .name = acceptable_cpp_name, .optional_default_value = {}, .extended_attributes = {} }; IDL::Parameter value_parameter { .type = parameterized_type.parameters[1], .name = acceptable_cpp_name, .optional_default_value = {}, .extended_attributes = {} };
generate_to_cpp(record_generator, value_parameter, "value", String::number(recursion_depth), String::formatted("typed_value{}", recursion_depth), dictionaries, false, false, {}, false, recursion_depth + 1); generate_to_cpp(record_generator, value_parameter, "value", String::number(recursion_depth), String::formatted("typed_value{}", recursion_depth), interface, false, false, {}, false, recursion_depth + 1);
record_generator.append(R"~~~( record_generator.append(R"~~~(
@cpp_name@.set(typed_key@recursion_depth@, typed_value@recursion_depth@); @cpp_name@.set(typed_key@recursion_depth@, typed_value@recursion_depth@);
@ -1597,7 +1735,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
auto types = union_type.flattened_member_types(); auto types = union_type.flattened_member_types();
bool contains_dictionary_type = false; bool contains_dictionary_type = false;
for (auto& dictionary : dictionaries) { for (auto& dictionary : interface.dictionaries) {
for (auto& type : types) { for (auto& type : types) {
if (type.name == dictionary.key) { if (type.name == dictionary.key) {
contains_dictionary_type = true; contains_dictionary_type = true;
@ -1711,7 +1849,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
if (method) { if (method) {
)~~~"); )~~~");
sequence_type->generate_sequence_from_iterable(union_generator, acceptable_cpp_name, String::formatted("{}{}", js_name, js_suffix), "method", dictionaries, recursion_depth + 1); sequence_type->generate_sequence_from_iterable(union_generator, acceptable_cpp_name, String::formatted("{}{}", js_name, js_suffix), "method", interface, recursion_depth + 1);
union_generator.append(R"~~~( union_generator.append(R"~~~(
@ -1739,7 +1877,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
if (record_type) { if (record_type) {
IDL::Parameter record_parameter { .type = *record_type, .name = acceptable_cpp_name, .optional_default_value = {}, .extended_attributes = {} }; IDL::Parameter record_parameter { .type = *record_type, .name = acceptable_cpp_name, .optional_default_value = {}, .extended_attributes = {} };
generate_to_cpp(union_generator, record_parameter, js_name, js_suffix, "record_union_type"sv, dictionaries, false, false, {}, false, recursion_depth + 1); generate_to_cpp(union_generator, record_parameter, js_name, js_suffix, "record_union_type"sv, interface, false, false, {}, false, recursion_depth + 1);
union_generator.append(R"~~~( union_generator.append(R"~~~(
return record_union_type; return record_union_type;
@ -1794,7 +1932,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
// NOTE: generate_to_cpp doesn't use the parameter name. // NOTE: generate_to_cpp doesn't use the parameter name.
// NOTE: generate_to_cpp will use to_{u32,etc.} which uses to_number internally and will thus use TRY, but it cannot throw as we know we are dealing with a number. // NOTE: generate_to_cpp will use to_{u32,etc.} which uses to_number internally and will thus use TRY, but it cannot throw as we know we are dealing with a number.
IDL::Parameter parameter { .type = *numeric_type, .name = String::empty(), .optional_default_value = {}, .extended_attributes = {} }; IDL::Parameter parameter { .type = *numeric_type, .name = String::empty(), .optional_default_value = {}, .extended_attributes = {} };
generate_to_cpp(union_generator, parameter, js_name, js_suffix, String::formatted("{}{}_number", js_name, js_suffix), dictionaries, false, false, {}, false, recursion_depth + 1); generate_to_cpp(union_generator, parameter, js_name, js_suffix, String::formatted("{}{}_number", js_name, js_suffix), interface, false, false, {}, false, recursion_depth + 1);
union_generator.append(R"~~~( union_generator.append(R"~~~(
return @js_name@@js_suffix@_number; return @js_name@@js_suffix@_number;
@ -1859,7 +1997,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
// NOTE: generate_to_cpp doesn't use the parameter name. // NOTE: generate_to_cpp doesn't use the parameter name.
// NOTE: generate_to_cpp will use to_{u32,etc.} which uses to_number internally and will thus use TRY, but it cannot throw as we know we are dealing with a number. // NOTE: generate_to_cpp will use to_{u32,etc.} which uses to_number internally and will thus use TRY, but it cannot throw as we know we are dealing with a number.
IDL::Parameter parameter { .type = *numeric_type, .name = String::empty(), .optional_default_value = {}, .extended_attributes = {} }; IDL::Parameter parameter { .type = *numeric_type, .name = String::empty(), .optional_default_value = {}, .extended_attributes = {} };
generate_to_cpp(union_numeric_type_generator, parameter, "x", String::empty(), "x_number", dictionaries, false, false, {}, false, recursion_depth + 1); generate_to_cpp(union_numeric_type_generator, parameter, "x", String::empty(), "x_number", interface, false, false, {}, false, recursion_depth + 1);
union_numeric_type_generator.append(R"~~~( union_numeric_type_generator.append(R"~~~(
return x_number; return x_number;
@ -1870,7 +2008,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
// NOTE: generate_to_cpp doesn't use the parameter name. // NOTE: generate_to_cpp doesn't use the parameter name.
// NOTE: generate_to_cpp will use to_{u32,etc.} which uses to_number internally and will thus use TRY, but it cannot throw as we know we are dealing with a number. // NOTE: generate_to_cpp will use to_{u32,etc.} which uses to_number internally and will thus use TRY, but it cannot throw as we know we are dealing with a number.
IDL::Parameter parameter { .type = *numeric_type, .name = String::empty(), .optional_default_value = {}, .extended_attributes = {} }; IDL::Parameter parameter { .type = *numeric_type, .name = String::empty(), .optional_default_value = {}, .extended_attributes = {} };
generate_to_cpp(union_generator, parameter, js_name, js_suffix, String::formatted("{}{}_number", js_name, js_suffix), dictionaries, false, false, {}, false, recursion_depth + 1); generate_to_cpp(union_generator, parameter, js_name, js_suffix, String::formatted("{}{}_number", js_name, js_suffix), interface, false, false, {}, false, recursion_depth + 1);
union_generator.append(R"~~~( union_generator.append(R"~~~(
return @js_name@@js_suffix@_number; return @js_name@@js_suffix@_number;
@ -1960,7 +2098,7 @@ static void generate_argument_count_check(SourceGenerator& generator, FunctionTy
)~~~"); )~~~");
} }
static void generate_arguments(SourceGenerator& generator, Vector<IDL::Parameter> const& parameters, StringBuilder& arguments_builder, HashMap<String, IDL::Dictionary> const& dictionaries) static void generate_arguments(SourceGenerator& generator, Vector<IDL::Parameter> const& parameters, StringBuilder& arguments_builder, IDL::Interface const& interface)
{ {
auto arguments_generator = generator.fork(); auto arguments_generator = generator.fork();
@ -1977,7 +2115,7 @@ static void generate_arguments(SourceGenerator& generator, Vector<IDL::Parameter
} }
bool legacy_null_to_empty_string = parameter.extended_attributes.contains("LegacyNullToEmptyString"); bool legacy_null_to_empty_string = parameter.extended_attributes.contains("LegacyNullToEmptyString");
generate_to_cpp(generator, parameter, "arg", String::number(argument_index), parameter.name.to_snakecase(), dictionaries, legacy_null_to_empty_string, parameter.optional, parameter.optional_default_value, parameter.variadic); generate_to_cpp(generator, parameter, "arg", String::number(argument_index), parameter.name.to_snakecase(), interface, legacy_null_to_empty_string, parameter.optional, parameter.optional_default_value, parameter.variadic, 0, true);
++argument_index; ++argument_index;
} }
@ -1985,7 +2123,7 @@ static void generate_arguments(SourceGenerator& generator, Vector<IDL::Parameter
} }
// https://webidl.spec.whatwg.org/#create-sequence-from-iterable // https://webidl.spec.whatwg.org/#create-sequence-from-iterable
void IDL::ParameterizedType::generate_sequence_from_iterable(SourceGenerator& generator, String const& cpp_name, String const& iterable_cpp_name, String const& iterator_method_cpp_name, HashMap<String, IDL::Dictionary> const& dictionaries, size_t recursion_depth) const void IDL::ParameterizedType::generate_sequence_from_iterable(SourceGenerator& generator, String const& cpp_name, String const& iterable_cpp_name, String const& iterator_method_cpp_name, IDL::Interface const& interface, size_t recursion_depth) const
{ {
auto sequence_generator = generator.fork(); auto sequence_generator = generator.fork();
sequence_generator.set("cpp_name", cpp_name); sequence_generator.set("cpp_name", cpp_name);
@ -2031,7 +2169,7 @@ void IDL::ParameterizedType::generate_sequence_from_iterable(SourceGenerator& ge
// FIXME: Sequences types should be TypeWithExtendedAttributes, which would allow us to get [LegacyNullToEmptyString] here. // FIXME: Sequences types should be TypeWithExtendedAttributes, which would allow us to get [LegacyNullToEmptyString] here.
IDL::Parameter parameter { .type = parameters.first(), .name = iterable_cpp_name, .optional_default_value = {}, .extended_attributes = {} }; IDL::Parameter parameter { .type = parameters.first(), .name = iterable_cpp_name, .optional_default_value = {}, .extended_attributes = {} };
generate_to_cpp(sequence_generator, parameter, "next_item", String::number(recursion_depth), String::formatted("sequence_item{}", recursion_depth), dictionaries, false, false, {}, false, recursion_depth); generate_to_cpp(sequence_generator, parameter, "next_item", String::number(recursion_depth), String::formatted("sequence_item{}", recursion_depth), interface, false, false, {}, false, recursion_depth);
sequence_generator.append(R"~~~( sequence_generator.append(R"~~~(
@cpp_name@.append(sequence_item@recursion_depth@); @cpp_name@.append(sequence_item@recursion_depth@);
@ -2044,7 +2182,7 @@ enum class WrappingReference {
Yes, Yes,
}; };
static void generate_wrap_statement(SourceGenerator& generator, String const& value, IDL::Type const& type, StringView result_expression, WrappingReference wrapping_reference = WrappingReference::No, size_t recursion_depth = 0) static void generate_wrap_statement(SourceGenerator& generator, String const& value, IDL::Type const& type, IDL::Interface const& interface, StringView result_expression, WrappingReference wrapping_reference = WrappingReference::No, size_t recursion_depth = 0)
{ {
auto scoped_generator = generator.fork(); auto scoped_generator = generator.fork();
scoped_generator.set("value", value); scoped_generator.set("value", value);
@ -2090,7 +2228,7 @@ static void generate_wrap_statement(SourceGenerator& generator, String const& va
auto& element@recursion_depth@ = @value@.at(i@recursion_depth@); auto& element@recursion_depth@ = @value@.at(i@recursion_depth@);
)~~~"); )~~~");
generate_wrap_statement(scoped_generator, String::formatted("element{}", recursion_depth), sequence_generic_type.parameters.first(), String::formatted("auto wrapped_element{} =", recursion_depth), WrappingReference::Yes, recursion_depth + 1); generate_wrap_statement(scoped_generator, String::formatted("element{}", recursion_depth), sequence_generic_type.parameters.first(), interface, String::formatted("auto wrapped_element{} =", recursion_depth), WrappingReference::Yes, recursion_depth + 1);
scoped_generator.append(R"~~~( scoped_generator.append(R"~~~(
auto property_index@recursion_depth@ = JS::PropertyKey { i@recursion_depth@ }; auto property_index@recursion_depth@ = JS::PropertyKey { i@recursion_depth@ };
@ -2124,6 +2262,10 @@ static void generate_wrap_statement(SourceGenerator& generator, String const& va
)~~~"); )~~~");
} else if (is<IDL::UnionType>(type)) { } else if (is<IDL::UnionType>(type)) {
TODO(); TODO();
} else if (interface.enumerations.contains(type.name)) {
scoped_generator.append(R"~~~(
@result_expression@ JS::js_string(global_object.heap(), Bindings::idl_enum_to_string(@value@));
)~~~");
} else { } else {
if (wrapping_reference == WrappingReference::No) { if (wrapping_reference == WrappingReference::No) {
scoped_generator.append(R"~~~( scoped_generator.append(R"~~~(
@ -2148,22 +2290,22 @@ enum class StaticFunction {
Yes, Yes,
}; };
static void generate_return_statement(SourceGenerator& generator, IDL::Type const& return_type) static void generate_return_statement(SourceGenerator& generator, IDL::Type const& return_type, IDL::Interface const& interface)
{ {
return generate_wrap_statement(generator, "retval", return_type, "return"sv); return generate_wrap_statement(generator, "retval", return_type, interface, "return"sv);
} }
static void generate_variable_statement(SourceGenerator& generator, String const& variable_name, IDL::Type const& value_type, String const& value_name) static void generate_variable_statement(SourceGenerator& generator, String const& variable_name, IDL::Type const& value_type, String const& value_name, IDL::Interface const& interface)
{ {
auto variable_generator = generator.fork(); auto variable_generator = generator.fork();
variable_generator.set("variable_name", variable_name); variable_generator.set("variable_name", variable_name);
variable_generator.append(R"~~~( variable_generator.append(R"~~~(
JS::Value @variable_name@; JS::Value @variable_name@;
)~~~"); )~~~");
return generate_wrap_statement(generator, value_name, value_type, String::formatted("{} = ", variable_name)); return generate_wrap_statement(generator, value_name, value_type, interface, String::formatted("{} = ", variable_name));
} }
static void generate_function(SourceGenerator& generator, IDL::Function const& function, StaticFunction is_static_function, String const& class_name, String const& interface_fully_qualified_name, HashMap<String, IDL::Dictionary> const& dictionaries) static void generate_function(SourceGenerator& generator, IDL::Function const& function, StaticFunction is_static_function, String const& class_name, String const& interface_fully_qualified_name, IDL::Interface const& interface)
{ {
auto function_generator = generator.fork(); auto function_generator = generator.fork();
function_generator.set("class_name", class_name); function_generator.set("class_name", class_name);
@ -2192,7 +2334,7 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::@function.name:snakecase@)
generate_argument_count_check(generator, function); generate_argument_count_check(generator, function);
StringBuilder arguments_builder; StringBuilder arguments_builder;
generate_arguments(generator, function.parameters, arguments_builder, dictionaries); generate_arguments(generator, function.parameters, arguments_builder, interface);
function_generator.set(".arguments", arguments_builder.string_view()); function_generator.set(".arguments", arguments_builder.string_view());
if (is_static_function == StaticFunction::No) { if (is_static_function == StaticFunction::No) {
@ -2205,7 +2347,7 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::@function.name:snakecase@)
)~~~"); )~~~");
} }
generate_return_statement(generator, *function.return_type); generate_return_statement(generator, *function.return_type, interface);
function_generator.append(R"~~~( function_generator.append(R"~~~(
} }
@ -2351,6 +2493,38 @@ private:
}; };
)~~~"); )~~~");
for (auto& it : interface.enumerations) {
auto enum_generator = generator.fork();
enum_generator.set("enum.type.name", it.key);
enum_generator.append(R"~~~(
enum class @enum.type.name@ {
)~~~");
for (auto& entry : it.value.translated_cpp_names) {
enum_generator.set("enum.entry", entry.value);
enum_generator.append(R"~~~(
@enum.entry@,
)~~~");
}
enum_generator.append(R"~~~(
};
inline String idl_enum_to_string(@enum.type.name@ value) {
switch(value) {
)~~~");
for (auto& entry : it.value.translated_cpp_names) {
enum_generator.set("enum.entry", entry.value);
enum_generator.set("enum.string", entry.key);
enum_generator.append(R"~~~(
case @enum.type.name@::@enum.entry@: return "@enum.string@";
)~~~");
}
enum_generator.append(R"~~~(
default: return "<unknown>";
};
}
)~~~");
}
if (should_emit_wrapper_factory(interface)) { if (should_emit_wrapper_factory(interface)) {
generator.append(R"~~~( generator.append(R"~~~(
@wrapper_class@* wrap(JS::GlobalObject&, @fully_qualified_name@&); @wrapper_class@* wrap(JS::GlobalObject&, @fully_qualified_name@&);
@ -2503,10 +2677,10 @@ static JS::Value wrap_for_legacy_platform_object_get_own_property(JS::GlobalObje
)~~~"); )~~~");
if (interface.named_property_getter.has_value()) { if (interface.named_property_getter.has_value()) {
generate_return_statement(scoped_generator, *interface.named_property_getter->return_type); generate_return_statement(scoped_generator, *interface.named_property_getter->return_type, interface);
} else { } else {
VERIFY(interface.indexed_property_getter.has_value()); VERIFY(interface.indexed_property_getter.has_value());
generate_return_statement(scoped_generator, *interface.indexed_property_getter->return_type); generate_return_statement(scoped_generator, *interface.indexed_property_getter->return_type, interface);
} }
scoped_generator.append(R"~~~( scoped_generator.append(R"~~~(
@ -2765,7 +2939,7 @@ static JS::ThrowCompletionOr<void> invoke_named_property_setter(JS::GlobalObject
// 4. Let value be the result of converting V to an IDL value of type T. // 4. Let value be the result of converting V to an IDL value of type T.
// NOTE: This takes the last parameter as it's enforced that there's only two parameters. // NOTE: This takes the last parameter as it's enforced that there's only two parameters.
generate_to_cpp(scoped_generator, interface.named_property_setter->parameters.last(), "value", "", "converted_value", interface.dictionaries); generate_to_cpp(scoped_generator, interface.named_property_setter->parameters.last(), "value", "", "converted_value", interface);
// 5. If operation was defined without an identifier, then: // 5. If operation was defined without an identifier, then:
if (interface.named_property_setter->name.is_empty()) { if (interface.named_property_setter->name.is_empty()) {
@ -2816,7 +2990,7 @@ static JS::ThrowCompletionOr<void> invoke_indexed_property_setter(JS::GlobalObje
// 5. Let value be the result of converting V to an IDL value of type T. // 5. Let value be the result of converting V to an IDL value of type T.
// NOTE: This takes the last parameter as it's enforced that there's only two parameters. // NOTE: This takes the last parameter as it's enforced that there's only two parameters.
generate_to_cpp(scoped_generator, interface.named_property_setter->parameters.last(), "value", "", "converted_value", interface.dictionaries); generate_to_cpp(scoped_generator, interface.named_property_setter->parameters.last(), "value", "", "converted_value", interface);
// 6. If operation was defined without an identifier, then: // 6. If operation was defined without an identifier, then:
if (interface.indexed_property_setter->name.is_empty()) { if (interface.indexed_property_setter->name.is_empty()) {
@ -3371,7 +3545,7 @@ JS::ThrowCompletionOr<JS::Object*> @constructor_class@::construct(FunctionObject
generate_argument_count_check(generator, constructor); generate_argument_count_check(generator, constructor);
StringBuilder arguments_builder; StringBuilder arguments_builder;
generate_arguments(generator, constructor.parameters, arguments_builder, interface.dictionaries); generate_arguments(generator, constructor.parameters, arguments_builder, interface);
generator.set(".constructor_arguments", arguments_builder.string_view()); generator.set(".constructor_arguments", arguments_builder.string_view());
generator.append(R"~~~( generator.append(R"~~~(
@ -3433,7 +3607,7 @@ define_direct_property("@constant.name@", JS::Value((i32)@constant.value@), JS::
// Implementation: Static Functions // Implementation: Static Functions
for (auto& function : interface.static_functions) { for (auto& function : interface.static_functions) {
generate_function(generator, function, StaticFunction::Yes, interface.constructor_class, interface.fully_qualified_name, interface.dictionaries); generate_function(generator, function, StaticFunction::Yes, interface.constructor_class, interface.fully_qualified_name, interface);
} }
generator.append(R"~~~( generator.append(R"~~~(
@ -3910,7 +4084,7 @@ JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::@attribute.getter_callback@)
)~~~"); )~~~");
} }
generate_return_statement(generator, *attribute.type); generate_return_statement(generator, *attribute.type, interface);
attribute_generator.append(R"~~~( attribute_generator.append(R"~~~(
} }
@ -3925,7 +4099,7 @@ JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::@attribute.setter_callback@)
auto value = vm.argument(0); auto value = vm.argument(0);
)~~~"); )~~~");
generate_to_cpp(generator, attribute, "value", "", "cpp_value", interface.dictionaries, attribute.extended_attributes.contains("LegacyNullToEmptyString")); generate_to_cpp(generator, attribute, "value", "", "cpp_value", interface, attribute.extended_attributes.contains("LegacyNullToEmptyString"));
if (attribute.extended_attributes.contains("Reflect")) { if (attribute.extended_attributes.contains("Reflect")) {
if (attribute.type->name != "boolean") { if (attribute.type->name != "boolean") {
@ -3955,7 +4129,7 @@ JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::@attribute.setter_callback@)
// Implementation: Functions // Implementation: Functions
for (auto& function : interface.functions) { for (auto& function : interface.functions) {
generate_function(generator, function, StaticFunction::No, interface.prototype_class, interface.fully_qualified_name, interface.dictionaries); generate_function(generator, function, StaticFunction::No, interface.prototype_class, interface.fully_qualified_name, interface);
} }
if (interface.has_stringifier) { if (interface.has_stringifier) {
@ -4007,8 +4181,8 @@ JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::for_each)
auto this_value = vm.this_value(global_object); auto this_value = vm.this_value(global_object);
TRY(impl->for_each([&](auto key, auto value) -> JS::ThrowCompletionOr<void> { TRY(impl->for_each([&](auto key, auto value) -> JS::ThrowCompletionOr<void> {
)~~~"); )~~~");
generate_variable_statement(iterator_generator, "wrapped_key", interface.pair_iterator_types->get<0>(), "key"); generate_variable_statement(iterator_generator, "wrapped_key", interface.pair_iterator_types->get<0>(), "key", interface);
generate_variable_statement(iterator_generator, "wrapped_value", interface.pair_iterator_types->get<1>(), "value"); generate_variable_statement(iterator_generator, "wrapped_value", interface.pair_iterator_types->get<1>(), "value", interface);
iterator_generator.append(R"~~~( iterator_generator.append(R"~~~(
TRY(call(global_object, callback.as_function(), vm.argument(1), wrapped_value, wrapped_key, this_value)); TRY(call(global_object, callback.as_function(), vm.argument(1), wrapped_value, wrapped_key, this_value));
return {}; return {};

View file

@ -287,7 +287,8 @@
M(BadArgCountOne, "{}() needs one argument") \ M(BadArgCountOne, "{}() needs one argument") \
M(BadArgCountAtLeastOne, "{}() needs at least one argument") \ M(BadArgCountAtLeastOne, "{}() needs at least one argument") \
M(BadArgCountMany, "{}() needs {} arguments") \ M(BadArgCountMany, "{}() needs {} arguments") \
M(NotEnoughMemoryToAllocate, "Not enough memory to allocate {} bytes") M(NotEnoughMemoryToAllocate, "Not enough memory to allocate {} bytes") \
M(InvalidEnumerationValue, "Invalid value '{}' for enumeration type '{}'")
namespace JS { namespace JS {