pvRequest parsing

This commit is contained in:
Michael Davidsaver
2020-02-28 15:33:26 -08:00
parent 62ba8ceafe
commit 4d7ca87f8a
6 changed files with 420 additions and 6 deletions
+191 -2
View File
@@ -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
View File
@@ -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;
+4
View File
@@ -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
+206
View File
@@ -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
View File
@@ -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
View File
@@ -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) {