diff --git a/src/clientreq.cpp b/src/clientreq.cpp index 621b6fa..4c4ca6e 100644 --- a/src/clientreq.cpp +++ b/src/clientreq.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -80,6 +81,8 @@ void CommonBase::_field(const std::string& s) if(idx==cur->children.size()) { cur->addChild(Member(TypeCode::Struct, child)); } + + cur = &cur->children[idx]; } } @@ -109,12 +112,198 @@ void CommonBase::_record(const std::string& key, const void* value, StoreType vt req->options[key] = std::move(v); } +struct PVRParser +{ + /* Grammar + * + * PVR : + * | ENT + * | ENT PVR + * ENT : FIELD | RECORD | name + * FIELD : "field" '(' FIELD_LIST ')' + * RECORD : "record" '[' OPTIONS '}' + * FIELD_LIST : + * | name + * | FIELD_LIST ',' name + * OPTIONS -> + * | name '=' name + * | OPTIONS ',' name '=' name + */ + enum token_t { + // terminals + comma = ',', + lp = '(', + rp = ')', + lb = '[', + rb = ']', + eq = '=', + field = 'f', + record = 'r', + name = 'n', + tEOF = -1, + }; + + std::regex lexer; + token_t lextok = tEOF; + std::string lexval; + + const char* input; + + CommonBase& target; + + PVRParser(CommonBase& target, const char* input) + :lexer(R"re((?:([\[\],\(\)=])|([a-zA-Z0-9_.]+))(.*))re") + // (?: literal | name ) remaining + // \1 \2 \3 + ,input(input) + ,target(target) + {} + + void lex() + { + lexval.clear(); + + if(!*input) { + lextok = tEOF; + return; + } + + // skip leading whitespace + while(' '==*input) + input++; + + std::cmatch M; + std::regex_match(input, M, lexer); + if(M.empty()) + throw std::runtime_error("invalid charactor near: "+std::string(input)); + + if(M[1].matched) { + lextok = token_t(input[M.position(1)]); + + } else if(M[2].matched) { + lexval = M[2].str(); + if(lexval=="field") { + lextok = field; + + } else if(lexval=="record") { + lextok = record; + + } else { + lextok = name; + } + + } else { + throw std::logic_error("pvRequest lexer logic error invalid state"); + } + + if(!M[3].matched) + throw std::logic_error("pvRequest lexer logic error no continuation"); + + input += M.position(3); + } + + void parse() + { + while(input) { + auto start = input; + + lex(); + + if(lextok==tEOF) { + break; + + } else if(lextok==field) { + lex(); + if(lextok!=lp) + throw std::runtime_error(SB()<<"Expected field( at "< { +class PutBuilder : protected detail::CommonBuilder { bool _doGet = true; std::function _builder; std::function _result; diff --git a/test/Makefile b/test/Makefile index 1d9414b..f5daca7 100644 --- a/test/Makefile +++ b/test/Makefile @@ -42,6 +42,10 @@ TESTPROD += testdata testdata_SRCS += testdata.cpp TESTS += testdata +TESTPROD += testpvreq +testpvreq_SRCS += testpvreq.cpp +TESTS += testpvreq + TESTPROD += testinfo testinfo_SRCS += testinfo.cpp TESTS += testinfo diff --git a/test/testpvreq.cpp b/test/testpvreq.cpp new file mode 100644 index 0000000..9a4b7da --- /dev/null +++ b/test/testpvreq.cpp @@ -0,0 +1,206 @@ +/** + * Copyright - See the COPYRIGHT that is included with this distribution. + * pvxs is distributed subject to a Software License Agreement found + * in file LICENSE that is included with this distribution. + */ + +#include + +#include + +#include + +#include + +#include +#include +#include +#include "utilpvt.h" + +namespace { +using namespace pvxs; + +struct TestBuilder : client::detail::CommonBuilder +{ + TestBuilder() + :client::detail::CommonBuilder(nullptr, "") + {} + + Value makeReq() const { + return _build(); + } +}; + +void testEmpty() +{ + testShow()<<__func__; + + auto req = TestBuilder().makeReq(); + + testShow()< valid({ + "field()", + "field(a,b,a.b)field(x)", + "a", // short-hand + "field(a,b,a.b)field(x)", + // should these be valid? + "field(,)", + "field(foo,)", + "record[foo=bar,]", + }); + + for(auto& pvr : valid) { + try { + testCase(true)< errors({ + "field(", + "field(value", + "field(value,alarm", + "field[]", + "record()", + "field(!@#)", + "record[", + "record[key", + "record[key=", + "record[key=]", + "record[,]", + }); + + for(auto& pvr : errors) { + + testThrows([&pvr](){ + auto req = TestBuilder() + .pvRequest(pvr) + .makeReq(); + testShow()< [pvname ...]\n" "\n" " -h Show this message.\n" + " -r pvRequest condition.\n" " -v Make more noise.\n" " -d Shorthand for $PVXS_LOG=\"pvxs.*=DEBUG\". Make a lot of noise.\n" " -w Operation timeout in seconds. default 5 sec.\n" @@ -38,10 +39,11 @@ int main(int argc, char *argv[]) logger_config_env(); // from $PVXS_LOG double timeout = 5.0; bool verbose = false; + std::string request("field()"); { int opt; - while ((opt = getopt(argc, argv, "hvdw:")) != -1) { + while ((opt = getopt(argc, argv, "hvdw:r:")) != -1) { switch(opt) { case 'h': usage(argv[0]); @@ -58,6 +60,9 @@ int main(int argc, char *argv[]) return 1; } break; + case 'r': + request = optarg; + break; default: usage(argv[0]); std::cerr<<"\nUnknown argument: "< [ | = ...]\n" "\n" " -h Show this message.\n" + " -r pvRequest condition.\n" " -v Make more noise.\n" " -d Shorthand for $PVXS_LOG=\"pvxs.*=DEBUG\". Make a lot of noise.\n" " -w Operation timeout in seconds. default 5 sec.\n" @@ -38,10 +39,11 @@ int main(int argc, char *argv[]) logger_config_env(); // from $PVXS_LOG double timeout = 5.0; bool verbose = false; + std::string request("field()"); { int opt; - while ((opt = getopt(argc, argv, "hvdw:")) != -1) { + while ((opt = getopt(argc, argv, "hvdw:r:")) != -1) { switch(opt) { case 'h': usage(argv[0]); @@ -58,6 +60,9 @@ int main(int argc, char *argv[]) return 1; } break; + case 'r': + request = optarg; + break; default: usage(argv[0]); std::cerr<<"\nUnknown argument: "< Value { auto val = std::move(prototype); for(auto& pair : values) {