summaryrefslogtreecommitdiffstats
path: root/tools/mkclass
diff options
context:
space:
mode:
Diffstat (limited to 'tools/mkclass')
-rw-r--r--tools/mkclass/CMakeLists.txt43
-rw-r--r--tools/mkclass/class_lexer.ll167
-rw-r--r--tools/mkclass/class_parser.yy558
-rw-r--r--tools/mkclass/classcompiler.cpp1485
-rw-r--r--tools/mkclass/classcompiler.hpp245
-rw-r--r--tools/mkclass/mkclass.cpp16
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]);
+}