summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp322
1 files changed, 190 insertions, 132 deletions
diff --git a/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp b/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp
index 81509d2880..2cd040f43f 100644
--- a/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp
+++ b/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp
@@ -520,6 +520,140 @@ static bool is_wrappable_type(const IDL::Type& type)
return false;
}
+template<typename ParameterType>
+static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter, const String& js_name, const String& js_suffix, const String& cpp_name, bool return_void = false, bool legacy_null_to_empty_string = false, bool optional = false)
+{
+ auto scoped_generator = generator.fork();
+ scoped_generator.set("cpp_name", make_input_acceptable_cpp(cpp_name));
+ scoped_generator.set("js_name", js_name);
+ scoped_generator.set("js_suffix", js_suffix);
+ scoped_generator.set("legacy_null_to_empty_string", legacy_null_to_empty_string ? "true" : "false");
+ scoped_generator.set("parameter.type.name", parameter.type.name);
+
+ if (return_void)
+ scoped_generator.set("return_statement", "return;");
+ else
+ scoped_generator.set("return_statement", "return {};");
+
+ // FIXME: Add support for optional to all types
+ if (parameter.type.name == "DOMString") {
+ if (!optional) {
+ scoped_generator.append(R"~~~(
+ auto @cpp_name@ = @js_name@@js_suffix@.to_string(global_object, @legacy_null_to_empty_string@);
+ if (vm.exception())
+ @return_statement@
+)~~~");
+ } else {
+ scoped_generator.append(R"~~~(
+ String @cpp_name@;
+ if (!@js_name@@js_suffix@.is_undefined()) {
+ @cpp_name@ = @js_name@@js_suffix@.to_string(global_object, @legacy_null_to_empty_string@);
+ if (vm.exception())
+ @return_statement@
+ }
+)~~~");
+ }
+ } else if (parameter.type.name == "EventListener") {
+ scoped_generator.append(R"~~~(
+ if (!@js_name@@js_suffix@.is_function()) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotA, "Function");
+ @return_statement@
+ }
+ auto @cpp_name@ = adopt(*new EventListener(JS::make_handle(&@js_name@@js_suffix@.as_function())));
+)~~~");
+ } else if (is_wrappable_type(parameter.type)) {
+ scoped_generator.append(R"~~~(
+ auto @cpp_name@_object = @js_name@@js_suffix@.to_object(global_object);
+ if (vm.exception())
+ @return_statement@
+
+ if (!is<@parameter.type.name@Wrapper>(@cpp_name@_object)) {
+ vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotA, "@parameter.type.name@");
+ @return_statement@
+ }
+
+ auto& @cpp_name@ = static_cast<@parameter.type.name@Wrapper*>(@cpp_name@_object)->impl();
+)~~~");
+ } else if (parameter.type.name == "double") {
+ scoped_generator.append(R"~~~(
+ auto @cpp_name@ = @js_name@@js_suffix@.to_double(global_object);
+ if (vm.exception())
+ @return_statement@
+)~~~");
+ } else if (parameter.type.name == "boolean") {
+ scoped_generator.append(R"~~~(
+ auto @cpp_name@ = @js_name@@js_suffix@.to_boolean();
+)~~~");
+ } else if (parameter.type.name == "unsigned long") {
+ scoped_generator.append(R"~~~(
+ auto @cpp_name@ = @js_name@@js_suffix@.to_u32(global_object);
+ if (vm.exception())
+ @return_statement@
+)~~~");
+ } else if (parameter.type.name == "EventHandler") {
+ // x.onfoo = function() { ... }
+ scoped_generator.append(R"~~~(
+ HTML::EventHandler @cpp_name@;
+ if (@js_name@@js_suffix@.is_function()) {
+ @cpp_name@.callback = JS::make_handle(&@js_name@@js_suffix@.as_function());
+ } else if (@js_name@@js_suffix@.is_string()) {
+ @cpp_name@.string = @js_name@@js_suffix@.as_string().string();
+ } else {
+ @return_statement@
+ }
+)~~~");
+ } else {
+ dbgln("Unimplemented JS-to-C++ conversion: {}", parameter.type.name);
+ ASSERT_NOT_REACHED();
+ }
+};
+
+template<typename FunctionType>
+static void generate_argument_count_check(SourceGenerator& generator, FunctionType& function)
+{
+ auto argument_count_check_generator = generator.fork();
+ argument_count_check_generator.set("function.name", function.name);
+ argument_count_check_generator.set("function.nargs", String::number(function.length()));
+
+ if (function.length() == 0)
+ return;
+ if (function.length() == 1) {
+ argument_count_check_generator.set(".bad_arg_count", "JS::ErrorType::BadArgCountOne");
+ argument_count_check_generator.set(".arg_count_suffix", "");
+ } else {
+ argument_count_check_generator.set(".bad_arg_count", "JS::ErrorType::BadArgCountMany");
+ argument_count_check_generator.set(".arg_count_suffix", String::formatted(", \"{}\"", function.length()));
+ }
+
+ argument_count_check_generator.append(R"~~~(
+ if (vm.argument_count() < @function.nargs@) {
+ vm.throw_exception<JS::TypeError>(global_object, @.bad_arg_count@, "@function.name@"@.arg_count_suffix@);
+ return {};
+ }
+)~~~");
+};
+
+static void generate_arguments(SourceGenerator& generator, const Vector<IDL::Parameter>& parameters, StringBuilder& arguments_builder, bool return_void = false)
+{
+ auto arguments_generator = generator.fork();
+
+ Vector<String> parameter_names;
+ size_t argument_index = 0;
+ for (auto& parameter : parameters) {
+ parameter_names.append(make_input_acceptable_cpp(snake_name(parameter.name)));
+ arguments_generator.set("argument.index", String::number(argument_index));
+
+ arguments_generator.append(R"~~~(
+ auto arg@argument.index@ = vm.argument(@argument.index@);
+)~~~");
+ // FIXME: Parameters can have [LegacyNullToEmptyString] attached.
+ generate_to_cpp(generator, parameter, "arg", String::number(argument_index), snake_name(parameter.name), return_void, false, parameter.optional);
+ ++argument_index;
+ }
+
+ arguments_builder.join(", ", parameter_names);
+};
+
static void generate_header(const IDL::Interface& interface)
{
StringBuilder builder;
@@ -566,6 +700,8 @@ namespace Web::Bindings {
class @wrapper_class@ : public @wrapper_base_class@ {
JS_OBJECT(@wrapper_class@, @wrapper_base_class@);
public:
+ static @wrapper_class@* create(JS::GlobalObject&, @fully_qualified_name@&);
+
@wrapper_class@(JS::GlobalObject&, @fully_qualified_name@&);
virtual void initialize(JS::GlobalObject&) override;
virtual ~@wrapper_class@() override;
@@ -656,6 +792,11 @@ using namespace Web::HTML;
namespace Web::Bindings {
+@wrapper_class@* @wrapper_class@::create(JS::GlobalObject& global_object, @fully_qualified_name@& impl)
+{
+ return global_object.heap().allocate<@wrapper_class@>(global_object, global_object, impl);
+}
+
)~~~");
if (interface.wrapper_base_class == "Wrapper") {
@@ -798,12 +939,52 @@ JS::Value @constructor_class@::call()
JS::Value @constructor_class@::construct(Function&)
{
+)~~~");
+
+ if (interface.constructors.is_empty()) {
+ // No constructor
+ generator.set("constructor.length", "0");
+ generator.append(R"~~~(
+ vm().throw_exception<JS::TypeError>(global_object(), JS::ErrorType::NotAConstructor, "@name@");
return {};
-#if 0
- // FIXME: It would be cool to construct stuff!
- auto& window = static_cast<WindowObject&>(global_object());
- return heap().allocate<@wrapper_class@>(window, window, @fully_qualified_name@::create(window.impl()));
-#endif
+)~~~");
+ } else if (interface.constructors.size() == 1) {
+ // Single constructor
+
+ auto& constructor = interface.constructors[0];
+ generator.set("constructor.length", String::number(constructor.length()));
+
+ generator.append(R"~~~(
+ [[maybe_unused]] auto& vm = this->vm();
+ auto& global_object = this->global_object();
+
+ auto& window = static_cast<WindowObject&>(global_object);
+)~~~");
+
+ if (!constructor.parameters.is_empty()) {
+ generate_argument_count_check(generator, constructor);
+
+ StringBuilder arguments_builder;
+ generate_arguments(generator, constructor.parameters, arguments_builder);
+ generator.set(".constructor_arguments", arguments_builder.string_view());
+
+ generator.append(R"~~~(
+ auto impl = @fully_qualified_name@::create_with_global_object(window, @.constructor_arguments@);
+)~~~");
+ } else {
+ generator.append(R"~~~(
+ auto impl = @fully_qualified_name@::create_with_global_object(window);
+)~~~");
+ }
+ generator.append(R"~~~(
+ return @wrapper_class@::create(global_object, impl);
+)~~~");
+ } else {
+ // Multiple constructor overloads - can't do that yet.
+ TODO();
+ }
+
+ generator.append(R"~~~(
}
void @constructor_class@::initialize(JS::GlobalObject& global_object)
@@ -814,7 +995,7 @@ void @constructor_class@::initialize(JS::GlobalObject& global_object)
NativeFunction::initialize(global_object);
define_property(vm.names.prototype, &window.ensure_web_prototype<@prototype_class@>("@name@"), 0);
- define_property(vm.names.length, JS::Value(1), JS::Attribute::Configurable);
+ define_property(vm.names.length, JS::Value(@constructor.length@), JS::Attribute::Configurable);
)~~~");
@@ -1061,112 +1242,6 @@ static @fully_qualified_name@* impl_from(JS::VM& vm, JS::GlobalObject& global_ob
)~~~");
}
- auto generate_to_cpp = [&](auto& parameter, auto& js_name, const auto& js_suffix, auto cpp_name, bool return_void = false, bool legacy_null_to_empty_string = false, bool optional = false) {
- auto scoped_generator = generator.fork();
- scoped_generator.set("cpp_name", make_input_acceptable_cpp(cpp_name));
- scoped_generator.set("js_name", js_name);
- scoped_generator.set("js_suffix", js_suffix);
- scoped_generator.set("legacy_null_to_empty_string", legacy_null_to_empty_string ? "true" : "false");
- scoped_generator.set("parameter.type.name", parameter.type.name);
-
- if (return_void)
- scoped_generator.set("return_statement", "return;");
- else
- scoped_generator.set("return_statement", "return {};");
-
- // FIXME: Add support for optional to all types
- if (parameter.type.name == "DOMString") {
- if (!optional) {
- scoped_generator.append(R"~~~(
- auto @cpp_name@ = @js_name@@js_suffix@.to_string(global_object, @legacy_null_to_empty_string@);
- if (vm.exception())
- @return_statement@
-)~~~");
- } else {
- scoped_generator.append(R"~~~(
- String @cpp_name@;
- if (!@js_name@@js_suffix@.is_undefined()) {
- @cpp_name@ = @js_name@@js_suffix@.to_string(global_object, @legacy_null_to_empty_string@);
- if (vm.exception())
- @return_statement@
- }
-)~~~");
- }
- } else if (parameter.type.name == "EventListener") {
- scoped_generator.append(R"~~~(
- if (!@js_name@@js_suffix@.is_function()) {
- vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotA, "Function");
- @return_statement@
- }
- auto @cpp_name@ = adopt(*new EventListener(JS::make_handle(&@js_name@@js_suffix@.as_function())));
-)~~~");
- } else if (is_wrappable_type(parameter.type)) {
- scoped_generator.append(R"~~~(
- auto @cpp_name@_object = @js_name@@js_suffix@.to_object(global_object);
- if (vm.exception())
- @return_statement@
-
- if (!is<@parameter.type.name@Wrapper>(@cpp_name@_object)) {
- vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotA, "@parameter.type.name@");
- @return_statement@
- }
-
- auto& @cpp_name@ = static_cast<@parameter.type.name@Wrapper*>(@cpp_name@_object)->impl();
-)~~~");
- } else if (parameter.type.name == "double") {
- scoped_generator.append(R"~~~(
- auto @cpp_name@ = @js_name@@js_suffix@.to_double(global_object);
- if (vm.exception())
- @return_statement@
-)~~~");
- } else if (parameter.type.name == "boolean") {
- scoped_generator.append(R"~~~(
- auto @cpp_name@ = @js_name@@js_suffix@.to_boolean();
-)~~~");
- } else if (parameter.type.name == "unsigned long") {
- scoped_generator.append(R"~~~(
- auto @cpp_name@ = @js_name@@js_suffix@.to_u32(global_object);
- if (vm.exception())
- @return_statement@
-)~~~");
- } else if (parameter.type.name == "EventHandler") {
- // x.onfoo = function() { ... }
- scoped_generator.append(R"~~~(
- HTML::EventHandler @cpp_name@;
- if (@js_name@@js_suffix@.is_function()) {
- @cpp_name@.callback = JS::make_handle(&@js_name@@js_suffix@.as_function());
- } else if (@js_name@@js_suffix@.is_string()) {
- @cpp_name@.string = @js_name@@js_suffix@.as_string().string();
- } else {
- @return_statement@
- }
-)~~~");
- } else {
- dbgln("Unimplemented JS-to-C++ conversion: {}", parameter.type.name);
- ASSERT_NOT_REACHED();
- }
- };
-
- auto generate_arguments = [&](auto& parameters, auto& arguments_builder, bool return_void = false) {
- auto arguments_generator = generator.fork();
-
- Vector<String> parameter_names;
- size_t argument_index = 0;
- for (auto& parameter : parameters) {
- parameter_names.append(make_input_acceptable_cpp(snake_name(parameter.name)));
- arguments_generator.set("argument.index", String::number(argument_index));
-
- arguments_generator.append(R"~~~(
- auto arg@argument.index@ = vm.argument(@argument.index@);
-)~~~");
- // FIXME: Parameters can have [LegacyNullToEmptyString] attached.
- generate_to_cpp(parameter, "arg", String::number(argument_index), snake_name(parameter.name), return_void, false, parameter.optional);
- ++argument_index;
- }
-
- arguments_builder.join(", ", parameter_names);
- };
-
auto generate_return_statement = [&](auto& return_type) {
auto scoped_generator = generator.fork();
scoped_generator.set("return_type", return_type.name);
@@ -1292,7 +1367,7 @@ JS_DEFINE_NATIVE_SETTER(@prototype_class@::@attribute.setter_callback@)
return;
)~~~");
- generate_to_cpp(attribute, "value", "", "cpp_value", true, attribute.extended_attributes.contains("LegacyNullToEmptyString"));
+ generate_to_cpp(generator, attribute, "value", "", "cpp_value", true, attribute.extended_attributes.contains("LegacyNullToEmptyString"));
if (attribute.extended_attributes.contains("Reflect")) {
if (attribute.type.name != "boolean") {
@@ -1324,7 +1399,6 @@ JS_DEFINE_NATIVE_SETTER(@prototype_class@::@attribute.setter_callback@)
auto function_generator = generator.fork();
function_generator.set("function.name", function.name);
function_generator.set("function.name:snakecase", snake_name(function.name));
- function_generator.set("function.nargs", String::number(function.length()));
function_generator.append(R"~~~(
JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::@function.name:snakecase@)
@@ -1334,26 +1408,10 @@ JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::@function.name:snakecase@)
return {};
)~~~");
- if (function.length() > 0) {
- if (function.length() == 1) {
- function_generator.set(".bad_arg_count", "JS::ErrorType::BadArgCountOne");
- function_generator.set(".arg_count_suffix", "");
- } else {
- function_generator.set(".bad_arg_count", "JS::ErrorType::BadArgCountMany");
- function_generator.set(".arg_count_suffix", String::formatted(", \"{}\"", function.length()));
- }
-
- function_generator.append(R"~~~(
- if (vm.argument_count() < @function.nargs@) {
- vm.throw_exception<JS::TypeError>(global_object, @.bad_arg_count@, "@function.name@"@.arg_count_suffix@);
- return {};
- }
-)~~~");
- }
+ generate_argument_count_check(generator, function);
StringBuilder arguments_builder;
- generate_arguments(function.parameters, arguments_builder);
-
+ generate_arguments(generator, function.parameters, arguments_builder);
function_generator.set(".arguments", arguments_builder.string_view());
if (function.return_type.name != "undefined") {