PLUGIN config lang: "C++" version: "1.1.1" date: "2025-09-22" author: "Julien BRUGUIER" maintainer: "Julien BRUGUIER " synopsis: "Configuration for applications" description: %{ This plugin provides a configuration scheme for Simple Virtual Machine applications with light but versatile syntax. .P The configuration syntax: - Ignore all blank characters, - Ignore everything after a hash (#), treated as comment. .P The values within the configuration are organised in a tree where the root node is the inside part of an object (see below). .P Possible values: - Integers, - Strings (between double quotes (")), - Booleans (TRUE or FALSE), - Plugin Entry Points, - Plugin constant values. .P Two other types are accessibles: - References: .nf $.. [...] .$ .fi Written like above, this value references another value located at the path indicated between the two dollar ($) signs; - Objects: - Objects are delimited by curved brackets ({ at begining and } at end), - Objects are made of members written as : , without separator between members, - Each member name shall be unique within each object, - Each object is a node within the configuration tree. .P Within an object, some members can be added depending on conditions, written like (The ELSE part is optional): .nf CASE THEN END CASE THEN END [...] CASE THEN END ELSE END END .fi The conditions are made of identifiers as atoms, with the regular NOT, AND, OR and XOR boolean identifiers and the parenthesis for precedence. %} changelog: %{ svm-plugin-config (1.1.1) UNRELEASED; urgency=medium * Build for SVM API 2.9. -- Julien Bruguier Sun, 01 Mar 2026 22:58:15 +0100 svm-plugin-config (1.1.0) UNRELEASED; urgency=medium * Adding ability of references to replace objects, by allowing paths to continue after reference resolution -- Julien Bruguier Fri, 28 Nov 2025 10:09:52 +0100 svm-plugin-config (1.0.0) UNRELEASED; urgency=medium * First release of this plugin -- Julien Bruguier Mon, 22 Sep 2025 23:07:43 +0200 %} example: "URL configuration" %{ Template configuration (config.svm_cfg): .nf config: { url: { protocol: "https" host: $values.url.host$ port: 80 resource: "index.html" } } .fi Values (values.svm_cfg): .nf values: { url: { host: "www.example.com" } } # Override config: { url: { CASE test THEN port: 8080 END CASE local THEN protocol: "http" port: 33000 END END } } .fi Code (open_url.svm): .nf #!/usr/bin/env svm LOG PLUGIN "svmcom.so" PLUGIN "svmstr.so" PLUGIN "===PLUGINLIB===" ARGUMENT STR environment PROCESS "url" CODE "main" INLINE :memory config.database*2/c, STR/s :config.read FILE "config.svm_cfg" { @&environment } -> &c :config.read FILE "values.svm_cfg" { @&environment } -> (c/1) :config.merge @&c @(c/1) -> &c :str.format { config.value @&c < "config" "url" "protocol" > } "://" { config.value @&c < "config" "url" "host" > } ":" { config.value @&c < "config" "url" "port" > } "/" { config.value @&c < "config" "url" "resource" > } -> &s :com.message @&s END MEMORY environment END .fi %} test: "simple" %{ PLUGIN "svmcom.so" PLUGIN "svmstr.so" PROCESS "test" CODE "main" INLINE :memory config.database/c, AUTO*7/v, BLN/t :config.read STR """ config: { int: 17 str: "example" bln: TRUE pep: com.device plg: CONST str.pattern "ab*a" } """ { } -> &c :config.value @&c < "config" "int" > -> (v/0) :config.value @&c < "config" "str" > -> (v/1) :config.value @&c < "config" "bln" > -> (v/2) :config.value @&c < "config" "pep" > -> (v/3) :config.value @&c < "config" "plg" > -> (v/4) :config.value @&c < "config" "none" > -> (v/5) :config.value @&c < "config" "none" > DEFAULT "default" -> (v/6) :shutdown 1 :unless (v/0) IS INT :shutdown 2 :unless (v/1) IS STR :shutdown 3 :unless (v/2) IS BLN :shutdown 4 :unless (v/3) IS PEP :shutdown 5 :unless (v/4) IS str.pattern :shutdown 6 :when (v/5) INITIALISED :str.cmp @(v/1) = "example" -> &t :shutdown 7 :unless @&t TRUE :str.cmp @(v/6) = "default" -> &t :shutdown 8 :unless @&t TRUE :str.format { config.value @&c < "config" "str" > } -> (v/6) :str.cmp @(v/1) = @(v/6) -> &t :shutdown 9 :unless @&t TRUE END END %} test: "reference" %{ PLUGIN "svmcom.so" PLUGIN "svmstr.so" PROCESS "test" CODE "main" INLINE :memory config.database/c, AUTO/v, BLN/t :config.read STR """ config: { ref: $config.value$ value: "example" ref2: $config.object$ object: { value: "example2" } } """ { } -> &c :config.value @&c < "config" "ref" > -> &v :shutdown 1 :unless &v IS STR :str.cmp @&v = "example" -> &t :shutdown 2 :unless @&t TRUE :config.value @&c < "config" "ref2" "value" > -> &v :shutdown 3 :unless &v IS STR :str.cmp @&v = "example2" -> &t :shutdown 4 :unless @&t TRUE END END %} test: "array" %{ PLUGIN "svmcom.so" PLUGIN "svmstr.so" PROCESS "test" CODE "main" INLINE :memory config.database/c, PTR/p, BLN/t :config.read STR """ config: { ref: $config.value1$ value1: "example1" value2: "example2" value3: { value4: "example3" value5: "example4" } } """ { } -> &c :config.array @&c < "config" > -> &p :shutdown 1 :unless (&0 + SIZE @&p) IN &5*1 END END %} test: "conditions" %{ PLUGIN "svmcom.so" PLUGIN "svmstr.so" PROCESS "test" CODE "main" INLINE :memory STR/s, config.database/c, INT/i """ config: { CASE a THEN value: 1 END CASE b THEN value: 2 END CASE c AND d THEN value: 3 END ELSE value: 0 END END } """ -> &s :config.read STR @&s { "a" } -> &c :config.value @&c < "config" "value" > -> &i :shutdown 1 :unless (&0 + @&i) IN &1*1 :config.read STR @&s { "a" "b" } -> &c :config.value @&c < "config" "value" > -> &i :shutdown 2 :unless (&0 + @&i) IN &1*1 :config.read STR @&s { "b" } -> &c :config.value @&c < "config" "value" > -> &i :shutdown 3 :unless (&0 + @&i) IN &2*1 :config.read STR @&s { "c" } -> &c :config.value @&c < "config" "value" > -> &i :shutdown 4 :unless (&0 + @&i) IN &0*1 :config.read STR @&s { "c" "d" } -> &c :config.value @&c < "config" "value" > -> &i :shutdown 5 :unless (&0 + @&i) IN &3*1 END END %} test: "merge" %{ PLUGIN "svmcom.so" PLUGIN "svmstr.so" PROCESS "test" CODE "main" INLINE :memory config.database*3/c, INT/i :config.read STR """ config: { value1: 1 value2: 2 } """ { } -> (c/0) :config.read STR """ config: { value2: 4 value3: 3 } """ { } -> (c/1) :config.merge @(c/0) @(c/1) -> (c/2) :config.value @(c/2) < "config" "value1" > -> &i :shutdown 1 :unless (&0 + @&i) IN &1*1 :config.value @(c/2) < "config" "value2" > -> &i :shutdown 2 :unless (&0 + @&i) IN &4*1 :config.value @(c/2) < "config" "value3" > -> &i :shutdown 3 :unless (&0 + @&i) IN &3*1 END END %} test: "find" %{ PLUGIN "svmcom.so" PLUGIN "svmstr.so" PROCESS "test" CODE "main" INLINE :memory config.database/c, PTR/p :config.read STR """ config: { value1: 1 value2: 2 value3: { value1: 3 value4: { value1: 4 value5: 5 } } } """ { } -> &c :config.find @&c "value1" -> &p :shutdown 1 :unless (&0 + SIZE @&p) IN &3*1 :shutdown 2 :unless (&0 + SIZE @(@&p/0)) IN &2*1 :shutdown 2 :unless (&0 + SIZE @(@&p/1)) IN &3*1 :shutdown 2 :unless (&0 + SIZE @(@&p/2)) IN &4*1 END END %} includes: %{ #include #include #include %} code: %{ struct yy_buffer_state; void parserlex_init(void **); void parserlex_destroy(void *); yy_buffer_state* parser_scan_buffer(char *, size_t, void*); yy_buffer_state* parser_create_buffer(FILE*, int, void *scanner); void parser_switch_to_buffer(yy_buffer_state*, void *scanner); void parser_delete_buffer(yy_buffer_state *buffer, void *scanner); int parserparse(void *scanner, std::shared_ptr& object, Config::FlagSet& flags, std::shared_ptr& error); std::mutex _lock; %} file source: "src/model.h" %{ #pragma once #include #include #include #include #include #include #include namespace Config { struct String { static std::string unescape(const std::string& s) { std::string r; bool escape = false; for(const auto& c:s) { if(escape) { if(c=='n') { r+='\n'; } else { r+=c; } escape=false; } else { if(c=='\\') { escape=true; } else { r+=c; } } } return r; } static std::string escape(const std::string& s) { std::string r; for(const auto& c:s) { if(c=='\n') { r+="\\n"; } else if((c=='"') or (c=='\\')) { r+='\\'; r+=c; } else { r+=c; } } return r; } }; struct ConfigElement: public std::enable_shared_from_this { virtual ~ConfigElement() {} virtual std::shared_ptr deep_copy() const = 0; virtual void format(std::ostream& os, const size_t indent) const = 0; virtual std::shared_ptr search(const std::shared_ptr& root, std::list& path, std::set& loop_detector) const = 0; virtual std::vector > values(const std::shared_ptr& root) const = 0; virtual std::shared_ptr merge(const std::shared_ptr& element) const = 0; virtual void find(const std::string& end, std::list >& result, const std::list& path) const = 0; }; struct ConfigValue : public ConfigElement { virtual std::shared_ptr search(const std::shared_ptr& root, std::list& path, std::set& loop_detector) const override { if(not path.empty()) { return std::shared_ptr(); } return this->shared_from_this(); } virtual std::vector > values(const std::shared_ptr& root) const override { return { this->shared_from_this() }; } virtual std::shared_ptr merge(const std::shared_ptr& element) const override { return element->deep_copy(); } virtual void find(const std::string& end, std::list >& result, const std::list& path) const override { } virtual SVM_Value value(const void *svm) const = 0; }; struct ConfigValueInteger : public ConfigValue { ConfigValueInteger(const long long int value) :_value(value) {} virtual std::shared_ptr deep_copy() const override { return std::make_shared(_value); } virtual void format(std::ostream& os, const size_t indent) const override { os << _value; } virtual SVM_Value value(const void *svm) const override { return ::svm_value_integer_new(svm,_value); } long long int _value; }; struct ConfigValueString : public ConfigValue { ConfigValueString(const std::string& value) :_value(value) {} virtual std::shared_ptr deep_copy() const override { return std::make_shared(_value); } virtual void format(std::ostream& os, const size_t indent) const override { os << "\"" << String::escape(_value) << "\""; } virtual SVM_Value value(const void *svm) const override { return ::svm_value_string_new(svm,::svm_string_new(svm,_value.c_str(),_value.size())); } std::string _value; }; struct ConfigValueBoolean : public ConfigValue { ConfigValueBoolean(const bool value) :_value(value) {} virtual std::shared_ptr deep_copy() const override { return std::make_shared(_value); } virtual void format(std::ostream& os, const size_t indent) const override { if(_value) os << "TRUE"; else os << "FALSE"; } virtual SVM_Value value(const void *svm) const override { return ::svm_value_boolean_new(svm,_value?TRUE:FALSE); } bool _value; }; struct ConfigValuePluginEntryPoint : public ConfigValue { ConfigValuePluginEntryPoint(const std::string& plugin, const std::string& entry) :_plugin(plugin), _entry(entry) {} virtual std::shared_ptr deep_copy() const override { return std::make_shared(_plugin,_entry); } virtual void format(std::ostream& os, const size_t indent) const override { os << _plugin << "." << _entry; } virtual SVM_Value value(const void *svm) const override { return ::svm_value_pluginentrypoint_new__raw(svm,_plugin.c_str(),_entry.c_str()); } std::string _plugin; std::string _entry; }; struct ConfigValuePlugin : public ConfigValue { ConfigValuePlugin(const std::string& plugin, const std::string& type, const std::string& value) :_plugin(plugin), _type(type), _value(value) {} virtual std::shared_ptr deep_copy() const override { return std::make_shared(_plugin,_type,_value); } virtual void format(std::ostream& os, const size_t indent) const override { os << "CONST " << _plugin << "." << _type << " \"" << String::escape(_value) << "\""; } virtual SVM_Value value(const void *svm) const override { return ::svm_value_plugin_new_const__string(svm,::svm_value_pluginentrypoint_new__raw(svm,_plugin.c_str(),_type.c_str()),::svm_string_new(svm,_value.c_str(),_value.size())); } std::string _plugin; std::string _type; std::string _value; }; struct ConfigValueReference : public ConfigElement { ConfigValueReference(const std::list& ref) :_ref(ref) {} virtual std::shared_ptr deep_copy() const override { return std::make_shared(_ref); } virtual void format(std::ostream& os, const size_t indent) const override { os << "$"; bool first = true; for(const auto& r: _ref) { if(first) first=false; else os << "."; os << r; } os << "$"; } virtual std::shared_ptr search(const std::shared_ptr& root, std::list& path, std::set& loop_detector) const override { if(not loop_detector.insert(this).second) { return std::shared_ptr(); } auto p = _ref; p.insert(p.end(),path.begin(),path.end()); return root->search(root,p,loop_detector); } virtual std::vector > values(const std::shared_ptr& root) const override { std::set loop_detector; auto p = _ref; auto e = root->search(root,p,loop_detector); if(not e) return {}; return e->values(root); } virtual std::shared_ptr merge(const std::shared_ptr& element) const override { return element->deep_copy(); } virtual void find(const std::string& end, std::list >& result, const std::list& path) const override { } std::list _ref; }; struct ConfigObject : public ConfigElement { virtual std::shared_ptr deep_copy() const override { auto object = std::make_shared(); for(const auto& m:_members) { object->_members.insert(std::make_pair(m.first,m.second->deep_copy())); } object->_root = _root; return object; } virtual void format(std::ostream& os, const size_t indent) const override { size_t add = _root?0:1; if(not _root) os << "{" << std::endl; for(auto& m: _members) { os << std::string(indent+add,'\t') << m.first << ": "; m.second->format(os,indent+add); os << std::endl; } if(not _root) os << std::string(indent,'\t') << "}"; } virtual std::shared_ptr search(const std::shared_ptr& root, std::list& path, std::set& loop_detector) const override { if(path.empty()) { return this->shared_from_this(); } auto it=_members.find(path.front()); if(it==_members.end()) { return std::shared_ptr(); } path.pop_front(); return it->second->search(root,path,loop_detector); } virtual std::vector > values(const std::shared_ptr& root) const override { std::vector > vv; for(const auto& m:_members) { auto v = m.second->values(root); vv.insert(vv.end(),v.begin(),v.end()); } return vv; } std::shared_ptr value(const std::list& path) const { std::set loop_detector; auto p = path; return std::dynamic_pointer_cast(search(this->shared_from_this(),p,loop_detector)); } std::vector > array(const std::list& path) const { std::set loop_detector; auto p = path; auto element = search(this->shared_from_this(),p,loop_detector); if(not element) { return {}; } auto v = element->values(this->shared_from_this()); std::vector > vv; for(const auto& i: v) { auto ii = std::dynamic_pointer_cast(i); vv.push_back(ii); } return vv; } virtual std::shared_ptr merge(const std::shared_ptr& element) const override { auto object = std::dynamic_pointer_cast(element); if(not object) { return element->deep_copy(); } auto result = std::make_shared(); auto itt = _members.begin(); auto ito = object->_members.begin(); for(;;) { if((itt==_members.end()) and (ito==object->_members.end())) break; if(itt==_members.end()) { result->_members.insert(std::make_pair(ito->first,ito->second->deep_copy())); ++ito; continue; } if(ito==object->_members.end()) { result->_members.insert(std::make_pair(itt->first,itt->second->deep_copy())); ++itt; continue; } if(itt->firstfirst) { result->_members.insert(std::make_pair(itt->first,itt->second->deep_copy())); ++itt; continue; } if(itt->first>ito->first) { result->_members.insert(std::make_pair(ito->first,ito->second->deep_copy())); ++ito; continue; } result->_members.insert(std::make_pair(itt->first,itt->second->merge(ito->second))); ++itt; ++ito; } result->_root = _root; return result; } virtual void find(const std::string& end, std::list >& result, const std::list& path) const override { std::list p = path; for(const auto& m:_members) { p.push_back(m.first); if(m.first==end) { result.push_back(p); } m.second->find(end,result,p); p.pop_back(); } } bool _root = false; std::map > _members; }; struct FlagSet { bool operator() (const std::string& f) const { return _flags.find(f)!=_flags.end(); } std::set _flags; }; struct Error { Error() :_top(0), _bottom(0) {} explicit Error(const std::string& message) :_message(message), _top(0), _bottom(0) {} void position(const size_t top, const size_t bottom) { _top = top; _bottom = (bottom>top)?bottom:top; } template friend Flux& operator<<(Flux& f, const Error& e) { f << "Error"; if(e._top>0) { if(e._top==e._bottom) { f << " at line " << e._top; } else { f << " at lines " << e._top << "-" << e._bottom; } } f << ": " << e._message << std::endl; return f; } const std::string& message() const { return _message; } private: std::string _message; size_t _top; size_t _bottom; }; } %} comment flex: "/*" " * " " */" comment bison: "/*" " * " " */" file source: "src/includes.h" %{ #pragma once #include #include %} file flex: "src/parser.lex.lpp" ${ %{ #include #define YY_USER_ACTION yylloc->first_line = yylloc->last_line; %} %option yy_scan_string %option nounput %option reentrant %option bison-bridge %option bison-locations %option noyywrap %% [a-z][a-z0-9_]* { yylval->string = std::string(yytext,yyleng); return IDENTIFIER; } (0|-?[1-9][0-9]{0,8}) { yylval->integer = ::atoi(yytext); return VAL_INTEGER; } TRUE { return VAL_TRUE; } FALSE { return VAL_FALSE; } \"([^\\"\n]|\\.)*\" { yylval->string = Config::String::unescape(std::string(yytext+1,yyleng-2)); return VAL_STRING; } \"([^\\"\n]|\\.)*\r?\n { return _INVALID_; } CONST { return CONST; } CASE { return CASE; } THEN { return THEN; } ELSE { return ELSE; } END { return END; } AND { return AND; } OR { return OR; } XOR { return XOR; } NOT { return NOT; } \( { return BEGIN_PAR; } \) { return END_PAR; } \{ { return BEGIN_CURVED; } \} { return END_CURVED; } \. { return DOT; } \$ { return DOLLAR; } : { return COLON; } #.* { } [ \t] { } \r?\n { ++yylloc->last_line; } . { return _INVALID_; } %% $} file bison: "src/parser.syn.ypp" ${ %{ //#define YYDEBUG 1 #include #include extern int parsererror(YYLTYPE *llocp, void *scanner, std::shared_ptr& object, Config::FlagSet& flags, std::shared_ptr& error, std::string mesg); extern int parserlex(YYSTYPE *lvalp, YYLTYPE *llocp, void *scanner); extern int parserlex_init(void *scanner); extern int parserlex_destroy(void *scanner); %} %locations %define api.pure full %define api.value.type { struct ParserValue } %param { void *scanner } %parse-param { std::shared_ptr& object } %parse-param { Config::FlagSet& flags } %parse-param { std::shared_ptr& error } %initial-action { #if YYDEBUG==1 parserdebug=1; #endif } %code requires { struct ParserValue { long int integer; std::string string; bool boolean; std::shared_ptr element; std::shared_ptr object; std::pair > member; std::list reference; std::pair > condition; std::vector > > list_condition; }; # define YYCOPY(Dst, Src, Count) \ do \ { \ YYSIZE_T yyi; \ for (yyi = 0; yyi < (Count); yyi++) \ (Dst)[yyi] = (Src)[yyi]; \ } \ while (0) } /* %destructor { } <*> */ %token _INVALID_ %token END_OF_LINE %token IDENTIFIER %token VAL_INTEGER %token VAL_STRING %token VAL_TRUE VAL_FALSE %left NOT %token BEGIN_PAR END_PAR %left AND OR XOR %token BEGIN_CURVED END_CURVED DOT COLON CASE THEN ELSE END DOLLAR CONST %type boolean_expression %type inside_object condition else_condition %type element %type member_object %type inside_reference %type list_case_condition %type case_condition %start config %% config: inside_object { object = $1; object->_root = true; } ; inside_object: { $$ = std::make_shared(); } | inside_object member_object { $$ = $1; if(not $$->_members.insert($2).second) { ::parsererror(&@$,scanner,object,flags,error,"Duplicate member name in object"); } } | inside_object condition { $$ = $1; for(const auto& m:$2->_members) { if(not $$->_members.insert(m).second) { ::parsererror(&@$,scanner,object,flags,error,"Duplicate member name in object"); } } } ; member_object: IDENTIFIER COLON element { $$ = std::make_pair($1,$3); } ; element: VAL_INTEGER { $$ = std::make_shared($1); } | VAL_STRING { $$ = std::make_shared($1); } | VAL_TRUE { $$ = std::make_shared(true); } | VAL_FALSE { $$ = std::make_shared(false); } | IDENTIFIER DOT IDENTIFIER { $$ = std::make_shared($1,$3); } | CONST IDENTIFIER DOT IDENTIFIER VAL_STRING { $$ = std::make_shared($2,$4,$5); } | DOLLAR inside_reference DOLLAR { $$ = std::make_shared($2); } | BEGIN_CURVED inside_object END_CURVED { $$ = $2; } ; inside_reference: IDENTIFIER { $$ = { $1 }; } | inside_reference DOT IDENTIFIER { $$ = $1; $$.push_back($3); } ; condition: list_case_condition else_condition END { bool found=false; for(const auto& c: $1) { if(c.first) { found = true; $$ = c.second; break; } } if(not found) { $$ = $2; } } ; list_case_condition: case_condition { $$ = { $1 }; } | list_case_condition case_condition { $$ = $1; $$.push_back($2); } ; case_condition: CASE boolean_expression THEN inside_object END { $$ = std::make_pair($2,$4); } ; boolean_expression: IDENTIFIER { $$ = flags($1); } | NOT boolean_expression { $$ = not $2; } | BEGIN_PAR boolean_expression END_PAR { $$ = $2; } | boolean_expression AND boolean_expression { $$ = $1 and $3; } | boolean_expression OR boolean_expression { $$ = $1 or $3; } | boolean_expression XOR boolean_expression { $$ = $1 xor $3; } ; else_condition: { $$ = std::make_shared(); } | ELSE inside_object END { $$ = $2; } ; %% int parsererror(YYLTYPE *llocp, void *scanner, std::shared_ptr& object, Config::FlagSet& flags, std::shared_ptr& error, std::string mesg) { std::ostringstream oss; oss << "Unable to parse input: " << mesg; error=std::make_shared(oss.str()); error->position(llocp->first_line,llocp->last_line); return 1; } $} # Makefile file make: "Makefile.config" %{ DEPENDENCIES=src/parser.syn.o src/parser.lex.o GENERATED=src/parser.lex.cpp src/parser.syn.{cpp,hpp,output} %} file make: "Makefile.rules" %{ src/parser.lex.cpp: src/parser.lex.lpp flex -P parser -o $@ $< src/parser.syn.cpp: src/parser.syn.ypp bison -d -v --file-prefix=y --name-prefix=parser -o $@ $< %} checks: %{ AM_PROG_LEX AC_PROG_YACC %} patch: "src/Makefile.am" %{ --- src/Makefile.am.orig 2025-09-23 00:05:49.024397018 +0200 +++ src/Makefile.am 2025-09-23 00:12:16.924381821 +0200 @@ -22,6 +22,17 @@ noinst_LTLIBRARIES=libplugin.la -libplugin_la_SOURCES=plugin.cpp plugin.h +BUILT_SOURCES=parser.lex.cpp parser.syn.cpp + +EXTRABUILTSOURCES=parser.syn.h + +CLEANFILES=parser.syn.output + +AM_YFLAGS=-d -v --file-prefix=y --name-prefix=parser -o y.tab.c +AM_LFLAGS=-P parser -o lex.yy.c + +libplugin_la_CXXFLAGS=$(AM_CXXFLAGS) -Wno-error=sign-compare + +libplugin_la_SOURCES=plugin.cpp plugin.h parser.lex.lpp parser.syn.ypp includes.h model.h libplugin_la_LIBADD= libplugin_la_LDFLAGS=-no-undefined %} DEFINE TYPE config.database %{ operator std::string () const { std::ostringstream oss; _database->format(oss,0); return oss.str(); } std::shared_ptr _database; std::shared_ptr _error; %} delete default: %{} print default: %{} help: %{ This type contains the parsed representation of the configuration. It is build from the text representation of the configuration and can be queried from the SVM code. %} INTERRUPTION config.invalid help: "This interruption is raised when the configuration text can not be parsed." INTERRUPTION config.invalid_file help: "This interruption is raised when the given name is not suitable for configuration build." SYSTEM INSTRUCTION config.read [ 'FILE' 'STR' ] STR:source ( PTR | { STR * } ) : flags -> config.database %{ auto type = ARGV_KEYWORD(0); SVM_String source = ARGV_VALUE(1,string); std::set flags; if(::svm_parameter_type_is_value(svm,argv[2])) { SVM_Value_Pointer p = ::svm_parameter_value_get(svm,argv[2]); SVM_Value_String *flags_array = ::svm_memory_read_pointer_type_internal(svm,CURRENT(kernel),p,STRING); for( ; *flags_array ; ++flags_array) { SVM_String s = ::svm_value_string_get(svm,*flags_array); flags.insert(RAW_STRING(s)); } } else { for(SVM_Index i=3 ; i<(argc-1) ; ++i) { SVM_String s = ARGV_VALUE(i,string); flags.insert(RAW_STRING(s)); } } Config::FlagSet flagset { flags }; std::lock_guard protection(_lock); type_database *t = new type_database; void *scanner; ::parserlex_init(&scanner); if(type=="FILE") { auto s = RAW_STRING(source); FILE* file = ::fopen(s.c_str(),"r"); if(not file) { std::ostringstream oss; oss << "Unable to open file " << s << ": " << ::strerror(errno); delete t; ::parserlex_destroy(scanner); ERROR_EXTERNAL(config,invalid_file,oss.str().c_str()); } yy_buffer_state* buffer = ::parser_create_buffer(file,16*1024,scanner); ::parser_switch_to_buffer(buffer,scanner); ::parserparse(scanner,t->_database,flagset,t->_error); ::parser_delete_buffer(buffer,scanner); ::fclose(file); } else { char *src = new char[source.size+2]; ::memcpy(src,source.string,source.size); src[source.size] = src[source.size+1] = '\0'; yy_buffer_state *buffer = ::parser_scan_buffer(src,source.size+2,scanner); ::parserparse(scanner,t->_database,flagset,t->_error); ::parser_delete_buffer(buffer,scanner); delete [] src; } ::parserlex_destroy(scanner); if(static_cast(t->_error)) { std::ostringstream oss; oss << (*(t->_error)); delete t; ERROR_EXTERNAL(config,invalid,oss.str().c_str()); } return NEW_PLUGIN(config,database,t); %} help: %{ This instruction reads a string of a file and builds a configuration database. .P Either using a pointer to strings, or between curved brackets, some strings can be specified as flags. Each flag passed to the instruction will turn to true each corresponding identifiers in conditions (After CASE keyword). .P It raises the interruption !config.invalid_file when a file is specified, but can not be read. It raises the interruption !config.invalid when the syntax of the config text is invalid. %} SYSTEM INSTRUCTION config.merge ( PTR | config.database config.database + ) -> config.database %{ std::list > dbs; if(argc==1) { SVM_Value_Plugin *t = ::svm_memory_read_pointer_type_external(svm,CURRENT(kernel),::svm_parameter_value_get(svm,argv[1]),CONST_PEP(config,database)); for( ; *t ; ++t) { auto s = reinterpret_cast(::svm_value_plugin_get_internal(svm,*t)); dbs.push_back(s->_database); } } else { for(SVM_Index i=0 ; i_database); } } if(dbs.size()<2) { ERROR_INTERNAL(FAILURE,"Need at least 2 config.database to merge."); } std::shared_ptr merged = dbs.front(); dbs.pop_front(); while(not dbs.empty()) { merged = merged->merge(dbs.front()); dbs.pop_front(); } auto db = std::dynamic_pointer_cast(merged); if(not db) { ERROR_INTERNAL(FAILURE,"Resulting merge is not an object."); } auto r = new type_database; r->_database = db; return NEW_PLUGIN(config,database,r); %} help: %{ This instruction merges several configuration databases in one. .P Each object in the merged configuration database contains the union of the object members. On conflict (same member at the same object), the more recent value is used. .P This instruction can be used as a template engine where the first database is the canvas, and others are customised values within the canvas. %} INSTRUCTION config.value config.database (PTR | < STR * > ) ( 'DEFAULT' VALUE ) ? -> VALUE ? %{ auto db = ARGV_PLUGIN(0,config,database); std::list path; SVM_Index default_index; if(::svm_parameter_type_is_value(svm,argv[1])) { SVM_Value_String *t = ::svm_memory_read_pointer_type_internal(svm,CURRENT(kernel),::svm_parameter_value_get(svm,argv[1]),STRING); for( ; *t ; ++t) { SVM_String s = ::svm_value_string_get(svm,*t); path.push_back(RAW_STRING(s)); } default_index = 2; } else { SVM_Index end = ::svm_parameter_marker_find_closing(svm,argc,argv,1); for(SVM_Index i=2 ; i<(end) ; ++i) { auto s = ARGV_VALUE(i,string); path.push_back(RAW_STRING(s)); } default_index = end+1; } auto value = db->_database->value(path); if(not value) { SVM_Value default_value = NEW_NULL_VALUE(automatic); if(default_indexvalue(svm); %} help: %{ This instruction is the main configuration database query. .P It retrieves the value within the given database at the specified path. .P If a default value is specified, it is returned when the value is not available in the database, or a null value if no default value has been specified. %} FUNCTION config.value config.database (PTR | < STR * > ) ( 'DEFAULT' VALUE ) ? -> VALUE %{ SVM_Value v = instruction_value(svm,argc,argv); if(not ::svm_value_state_is_null(svm,v)) return v; return NEW_VALUE(string,NEW_STRING(std::string())); %} help: %{ This function provides the same functionality as the instruction :config.value with one difference: When the path corresponds to no value, an empty string is returned instead. %} INSTRUCTION config.array config.database (PTR | < STR * > ) -> PTR ? %{ auto db = ARGV_PLUGIN(0,config,database); std::list path; if(::svm_parameter_type_is_value(svm,argv[1])) { SVM_Value_String *t = ::svm_memory_read_pointer_type_internal(svm,CURRENT(kernel),::svm_parameter_value_get(svm,argv[1]),STRING); for( ; *t ; ++t) { SVM_String s = ::svm_value_string_get(svm,*t); path.push_back(RAW_STRING(s)); } } else { for(SVM_Index i=2 ; i<(argc-1) ; ++i) { auto s = ARGV_VALUE(i,string); path.push_back(RAW_STRING(s)); } } auto value = db->_database->array(path); if(value.empty()) { return ::svm_value_pointer_new__raw(svm,0,0); } SVM_Value *t = ::svm_value_array_new(svm,value.size()); SVM_Value *it=t; for(const auto& v:value) { *it = v->value(svm); ++it; } SVM_Memory_Zone z = ::svm_memory_zone_new(svm); ::svm_memory_zone_append_internal__raw(svm,z,AUTOMATIC,value.size()); SVM_Value_Pointer p = ::svm_memory_allocate(svm,CURRENT(kernel),z); ::svm_memory_write_pointer(svm,CURRENT(kernel),p,t); return p; %} help: %{ This instruction returns values from an object in the configuration, and returns them like an array. .P Object members are written in the lexical order of their identifier. References are followed to find the real value behind the reference, and references pointing to no value are ignored. Embedded objects are recursively used. .P The values are written into memory as single block and the pointer to the values is returned by the instruction. %} INSTRUCTION config.find config.database STR -> PTR %{ auto db = ARGV_PLUGIN(0,config,database); auto f = ARGV_VALUE(1,string); std::list > paths; db->_database->find(RAW_STRING(f),paths,{}); SVM_Memory_Zone z = ::svm_memory_zone_new(svm); ::svm_memory_zone_append_internal__raw(svm,z,POINTER,paths.size()); SVM_Value_Pointer p = ::svm_memory_allocate(svm,CURRENT(kernel),z); SVM_Value_Pointer *tp = ::svm_value_array_new(svm,paths.size()); SVM_Index i=0; for(const auto& p:paths) { SVM_Memory_Zone zz = ::svm_memory_zone_new(svm); ::svm_memory_zone_append_internal__raw(svm,zz,STRING,p.size()); SVM_Value_Pointer pp = ::svm_memory_allocate(svm,CURRENT(kernel),zz); SVM_Value_String *tpp = ::svm_value_array_new(svm,p.size()); SVM_Index ii = 0; for(const auto& s:p) { SVM_Value_String vs = ::svm_value_string_new__buffer(svm,s.c_str(),s.size()); tpp[ii++] = vs; } ::svm_memory_write_pointer(svm,CURRENT(kernel),pp,tpp); tp[i++]=pp; } ::svm_memory_write_pointer(svm,CURRENT(kernel),p,tp); return p; %} help: %{ This instruction returns all paths terminated by the given name as member. .P Each path is represented as an array of strings where the last one is equal to the one provided as search criteria. %}