diff options
Diffstat (limited to 'tools/mkclass')
-rw-r--r-- | tools/mkclass/CMakeLists.txt | 43 | ||||
-rw-r--r-- | tools/mkclass/class_lexer.ll | 167 | ||||
-rw-r--r-- | tools/mkclass/class_parser.yy | 558 | ||||
-rw-r--r-- | tools/mkclass/classcompiler.cpp | 1485 | ||||
-rw-r--r-- | tools/mkclass/classcompiler.hpp | 245 | ||||
-rw-r--r-- | tools/mkclass/mkclass.cpp | 16 |
6 files changed, 2514 insertions, 0 deletions
diff --git a/tools/mkclass/CMakeLists.txt b/tools/mkclass/CMakeLists.txt new file mode 100644 index 0000000..1b97bda --- /dev/null +++ b/tools/mkclass/CMakeLists.txt @@ -0,0 +1,43 @@ +# Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ + +find_package(BISON 2.3.0 REQUIRED) +find_package(FLEX 2.5.31 REQUIRED) + +bison_target(class_parser class_parser.yy ${CMAKE_CURRENT_BINARY_DIR}/class_parser.cc) +flex_target(class_lexer class_lexer.ll ${CMAKE_CURRENT_BINARY_DIR}/class_lexer.cc) +add_flex_bison_dependency(class_lexer class_parser) + +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set_property(SOURCE ${CMAKE_CURRENT_BINARY_DIR}/class_parser.cc PROPERTY COMPILE_FLAGS "-Wno-deprecated-register") + set_property(SOURCE ${CMAKE_CURRENT_BINARY_DIR}/class_lexer.cc PROPERTY COMPILE_FLAGS "-Wno-deprecated-register -Wno-null-conversion") +endif() + +include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) + +set(mkclass_SOURCES + mkclass.cpp + classcompiler.cpp classcompiler.hpp + ${FLEX_class_lexer_OUTPUTS} + ${BISON_class_parser_OUTPUTS} +) + +add_executable(mkclass ${mkclass_SOURCES}) + +if(WIN32) + target_link_libraries(mkclass shlwapi) +endif() + +set_target_properties ( + mkclass PROPERTIES + FOLDER Bin +) + +macro(MKCLASS_TARGET ClassInput ClassImplOutput ClassHeaderOutput) + add_custom_command( + OUTPUT ${ClassImplOutput} ${ClassHeaderOutput} + COMMAND mkclass + ARGS ${ClassInput} ${CMAKE_CURRENT_BINARY_DIR}/${ClassImplOutput} ${CMAKE_CURRENT_BINARY_DIR}/${ClassHeaderOutput} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS mkclass ${ClassInput} + ) +endmacro() diff --git a/tools/mkclass/class_lexer.ll b/tools/mkclass/class_lexer.ll new file mode 100644 index 0000000..217ca49 --- /dev/null +++ b/tools/mkclass/class_lexer.ll @@ -0,0 +1,167 @@ +%{ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "classcompiler.hpp" + +using namespace icinga; + +#define YYLTYPE icinga::ClassDebugInfo + +#include "class_parser.hh" + +#define YY_EXTRA_TYPE ClassCompiler * +#define YY_USER_ACTION \ +do { \ + yylloc->path = yyextra->GetPath(); \ + yylloc->first_line = yylineno; \ + yylloc->first_column = yycolumn; \ + yylloc->last_line = yylineno; \ + yylloc->last_column = yycolumn + yyleng - 1; \ + yycolumn += yyleng; \ +} while (0); + +#define YY_INPUT(buf, result, max_size) \ +do { \ + result = yyextra->ReadInput(buf, max_size); \ +} while (0) + +struct lex_buf { + char *buf; + size_t size; +}; + +static void lb_init(lex_buf *lb) +{ + lb->buf = NULL; + lb->size = 0; +} + +/*static void lb_cleanup(lex_buf *lb) +{ + free(lb->buf); +}*/ + +static void lb_append_char(lex_buf *lb, char new_char) +{ + const size_t block_size = 64; + + size_t old_blocks = (lb->size + (block_size - 1)) / block_size; + size_t new_blocks = ((lb->size + 1) + (block_size - 1)) / block_size; + + if (old_blocks != new_blocks) { + char *new_buf = (char *)realloc(lb->buf, new_blocks * block_size); + + if (new_buf == NULL && new_blocks > 0) + throw std::bad_alloc(); + + lb->buf = new_buf; + } + + lb->size++; + lb->buf[lb->size - 1] = new_char; +} + +static char *lb_steal(lex_buf *lb) +{ + lb_append_char(lb, '\0'); + + char *buf = lb->buf; + lb->buf = NULL; + lb->size = 0; + return buf; +} +%} + +%option reentrant noyywrap yylineno +%option bison-bridge bison-locations +%option never-interactive nounistd +%option noinput nounput + +%x HEREDOC +%x C_COMMENT + +%% + lex_buf string_buf; + +\{\{\{ { lb_init(&string_buf); BEGIN(HEREDOC); } + +<HEREDOC>\}\}\} { + BEGIN(INITIAL); + + lb_append_char(&string_buf, '\0'); + + yylval->text = lb_steal(&string_buf); + + return T_STRING; + } + +<HEREDOC>(.|\n) { lb_append_char(&string_buf, yytext[0]); } + +"/*" { BEGIN(C_COMMENT); } + +<C_COMMENT>{ +"*/" { BEGIN(INITIAL); } +[^*] /* ignore comment */ +"*" /* ignore star */ +} + +<C_COMMENT><<EOF>> { + fprintf(stderr, "End-of-file while in comment.\n"); + yyterminate(); + } +\/\/[^\n]* /* ignore C++-style comments */ +[ \t\r\n] /* ignore whitespace */ + +#include { return T_INCLUDE; } +#impl_include { return T_IMPL_INCLUDE; } +class { return T_CLASS; } +namespace { return T_NAMESPACE; } +code { return T_CODE; } +load_after { return T_LOAD_AFTER; } +activation_priority { return T_ACTIVATION_PRIORITY; } +library { return T_LIBRARY; } +abstract { yylval->num = TAAbstract; return T_CLASS_ATTRIBUTE; } +vararg_constructor { yylval->num = TAVarArgConstructor; return T_CLASS_ATTRIBUTE; } +config { yylval->num = FAConfig; return T_FIELD_ATTRIBUTE; } +state { yylval->num = FAState; return T_FIELD_ATTRIBUTE; } +enum { yylval->num = FAEnum; return T_FIELD_ATTRIBUTE; } +get_protected { yylval->num = FAGetProtected; return T_FIELD_ATTRIBUTE; } +set_protected { yylval->num = FASetProtected; return T_FIELD_ATTRIBUTE; } +protected { yylval->num = FAGetProtected | FASetProtected; return T_FIELD_ATTRIBUTE; } +no_storage { yylval->num = FANoStorage; return T_FIELD_ATTRIBUTE; } +no_user_modify { yylval->num = FANoUserModify; return T_FIELD_ATTRIBUTE; } +no_user_view { yylval->num = FANoUserView; return T_FIELD_ATTRIBUTE; } +deprecated { yylval->num = FADeprecated; return T_FIELD_ATTRIBUTE; } +get_virtual { yylval->num = FAGetVirtual; return T_FIELD_ATTRIBUTE; } +set_virtual { yylval->num = FASetVirtual; return T_FIELD_ATTRIBUTE; } +signal_with_old_value { yylval->num = FASignalWithOldValue; return T_FIELD_ATTRIBUTE; } +virtual { yylval->num = FAGetVirtual | FASetVirtual; return T_FIELD_ATTRIBUTE; } +navigation { return T_NAVIGATION; } +validator { return T_VALIDATOR; } +required { return T_REQUIRED; } +name { return T_NAME; } +array { return T_ARRAY; } +default { yylval->num = FTDefault; return T_FIELD_ACCESSOR_TYPE; } +get { yylval->num = FTGet; return T_FIELD_ACCESSOR_TYPE; } +set { yylval->num = FTSet; return T_FIELD_ACCESSOR_TYPE; } +track { yylval->num = FTTrack; return T_FIELD_ACCESSOR_TYPE; } +navigate { yylval->num = FTNavigate; return T_FIELD_ACCESSOR_TYPE; } +\"[^\"]+\" { yylval->text = strdup(yytext + 1); yylval->text[strlen(yylval->text) - 1] = '\0'; return T_STRING; } +\<[^ \>]*\> { yylval->text = strdup(yytext + 1); yylval->text[strlen(yylval->text) - 1] = '\0'; return T_ANGLE_STRING; } +[a-zA-Z_][:a-zA-Z0-9\-_]* { yylval->text = strdup(yytext); return T_IDENTIFIER; } +-?[0-9]+(\.[0-9]+)? { yylval->num = strtod(yytext, NULL); return T_NUMBER; } + +. return yytext[0]; + +%% + +void ClassCompiler::InitializeScanner(void) +{ + yylex_init(&m_Scanner); + yyset_extra(this, m_Scanner); +} + +void ClassCompiler::DestroyScanner(void) +{ + yylex_destroy(m_Scanner); +} diff --git a/tools/mkclass/class_parser.yy b/tools/mkclass/class_parser.yy new file mode 100644 index 0000000..0524b2d --- /dev/null +++ b/tools/mkclass/class_parser.yy @@ -0,0 +1,558 @@ +%{ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "classcompiler.hpp" +#include <iostream> +#include <vector> +#include <cstring> + +using std::malloc; +using std::free; +using std::exit; + +using namespace icinga; + +#define YYLTYPE icinga::ClassDebugInfo + +%} + +%pure-parser + +%locations +%defines +%error-verbose + +%parse-param { ClassCompiler *context } +%lex-param { void *scanner } + +%union { + char *text; + int num; + FieldType *type; + Field *field; + std::vector<Field> *fields; + Klass *klass; + FieldAccessor *fieldaccessor; + std::vector<FieldAccessor> *fieldaccessors; + Rule *rule; + std::vector<Rule> *rules; + Validator *validator; +} + +%token T_INCLUDE "#include (T_INCLUDE)" +%token T_IMPL_INCLUDE "#impl_include (T_IMPL_INCLUDE)" +%token T_CLASS "class (T_CLASS)" +%token T_CODE "code (T_CODE)" +%token T_LOAD_AFTER "load_after (T_LOAD_AFTER)" +%token T_ACTIVATION_PRIORITY "activation_priority (T_ACTIVATION_PRIORITY)" +%token T_LIBRARY "library (T_LIBRARY)" +%token T_NAMESPACE "namespace (T_NAMESPACE)" +%token T_VALIDATOR "validator (T_VALIDATOR)" +%token T_REQUIRED "required (T_REQUIRED)" +%token T_NAVIGATION "navigation (T_NAVIGATION)" +%token T_NAME "name (T_NAME)" +%token T_ARRAY "array (T_ARRAY)" +%token T_STRING "string (T_STRING)" +%token T_ANGLE_STRING "angle_string (T_ANGLE_STRING)" +%token T_FIELD_ATTRIBUTE "field_attribute (T_FIELD_ATTRIBUTE)" +%token T_CLASS_ATTRIBUTE "class_attribute (T_CLASS_ATTRIBUTE)" +%token T_IDENTIFIER "identifier (T_IDENTIFIER)" +%token T_GET "get (T_GET)" +%token T_SET "set (T_SET)" +%token T_DEFAULT "default (T_DEFAULT)" +%token T_FIELD_ACCESSOR_TYPE "field_accessor_type (T_FIELD_ACCESSOR_TYPE)" +%token T_NUMBER "number (T_NUMBER)" +%type <text> T_IDENTIFIER +%type <text> T_STRING +%type <text> T_ANGLE_STRING +%type <text> identifier +%type <text> alternative_name_specifier +%type <text> inherits_specifier +%type <text> type_base_specifier +%type <text> include +%type <text> angle_include +%type <text> impl_include +%type <text> angle_impl_include +%type <text> code +%type <num> T_FIELD_ATTRIBUTE +%type <field> field_attribute +%type <field> field_attributes +%type <field> field_attribute_list +%type <num> T_FIELD_ACCESSOR_TYPE +%type <num> T_CLASS_ATTRIBUTE +%type <num> class_attribute_list +%type <type> field_type +%type <field> class_field +%type <fields> class_fields +%type <klass> class +%type <fieldaccessors> field_accessor_list +%type <fieldaccessors> field_accessors +%type <fieldaccessor> field_accessor +%type <rule> validator_rule +%type <rules> validator_rules +%type <validator> validator +%type <num> T_NUMBER + +%{ + +int yylex(YYSTYPE *lvalp, YYLTYPE *llocp, void *scanner); + +void yyerror(YYLTYPE *locp, ClassCompiler *, const char *err) +{ + std::cerr << "in " << locp->path << " at " << locp->first_line << ":" << locp->first_column << "-" + << locp->last_line << ":" << locp->last_column << ": " << err << std::endl; + std::exit(1); +} + +int yyparse(ClassCompiler *context); + +void ClassCompiler::Compile(void) +{ + try { + yyparse(this); + } catch (const std::exception& ex) { + std::cerr << "Exception: " << ex.what(); + } + + HandleMissingValidators(); +} + +#define scanner (context->GetScanner()) + +%} + +%% + +statements: /* empty */ + | statements statement + ; + +statement: include + { + context->HandleInclude($1, yylloc); + std::free($1); + } + | angle_include + { + context->HandleAngleInclude($1, yylloc); + std::free($1); + } + | impl_include + { + context->HandleImplInclude($1, yylloc); + std::free($1); + } + | angle_impl_include + { + context->HandleAngleImplInclude($1, yylloc); + std::free($1); + } + | class + { + context->HandleClass(*$1, yylloc); + delete $1; + } + | validator + { + context->HandleValidator(*$1, yylloc); + delete $1; + } + | namespace + | code + { + context->HandleCode($1, yylloc); + std::free($1); + } + | library + ; + +include: T_INCLUDE T_STRING + { + $$ = $2; + } + ; + +angle_include: T_INCLUDE T_ANGLE_STRING + { + $$ = $2; + } + ; + +impl_include: T_IMPL_INCLUDE T_STRING + { + $$ = $2; + } + ; + +angle_impl_include: T_IMPL_INCLUDE T_ANGLE_STRING + { + $$ = $2; + } + ; + +namespace: T_NAMESPACE identifier '{' + { + context->HandleNamespaceBegin($2, yylloc); + std::free($2); + } + statements '}' + { + context->HandleNamespaceEnd(yylloc); + } + ; + +code: T_CODE T_STRING + { + $$ = $2; + } + ; + +library: T_LIBRARY T_IDENTIFIER ';' + { + context->HandleLibrary($2, yylloc); + free($2); + } + ; + +class: class_attribute_list T_CLASS T_IDENTIFIER inherits_specifier type_base_specifier '{' class_fields '}' ';' + { + $$ = new Klass(); + + $$->Name = $3; + std::free($3); + + if ($4) { + $$->Parent = $4; + std::free($4); + } + + if ($5) { + $$->TypeBase = $5; + std::free($5); + } + + $$->Attributes = $1; + + for (const Field& field : *$7) { + if (field.Attributes & FALoadDependency) { + $$->LoadDependencies.push_back(field.Name); + } else if (field.Attributes & FAActivationPriority) { + $$->ActivationPriority = field.Priority; + } else + $$->Fields.push_back(field); + } + + delete $7; + + ClassCompiler::OptimizeStructLayout($$->Fields); + } + ; + +class_attribute_list: /* empty */ + { + $$ = 0; + } + | T_CLASS_ATTRIBUTE + { + $$ = $1; + } + | class_attribute_list T_CLASS_ATTRIBUTE + { + $$ = $1 | $2; + } + +inherits_specifier: /* empty */ + { + $$ = NULL; + } + | ':' identifier + { + $$ = $2; + } + ; + +type_base_specifier: /* empty */ + { + $$ = NULL; + } + | '<' identifier + { + $$ = $2; + } + ; + +class_fields: /* empty */ + { + $$ = new std::vector<Field>(); + } + | class_fields class_field + { + $$->push_back(*$2); + delete $2; + } + ; + +field_type: identifier + { + $$ = new FieldType(); + $$->IsName = false; + $$->TypeName = $1; + free($1); + } + | T_NAME '(' identifier ')' + { + $$ = new FieldType(); + $$->IsName = true; + $$->TypeName = $3; + $$->ArrayRank = 0; + free($3); + } + | T_ARRAY '(' field_type ')' + { + $$ = $3; + $$->ArrayRank++; + } + ; + +class_field: field_attribute_list field_type identifier alternative_name_specifier field_accessor_list ';' + { + Field *field = $1; + + if ((field->Attributes & (FAConfig | FAState)) == 0) + field->Attributes |= FAEphemeral; + + field->Type = *$2; + delete $2; + + field->Name = $3; + std::free($3); + + if ($4) { + field->AlternativeName = $4; + std::free($4); + } + + std::vector<FieldAccessor>::const_iterator it; + for (it = $5->begin(); it != $5->end(); it++) { + switch (it->Type) { + case FTGet: + field->GetAccessor = it->Accessor; + field->PureGetAccessor = it->Pure; + break; + case FTSet: + field->SetAccessor = it->Accessor; + field->PureSetAccessor = it->Pure; + break; + case FTDefault: + field->DefaultAccessor = it->Accessor; + break; + case FTTrack: + field->TrackAccessor = it->Accessor; + break; + case FTNavigate: + field->NavigateAccessor = it->Accessor; + field->PureNavigateAccessor = it->Pure; + break; + } + } + + delete $5; + + $$ = field; + } + | T_LOAD_AFTER identifier ';' + { + auto *field = new Field(); + field->Attributes = FALoadDependency; + field->Name = $2; + std::free($2); + $$ = field; + } + | T_ACTIVATION_PRIORITY T_NUMBER ';' + { + auto *field = new Field(); + field->Attributes = FAActivationPriority; + field->Priority = $2; + $$ = field; + } + ; + +alternative_name_specifier: /* empty */ + { + $$ = NULL; + } + | '(' identifier ')' + { + $$ = $2; + } + ; + +field_attribute_list: /* empty */ + { + $$ = new Field(); + } + | '[' field_attributes ']' + { + $$ = $2; + } + ; + +field_attribute: T_FIELD_ATTRIBUTE + { + $$ = new Field(); + $$->Attributes = $1; + } + | T_REQUIRED + { + $$ = new Field(); + $$->Attributes = FARequired; + } + | T_NAVIGATION '(' identifier ')' + { + $$ = new Field(); + $$->Attributes = FANavigation; + $$->NavigationName = $3; + std::free($3); + } + | T_NAVIGATION + { + $$ = new Field(); + $$->Attributes = FANavigation; + } + ; + +field_attributes: /* empty */ + { + $$ = new Field(); + } + | field_attributes ',' field_attribute + { + $$ = $1; + $$->Attributes |= $3->Attributes; + if (!$3->NavigationName.empty()) + $$->NavigationName = $3->NavigationName; + delete $3; + } + | field_attribute + { + $$ = $1; + } + ; + +field_accessor_list: /* empty */ + { + $$ = new std::vector<FieldAccessor>(); + } + | '{' field_accessors '}' + { + $$ = $2; + } + ; + +field_accessors: /* empty */ + { + $$ = new std::vector<FieldAccessor>(); + } + | field_accessors field_accessor + { + $$ = $1; + $$->push_back(*$2); + delete $2; + } + ; + +field_accessor: T_FIELD_ACCESSOR_TYPE T_STRING + { + $$ = new FieldAccessor(static_cast<FieldAccessorType>($1), $2, false); + std::free($2); + } + | T_FIELD_ACCESSOR_TYPE ';' + { + $$ = new FieldAccessor(static_cast<FieldAccessorType>($1), "", true); + } + ; + +validator_rules: /* empty */ + { + $$ = new std::vector<Rule>(); + } + | validator_rules validator_rule + { + $$->push_back(*$2); + delete $2; + } + ; + +validator_rule: T_NAME '(' T_IDENTIFIER ')' identifier ';' + { + $$ = new Rule(); + $$->Attributes = 0; + $$->IsName = true; + $$->Type = $3; + std::free($3); + $$->Pattern = $5; + std::free($5); + } + | T_IDENTIFIER identifier ';' + { + $$ = new Rule(); + $$->Attributes = 0; + $$->IsName = false; + $$->Type = $1; + std::free($1); + $$->Pattern = $2; + std::free($2); + } + | T_NAME '(' T_IDENTIFIER ')' identifier '{' validator_rules '}' ';' + { + $$ = new Rule(); + $$->Attributes = 0; + $$->IsName = true; + $$->Type = $3; + std::free($3); + $$->Pattern = $5; + std::free($5); + $$->Rules = *$7; + delete $7; + } + | T_IDENTIFIER identifier '{' validator_rules '}' ';' + { + $$ = new Rule(); + $$->Attributes = 0; + $$->IsName = false; + $$->Type = $1; + std::free($1); + $$->Pattern = $2; + std::free($2); + $$->Rules = *$4; + delete $4; + } + | T_REQUIRED identifier ';' + { + $$ = new Rule(); + $$->Attributes = RARequired; + $$->IsName = false; + $$->Type = ""; + $$->Pattern = $2; + std::free($2); + } + ; + +validator: T_VALIDATOR T_IDENTIFIER '{' validator_rules '}' ';' + { + $$ = new Validator(); + + $$->Name = $2; + std::free($2); + + $$->Rules = *$4; + delete $4; + } + ; + +identifier: T_IDENTIFIER + | T_STRING + { + $$ = $1; + } + ; diff --git a/tools/mkclass/classcompiler.cpp b/tools/mkclass/classcompiler.cpp new file mode 100644 index 0000000..3a49576 --- /dev/null +++ b/tools/mkclass/classcompiler.cpp @@ -0,0 +1,1485 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "classcompiler.hpp" +#include <iostream> +#include <sstream> +#include <fstream> +#include <stdexcept> +#include <map> +#include <set> +#include <utility> +#include <vector> +#include <cstring> +#include <locale> +#ifndef _WIN32 +#include <libgen.h> +#else /* _WIN32 */ +#include <shlwapi.h> +#endif /* _WIN32 */ + +using namespace icinga; + +ClassCompiler::ClassCompiler(std::string path, std::istream& input, + std::ostream& oimpl, std::ostream& oheader) + : m_Path(std::move(path)), m_Input(input), m_Impl(oimpl), m_Header(oheader) +{ + InitializeScanner(); +} + +ClassCompiler::~ClassCompiler() +{ + DestroyScanner(); +} + +std::string ClassCompiler::GetPath() const +{ + return m_Path; +} + +void *ClassCompiler::GetScanner() +{ + return m_Scanner; +} + +size_t ClassCompiler::ReadInput(char *buffer, size_t max_size) +{ + m_Input.read(buffer, max_size); + return static_cast<size_t>(m_Input.gcount()); +} + +void ClassCompiler::HandleInclude(const std::string& path, const ClassDebugInfo&) +{ + m_Header << "#include \"" << path << "\"" << std::endl << std::endl; +} + +void ClassCompiler::HandleAngleInclude(const std::string& path, const ClassDebugInfo&) +{ + m_Header << "#include <" << path << ">" << std::endl << std::endl; +} + +void ClassCompiler::HandleImplInclude(const std::string& path, const ClassDebugInfo&) +{ + m_Impl << "#include \"" << path << "\"" << std::endl << std::endl; +} + +void ClassCompiler::HandleAngleImplInclude(const std::string& path, const ClassDebugInfo&) +{ + m_Impl << "#include <" << path << ">" << std::endl << std::endl; +} + +void ClassCompiler::HandleNamespaceBegin(const std::string& name, const ClassDebugInfo&) +{ + m_Header << "namespace " << name << std::endl + << "{" << std::endl << std::endl; + + m_Impl << "namespace " << name << std::endl + << "{" << std::endl << std::endl; +} + +void ClassCompiler::HandleNamespaceEnd(const ClassDebugInfo&) +{ + HandleMissingValidators(); + + m_Header << "}" << std::endl; + + m_Impl << "}" << std::endl; +} + +void ClassCompiler::HandleCode(const std::string& code, const ClassDebugInfo&) +{ + m_Header << code << std::endl; +} + +void ClassCompiler::HandleLibrary(const std::string& library, const ClassDebugInfo&) +{ + m_Library = library; +} + +unsigned long ClassCompiler::SDBM(const std::string& str, size_t len = std::string::npos) +{ + unsigned long hash = 0; + size_t current = 0; + + for (const char& ch : str) { + if (current >= len) + break; + + hash = ch + (hash << 6) + (hash << 16) - hash; + + current++; + } + + return hash; +} + +static int TypePreference(const std::string& type) +{ + if (type == "Value") + return 0; + else if (type == "String") + return 1; + else if (type == "double") + return 2; + else if (type.find("::Ptr") != std::string::npos) + return 3; + else if (type == "int") + return 4; + else + return 5; +} + +static bool FieldLayoutCmp(const Field& a, const Field& b) +{ + return TypePreference(a.Type.GetRealType()) < TypePreference(b.Type.GetRealType()); +} + +static bool FieldTypeCmp(const Field& a, const Field& b) +{ + return a.Type.GetRealType() < b.Type.GetRealType(); +} + +static std::string FieldTypeToIcingaName(const Field& field, bool inner) +{ + std::string ftype = field.Type.TypeName; + + if (!inner && field.Type.ArrayRank > 0) + return "Array"; + + if (field.Type.IsName) + return "String"; + + if (field.Attributes & FAEnum) + return "Number"; + + if (ftype == "int" || ftype == "double") + return "Number"; + else if (ftype == "bool") + return "Boolean"; + + if (ftype.find("::Ptr") != std::string::npos) + return ftype.substr(0, ftype.size() - strlen("::Ptr")); + + return ftype; +} + +void ClassCompiler::OptimizeStructLayout(std::vector<Field>& fields) +{ + std::sort(fields.begin(), fields.end(), FieldTypeCmp); + std::stable_sort(fields.begin(), fields.end(), FieldLayoutCmp); +} + +void ClassCompiler::HandleClass(const Klass& klass, const ClassDebugInfo&) +{ + /* forward declaration */ + if (klass.Name.find_first_of(':') == std::string::npos) + m_Header << "class " << klass.Name << ";" << std::endl << std::endl; + + /* TypeHelper */ + if (klass.Attributes & TAAbstract) { + m_Header << "template<>" << std::endl + << "struct TypeHelper<" << klass.Name << ", " << ((klass.Attributes & TAVarArgConstructor) ? "true" : "false") << ">" << std::endl + << "{" << std::endl + << "\t" << "static ObjectFactory GetFactory()" << std::endl + << "\t" << "{" << std::endl + << "\t\t" << "return nullptr;" << std::endl + << "\t" << "}" << std::endl + << "};" << std::endl << std::endl; + } + + /* TypeImpl */ + m_Header << "template<>" << std::endl + << "class TypeImpl<" << klass.Name << ">" + << " : public Type"; + + if (!klass.Parent.empty()) + m_Header << "Impl<" << klass.Parent << ">"; + + if (!klass.TypeBase.empty()) + m_Header << ", public " + klass.TypeBase; + + m_Header << std::endl + << "{" << std::endl + << "public:" << std::endl + << "\t" << "DECLARE_PTR_TYPEDEFS(TypeImpl<" << klass.Name << ">);" << std::endl << std::endl + << "\t" << "TypeImpl();" << std::endl + << "\t" << "~TypeImpl() override;" << std::endl << std::endl; + + m_Impl << "TypeImpl<" << klass.Name << ">::TypeImpl()" << std::endl + << "{ }" << std::endl << std::endl + << "TypeImpl<" << klass.Name << ">::~TypeImpl()" << std::endl + << "{ }" << std::endl << std::endl; + + /* GetName */ + m_Header << "\t" << "String GetName() const override;" << std::endl; + + m_Impl << "String TypeImpl<" << klass.Name << ">::GetName() const" << std::endl + << "{" << std::endl + << "\t" << "return \"" << klass.Name << "\";" << std::endl + << "}" << std::endl << std::endl; + + /* GetAttributes */ + m_Header << "\t" << "int GetAttributes() const override;" << std::endl; + + m_Impl << "int TypeImpl<" << klass.Name << ">::GetAttributes() const" << std::endl + << "{" << std::endl + << "\t" << "return " << klass.Attributes << ";" << std::endl + << "}" << std::endl << std::endl; + + /* GetBaseType */ + m_Header << "\t" << "Type::Ptr GetBaseType() const override;" << std::endl; + + m_Impl << "Type::Ptr TypeImpl<" << klass.Name << ">::GetBaseType() const" << std::endl + << "{" << std::endl + << "\t" << "return "; + + if (!klass.Parent.empty()) + m_Impl << klass.Parent << "::TypeInstance"; + else + m_Impl << "Object::TypeInstance"; + + m_Impl << ";" << std::endl + << "}" << std::endl << std::endl; + + /* GetFieldId */ + m_Header << "\t" << "int GetFieldId(const String& name) const override;" << std::endl; + + m_Impl << "int TypeImpl<" << klass.Name << ">::GetFieldId(const String& name) const" << std::endl + << "{" << std::endl; + + if (!klass.Fields.empty()) { + m_Impl << "\t" << "int offset = "; + + if (!klass.Parent.empty()) + m_Impl << klass.Parent << "::TypeInstance->GetFieldCount()"; + else + m_Impl << "0"; + + m_Impl << ";" << std::endl << std::endl; + } + + std::map<int, std::vector<std::pair<int, std::string> > > jumptable; + + int hlen = 0, collisions = 0; + + do { + int num = 0; + + hlen++; + jumptable.clear(); + collisions = 0; + + for (const Field& field : klass.Fields) { + auto hash = static_cast<int>(SDBM(field.Name, hlen)); + jumptable[hash].emplace_back(num, field.Name); + num++; + + if (jumptable[hash].size() > 1) + collisions++; + } + } while (collisions >= 5 && hlen < 8); + + if (!klass.Fields.empty()) { + m_Impl << "\tswitch (static_cast<int>(Utility::SDBM(name, " << hlen << "))) {" << std::endl; + + for (const auto& itj : jumptable) { + m_Impl << "\t\tcase " << itj.first << ":" << std::endl; + + for (const auto& itf : itj.second) { + m_Impl << "\t\t\t" << "if (name == \"" << itf.second << "\")" << std::endl + << "\t\t\t\t" << "return offset + " << itf.first << ";" << std::endl; + } + + m_Impl << std::endl + << "\t\t\tbreak;" << std::endl; + } + + m_Impl << "\t}" << std::endl; + } + + m_Impl << std::endl + << "\t" << "return "; + + if (!klass.Parent.empty()) + m_Impl << klass.Parent << "::TypeInstance->GetFieldId(name)"; + else + m_Impl << "-1"; + + m_Impl << ";" << std::endl + << "}" << std::endl << std::endl; + + /* GetFieldInfo */ + m_Header << "\t" << "Field GetFieldInfo(int id) const override;" << std::endl; + + m_Impl << "Field TypeImpl<" << klass.Name << ">::GetFieldInfo(int id) const" << std::endl + << "{" << std::endl; + + if (!klass.Parent.empty()) + m_Impl << "\t" << "int real_id = id - " << klass.Parent << "::TypeInstance->GetFieldCount();" << std::endl + << "\t" << "if (real_id < 0) { return " << klass.Parent << "::TypeInstance->GetFieldInfo(id); }" << std::endl; + + if (!klass.Fields.empty()) { + m_Impl << "\t" << "switch ("; + + if (!klass.Parent.empty()) + m_Impl << "real_id"; + else + m_Impl << "id"; + + m_Impl << ") {" << std::endl; + + size_t num = 0; + for (const Field& field : klass.Fields) { + std::string ftype = FieldTypeToIcingaName(field, false); + + std::string nameref; + + if (field.Type.IsName) + nameref = "\"" + field.Type.TypeName + "\""; + else + nameref = "nullptr"; + + m_Impl << "\t\t" << "case " << num << ":" << std::endl + << "\t\t\t" << "return {" << num << ", \"" << ftype << "\", \"" << field.Name << "\", \"" << (field.NavigationName.empty() ? field.Name : field.NavigationName) << "\", " << nameref << ", " << field.Attributes << ", " << field.Type.ArrayRank << "};" << std::endl; + num++; + } + + m_Impl << "\t\t" << "default:" << std::endl + << "\t\t"; + } + + m_Impl << "\t" << "throw std::runtime_error(\"Invalid field ID.\");" << std::endl; + + if (!klass.Fields.empty()) + m_Impl << "\t" << "}" << std::endl; + + m_Impl << "}" << std::endl << std::endl; + + /* GetFieldCount */ + m_Header << "\t" << "int GetFieldCount() const override;" << std::endl; + + m_Impl << "int TypeImpl<" << klass.Name << ">::GetFieldCount() const" << std::endl + << "{" << std::endl + << "\t" << "return " << klass.Fields.size(); + + if (!klass.Parent.empty()) + m_Impl << " + " << klass.Parent << "::TypeInstance->GetFieldCount()"; + + m_Impl << ";" << std::endl + << "}" << std::endl << std::endl; + + /* GetFactory */ + m_Header << "\t" << "ObjectFactory GetFactory() const override;" << std::endl; + + m_Impl << "ObjectFactory TypeImpl<" << klass.Name << ">::GetFactory() const" << std::endl + << "{" << std::endl + << "\t" << "return TypeHelper<" << klass.Name << ", " << ((klass.Attributes & TAVarArgConstructor) ? "true" : "false") << ">::GetFactory();" << std::endl + << "}" << std::endl << std::endl; + + /* GetLoadDependencies */ + m_Header << "\t" << "const std::unordered_set<Type*>& GetLoadDependencies() const override;" << std::endl; + + m_Impl << "const std::unordered_set<Type*>& TypeImpl<" << klass.Name << ">::GetLoadDependencies() const" << std::endl + << "{" << std::endl + << "\t" << "static const std::unordered_set<Type*> deps ({" << std::endl; + + for (const std::string& dep : klass.LoadDependencies) + m_Impl << "\t\t" << "GetByName(\"" << dep << "\").get()," << std::endl; + + m_Impl << "\t" << "});" << std::endl; + + m_Impl << "\t" << "return deps;" << std::endl + << "}" << std::endl << std::endl; + + /* GetActivationPriority */ + m_Header << "\t" << "int GetActivationPriority() const override;" << std::endl; + + m_Impl << "int TypeImpl<" << klass.Name << ">::GetActivationPriority() const" << std::endl + << "{" << std::endl + << "\t" << "return " << klass.ActivationPriority << ";" << std::endl + << "}" << std::endl << std::endl; + + /* RegisterAttributeHandler */ + m_Header << "public:" << std::endl + << "\t" << "void RegisterAttributeHandler(int fieldId, const Type::AttributeHandler& callback) override;" << std::endl; + + m_Impl << "void TypeImpl<" << klass.Name << ">::RegisterAttributeHandler(int fieldId, const Type::AttributeHandler& callback)" << std::endl + << "{" << std::endl; + + if (!klass.Parent.empty()) + m_Impl << "\t" << "int real_id = fieldId - " << klass.Parent << "::TypeInstance->GetFieldCount(); " << std::endl + << "\t" << "if (real_id < 0) { " << klass.Parent << "::TypeInstance->RegisterAttributeHandler(fieldId, callback); return; }" << std::endl; + + if (!klass.Fields.empty()) { + m_Impl << "\t" << "switch ("; + + if (!klass.Parent.empty()) + m_Impl << "real_id"; + else + m_Impl << "fieldId"; + + m_Impl << ") {" << std::endl; + + int num = 0; + for (const Field& field : klass.Fields) { + m_Impl << "\t\t" << "case " << num << ":" << std::endl + << "\t\t\t" << "ObjectImpl<" << klass.Name << ">::On" << field.GetFriendlyName() << "Changed.connect(callback);" << std::endl + << "\t\t\t" << "break;" << std::endl; + num++; + } + + m_Impl << "\t\t" << "default:" << std::endl + << "\t\t"; + } + m_Impl << "\t" << "throw std::runtime_error(\"Invalid field ID.\");" << std::endl; + + if (!klass.Fields.empty()) + m_Impl << "\t" << "}" << std::endl; + + m_Impl << "}" << std::endl << std::endl; + + m_Header << "};" << std::endl << std::endl; + + m_Header << std::endl; + + /* ObjectImpl */ + m_Header << "template<>" << std::endl + << "class ObjectImpl<" << klass.Name << ">" + << " : public " << (klass.Parent.empty() ? "Object" : klass.Parent) << std::endl + << "{" << std::endl + << "public:" << std::endl + << "\t" << "DECLARE_PTR_TYPEDEFS(ObjectImpl<" << klass.Name << ">);" << std::endl << std::endl; + + /* Validate */ + m_Header << "\t" << "void Validate(int types, const ValidationUtils& utils) override;" << std::endl; + + m_Impl << "void ObjectImpl<" << klass.Name << ">::Validate(int types, const ValidationUtils& utils)" << std::endl + << "{" << std::endl; + + if (!klass.Parent.empty()) + m_Impl << "\t" << klass.Parent << "::Validate(types, utils);" << std::endl << std::endl; + + for (const Field& field : klass.Fields) { + m_Impl << "\t" << "if (" << (field.Attributes & (FAEphemeral|FAConfig|FAState)) << " & types)" << std::endl + << "\t\t" << "Validate" << field.GetFriendlyName() << "(Lazy<" << field.Type.GetRealType() << ">([this]() { return Get" << field.GetFriendlyName() << "(); }), utils);" << std::endl; + } + + m_Impl << "}" << std::endl << std::endl; + + for (const Field& field : klass.Fields) { + std::string argName, valName; + + if (field.Type.ArrayRank > 0) { + argName = "avalue"; + valName = "value"; + } else { + argName = "value"; + valName = "value()"; + } + + m_Header << "\t" << "void SimpleValidate" << field.GetFriendlyName() << "(const Lazy<" << field.Type.GetRealType() << ">& " << argName << ", const ValidationUtils& utils);" << std::endl; + + m_Impl << "void ObjectImpl<" << klass.Name << ">::SimpleValidate" << field.GetFriendlyName() << "(const Lazy<" << field.Type.GetRealType() << ">& " << argName << ", const ValidationUtils& utils)" << std::endl + << "{" << std::endl; + + if (field.Attributes & FARequired) { + if (field.Type.GetRealType().find("::Ptr") != std::string::npos) + m_Impl << "\t" << "if (!" << argName << "())" << std::endl; + else + m_Impl << "\t" << "if (" << argName << "().IsEmpty())" << std::endl; + + m_Impl << "\t\t" << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_cast<ConfigObject *>(this), { \"" << field.Name << R"(" }, "Attribute must not be empty."));)" << std::endl << std::endl; + } + + if (field.Attributes & FADeprecated) { + if (field.Type.GetRealType().find("::Ptr") != std::string::npos) + m_Impl << "\t" << "if (" << argName << "())" << std::endl; + else + m_Impl << "\t" << "if (" << argName << "() != GetDefault" << field.GetFriendlyName() << "())" << std::endl; + + m_Impl << "\t\t" << "Log(LogWarning, \"" << klass.Name << "\") << \"Attribute '" << field.Name << R"(' for object '" << dynamic_cast<ConfigObject *>(this)->GetName() << "' of type '" << dynamic_cast<ConfigObject *>(this)->GetReflectionType()->GetName() << "' is deprecated and should not be used.";)" << std::endl; + } + + if (field.Type.ArrayRank > 0) { + m_Impl << "\t" << "if (avalue()) {" << std::endl + << "\t\t" << "ObjectLock olock(avalue());" << std::endl + << "\t\t" << "for (const Value& value : avalue()) {" << std::endl; + } + + std::string ftype = FieldTypeToIcingaName(field, true); + + if (ftype == "Value") { + m_Impl << "\t" << "if (" << valName << ".IsObjectType<Function>()) {" << std::endl + << "\t\t" << "Function::Ptr func = " << valName << ";" << std::endl + << "\t\t" << "if (func->IsDeprecated())" << std::endl + << "\t\t\t" << "Log(LogWarning, \"" << klass.Name << "\") << \"Attribute '" << field.Name << R"(' for object '" << dynamic_cast<ConfigObject *>(this)->GetName() << "' of type '" << dynamic_cast<ConfigObject *>(this)->GetReflectionType()->GetName() << "' is set to a deprecated function: " << func->GetName();)" << std::endl + << "\t" << "}" << std::endl << std::endl; + } + + if (field.Type.IsName) { + if (field.Type.ArrayRank > 0) { + m_Impl << "\t\t\t" << "if (!" << valName << ".IsEmpty() && "; + + m_Impl << "!" << valName << ".IsString())" << std::endl + << "\t\t\t\t" << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_cast<ConfigObject *>(this), { \"" + << field.Name << R"(" }, "It is not allowed to specify '" + )" << valName << R"( + "' of type '" + )" + << valName << ".GetTypeName()" << R"( + "' as ')" << field.Type.TypeName + << "' name. Expected type of '" << field.Type.TypeName << "' name: 'String'.\"));" << std::endl; + } + + m_Impl << "\t\t\t" << "if ("; + + if (field.Type.ArrayRank > 0) + m_Impl << valName << ".IsEmpty() || "; + else + m_Impl << "!" << valName << ".IsEmpty() && "; + + m_Impl << "!utils.ValidateName(\"" << field.Type.TypeName << "\", " << valName << "))" << std::endl + << "\t\t\t\t" << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_cast<ConfigObject *>(this), { \"" << field.Name << R"(" }, "Object '" + )" << valName << R"( + "' of type ')" << field.Type.TypeName + << "' does not exist.\"));" << std::endl; + } else if (field.Type.ArrayRank > 0 && (ftype == "Number" || ftype == "Boolean")) { + m_Impl << "\t" << "try {" << std::endl + << "\t\t" << "Convert::ToDouble(" << valName << ");" << std::endl + << "\t" << "} catch (const std::invalid_argument&) {" << std::endl + << "\t\t" << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_cast<ConfigObject *>(this), { \"" << field.Name << R"(", "Array element '" + " << valName << " + "' of type '" + " << valName << ".GetReflectionType()->GetName() + "' is not valid here; expected type ')" << ftype << "'.\"));" << std::endl + << "\t" << "}" << std::endl; + } + + if (field.Type.ArrayRank > 0) { + m_Impl << "\t\t" << "}" << std::endl + << "\t" << "}" << std::endl; + } + + m_Impl << "}" << std::endl << std::endl; + } + + /* constructor */ + m_Header << "public:" << std::endl + << "\t" << "ObjectImpl<" << klass.Name << ">();" << std::endl; + + m_Impl << "ObjectImpl<" << klass.Name << ">::ObjectImpl()" << std::endl + << "{" << std::endl; + + for (const Field& field : klass.Fields) { + if (!field.PureSetAccessor) + m_Impl << "\t" << "Set" << field.GetFriendlyName() << "(" << "GetDefault" << field.GetFriendlyName() << "(), true);" << std::endl; + } + + m_Impl << "}" << std::endl << std::endl; + + /* destructor */ + m_Header << "public:" << std::endl + << "\t" << "~ObjectImpl<" << klass.Name << ">() override;" << std::endl; + + m_Impl << "ObjectImpl<" << klass.Name << ">::~ObjectImpl()" << std::endl + << "{ }" << std::endl << std::endl; + + if (!klass.Fields.empty()) { + /* SetField */ + m_Header << "public:" << std::endl + << "\t" << "void SetField(int id, const Value& value, bool suppress_events = false, const Value& cookie = Empty) override;" << std::endl; + + m_Impl << "void ObjectImpl<" << klass.Name << ">::SetField(int id, const Value& value, bool suppress_events, const Value& cookie)" << std::endl + << "{" << std::endl; + + if (!klass.Parent.empty()) + m_Impl << "\t" << "int real_id = id - " << klass.Parent << "::TypeInstance->GetFieldCount(); " << std::endl + << "\t" << "if (real_id < 0) { " << klass.Parent << "::SetField(id, value, suppress_events, cookie); return; }" << std::endl; + + m_Impl << "\t" << "switch ("; + + if (!klass.Parent.empty()) + m_Impl << "real_id"; + else + m_Impl << "id"; + + m_Impl << ") {" << std::endl; + + size_t num = 0; + for (const Field& field : klass.Fields) { + m_Impl << "\t\t" << "case " << num << ":" << std::endl + << "\t\t\t" << "Set" << field.GetFriendlyName() << "("; + + if (field.Attributes & FAEnum) + m_Impl << "static_cast<" << field.Type.GetRealType() << ">(static_cast<int>("; + + m_Impl << "value"; + + if (field.Attributes & FAEnum) + m_Impl << "))"; + + m_Impl << ", suppress_events, cookie);" << std::endl + << "\t\t\t" << "break;" << std::endl; + num++; + } + + m_Impl << "\t\t" << "default:" << std::endl + << "\t\t\t" << "throw std::runtime_error(\"Invalid field ID.\");" << std::endl + << "\t" << "}" << std::endl; + + m_Impl << "}" << std::endl << std::endl; + + /* GetField */ + m_Header << "public:" << std::endl + << "\t" << "Value GetField(int id) const override;" << std::endl; + + m_Impl << "Value ObjectImpl<" << klass.Name << ">::GetField(int id) const" << std::endl + << "{" << std::endl; + + if (!klass.Parent.empty()) + m_Impl << "\t" << "int real_id = id - " << klass.Parent << "::TypeInstance->GetFieldCount(); " << std::endl + << "\t" << "if (real_id < 0) { return " << klass.Parent << "::GetField(id); }" << std::endl; + + m_Impl << "\t" << "switch ("; + + if (!klass.Parent.empty()) + m_Impl << "real_id"; + else + m_Impl << "id"; + + m_Impl << ") {" << std::endl; + + num = 0; + for (const Field& field : klass.Fields) { + m_Impl << "\t\t" << "case " << num << ":" << std::endl + << "\t\t\t" << "return Get" << field.GetFriendlyName() << "();" << std::endl; + num++; + } + + m_Impl << "\t\t" << "default:" << std::endl + << "\t\t\t" << "throw std::runtime_error(\"Invalid field ID.\");" << std::endl + << "\t" << "}" << std::endl; + + m_Impl << "}" << std::endl << std::endl; + + /* ValidateField */ + m_Header << "public:" << std::endl + << "\t" << "void ValidateField(int id, const Lazy<Value>& lvalue, const ValidationUtils& utils) override;" << std::endl; + + m_Impl << "void ObjectImpl<" << klass.Name << ">::ValidateField(int id, const Lazy<Value>& lvalue, const ValidationUtils& utils)" << std::endl + << "{" << std::endl; + + if (!klass.Parent.empty()) + m_Impl << "\t" << "int real_id = id - " << klass.Parent << "::TypeInstance->GetFieldCount(); " << std::endl + << "\t" << "if (real_id < 0) { " << klass.Parent << "::ValidateField(id, lvalue, utils); return; }" << std::endl; + + m_Impl << "\t" << "switch ("; + + if (!klass.Parent.empty()) + m_Impl << "real_id"; + else + m_Impl << "id"; + + m_Impl << ") {" << std::endl; + + num = 0; + for (const Field& field : klass.Fields) { + m_Impl << "\t\t" << "case " << num << ":" << std::endl + << "\t\t\t" << "Validate" << field.GetFriendlyName() << "("; + + if (field.Attributes & FAEnum) + m_Impl << "static_cast<Lazy<" << field.Type.GetRealType() << "> >(static_cast<Lazy<int> >("; + + m_Impl << "lvalue"; + + if (field.Attributes & FAEnum) + m_Impl << "))"; + + m_Impl << ", utils);" << std::endl + << "\t\t\t" << "break;" << std::endl; + num++; + } + + m_Impl << "\t\t" << "default:" << std::endl + << "\t\t\t" << "throw std::runtime_error(\"Invalid field ID.\");" << std::endl + << "\t" << "}" << std::endl; + + m_Impl << "}" << std::endl << std::endl; + + /* NotifyField */ + m_Header << "public:" << std::endl + << "\t" << "void NotifyField(int id, const Value& cookie = Empty) override;" << std::endl; + + m_Impl << "void ObjectImpl<" << klass.Name << ">::NotifyField(int id, const Value& cookie)" << std::endl + << "{" << std::endl; + + if (!klass.Parent.empty()) + m_Impl << "\t" << "int real_id = id - " << klass.Parent << "::TypeInstance->GetFieldCount(); " << std::endl + << "\t" << "if (real_id < 0) { " << klass.Parent << "::NotifyField(id, cookie); return; }" << std::endl; + + m_Impl << "\t" << "switch ("; + + if (!klass.Parent.empty()) + m_Impl << "real_id"; + else + m_Impl << "id"; + + m_Impl << ") {" << std::endl; + + num = 0; + for (const Field& field : klass.Fields) { + m_Impl << "\t\t" << "case " << num << ":" << std::endl + << "\t\t\t" << "Notify" << field.GetFriendlyName() << "(cookie);" << std::endl + << "\t\t\t" << "break;" << std::endl; + num++; + } + + m_Impl << "\t\t" << "default:" << std::endl + << "\t\t\t" << "throw std::runtime_error(\"Invalid field ID.\");" << std::endl + << "\t" << "}" << std::endl; + + m_Impl << "}" << std::endl << std::endl; + + /* NavigateField */ + m_Header << "public:" << std::endl + << "\t" << "Object::Ptr NavigateField(int id) const override;" << std::endl; + + m_Impl << "Object::Ptr ObjectImpl<" << klass.Name << ">::NavigateField(int id) const" << std::endl + << "{" << std::endl; + + if (!klass.Parent.empty()) + m_Impl << "\t" << "int real_id = id - " << klass.Parent << "::TypeInstance->GetFieldCount(); " << std::endl + << "\t" << "if (real_id < 0) { return " << klass.Parent << "::NavigateField(id); }" << std::endl; + + bool haveNavigationFields = false; + + for (const Field& field : klass.Fields) { + if (field.Attributes & FANavigation) { + haveNavigationFields = true; + break; + } + } + + if (haveNavigationFields) { + m_Impl << "\t" << "switch ("; + + if (!klass.Parent.empty()) + m_Impl << "real_id"; + else + m_Impl << "id"; + + m_Impl << ") {" << std::endl; + + num = 0; + for (const Field& field : klass.Fields) { + if (field.Attributes & FANavigation) { + m_Impl << "\t\t" << "case " << num << ":" << std::endl + << "\t\t\t" << "return Navigate" << field.GetFriendlyName() << "();" << std::endl; + } + + num++; + } + + m_Impl << "\t\t" << "default:" << std::endl + << "\t\t"; + } + + m_Impl << "\t" << "throw std::runtime_error(\"Invalid field ID.\");" << std::endl; + + if (haveNavigationFields) + m_Impl << "\t" << "}" << std::endl; + + m_Impl << "}" << std::endl << std::endl; + + /* getters */ + for (const Field& field : klass.Fields) { + std::string prot; + + if (field.Attributes & FAGetProtected) + prot = "protected"; + else + prot = "public"; + + m_Header << prot << ":" << std::endl + << "\t"; + + if (field.Attributes & FAGetVirtual || field.PureGetAccessor) + m_Header << "virtual "; + + m_Header << field.Type.GetRealType() << " Get" << field.GetFriendlyName() << "() const"; + + if (field.PureGetAccessor) { + m_Header << " = 0;" << std::endl; + } else { + m_Header << ";" << std::endl; + + m_Impl << field.Type.GetRealType() << " ObjectImpl<" << klass.Name << ">::Get" << field.GetFriendlyName() << "() const" << std::endl + << "{" << std::endl; + + if (field.GetAccessor.empty() && !(field.Attributes & FANoStorage)) + m_Impl << "\t" << "return m_" << field.GetFriendlyName() << ".load();" << std::endl; + else + m_Impl << field.GetAccessor << std::endl; + + m_Impl << "}" << std::endl << std::endl; + } + } + + /* setters */ + for (const Field& field : klass.Fields) { + std::string prot; + + if (field.Attributes & FASetProtected) + prot = "protected"; + else + prot = "public"; + + m_Header << prot << ":" << std::endl + << "\t"; + + if (field.Attributes & FASetVirtual || field.PureSetAccessor) + m_Header << "virtual "; + + m_Header << "void Set" << field.GetFriendlyName() << "(" << field.Type.GetArgumentType() << " value, bool suppress_events = false, const Value& cookie = Empty)"; + + if (field.PureSetAccessor) { + m_Header << " = 0;" << std::endl; + } else { + m_Header << ";" << std::endl; + + m_Impl << "void ObjectImpl<" << klass.Name << ">::Set" << field.GetFriendlyName() << "(" << field.Type.GetArgumentType() << " value, bool suppress_events, const Value& cookie)" << std::endl + << "{" << std::endl; + + if (field.Type.IsName || !field.TrackAccessor.empty() || field.Attributes & FASignalWithOldValue) + m_Impl << "\t" << "Value oldValue = Get" << field.GetFriendlyName() << "();" << std::endl + << "\t" << "auto *dobj = dynamic_cast<ConfigObject *>(this);" << std::endl; + + if (field.SetAccessor.empty() && !(field.Attributes & FANoStorage)) + m_Impl << "\t" << "m_" << field.GetFriendlyName() << ".store(value);" << std::endl; + else + m_Impl << field.SetAccessor << std::endl << std::endl; + + if (field.Type.IsName || !field.TrackAccessor.empty()) { + if (field.Name != "active") { + m_Impl << "\t" << "if (!dobj || dobj->IsActive())" << std::endl + << "\t"; + } + + m_Impl << "\t" << "Track" << field.GetFriendlyName() << "(oldValue, value);" << std::endl; + } + + m_Impl << "\t" << "if (!suppress_events) {" << std::endl + << "\t\t" << "Notify" << field.GetFriendlyName() << "(cookie);" << std::endl; + + if (field.Attributes & FASignalWithOldValue) { + m_Impl << "\t\t" << "if (!dobj || dobj->IsActive())" << std::endl + << "\t\t\t" << "On" << field.GetFriendlyName() << "ChangedWithOldValue(static_cast<" << klass.Name << " *>(this), oldValue, value);" << std::endl; + } + + m_Impl << "\t" "}" << std::endl << std::endl + << "}" << std::endl << std::endl; + } + } + + m_Header << "protected:" << std::endl; + + bool needs_tracking = false; + + /* tracking */ + for (const Field& field : klass.Fields) { + if (!field.Type.IsName && field.TrackAccessor.empty()) + continue; + + needs_tracking = true; + + m_Header << "\t" << "void Track" << field.GetFriendlyName() << "(" << field.Type.GetArgumentType() << " oldValue, " << field.Type.GetArgumentType() << " newValue);" << std::endl; + + m_Impl << "void ObjectImpl<" << klass.Name << ">::Track" << field.GetFriendlyName() << "(" << field.Type.GetArgumentType() << " oldValue, " << field.Type.GetArgumentType() << " newValue)" << std::endl + << "{" << std::endl; + + if (!field.TrackAccessor.empty()) + m_Impl << "\t" << field.TrackAccessor << std::endl; + + if (field.Type.TypeName != "String") { + if (field.Type.ArrayRank > 0) { + m_Impl << "\t" << "if (oldValue) {" << std::endl + << "\t\t" << "ObjectLock olock(oldValue);" << std::endl + << "\t\t" << "for (const String& ref : oldValue) {" << std::endl + << "\t\t\t" << "DependencyGraph::RemoveDependency(this, ConfigObject::GetObject"; + + /* Ew */ + if (field.Type.TypeName == "Zone" && m_Library == "base") + m_Impl << "(\"Zone\", "; + else + m_Impl << "<" << field.Type.TypeName << ">("; + + m_Impl << "ref).get());" << std::endl + << "\t\t" << "}" << std::endl + << "\t" << "}" << std::endl + << "\t" << "if (newValue) {" << std::endl + << "\t\t" << "ObjectLock olock(newValue);" << std::endl + << "\t\t" << "for (const String& ref : newValue) {" << std::endl + << "\t\t\t" << "DependencyGraph::AddDependency(this, ConfigObject::GetObject"; + + /* Ew */ + if (field.Type.TypeName == "Zone" && m_Library == "base") + m_Impl << "(\"Zone\", "; + else + m_Impl << "<" << field.Type.TypeName << ">("; + + m_Impl << "ref).get());" << std::endl + << "\t\t" << "}" << std::endl + << "\t" << "}" << std::endl; + } else { + m_Impl << "\t" << "if (!oldValue.IsEmpty())" << std::endl + << "\t\t" << "DependencyGraph::RemoveDependency(this, ConfigObject::GetObject"; + + /* Ew */ + if (field.Type.TypeName == "Zone" && m_Library == "base") + m_Impl << "(\"Zone\", "; + else + m_Impl << "<" << field.Type.TypeName << ">("; + + m_Impl << "oldValue).get());" << std::endl + << "\t" << "if (!newValue.IsEmpty())" << std::endl + << "\t\t" << "DependencyGraph::AddDependency(this, ConfigObject::GetObject"; + + /* Ew */ + if (field.Type.TypeName == "Zone" && m_Library == "base") + m_Impl << "(\"Zone\", "; + else + m_Impl << "<" << field.Type.TypeName << ">("; + + m_Impl << "newValue).get());" << std::endl; + } + } + + m_Impl << "}" << std::endl << std::endl; + } + + /* navigation */ + for (const Field& field : klass.Fields) { + if ((field.Attributes & FANavigation) == 0) + continue; + + m_Header << "public:" << std::endl + << "\t" << "Object::Ptr Navigate" << field.GetFriendlyName() << "() const"; + + if (field.PureNavigateAccessor) { + m_Header << " = 0;" << std::endl; + } else { + m_Header << ";" << std::endl; + + m_Impl << "Object::Ptr ObjectImpl<" << klass.Name << ">::Navigate" << field.GetFriendlyName() << "() const" << std::endl + << "{" << std::endl; + + if (field.NavigateAccessor.empty()) + m_Impl << "\t" << "return Get" << field.GetFriendlyName() << "();" << std::endl; + else + m_Impl << "\t" << field.NavigateAccessor << std::endl; + + m_Impl << "}" << std::endl << std::endl; + } + } + + /* start/stop */ + if (needs_tracking) { + m_Header << "protected:" << std::endl + << "\tvoid Start(bool runtimeCreated = false) override;" << std::endl + << "\tvoid Stop(bool runtimeRemoved = false) override;" << std::endl; + + m_Impl << "void ObjectImpl<" << klass.Name << ">::Start(bool runtimeCreated)" << std::endl + << "{" << std::endl + << "\t" << klass.Parent << "::Start(runtimeCreated);" << std::endl << std::endl; + + for (const Field& field : klass.Fields) { + if (!field.Type.IsName && field.TrackAccessor.empty()) + continue; + + m_Impl << "\t" << "Track" << field.GetFriendlyName() << "(Empty, Get" << field.GetFriendlyName() << "());" << std::endl; + } + + m_Impl << "}" << std::endl << std::endl + << "void ObjectImpl<" << klass.Name << ">::Stop(bool runtimeRemoved)" << std::endl + << "{" << std::endl + << "\t" << klass.Parent << "::Stop(runtimeRemoved);" << std::endl << std::endl; + + for (const Field& field : klass.Fields) { + if (!field.Type.IsName && field.TrackAccessor.empty()) + continue; + + m_Impl << "\t" << "Track" << field.GetFriendlyName() << "(Get" << field.GetFriendlyName() << "(), Empty);" << std::endl; + } + + m_Impl << "}" << std::endl << std::endl; + } + + /* notify */ + for (const Field& field : klass.Fields) { + std::string prot; + + if (field.Attributes & FASetProtected) + prot = "protected"; + else + prot = "public"; + + m_Header << prot << ":" << std::endl + << "\t" << "virtual void Notify" << field.GetFriendlyName() << "(const Value& cookie = Empty);" << std::endl; + + m_Impl << "void ObjectImpl<" << klass.Name << ">::Notify" << field.GetFriendlyName() << "(const Value& cookie)" << std::endl + << "{" << std::endl; + + if (field.Name != "active") { + m_Impl << "\t" << "auto *dobj = dynamic_cast<ConfigObject *>(this);" << std::endl + << "\t" << "if (!dobj || dobj->IsActive())" << std::endl + << "\t"; + } + + m_Impl << "\t" << "On" << field.GetFriendlyName() << "Changed(static_cast<" << klass.Name << " *>(this), cookie);" << std::endl + << "}" << std::endl << std::endl; + } + + /* default */ + for (const Field& field : klass.Fields) { + std::string realType = field.Type.GetRealType(); + + m_Header << "private:" << std::endl + << "\t" << "inline " << realType << " GetDefault" << field.GetFriendlyName() << "() const;" << std::endl; + + m_Impl << realType << " ObjectImpl<" << klass.Name << ">::GetDefault" << field.GetFriendlyName() << "() const" << std::endl + << "{" << std::endl; + + if (field.DefaultAccessor.empty()) + m_Impl << "\t" << "return " << realType << "();" << std::endl; + else + m_Impl << "\t" << field.DefaultAccessor << std::endl; + + m_Impl << "}" << std::endl << std::endl; + } + + /* validators */ + for (const Field& field : klass.Fields) { + m_Header << "protected:" << std::endl + << "\t" << "virtual void Validate" << field.GetFriendlyName() << "(const Lazy<" << field.Type.GetRealType() << ">& lvalue, const ValidationUtils& utils);" << std::endl; + } + + /* instance variables */ + m_Header << "private:" << std::endl; + + for (const Field& field : klass.Fields) { + if (field.Attributes & FANoStorage) + continue; + + m_Header << "\tAtomicOrLocked<" << field.Type.GetRealType() << "> m_" << field.GetFriendlyName() << ";" << std::endl; + } + + /* signal */ + m_Header << "public:" << std::endl; + + for (const Field& field : klass.Fields) { + m_Header << "\t" << "static boost::signals2::signal<void (const intrusive_ptr<" << klass.Name << ">&, const Value&)> On" << field.GetFriendlyName() << "Changed;" << std::endl; + m_Impl << std::endl << "boost::signals2::signal<void (const intrusive_ptr<" << klass.Name << ">&, const Value&)> ObjectImpl<" << klass.Name << ">::On" << field.GetFriendlyName() << "Changed;" << std::endl << std::endl; + + if (field.Attributes & FASignalWithOldValue) { + m_Header << "\t" << "static boost::signals2::signal<void (const intrusive_ptr<" << klass.Name + << ">&, const Value&, const Value&)> On" << field.GetFriendlyName() << "ChangedWithOldValue;" + << std::endl; + m_Impl << std::endl << "boost::signals2::signal<void (const intrusive_ptr<" << klass.Name + << ">&, const Value&, const Value&)> ObjectImpl<" << klass.Name << ">::On" + << field.GetFriendlyName() << "ChangedWithOldValue;" << std::endl << std::endl; + } + } + } + + if (klass.Name == "ConfigObject") + m_Header << "\t" << "friend class ConfigItem;" << std::endl; + + if (!klass.TypeBase.empty()) + m_Header << "\t" << "friend class " << klass.TypeBase << ";" << std::endl; + + m_Header << "};" << std::endl << std::endl; + + for (const Field& field : klass.Fields) { + m_MissingValidators[std::make_pair(klass.Name, field.GetFriendlyName())] = field; + } +} + +void ClassCompiler::CodeGenValidator(const std::string& name, const std::string& klass, const std::vector<Rule>& rules, const std::string& field, const FieldType& fieldType, ValidatorType validatorType) +{ + m_Impl << "static void TIValidate" << name << "(const intrusive_ptr<ObjectImpl<" << klass << "> >& object, "; + + if (validatorType != ValidatorField) + m_Impl << "const String& key, "; + + m_Impl << fieldType.GetArgumentType() << " value, std::vector<String>& location, const ValidationUtils& utils)" << std::endl + << "{" << std::endl; + + if (validatorType == ValidatorField) { + bool required = false; + + for (const Rule& rule : rules) { + if ((rule.Attributes & RARequired) && rule.Pattern == field) { + required = true; + break; + } + } + + if (fieldType.GetRealType() != "int" && fieldType.GetRealType() != "double") { + if (fieldType.GetRealType() == "Value" || fieldType.GetRealType() == "String") + m_Impl << "\t" << "if (value.IsEmpty())" << std::endl; + else + m_Impl << "\t" << "if (!value)" << std::endl; + + if (required) + m_Impl << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_cast<ConfigObject *>(this), location, \"This attribute must not be empty.\"));" << std::endl; + else + m_Impl << "\t\t" << "return;" << std::endl; + + m_Impl << std::endl; + } + } + + if (validatorType != ValidatorField) + m_Impl << "\t" << "bool known_attribute = false;" << std::endl; + + bool type_check = false; + int i = 0; + + for (const Rule& rule : rules) { + if (rule.Attributes & RARequired) + continue; + + i++; + + if (validatorType == ValidatorField && rule.Pattern != field) + continue; + + m_Impl << "\t" << "do {" << std::endl; + + if (validatorType != ValidatorField) { + if (rule.Pattern != "*") { + if (rule.Pattern.find_first_of("*?") != std::string::npos) + m_Impl << "\t\t" << "if (!Utility::Match(\"" << rule.Pattern << "\", key))" << std::endl; + else + m_Impl << "\t\t" << "if (key != \"" << rule.Pattern << "\")" << std::endl; + + m_Impl << "\t\t\t" << "break;" << std::endl; + } + + m_Impl << "\t\t" << "known_attribute = true;" << std::endl; + } + + if (rule.IsName) { + m_Impl << "\t\t" << "if (value.IsScalar()) {" << std::endl + << "\t\t\t" << "if (utils.ValidateName(\"" << rule.Type << "\", value))" << std::endl + << "\t\t\t\t" << "return;" << std::endl + << "\t\t\t" << "else" << std::endl + << "\t\t\t\t" << R"(BOOST_THROW_EXCEPTION(ValidationError(dynamic_pointer_cast<ConfigObject>(object), location, "Object '" + ")" << "xxx" << R"( + "' of type ')" << rule.Type << "' does not exist.\"));" << std::endl + << "\t\t" << "}" << std::endl; + } + + if (fieldType.GetRealType() == "Value") { + if (rule.Type == "String") + m_Impl << "\t\t" << "if (value.IsEmpty() || value.IsScalar())" << std::endl + << "\t\t\t" << "return;" << std::endl; + else if (rule.Type == "Number") { + m_Impl << "\t\t" << "try {" << std::endl + << "\t\t\t" << "Convert::ToDouble(value);" << std::endl + << "\t\t\t" << "return;" << std::endl + << "\t\t" << "} catch (...) { }" << std::endl; + } + } + + if (rule.Type == "Dictionary" || rule.Type == "Array" || rule.Type == "Function") { + if (fieldType.GetRealType() == "Value") { + m_Impl << "\t\t" << "if (value.IsObjectType<" << rule.Type << ">()) {" << std::endl; + type_check = true; + } else if (fieldType.GetRealType() != rule.Type + "::Ptr") { + m_Impl << "\t\t" << "if (dynamic_pointer_cast<" << rule.Type << ">(value)) {" << std::endl; + type_check = true; + } + + if (!rule.Rules.empty()) { + bool indent = false; + + if (rule.Type == "Dictionary") { + if (type_check) + m_Impl << "\t\t\t" << "Dictionary::Ptr dict = value;" << std::endl; + else + m_Impl << "\t\t" << "const Dictionary::Ptr& dict = value;" << std::endl; + + m_Impl << (type_check ? "\t" : "") << "\t\t" << "{" << std::endl + << (type_check ? "\t" : "") << "\t\t\t" << "ObjectLock olock(dict);" << std::endl + << (type_check ? "\t" : "") << "\t\t\t" << "for (const Dictionary::Pair& kv : dict) {" << std::endl + << (type_check ? "\t" : "") << "\t\t\t\t" << "const String& akey = kv.first;" << std::endl + << (type_check ? "\t" : "") << "\t\t\t\t" << "const Value& avalue = kv.second;" << std::endl; + indent = true; + } else if (rule.Type == "Array") { + if (type_check) + m_Impl << "\t\t\t" << "Array::Ptr arr = value;" << std::endl; + else + m_Impl << "\t\t" << "const Array::Ptr& arr = value;" << std::endl; + + m_Impl << (type_check ? "\t" : "") << "\t\t" << "Array::SizeType anum = 0;" << std::endl + << (type_check ? "\t" : "") << "\t\t" << "{" << std::endl + << (type_check ? "\t" : "") << "\t\t\t" << "ObjectLock olock(arr);" << std::endl + << (type_check ? "\t" : "") << "\t\t\t" << "for (const Value& avalue : arr) {" << std::endl + << (type_check ? "\t" : "") << "\t\t\t\t" << "String akey = Convert::ToString(anum);" << std::endl; + indent = true; + } else { + m_Impl << (type_check ? "\t" : "") << "\t\t" << "String akey = \"\";" << std::endl + << (type_check ? "\t" : "") << "\t\t" << "const Value& avalue = value;" << std::endl; + } + + std::string subvalidator_prefix; + + if (validatorType == ValidatorField) + subvalidator_prefix = klass; + else + subvalidator_prefix = name; + + m_Impl << (type_check ? "\t" : "") << (indent ? "\t\t" : "") << "\t\t" << "location.emplace_back(akey);" << std::endl + << (type_check ? "\t" : "") << (indent ? "\t\t" : "") << "\t\t" << "TIValidate" << subvalidator_prefix << "_" << i << "(object, akey, avalue, location, utils);" << std::endl + << (type_check ? "\t" : "") << (indent ? "\t\t" : "") << "\t\t" << "location.pop_back();" << std::endl; + + if (rule.Type == "Array") + m_Impl << (type_check ? "\t" : "") << "\t\t\t\t" << "anum++;" << std::endl; + + if (rule.Type == "Dictionary" || rule.Type == "Array") { + m_Impl << (type_check ? "\t" : "") << "\t\t\t" << "}" << std::endl + << (type_check ? "\t" : "") << "\t\t" << "}" << std::endl; + } + + for (const Rule& srule : rule.Rules) { + if ((srule.Attributes & RARequired) == 0) + continue; + + if (rule.Type == "Dictionary") { + m_Impl << (type_check ? "\t" : "") << "\t\t" << "if (dict->Get(\"" << srule.Pattern << "\").IsEmpty())" << std::endl + << (type_check ? "\t" : "") << "\t\t\t" << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_pointer_cast<ConfigObject>(object), location, \"Required dictionary item '" << srule.Pattern << "' is not set.\"));" << std::endl; + } else if (rule.Type == "Array") { + int index = -1; + std::stringstream idxbuf; + idxbuf << srule.Pattern; + idxbuf >> index; + + if (index == -1) { + std::cerr << "Invalid index for 'required' keyword: " << srule.Pattern; + std::exit(1); + } + + m_Impl << (type_check ? "\t" : "") << "\t\t" << "if (arr.GetLength() < " << (index + 1) << ")" << std::endl + << (type_check ? "\t" : "") << "\t\t\t" << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_cast<ConfigObject *>(this), location, \"Required index '" << index << "' is not set.\"));" << std::endl; + } + } + } + + m_Impl << (type_check ? "\t" : "") << "\t\t" << "return;" << std::endl; + + if (fieldType.GetRealType() == "Value" || fieldType.GetRealType() != rule.Type + "::Ptr") + m_Impl << "\t\t" << "}" << std::endl; + } + + m_Impl << "\t" << "} while (0);" << std::endl << std::endl; + } + + if (type_check || validatorType != ValidatorField) { + if (validatorType != ValidatorField) { + m_Impl << "\t" << "if (!known_attribute)" << std::endl + << "\t\t" << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_pointer_cast<ConfigObject>(object), location, \"Invalid attribute: \" + key));" << std::endl + << "\t" << "else" << std::endl; + } + + m_Impl << (validatorType != ValidatorField ? "\t" : "") << "\t" << "BOOST_THROW_EXCEPTION(ValidationError(dynamic_pointer_cast<ConfigObject>(object), location, \"Invalid type.\"));" << std::endl; + } + + m_Impl << "}" << std::endl << std::endl; +} + +void ClassCompiler::CodeGenValidatorSubrules(const std::string& name, const std::string& klass, const std::vector<Rule>& rules) +{ + int i = 0; + + for (const Rule& rule : rules) { + if (rule.Attributes & RARequired) + continue; + + i++; + + if (!rule.Rules.empty()) { + ValidatorType subtype; + + if (rule.Type == "Array") + subtype = ValidatorArray; + else if (rule.Type == "Dictionary") + subtype = ValidatorDictionary; + else { + std::cerr << "Invalid sub-validator type: " << rule.Type << std::endl; + std::exit(EXIT_FAILURE); + } + + std::ostringstream namebuf; + namebuf << name << "_" << i; + + CodeGenValidatorSubrules(namebuf.str(), klass, rule.Rules); + + FieldType ftype; + ftype.IsName = false; + ftype.TypeName = "Value"; + CodeGenValidator(namebuf.str(), klass, rule.Rules, rule.Pattern, ftype, subtype); + } + } +} + +void ClassCompiler::HandleValidator(const Validator& validator, const ClassDebugInfo&) +{ + CodeGenValidatorSubrules(validator.Name, validator.Name, validator.Rules); + + for (const auto& it : m_MissingValidators) + CodeGenValidator(it.first.first + it.first.second, it.first.first, validator.Rules, it.second.Name, it.second.Type, ValidatorField); + + for (const auto& it : m_MissingValidators) { + m_Impl << "void ObjectImpl<" << it.first.first << ">::Validate" << it.first.second << "(const Lazy<" << it.second.Type.GetRealType() << ">& lvalue, const ValidationUtils& utils)" << std::endl + << "{" << std::endl + << "\t" << "SimpleValidate" << it.first.second << "(lvalue, utils);" << std::endl + << "\t" << "std::vector<String> location;" << std::endl + << "\t" << "location.emplace_back(\"" << it.second.Name << "\");" << std::endl + << "\t" << "TIValidate" << it.first.first << it.first.second << "(this, lvalue(), location, utils);" << std::endl + << "\t" << "location.pop_back();" << std::endl + << "}" << std::endl << std::endl; + } + + m_MissingValidators.clear(); +} + +void ClassCompiler::HandleMissingValidators() +{ + for (const auto& it : m_MissingValidators) { + m_Impl << "void ObjectImpl<" << it.first.first << ">::Validate" << it.first.second << "(const Lazy<" << it.second.Type.GetRealType() << ">& lvalue, const ValidationUtils& utils)" << std::endl + << "{" << std::endl + << "\t" << "SimpleValidate" << it.first.second << "(lvalue, utils);" << std::endl + << "}" << std::endl << std::endl; + } + + m_MissingValidators.clear(); +} + +void ClassCompiler::CompileFile(const std::string& inputpath, + const std::string& implpath, const std::string& headerpath) +{ + std::ifstream input; + input.open(inputpath.c_str(), std::ifstream::in); + + if (!input) { + std::cerr << "Could not open input file: " << inputpath << std::endl; + std::exit(EXIT_FAILURE); + } + + std::string tmpheaderpath = headerpath + ".tmp"; + std::ofstream oheader; + oheader.open(tmpheaderpath.c_str(), std::ofstream::out); + + if (!oheader) { + std::cerr << "Could not open header file: " << tmpheaderpath << std::endl; + std::exit(EXIT_FAILURE); + } + + std::string tmpimplpath = implpath + ".tmp"; + std::ofstream oimpl; + oimpl.open(tmpimplpath.c_str(), std::ofstream::out); + + if (!oimpl) { + std::cerr << "Could not open implementation file: " << tmpimplpath << std::endl; + std::exit(EXIT_FAILURE); + } + + CompileStream(inputpath, input, oimpl, oheader); + + input.close(); + oimpl.close(); + oheader.close(); + +#ifdef _WIN32 + _unlink(headerpath.c_str()); +#endif /* _WIN32 */ + + if (rename(tmpheaderpath.c_str(), headerpath.c_str()) < 0) { + std::cerr << "Could not rename header file: " << tmpheaderpath << " -> " << headerpath << std::endl; + std::exit(EXIT_FAILURE); + } + +#ifdef _WIN32 + _unlink(implpath.c_str()); +#endif /* _WIN32 */ + + if (rename(tmpimplpath.c_str(), implpath.c_str()) < 0) { + std::cerr << "Could not rename implementation file: " << tmpimplpath << " -> " << implpath << std::endl; + std::exit(EXIT_FAILURE); + } +} + +std::string ClassCompiler::BaseName(const std::string& path) +{ + char *dir = strdup(path.c_str()); + std::string result; + + if (!dir) + throw std::bad_alloc(); + +#ifndef _WIN32 + result = basename(dir); +#else /* _WIN32 */ + result = PathFindFileName(dir); +#endif /* _WIN32 */ + + free(dir); + + return result; +} + +std::string ClassCompiler::FileNameToGuardName(const std::string& fname) +{ + std::string result = fname; + std::locale locale; + + for (auto& ch : result) { + ch = std::toupper(ch, locale); + + if (ch == '.') + ch = '_'; + } + + return result; +} + +void ClassCompiler::CompileStream(const std::string& path, std::istream& input, + std::ostream& oimpl, std::ostream& oheader) +{ + input.exceptions(std::istream::badbit); + + std::string guard_name = FileNameToGuardName(BaseName(path)); + + oheader << "#ifndef " << guard_name << std::endl + << "#define " << guard_name << std::endl << std::endl; + + oheader << "#include \"base/object.hpp\"" << std::endl + << "#include \"base/type.hpp\"" << std::endl + << "#include \"base/value.hpp\"" << std::endl + << "#include \"base/array.hpp\"" << std::endl + << "#include \"base/atomic.hpp\"" << std::endl + << "#include \"base/dictionary.hpp\"" << std::endl + << "#include <boost/signals2.hpp>" << std::endl << std::endl; + + oimpl << "#include \"base/exception.hpp\"" << std::endl + << "#include \"base/objectlock.hpp\"" << std::endl + << "#include \"base/utility.hpp\"" << std::endl + << "#include \"base/convert.hpp\"" << std::endl + << "#include \"base/dependencygraph.hpp\"" << std::endl + << "#include \"base/logger.hpp\"" << std::endl + << "#include \"base/function.hpp\"" << std::endl + << "#include \"base/configtype.hpp\"" << std::endl + << "#ifdef _MSC_VER" << std::endl + << "#pragma warning( push )" << std::endl + << "#pragma warning( disable : 4244 )" << std::endl + << "#pragma warning( disable : 4800 )" << std::endl + << "#endif /* _MSC_VER */" << std::endl << std::endl; + + + ClassCompiler ctx(path, input, oimpl, oheader); + ctx.Compile(); + + oheader << "#endif /* " << guard_name << " */" << std::endl; + + oimpl << "#ifdef _MSC_VER" << std::endl + << "#pragma warning ( pop )" << std::endl + << "#endif /* _MSC_VER */" << std::endl; +} diff --git a/tools/mkclass/classcompiler.hpp b/tools/mkclass/classcompiler.hpp new file mode 100644 index 0000000..0bd789d --- /dev/null +++ b/tools/mkclass/classcompiler.hpp @@ -0,0 +1,245 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef CLASSCOMPILER_H +#define CLASSCOMPILER_H + +#include <string> +#include <istream> +#include <utility> +#include <vector> +#include <algorithm> +#include <map> + +namespace icinga +{ + +struct ClassDebugInfo +{ + std::string path; + int first_line; + int first_column; + int last_line; + int last_column; +}; + +enum FieldAccessorType +{ + FTGet, + FTSet, + FTDefault, + FTTrack, + FTNavigate +}; + +struct FieldAccessor +{ + FieldAccessorType Type; + std::string Accessor; + bool Pure; + + FieldAccessor(FieldAccessorType type, std::string accessor, bool pure) + : Type(type), Accessor(std::move(accessor)), Pure(pure) + { } +}; + +/* keep this in sync with lib/base/type.hpp */ +enum FieldAttribute +{ + FAEphemeral = 1, + FAConfig = 2, + FAState = 4, + FAEnum = 8, + FAGetProtected = 16, + FASetProtected = 32, + FANoStorage = 64, + FALoadDependency = 128, + FARequired = 256, + FANavigation = 512, + FANoUserModify = 1024, + FANoUserView = 2048, + FADeprecated = 4096, + FAGetVirtual = 8192, + FASetVirtual = 16384, + FAActivationPriority = 32768, + FASignalWithOldValue = 65536, +}; + +struct FieldType +{ + bool IsName{false}; + std::string TypeName; + int ArrayRank{0}; + + inline std::string GetRealType() const + { + if (ArrayRank > 0) + return "Array::Ptr"; + + if (IsName) + return "String"; + + return TypeName; + } + + inline std::string GetArgumentType() const + { + std::string realType = GetRealType(); + + if (realType == "bool" || realType == "double" || realType == "int") + return realType; + else + return "const " + realType + "&"; + } +}; + +struct Field +{ + int Attributes{0}; + FieldType Type; + std::string Name; + std::string AlternativeName; + std::string GetAccessor; + bool PureGetAccessor{false}; + std::string SetAccessor; + bool PureSetAccessor{false}; + std::string DefaultAccessor; + std::string TrackAccessor; + std::string NavigationName; + std::string NavigateAccessor; + bool PureNavigateAccessor{false}; + int Priority{0}; + + inline std::string GetFriendlyName() const + { + if (!AlternativeName.empty()) + return AlternativeName; + + bool cap = true; + std::string name = Name; + + for (char& ch : name) { + if (ch == '_') { + cap = true; + continue; + } + + if (cap) { + ch = toupper(ch); + cap = false; + } + } + + name.erase( + std::remove(name.begin(), name.end(), '_'), + name.end() + ); + + /* TODO: figure out name */ + return name; + } +}; + +enum TypeAttribute +{ + TAAbstract = 1, + TAVarArgConstructor = 2 +}; + +struct Klass +{ + std::string Name; + std::string Parent; + std::string TypeBase; + int Attributes; + std::vector<Field> Fields; + std::vector<std::string> LoadDependencies; + int ActivationPriority{0}; +}; + +enum RuleAttribute +{ + RARequired = 1 +}; + +struct Rule +{ + int Attributes; + bool IsName; + std::string Type; + std::string Pattern; + + std::vector<Rule> Rules; +}; + +enum ValidatorType +{ + ValidatorField, + ValidatorArray, + ValidatorDictionary +}; + +struct Validator +{ + std::string Name; + std::vector<Rule> Rules; +}; + +class ClassCompiler +{ +public: + ClassCompiler(std::string path, std::istream& input, std::ostream& oimpl, std::ostream& oheader); + ~ClassCompiler(); + + void Compile(); + + std::string GetPath() const; + + void InitializeScanner(); + void DestroyScanner(); + + void *GetScanner(); + + size_t ReadInput(char *buffer, size_t max_size); + + void HandleInclude(const std::string& path, const ClassDebugInfo& locp); + void HandleAngleInclude(const std::string& path, const ClassDebugInfo& locp); + void HandleImplInclude(const std::string& path, const ClassDebugInfo& locp); + void HandleAngleImplInclude(const std::string& path, const ClassDebugInfo& locp); + void HandleClass(const Klass& klass, const ClassDebugInfo& locp); + void HandleValidator(const Validator& validator, const ClassDebugInfo& locp); + void HandleNamespaceBegin(const std::string& name, const ClassDebugInfo& locp); + void HandleNamespaceEnd(const ClassDebugInfo& locp); + void HandleCode(const std::string& code, const ClassDebugInfo& locp); + void HandleLibrary(const std::string& library, const ClassDebugInfo& locp); + void HandleMissingValidators(); + + void CodeGenValidator(const std::string& name, const std::string& klass, const std::vector<Rule>& rules, const std::string& field, const FieldType& fieldType, ValidatorType validatorType); + void CodeGenValidatorSubrules(const std::string& name, const std::string& klass, const std::vector<Rule>& rules); + + static void CompileFile(const std::string& inputpath, const std::string& implpath, + const std::string& headerpath); + static void CompileStream(const std::string& path, std::istream& input, + std::ostream& oimpl, std::ostream& oheader); + + static void OptimizeStructLayout(std::vector<Field>& fields); + +private: + std::string m_Path; + std::istream& m_Input; + std::ostream& m_Impl; + std::ostream& m_Header; + void *m_Scanner; + + std::string m_Library; + + std::map<std::pair<std::string, std::string>, Field> m_MissingValidators; + + static unsigned long SDBM(const std::string& str, size_t len); + static std::string BaseName(const std::string& path); + static std::string FileNameToGuardName(const std::string& path); +}; + +} + +#endif /* CLASSCOMPILER_H */ + diff --git a/tools/mkclass/mkclass.cpp b/tools/mkclass/mkclass.cpp new file mode 100644 index 0000000..eb0d8f8 --- /dev/null +++ b/tools/mkclass/mkclass.cpp @@ -0,0 +1,16 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "classcompiler.hpp" +#include <iostream> + +using namespace icinga; + +int main(int argc, char **argv) +{ + if (argc < 4) { + std::cerr << "Syntax: " << argv[0] << " <input> <output-impl> <output-header>" << std::endl; + return 1; + } + + ClassCompiler::CompileFile(argv[1], argv[2], argv[3]); +} |