diff options
author | Sam Atkins <atkinssj@serenityos.org> | 2022-08-22 14:50:06 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-09-17 21:27:17 +0200 |
commit | c4668053d11267787537792a09929a6df301497f (patch) | |
tree | d3aa0a8ca9504352eed0b06e9db6781b3ce95175 /Userland/Libraries/LibIDL | |
parent | 634a52b589d7f3044151e629a7f9c47a582f6432 (diff) | |
download | serenity-c4668053d11267787537792a09929a6df301497f.zip |
LibIDL+WrapperGenerator: Move IDL code into a library
IDL function overload resolution requires knowing each IDL function's
parameters and their types at runtime. The simplest way to do that is
just to make the types the generator uses available to the runtime.
Parsing has moved to LibIDL, but code generation has not, since that is
very specific to WrapperGenerator.
Diffstat (limited to 'Userland/Libraries/LibIDL')
-rw-r--r-- | Userland/Libraries/LibIDL/CMakeLists.txt | 6 | ||||
-rw-r--r-- | Userland/Libraries/LibIDL/IDLParser.cpp | 1000 | ||||
-rw-r--r-- | Userland/Libraries/LibIDL/IDLParser.h | 65 | ||||
-rw-r--r-- | Userland/Libraries/LibIDL/Types.h | 339 |
4 files changed, 1410 insertions, 0 deletions
diff --git a/Userland/Libraries/LibIDL/CMakeLists.txt b/Userland/Libraries/LibIDL/CMakeLists.txt new file mode 100644 index 0000000000..6c36da7885 --- /dev/null +++ b/Userland/Libraries/LibIDL/CMakeLists.txt @@ -0,0 +1,6 @@ +set(SOURCES + IDLParser.cpp +) + +serenity_lib(LibIDL idl) +target_link_libraries(LibIDL LibCore) diff --git a/Userland/Libraries/LibIDL/IDLParser.cpp b/Userland/Libraries/LibIDL/IDLParser.cpp new file mode 100644 index 0000000000..7b4d4ba919 --- /dev/null +++ b/Userland/Libraries/LibIDL/IDLParser.cpp @@ -0,0 +1,1000 @@ +/* + * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> + * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org> + * Copyright (c) 2022, Ali Mohammad Pur <mpfard@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "IDLParser.h" +#include <AK/LexicalPath.h> +#include <AK/QuickSort.h> +#include <LibCore/File.h> + +[[noreturn]] static void report_parsing_error(StringView message, StringView filename, StringView input, size_t offset) +{ + // FIXME: Spaghetti code ahead. + + size_t lineno = 1; + size_t colno = 1; + size_t start_line = 0; + size_t line_length = 0; + for (size_t index = 0; index < input.length(); ++index) { + if (offset == index) + colno = index - start_line + 1; + + if (input[index] == '\n') { + if (index >= offset) + break; + + start_line = index + 1; + line_length = 0; + ++lineno; + } else { + ++line_length; + } + } + + StringBuilder error_message; + error_message.appendff("{}\n", input.substring_view(start_line, line_length)); + for (size_t i = 0; i < colno - 1; ++i) + error_message.append(' '); + error_message.append("\033[1;31m^\n"sv); + error_message.appendff("{}:{}: error: {}\033[0m\n", filename, lineno, message); + + warnln("{}", error_message.string_view()); + exit(EXIT_FAILURE); +} + +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 is_ascii_space(c) || c == '-' || c == '_'; }); + auto word = lexer.consume_while([](auto c) { return is_ascii_alphanumeric(c); }); + if (!word.is_empty()) { + builder.append(word.to_titlecase_string()); + } else { + auto non_alnum_string = lexer.consume_while([](auto c) { return !is_ascii_alphanumeric(c); }); + if (!non_alnum_string.is_empty()) + builder.append('_'); + } + } + + if (builder.is_empty()) + builder.append("Empty"sv); + + while (names_already_seen.contains(builder.string_view())) + builder.append('_'); + + names_already_seen.set(builder.string_view()); + return builder.build(); +} + +namespace IDL { + +HashTable<NonnullOwnPtr<Interface>> Parser::s_interfaces {}; +HashMap<String, Interface*> Parser::s_resolved_imports {}; + +void Parser::assert_specific(char ch) +{ + if (!lexer.consume_specific(ch)) + report_parsing_error(String::formatted("expected '{}'", ch), filename, input, lexer.tell()); +} + +void Parser::consume_whitespace() +{ + bool consumed = true; + while (consumed) { + consumed = lexer.consume_while(is_ascii_space).length() > 0; + + if (lexer.consume_specific("//")) { + lexer.consume_until('\n'); + lexer.ignore(); + consumed = true; + } + } +} + +void Parser::assert_string(StringView expected) +{ + if (!lexer.consume_specific(expected)) + report_parsing_error(String::formatted("expected '{}'", expected), filename, input, lexer.tell()); +} + +HashMap<String, String> Parser::parse_extended_attributes() +{ + HashMap<String, String> extended_attributes; + for (;;) { + consume_whitespace(); + if (lexer.consume_specific(']')) + break; + auto name = lexer.consume_until([](auto ch) { return ch == ']' || ch == '=' || ch == ','; }); + if (lexer.consume_specific('=')) { + auto value = lexer.consume_until([](auto ch) { return ch == ']' || ch == ','; }); + extended_attributes.set(name, value); + } else { + extended_attributes.set(name, {}); + } + lexer.consume_specific(','); + } + consume_whitespace(); + return extended_attributes; +} + +static HashTable<String> import_stack; +Optional<Interface&> Parser::resolve_import(auto path) +{ + auto include_path = LexicalPath::join(import_base_path, path).string(); + if (!Core::File::exists(include_path)) + report_parsing_error(String::formatted("{}: No such file or directory", include_path), filename, input, lexer.tell()); + + auto real_path = Core::File::real_path_for(include_path); + if (s_resolved_imports.contains(real_path)) + return *s_resolved_imports.find(real_path)->value; + + if (import_stack.contains(real_path)) + report_parsing_error(String::formatted("Circular import detected: {}", include_path), filename, input, lexer.tell()); + import_stack.set(real_path); + + auto file_or_error = Core::File::open(real_path, Core::OpenMode::ReadOnly); + if (file_or_error.is_error()) + report_parsing_error(String::formatted("Failed to open {}: {}", real_path, file_or_error.error()), filename, input, lexer.tell()); + + auto data = file_or_error.value()->read_all(); + auto& result = Parser(real_path, data, import_base_path).parse(); + import_stack.remove(real_path); + + s_resolved_imports.set(real_path, &result); + return result; +} + +NonnullRefPtr<Type> Parser::parse_type() +{ + if (lexer.consume_specific('(')) { + NonnullRefPtrVector<Type> union_member_types; + union_member_types.append(parse_type()); + consume_whitespace(); + assert_string("or"sv); + consume_whitespace(); + union_member_types.append(parse_type()); + consume_whitespace(); + + while (lexer.consume_specific("or")) { + consume_whitespace(); + union_member_types.append(parse_type()); + consume_whitespace(); + } + + assert_specific(')'); + + bool nullable = lexer.consume_specific('?'); + + return adopt_ref(*new UnionType("", nullable, move(union_member_types))); + } + + bool unsigned_ = lexer.consume_specific("unsigned"); + if (unsigned_) + consume_whitespace(); + + // FIXME: Actually treat "unrestricted" and normal floats/doubles differently. + if (lexer.consume_specific("unrestricted")) + consume_whitespace(); + + auto name = lexer.consume_until([](auto ch) { return !is_ascii_alphanumeric(ch) && ch != '_'; }); + + if (name.equals_ignoring_case("long"sv)) { + consume_whitespace(); + if (lexer.consume_specific("long"sv)) + name = "long long"sv; + } + + NonnullRefPtrVector<Type> parameters; + bool is_parameterized_type = false; + if (lexer.consume_specific('<')) { + is_parameterized_type = true; + parameters.append(parse_type()); + while (lexer.consume_specific(',')) { + consume_whitespace(); + parameters.append(parse_type()); + } + lexer.consume_specific('>'); + } + auto nullable = lexer.consume_specific('?'); + StringBuilder builder; + if (unsigned_) + builder.append("unsigned "sv); + builder.append(name); + + if (is_parameterized_type) + return adopt_ref(*new ParameterizedType(builder.to_string(), nullable, move(parameters))); + + return adopt_ref(*new Type(builder.to_string(), nullable)); +} + +void Parser::parse_attribute(HashMap<String, String>& extended_attributes, Interface& interface) +{ + bool readonly = lexer.consume_specific("readonly"); + if (readonly) + consume_whitespace(); + + if (lexer.consume_specific("attribute")) + consume_whitespace(); + + auto type = parse_type(); + consume_whitespace(); + auto name = lexer.consume_until([](auto ch) { return is_ascii_space(ch) || ch == ';'; }); + consume_whitespace(); + + assert_specific(';'); + + auto name_as_string = name.to_string(); + auto getter_callback_name = String::formatted("{}_getter", name_as_string.to_snakecase()); + auto setter_callback_name = String::formatted("{}_setter", name_as_string.to_snakecase()); + + Attribute attribute { + readonly, + move(type), + move(name_as_string), + move(extended_attributes), + move(getter_callback_name), + move(setter_callback_name), + }; + interface.attributes.append(move(attribute)); +} + +void Parser::parse_constant(Interface& interface) +{ + lexer.consume_specific("const"); + consume_whitespace(); + + auto type = parse_type(); + consume_whitespace(); + auto name = lexer.consume_until([](auto ch) { return is_ascii_space(ch) || ch == '='; }); + consume_whitespace(); + lexer.consume_specific('='); + consume_whitespace(); + auto value = lexer.consume_while([](auto ch) { return !is_ascii_space(ch) && ch != ';'; }); + consume_whitespace(); + assert_specific(';'); + + Constant constant { + move(type), + move(name), + move(value), + }; + interface.constants.append(move(constant)); +} + +Vector<Parameter> Parser::parse_parameters() +{ + consume_whitespace(); + Vector<Parameter> parameters; + for (;;) { + if (lexer.next_is(')')) + break; + HashMap<String, String> extended_attributes; + if (lexer.consume_specific('[')) + extended_attributes = parse_extended_attributes(); + bool optional = lexer.consume_specific("optional"); + if (optional) + consume_whitespace(); + auto type = parse_type(); + bool variadic = lexer.consume_specific("..."sv); + consume_whitespace(); + auto name = lexer.consume_until([](auto ch) { return is_ascii_space(ch) || ch == ',' || ch == ')' || ch == '='; }); + Parameter parameter = { move(type), move(name), optional, {}, extended_attributes, variadic }; + consume_whitespace(); + if (variadic) { + // Variadic parameters must be last and do not have default values. + parameters.append(move(parameter)); + break; + } + if (lexer.next_is(')')) { + parameters.append(move(parameter)); + break; + } + if (lexer.next_is('=') && optional) { + assert_specific('='); + consume_whitespace(); + auto default_value = lexer.consume_until([](auto ch) { return is_ascii_space(ch) || ch == ',' || ch == ')'; }); + parameter.optional_default_value = default_value; + } + parameters.append(move(parameter)); + if (lexer.next_is(')')) + break; + assert_specific(','); + consume_whitespace(); + } + return parameters; +} + +Function Parser::parse_function(HashMap<String, String>& extended_attributes, Interface& interface, IsSpecialOperation is_special_operation) +{ + bool static_ = false; + if (lexer.consume_specific("static")) { + static_ = true; + consume_whitespace(); + } + + auto return_type = parse_type(); + consume_whitespace(); + auto name = lexer.consume_until([](auto ch) { return is_ascii_space(ch) || ch == '('; }); + consume_whitespace(); + assert_specific('('); + auto parameters = parse_parameters(); + assert_specific(')'); + consume_whitespace(); + assert_specific(';'); + + Function function { move(return_type), name, move(parameters), move(extended_attributes), {}, false }; + + // "Defining a special operation with an identifier is equivalent to separating the special operation out into its own declaration without an identifier." + if (is_special_operation == IsSpecialOperation::No || (is_special_operation == IsSpecialOperation::Yes && !name.is_empty())) { + if (!static_) + interface.functions.append(function); + else + interface.static_functions.append(function); + } + + return function; +} + +void Parser::parse_constructor(Interface& interface) +{ + assert_string("constructor"sv); + consume_whitespace(); + assert_specific('('); + auto parameters = parse_parameters(); + assert_specific(')'); + consume_whitespace(); + assert_specific(';'); + + interface.constructors.append(Constructor { interface.name, move(parameters) }); +} + +void Parser::parse_stringifier(HashMap<String, String>& extended_attributes, Interface& interface) +{ + assert_string("stringifier"sv); + consume_whitespace(); + interface.has_stringifier = true; + if (lexer.next_is("readonly"sv) || lexer.next_is("attribute"sv)) { + parse_attribute(extended_attributes, interface); + interface.stringifier_attribute = interface.attributes.last().name; + } else { + assert_specific(';'); + } +} + +void Parser::parse_iterable(Interface& interface) +{ + assert_string("iterable"sv); + assert_specific('<'); + auto first_type = parse_type(); + if (lexer.next_is(',')) { + if (interface.supports_indexed_properties()) + report_parsing_error("Interfaces with a pair iterator must not supported indexed properties."sv, filename, input, lexer.tell()); + + assert_specific(','); + consume_whitespace(); + auto second_type = parse_type(); + interface.pair_iterator_types = Tuple { move(first_type), move(second_type) }; + } else { + if (!interface.supports_indexed_properties()) + report_parsing_error("Interfaces with a value iterator must supported indexed properties."sv, filename, input, lexer.tell()); + + interface.value_iterator_type = move(first_type); + } + assert_specific('>'); + assert_specific(';'); +} + +void Parser::parse_getter(HashMap<String, String>& extended_attributes, Interface& interface) +{ + assert_string("getter"sv); + consume_whitespace(); + auto function = parse_function(extended_attributes, interface, IsSpecialOperation::Yes); + + if (function.parameters.size() != 1) + report_parsing_error(String::formatted("Named/indexed property getters must have only 1 parameter, got {} parameters.", function.parameters.size()), filename, input, lexer.tell()); + + auto& identifier = function.parameters.first(); + + if (identifier.type->nullable) + report_parsing_error("identifier's type must not be nullable."sv, filename, input, lexer.tell()); + + if (identifier.optional) + report_parsing_error("identifier must not be optional."sv, filename, input, lexer.tell()); + + // FIXME: Disallow variadic functions once they're supported. + + if (identifier.type->name == "DOMString") { + if (interface.named_property_getter.has_value()) + report_parsing_error("An interface can only have one named property getter."sv, filename, input, lexer.tell()); + + interface.named_property_getter = move(function); + } else if (identifier.type->name == "unsigned long") { + if (interface.indexed_property_getter.has_value()) + report_parsing_error("An interface can only have one indexed property getter."sv, filename, input, lexer.tell()); + + interface.indexed_property_getter = move(function); + } else { + report_parsing_error(String::formatted("Named/indexed property getter's identifier's type must be either 'DOMString' or 'unsigned long', got '{}'.", identifier.type->name), filename, input, lexer.tell()); + } +} + +void Parser::parse_setter(HashMap<String, String>& extended_attributes, Interface& interface) +{ + assert_string("setter"sv); + consume_whitespace(); + auto function = parse_function(extended_attributes, interface, IsSpecialOperation::Yes); + + if (function.parameters.size() != 2) + report_parsing_error(String::formatted("Named/indexed property setters must have only 2 parameters, got {} parameter(s).", function.parameters.size()), filename, input, lexer.tell()); + + auto& identifier = function.parameters.first(); + + if (identifier.type->nullable) + report_parsing_error("identifier's type must not be nullable."sv, filename, input, lexer.tell()); + + if (identifier.optional) + report_parsing_error("identifier must not be optional."sv, filename, input, lexer.tell()); + + // FIXME: Disallow variadic functions once they're supported. + + if (identifier.type->name == "DOMString") { + if (interface.named_property_setter.has_value()) + report_parsing_error("An interface can only have one named property setter."sv, filename, input, lexer.tell()); + + if (!interface.named_property_getter.has_value()) + report_parsing_error("A named property setter must be accompanied by a named property getter."sv, filename, input, lexer.tell()); + + interface.named_property_setter = move(function); + } else if (identifier.type->name == "unsigned long") { + if (interface.indexed_property_setter.has_value()) + report_parsing_error("An interface can only have one indexed property setter."sv, filename, input, lexer.tell()); + + if (!interface.indexed_property_getter.has_value()) + report_parsing_error("An indexed property setter must be accompanied by an indexed property getter."sv, filename, input, lexer.tell()); + + interface.indexed_property_setter = move(function); + } else { + report_parsing_error(String::formatted("Named/indexed property setter's identifier's type must be either 'DOMString' or 'unsigned long', got '{}'.", identifier.type->name), filename, input, lexer.tell()); + } +} + +void Parser::parse_deleter(HashMap<String, String>& extended_attributes, Interface& interface) +{ + assert_string("deleter"sv); + consume_whitespace(); + auto function = parse_function(extended_attributes, interface, IsSpecialOperation::Yes); + + if (function.parameters.size() != 1) + report_parsing_error(String::formatted("Named property deleter must have only 1 parameter, got {} parameters.", function.parameters.size()), filename, input, lexer.tell()); + + auto& identifier = function.parameters.first(); + + if (identifier.type->nullable) + report_parsing_error("identifier's type must not be nullable."sv, filename, input, lexer.tell()); + + if (identifier.optional) + report_parsing_error("identifier must not be optional."sv, filename, input, lexer.tell()); + + // FIXME: Disallow variadic functions once they're supported. + + if (identifier.type->name == "DOMString") { + if (interface.named_property_deleter.has_value()) + report_parsing_error("An interface can only have one named property deleter."sv, filename, input, lexer.tell()); + + if (!interface.named_property_getter.has_value()) + report_parsing_error("A named property deleter must be accompanied by a named property getter."sv, filename, input, lexer.tell()); + + interface.named_property_deleter = move(function); + } else { + report_parsing_error(String::formatted("Named property deleter's identifier's type must be 'DOMString', got '{}'.", identifier.type->name), filename, input, lexer.tell()); + } +} + +void Parser::parse_interface(Interface& interface) +{ + consume_whitespace(); + interface.name = lexer.consume_until([](auto ch) { return is_ascii_space(ch); }); + consume_whitespace(); + if (lexer.consume_specific(':')) { + consume_whitespace(); + interface.parent_name = lexer.consume_until([](auto ch) { return is_ascii_space(ch); }); + consume_whitespace(); + } + assert_specific('{'); + + for (;;) { + HashMap<String, String> extended_attributes; + + consume_whitespace(); + + if (lexer.consume_specific('}')) { + consume_whitespace(); + assert_specific(';'); + break; + } + + if (lexer.consume_specific('[')) { + extended_attributes = parse_extended_attributes(); + if (!interface.has_unscopable_member && extended_attributes.contains("Unscopable")) + interface.has_unscopable_member = true; + } + + if (lexer.next_is("constructor")) { + parse_constructor(interface); + continue; + } + + if (lexer.next_is("const")) { + parse_constant(interface); + continue; + } + + if (lexer.next_is("stringifier")) { + parse_stringifier(extended_attributes, interface); + continue; + } + + if (lexer.next_is("iterable")) { + parse_iterable(interface); + continue; + } + + if (lexer.next_is("readonly") || lexer.next_is("attribute")) { + parse_attribute(extended_attributes, interface); + continue; + } + + if (lexer.next_is("getter")) { + parse_getter(extended_attributes, interface); + continue; + } + + if (lexer.next_is("setter")) { + parse_setter(extended_attributes, interface); + continue; + } + + if (lexer.next_is("deleter")) { + parse_deleter(extended_attributes, interface); + continue; + } + + parse_function(extended_attributes, interface); + } + + interface.wrapper_class = interface.name; + + interface.wrapper_base_class = String::formatted("{}Wrapper", interface.parent_name.is_empty() ? String::empty() : interface.parent_name); + interface.constructor_class = String::formatted("{}Constructor", interface.name); + interface.prototype_class = String::formatted("{}Prototype", interface.name); + interface.prototype_base_class = String::formatted("{}Prototype", interface.parent_name.is_empty() ? "Object" : interface.parent_name); + consume_whitespace(); +} + +void Parser::parse_enumeration(Interface& interface) +{ + assert_string("enum"sv); + consume_whitespace(); + + Enumeration enumeration {}; + + auto name = lexer.consume_until([](auto ch) { return is_ascii_space(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(); +} + +void Parser::parse_typedef(Interface& interface) +{ + assert_string("typedef"sv); + consume_whitespace(); + + HashMap<String, String> extended_attributes; + if (lexer.consume_specific('[')) + extended_attributes = parse_extended_attributes(); + + auto type = parse_type(); + consume_whitespace(); + + auto name = lexer.consume_until(';'); + assert_specific(';'); + + interface.typedefs.set(name, Typedef { move(extended_attributes), move(type) }); + consume_whitespace(); +} + +void Parser::parse_dictionary(Interface& interface) +{ + assert_string("dictionary"sv); + consume_whitespace(); + + Dictionary dictionary {}; + + auto name = lexer.consume_until([](auto ch) { return is_ascii_space(ch); }); + consume_whitespace(); + + if (lexer.consume_specific(':')) { + consume_whitespace(); + dictionary.parent_name = lexer.consume_until([](auto ch) { return is_ascii_space(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 is_ascii_space(ch) || ch == ';'; }); + consume_whitespace(); + + Optional<StringView> default_value; + + if (lexer.consume_specific('=')) { + VERIFY(!required); + consume_whitespace(); + default_value = lexer.consume_until([](auto ch) { return is_ascii_space(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(); +} + +void Parser::parse_interface_mixin(Interface& interface) +{ + auto mixin_interface_ptr = make<Interface>(); + auto& mixin_interface = *mixin_interface_ptr; + VERIFY(s_interfaces.set(move(mixin_interface_ptr)) == AK::HashSetResult::InsertedNewEntry); + mixin_interface.module_own_path = interface.module_own_path; + mixin_interface.is_mixin = true; + + assert_string("interface"sv); + consume_whitespace(); + assert_string("mixin"sv); + auto offset = lexer.tell(); + + parse_interface(mixin_interface); + if (!mixin_interface.parent_name.is_empty()) + report_parsing_error("Mixin interfaces are not allowed to have inherited parents"sv, filename, input, offset); + + auto name = mixin_interface.name; + interface.mixins.set(move(name), &mixin_interface); +} + +void Parser::parse_callback_function(HashMap<String, String>& extended_attributes, Interface& interface) +{ + assert_string("callback"sv); + consume_whitespace(); + + auto name = lexer.consume_until([](auto ch) { return is_ascii_space(ch); }); + consume_whitespace(); + + assert_specific('='); + consume_whitespace(); + + auto return_type = parse_type(); + consume_whitespace(); + assert_specific('('); + auto parameters = parse_parameters(); + assert_specific(')'); + consume_whitespace(); + assert_specific(';'); + + interface.callback_functions.set(name, CallbackFunction { move(return_type), move(parameters), extended_attributes.contains("LegacyTreatNonObjectAsNull") }); + consume_whitespace(); +} + +void Parser::parse_non_interface_entities(bool allow_interface, Interface& interface) +{ + consume_whitespace(); + + while (!lexer.is_eof()) { + HashMap<String, String> extended_attributes; + if (lexer.consume_specific('[')) + extended_attributes = parse_extended_attributes(); + if (lexer.next_is("dictionary")) { + parse_dictionary(interface); + } else if (lexer.next_is("enum")) { + parse_enumeration(interface); + } else if (lexer.next_is("typedef")) { + parse_typedef(interface); + } else if (lexer.next_is("interface mixin")) { + parse_interface_mixin(interface); + } else if (lexer.next_is("callback")) { + parse_callback_function(extended_attributes, interface); + } else if ((allow_interface && !lexer.next_is("interface")) || !allow_interface) { + auto current_offset = lexer.tell(); + auto name = lexer.consume_until([](auto ch) { return is_ascii_space(ch); }); + consume_whitespace(); + if (lexer.consume_specific("includes")) { + consume_whitespace(); + auto mixin_name = lexer.consume_until([](auto ch) { return is_ascii_space(ch) || ch == ';'; }); + interface.included_mixins.ensure(name).set(mixin_name); + consume_whitespace(); + assert_specific(';'); + consume_whitespace(); + } else { + report_parsing_error("expected 'enum' or 'dictionary'"sv, filename, input, current_offset); + } + } else { + interface.extended_attributes = move(extended_attributes); + break; + } + } + + consume_whitespace(); +} + +static void resolve_typedef(Interface& interface, NonnullRefPtr<Type>& type, HashMap<String, String>* extended_attributes = {}) +{ + if (is<ParameterizedType>(*type)) { + auto parameterized_type = static_ptr_cast<ParameterizedType>(type); + auto& parameters = static_cast<Vector<NonnullRefPtr<Type>>&>(parameterized_type->parameters); + for (auto& parameter : parameters) + resolve_typedef(interface, parameter); + return; + } + + auto it = interface.typedefs.find(type->name); + if (it == interface.typedefs.end()) + return; + bool is_nullable = type->nullable; + type = it->value.type; + type->nullable = is_nullable; + if (!extended_attributes) + return; + for (auto& attribute : it->value.extended_attributes) + extended_attributes->set(attribute.key, attribute.value); +} +static void resolve_parameters_typedefs(Interface& interface, Vector<Parameter>& parameters) +{ + for (auto& parameter : parameters) + resolve_typedef(interface, parameter.type, ¶meter.extended_attributes); +} +template<typename FunctionType> +void resolve_function_typedefs(Interface& interface, FunctionType& function) +{ + resolve_typedef(interface, function.return_type); + resolve_parameters_typedefs(interface, function.parameters); +} + +Interface& Parser::parse() +{ + auto this_module = Core::File::real_path_for(filename); + + auto interface_ptr = make<Interface>(); + auto& interface = *interface_ptr; + VERIFY(s_interfaces.set(move(interface_ptr)) == AK::HashSetResult::InsertedNewEntry); + interface.module_own_path = this_module; + s_resolved_imports.set(this_module, &interface); + + Vector<Interface&> imports; + HashTable<String> required_imported_paths; + while (lexer.consume_specific("#import")) { + consume_whitespace(); + assert_specific('<'); + auto path = lexer.consume_until('>'); + lexer.ignore(); + auto maybe_interface = resolve_import(path); + if (maybe_interface.has_value()) { + for (auto& entry : maybe_interface.value().required_imported_paths) + required_imported_paths.set(entry); + imports.append(maybe_interface.release_value()); + } + consume_whitespace(); + } + interface.required_imported_paths = required_imported_paths; + + parse_non_interface_entities(true, interface); + + if (lexer.consume_specific("interface")) + parse_interface(interface); + + parse_non_interface_entities(false, interface); + + for (auto& import : imports) { + // FIXME: Instead of copying every imported entity into the current interface, query imports directly + for (auto& dictionary : import.dictionaries) + 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)); + } + + for (auto& typedef_ : import.typedefs) + interface.typedefs.set(typedef_.key, typedef_.value); + + for (auto& mixin : import.mixins) { + if (auto it = interface.mixins.find(mixin.key); it != interface.mixins.end() && it->value != mixin.value) + report_parsing_error(String::formatted("Mixin '{}' was already defined in {}", mixin.key, mixin.value->module_own_path), filename, input, lexer.tell()); + interface.mixins.set(mixin.key, mixin.value); + } + + for (auto& callback_function : import.callback_functions) + interface.callback_functions.set(callback_function.key, callback_function.value); + } + + // Resolve mixins + if (auto it = interface.included_mixins.find(interface.name); it != interface.included_mixins.end()) { + for (auto& entry : it->value) { + auto mixin_it = interface.mixins.find(entry); + if (mixin_it == interface.mixins.end()) + report_parsing_error(String::formatted("Mixin '{}' was never defined", entry), filename, input, lexer.tell()); + + auto& mixin = mixin_it->value; + interface.attributes.extend(mixin->attributes); + interface.constants.extend(mixin->constants); + interface.functions.extend(mixin->functions); + interface.static_functions.extend(mixin->static_functions); + if (interface.has_stringifier && mixin->has_stringifier) + report_parsing_error(String::formatted("Both interface '{}' and mixin '{}' have defined stringifier attributes", interface.name, mixin->name), filename, input, lexer.tell()); + + if (mixin->has_stringifier) { + interface.stringifier_attribute = mixin->stringifier_attribute; + interface.has_stringifier = true; + } + + if (mixin->has_unscopable_member) + interface.has_unscopable_member = true; + } + } + + // Resolve typedefs + for (auto& attribute : interface.attributes) + resolve_typedef(interface, attribute.type, &attribute.extended_attributes); + for (auto& constant : interface.constants) + resolve_typedef(interface, constant.type); + for (auto& constructor : interface.constructors) + resolve_parameters_typedefs(interface, constructor.parameters); + for (auto& function : interface.functions) + resolve_function_typedefs(interface, function); + for (auto& static_function : interface.static_functions) + resolve_function_typedefs(interface, static_function); + if (interface.value_iterator_type.has_value()) + resolve_typedef(interface, *interface.value_iterator_type); + if (interface.pair_iterator_types.has_value()) { + resolve_typedef(interface, interface.pair_iterator_types->get<0>()); + resolve_typedef(interface, interface.pair_iterator_types->get<1>()); + } + if (interface.named_property_getter.has_value()) + resolve_function_typedefs(interface, *interface.named_property_getter); + if (interface.named_property_setter.has_value()) + resolve_function_typedefs(interface, *interface.named_property_setter); + if (interface.indexed_property_getter.has_value()) + resolve_function_typedefs(interface, *interface.indexed_property_getter); + if (interface.indexed_property_setter.has_value()) + resolve_function_typedefs(interface, *interface.indexed_property_setter); + if (interface.named_property_deleter.has_value()) + resolve_function_typedefs(interface, *interface.named_property_deleter); + if (interface.named_property_getter.has_value()) + resolve_function_typedefs(interface, *interface.named_property_getter); + for (auto& dictionary : interface.dictionaries) { + for (auto& dictionary_member : dictionary.value.members) + resolve_typedef(interface, dictionary_member.type, &dictionary_member.extended_attributes); + } + for (auto& callback_function : interface.callback_functions) + resolve_function_typedefs(interface, callback_function.value); + + // Create overload sets + for (auto& function : interface.functions) { + auto& overload_set = interface.overload_sets.ensure(function.name); + function.overload_index = overload_set.size(); + overload_set.append(function); + } + for (auto& overload_set : interface.overload_sets) { + if (overload_set.value.size() == 1) + continue; + for (auto& overloaded_function : overload_set.value) + overloaded_function.is_overloaded = true; + } + for (auto& function : interface.static_functions) { + auto& overload_set = interface.static_overload_sets.ensure(function.name); + function.overload_index = overload_set.size(); + overload_set.append(function); + } + for (auto& overload_set : interface.static_overload_sets) { + if (overload_set.value.size() == 1) + continue; + for (auto& overloaded_function : overload_set.value) + overloaded_function.is_overloaded = true; + } + // FIXME: Add support for overloading constructors + + if (interface.will_generate_code()) + interface.required_imported_paths.set(this_module); + interface.imported_modules = move(imports); + + return interface; +} + +Parser::Parser(String filename, StringView contents, String import_base_path) + : import_base_path(move(import_base_path)) + , filename(move(filename)) + , input(contents) + , lexer(input) +{ +} + +} diff --git a/Userland/Libraries/LibIDL/IDLParser.h b/Userland/Libraries/LibIDL/IDLParser.h new file mode 100644 index 0000000000..9cf488513b --- /dev/null +++ b/Userland/Libraries/LibIDL/IDLParser.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> + * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org> + * Copyright (c) 2022, Ali Mohammad Pur <mpfard@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/CharacterTypes.h> +#include <AK/GenericLexer.h> +#include <LibIDL/Types.h> + +namespace IDL { + +class Parser { +public: + Parser(String filename, StringView contents, String import_base_path); + Interface& parse(); + +private: + // https://webidl.spec.whatwg.org/#dfn-special-operation + // A special operation is a getter, setter or deleter. + enum class IsSpecialOperation { + No, + Yes, + }; + + void assert_specific(char ch); + void assert_string(StringView expected); + void consume_whitespace(); + Optional<Interface&> resolve_import(auto path); + + HashMap<String, String> parse_extended_attributes(); + void parse_attribute(HashMap<String, String>& extended_attributes, Interface&); + void parse_interface(Interface&); + void parse_non_interface_entities(bool allow_interface, Interface&); + void parse_enumeration(Interface&); + void parse_typedef(Interface&); + void parse_interface_mixin(Interface&); + void parse_dictionary(Interface&); + void parse_callback_function(HashMap<String, String>& extended_attributes, Interface&); + void parse_constructor(Interface&); + void parse_getter(HashMap<String, String>& extended_attributes, Interface&); + void parse_setter(HashMap<String, String>& extended_attributes, Interface&); + void parse_deleter(HashMap<String, String>& extended_attributes, Interface&); + void parse_stringifier(HashMap<String, String>& extended_attributes, Interface&); + void parse_iterable(Interface&); + Function parse_function(HashMap<String, String>& extended_attributes, Interface&, IsSpecialOperation is_special_operation = IsSpecialOperation::No); + Vector<Parameter> parse_parameters(); + NonnullRefPtr<Type> parse_type(); + void parse_constant(Interface&); + + static HashTable<NonnullOwnPtr<Interface>> s_interfaces; + static HashMap<String, Interface*> s_resolved_imports; + + String import_base_path; + String filename; + StringView input; + GenericLexer lexer; +}; + +} diff --git a/Userland/Libraries/LibIDL/Types.h b/Userland/Libraries/LibIDL/Types.h new file mode 100644 index 0000000000..7dc9783753 --- /dev/null +++ b/Userland/Libraries/LibIDL/Types.h @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> + * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org> + * Copyright (c) 2022, Ali Mohammad Pur <mpfard@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/HashMap.h> +#include <AK/NonnullRefPtr.h> +#include <AK/NonnullRefPtrVector.h> +#include <AK/SourceGenerator.h> +#include <AK/String.h> +#include <AK/StringBuilder.h> +#include <AK/Tuple.h> +#include <AK/TypeCasts.h> + +namespace IDL { + +template<typename FunctionType> +static size_t get_function_shortest_length(FunctionType& function) +{ + size_t length = 0; + for (auto& parameter : function.parameters) { + if (!parameter.optional && !parameter.variadic) + length++; + } + return length; +} + +enum class SequenceStorageType { + Vector, // Used to safely store non-JS values + MarkedVector, // Used to safely store JS::Value and anything that inherits JS::Cell, e.g. JS::Object +}; + +struct CppType { + String name; + SequenceStorageType sequence_storage_type; +}; + +struct Type : public RefCounted<Type> { + Type() = default; + + Type(String name, bool nullable) + : name(move(name)) + , nullable(nullable) + { + } + + virtual ~Type() = default; + + String name; + bool nullable { false }; + bool is_string() const { return name.is_one_of("ByteString", "CSSOMString", "DOMString", "USVString"); } + + // https://webidl.spec.whatwg.org/#dfn-integer-type + bool is_integer() const { return name.is_one_of("byte", "octet", "short", "unsigned short", "long", "unsigned long", "long long", "unsigned long long"); } + + // https://webidl.spec.whatwg.org/#dfn-numeric-type + bool is_numeric() const { return is_integer() || name.is_one_of("float", "unrestricted float", "double", "unrestricted double"); } + + // https://webidl.spec.whatwg.org/#dfn-primitive-type + bool is_primitive() const { return is_numeric() || name.is_one_of("bigint", "boolean"); } +}; + +struct Parameter { + NonnullRefPtr<Type> type; + String name; + bool optional { false }; + Optional<String> optional_default_value; + HashMap<String, String> extended_attributes; + bool variadic { false }; +}; + +struct Function { + NonnullRefPtr<Type> return_type; + String name; + Vector<Parameter> parameters; + HashMap<String, String> extended_attributes; + size_t overload_index { 0 }; + bool is_overloaded { false }; + + size_t shortest_length() const { return get_function_shortest_length(*this); } +}; + +struct Constructor { + String name; + Vector<Parameter> parameters; + + size_t shortest_length() const { return get_function_shortest_length(*this); } +}; + +struct Constant { + NonnullRefPtr<Type> type; + String name; + String value; +}; + +struct Attribute { + bool readonly { false }; + NonnullRefPtr<Type> type; + String name; + HashMap<String, String> extended_attributes; + + // Added for convenience after parsing + String getter_callback_name; + String setter_callback_name; +}; + +struct DictionaryMember { + bool required { false }; + NonnullRefPtr<Type> type; + String name; + HashMap<String, String> extended_attributes; + Optional<String> default_value; +}; + +struct Dictionary { + String parent_name; + Vector<DictionaryMember> members; +}; + +struct Typedef { + HashMap<String, String> extended_attributes; + NonnullRefPtr<Type> type; +}; + +struct Enumeration { + HashTable<String> values; + HashMap<String, String> translated_cpp_names; + String first_member; + bool is_original_definition { true }; +}; + +struct CallbackFunction { + NonnullRefPtr<Type> return_type; + Vector<Parameter> parameters; + bool is_legacy_treat_non_object_as_null { false }; +}; + +class Interface; + +struct ParameterizedType : public Type { + ParameterizedType() = default; + + ParameterizedType(String name, bool nullable, NonnullRefPtrVector<Type> parameters) + : Type(move(name), nullable) + , parameters(move(parameters)) + { + } + + virtual ~ParameterizedType() override = default; + + 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, IDL::Interface const&, size_t recursion_depth) const; +}; + +static inline size_t get_shortest_function_length(Vector<Function&> const& overload_set) +{ + size_t shortest_length = SIZE_MAX; + for (auto const& function : overload_set) + shortest_length = min(function.shortest_length(), shortest_length); + return shortest_length; +} + +class Interface { + AK_MAKE_NONCOPYABLE(Interface); + AK_MAKE_NONMOVABLE(Interface); + +public: + explicit Interface() = default; + + String name; + String parent_name; + + bool is_mixin { false }; + + HashMap<String, String> extended_attributes; + + Vector<Attribute> attributes; + Vector<Constant> constants; + Vector<Constructor> constructors; + Vector<Function> functions; + Vector<Function> static_functions; + bool has_stringifier { false }; + Optional<String> stringifier_attribute; + bool has_unscopable_member { false }; + + Optional<NonnullRefPtr<Type>> value_iterator_type; + Optional<Tuple<NonnullRefPtr<Type>, NonnullRefPtr<Type>>> pair_iterator_types; + + Optional<Function> named_property_getter; + Optional<Function> named_property_setter; + + Optional<Function> indexed_property_getter; + Optional<Function> indexed_property_setter; + + Optional<Function> named_property_deleter; + + HashMap<String, Dictionary> dictionaries; + HashMap<String, Enumeration> enumerations; + HashMap<String, Typedef> typedefs; + HashMap<String, Interface*> mixins; + HashMap<String, CallbackFunction> callback_functions; + + // Added for convenience after parsing + String wrapper_class; + String wrapper_base_class; + String fully_qualified_name; + String constructor_class; + String prototype_class; + String prototype_base_class; + HashMap<String, HashTable<String>> included_mixins; + + String module_own_path; + HashTable<String> required_imported_paths; + Vector<Interface&> imported_modules; + + HashMap<String, Vector<Function&>> overload_sets; + HashMap<String, Vector<Function&>> static_overload_sets; + + // https://webidl.spec.whatwg.org/#dfn-support-indexed-properties + bool supports_indexed_properties() const { return indexed_property_getter.has_value(); } + + // https://webidl.spec.whatwg.org/#dfn-support-named-properties + bool supports_named_properties() const { return named_property_getter.has_value(); } + + // https://webidl.spec.whatwg.org/#dfn-legacy-platform-object + bool is_legacy_platform_object() const { return !extended_attributes.contains("Global") && (supports_indexed_properties() || supports_named_properties()); } + + bool will_generate_code() const + { + return !name.is_empty() || any_of(enumerations, [](auto& entry) { return entry.value.is_original_definition; }); + } +}; + +struct UnionType : public Type { + UnionType() = default; + + UnionType(String name, bool nullable, NonnullRefPtrVector<Type> member_types) + : Type(move(name), nullable) + , member_types(move(member_types)) + { + } + + virtual ~UnionType() override = default; + + NonnullRefPtrVector<Type> member_types; + + // https://webidl.spec.whatwg.org/#dfn-flattened-union-member-types + NonnullRefPtrVector<Type> flattened_member_types() const + { + // 1. Let T be the union type. + + // 2. Initialize S to ∅. + NonnullRefPtrVector<Type> types; + + // 3. For each member type U of T: + for (auto& type : member_types) { + // FIXME: 1. If U is an annotated type, then set U to be the inner type of U. + + // 2. If U is a nullable type, then set U to be the inner type of U. (NOTE: Not necessary as nullable is stored with Type and not as a separate struct) + + // 3. If U is a union type, then add to S the flattened member types of U. + if (is<UnionType>(type)) { + auto& union_member_type = verify_cast<UnionType>(type); + types.extend(union_member_type.flattened_member_types()); + } else { + // 4. Otherwise, U is not a union type. Add U to S. + types.append(type); + } + } + + // 4. Return S. + return types; + } + + // https://webidl.spec.whatwg.org/#dfn-number-of-nullable-member-types + size_t number_of_nullable_member_types() const + { + // 1. Let T be the union type. + + // 2. Initialize n to 0. + size_t num_nullable_member_types = 0; + + // 3. For each member type U of T: + for (auto& type : member_types) { + // 1. If U is a nullable type, then: + if (type.nullable) { + // 1. Set n to n + 1. + ++num_nullable_member_types; + + // 2. Set U to be the inner type of U. (NOTE: Not necessary as nullable is stored with Type and not as a separate struct) + } + + // 2. If U is a union type, then: + if (is<UnionType>(type)) { + auto& union_member_type = verify_cast<UnionType>(type); + + // 1. Let m be the number of nullable member types of U. + // 2. Set n to n + m. + num_nullable_member_types += union_member_type.number_of_nullable_member_types(); + } + } + + // 4. Return n. + return num_nullable_member_types; + } + + // https://webidl.spec.whatwg.org/#dfn-includes-a-nullable-type + bool includes_nullable_type() const + { + // -> the type is a union type and its number of nullable member types is 1. + return number_of_nullable_member_types() == 1; + } + + // -> https://webidl.spec.whatwg.org/#dfn-includes-undefined + bool includes_undefined() const + { + // -> the type is a union type and one of its member types includes undefined. + for (auto& type : member_types) { + if (is<UnionType>(type)) { + auto& union_type = verify_cast<UnionType>(type); + if (union_type.includes_undefined()) + return true; + } + + if (type.name == "undefined"sv) + return true; + } + return false; + } +}; + +} |