diff options
author | Andrew Kaster <akaster@serenityos.org> | 2021-08-08 01:31:23 -0600 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2021-08-28 08:44:17 +0100 |
commit | 63956b36d06866b1d4660c2768a9c7eab0e182fb (patch) | |
tree | c5bd525d54d4b7e5476cbd4e194e2e130ecd7b69 /Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp | |
parent | fb15cdcc107adb13cb3c1cea831d36075d6b427b (diff) | |
download | serenity-63956b36d06866b1d4660c2768a9c7eab0e182fb.zip |
Everywhere: Move all host tools into the Lagom/Tools subdirectory
This allows us to remove all the add_subdirectory calls from the top
level CMakeLists.txt that referred to targets linking LagomCore.
Segregating the host tools and Serenity targets helps us get to a place
where the main Serenity build can simply use a CMake toolchain file
rather than swapping all the compiler/sysroot variables after building
host libraries and tools.
Diffstat (limited to 'Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp')
-rw-r--r-- | Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp | 899 |
1 files changed, 899 insertions, 0 deletions
diff --git a/Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp b/Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp new file mode 100644 index 0000000000..90f451e863 --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp @@ -0,0 +1,899 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/Debug.h> +#include <AK/Function.h> +#include <AK/GenericLexer.h> +#include <AK/HashMap.h> +#include <AK/SourceGenerator.h> +#include <AK/StringBuilder.h> +#include <LibCore/File.h> +#include <ctype.h> +#include <stdio.h> + +struct Parameter { + Vector<String> attributes; + String type; + String name; +}; + +static String pascal_case(String const& identifier) +{ + StringBuilder builder; + bool was_new_word = true; + for (auto ch : identifier) { + if (ch == '_') { + was_new_word = true; + continue; + } + if (was_new_word) { + builder.append(toupper(ch)); + was_new_word = false; + } else + builder.append(ch); + } + return builder.to_string(); +} + +struct Message { + String name; + bool is_synchronous { false }; + Vector<Parameter> inputs; + Vector<Parameter> outputs; + + String response_name() const + { + StringBuilder builder; + builder.append(pascal_case(name)); + builder.append("Response"); + return builder.to_string(); + } +}; + +struct Endpoint { + Vector<String> includes; + String name; + u32 magic; + Vector<Message> messages; +}; + +static bool is_primitive_type(String const& type) +{ + return type.is_one_of("u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "bool", "double", "float", "int", "unsigned", "unsigned int"); +} + +static String message_name(String const& endpoint, String& message, bool is_response) +{ + StringBuilder builder; + builder.append("Messages::"); + builder.append(endpoint); + builder.append("::"); + builder.append(pascal_case(message)); + if (is_response) + builder.append("Response"); + return builder.to_string(); +} + +int main(int argc, char** argv) +{ + if (argc != 2) { + outln("usage: {} <IPC endpoint definition file>", argv[0]); + return 0; + } + + auto file = Core::File::construct(argv[1]); + if (!file->open(Core::OpenMode::ReadOnly)) { + warnln("Error: Cannot open {}: {}", argv[1], file->error_string()); + return 1; + } + + auto file_contents = file->read_all(); + GenericLexer lexer(file_contents); + + Vector<Endpoint> endpoints; + + auto assert_specific = [&](char ch) { + if (lexer.peek() != ch) + warnln("assert_specific: wanted '{}', but got '{}' at index {}", ch, lexer.peek(), lexer.tell()); + bool saw_expected = lexer.consume_specific(ch); + VERIFY(saw_expected); + }; + + auto consume_whitespace = [&] { + lexer.ignore_while([](char ch) { return isspace(ch); }); + if (lexer.peek() == '/' && lexer.peek(1) == '/') + lexer.ignore_until([](char ch) { return ch == '\n'; }); + }; + + auto parse_parameter = [&](Vector<Parameter>& storage) { + for (;;) { + Parameter parameter; + consume_whitespace(); + if (lexer.peek() == ')') + break; + if (lexer.consume_specific('[')) { + for (;;) { + if (lexer.consume_specific(']')) { + consume_whitespace(); + break; + } + if (lexer.consume_specific(',')) { + consume_whitespace(); + } + auto attribute = lexer.consume_until([](char ch) { return ch == ']' || ch == ','; }); + parameter.attributes.append(attribute); + consume_whitespace(); + } + } + parameter.type = lexer.consume_until([](char ch) { return isspace(ch); }); + consume_whitespace(); + parameter.name = lexer.consume_until([](char ch) { return isspace(ch) || ch == ',' || ch == ')'; }); + consume_whitespace(); + storage.append(move(parameter)); + if (lexer.consume_specific(',')) + continue; + if (lexer.peek() == ')') + break; + } + }; + + auto parse_parameters = [&](Vector<Parameter>& storage) { + for (;;) { + consume_whitespace(); + parse_parameter(storage); + consume_whitespace(); + if (lexer.consume_specific(',')) + continue; + if (lexer.peek() == ')') + break; + } + }; + + auto parse_message = [&] { + Message message; + consume_whitespace(); + message.name = lexer.consume_until([](char ch) { return isspace(ch) || ch == '('; }); + consume_whitespace(); + assert_specific('('); + parse_parameters(message.inputs); + assert_specific(')'); + consume_whitespace(); + assert_specific('='); + + auto type = lexer.consume(); + if (type == '>') + message.is_synchronous = true; + else if (type == '|') + message.is_synchronous = false; + else + VERIFY_NOT_REACHED(); + + consume_whitespace(); + + if (message.is_synchronous) { + assert_specific('('); + parse_parameters(message.outputs); + assert_specific(')'); + } + + consume_whitespace(); + + endpoints.last().messages.append(move(message)); + }; + + auto parse_messages = [&] { + for (;;) { + consume_whitespace(); + if (lexer.peek() == '}') + break; + parse_message(); + consume_whitespace(); + } + }; + + auto parse_include = [&] { + String include; + consume_whitespace(); + include = lexer.consume_while([](char ch) { return ch != '\n'; }); + consume_whitespace(); + + endpoints.last().includes.append(move(include)); + }; + + auto parse_includes = [&] { + for (;;) { + consume_whitespace(); + if (lexer.peek() != '#') + break; + parse_include(); + consume_whitespace(); + } + }; + + auto parse_endpoint = [&] { + endpoints.empend(); + consume_whitespace(); + parse_includes(); + consume_whitespace(); + lexer.consume_specific("endpoint"); + consume_whitespace(); + endpoints.last().name = lexer.consume_while([](char ch) { return !isspace(ch); }); + endpoints.last().magic = Traits<String>::hash(endpoints.last().name); + consume_whitespace(); + if (lexer.peek() == '[') { + // This only supports a single parameter for now, and adding multiple + // endpoint parameter support is left as an exercise for the reader. :^) + + lexer.consume_specific('['); + consume_whitespace(); + + auto parameter = lexer.consume_while([](char ch) { return !isspace(ch) && ch != '='; }); + consume_whitespace(); + assert_specific('='); + consume_whitespace(); + + if (parameter == "magic") { + // "magic" overwrites the default magic with a hardcoded one. + auto magic_string = lexer.consume_while([](char ch) { return !isspace(ch) && ch != ']'; }); + endpoints.last().magic = magic_string.to_uint().value(); + } else { + warnln("parse_endpoint: unknown parameter '{}' passed", parameter); + VERIFY_NOT_REACHED(); + } + + assert_specific(']'); + consume_whitespace(); + } + assert_specific('{'); + parse_messages(); + assert_specific('}'); + consume_whitespace(); + }; + + while (lexer.tell() < file_contents.size()) + parse_endpoint(); + + StringBuilder builder; + SourceGenerator generator { builder }; + + generator.append("#pragma once\n"); + + // This must occur before LibIPC/Decoder.h + for (auto& endpoint : endpoints) { + for (auto& include : endpoint.includes) { + generator.append(include); + generator.append("\n"); + } + } + + generator.append(R"~~~(#include <AK/MemoryStream.h> +#include <AK/OwnPtr.h> +#include <AK/Result.h> +#include <AK/Utf8View.h> +#include <LibIPC/Connection.h> +#include <LibIPC/Decoder.h> +#include <LibIPC/Dictionary.h> +#include <LibIPC/Encoder.h> +#include <LibIPC/File.h> +#include <LibIPC/Message.h> +#include <LibIPC/Stub.h> + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdefaulted-function-deleted" +#endif +)~~~"); + + for (auto& endpoint : endpoints) { + auto endpoint_generator = generator.fork(); + + endpoint_generator.set("endpoint.name", endpoint.name); + endpoint_generator.set("endpoint.magic", String::number(endpoint.magic)); + + endpoint_generator.append(R"~~~( +namespace Messages::@endpoint.name@ { +)~~~"); + + HashMap<String, int> message_ids; + + endpoint_generator.append(R"~~~( +enum class MessageID : i32 { +)~~~"); + for (auto& message : endpoint.messages) { + auto message_generator = endpoint_generator.fork(); + + message_ids.set(message.name, message_ids.size() + 1); + message_generator.set("message.name", message.name); + message_generator.set("message.pascal_name", pascal_case(message.name)); + message_generator.set("message.id", String::number(message_ids.size())); + + message_generator.append(R"~~~( + @message.pascal_name@ = @message.id@, +)~~~"); + if (message.is_synchronous) { + message_ids.set(message.response_name(), message_ids.size() + 1); + message_generator.set("message.name", message.response_name()); + message_generator.set("message.pascal_name", pascal_case(message.response_name())); + message_generator.set("message.id", String::number(message_ids.size())); + + message_generator.append(R"~~~( + @message.pascal_name@ = @message.id@, +)~~~"); + } + } + endpoint_generator.append(R"~~~( +}; +)~~~"); + + auto constructor_for_message = [&](const String& name, const Vector<Parameter>& parameters) { + StringBuilder builder; + builder.append(name); + + if (parameters.is_empty()) { + builder.append("() {}"); + return builder.to_string(); + } + + builder.append('('); + for (size_t i = 0; i < parameters.size(); ++i) { + auto& parameter = parameters[i]; + builder.append(parameter.type); + builder.append(" "); + builder.append(parameter.name); + if (i != parameters.size() - 1) + builder.append(", "); + } + builder.append(") : "); + for (size_t i = 0; i < parameters.size(); ++i) { + auto& parameter = parameters[i]; + builder.append("m_"); + builder.append(parameter.name); + builder.append("(move("); + builder.append(parameter.name); + builder.append("))"); + if (i != parameters.size() - 1) + builder.append(", "); + } + builder.append(" {}"); + return builder.to_string(); + }; + + auto do_message = [&](const String& name, const Vector<Parameter>& parameters, const String& response_type = {}) { + auto message_generator = endpoint_generator.fork(); + auto pascal_name = pascal_case(name); + message_generator.set("message.name", name); + message_generator.set("message.pascal_name", pascal_name); + message_generator.set("message.response_type", response_type); + message_generator.set("message.constructor", constructor_for_message(pascal_name, parameters)); + + message_generator.append(R"~~~( +class @message.pascal_name@ final : public IPC::Message { +public: +)~~~"); + + if (!response_type.is_null()) + message_generator.append(R"~~~( + typedef class @message.response_type@ ResponseType; +)~~~"); + + message_generator.append(R"~~~( + @message.pascal_name@(decltype(nullptr)) : m_ipc_message_valid(false) { } + @message.pascal_name@(@message.pascal_name@ const&) = default; + @message.pascal_name@(@message.pascal_name@&&) = default; + @message.pascal_name@& operator=(@message.pascal_name@ const&) = default; + @message.constructor@ + virtual ~@message.pascal_name@() override {} + + virtual u32 endpoint_magic() const override { return @endpoint.magic@; } + virtual i32 message_id() const override { return (int)MessageID::@message.pascal_name@; } + static i32 static_message_id() { return (int)MessageID::@message.pascal_name@; } + virtual const char* message_name() const override { return "@endpoint.name@::@message.pascal_name@"; } + + static OwnPtr<@message.pascal_name@> decode(InputMemoryStream& stream, [[maybe_unused]] int sockfd) + { + IPC::Decoder decoder { stream, sockfd }; +)~~~"); + + for (auto& parameter : parameters) { + auto parameter_generator = message_generator.fork(); + + parameter_generator.set("parameter.type", parameter.type); + parameter_generator.set("parameter.name", parameter.name); + + if (parameter.type == "bool") + parameter_generator.set("parameter.initial_value", "false"); + else + parameter_generator.set("parameter.initial_value", "{}"); + + parameter_generator.append(R"~~~( + @parameter.type@ @parameter.name@ = @parameter.initial_value@; + if (!decoder.decode(@parameter.name@)) + return {}; +)~~~"); + + if (parameter.attributes.contains_slow("UTF8")) { + parameter_generator.append(R"~~~( + if (!Utf8View(@parameter.name@).validate()) + return {}; +)~~~"); + } + } + + StringBuilder builder; + for (size_t i = 0; i < parameters.size(); ++i) { + auto& parameter = parameters[i]; + builder.append("move("); + builder.append(parameter.name); + builder.append(")"); + if (i != parameters.size() - 1) + builder.append(", "); + } + + message_generator.set("message.constructor_call_parameters", builder.build()); + + message_generator.append(R"~~~( + return make<@message.pascal_name@>(@message.constructor_call_parameters@); + } +)~~~"); + + message_generator.append(R"~~~( + virtual bool valid() const { return m_ipc_message_valid; } + + virtual IPC::MessageBuffer encode() const override + { + VERIFY(valid()); + + IPC::MessageBuffer buffer; + IPC::Encoder stream(buffer); + stream << endpoint_magic(); + stream << (int)MessageID::@message.pascal_name@; +)~~~"); + + for (auto& parameter : parameters) { + auto parameter_generator = message_generator.fork(); + + parameter_generator.set("parameter.name", parameter.name); + parameter_generator.append(R"~~~( + stream << m_@parameter.name@; +)~~~"); + } + + message_generator.append(R"~~~( + return buffer; + } +)~~~"); + + for (auto& parameter : parameters) { + auto parameter_generator = message_generator.fork(); + parameter_generator.set("parameter.type", parameter.type); + parameter_generator.set("parameter.name", parameter.name); + parameter_generator.append(R"~~~( + const @parameter.type@& @parameter.name@() const { return m_@parameter.name@; } + @parameter.type@ take_@parameter.name@() { return move(m_@parameter.name@); } +)~~~"); + } + + message_generator.append(R"~~~( +private: + bool m_ipc_message_valid { true }; + )~~~"); + + for (auto& parameter : parameters) { + auto parameter_generator = message_generator.fork(); + parameter_generator.set("parameter.type", parameter.type); + parameter_generator.set("parameter.name", parameter.name); + parameter_generator.append(R"~~~( + @parameter.type@ m_@parameter.name@; +)~~~"); + } + + message_generator.append(R"~~~( +}; + )~~~"); + }; + for (auto& message : endpoint.messages) { + String response_name; + if (message.is_synchronous) { + response_name = message.response_name(); + do_message(response_name, message.outputs); + } + do_message(message.name, message.inputs, response_name); + } + + endpoint_generator.append(R"~~~( +} // namespace Messages::@endpoint.name@ + )~~~"); + + endpoint_generator.append(R"~~~( +template<typename LocalEndpoint, typename PeerEndpoint> +class @endpoint.name@Proxy { +public: + // Used to disambiguate the constructor call. + struct Tag { }; + + @endpoint.name@Proxy(IPC::Connection<LocalEndpoint, PeerEndpoint>& connection, Tag) + : m_connection(connection) + { } +)~~~"); + + for (auto& message : endpoint.messages) { + auto message_generator = endpoint_generator.fork(); + + auto do_implement_proxy = [&](String const& name, Vector<Parameter> const& parameters, bool is_synchronous, bool is_try) { + String return_type = "void"; + if (is_synchronous) { + if (message.outputs.size() == 1) + return_type = message.outputs[0].type; + else if (!message.outputs.is_empty()) + return_type = message_name(endpoint.name, message.name, true); + } + String inner_return_type = return_type; + if (is_try) { + StringBuilder builder; + builder.append("Result<"); + builder.append(return_type); + builder.append(", IPC::ErrorCode>"); + return_type = builder.to_string(); + } + message_generator.set("message.name", message.name); + message_generator.set("message.pascal_name", pascal_case(message.name)); + message_generator.set("message.complex_return_type", return_type); + message_generator.set("async_prefix_maybe", is_synchronous ? "" : "async_"); + message_generator.set("try_prefix_maybe", is_try ? "try_" : ""); + + message_generator.set("handler_name", name); + message_generator.append(R"~~~( + @message.complex_return_type@ @try_prefix_maybe@@async_prefix_maybe@@handler_name@()~~~"); + + for (size_t i = 0; i < parameters.size(); ++i) { + auto& parameter = parameters[i]; + auto argument_generator = message_generator.fork(); + argument_generator.set("argument.type", parameter.type); + argument_generator.set("argument.name", parameter.name); + argument_generator.append("@argument.type@ @argument.name@"); + if (i != parameters.size() - 1) + argument_generator.append(", "); + } + + message_generator.append(") {"); + + if (is_synchronous && !is_try) { + if (return_type != "void") { + message_generator.append(R"~~~( + return )~~~"); + if (message.outputs.size() != 1) + message_generator.append("move(*"); + } else { + message_generator.append(R"~~~( + )~~~"); + } + + message_generator.append("m_connection.template send_sync<Messages::@endpoint.name@::@message.pascal_name@>("); + } else if (is_try) { + message_generator.append(R"~~~( + auto result = m_connection.template send_sync_but_allow_failure<Messages::@endpoint.name@::@message.pascal_name@>()~~~"); + } else { + message_generator.append(R"~~~( + m_connection.post_message(Messages::@endpoint.name@::@message.pascal_name@ { )~~~"); + } + + for (size_t i = 0; i < parameters.size(); ++i) { + auto& parameter = parameters[i]; + auto argument_generator = message_generator.fork(); + argument_generator.set("argument.name", parameter.name); + if (is_primitive_type(parameters[i].type)) + argument_generator.append("@argument.name@"); + else + argument_generator.append("move(@argument.name@)"); + if (i != parameters.size() - 1) + argument_generator.append(", "); + } + + if (is_synchronous && !is_try) { + if (return_type != "void") { + message_generator.append(")"); + } + + if (message.outputs.size() == 1) { + message_generator.append("->take_"); + message_generator.append(message.outputs[0].name); + message_generator.append("()"); + } else + message_generator.append(")"); + + message_generator.append(";"); + } else if (is_try) { + message_generator.append(R"~~~(); + if (!result) + return IPC::ErrorCode::PeerDisconnected; +)~~~"); + if (inner_return_type != "void") { + message_generator.append(R"~~~( + return move(*result); +)~~~"); + } else { + message_generator.append(R"~~~( + return { }; +)~~~"); + } + } else { + message_generator.append(R"~~~( }); +)~~~"); + } + + message_generator.append(R"~~~( + } +)~~~"); + }; + + do_implement_proxy(message.name, message.inputs, message.is_synchronous, false); + if (message.is_synchronous) { + do_implement_proxy(message.name, message.inputs, false, false); + do_implement_proxy(message.name, message.inputs, true, true); + } + } + + endpoint_generator.append(R"~~~( +private: + IPC::Connection<LocalEndpoint, PeerEndpoint>& m_connection; +}; +)~~~"); + + endpoint_generator.append(R"~~~( +template<typename LocalEndpoint, typename PeerEndpoint> +class @endpoint.name@Proxy; +class @endpoint.name@Stub; + +class @endpoint.name@Endpoint { +public: + template<typename LocalEndpoint> + using Proxy = @endpoint.name@Proxy<LocalEndpoint, @endpoint.name@Endpoint>; + using Stub = @endpoint.name@Stub; + + static u32 static_magic() { return @endpoint.magic@; } + + static OwnPtr<IPC::Message> decode_message(ReadonlyBytes buffer, [[maybe_unused]] int sockfd) + { + InputMemoryStream stream { buffer }; + u32 message_endpoint_magic = 0; + stream >> message_endpoint_magic; + if (stream.handle_any_error()) { +)~~~"); + if constexpr (GENERATE_DEBUG) { + endpoint_generator.append(R"~~~( + dbgln("Failed to read message endpoint magic"); +)~~~"); + } + endpoint_generator.append(R"~~~( + return {}; + } + + if (message_endpoint_magic != @endpoint.magic@) { +)~~~"); + if constexpr (GENERATE_DEBUG) { + endpoint_generator.append(R"~~~( + dbgln("@endpoint.name@: Endpoint magic number message_endpoint_magic != @endpoint.magic@, not my message! (the other endpoint may have handled it)"); +)~~~"); + } + endpoint_generator.append(R"~~~( + return {}; + } + + i32 message_id = 0; + stream >> message_id; + if (stream.handle_any_error()) { +)~~~"); + if constexpr (GENERATE_DEBUG) { + endpoint_generator.append(R"~~~( + dbgln("Failed to read message ID"); +)~~~"); + } + endpoint_generator.append(R"~~~( + return {}; + } + + OwnPtr<IPC::Message> message; + switch (message_id) { +)~~~"); + + for (auto& message : endpoint.messages) { + auto do_decode_message = [&](const String& name) { + auto message_generator = endpoint_generator.fork(); + + message_generator.set("message.name", name); + message_generator.set("message.pascal_name", pascal_case(name)); + + message_generator.append(R"~~~( + case (int)Messages::@endpoint.name@::MessageID::@message.pascal_name@: + message = Messages::@endpoint.name@::@message.pascal_name@::decode(stream, sockfd); + break; +)~~~"); + }; + + do_decode_message(message.name); + if (message.is_synchronous) + do_decode_message(message.response_name()); + } + + endpoint_generator.append(R"~~~( + default: +)~~~"); + if constexpr (GENERATE_DEBUG) { + endpoint_generator.append(R"~~~( + dbgln("Failed to decode @endpoint.name@.({})", message_id); +)~~~"); + } + endpoint_generator.append(R"~~~( + return {}; + } + + if (stream.handle_any_error()) { +)~~~"); + if constexpr (GENERATE_DEBUG) { + endpoint_generator.append(R"~~~( + dbgln("Failed to read the message"); +)~~~"); + } + endpoint_generator.append(R"~~~( + return {}; + } + + return message; + } + +}; + +class @endpoint.name@Stub : public IPC::Stub { +public: + @endpoint.name@Stub() { } + virtual ~@endpoint.name@Stub() override { } + + virtual u32 magic() const override { return @endpoint.magic@; } + virtual String name() const override { return "@endpoint.name@"; } + + virtual OwnPtr<IPC::MessageBuffer> handle(const IPC::Message& message) override + { + switch (message.message_id()) { +)~~~"); + for (auto& message : endpoint.messages) { + auto do_handle_message = [&](String const& name, Vector<Parameter> const& parameters, bool returns_something) { + auto message_generator = endpoint_generator.fork(); + + StringBuilder argument_generator; + for (size_t i = 0; i < parameters.size(); ++i) { + auto& parameter = parameters[i]; + argument_generator.append("request."); + argument_generator.append(parameter.name); + argument_generator.append("()"); + if (i != parameters.size() - 1) + argument_generator.append(", "); + } + + message_generator.set("message.pascal_name", pascal_case(name)); + message_generator.set("message.response_type", pascal_case(message.response_name())); + message_generator.set("handler_name", name); + message_generator.set("arguments", argument_generator.to_string()); + message_generator.append(R"~~~( + case (int)Messages::@endpoint.name@::MessageID::@message.pascal_name@: { +)~~~"); + if (returns_something) { + if (message.outputs.is_empty()) { + message_generator.append(R"~~~( + [[maybe_unused]] auto& request = static_cast<const Messages::@endpoint.name@::@message.pascal_name@&>(message); + @handler_name@(@arguments@); + auto response = Messages::@endpoint.name@::@message.response_type@ { }; + return make<IPC::MessageBuffer>(response.encode()); +)~~~"); + } else { + message_generator.append(R"~~~( + [[maybe_unused]] auto& request = static_cast<const Messages::@endpoint.name@::@message.pascal_name@&>(message); + auto response = @handler_name@(@arguments@); + if (!response.valid()) + return {}; + return make<IPC::MessageBuffer>(response.encode()); +)~~~"); + } + } else { + message_generator.append(R"~~~( + [[maybe_unused]] auto& request = static_cast<const Messages::@endpoint.name@::@message.pascal_name@&>(message); + @handler_name@(@arguments@); + return {}; +)~~~"); + } + message_generator.append(R"~~~( + } +)~~~"); + }; + do_handle_message(message.name, message.inputs, message.is_synchronous); + } + endpoint_generator.append(R"~~~( + default: + return {}; + } + } +)~~~"); + + for (auto& message : endpoint.messages) { + auto message_generator = endpoint_generator.fork(); + + auto do_handle_message_decl = [&](String const& name, Vector<Parameter> const& parameters, bool is_response) { + String return_type = "void"; + if (message.is_synchronous && !message.outputs.is_empty() && !is_response) + return_type = message_name(endpoint.name, message.name, true); + message_generator.set("message.complex_return_type", return_type); + + message_generator.set("handler_name", name); + message_generator.append(R"~~~( + virtual @message.complex_return_type@ @handler_name@()~~~"); + + auto make_argument_type = [](String const& type) { + StringBuilder builder; + + bool const_ref = !is_primitive_type(type); + + builder.append(type); + if (const_ref) + builder.append(" const&"); + + return builder.to_string(); + }; + + for (size_t i = 0; i < parameters.size(); ++i) { + auto& parameter = parameters[i]; + auto argument_generator = message_generator.fork(); + argument_generator.set("argument.type", make_argument_type(parameter.type)); + argument_generator.set("argument.name", parameter.name); + argument_generator.append("[[maybe_unused]] @argument.type@ @argument.name@"); + if (i != parameters.size() - 1) + argument_generator.append(", "); + } + + if (is_response) { + message_generator.append(R"~~~() { }; +)~~~"); + } else { + message_generator.append(R"~~~() = 0; +)~~~"); + } + }; + + do_handle_message_decl(message.name, message.inputs, false); + } + + endpoint_generator.append(R"~~~( +private: +}; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +)~~~"); + } + + outln("{}", generator.as_string_view()); + + if constexpr (GENERATE_DEBUG) { + for (auto& endpoint : endpoints) { + warnln("Endpoint '{}' (magic: {})", endpoint.name, endpoint.magic); + for (auto& message : endpoint.messages) { + warnln(" Message: '{}'", message.name); + warnln(" Sync: {}", message.is_synchronous); + warnln(" Inputs:"); + for (auto& parameter : message.inputs) + warnln(" Parameter: {} ({})", parameter.name, parameter.type); + if (message.inputs.is_empty()) + warnln(" (none)"); + if (message.is_synchronous) { + warnln(" Outputs:"); + for (auto& parameter : message.outputs) + warnln(" Parameter: {} ({})", parameter.name, parameter.type); + if (message.outputs.is_empty()) + warnln(" (none)"); + } + } + } + } +} |