PLUGIN json lang: "C++" version: "1.0" date: "2022-05-24" author: "Julien BRUGUIER" maintainer: "Julien BRUGUIER " synopsis: "Plugin to manage JSON documents" description: %{ This plugin is a JSON document manager. It allows JSON parsing, JSON generation, objects and arrays manipulations. %} comment flex: "/*" " * " " */" comment bison: "/*" " * " " */" includes: %{ #include #include #include #include #include %} code: %{ struct yy_buffer_state; void jsonparserlex_init(void **); void jsonparserlex_destroy(void *); yy_buffer_state* jsonparser_scan_buffer(char *, size_t, void*); void jsonparser_delete_buffer(yy_buffer_state *buffer, void *scanner); int jsonparserparse(void *scanner, JSON::Value &value, std::string& error); %} file source: "src/json.h" %{ #pragma once #include #include #include #include #include #include namespace JSON { template struct Compare { bool operator() (const std::weak_ptr& l, const std::weak_ptr& r) { return l.lock() { typedef std::shared_ptr SP; virtual ~Value() {} virtual Value::SP clone() const = 0; template friend oStream& operator<<(oStream& os, const Value::SP& e) { e->print(os); return os; } virtual void print(std::ostream& os) const = 0; }; struct Null : public Value { typedef std::shared_ptr SP; Null() = default; virtual ~Null() {} virtual Value::SP clone() const override { return strict_clone(); } Null::SP strict_clone() const { return std::make_shared(); } virtual void print(std::ostream& os) const override { os << "null"; } }; struct Integer : public Value { typedef std::shared_ptr SP; Integer(const long long int i) :_i(i) {} virtual ~Integer() {} virtual Value::SP clone() const override { return strict_clone(); } Integer::SP strict_clone() const { return std::make_shared(_i); } virtual void print(std::ostream& os) const override { os << _i; } long long int _i; }; struct String : public Value { typedef std::shared_ptr SP; String(const std::string& s) :_s(s) {} virtual ~String() {} virtual Value::SP clone() const override { return strict_clone(); } String::SP strict_clone() const { return std::make_shared(_s); } virtual void print(std::ostream& os) const override { os << "\"" << _s << "\""; } std::string _s; }; struct Boolean : public Value { typedef std::shared_ptr SP; Boolean(const bool b) :_b(b) {} virtual ~Boolean() {} virtual Value::SP clone() const override { return strict_clone(); } Boolean::SP strict_clone() const { return std::make_shared(_b); } virtual void print(std::ostream& os) const override { os << (_b?"true":"false"); } bool _b; }; struct Object : public Value { typedef std::shared_ptr SP; virtual ~Object() {} virtual Value::SP clone() const override { return strict_clone(); } Object::SP strict_clone() const { auto o = std::make_shared(); for(const auto& m:_members) { o->_members.insert(std::make_pair(m.first,m.second->clone())); } return o; } virtual void print(std::ostream& os) const override { os << "{"; bool cont=false; for(const auto& m:_members) { if(cont) { os << ","; } cont=true; os << " \"" << m.first << "\": "; m.second->print(os); } os << " }"; } std::map _members; }; struct Array : public Value { typedef std::shared_ptr SP; virtual ~Array() {} virtual Value::SP clone() const override { return strict_clone(); } Array::SP strict_clone() const { auto o = std::make_shared(); for(const auto& e:_elements) { o->_elements.push_back(e->clone()); } return o; } virtual void print(std::ostream& os) const override { os << "["; bool cont=false; for(const auto& e:_elements) { if(cont) { os << ","; } cont=true; os << " "; e->print(os); } os << " ]"; } std::vector _elements; }; } %} file source: "src/json.cpp" %{ #include %} # flex & bison file flex: "src/parser/parser.lex.lpp" ${ %{ #include #include #define YY_USER_ACTION yylloc->first_line = yylloc->last_line = yylineno; %} %option nounput %option reentrant %option bison-bridge %option bison-locations %option noyywrap %% (0|-?[1-9][0-9]*) { yylval->integer = ::atoi(yytext); return JSON_INTEGER; } \"([^\"]|\\.)*\" { std::string token(yytext,yyleng); yylval->string = token.substr(1,token.size()-2); return JSON_STRING; } true { return JSON_TRUE; } false { return JSON_FALSE; } null { return JSON_NULL; } "{" { return START_OBJECT; } "}" { return END_OBJECT; } : { return MEMBER; } "[" { return START_ARRAY; } "]" { return END_ARRAY; } , { return SEPARATOR; } [ \t] { } \n { ++yylineno; } . { return _INVALID_; } %% $} file bison: "src/parser/parser.syn.ypp" ${ %{ #include #define YYDEBUG 0 #include extern int jsonparsererror(YYLTYPE *llocp, void *scanner, JSON::Value::SP& value, std::string& error, std::string mesg); extern int jsonparserlex(YYSTYPE *lvalp, YYLTYPE *llocp, void *scanner); extern int jsonparserlex_init(void *scanner); extern int jsonparserlex_destroy(void *scanner); %} %locations %define api.pure full %define api.value.type { struct ParserValue } %param { void *scanner } %parse-param { JSON::Value::SP& value } %parse-param { std::string& error } %initial-action { #if YYDEBUG==1 jsonparserdebug=1; #endif } %code requires { struct ParserValue { long long int integer; std::string string; bool boolean; JSON::Value::SP value; std::map object; std::pair member; std::vector array; }; # define YYCOPY(Dst, Src, Count) \ do \ { \ YYSIZE_T yyi; \ for (yyi = 0; yyi < (Count); yyi++) \ (Dst)[yyi] = (Src)[yyi]; \ } \ while (0) } %token _INVALID_ %token JSON_INTEGER %token JSON_STRING %token JSON_TRUE JSON_FALSE %token JSON_NULL %token SEPARATOR %token START_OBJECT END_OBJECT MEMBER %token START_ARRAY END_ARRAY %type value null integer string boolean object array %type member_list non_empy_member_list %type member %type element_list non_empy_element_list %start top_value %% top_value: value { value = $1; } ; value: null { $$ = $1; } | integer { $$ = $1; } | string { $$ = $1; } | boolean { $$ = $1; } | object { $$ = $1; } | array { $$ = $1; } ; null: JSON_NULL { $$ = std::make_shared(); } ; integer: JSON_INTEGER { $$ = std::make_shared($1); } ; string: JSON_STRING { $$ = std::make_shared($1); } ; boolean: JSON_TRUE { $$ = std::make_shared(true); } | JSON_FALSE { $$ = std::make_shared(false); } ; object: START_OBJECT member_list END_OBJECT { auto object = std::make_shared(); object->_members = $2; $$ = object; } ; member_list: { $$ = std::map(); } | non_empy_member_list { $$ = $1; } ; non_empy_member_list: member { $$ = std::map(); $$.insert($1); } | non_empy_member_list SEPARATOR member { $$ = $1; $$.insert($3); } ; member: JSON_STRING MEMBER value { $$ = std::make_pair($1,$3); } ; array: START_ARRAY element_list END_ARRAY { auto array = std::make_shared(); array->_elements = $2; $$ = array; } element_list: { $$ = std::vector(); } | non_empy_element_list { $$ = $1; } ; non_empy_element_list: value { $$ = std::vector(); $$.push_back($1); } | element_list SEPARATOR value { $$ = $1; $$.push_back($3); } ; %% int jsonparsererror(YYLTYPE *llocp, void *scanner, JSON::Value::SP& value, std::string& error, std::string mesg) { std::ostringstream oss; oss << "Invalid JSON text, line"; if(llocp->first_line==llocp->last_line) { oss << " " << llocp->first_line; } else { oss << "s " << llocp->first_line << "-" << llocp->last_line; } oss << ": " << mesg; error = oss.str(); return 1; } $} file source: "src/parser/includes.h" %{ #include #include %} # Makefile file make: "src/parser/Makefile.am" %{ AM_CPPFLAGS=-I${top_builddir} -I${top_srcdir} SUBDIRS= noinst_LTLIBRARIES=libparser.la 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=jsonparser -o y.tab.c AM_LFLAGS=-P jsonparser -o lex.yy.c libparser_la_CXXFLAGS=$(AM_CXXFLAGS) -Wno-error=sign-compare libparser_la_SOURCES=parser.lex.lpp parser.syn.ypp includes.h libparser_la_LIBADD= libparser_la_LDFLAGS=-no-undefined -module %} patch: "configure.ac" %{ --- configure.ac.orig 2023-03-24 04:43:36.421673417 +0100 +++ configure.ac 2023-03-24 04:43:36.483673585 +0100 @@ -29,7 +29,10 @@ AC_CHECK_PROG([DIFF],[diff],[diff -u]) AC_PROG_CXX +AM_PROG_LEX +AC_PROG_YACC AC_LANG([C++]) +AM_CXXFLAGS="$(AM_CXXFLAGS) -std=c++14" AM_PROG_LIBTOOL AC_CHECK_TOOL([STRIP],[strip]) @@ -54,6 +57,7 @@ AC_CONFIG_FILES([ Makefile src/Makefile + src/parser/Makefile doc/Makefile test/Makefile ]) %} patch: "Makefile.am" %{ --- Makefile.am.orig 2023-03-24 04:43:36.476673567 +0100 +++ Makefile.am 2023-03-24 04:48:19.111443442 +0100 @@ -26,5 +26,5 @@ libsvmjson_la_SOURCES= libsvmjson_la_LDFLAGS=-module -ldl -Wl,-rpath -Wl,${pkglibdir} -Wl,-L$(SVM) -libsvmjson_la_LIBADD=src/libplugin.la +libsvmjson_la_LIBADD=src/libplugin.la src/parser/libparser.la libsvmjson_la_LIBTOOLFLAGS=--tag=disable-static %} patch: "Makefile.local" %{ --- Makefile.local.orig 2023-03-24 04:43:36.477673569 +0100 +++ Makefile.local 2023-03-24 04:43:36.480673578 +0100 @@ -20,8 +20,8 @@ OPTIONS= -DEPENDENCIES= -GENERATED= +DEPENDENCIES=src/json.o src/parser/parser.syn.o src/parser/parser.lex.o +GENERATED=src/parser/parser.lex.cpp src/parser/parser.syn.{cpp,hpp,output} all: libsvmjson.so @@ -31,6 +31,12 @@ .cpp.o: g++ -std=c++14 -o $@ -c $< -fPIC -DPIC $(OPTIONS) -I. +src/parser/parser.lex.cpp: src/parser/parser.lex.lpp + flex -P jsonparser -o $@ $< + +src/parser/parser.syn.cpp: src/parser/parser.syn.ypp + bison -d -v --file-prefix=y --name-prefix=jsonparser -o $@ $< + .PHONY: clean clean: rm -rf src/plugin.o $(DEPENDENCIES) $(GENERATED) libsvmjson.so %} patch: "src/Makefile.am" %{ --- src/Makefile.am.orig 2023-03-24 04:43:36.437673461 +0100 +++ src/Makefile.am 2023-03-24 04:43:36.485673591 +0100 @@ -18,10 +18,10 @@ AM_CPPFLAGS=-I${top_builddir} -I${top_srcdir} -std=c++14 -SUBDIRS= +SUBDIRS=parser noinst_LTLIBRARIES=libplugin.la -libplugin_la_SOURCES=plugin.cpp plugin.h +libplugin_la_SOURCES=plugin.cpp plugin.h json.cpp json.h libplugin_la_LIBADD= libplugin_la_LDFLAGS=-no-undefined %} DEFINE TYPE json.value %{ type_value() = default; explicit type_value(const JSON::Value::SP& v) :_value(v) {} type_value(const type_value& v) :_error(v._error) { if(static_cast(v._value)) { _value = v._value->clone(); } } operator std::string () const { std::ostringstream oss; if(not _error.empty()) { oss << "\"Error: " << _error << "\""; } else { oss << (_value); } return oss.str(); } JSON::Value::SP _value; std::string _error; %} delete default: %{} copy default: %{} constant: %{ void *scanner; ::jsonparserlex_init(&scanner); char *src = new char[value.size+2]; ::memcpy(src,value.string,value.size); src[value.size] = src[value.size+1] = '\0'; yy_buffer_state *buffer = ::jsonparser_scan_buffer(src,value.size+2,scanner); type_value *t = new type_value; ::jsonparserparse(scanner,t->_value,t->_error); ::jsonparser_delete_buffer(buffer,scanner); delete [] src; ::jsonparserlex_destroy(scanner); if(not t->_error.empty()) { std::string e = t->_error; delete t; ERROR_EXTERNAL(json,invalid,e.c_str()); } return t; %} print default: %{} help: %{ This type is a generic container for all JSON values: integers, strings, booleans, objects, arrays and the null object. .P It is used for generic manipulations of JSON values, and can be converted from and to raw values. .P This type is copyable, and can be constructed from a constant. %} TYPE json.object %{ explicit type_object(const JSON::Object::SP& o) :_object(o) {} type_object(const type_object& o) :_object(o._object->strict_clone()) { } operator std::string () const { std::ostringstream oss; _object->print(oss); return oss.str(); } JSON::Object::SP _object; %} delete default: %{} copy default: %{} print default: %{} help: %{ This type represents a raw JSON object. .P It can be used to manipulate JSON object members. %} TYPE json.array %{ explicit type_array(const JSON::Array::SP& a) :_array(a) {} type_array(const type_array& a) :_array(a._array->strict_clone()) { } operator std::string () const { std::ostringstream oss; _array->print(oss); return oss.str(); } JSON::Array::SP _array; %} delete default: %{} copy default: %{} print default: %{} help: %{ This type represents a raw JSON array. .P It can be used to manipulate JSON arrays elements. %} TYPE json.null %{ explicit type_null(const JSON::Null::SP& n) :_null(n) {} type_null(const type_null& n) :_null(n._null->strict_clone()) { } operator std::string () const { std::ostringstream oss; _null->print(oss); return oss.str(); } JSON::Null::SP _null; %} delete default: %{} copy default: %{} print default: %{} help: %{ This type represents a raw null JSON value. %} INTERRUPTION json.invalid help: %{ Interruption raised when an invalid JSON value is detected. %} INSTRUCTION json.parse STR -> json.value %{ auto s = ARGV_VALUE(0,string); auto t = ::type_value_constant(svm,s); return NEW_PLUGIN(json,value,t); %} help: %{ Create a JSON value from a textual representation. .P This instruction raises the json.invalid interruption if the string does not represent a valid JSON value. %} INSTRUCTION json.print json.value -> STR %{ auto j = ARGV_PLUGIN(0,json,value); std::string s = *j; SVM_String ss = ::svm_string_new(svm,s.c_str(),s.size()); return NEW_VALUE(string,ss); %} help: %{ Generate a string corresponding to the JSON value. .P This string can be parsed again to produce a clone of the JSON value. %} INSTRUCTION json.value [ INT STR BLN json.object json.array json.null ] ? -> json.value %{ if(argc==0) { auto t = new type_value(std::make_shared()); return NEW_PLUGIN(json,value,t); } SVM_Value v = ::svm_parameter_value_get(svm,argv[0]); if(::svm_value_type_is_integer(svm,v)) { auto r = ::svm_value_integer_get(svm,v); auto t = new type_value(std::make_shared(r)); return NEW_PLUGIN(json,value,t); } if(::svm_value_type_is_string(svm,v)) { auto r = ::svm_value_string_get(svm,v); auto t = new type_value(std::make_shared(std::string(r.string,r.size))); return NEW_PLUGIN(json,value,t); } if(::svm_value_type_is_boolean(svm,v)) { auto r = ::svm_value_boolean_get(svm,v); auto t = new type_value(std::make_shared(r==TRUE)); return NEW_PLUGIN(json,value,t); } auto type = ::svm_value_plugin_get_type(svm,v); std::string p = ::svm_value_pluginentrypoint_get_entry(svm,type).string; if(p=="object") { auto r = ARGV_PLUGIN(0,json,object); auto t = new type_value(r->_object); return NEW_PLUGIN(json,value,t); } if(p=="array") { auto r = ARGV_PLUGIN(0,json,array); auto t = new type_value(r->_array); return NEW_PLUGIN(json,value,t); } if(p=="null") { auto r = ARGV_PLUGIN(0,json,null); auto t = new type_value(r->_null); return NEW_PLUGIN(json,value,t); } ERROR_INTERNAL(FAILURE,"Unknown raw type"); return nullptr; %} help: %{ Convert a raw value into a JSON value. .P When no raw value is provided, the JSON null value is contained into the returned value. %} INSTRUCTION json.raw json.value -> [ INT STR BLN json.object json.array json.null ] %{ auto t = ARGV_PLUGIN(0,json,value); auto n = std::dynamic_pointer_cast(t->_value); if(static_cast(n)) { type_null *tt = new type_null(n); return NEW_PLUGIN(json,null,tt); } auto i = std::dynamic_pointer_cast(t->_value); if(static_cast(i)) { return NEW_VALUE(integer,i->_i); } auto s = std::dynamic_pointer_cast(t->_value); if(static_cast(s)) { SVM_String ss = ::svm_string_new(svm,s->_s.c_str(),s->_s.size()); return NEW_VALUE(string,ss); } auto b = std::dynamic_pointer_cast(t->_value); if(static_cast(b)) { return NEW_VALUE(boolean,b->_b?TRUE:FALSE); } auto o = std::dynamic_pointer_cast(t->_value); if(static_cast(o)) { type_object *tt = new type_object(o); return NEW_PLUGIN(json,object,tt); } auto a = std::dynamic_pointer_cast(t->_value); if(static_cast(a)) { type_array *tt = new type_array(a); return NEW_PLUGIN(json,array,tt); } ERROR_INTERNAL(FAILURE,"Unknown JSON type"); return nullptr; %} help: %{ Convert a JSON value into a raw value. .P The type of the returned value depends on the contained raw value: This can be used for hard type checking. Soft type checking can be performed using the AUTO type and the IS condition. %} INSTRUCTION json.object ( STR = json.value ( , STR = json.value ) * ) ? -> json.object %{ auto o = std::make_shared(); for(SVM_Size index=0 ; index_members.insert(std::make_pair(std::string(key.string,key.size),value->_value->clone())); } type_object *t = new type_object(o); return NEW_PLUGIN(json,object,t); %} help: %{ Create a raw JSON object. .P Some members can be specified at creation time. When a key is repeated, only the first one is taken into account. %} INSTRUCTION json.keys json.object -> PTR %{ auto t = ARGV_PLUGIN(0,json,object); std::vector keys; for(const auto& m: t->_object->_members) { keys.push_back(m.first); } SVM_Memory_Zone zone = ::svm_memory_zone_new(svm); ::svm_memory_zone_append_internal__raw(svm,zone,STRING,keys.size()); SVM_Value_Pointer p = ::svm_memory_allocate(svm,CURRENT(kernel),zone); SVM_Address a = ::svm_value_pointer_get_address(svm,p); for(const auto& k:keys) { ::svm_memory_write_address(svm,CURRENT(kernel),a++,NEW_VALUE(string,::svm_string_new(svm,k.c_str(),k.size()))); } return p; %} help: %{ Return the keys of a JSON object as an array of strings, referenced by the returned pointer. %} INSTRUCTION json.member json.object STR : key -> json.value ? %{ auto t = ARGV_PLUGIN(0,json,object); auto k = ARGV_VALUE(1,string); auto it = t->_object->_members.find(std::string(k.string,k.size)); if(it==t->_object->_members.end()) { return NEW_NULL_PLUGIN(json,value); } auto v = new type_value(it->second); return NEW_PLUGIN(json,value,v); %} help: %{ Return the member from a JSON object from its key. .P When the key is invalid, a null value is returned. %} INSTRUCTION json.add_member MUTABLE json.object STR : key json.value : value -> BLN %{ auto t = ARGV_PLUGIN(0,json,object); auto k = ARGV_VALUE(1,string); auto v = ARGV_PLUGIN(2,json,value); auto b = t->_object->_members.insert(std::make_pair(std::string(k.string,k.size),v->_value->clone())).second; return NEW_VALUE(boolean,b?TRUE:FALSE); %} help: %{ Add a member to a JSON object. .P Return true when the member has been added, false otherwise. %} INSTRUCTION json.change_member MUTABLE json.object STR : key json.value : value -> BLN %{ auto t = ARGV_PLUGIN(0,json,object); auto k = ARGV_VALUE(1,string); auto v = ARGV_PLUGIN(2,json,value); auto it = t->_object->_members.find(std::string(k.string,k.size)); if(it==t->_object->_members.end()) { return NEW_VALUE(boolean,FALSE); } it->second = v->_value->clone(); return NEW_VALUE(boolean,TRUE); %} help: %{ Change a member from a JSON object. .P Return true when the member has been changed, false otherwise. %} INSTRUCTION json.remove_member MUTABLE json.object STR : key -> BLN %{ auto t = ARGV_PLUGIN(0,json,object); auto k = ARGV_VALUE(1,string); auto it = t->_object->_members.find(std::string(k.string,k.size)); if(it==t->_object->_members.end()) { return NEW_VALUE(boolean,FALSE); } t->_object->_members.erase(it); return NEW_VALUE(boolean,TRUE); %} help: %{ Remove a member from a JSON object. .P Return true when the member has been removed, false otherwise. %} INSTRUCTION json.array json.value * -> json.array %{ auto a = std::make_shared(); for(SVM_Size index=0 ; index_elements.push_back(value->_value->clone()); } type_array *t = new type_array(a); return NEW_PLUGIN(json,array,t); %} help: %{ Create a JSON array from its elements. %} INSTRUCTION json.size json.array -> INT : size %{ auto t = ARGV_PLUGIN(0,json,array); return NEW_VALUE(integer,t->_array->_elements.size()); %} help: %{ Return a JSON array size. %} INSTRUCTION json.element json.array INT : index 'END' ? -> json.value ? %{ auto t = ARGV_PLUGIN(0,json,array); auto i = ARGV_VALUE(1,integer); if(argc==3) { i += t->_array->_elements.size(); } if(not ((i>=0) and (i_array->_elements.size()))) { return NEW_NULL_PLUGIN(json,value); } auto v = new type_value(t->_array->_elements[i]); return NEW_PLUGIN(json,value,v); %} help: %{ Return the element from a JSON array at the specified index. The END keyword specify the origin of indexes at the end of the array. .P When the index is invalid, a null value is returned. %} INSTRUCTION json.add_element MUTABLE json.array INT : index 'END' ? json.value : element -> BLN %{ auto t = ARGV_PLUGIN(0,json,array); auto i = ARGV_VALUE(1,integer); SVM_Size iv = 2; if(argc==4) { ++iv; i += t->_array->_elements.size(); } auto v = ARGV_PLUGIN(iv,json,value); if(not ((i>=0) and (i<=t->_array->_elements.size()))) { return NEW_VALUE(boolean,FALSE); } if(i==t->_array->_elements.size()) { t->_array->_elements.push_back(v->_value->clone()); return NEW_VALUE(boolean,TRUE); } t->_array->_elements.insert(t->_array->_elements.begin()+i,v->_value->clone()); return NEW_VALUE(boolean,TRUE); %} help: %{ Add an element to a JSON array at the specified index. The END keyword specify the origin of indexes at the end of the array. %} INSTRUCTION json.change_element MUTABLE json.array INT : index 'END' ? json.value : element -> BLN %{ auto t = ARGV_PLUGIN(0,json,array); auto i = ARGV_VALUE(1,integer); SVM_Size iv = 2; if(argc==4) { ++iv; i += t->_array->_elements.size(); } auto v = ARGV_PLUGIN(iv,json,value); if(not ((i>=0) and (i_array->_elements.size()))) { return NEW_VALUE(boolean,FALSE); } t->_array->_elements[i] = v->_value->clone(); return NEW_VALUE(boolean,TRUE); %} help: %{ Change an element from a JSON array at the specified index. The END keyword specify the origin of indexes at the end of the array. %} INSTRUCTION json.remove_element MUTABLE json.array INT : index 'END' ? -> BLN %{ auto t = ARGV_PLUGIN(0,json,array); auto i = ARGV_VALUE(1,integer); if(argc==3) { i += t->_array->_elements.size(); } if(not ((i>=0) and (i_array->_elements.size()))) { return NEW_VALUE(boolean,FALSE); } t->_array->_elements.erase(t->_array->_elements.begin()+i); return NEW_VALUE(boolean,TRUE); %} help: %{ Remove an element from a JSON array at the specified index. The END keyword specify the origin of indexes at the end of the array. %}