Serenity Operating System
at master 455 lines 19 kB view raw
1/* 2 * Copyright (c) 2022, Andrew Kaster <akaster@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/DeprecatedString.h> 8#include <AK/LexicalPath.h> 9#include <AK/SourceGenerator.h> 10#include <AK/StringBuilder.h> 11#include <LibCore/ArgsParser.h> 12#include <LibCore/File.h> 13#include <LibIDL/IDLParser.h> 14#include <LibIDL/Types.h> 15#include <LibMain/Main.h> 16 17static ErrorOr<void> add_to_interface_sets(IDL::Interface&, Vector<IDL::Interface&>& intrinsics, Vector<IDL::Interface&>& window_exposed, Vector<IDL::Interface&>& dedicated_worker_exposed, Vector<IDL::Interface&>& shared_worker_exposed); 18static DeprecatedString s_error_string; 19 20struct LegacyConstructor { 21 DeprecatedString name; 22 DeprecatedString constructor_class; 23}; 24 25static void consume_whitespace(GenericLexer& lexer) 26{ 27 bool consumed = true; 28 while (consumed) { 29 consumed = lexer.consume_while(is_ascii_space).length() > 0; 30 31 if (lexer.consume_specific("//")) { 32 lexer.consume_until('\n'); 33 lexer.ignore(); 34 consumed = true; 35 } 36 } 37} 38 39static Optional<LegacyConstructor> const& lookup_legacy_constructor(IDL::Interface& interface) 40{ 41 static HashMap<StringView, Optional<LegacyConstructor>> s_legacy_constructors; 42 if (auto cache = s_legacy_constructors.get(interface.name); cache.has_value()) 43 return cache.value(); 44 45 auto attribute = interface.extended_attributes.get("LegacyFactoryFunction"sv); 46 if (!attribute.has_value()) { 47 s_legacy_constructors.set(interface.name, {}); 48 return s_legacy_constructors.get(interface.name).value(); 49 } 50 51 GenericLexer function_lexer(attribute.value()); 52 consume_whitespace(function_lexer); 53 54 auto name = function_lexer.consume_until([](auto ch) { return is_ascii_space(ch) || ch == '('; }); 55 auto constructor_class = DeprecatedString::formatted("{}Constructor", name); 56 57 s_legacy_constructors.set(interface.name, LegacyConstructor { name, move(constructor_class) }); 58 return s_legacy_constructors.get(interface.name).value(); 59} 60 61static ErrorOr<void> generate_forwarding_header(StringView output_path, Vector<IDL::Interface&>& exposed_interfaces) 62{ 63 StringBuilder builder; 64 SourceGenerator generator(builder); 65 66 generator.append(R"~~~( 67#pragma once 68 69namespace Web::Bindings { 70)~~~"); 71 72 auto add_interface = [](SourceGenerator& gen, StringView prototype_class, StringView constructor_class, Optional<LegacyConstructor> const& legacy_constructor) { 73 gen.set("prototype_class", prototype_class); 74 gen.set("constructor_class", constructor_class); 75 76 gen.append(R"~~~( 77class @prototype_class@; 78class @constructor_class@;)~~~"); 79 80 if (legacy_constructor.has_value()) { 81 gen.set("legacy_constructor_class", legacy_constructor->constructor_class); 82 gen.append(R"~~~( 83class @legacy_constructor_class@;)~~~"); 84 } 85 }; 86 87 for (auto& interface : exposed_interfaces) { 88 auto gen = generator.fork(); 89 add_interface(gen, interface.prototype_class, interface.constructor_class, lookup_legacy_constructor(interface)); 90 } 91 92 // FIXME: Special case WebAssembly. We should convert WASM to use IDL. 93 { 94 auto gen = generator.fork(); 95 add_interface(gen, "WebAssemblyMemoryPrototype"sv, "WebAssemblyMemoryConstructor"sv, {}); 96 add_interface(gen, "WebAssemblyInstancePrototype"sv, "WebAssemblyInstanceConstructor"sv, {}); 97 add_interface(gen, "WebAssemblyModulePrototype"sv, "WebAssemblyModuleConstructor"sv, {}); 98 add_interface(gen, "WebAssemblyTablePrototype"sv, "WebAssemblyTableConstructor"sv, {}); 99 } 100 101 generator.append(R"~~~( 102 103} 104)~~~"); 105 106 auto generated_forward_path = LexicalPath(output_path).append("Forward.h"sv).string(); 107 auto generated_forward_file = TRY(Core::File::open(generated_forward_path, Core::File::OpenMode::Write)); 108 TRY(generated_forward_file->write_until_depleted(generator.as_string_view().bytes())); 109 110 return {}; 111} 112 113static ErrorOr<void> generate_intrinsic_definitions(StringView output_path, Vector<IDL::Interface&>& exposed_interfaces) 114{ 115 StringBuilder builder; 116 SourceGenerator generator(builder); 117 118 generator.append(R"~~~( 119#include <LibJS/Heap/DeferGC.h> 120#include <LibJS/Runtime/Object.h> 121#include <LibWeb/Bindings/Intrinsics.h>)~~~"); 122 123 for (auto& interface : exposed_interfaces) { 124 auto gen = generator.fork(); 125 gen.set("prototype_class", interface.prototype_class); 126 gen.set("constructor_class", interface.constructor_class); 127 128 gen.append(R"~~~( 129#include <LibWeb/Bindings/@constructor_class@.h> 130#include <LibWeb/Bindings/@prototype_class@.h>)~~~"); 131 132 if (auto const& legacy_constructor = lookup_legacy_constructor(interface); legacy_constructor.has_value()) { 133 gen.set("legacy_constructor_class", legacy_constructor->constructor_class); 134 gen.append(R"~~~( 135#include <LibWeb/Bindings/@legacy_constructor_class@.h>)~~~"); 136 } 137 } 138 139 // FIXME: Special case WebAssembly. We should convert WASM to use IDL. 140 generator.append(R"~~~( 141#include <LibWeb/WebAssembly/WebAssemblyMemoryConstructor.h> 142#include <LibWeb/WebAssembly/WebAssemblyMemoryPrototype.h> 143#include <LibWeb/WebAssembly/WebAssemblyInstanceConstructor.h> 144#include <LibWeb/WebAssembly/WebAssemblyInstanceObjectPrototype.h> 145#include <LibWeb/WebAssembly/WebAssemblyModuleConstructor.h> 146#include <LibWeb/WebAssembly/WebAssemblyModulePrototype.h> 147#include <LibWeb/WebAssembly/WebAssemblyTableConstructor.h> 148#include <LibWeb/WebAssembly/WebAssemblyTablePrototype.h>)~~~"); 149 150 generator.append(R"~~~( 151 152namespace Web::Bindings { 153)~~~"); 154 155 auto add_interface = [&](SourceGenerator& gen, StringView name, StringView prototype_class, StringView constructor_class, Optional<LegacyConstructor> const& legacy_constructor) { 156 gen.set("interface_name", name); 157 gen.set("prototype_class", prototype_class); 158 gen.set("constructor_class", constructor_class); 159 160 gen.append(R"~~~( 161template<> 162void Intrinsics::create_web_prototype_and_constructor<@prototype_class@>(JS::Realm& realm) 163{ 164 auto& vm = realm.vm(); 165 166 auto prototype = heap().allocate<@prototype_class@>(realm, realm).release_allocated_value_but_fixme_should_propagate_errors(); 167 m_prototypes.set("@interface_name@"sv, prototype); 168 169 auto constructor = heap().allocate<@constructor_class@>(realm, realm).release_allocated_value_but_fixme_should_propagate_errors(); 170 m_constructors.set("@interface_name@"sv, constructor); 171 172 prototype->define_direct_property(vm.names.constructor, constructor.ptr(), JS::Attribute::Writable | JS::Attribute::Configurable); 173 constructor->define_direct_property(vm.names.name, JS::PrimitiveString::create(vm, "@interface_name@"sv).release_allocated_value_but_fixme_should_propagate_errors(), JS::Attribute::Configurable); 174)~~~"); 175 176 if (legacy_constructor.has_value()) { 177 gen.set("legacy_interface_name", legacy_constructor->name); 178 gen.set("legacy_constructor_class", legacy_constructor->constructor_class); 179 gen.append(R"~~~( 180 auto legacy_constructor = heap().allocate<@legacy_constructor_class@>(realm, realm).release_allocated_value_but_fixme_should_propagate_errors(); 181 m_constructors.set("@legacy_interface_name@"sv, legacy_constructor); 182 183 legacy_constructor->define_direct_property(vm.names.name, JS::PrimitiveString::create(vm, "@legacy_interface_name@"sv).release_allocated_value_but_fixme_should_propagate_errors(), JS::Attribute::Configurable);)~~~"); 184 } 185 186 gen.append(R"~~~( 187} 188)~~~"); 189 }; 190 191 for (auto& interface : exposed_interfaces) { 192 auto gen = generator.fork(); 193 add_interface(gen, interface.name, interface.prototype_class, interface.constructor_class, lookup_legacy_constructor(interface)); 194 } 195 196 // FIXME: Special case WebAssembly. We should convert WASM to use IDL. 197 { 198 auto gen = generator.fork(); 199 add_interface(gen, "WebAssembly.Memory"sv, "WebAssemblyMemoryPrototype"sv, "WebAssemblyMemoryConstructor"sv, {}); 200 add_interface(gen, "WebAssembly.Instance"sv, "WebAssemblyInstancePrototype"sv, "WebAssemblyInstanceConstructor"sv, {}); 201 add_interface(gen, "WebAssembly.Module"sv, "WebAssemblyModulePrototype"sv, "WebAssemblyModuleConstructor"sv, {}); 202 add_interface(gen, "WebAssembly.Table"sv, "WebAssemblyTablePrototype"sv, "WebAssemblyTableConstructor"sv, {}); 203 } 204 205 generator.append(R"~~~( 206} 207)~~~"); 208 209 auto generated_intrinsics_path = LexicalPath(output_path).append("IntrinsicDefinitions.cpp"sv).string(); 210 auto generated_intrinsics_file = TRY(Core::File::open(generated_intrinsics_path, Core::File::OpenMode::Write)); 211 TRY(generated_intrinsics_file->write_until_depleted(generator.as_string_view().bytes())); 212 213 return {}; 214} 215 216static ErrorOr<void> generate_exposed_interface_header(StringView class_name, StringView output_path) 217{ 218 StringBuilder builder; 219 SourceGenerator generator(builder); 220 221 generator.set("global_object_snake_name", DeprecatedString(class_name).to_snakecase()); 222 generator.append(R"~~~( 223#pragma once 224 225#include <LibJS/Forward.h> 226 227namespace Web::Bindings { 228 229void add_@global_object_snake_name@_exposed_interfaces(JS::Object&); 230 231} 232 233)~~~"); 234 235 auto generated_header_path = LexicalPath(output_path).append(DeprecatedString::formatted("{}ExposedInterfaces.h", class_name)).string(); 236 auto generated_header_file = TRY(Core::File::open(generated_header_path, Core::File::OpenMode::Write)); 237 TRY(generated_header_file->write_until_depleted(generator.as_string_view().bytes())); 238 239 return {}; 240} 241 242static ErrorOr<void> generate_exposed_interface_implementation(StringView class_name, StringView output_path, Vector<IDL::Interface&>& exposed_interfaces) 243{ 244 StringBuilder builder; 245 SourceGenerator generator(builder); 246 247 generator.set("global_object_name", class_name); 248 generator.set("global_object_snake_name", DeprecatedString(class_name).to_snakecase()); 249 250 generator.append(R"~~~( 251#include <LibJS/Runtime/Object.h> 252#include <LibWeb/Bindings/Intrinsics.h> 253#include <LibWeb/Bindings/@global_object_name@ExposedInterfaces.h> 254)~~~"); 255 for (auto& interface : exposed_interfaces) { 256 auto gen = generator.fork(); 257 gen.set("prototype_class", interface.prototype_class); 258 gen.set("constructor_class", interface.constructor_class); 259 260 gen.append(R"~~~(#include <LibWeb/Bindings/@constructor_class@.h> 261#include <LibWeb/Bindings/@prototype_class@.h> 262)~~~"); 263 264 if (auto const& legacy_constructor = lookup_legacy_constructor(interface); legacy_constructor.has_value()) { 265 gen.set("legacy_constructor_class", legacy_constructor->constructor_class); 266 gen.append(R"~~~(#include <LibWeb/Bindings/@legacy_constructor_class@.h> 267)~~~"); 268 } 269 } 270 271 generator.append(R"~~~( 272namespace Web::Bindings { 273 274void add_@global_object_snake_name@_exposed_interfaces(JS::Object& global) 275{ 276 static constexpr u8 attr = JS::Attribute::Writable | JS::Attribute::Configurable; 277)~~~"); 278 279 auto add_interface = [](SourceGenerator& gen, StringView name, StringView prototype_class, Optional<LegacyConstructor> const& legacy_constructor) { 280 gen.set("interface_name", name); 281 gen.set("prototype_class", prototype_class); 282 283 gen.append(R"~~~( 284 global.define_intrinsic_accessor("@interface_name@", attr, [](auto& realm) -> JS::Value { return &ensure_web_constructor<@prototype_class@>(realm, "@interface_name@"sv); });)~~~"); 285 286 if (legacy_constructor.has_value()) { 287 gen.set("legacy_interface_name", legacy_constructor->name); 288 gen.append(R"~~~( 289 global.define_intrinsic_accessor("@legacy_interface_name@", attr, [](auto& realm) -> JS::Value { return &ensure_web_constructor<@prototype_class@>(realm, "@legacy_interface_name@"sv); });)~~~"); 290 } 291 }; 292 293 for (auto& interface : exposed_interfaces) { 294 auto gen = generator.fork(); 295 add_interface(gen, interface.name, interface.prototype_class, lookup_legacy_constructor(interface)); 296 } 297 298 generator.append(R"~~~( 299} 300 301} 302)~~~"); 303 304 auto generated_implementation_path = LexicalPath(output_path).append(DeprecatedString::formatted("{}ExposedInterfaces.cpp", class_name)).string(); 305 auto generated_implementation_file = TRY(Core::File::open(generated_implementation_path, Core::File::OpenMode::Write)); 306 TRY(generated_implementation_file->write_until_depleted(generator.as_string_view().bytes())); 307 308 return {}; 309} 310 311ErrorOr<int> serenity_main(Main::Arguments arguments) 312{ 313 Core::ArgsParser args_parser; 314 315 StringView output_path; 316 StringView base_path; 317 Vector<DeprecatedString> paths; 318 319 args_parser.add_option(output_path, "Path to output generated files into", "output-path", 'o', "output-path"); 320 args_parser.add_option(base_path, "Path to root of IDL file tree", "base-path", 'b', "base-path"); 321 args_parser.add_positional_argument(paths, "Paths of every IDL file that could be Exposed", "paths"); 322 args_parser.parse(arguments); 323 324 VERIFY(!paths.is_empty()); 325 VERIFY(!base_path.is_empty()); 326 327 const LexicalPath lexical_base(base_path); 328 329 // Read in all IDL files, we must own the storage for all of these for the lifetime of the program 330 Vector<DeprecatedString> file_contents; 331 for (DeprecatedString const& path : paths) { 332 auto file_or_error = Core::File::open(path, Core::File::OpenMode::Read); 333 if (file_or_error.is_error()) { 334 s_error_string = DeprecatedString::formatted("Unable to open file {}", path); 335 return Error::from_string_view(s_error_string); 336 } 337 auto file = file_or_error.release_value(); 338 auto string = MUST(file->read_until_eof()); 339 file_contents.append(DeprecatedString(ReadonlyBytes(string))); 340 } 341 VERIFY(paths.size() == file_contents.size()); 342 343 Vector<IDL::Parser> parsers; 344 Vector<IDL::Interface&> intrinsics; 345 Vector<IDL::Interface&> window_exposed; 346 Vector<IDL::Interface&> dedicated_worker_exposed; 347 Vector<IDL::Interface&> shared_worker_exposed; 348 // TODO: service_worker_exposed 349 350 for (size_t i = 0; i < paths.size(); ++i) { 351 IDL::Parser parser(paths[i], file_contents[i], lexical_base.string()); 352 TRY(add_to_interface_sets(parser.parse(), intrinsics, window_exposed, dedicated_worker_exposed, shared_worker_exposed)); 353 parsers.append(move(parser)); 354 } 355 356 TRY(generate_forwarding_header(output_path, intrinsics)); 357 TRY(generate_intrinsic_definitions(output_path, intrinsics)); 358 359 TRY(generate_exposed_interface_header("Window"sv, output_path)); 360 TRY(generate_exposed_interface_header("DedicatedWorker"sv, output_path)); 361 TRY(generate_exposed_interface_header("SharedWorker"sv, output_path)); 362 // TODO: ServiceWorkerExposed.h 363 364 TRY(generate_exposed_interface_implementation("Window"sv, output_path, window_exposed)); 365 TRY(generate_exposed_interface_implementation("DedicatedWorker"sv, output_path, dedicated_worker_exposed)); 366 TRY(generate_exposed_interface_implementation("SharedWorker"sv, output_path, shared_worker_exposed)); 367 // TODO: ServiceWorkerExposed.cpp 368 369 return 0; 370} 371 372enum ExposedTo { 373 Nobody = 0x0, 374 DedicatedWorker = 0x1, 375 SharedWorker = 0x2, 376 ServiceWorker = 0x4, 377 AudioWorklet = 0x8, 378 Window = 0x10, 379 AllWorkers = 0xF, // FIXME: Is "AudioWorklet" a Worker? We'll assume it is for now 380 All = 0x1F, 381}; 382AK_ENUM_BITWISE_OPERATORS(ExposedTo); 383 384static ErrorOr<ExposedTo> parse_exposure_set(IDL::Interface& interface) 385{ 386 // NOTE: This roughly follows the definitions of https://webidl.spec.whatwg.org/#Exposed 387 // It does not remotely interpret all the abstract operations therein though. 388 389 auto maybe_exposed = interface.extended_attributes.get("Exposed"); 390 if (!maybe_exposed.has_value()) { 391 s_error_string = DeprecatedString::formatted("Interface {} is missing extended attribute Exposed", interface.name); 392 return Error::from_string_view(s_error_string); 393 } 394 auto exposed = maybe_exposed.value().trim_whitespace(); 395 if (exposed == "*"sv) 396 return ExposedTo::All; 397 if (exposed == "Window"sv) 398 return ExposedTo::Window; 399 if (exposed == "Worker"sv) 400 return ExposedTo::AllWorkers; 401 if (exposed == "AudioWorklet"sv) 402 return ExposedTo::AudioWorklet; 403 404 if (exposed[0] == '(') { 405 ExposedTo whom = Nobody; 406 for (StringView candidate : exposed.substring_view(1, exposed.length() - 1).split_view(',')) { 407 candidate = candidate.trim_whitespace(); 408 if (candidate == "Window"sv) { 409 whom |= ExposedTo::Window; 410 } else if (candidate == "Worker"sv) { 411 whom |= ExposedTo::AllWorkers; 412 } else if (candidate == "DedicatedWorker"sv) { 413 whom |= ExposedTo::DedicatedWorker; 414 } else if (candidate == "SharedWorker"sv) { 415 whom |= ExposedTo::SharedWorker; 416 } else if (candidate == "ServiceWorker"sv) { 417 whom |= ExposedTo::ServiceWorker; 418 } else if (candidate == "AudioWorklet"sv) { 419 whom |= ExposedTo::AudioWorklet; 420 } else { 421 s_error_string = DeprecatedString::formatted("Unknown Exposed attribute candidate {} in {} in {}", candidate, exposed, interface.name); 422 return Error::from_string_view(s_error_string); 423 } 424 } 425 if (whom == ExposedTo::Nobody) { 426 s_error_string = DeprecatedString::formatted("Unknown Exposed attribute {} in {}", exposed, interface.name); 427 return Error::from_string_view(s_error_string); 428 } 429 return whom; 430 } 431 432 s_error_string = DeprecatedString::formatted("Unknown Exposed attribute {} in {}", exposed, interface.name); 433 return Error::from_string_view(s_error_string); 434} 435 436ErrorOr<void> add_to_interface_sets(IDL::Interface& interface, Vector<IDL::Interface&>& intrinsics, Vector<IDL::Interface&>& window_exposed, Vector<IDL::Interface&>& dedicated_worker_exposed, Vector<IDL::Interface&>& shared_worker_exposed) 437{ 438 // TODO: Add service worker exposed and audio worklet exposed 439 auto whom = TRY(parse_exposure_set(interface)); 440 VERIFY(whom != ExposedTo::Nobody); 441 442 if ((whom & ExposedTo::Window) || (whom & ExposedTo::DedicatedWorker) || (whom & ExposedTo::SharedWorker)) 443 intrinsics.append(interface); 444 445 if (whom & ExposedTo::Window) 446 window_exposed.append(interface); 447 448 if (whom & ExposedTo::DedicatedWorker) 449 dedicated_worker_exposed.append(interface); 450 451 if (whom & ExposedTo::SharedWorker) 452 shared_worker_exposed.append(interface); 453 454 return {}; 455}