From 3b551a2caabdfebaac592b5fcbbeb6cbfe2fd43f Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Sat, 17 Dec 2022 14:17:08 +0200 Subject: [PATCH] Add a tool for generating setup structs and loaders for them --- .gitignore | 1 + Build | 10 +- tools/setupgen/enum.cpp | 76 +++++++++++++ tools/setupgen/enum.h | 43 ++++++++ tools/setupgen/setupgen.cpp | 214 ++++++++++++++++++++++++++++++++++++ tools/setupgen/setupgen.h | 55 +++++++++ tools/setupgen/struct.cpp | 128 +++++++++++++++++++++ tools/setupgen/struct.h | 62 +++++++++++ tools/setupgen/type.cpp | 171 ++++++++++++++++++++++++++++ tools/setupgen/type.h | 57 ++++++++++ 10 files changed, 815 insertions(+), 2 deletions(-) create mode 100644 tools/setupgen/enum.cpp create mode 100644 tools/setupgen/enum.h create mode 100644 tools/setupgen/setupgen.cpp create mode 100644 tools/setupgen/setupgen.h create mode 100644 tools/setupgen/struct.cpp create mode 100644 tools/setupgen/struct.h create mode 100644 tools/setupgen/type.cpp create mode 100644 tools/setupgen/type.h diff --git a/.gitignore b/.gitignore index ecc88cd..6816e2d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ temp /libmspgameview.a /libmspgameview.so /mspgame.pc +/mspgame-setupgen diff --git a/Build b/Build index 15fb6f7..7548a1a 100644 --- a/Build +++ b/Build @@ -5,8 +5,6 @@ package "mspgame" require "mspcore"; require "mspdatafile"; require "mspmath"; - require "mspgui"; - require "mspgl"; require "sigc++-2.0"; build_info @@ -28,10 +26,18 @@ package "mspgame" { source "source/gameview"; use "mspgame"; + require "mspgui"; + require "mspgl"; install true; install_map { map "source" "include/msp"; }; }; + + program "mspgame-setupgen" + { + source "tools/setupgen"; + install true; + }; }; diff --git a/tools/setupgen/enum.cpp b/tools/setupgen/enum.cpp new file mode 100644 index 0000000..b6a40c2 --- /dev/null +++ b/tools/setupgen/enum.cpp @@ -0,0 +1,76 @@ +#include "enum.h" +#include + +using namespace std; +using namespace Msp; + +Enum::Enum(const string &n): + name(n) +{ } + +void Enum::define_type(IO::Base &out) const +{ + IO::print(out, "enum class %s\n{\n", name); + for(auto i=values.begin();; ) + { + IO::print(out, "\t%s", i->name); + if(i->value) + IO::print(out, " = %d", i->value.value()); + ++i; + if(i==values.end()) + break; + IO::print(out, ",\n"); + } + IO::print(out, "\n};\n"); +} + +void Enum::declare_conversions(IO::Base &out) const +{ + IO::print(out, "void operator>>(const Msp::LexicalConverter &, %s &);\n", name); + IO::print(out, "void operator<<(Msp::LexicalConverter &, %s);\n", name); +} + +void Enum::define_conversions(IO::Base &out) const +{ + IO::print(out, "void operator>>(const Msp::LexicalConverter &conv, %s &_e)\n{\n", name); + IO::print(out, "\tconst std::string &str = conv.get();\n\t"); + for(auto i=values.begin(); i!=values.end(); ++i) + { + if(i!=values.begin()) + IO::print(out, "else "); + IO::print(out, "if(str==\"%s\")\n\t\t_e = %s::%s;\n\t", i->name, name, i->name); + } + IO::print(out, "else\n\t\tthrow Msp::lexical_error(Msp::format(\"conversion of '%%s' to %s\", str));\n", name); + IO::print(out, "}\n"); + + IO::print(out, "\nvoid operator<<(Msp::LexicalConverter &conv, %s _e)\n{\n", name); + IO::print(out, "\tswitch(_e)\n\t{\n"); + for(const Value &v: values) + IO::print(out, "\tcase %s::%s: conv.result(\"%s\"); break;\n", name, v.name, v.name); + IO::print(out, "\tdefault: conv.result(Msp::format(\"%s(%%#x)\", static_cast(_e)));\n", name); + IO::print(out, "\t}\n}\n"); +} + + +Enum::Loader::Loader(Enum &e): + ObjectLoader(e) +{ + static ActionMap shared_actions; + set_actions(shared_actions); +} + +void Enum::Loader::init_actions() +{ + add("value", &Loader::value); + add("value", &Loader::value_auto); +} + +void Enum::Loader::value(const DataFile::Symbol &n, int v) +{ + obj.values.emplace_back(n.name, v); +} + +void Enum::Loader::value_auto(const DataFile::Symbol &n) +{ + obj.values.emplace_back(n.name); +} diff --git a/tools/setupgen/enum.h b/tools/setupgen/enum.h new file mode 100644 index 0000000..26723b2 --- /dev/null +++ b/tools/setupgen/enum.h @@ -0,0 +1,43 @@ +#ifndef ENUM_H_ +#define ENUM_H_ + +#include +#include +#include +#include +#include + +class Enum +{ +public: + class Loader: public Msp::DataFile::ObjectLoader + { + public: + Loader(Enum &); + + private: + void init_actions() override; + + void value(const Msp::DataFile::Symbol &, int); + void value_auto(const Msp::DataFile::Symbol &); + }; + + struct Value + { + std::string name; + std::optional value; + }; + +private: + std::string name; + std::vector values; + +public: + Enum(const std::string &); + + void define_type(Msp::IO::Base &) const; + void declare_conversions(Msp::IO::Base &) const; + void define_conversions(Msp::IO::Base &) const; +}; + +#endif diff --git a/tools/setupgen/setupgen.cpp b/tools/setupgen/setupgen.cpp new file mode 100644 index 0000000..9506677 --- /dev/null +++ b/tools/setupgen/setupgen.cpp @@ -0,0 +1,214 @@ +#include "setupgen.h" +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace Msp; + +SetupGen::SetupGen(int argc, char **argv) +{ + GetOpt getopt; + getopt.add_option('o', "output", out_fn, GetOpt::REQUIRED_ARG); + getopt.add_argument("input_file", in_fn, GetOpt::REQUIRED_ARG); + getopt(argc, argv); +} + +int SetupGen::main() +{ + create_standard_types(); + load(); + + collect_headers(); + + if(out_fn.empty()) + { + generate_header(IO::cout); + generate_code(IO::cout); + } + else + { + string out_base = FS::basename(out_fn); + string out_ext = FS::extpart(out_base); + if(out_ext==".cpp" || out_ext==".h") + { + FS::Path out_dir = FS::dirname(out_fn); + out_base = FS::basepart(out_base); + IO::BufferedFile header_out((out_dir/(out_base+".h")).str(), IO::M_WRITE); + generate_header(header_out); + IO::BufferedFile code_out((out_dir/(out_base+".cpp")).str(), IO::M_WRITE); + IO::print(code_out, "#include \"%s.h\"\n", out_base); + if(!enums.empty()) + IO::print(code_out, "#include \n"); + generate_code(code_out); + } + else + { + IO::BufferedFile out(out_fn, IO::M_WRITE); + generate_header(out); + out.put('\n'); + generate_code(out); + } + } + + return 0; +} + +void SetupGen::create_standard_types() +{ + add_type("bool", Type::VALUE); + add_type("int", Type::VALUE); + add_type("uint", Type::VALUE).set_cpp_type("unsigned"); + const Type &float_type = add_type("float", Type::VALUE); + add_type("string", Type::VALUE).set_cpp_type("std::string", "string"); + add_type("angle", Type::VALUE).set_cpp_type("Msp::Geometry::Angle", "msp/geometry/angle.h") + .set_load("float", "Msp::Geometry::Angle::from_degrees"); + add_type("vector2", Type::AGGREGATE).set_cpp_type("Msp::LinAl::Vector", "msp/linal/vector.h") + .set_elements(float_type, 2); + add_type("vector3", Type::AGGREGATE).set_cpp_type("Msp::LinAl::Vector", "msp/linal/vector.h") + .set_elements(float_type, 3); +} + +Type &SetupGen::add_type(const string &n, Type::Kind k) +{ + auto result = types.try_emplace(n, n, k); + if(!result.second) + throw key_error(n); + return result.first->second; +} + +const Type &SetupGen::get_type(const string &n) const +{ + return get_item(types, n); +} + +void SetupGen::load() +{ + IO::BufferedFile in_file(in_fn); + DataFile::Parser parser(in_file, in_fn); + Loader ldr(*this); + ldr.load(parser); +} + +void SetupGen::collect_headers() +{ + headers.insert("msp/datafile/objectloader.h"); + headers.insert("msp/strings/lexicalcast.h"); + for(const unique_ptr &s: structs) + for(const Struct::Field &f: s->get_fields()) + if(const string &h = f.type->get_header(); !h.empty()) + headers.insert(h); +} + +void SetupGen::generate_header(IO::Base &out) const +{ + string guard = toupper(FS::basepart(FS::basename(out_fn.empty() ? in_fn : out_fn)))+"_H_"; + if(!name_space.empty()) + { + vector parts = split(name_space, "::"); + parts.emplace_back(move(guard)); + guard = join(parts.begin(), parts.end(), "_"); + } + IO::print(out, "#ifndef %s\n", guard); + IO::print(out, "#define %s\n", guard); + + if(!headers.empty()) + { + out.put('\n'); + for(const string &h: headers) + IO::print(out, "#include <%s>\n", h); + } + + if(!name_space.empty()) + IO::print(out, "\nnamespace %s {\n", name_space); + + for(const unique_ptr &e: enums) + { + out.put('\n'); + e->define_type(out); + } + + for(const unique_ptr &s: structs) + { + out.put('\n'); + s->define_type(out); + } + + for(const unique_ptr &s: structs) + { + out.put('\n'); + s->define_loader(out); + } + + for(const unique_ptr &e: enums) + { + out.put('\n'); + e->declare_conversions(out); + } + + if(!name_space.empty()) + IO::print(out, "\n} // namespace %s\n", name_space); + + IO::print(out, "\n#endif\n"); +} + +void SetupGen::generate_code(IO::Base &out) const +{ + if(!name_space.empty()) + IO::print(out, "\nnamespace %s {\n", name_space); + + for(const unique_ptr &s: structs) + { + out.put('\n'); + s->define_functions(out); + } + + for(const unique_ptr &e: enums) + { + out.put('\n'); + e->define_conversions(out); + } + + if(!name_space.empty()) + IO::print(out, "\n} // namespace %s\n", name_space); +} + + +SetupGen::Loader::Loader(SetupGen &s): + ObjectLoader(s) +{ + static ActionMap shared_actions; + set_actions(shared_actions); +} + +void SetupGen::Loader::init_actions() +{ + add("component", &Loader::struct_def, Struct::COMPONENT); + add("entity", &Loader::struct_def, Struct::ENTITY); + add("enum", &Loader::enum_def); + add("namespace", &Loader::name_space); +} + +void SetupGen::Loader::enum_def(const DataFile::Symbol &n) +{ + Enum en(n.name); + load_sub(en); + Type &type = obj.add_type(n.name, Type::ENUM); + type.set_enum(*obj.enums.emplace_back(make_unique(move(en)))); +} + +void SetupGen::Loader::name_space(const string &ns) +{ + obj.name_space = ns; +} + +void SetupGen::Loader::struct_def(Struct::Kind kind, const DataFile::Symbol &n) +{ + Struct sct(n.name+"Setup", kind); + load_sub(sct, obj); + Type &type = obj.add_type(n.name, Type::STRUCT); + type.set_struct(*obj.structs.emplace_back(make_unique(move(sct)))); +} diff --git a/tools/setupgen/setupgen.h b/tools/setupgen/setupgen.h new file mode 100644 index 0000000..98d1ab8 --- /dev/null +++ b/tools/setupgen/setupgen.h @@ -0,0 +1,55 @@ +#ifndef SETUPGEN_H_ +#define SETUPGEN_H_ + +#include +#include +#include +#include +#include +#include +#include "enum.h" +#include "struct.h" +#include "type.h" + +class SetupGen: public Msp::RegisteredApplication +{ +private: + class Loader: public Msp::DataFile::ObjectLoader + { + public: + Loader(SetupGen &); + + private: + void init_actions() override; + + void enum_def(const Msp::DataFile::Symbol &); + void name_space(const std::string &); + void struct_def(Struct::Kind, const Msp::DataFile::Symbol &); + }; + + std::string in_fn; + std::string out_fn; + std::string name_space; + std::map types; + std::vector> enums; + std::vector> structs; + std::set headers; + +public: + SetupGen(int, char **); + + int main() override; +private: + void create_standard_types(); + Type &add_type(const std::string &, Type::Kind); +public: + const Type &get_type(const std::string &) const; + +private: + void load(); + void collect_headers(); + void generate_header(Msp::IO::Base &) const; + void generate_code(Msp::IO::Base &) const; +}; + +#endif diff --git a/tools/setupgen/struct.cpp b/tools/setupgen/struct.cpp new file mode 100644 index 0000000..b93b5be --- /dev/null +++ b/tools/setupgen/struct.cpp @@ -0,0 +1,128 @@ +#include "struct.h" +#include +#include +#include +#include "setupgen.h" + +using namespace std; +using namespace Msp; + +class Struct::Field::Loader: public Msp::DataFile::ObjectLoader +{ +public: + Loader(Struct::Field &); + +private: + void init_actions() override; +}; + + +Struct::Struct(const string &n, Kind k): + name(n), + kind(k) +{ } + +void Struct::define_type(IO::Base &out) const +{ + IO::print(out, "struct %s\n{\n", name); + IO::print(out, "\tclass Loader;\n\n"); + + for(const Field &f: fields) + { + IO::print(out, "\t%s %s", f.type->get_cpp_type(), f.name); + if(!f.default_value.empty()) + { + if(const string &c = f.type->get_conversion(); !c.empty()) + IO::print(out, " = %s(%s)", f.type->get_conversion(), f.default_value); + else if(f.type->get_kind() == Type::AGGREGATE) + IO::print(out, " = { %s }", f.default_value); + else if(f.type->get_kind() == Type::ENUM) + IO::print(out, " = %s::%s", f.type->get_name(), f.default_value); + else + IO::print(out, " = %s", f.default_value); + } + IO::print(out, ";\n"); + } + + IO::print(out, "};\n"); +} + +void Struct::define_loader(IO::Base &out) const +{ + IO::print(out, "class %s::Loader: public Msp::DataFile::ObjectLoader<%s>\n{\n", name, name); + IO::print(out, "public:\n"); + IO::print(out, "\tLoader(%s &);\n", name); + IO::print(out, "\nprivate:\n"); + IO::print(out, "\tvoid init_actions() override;\n"); + for(const Field &f: fields) + if(f.type->needs_loader_function()) + IO::print(out, "\tvoid %s(%s);\n", f.name, f.type->create_loader_params(false)); + IO::print(out, "};\n"); +} + +void Struct::define_functions(IO::Base &out) const +{ + IO::print(out, "%s::Loader::Loader(%s &o):\n", name, name); + IO::print(out, "\tObjectLoader<%s>(o)\n{\n", name); + IO::print(out, "\tstatic ActionMap shared_actions;\n"); + IO::print(out, "\tset_actions(shared_actions);\n}\n"); + + IO::print(out, "\nvoid %s::Loader::init_actions()\n{\n", name); + for(const Field &f: fields) + { + IO::print(out, "\tadd(\"%s\", ", f.name); + if(f.type->needs_loader_function()) + IO::print(out, "&Loader::%s", f.name); + else + IO::print(out, "&%s::%s", name, f.name); + IO::print(out, ");\n"); + } + IO::print(out, "}\n"); + + for(const Field &f: fields) + if(f.type->needs_loader_function()) + { + IO::print(out, "\nvoid %s::Loader::%s(%s)\n{\n", name, f.name, f.type->create_loader_params(true)); + IO::print(out, "\tauto &_v = obj.%s;\n", f.name); + IO::print(out, "\t%s\n", f.type->create_loader_statement()); + IO::print(out, "}\n"); + } +} + + +Struct::Loader::Loader(Struct &s, const SetupGen &g): + ObjectLoader(s), + gen(g) +{ + static ActionMap shared_actions; + set_actions(shared_actions); +} + +void Struct::Loader::init_actions() +{ + add("field", &Loader::field); +} + +void Struct::Loader::field(const DataFile::Symbol &n, const DataFile::Symbol &t) +{ + if(ranges::any_of(obj.fields, [&n](const Field &f){ return f.name==n.name; })) + throw key_error(n.name); + Field fld; + fld.name=n.name; + fld.type=&gen.get_type(t.name); + load_sub(fld); + obj.fields.emplace_back(move(fld)); +} + + +Struct::Field::Loader::Loader(Field &f): + ObjectLoader(f) +{ + static ActionMap shared_actions; + set_actions(shared_actions); +} + +void Struct::Field::Loader::init_actions() +{ + add("default", &Field::default_value); +} diff --git a/tools/setupgen/struct.h b/tools/setupgen/struct.h new file mode 100644 index 0000000..adc95f8 --- /dev/null +++ b/tools/setupgen/struct.h @@ -0,0 +1,62 @@ +#ifndef STRUCT_H_ +#define STRUCT_H_ + +#include +#include +#include +#include + +class SetupGen; +class Type; + +class Struct +{ +public: + enum Kind + { + DATA, + ENTITY, + COMPONENT + }; + + struct Field + { + class Loader; + + std::string name; + const Type *type = nullptr; + std::string default_value; + }; + + class Loader; + +private: + std::string name; + Kind kind; + std::vector fields; + +public: + Struct(const std::string &, Kind); + + const std::vector &get_fields() const { return fields; } + + void define_type(Msp::IO::Base &) const; + void define_loader(Msp::IO::Base &) const; + void define_functions(Msp::IO::Base &) const; +}; + +class Struct::Loader: public Msp::DataFile::ObjectLoader +{ +private: + const SetupGen &gen; + +public: + Loader(Struct &, const SetupGen &); + +private: + void init_actions() override; + + void field(const Msp::DataFile::Symbol &, const Msp::DataFile::Symbol &); +}; + +#endif diff --git a/tools/setupgen/type.cpp b/tools/setupgen/type.cpp new file mode 100644 index 0000000..4eb19da --- /dev/null +++ b/tools/setupgen/type.cpp @@ -0,0 +1,171 @@ +#include "type.h" +#include +#include +#include + +using namespace std; +using namespace Msp; + +Type::Type(const string &n, Kind k): + name(n), + kind(k) +{ + if(kind==VALUE || kind==ENUM) + cpp_type = name; + if(kind==STRUCT) + cpp_type = name+"Setup"; +} + +Type &Type::set_cpp_type(const string &t, const string &h) +{ + cpp_type = t; + header = h; + return *this; +} + +Type &Type::set_elements(const Type &t, unsigned c) +{ + if(kind!=AGGREGATE && kind!=POINTER && kind!=DYN_ARRAY) + throw invalid_state("wrong kind"); + if((kind==POINTER && c>1) || (kind==DYN_ARRAY && c>0)) + throw invalid_argument("Type::set_elements"); + + element_type = &t; + element_count = (kind==POINTER ? 1 : c); + if(cpp_type.empty()) + { + if(kind==POINTER) + cpp_type = format("%s *", t.get_cpp_type()); + else if(kind==DYN_ARRAY) + cpp_type = format("std::vector<%s>", t.get_cpp_type()); + } + + return *this; +} + +Type &Type::set_struct(const Struct &s) +{ + if(kind!=STRUCT) + throw invalid_state("wrong kind"); + struct_def = &s; + return *this; +} + +Type &Type::set_enum(const Enum &e) +{ + if(kind!=ENUM) + throw invalid_state("wrong kind"); + enum_def = &e; + return *this; +} + +Type &Type::set_load(const string &l, const string &c) +{ + if(kind!=VALUE) + throw invalid_state("wrong kind"); + load_type = l; + conversion = c; + return *this; +} + +const Type &Type::get_element_type() const +{ + if(kind!=AGGREGATE && kind!=POINTER && kind!=DYN_ARRAY) + throw invalid_state("wrong kind"); + if(!element_type) + throw invalid_state("no element type"); + return *element_type; +} + +const Struct &Type::get_struct() const +{ + if(kind!=STRUCT) + throw invalid_state("wrong kind"); + if(!struct_def) + throw invalid_state("no struct"); + return *struct_def; +} + +const Enum &Type::get_enum() const +{ + if(kind!=ENUM) + throw invalid_state("wrong kind"); + if(!enum_def) + throw invalid_state("no enum"); + return *enum_def; +} + +bool Type::needs_loader_function() const +{ + switch(kind) + { + case VALUE: return !load_type.empty(); + case POINTER: + case ENUM: return false; + case AGGREGATE: + case DYN_ARRAY: + case STRUCT: return true; + default: throw invalid_state("bad kind"); + } +} + +string Type::create_loader_params(bool with_names) const +{ + if(kind==AGGREGATE) + { + string result; + for(unsigned i=0; iget_cpp_type(), i)); + else + append(result, ", ", element_type->get_cpp_type()); + } + return result; + } + else if(kind==VALUE) + { + const string &type = (load_type.empty() ? cpp_type : load_type); + return (with_names ? format("%s _a", type) : type); + } + else if(kind==DYN_ARRAY) + { + Kind elem_kind = element_type->get_kind(); + if(elem_kind==VALUE || elem_kind==ENUM) + { + string type = format("std::vector<%s>", element_type->get_cpp_type()); + return (with_names ? format("%s _a", type) : type); + } + else + return string(); + } + else if(kind==STRUCT) + return string(); + else + throw invalid_state("wrong kind"); +} + +string Type::create_loader_statement() const +{ + if(kind==AGGREGATE) + { + string init; + for(unsigned i=0; iget_kind(); + if(elem_kind==VALUE || elem_kind==ENUM) + return "_v.insert(_v.end(), _a.begin(), _a.end());"; + else + return format("%s _e; load_sub(_e); _v.emplace_back(move(_e));", cpp_type); + } + else + throw invalid_state("wrong kind"); +} diff --git a/tools/setupgen/type.h b/tools/setupgen/type.h new file mode 100644 index 0000000..3338fd9 --- /dev/null +++ b/tools/setupgen/type.h @@ -0,0 +1,57 @@ +#ifndef TYPE_H_ +#define TYPE_H_ + +#include + +class Enum; +class Struct; + +class Type +{ +public: + enum Kind + { + VALUE, + AGGREGATE, + POINTER, + DYN_ARRAY, + STRUCT, + ENUM + }; + +private: + std::string name; + Kind kind = VALUE; + std::string cpp_type; + std::string header; + const Type *element_type = nullptr; + unsigned element_count = 0; + std::string load_type; + std::string conversion; + const Struct *struct_def = nullptr; + const Enum *enum_def = nullptr; + +public: + Type(const std::string &, Kind); + + Type &set_cpp_type(const std::string &, const std::string & = std::string()); + Type &set_elements(const Type &, unsigned = 0); + Type &set_struct(const Struct &); + Type &set_enum(const Enum &); + Type &set_load(const std::string &, const std::string & = std::string()); + + const std::string &get_name() const { return name; } + Kind get_kind() const { return kind; } + const std::string &get_cpp_type() const { return cpp_type; } + const std::string &get_header() const { return header; } + const Type &get_element_type() const; + const std::string &get_conversion() const { return conversion; } + const Struct &get_struct() const; + const Enum &get_enum() const; + + bool needs_loader_function() const; + std::string create_loader_params(bool) const; + std::string create_loader_statement() const; +}; + +#endif -- 2.45.2