mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 03:55:24 +00:00
IPCCompiler: Use new SourceGenerator class.
This commit is contained in:
parent
b99cebf63a
commit
0c5497829e
Notes:
sideshowbarker
2024-07-19 01:55:57 +09:00
Author: https://github.com/asynts Commit: https://github.com/SerenityOS/serenity/commit/0c5497829e7 Pull-request: https://github.com/SerenityOS/serenity/pull/3745 Reviewed-by: https://github.com/alimpfard
1 changed files with 283 additions and 132 deletions
|
@ -27,6 +27,7 @@
|
|||
#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>
|
||||
|
@ -64,13 +65,13 @@ struct Endpoint {
|
|||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc != 2) {
|
||||
printf("usage: %s <IPC endpoint definition file>\n", argv[0]);
|
||||
outln("usage: {} <IPC endpoint definition file>", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto file = Core::File::construct(argv[1]);
|
||||
if (!file->open(Core::IODevice::ReadOnly)) {
|
||||
fprintf(stderr, "Error: Cannot open %s: %s\n", argv[1], file->error_string());
|
||||
warnln("Error: Cannot open {}: {}", argv[1], file->error_string());
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -81,7 +82,7 @@ int main(int argc, char** argv)
|
|||
|
||||
auto assert_specific = [&](char ch) {
|
||||
if (lexer.peek() != ch)
|
||||
warn() << "assert_specific: wanted '" << ch << "', but got '" << lexer.peek() << "' at index " << lexer.tell();
|
||||
warnln("assert_specific: wanted '{}', but got '{}' at index {}", ch, lexer.peek(), lexer.tell());
|
||||
bool saw_expected = lexer.consume_specific(ch);
|
||||
ASSERT(saw_expected);
|
||||
};
|
||||
|
@ -199,38 +200,62 @@ int main(int argc, char** argv)
|
|||
while (lexer.tell() < file_contents.size())
|
||||
parse_endpoint();
|
||||
|
||||
out() << "#pragma once";
|
||||
out() << "#include <AK/MemoryStream.h>";
|
||||
out() << "#include <AK/OwnPtr.h>";
|
||||
out() << "#include <AK/URL.h>";
|
||||
out() << "#include <AK/Utf8View.h>";
|
||||
out() << "#include <LibGfx/Color.h>";
|
||||
out() << "#include <LibGfx/Rect.h>";
|
||||
out() << "#include <LibGfx/ShareableBitmap.h>";
|
||||
out() << "#include <LibIPC/Decoder.h>";
|
||||
out() << "#include <LibIPC/Dictionary.h>";
|
||||
out() << "#include <LibIPC/Encoder.h>";
|
||||
out() << "#include <LibIPC/Endpoint.h>";
|
||||
out() << "#include <LibIPC/Message.h>";
|
||||
out();
|
||||
SourceGenerator generator;
|
||||
|
||||
generator.append(R"~~~(
|
||||
#pragma once
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/URL.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <LibGfx/ShareableBitmap.h>
|
||||
#include <LibIPC/Decoder.h>
|
||||
#include <LibIPC/Dictionary.h>
|
||||
#include <LibIPC/Encoder.h>
|
||||
#include <LibIPC/Endpoint.h>
|
||||
#include <LibIPC/Message.h>
|
||||
)~~~");
|
||||
|
||||
for (auto& endpoint : endpoints) {
|
||||
out() << "namespace Messages::" << endpoint.name << " {";
|
||||
out();
|
||||
SourceGenerator endpoint_generator { generator };
|
||||
|
||||
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;
|
||||
|
||||
out() << "enum class MessageID : i32 {";
|
||||
endpoint_generator.append(R"~~~(
|
||||
enum class MessageID : i32 {
|
||||
)~~~");
|
||||
for (auto& message : endpoint.messages) {
|
||||
SourceGenerator message_generator { endpoint_generator };
|
||||
|
||||
message_ids.set(message.name, message_ids.size() + 1);
|
||||
out() << " " << message.name << " = " << message_ids.size() << ",";
|
||||
message_generator.set("message.name", message.name);
|
||||
message_generator.set("message.id", String::number(message_ids.size()));
|
||||
|
||||
message_generator.append(R"~~~(
|
||||
@message.name@ = @message.id@,
|
||||
)~~~");
|
||||
if (message.is_synchronous) {
|
||||
message_ids.set(message.response_name(), message_ids.size() + 1);
|
||||
out() << " " << message.response_name() << " = " << message_ids.size() << ",";
|
||||
message_generator.set("message.name", message.response_name());
|
||||
message_generator.set("message.id", String::number(message_ids.size()));
|
||||
|
||||
message_generator.append(R"~~~(
|
||||
@message.name@ = @message.id@,
|
||||
)~~~");
|
||||
}
|
||||
}
|
||||
out() << "};";
|
||||
out();
|
||||
endpoint_generator.append(R"~~~(
|
||||
};
|
||||
)~~~");
|
||||
|
||||
auto constructor_for_message = [&](const String& name, const Vector<Parameter>& parameters) {
|
||||
StringBuilder builder;
|
||||
|
@ -267,31 +292,58 @@ int main(int argc, char** argv)
|
|||
};
|
||||
|
||||
auto do_message = [&](const String& name, const Vector<Parameter>& parameters, const String& response_type = {}) {
|
||||
out() << "class " << name << " final : public IPC::Message {";
|
||||
out() << "public:";
|
||||
if (!response_type.is_null())
|
||||
out() << " typedef class " << response_type << " ResponseType;";
|
||||
out() << " " << constructor_for_message(name, parameters);
|
||||
out() << " virtual ~" << name << "() override {}";
|
||||
out() << " virtual i32 endpoint_magic() const override { return " << endpoint.magic << "; }";
|
||||
out() << " virtual i32 message_id() const override { return (int)MessageID::" << name << "; }";
|
||||
out() << " static i32 static_message_id() { return (int)MessageID::" << name << "; }";
|
||||
out() << " virtual const char* message_name() const override { return \"" << endpoint.name << "::" << name << "\"; }";
|
||||
out() << " static OwnPtr<" << name << "> decode(InputMemoryStream& stream, size_t& size_in_bytes)";
|
||||
out() << " {";
|
||||
SourceGenerator message_generator { endpoint_generator };
|
||||
|
||||
out() << " IPC::Decoder decoder(stream);";
|
||||
message_generator.set("message.name", name);
|
||||
message_generator.set("message.response_type", response_type);
|
||||
message_generator.set("message.constructor", constructor_for_message(name, parameters));
|
||||
|
||||
message_generator.append(R"~~~(
|
||||
class @message.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.constructor@
|
||||
virtual ~@message.name@() override {}
|
||||
|
||||
virtual i32 endpoint_magic() const override { return @endpoint.magic@; }
|
||||
virtual i32 message_id() const override { return (int)MessageID::@message.name@; }
|
||||
static i32 static_message_id() { return (int)MessageID::@message.name@; }
|
||||
virtual const char* message_name() const override { return "@endpoint.name@::@message.name@"; }
|
||||
|
||||
static OwnPtr<@message.name@> decode(InputMemoryStream& stream, size_t& size_in_bytes)
|
||||
{
|
||||
IPC::Decoder decoder {stream};
|
||||
)~~~");
|
||||
|
||||
for (auto& parameter : parameters) {
|
||||
String initial_value = "{}";
|
||||
SourceGenerator parameter_generator { message_generator };
|
||||
|
||||
parameter_generator.set("parameter.type", parameter.type);
|
||||
parameter_generator.set("parameter.name", parameter.name);
|
||||
|
||||
if (parameter.type == "bool")
|
||||
initial_value = "false";
|
||||
out() << " " << parameter.type << " " << parameter.name << " = " << initial_value << ";";
|
||||
out() << " if (!decoder.decode(" << parameter.name << "))";
|
||||
out() << " return nullptr;";
|
||||
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 nullptr;
|
||||
)~~~");
|
||||
|
||||
if (parameter.attributes.contains_slow("UTF8")) {
|
||||
out() << " if (!Utf8View(" << parameter.name << ").validate())";
|
||||
out() << " return nullptr;";
|
||||
parameter_generator.append(R"~~~(
|
||||
if (!Utf8View(@parameter.name@).validate())
|
||||
return nullptr;
|
||||
)~~~");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -302,29 +354,65 @@ int main(int argc, char** argv)
|
|||
if (i != parameters.size() - 1)
|
||||
builder.append(", ");
|
||||
}
|
||||
out() << " size_in_bytes = stream.offset();";
|
||||
out() << " return make<" << name << ">(" << builder.to_string() << ");";
|
||||
out() << " }";
|
||||
out() << " virtual IPC::MessageBuffer encode() const override";
|
||||
out() << " {";
|
||||
out() << " IPC::MessageBuffer buffer;";
|
||||
out() << " IPC::Encoder stream(buffer);";
|
||||
out() << " stream << endpoint_magic();";
|
||||
out() << " stream << (int)MessageID::" << name << ";";
|
||||
|
||||
message_generator.set("message.constructor_call_parameters", builder.build());
|
||||
|
||||
message_generator.append(R"~~~(
|
||||
size_in_bytes = stream.offset();
|
||||
return make<@message.name@>(@message.constructor_call_parameters@);
|
||||
}
|
||||
)~~~");
|
||||
|
||||
message_generator.append(R"~~~(
|
||||
virtual IPC::MessageBuffer encode() const override
|
||||
{
|
||||
IPC::MessageBuffer buffer;
|
||||
IPC::Encoder stream(buffer);
|
||||
stream << endpoint_magic();
|
||||
stream << (int)MessageID::@message.name@;
|
||||
)~~~");
|
||||
|
||||
for (auto& parameter : parameters) {
|
||||
out() << " stream << m_" << parameter.name << ";";
|
||||
SourceGenerator parameter_generator { message_generator };
|
||||
|
||||
parameter_generator.set("parameter.name", parameter.name);
|
||||
parameter_generator.append(R"~~~(
|
||||
stream << m_@parameter.name@;
|
||||
)~~~");
|
||||
}
|
||||
out() << " return buffer;";
|
||||
out() << " }";
|
||||
|
||||
message_generator.append(R"~~~(
|
||||
return buffer;
|
||||
}
|
||||
)~~~");
|
||||
|
||||
for (auto& parameter : parameters) {
|
||||
out() << " const " << parameter.type << "& " << parameter.name << "() const { return m_" << parameter.name << "; }";
|
||||
SourceGenerator parameter_generator { message_generator };
|
||||
|
||||
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@; }
|
||||
)~~~");
|
||||
}
|
||||
out() << "private:";
|
||||
|
||||
message_generator.append(R"~~~(
|
||||
private:
|
||||
)~~~");
|
||||
|
||||
for (auto& parameter : parameters) {
|
||||
out() << " " << parameter.type << " m_" << parameter.name << ";";
|
||||
SourceGenerator parameter_generator { message_generator };
|
||||
|
||||
parameter_generator.set("parameter.type", parameter.type);
|
||||
parameter_generator.set("parameter.name", parameter.name);
|
||||
parameter_generator.append(R"~~~(
|
||||
@parameter.type@ m_@parameter.name@;
|
||||
)~~~");
|
||||
}
|
||||
out() << "};";
|
||||
out();
|
||||
|
||||
message_generator.append(R"~~~(
|
||||
};
|
||||
)~~~");
|
||||
};
|
||||
for (auto& message : endpoint.messages) {
|
||||
String response_name;
|
||||
|
@ -334,94 +422,149 @@ int main(int argc, char** argv)
|
|||
}
|
||||
do_message(message.name, message.inputs, response_name);
|
||||
}
|
||||
out() << "}";
|
||||
out();
|
||||
|
||||
out() << "class " << endpoint.name << "Endpoint : public IPC::Endpoint {";
|
||||
out() << "public:";
|
||||
out() << " " << endpoint.name << "Endpoint() {}";
|
||||
out() << " virtual ~" << endpoint.name << "Endpoint() override {}";
|
||||
out() << " static int static_magic() { return " << endpoint.magic << "; }";
|
||||
out() << " virtual int magic() const override { return " << endpoint.magic << "; }";
|
||||
out() << " static String static_name() { return \"" << endpoint.name << "\"; };";
|
||||
out() << " virtual String name() const override { return \"" << endpoint.name << "\"; };";
|
||||
out() << " static OwnPtr<IPC::Message> decode_message(const ByteBuffer& buffer, size_t& size_in_bytes)";
|
||||
out() << " {";
|
||||
out() << " InputMemoryStream stream { buffer };";
|
||||
out() << " i32 message_endpoint_magic = 0;";
|
||||
out() << " stream >> message_endpoint_magic;";
|
||||
out() << " if (stream.handle_any_error()) {";
|
||||
endpoint_generator.append(R"~~~(
|
||||
} // namespace Messages::@endpoint.name@
|
||||
)~~~");
|
||||
|
||||
endpoint_generator.append(R"~~~(
|
||||
class @endpoint.name@Endpoint : public IPC::Endpoint {
|
||||
public:
|
||||
@endpoint.name@Endpoint() { }
|
||||
virtual ~@endpoint.name@Endpoint() override { }
|
||||
|
||||
static int static_magic() { return @endpoint.magic@; }
|
||||
virtual int magic() const override { return @endpoint.magic@; }
|
||||
static String static_name() { return "@endpoint.name@"; }
|
||||
virtual String name() const override { return "@endpoint.name@"; }
|
||||
|
||||
static OwnPtr<IPC::Message> decode_message(const ByteBuffer& buffer, size_t& size_in_bytes)
|
||||
{
|
||||
InputMemoryStream stream { buffer };
|
||||
i32 message_endpoint_magic = 0;
|
||||
stream >> message_endpoint_magic;
|
||||
if (stream.handle_any_error()) {
|
||||
)~~~");
|
||||
#ifdef GENERATE_DEBUG_CODE
|
||||
out() << " dbg() << \"Failed to read message endpoint magic\";";
|
||||
endpoint_generator.append(R"~~~(
|
||||
dbgln("Failed to read message endpoint magic");
|
||||
)~~~");
|
||||
#endif
|
||||
out() << " return nullptr;";
|
||||
out() << " }";
|
||||
out() << " if (message_endpoint_magic != " << endpoint.magic << ") {";
|
||||
endpoint_generator.append(R"~~~(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (message_endpoint_magic != @endpoint.magic@) {
|
||||
)~~~");
|
||||
#ifdef GENERATE_DEBUG_CODE
|
||||
out() << " dbg() << \"endpoint magic \" << message_endpoint_magic << \" != " << endpoint.magic << "\";";
|
||||
endpoint_generator.append(R"~~~(
|
||||
dbgln("Endpoint magic number message_endpoint_magic != @endpoint.magic@");
|
||||
)~~~");
|
||||
#endif
|
||||
out() << " return nullptr;";
|
||||
out() << " }";
|
||||
out() << " i32 message_id = 0;";
|
||||
out() << " stream >> message_id;";
|
||||
out() << " if (stream.handle_any_error()) {";
|
||||
endpoint_generator.append(R"~~~(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
i32 message_id = 0;
|
||||
stream >> message_id;
|
||||
if (stream.handle_any_error()) {
|
||||
)~~~");
|
||||
#ifdef GENERATE_DEBUG_CODE
|
||||
out() << " dbg() << \"Failed to read message ID\";";
|
||||
endpoint_generator.append(R"~~~(
|
||||
dbgln("Failed to read message ID");
|
||||
)~~~");
|
||||
#endif
|
||||
out() << " return nullptr;";
|
||||
out() << " }";
|
||||
out() << " OwnPtr<IPC::Message> message;";
|
||||
out() << " switch (message_id) {";
|
||||
endpoint_generator.append(R"~~~(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OwnPtr<IPC::Message> message;
|
||||
switch (message_id) {
|
||||
)~~~");
|
||||
|
||||
for (auto& message : endpoint.messages) {
|
||||
auto do_decode_message = [&](const String& name) {
|
||||
out() << " case (int)Messages::" << endpoint.name << "::MessageID::" << name << ":";
|
||||
out() << " message = Messages::" << endpoint.name << "::" << name << "::decode(stream, size_in_bytes);";
|
||||
out() << " break;";
|
||||
SourceGenerator message_generator { endpoint_generator };
|
||||
|
||||
message_generator.set("message.name", name);
|
||||
|
||||
message_generator.append(R"~~~(
|
||||
case (int)Messages::@endpoint.name@::MessageID::@message.name@:
|
||||
message = Messages::@endpoint.name@::@message.name@::decode(stream, size_in_bytes);
|
||||
break;
|
||||
)~~~");
|
||||
};
|
||||
|
||||
do_decode_message(message.name);
|
||||
if (message.is_synchronous)
|
||||
do_decode_message(message.response_name());
|
||||
}
|
||||
out() << " default:";
|
||||
#ifdef GENERATE_DEBUG_CODE
|
||||
out() << " dbg() << \"Failed to decode " << endpoint.name << ".(\" << message_id << \")\";";
|
||||
#endif
|
||||
out() << " return nullptr;";
|
||||
|
||||
out() << " }";
|
||||
out() << " if (stream.handle_any_error()) {";
|
||||
endpoint_generator.append(R"~~~(
|
||||
default:
|
||||
)~~~");
|
||||
#ifdef GENERATE_DEBUG_CODE
|
||||
out() << " dbg() << \"Failed to read the message\";";
|
||||
endpoint_generator.append(R"~~~(
|
||||
dbgln("Failed to decode @endpoint.name@.({})", message_id);
|
||||
)~~~");
|
||||
#endif
|
||||
out() << " return nullptr;";
|
||||
out() << " }";
|
||||
out() << " return message;";
|
||||
out() << " }";
|
||||
out();
|
||||
out() << " virtual OwnPtr<IPC::Message> handle(const IPC::Message& message) override";
|
||||
out() << " {";
|
||||
out() << " switch (message.message_id()) {";
|
||||
endpoint_generator.append(R"~~~(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (stream.handle_any_error()) {
|
||||
)~~~");
|
||||
#ifdef GENERATE_DEBUG_CODE
|
||||
endpoint_generator.append(R"~~~(
|
||||
dbgln("Failed to read the message");
|
||||
)~~~");
|
||||
#endif
|
||||
endpoint_generator.append(R"~~~(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
virtual OwnPtr<IPC::Message> handle(const IPC::Message& message) override
|
||||
{
|
||||
switch (message.message_id()) {
|
||||
)~~~");
|
||||
for (auto& message : endpoint.messages) {
|
||||
auto do_decode_message = [&](const String& name, bool returns_something) {
|
||||
out() << " case (int)Messages::" << endpoint.name << "::MessageID::" << name << ":";
|
||||
SourceGenerator message_generator { endpoint_generator };
|
||||
|
||||
message_generator.set("message.name", name);
|
||||
message_generator.append(R"~~~(
|
||||
case (int)Messages::@endpoint.name@::MessageID::@message.name@:
|
||||
)~~~");
|
||||
if (returns_something) {
|
||||
out() << " return handle(static_cast<const Messages::" << endpoint.name << "::" << name << "&>(message));";
|
||||
message_generator.append(R"~~~(
|
||||
return handle(static_cast<const Messages::@endpoint.name@::@message.name@&>(message));
|
||||
)~~~");
|
||||
} else {
|
||||
out() << " handle(static_cast<const Messages::" << endpoint.name << "::" << name << "&>(message));";
|
||||
out() << " return nullptr;";
|
||||
message_generator.append(R"~~~(
|
||||
handle(static_cast<const Messages::@endpoint.name@::@message.name@&>(message));
|
||||
return nullptr;
|
||||
)~~~");
|
||||
}
|
||||
};
|
||||
do_decode_message(message.name, message.is_synchronous);
|
||||
if (message.is_synchronous)
|
||||
do_decode_message(message.response_name(), false);
|
||||
}
|
||||
out() << " default:";
|
||||
out() << " return nullptr;";
|
||||
|
||||
out() << " }";
|
||||
out() << " }";
|
||||
endpoint_generator.append(R"~~~(
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
)~~~");
|
||||
|
||||
for (auto& message : endpoint.messages) {
|
||||
SourceGenerator message_generator { endpoint_generator };
|
||||
|
||||
message_generator.set("message.name", message.name);
|
||||
|
||||
String return_type = "void";
|
||||
if (message.is_synchronous) {
|
||||
StringBuilder builder;
|
||||
|
@ -433,30 +576,38 @@ int main(int argc, char** argv)
|
|||
builder.append(">");
|
||||
return_type = builder.to_string();
|
||||
}
|
||||
out() << " virtual " << return_type << " handle(const Messages::" << endpoint.name << "::" << message.name << "&) = 0;";
|
||||
message_generator.set("message.complex_return_type", return_type);
|
||||
|
||||
message_generator.append(R"~~~(
|
||||
virtual @message.complex_return_type@ handle(const Messages::@endpoint.name@::@message.name@&) = 0;
|
||||
)~~~");
|
||||
}
|
||||
|
||||
out() << "private:";
|
||||
out() << "};";
|
||||
endpoint_generator.append(R"~~~(
|
||||
private:
|
||||
};
|
||||
)~~~");
|
||||
}
|
||||
|
||||
outln("{}", generator.generate());
|
||||
|
||||
#ifdef DEBUG
|
||||
for (auto& endpoint : endpoints) {
|
||||
warn() << "Endpoint: '" << endpoint.name << "' (magic: " << endpoint.magic << ")";
|
||||
warnln("Endpoint '{}' (magic: {})", endpoint.name, endpoint.magic);
|
||||
for (auto& message : endpoint.messages) {
|
||||
warn() << " Message: '" << message.name << "'";
|
||||
warn() << " Sync: " << message.is_synchronous;
|
||||
warn() << " Inputs:";
|
||||
warnln(" Message: '{}'", message.name);
|
||||
warnln(" Sync: {}", message.is_synchronous);
|
||||
warnln(" Inputs:");
|
||||
for (auto& parameter : message.inputs)
|
||||
warn() << " Parameter: " << parameter.name << " (" << parameter.type << ")";
|
||||
warnln(" Parameter: {} ({})", parameter.name, parameter.type);
|
||||
if (message.inputs.is_empty())
|
||||
warn() << " (none)";
|
||||
warnln(" (none)");
|
||||
if (message.is_synchronous) {
|
||||
warn() << " Outputs:";
|
||||
warnln(" Outputs:");
|
||||
for (auto& parameter : message.outputs)
|
||||
warn() << " Parameter: " << parameter.name << " (" << parameter.type << ")";
|
||||
warnln(" Parameter: {} ({})", parameter.name, parameter.type);
|
||||
if (message.outputs.is_empty())
|
||||
warn() << " (none)";
|
||||
warnln(" (none)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue