pvRequest parsing
This commit is contained in:
+191
-2
@@ -7,6 +7,7 @@
|
||||
#include <stdexcept>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <regex>
|
||||
|
||||
#include <pvxs/version.h>
|
||||
#include <pvxs/client.h>
|
||||
@@ -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 "<<start);
|
||||
parse_fields();
|
||||
if(lextok!=rp)
|
||||
throw std::runtime_error(SB()<<"Expected field(...) at "<<start);
|
||||
|
||||
} else if(lextok==record) {
|
||||
lex();
|
||||
if(lextok!=lb)
|
||||
throw std::runtime_error(SB()<<"Expected record[ at "<<start);
|
||||
parse_options();
|
||||
if(lextok!=rb)
|
||||
throw std::runtime_error(SB()<<"Expected record[...] at "<<start);
|
||||
|
||||
} else if(lextok==name) {
|
||||
// short-hand for field(name)
|
||||
|
||||
if(lexval=="field" || lexval=="record")
|
||||
std::logic_error("pvReq regex alternative order logic error");
|
||||
|
||||
target._field(lexval);
|
||||
|
||||
} else {
|
||||
throw std::runtime_error(SB()<<"Expected field|record|name|EOF at "<<start<<" not token="<<lextok<<"("<<lexval<<")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void parse_fields()
|
||||
{
|
||||
do {
|
||||
lex();
|
||||
if(lextok==name) {
|
||||
target._field(lexval);
|
||||
continue;
|
||||
} else if(lextok==comma) {
|
||||
continue;
|
||||
} else {
|
||||
break; // caller signals error
|
||||
}
|
||||
} while(true);
|
||||
}
|
||||
|
||||
void parse_options()
|
||||
{
|
||||
lex();
|
||||
do {
|
||||
auto start = input;
|
||||
std::string key, val;
|
||||
|
||||
if(lextok==rb) {
|
||||
break;
|
||||
|
||||
} else if(lextok==name) {
|
||||
key = lexval;
|
||||
|
||||
bool ok = true;
|
||||
lex();
|
||||
ok &= lextok==eq;
|
||||
lex();
|
||||
ok &= lextok==name;
|
||||
val = lexval;
|
||||
|
||||
if(!ok) {
|
||||
throw std::runtime_error(SB()<<"Expected K=V or K=V,... at "<<start);
|
||||
}
|
||||
target._record(key, &val, StoreType::String);
|
||||
|
||||
lex();
|
||||
if(lextok==comma) {
|
||||
lex();
|
||||
continue;
|
||||
} else {
|
||||
break; // caller signals error
|
||||
}
|
||||
|
||||
} else {
|
||||
break; // caller signals error
|
||||
}
|
||||
|
||||
} while(true);
|
||||
}
|
||||
};
|
||||
|
||||
void CommonBase::_parse(const std::string& req)
|
||||
{
|
||||
throw std::logic_error("Not implemented");
|
||||
PVRParser(*this, req.c_str()).parse();
|
||||
}
|
||||
|
||||
Value CommonBase::_build()
|
||||
Value CommonBase::_build() const
|
||||
{
|
||||
if(!req) {
|
||||
using namespace pvxs::members;
|
||||
|
||||
+5
-2
@@ -201,6 +201,7 @@ private:
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
struct PVRParser;
|
||||
|
||||
class PVXS_API CommonBase {
|
||||
protected:
|
||||
@@ -218,7 +219,9 @@ protected:
|
||||
void _field(const std::string& s);
|
||||
void _record(const std::string& key, const void* value, StoreType vtype);
|
||||
void _parse(const std::string& req);
|
||||
Value _build();
|
||||
Value _build() const;
|
||||
|
||||
friend struct PVRParser;
|
||||
};
|
||||
|
||||
//! Options common to all operations
|
||||
@@ -296,7 +299,7 @@ GetBuilder Context::info(const std::string& name) { return GetBuilder{pvt, name,
|
||||
GetBuilder Context::get(const std::string& name) { return GetBuilder{pvt, name, true}; }
|
||||
|
||||
//! Prepare a remote PUT operation
|
||||
class PutBuilder : protected detail::CommonBuilder<GetBuilder> {
|
||||
class PutBuilder : protected detail::CommonBuilder<PutBuilder> {
|
||||
bool _doGet = true;
|
||||
std::function<Value(Value&&)> _builder;
|
||||
std::function<void(Result&&)> _result;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <atomic>
|
||||
|
||||
#include <testMain.h>
|
||||
|
||||
#include <epicsUnitTest.h>
|
||||
|
||||
#include <epicsEvent.h>
|
||||
|
||||
#include <pvxs/unittest.h>
|
||||
#include <pvxs/log.h>
|
||||
#include <pvxs/client.h>
|
||||
#include "utilpvt.h"
|
||||
|
||||
namespace {
|
||||
using namespace pvxs;
|
||||
|
||||
struct TestBuilder : client::detail::CommonBuilder<TestBuilder>
|
||||
{
|
||||
TestBuilder()
|
||||
:client::detail::CommonBuilder<TestBuilder>(nullptr, "")
|
||||
{}
|
||||
|
||||
Value makeReq() const {
|
||||
return _build();
|
||||
}
|
||||
};
|
||||
|
||||
void testEmpty()
|
||||
{
|
||||
testShow()<<__func__;
|
||||
|
||||
auto req = TestBuilder().makeReq();
|
||||
|
||||
testShow()<<req;
|
||||
testEq(std::string(SB()<<"\n"<<req), R"out(
|
||||
struct {
|
||||
struct {
|
||||
} field
|
||||
}
|
||||
)out");
|
||||
}
|
||||
|
||||
void testAssemble()
|
||||
{
|
||||
testShow()<<__func__;
|
||||
|
||||
auto req = TestBuilder()
|
||||
.field("foo")
|
||||
.field("bar.baz")
|
||||
.record("abc", "xyz")
|
||||
.record("pipeline", true)
|
||||
.makeReq();
|
||||
|
||||
testShow()<<req;
|
||||
testEq(std::string(SB()<<"\n"<<req), R"out(
|
||||
struct {
|
||||
struct {
|
||||
struct {
|
||||
} foo
|
||||
struct {
|
||||
struct {
|
||||
} baz
|
||||
} bar
|
||||
} fields
|
||||
struct {
|
||||
struct {
|
||||
string abc = "xyz"
|
||||
bool pipeline = true
|
||||
} _options
|
||||
} record
|
||||
}
|
||||
)out");
|
||||
}
|
||||
|
||||
void testParse1()
|
||||
{
|
||||
testShow()<<__func__;
|
||||
|
||||
auto req = TestBuilder()
|
||||
.pvRequest("field(foo)field(bar.baz)record[abc=xyz]record[pipeline=true]")
|
||||
.makeReq();
|
||||
|
||||
testShow()<<req;
|
||||
testEq(std::string(SB()<<"\n"<<req), R"out(
|
||||
struct {
|
||||
struct {
|
||||
struct {
|
||||
} foo
|
||||
struct {
|
||||
struct {
|
||||
} baz
|
||||
} bar
|
||||
} fields
|
||||
struct {
|
||||
struct {
|
||||
string abc = "xyz"
|
||||
string pipeline = "true"
|
||||
} _options
|
||||
} record
|
||||
}
|
||||
)out");
|
||||
}
|
||||
|
||||
void testParse2()
|
||||
{
|
||||
testShow()<<__func__;
|
||||
|
||||
auto req = TestBuilder()
|
||||
.pvRequest("field(foo,bar.baz)record[abc=xyz,pipeline=true]")
|
||||
.makeReq();
|
||||
|
||||
testShow()<<req;
|
||||
testEq(std::string(SB()<<"\n"<<req), R"out(
|
||||
struct {
|
||||
struct {
|
||||
struct {
|
||||
} foo
|
||||
struct {
|
||||
struct {
|
||||
} baz
|
||||
} bar
|
||||
} fields
|
||||
struct {
|
||||
struct {
|
||||
string abc = "xyz"
|
||||
string pipeline = "true"
|
||||
} _options
|
||||
} record
|
||||
}
|
||||
)out");
|
||||
}
|
||||
|
||||
void testValid()
|
||||
{
|
||||
testShow()<<__func__;
|
||||
|
||||
std::vector<std::string> 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)<<pvr<<"\n"<<TestBuilder().pvRequest(pvr).makeReq();
|
||||
}catch(std::exception& e){
|
||||
testCase(false)<<pvr<<" : "<<typeid(e).name()<<" : "<<e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void testError()
|
||||
{
|
||||
testShow()<<__func__;
|
||||
|
||||
std::vector<std::string> errors({
|
||||
"field(",
|
||||
"field(value",
|
||||
"field(value,alarm",
|
||||
"field[]",
|
||||
"record()",
|
||||
"field(!@#)",
|
||||
"record[",
|
||||
"record[key",
|
||||
"record[key=",
|
||||
"record[key=]",
|
||||
"record[,]",
|
||||
});
|
||||
|
||||
for(auto& pvr : errors) {
|
||||
|
||||
testThrows<std::runtime_error>([&pvr](){
|
||||
auto req = TestBuilder()
|
||||
.pvRequest(pvr)
|
||||
.makeReq();
|
||||
testShow()<<req;
|
||||
})<<pvr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MAIN(testpvreq)
|
||||
{
|
||||
testPlan(22);
|
||||
logger_config_env();
|
||||
testEmpty();
|
||||
testAssemble();
|
||||
testParse1();
|
||||
testParse2();
|
||||
testValid();
|
||||
testError();
|
||||
cleanup_for_valgrind();
|
||||
return testDone();
|
||||
}
|
||||
+7
-1
@@ -25,6 +25,7 @@ void usage(const char* argv0)
|
||||
std::cerr<<"Usage: "<<argv0<<" <opts> [pvname ...]\n"
|
||||
"\n"
|
||||
" -h Show this message.\n"
|
||||
" -r <req> pvRequest condition.\n"
|
||||
" -v Make more noise.\n"
|
||||
" -d Shorthand for $PVXS_LOG=\"pvxs.*=DEBUG\". Make a lot of noise.\n"
|
||||
" -w <sec> 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: "<<char(opt)<<std::endl;
|
||||
@@ -79,6 +84,7 @@ int main(int argc, char *argv[])
|
||||
for(auto n : range(optind, argc)) {
|
||||
|
||||
ops.push_back(ctxt.get(argv[n])
|
||||
.pvRequest(request)
|
||||
.result([&argv, n, &remaining, &done](client::Result&& result) {
|
||||
std::cout<<argv[n]<<"\n"<<result();
|
||||
|
||||
|
||||
+7
-1
@@ -25,6 +25,7 @@ void usage(const char* argv0)
|
||||
std::cerr<<"Usage: "<<argv0<<" <opts> <pvname> [ <value> | <fld>=<value> ...]\n"
|
||||
"\n"
|
||||
" -h Show this message.\n"
|
||||
" -r <req> pvRequest condition.\n"
|
||||
" -v Make more noise.\n"
|
||||
" -d Shorthand for $PVXS_LOG=\"pvxs.*=DEBUG\". Make a lot of noise.\n"
|
||||
" -w <sec> 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: "<<char(opt)<<std::endl;
|
||||
@@ -105,6 +110,7 @@ int main(int argc, char *argv[])
|
||||
int ret=2;
|
||||
|
||||
auto op =ctxt.put(pvname)
|
||||
.pvRequest(request)
|
||||
.build([&values](Value&& prototype) -> Value {
|
||||
auto val = std::move(prototype);
|
||||
for(auto& pair : values) {
|
||||
|
||||
Reference in New Issue
Block a user