使用 boost::spirit 解析为复杂结构
Parse into a complex struct with boost::spirit
我有一个用 ABNF 语法规则编码的输入(它是 MEGACO 协议):
!/3 [15.232.33.21]:2134
T=173619123
{
C=230234621
{
PR=9,
MF=ip/187/6/23045241
{
...
},
MF=ip/187/6/23045242
{
我想用boost::spirit把它解析成一个复杂的结构,伪代码:
megaco
{
param1
param2
{
param1
param2
list of param3
param3-1
{
param4
...
}
param3-2
{
...
}
}
}
需要注意的是,语法比较复杂,包含了很多选择、层次和序列。我不确定如何创建一个 boost::spirit 解析器来逐级解码这样的消息,从而在过程中保存必要的值。
我也不确定 boost::spirit 是否适合它。
那我应该怎么做呢?
更新:我要感谢 sehe 提供的出色示例,但主要任务仍然是使用消息中的值填充某些结构。我现在看到的唯一可能的方法是将 BOOST_FUSION_ADAPT_STRUCT 与许多结构一起使用,使用 boost::variant 作为替代,使用 std::vector 作为项目列表。我说得对吗?
具有讽刺意味的是,转到 Appendix B.2 of RFC 3525,并在表面上实现了其中的大部分内容¹,结果发现您的示例代码段无效。
它丢失了 ammParameter
,一旦你有空缺就不是可选的 {
:
那么,修复代码段,这里有一些可以帮助您入门的东西。它只有 850 行代码,因为规范很长:
//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
template <typename It> struct Megaco {
struct Tokens {
Tokens() {
using namespace qi;
auto SafeChar = copy(alnum | char_("-+&!_/'?@^`~*$\()%|."));
auto RestChar = copy(char_(";[]{}:,#<>="));
auto WSP = copy(char_(" \t"));
COMMENT = ';' >> *(SafeChar | RestChar | WSP | '"') >> (eol|eoi);
SEP = +(WSP | COMMENT | eol);
LWSP = *(WSP | COMMENT | eol);
NAME = raw [ alpha >> repeat(0,63) [alnum|'_'] ];
// Note - The double-quote character is not allowed in quotedString.
quotedString = '"' >> *(SafeChar | RestChar | WSP) >> '"';
VALUE = quotedString | lexeme[+SafeChar];
octetString = *("\}" | char_("\x01-\x7C\x7E-\xFF"));
MTPToken=no_case["MTP"];
H221Token=no_case["H221"];
H223Token=no_case["H223"];
H226Token=no_case["H226"];
V18Token=no_case["V18"];
V22Token=no_case["V22"];
V22bisToken=no_case["V22b"];
V32Token=no_case["V32"];
V32bisToken=no_case["V32b"];
V34Token=no_case["V34"];
V76Token=no_case["V76"];
V90Token=no_case["V90"];
V91Token=no_case["V91"];
BOOST_SPIRIT_DEBUG_NODES(
// snipped for Stack Overflow, see Coliru
)
}
protected:
using Token = qi::rule<It>;
Token COMMENT, SEP, LWSP;
qi::rule<It, std::string()> NAME, quotedString, VALUE, octetString;
#define TOK(name,long,short) name##Token{qi::no_case[qi::lit(#long)|#short]},
Token
TOK(Add,Add,A) TOK(Audit,Audit,AT) TOK(AuditCap,AuditCapability,AC) TOK(AuditValue,AuditValue,AV) TOK(Auth,Authentication,AU)
TOK(Bothway,Bothway,BW) TOK(Brief,Brief,BR) TOK(Buffer,Buffer,BF) TOK(Ctx,Context,C) TOK(ContextAudit,ContextAudit,CA)
TOK(DigitMap,DigitMap,DM) TOK(Disconnected,Disconnected,DC) TOK(Delay,Delay,DL) TOK(Duration,Duration,DR) TOK(Embed,Embed,EM)
TOK(Emergency,Emergency,EG) TOK(Error,Error,ER) TOK(EventBuffer,EventBuffer,EB) TOK(Events,Events,E) TOK(Failover,Failover,FL)
TOK(Forced,Forced,FO) TOK(Graceful,Graceful,GR) TOK(HandOff,HandOff,HO) TOK(ImmAckRequired,ImmAckRequired,IA)
TOK(Inactive,Inactive,IN) TOK(Isolate,Isolate,IS) TOK(InSvc,InService,IV) TOK(InterruptByEvent,IntByEvent,IBE)
TOK(InterruptByNewSignalsDescr,IntBySigDescr,IBS) TOK(KeepActive,KeepActive,KA) TOK(Local,Local,L) TOK(LocalControl,LocalControl,O)
TOK(LockStep,LockStep,SP) TOK(Loopback,Loopback,LB) TOK(Media,Media,M) TOK(Megacop,MEGACO,!) TOK(Method,Method,MT)
TOK(MgcId,MgcIdToTry,MG) TOK(Mode,Mode,MO) TOK(Modify,Modify,MF) TOK(Modem,Modem,MD) TOK(Move,Move,MV)
TOK(Mux,Mux,MX) TOK(Notify,Notify,N) TOK(NotifyCompletion,NotifyCompletion,NC) TOK(ObservedEvents,ObservedEvents,OE) TOK(Oneway,Oneway,OW)
TOK(OnOff,OnOff,OO) TOK(OtherReason,OtherReason,OR) TOK(OutOfSvc,OutOfService,OS) TOK(Packages,Packages,PG) TOK(Pending,Pending,PN)
TOK(Priority,Priority,PR) TOK(Profile,Profile,PF) TOK(Reason,Reason,RE) TOK(Recvonly,ReceiveOnly,RC) TOK(Reply,Reply,P)
TOK(Restart,Restart,RS) TOK(Remote,Remote,R) TOK(ReservedGroup,ReservedGroup,RG) TOK(ReservedValue,ReservedValue,RV) TOK(Sendonly,SendOnly,SO)
TOK(Sendrecv,SendReceive,SR) TOK(Services,Services,SV) TOK(ServiceStates,ServiceStates,SI) TOK(ServiceChange,ServiceChange,SC)
TOK(ServiceChangeAddress,ServiceChangeAddress,AD) TOK(SignalList,SignalList,SL) TOK(Signals,Signals,SG) TOK(SignalType,SignalType,SY)
TOK(Stats,Statistics,SA) TOK(Stream,Stream,ST) TOK(Subtract,Subtract,S) TOK(SynchISDN,SynchISDN,SN) TOK(TerminationState,TerminationState,TS)
TOK(Test,Test,TE) TOK(TimeOut,TimeOut,TO) TOK(Topology,Topology,TP) TOK(Trans,Transaction,T) TOK(ResponseAck,TransactionResponseAck,K)
TOK(Version,Version,V) MTPToken, H221Token, H223Token, H226Token, V18Token, V22Token, V22bisToken,
V32Token, V32bisToken, V34Token, V76Token, V90Token, V91Token;
// The values 0x0, 0xFFFFFFFE and 0xFFFFFFFF are reserved.
struct SpecialContexts : qi::symbols<char, uint32_t> {
enum { NULL_, CHOOSE = 0xFFFFFFFEl, ALL = 0xFFFFFFFFl };
SpecialContexts() { this->add
("$", CHOOSE)
("*", ALL)
("-", NULL_);
}
} SpecialContext;
};
struct Parser : Tokens, qi::grammar<It> {
using Tokens::LWSP;
using Tokens::SEP;
using Tokens::SpecialContext;
using Tokens::NAME;
using Tokens::VALUE;
using Tokens::quotedString;
using Tokens::octetString;
Parser() : Parser::base_type(megacoMessage) {
using namespace qi;
auto LBRKT = copy(LWSP >> '{' >> LWSP);
auto RBRKT = copy(LWSP >> '}' >> LWSP);
auto EQUAL = copy(LWSP >> '=' >> LWSP);
auto COMMA = copy(LWSP >> ',' >> LWSP);
auto INEQUAL = copy(LWSP >> char_("><#") >> LWSP);
auto LSBRKT = copy(LWSP >> '[' >> LWSP);
auto RSBRKT = copy(LWSP >> ']' >> LWSP);
auto LPAREN = copy(LWSP >> '(' >> LWSP);
auto RPAREN = copy(LWSP >> ')' >> LWSP);
auto PIPE = copy(LWSP >> '|' >> LWSP);
megacoMessage = LWSP >> -(authenticationHeader >> SEP) >> message;
authenticationHeader
= Tokens::AuthToken
>> EQUAL
>> ("0x" >> repeat(8)[xdigit])
>> ':'
>> ("0x" >> repeat(8)[xdigit])
>> ':'
>> repeat(24,64)[xdigit]
;
message
= Tokens::MegacopToken >> '/' >> Version >> SEP >> mId >> SEP
>> messageBody;
mId
= ((domainAddress | domainName) >> -(':' >> portNumber))
| mtpAddress
| deviceName
;
domainName
= '<' >> alnum >> repeat(0,63)[char_("a-zA-Z0-9.-")] >> '>';
deviceName
= pathNAME.alias();
pathNAME // TODO total lenght limit according to RFC comment
= -lit('*') >> NAME >> *char_("/*a-zA-Z0-9_$") >> -('@' >> pathDomainName);
pathDomainName = raw [(alnum|'*') >> repeat(0,63)[alnum|'-'|'*'|'.'] ];
ContextID = SpecialContext | UINT32;
domainAddress = '[' >> (IPv4address | IPv6address) >> ']';
// RFC2373 contains the definition of IP6Addresses.
IPv6address = hexpart >> - (':' >> IPv4address);
IPv4address = V4hex >> '.' >> V4hex >> '.' >> V4hex >> '.' >> V4hex;
V4hex = qi::uint_parser<uint8_t, 10, 1, 3>{}; // "0".."255"
hexpart = raw [
"::" >> -hexseq
| hexseq >> "::" >> -hexseq
| hexseq
];
hexseq = raw[ uint_parser<uint16_t, 16, 1, 4>{} % ':' ];
portNumber = UINT16;
// TODO constraint checking?
// To octet align the mtpAddress the MSBs shall be encoded as 0s.
// An octet shall be represented by 2 hex digits.
mtpAddress
= Tokens::MTPToken
>> LBRKT
>> uint_parser<uint32_t, 16, 4, 8>{}
>> RBRKT
;
messageBody = errorDescriptor | transactionList;
transactionList = +( transactionRequest | transactionReply |
transactionPending | transactionResponseAck );
//Use of response acks is dependent on underlying transport
transactionPending = Tokens::PendingToken >> EQUAL >> transactionID >> LBRKT >> RBRKT;
transactionResponseAck = Tokens::ResponseAckToken
>> LBRKT >> (transactionAck % COMMA) >> RBRKT;
transactionAck = transactionID | (transactionID >> "-" >> transactionID);
transactionRequest = Tokens::TransToken >> EQUAL >> transactionID >> LBRKT
>> (actionRequest % COMMA) >> RBRKT;
actionRequest = Tokens::CtxToken >> EQUAL >> ContextID >> LBRKT >> ((
contextRequest >> -(COMMA >> commandRequestList))
| commandRequestList) >> RBRKT;
contextRequest = ((contextProperties >> -(COMMA >> contextAudit))
| contextAudit);
contextProperties = contextProperty % COMMA;
// at-most-once
contextProperty = (topologyDescriptor | priority | Tokens::EmergencyToken);
contextAudit = Tokens::ContextAuditToken
>> LBRKT >> (contextAuditProperties % COMMA) >> RBRKT;
// at-most-once
contextAuditProperties = ( Tokens::TopologyToken | Tokens::EmergencyToken | Tokens::PriorityToken );
// "O-" indicates an optional command
// "W-" indicates a wildcarded response to a command
commandRequestList = -lit("O-") >> -lit("W-") >> commandRequest
>> *(COMMA >> -lit("O-") >> -lit("W-") >> commandRequest);
commandRequest = ( ammRequest | subtractRequest | auditRequest |
notifyRequest | serviceChangeRequest);
transactionReply = Tokens::ReplyToken >> EQUAL >> transactionID >> LBRKT
>> -( Tokens::ImmAckRequiredToken >> COMMA)
>> ( errorDescriptor | actionReplyList ) >> RBRKT;
actionReplyList = actionReply % COMMA ;
actionReply = Tokens::CtxToken >> EQUAL >> ContextID >> LBRKT
>> ( ( errorDescriptor | commandReply ) |
(commandReply >> COMMA >> errorDescriptor) ) >> RBRKT;
commandReply = (( contextProperties >> -(COMMA >> commandReplyList) ) |
commandReplyList );
commandReplyList = commandReplys % COMMA ;
commandReplys = (serviceChangeReply | auditReply | ammsReply |
notifyReply );
//Add Move and Modify have the same request parameters
ammRequest = (Tokens::AddToken | Tokens::MoveToken | Tokens::ModifyToken ) >> EQUAL
>> TerminationID >>
-(LBRKT >> (ammParameter % COMMA) >> RBRKT);
//at-most-once
ammParameter = (mediaDescriptor | modemDescriptor |
muxDescriptor | eventsDescriptor |
signalsDescriptor | digitMapDescriptor |
eventBufferDescriptor | auditDescriptor);
ammsReply = (Tokens::AddToken | Tokens::MoveToken | Tokens::ModifyToken |
Tokens::SubtractToken ) >> EQUAL >> TerminationID >> -( LBRKT
>> terminationAudit >> RBRKT );
subtractRequest = Tokens::SubtractToken >> EQUAL >> TerminationID
>> -( LBRKT >> auditDescriptor >> RBRKT);
auditRequest = (Tokens::AuditValueToken | Tokens::AuditCapToken ) >> EQUAL
>> TerminationID >> LBRKT >> auditDescriptor >> RBRKT;
auditReply = (Tokens::AuditValueToken | Tokens::AuditCapToken )
>> ( contextTerminationAudit | auditOther);
auditOther = EQUAL >> TerminationID >> -(LBRKT >> terminationAudit >> RBRKT);
terminationAudit = auditReturnParameter % COMMA;
contextTerminationAudit = EQUAL >> Tokens::CtxToken >> ( terminationIDList |
LBRKT >> errorDescriptor >> RBRKT );
auditReturnParameter = (mediaDescriptor | modemDescriptor |
muxDescriptor | eventsDescriptor |
signalsDescriptor | digitMapDescriptor |
observedEventsDescriptor | eventBufferDescriptor |
statisticsDescriptor | packagesDescriptor |
errorDescriptor | auditItem);
auditDescriptor = Tokens::AuditToken >> LBRKT >> -( auditItem % COMMA ) >> RBRKT;
notifyRequest = Tokens::NotifyToken >> EQUAL >> TerminationID
>> LBRKT >> ( observedEventsDescriptor
>> -( COMMA >> errorDescriptor ) ) >> RBRKT;
notifyReply = Tokens::NotifyToken >> EQUAL >> TerminationID
>> -( LBRKT >> errorDescriptor >> RBRKT );
serviceChangeRequest = Tokens::ServiceChangeToken >> EQUAL >> TerminationID
>> LBRKT >> serviceChangeDescriptor >> RBRKT;
serviceChangeReply = Tokens::ServiceChangeToken >> EQUAL >> TerminationID
>> -(LBRKT >> (errorDescriptor | serviceChangeReplyDescriptor) >> RBRKT);
errorDescriptor = Tokens::ErrorToken >> EQUAL >> ErrorCode
>> LBRKT >> -quotedString >> RBRKT;
ErrorCode = repeat(1,4)[digit]; // could be extended
transactionID = UINT32;
// OTHER STUFF, DESCRIPTORS
terminationIDList = LBRKT >> (TerminationID % COMMA) >> RBRKT;
TerminationID = "ROOT" | pathNAME | "$" | "*";
mediaDescriptor = Tokens::MediaToken >> LBRKT >> (mediaParm % COMMA) >> RBRKT;
// at-most one terminationStateDescriptor
// and either streamParm(s) or streamDescriptor(s) but not both
mediaParm = (streamParm | streamDescriptor | terminationStateDescriptor);
// at-most-once per item
streamParm = (localDescriptor | remoteDescriptor | localControlDescriptor);
streamDescriptor = Tokens::StreamToken >> EQUAL >> StreamID
>> LBRKT >> streamParm >> *(COMMA >> streamParm) >> RBRKT;
localControlDescriptor = Tokens::LocalControlToken
>> LBRKT >> localParm >> *(COMMA >> localParm) >> RBRKT;
// at-most-once per item except for propertyParm
localParm = (streamMode | propertyParm | reservedValueMode | reservedGroupMode);
reservedValueMode = Tokens::ReservedValueToken >> EQUAL >> ( lit("ON") | "OFF" );
reservedGroupMode = Tokens::ReservedGroupToken >> EQUAL >> ( lit("ON") | "OFF" );
streamMode = Tokens::ModeToken >> EQUAL >> streamModes;
streamModes = (Tokens::SendonlyToken | Tokens::RecvonlyToken | Tokens::SendrecvToken |
Tokens::InactiveToken | Tokens::LoopbackToken );
propertyParm = pkgdName >> parmValue;
parmValue = (EQUAL >> alternativeValue | INEQUAL >> VALUE);
alternativeValue = ( VALUE
| LSBRKT >> (VALUE % COMMA) >> RSBRKT // sublist (i.e., A AND B AND ...)
| LBRKT >> (VALUE % COMMA) >> RBRKT // alternatives (i.e., A OR B OR ...)
| LSBRKT >> VALUE >> ':' >> VALUE >> RSBRKT ) // range
;
// Note - The octet zero is not among the permitted characters in
// octet string. As the current definition is limited to SDP, and a
// zero octet would not be a legal character in SDP, this is not a
// concern.
localDescriptor = Tokens::LocalToken >> LBRKT >> octetString >> RBRKT;
remoteDescriptor = Tokens::RemoteToken >> LBRKT >> octetString >> RBRKT;
eventBufferDescriptor= Tokens::EventBufferToken >> -( LBRKT >> eventSpec >> *( COMMA >> eventSpec) >> RBRKT );
eventSpec = pkgdName >> -( LBRKT >> (eventSpecParameter % COMMA) >> RBRKT );
eventSpecParameter = (eventStream | eventOther);
eventBufferControl = Tokens::BufferToken >> EQUAL >> ( "OFF" | Tokens::LockStepToken );
terminationStateDescriptor = Tokens::TerminationStateToken >> LBRKT
>> terminationStateParm >> *( COMMA >> terminationStateParm ) >> RBRKT;
// at-most-once per item except for propertyParm
terminationStateParm = (propertyParm | serviceStates | eventBufferControl);
serviceStates = Tokens::ServiceStatesToken >> EQUAL >> ( Tokens::TestToken | Tokens::OutOfSvcToken | Tokens::InSvcToken );
muxDescriptor = Tokens::MuxToken >> EQUAL >> MuxType >> terminationIDList;
MuxType = ( Tokens::H221Token | Tokens::H223Token | Tokens::H226Token | Tokens::V76Token
| extensionParameter );
StreamID = UINT16;
pkgdName = (PackageName >> '/' >> ItemID) //specific item
| (PackageName >> "/*") //all items in package
| "*/*" // all items supported by the MG
;
PackageName = NAME.alias();
ItemID = NAME.alias();
eventsDescriptor = Tokens::EventsToken >> -( EQUAL >> RequestID >> LBRKT >> requestedEvent >> *( COMMA >> requestedEvent ) >> RBRKT );
requestedEvent = pkgdName >> -( LBRKT >> eventParameter >> *( COMMA >> eventParameter ) >> RBRKT );
// at-most-once each of KeepActiveToken , eventDM and eventStream
//at most one of either embedWithSig or embedNoSig but not both
//KeepActiveToken and embedWithSig must not both be present
eventParameter = ( embedWithSig | embedNoSig | Tokens::KeepActiveToken | eventDM | eventStream | eventOther );
embedWithSig = Tokens::EmbedToken >> LBRKT >> signalsDescriptor
>> -(COMMA >> embedFirst ) >> RBRKT;
embedNoSig = Tokens::EmbedToken >> LBRKT >> embedFirst >> RBRKT;
// at-most-once of each
embedFirst = Tokens::EventsToken >> -( EQUAL >> RequestID >> LBRKT >> (secondRequestedEvent % COMMA) >> RBRKT );
secondRequestedEvent = pkgdName >> -( LBRKT >> secondEventParameter >> *( COMMA >> secondEventParameter ) >> RBRKT );
// at-most-once each of embedSig , KeepActiveToken, eventDM or
// eventStream
// KeepActiveToken and embedSig must not both be present
secondEventParameter = ( embedSig | Tokens::KeepActiveToken | eventDM |
eventStream | eventOther );
embedSig = Tokens::EmbedToken >> LBRKT >> signalsDescriptor >> RBRKT;
eventStream = Tokens::StreamToken >> EQUAL >> StreamID;
eventOther = eventParameterName >> parmValue;
eventParameterName = NAME.alias();
eventDM = Tokens::DigitMapToken >> EQUAL >> ( digitMapName |
(LBRKT >> digitMapValue >> RBRKT ));
signalsDescriptor = Tokens::SignalsToken >> LBRKT >> -( signalParm % COMMA) >> RBRKT;
signalParm = signalList | signalRequest;
signalRequest = signalName >> -( LBRKT >> (sigParameter % COMMA) >> RBRKT );
signalList = Tokens::SignalListToken >> EQUAL >> signalListId >> LBRKT
>> (signalListParm % COMMA) >> RBRKT;
signalListId = UINT16;
//exactly once signalType, at most once duration and every signal
//parameter
signalListParm = signalRequest.alias();
signalName = pkgdName.alias();
//at-most-once sigStream, at-most-once sigSignalType,
//at-most-once sigDuration, every signalParameterName at most once
sigParameter = sigStream | sigSignalType | sigDuration | sigOther
| notifyCompletion | Tokens::KeepActiveToken;
sigStream = Tokens::StreamToken >> EQUAL >> StreamID;
sigOther = sigParameterName >> parmValue;
sigParameterName = NAME.alias();
sigSignalType = Tokens::SignalTypeToken >> EQUAL >> signalType;
signalType = (Tokens::OnOffToken | Tokens::TimeOutToken | Tokens::BriefToken);
sigDuration = Tokens::DurationToken >> EQUAL >> UINT16;
notifyCompletion = Tokens::NotifyCompletionToken >> EQUAL >> (LBRKT
>> (notificationReason % COMMA) >> RBRKT);
notificationReason = ( Tokens::TimeOutToken | Tokens::InterruptByEventToken
| Tokens::InterruptByNewSignalsDescrToken
| Tokens::OtherReasonToken );
observedEventsDescriptor = Tokens::ObservedEventsToken >> EQUAL >> RequestID
>> LBRKT >> (observedEvent % COMMA) >> RBRKT;
//time per event, because it might be buffered
observedEvent = -( TimeStamp >> LWSP >> ':') >> LWSP
>> pkgdName >> -( LBRKT >> (observedEventParameter % COMMA) >> RBRKT );
//at-most-once eventStream, every eventParameterName at most once
observedEventParameter = eventStream | eventOther;
// For an AuditCapReply with all events, the RequestID should be ALL.
RequestID = ( UINT32 | "*" );
modemDescriptor = Tokens::ModemToken >> (( EQUAL >> modemType) |
(LSBRKT >> (modemType % COMMA) >> RSBRKT))
>> -( LBRKT >> (propertyParm % COMMA) >> RBRKT );
// at-most-once except for extensionParameter
modemType = (Tokens::V32bisToken | Tokens::V22bisToken | Tokens::V18Token |
Tokens::V22Token | Tokens::V32Token | Tokens::V34Token | Tokens::V90Token |
Tokens::V91Token | Tokens::SynchISDNToken | extensionParameter);
digitMapDescriptor = Tokens::DigitMapToken >> EQUAL
>> ( ( LBRKT >> digitMapValue >> RBRKT ) |
(digitMapName >> -( LBRKT >> digitMapValue >> RBRKT )) );
digitMapName = NAME.alias();
digitMapValue = -("T:" >> Timer >> COMMA) >> -("S:" >> Timer >> COMMA)
>> -("L:" >> Timer >> COMMA) >> digitMap;
Timer = repeat(1,2)[digit];
// Units are seconds for T, S, and L timers, and hundreds of
// milliseconds for Z timer. Thus T, S, and L range from 1 to 99
// seconds and Z from 100 ms to 9.9 s
digitMap = (digitString |
LPAREN >> digitStringList >> RPAREN);
digitStringList = digitString >> *( PIPE >> digitString );
digitString = +digitStringElement;
digitStringElement = digitPosition >> -lit('.');
digitPosition = digitMapLetter | digitMapRange;
digitMapRange = ("x" | (LSBRKT >> digitLetter >> RSBRKT));
digitLetter = *((digit >> "-" >> digit ) | digitMapLetter);
digitMapLetter = char_("0-9" //Basic event symbols
"a-kA-K"
"LS" // Inter-event timers (long, short)
"Z" //Long duration modifier
);
//at-most-once, and DigitMapToken and PackagesToken are not allowed
//in AuditCapabilities command
auditItem = ( Tokens::MuxToken | Tokens::ModemToken | Tokens::MediaToken |
Tokens::SignalsToken | Tokens::EventBufferToken |
Tokens::DigitMapToken | Tokens::StatsToken | Tokens::EventsToken |
Tokens::ObservedEventsToken | Tokens::PackagesToken );
serviceChangeDescriptor = Tokens::ServicesToken
>> LBRKT >> serviceChangeParm >> *(COMMA >> serviceChangeParm) >> RBRKT;
// each parameter at-most-once
// at most one of either serviceChangeAddress or serviceChangeMgcId
// but not both
// serviceChangeMethod and serviceChangeReason are REQUIRED
serviceChangeParm = (serviceChangeMethod | serviceChangeReason |
serviceChangeDelay | serviceChangeAddress |
serviceChangeProfile | extension | TimeStamp |
serviceChangeMgcId | serviceChangeVersion );
serviceChangeReplyDescriptor = Tokens::ServicesToken >> LBRKT
>> (servChgReplyParm % COMMA) >> RBRKT;
// at-most-once. Version is REQUIRED on first ServiceChange response
// at most one of either serviceChangeAddress or serviceChangeMgcId
// but not both
servChgReplyParm = (serviceChangeAddress | serviceChangeMgcId |
serviceChangeProfile | serviceChangeVersion |
TimeStamp);
serviceChangeMethod = Tokens::MethodToken >> EQUAL >> (Tokens::FailoverToken |
Tokens::ForcedToken | Tokens::GracefulToken | Tokens::RestartToken |
Tokens::DisconnectedToken | Tokens::HandOffToken |
extensionParameter);
// A serviceChangeReason consists of a numeric reason code
// and an optional text description.
// A serviceChangeReason MUST be encoded using the quotedString
// form of VALUE.
// The quotedString SHALL contain a decimal reason code,
// optionally followed by a single space character and a
// textual description string.
serviceChangeReason = Tokens::ReasonToken >> EQUAL >> VALUE;
serviceChangeDelay = Tokens::DelayToken >> EQUAL >> UINT32;
serviceChangeAddress = Tokens::ServiceChangeAddressToken >> EQUAL >> ( mId |
portNumber );
serviceChangeMgcId = Tokens::MgcIdToken >> EQUAL >> mId;
serviceChangeProfile = Tokens::ProfileToken >> EQUAL >> NAME >> '/' >> Version;
serviceChangeVersion = Tokens::VersionToken >> EQUAL >> Version;
extension = extensionParameter >> parmValue;
packagesDescriptor = Tokens::PackagesToken
>> LBRKT >> packagesItem >> *(COMMA >> packagesItem) >> RBRKT;
packagesItem = NAME >> "-" >> UINT16;
TimeStamp = Date >> "T" >> Time; // per ISO 8601:1988
// Date = yyyymmdd
Date = repeat(8)[digit];
// Time = hhmmssss
Time = repeat(8)[digit];
statisticsDescriptor = Tokens::StatsToken
>> LBRKT >> statisticsParameter >> *(COMMA >> statisticsParameter ) >> RBRKT;
//at-most-once per item
statisticsParameter = pkgdName >> -(EQUAL >> VALUE);
topologyDescriptor = Tokens::TopologyToken
>> LBRKT >> topologyTriple >> *(COMMA >> topologyTriple) >> RBRKT;
topologyTriple = terminationA >> COMMA >>
terminationB >> COMMA >> topologyDirection;
terminationA = TerminationID.alias();
terminationB = TerminationID.alias();
topologyDirection = Tokens::BothwayToken | Tokens::IsolateToken | Tokens::OnewayToken;
priority = Tokens::PriorityToken >> EQUAL >> UINT16;
extensionParameter = "X" >> char_("-+") >> repeat(1,6)[alnum];
BOOST_SPIRIT_DEBUG_NODES(
// snipped for Stack Overflow, see Coliru
)
}
private:
qi::rule<It> megacoMessage, message;
qi::rule<It, uint32_t()> mtpAddress, ContextID;
qi::rule<It, std::string()> mId, domainName, deviceName, pathNAME, domainAddress, pathDomainName,
IPv4address, IPv6address, hexpart, hexseq;
qi::rule<It, uint16_t()> portNumber;
qi::rule<It, uint8_t()> V4hex;
// implicit lexemes (no implicit whitespace allowed):
qi::rule<It> authenticationHeader;
qi::uint_parser<int, 10, 1, 2> Version;
qi::uint_parser<uint32_t, 10, 1, 10> UINT32;
qi::uint_parser<uint16_t, 10, 1, 5> UINT16;
// message payload
qi::rule<It> messageBody,
transactionList, transactionPending, transactionResponseAck, transactionAck, transactionRequest,
actionRequest,
contextRequest, contextProperties, contextProperty, contextAudit, contextAuditProperties,
commandRequestList, commandRequest,
transactionReply,
actionReplyList, actionReply, commandReply,
commandReplyList, commandReplys,
ammRequest, ammParameter, ammsReply,
subtractRequest,
auditRequest, auditReply, auditOther,
terminationAudit,
contextTerminationAudit,
auditReturnParameter, auditDescriptor, notifyRequest,
notifyReply,
serviceChangeRequest, serviceChangeReply,
errorDescriptor, ErrorCode, transactionID;
// OTHER STUFF, DESCRIPTORS
qi::rule<It>
terminationIDList,
TerminationID, mediaDescriptor,
mediaParm, streamParm,
streamDescriptor, localControlDescriptor, localParm,
reservedValueMode, reservedGroupMode,
streamMode, streamModes,
propertyParm, parmValue, alternativeValue,
localDescriptor, remoteDescriptor,
eventBufferDescriptor, eventSpec, eventSpecParameter, eventBufferControl,
terminationStateDescriptor, terminationStateParm,
serviceStates,
muxDescriptor, MuxType,
StreamID,
pkgdName, PackageName,
ItemID,
eventsDescriptor, requestedEvent, eventParameter,
embedWithSig, embedNoSig, embedFirst,
secondRequestedEvent, secondEventParameter,
embedSig,
eventStream, eventOther, eventParameterName, eventDM,
signalsDescriptor, signalParm, signalRequest, signalList, signalListId, signalListParm, signalName,
sigParameter, sigStream, sigOther, sigParameterName, sigSignalType, signalType, sigDuration,
notifyCompletion, notificationReason,
observedEventsDescriptor, observedEvent, observedEventParameter,
RequestID,
modemDescriptor, modemType,
digitMapDescriptor, digitMapName, digitMapValue,
Timer,
digitMap, digitStringList, digitString, digitStringElement, digitPosition, digitMapRange, digitLetter, digitMapLetter,
auditItem,
serviceChangeDescriptor, serviceChangeParm, serviceChangeReplyDescriptor,
servChgReplyParm,
serviceChangeMethod, serviceChangeReason, serviceChangeDelay, serviceChangeAddress, serviceChangeMgcId, serviceChangeProfile, serviceChangeVersion,
extension,
packagesDescriptor,
packagesItem,
TimeStamp, Date, Time,
statisticsDescriptor, statisticsParameter,
topologyDescriptor, topologyTriple,
terminationA, terminationB,
topologyDirection,
priority,
extensionParameter;
};
};
int main() {
std::string const sample = R"(
!/3 [15.232.33.21]:2134
T=173619123
{
C=230234621
{
PR=9,
MF=ip/187/6/23045241
{
MD=V90
},
MF=ip/187/6/23045242
{
MD=V90
}
}
})";
using It = std::string::const_iterator;
Megaco<It>::Parser parser;
It f = sample.begin(), l = sample.end();
bool ok = qi::parse(f, l, parser);
if (ok)
std::cout << "Parse success\n";
else
std::cout << "Parse failed\n";
if (f != l)
std::cout << "Remaining unparsed input: '" << std::string(f,l) << "'\n";
}
版画
Parse success
¹ 即大部分产品的愚蠢转换,特别是几乎没有属性处理并且非常繁重(例如调制解调器类型等没有 symbols<>
)
我有一个用 ABNF 语法规则编码的输入(它是 MEGACO 协议):
!/3 [15.232.33.21]:2134
T=173619123
{
C=230234621
{
PR=9,
MF=ip/187/6/23045241
{
...
},
MF=ip/187/6/23045242
{
我想用boost::spirit把它解析成一个复杂的结构,伪代码:
megaco
{
param1
param2
{
param1
param2
list of param3
param3-1
{
param4
...
}
param3-2
{
...
}
}
}
需要注意的是,语法比较复杂,包含了很多选择、层次和序列。我不确定如何创建一个 boost::spirit 解析器来逐级解码这样的消息,从而在过程中保存必要的值。
我也不确定 boost::spirit 是否适合它。
那我应该怎么做呢?
更新:我要感谢 sehe 提供的出色示例,但主要任务仍然是使用消息中的值填充某些结构。我现在看到的唯一可能的方法是将 BOOST_FUSION_ADAPT_STRUCT 与许多结构一起使用,使用 boost::variant 作为替代,使用 std::vector 作为项目列表。我说得对吗?
具有讽刺意味的是,转到 Appendix B.2 of RFC 3525,并在表面上实现了其中的大部分内容¹,结果发现您的示例代码段无效。
它丢失了 ammParameter
,一旦你有空缺就不是可选的 {
:
那么,修复代码段,这里有一些可以帮助您入门的东西。它只有 850 行代码,因为规范很长:
//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
template <typename It> struct Megaco {
struct Tokens {
Tokens() {
using namespace qi;
auto SafeChar = copy(alnum | char_("-+&!_/'?@^`~*$\()%|."));
auto RestChar = copy(char_(";[]{}:,#<>="));
auto WSP = copy(char_(" \t"));
COMMENT = ';' >> *(SafeChar | RestChar | WSP | '"') >> (eol|eoi);
SEP = +(WSP | COMMENT | eol);
LWSP = *(WSP | COMMENT | eol);
NAME = raw [ alpha >> repeat(0,63) [alnum|'_'] ];
// Note - The double-quote character is not allowed in quotedString.
quotedString = '"' >> *(SafeChar | RestChar | WSP) >> '"';
VALUE = quotedString | lexeme[+SafeChar];
octetString = *("\}" | char_("\x01-\x7C\x7E-\xFF"));
MTPToken=no_case["MTP"];
H221Token=no_case["H221"];
H223Token=no_case["H223"];
H226Token=no_case["H226"];
V18Token=no_case["V18"];
V22Token=no_case["V22"];
V22bisToken=no_case["V22b"];
V32Token=no_case["V32"];
V32bisToken=no_case["V32b"];
V34Token=no_case["V34"];
V76Token=no_case["V76"];
V90Token=no_case["V90"];
V91Token=no_case["V91"];
BOOST_SPIRIT_DEBUG_NODES(
// snipped for Stack Overflow, see Coliru
)
}
protected:
using Token = qi::rule<It>;
Token COMMENT, SEP, LWSP;
qi::rule<It, std::string()> NAME, quotedString, VALUE, octetString;
#define TOK(name,long,short) name##Token{qi::no_case[qi::lit(#long)|#short]},
Token
TOK(Add,Add,A) TOK(Audit,Audit,AT) TOK(AuditCap,AuditCapability,AC) TOK(AuditValue,AuditValue,AV) TOK(Auth,Authentication,AU)
TOK(Bothway,Bothway,BW) TOK(Brief,Brief,BR) TOK(Buffer,Buffer,BF) TOK(Ctx,Context,C) TOK(ContextAudit,ContextAudit,CA)
TOK(DigitMap,DigitMap,DM) TOK(Disconnected,Disconnected,DC) TOK(Delay,Delay,DL) TOK(Duration,Duration,DR) TOK(Embed,Embed,EM)
TOK(Emergency,Emergency,EG) TOK(Error,Error,ER) TOK(EventBuffer,EventBuffer,EB) TOK(Events,Events,E) TOK(Failover,Failover,FL)
TOK(Forced,Forced,FO) TOK(Graceful,Graceful,GR) TOK(HandOff,HandOff,HO) TOK(ImmAckRequired,ImmAckRequired,IA)
TOK(Inactive,Inactive,IN) TOK(Isolate,Isolate,IS) TOK(InSvc,InService,IV) TOK(InterruptByEvent,IntByEvent,IBE)
TOK(InterruptByNewSignalsDescr,IntBySigDescr,IBS) TOK(KeepActive,KeepActive,KA) TOK(Local,Local,L) TOK(LocalControl,LocalControl,O)
TOK(LockStep,LockStep,SP) TOK(Loopback,Loopback,LB) TOK(Media,Media,M) TOK(Megacop,MEGACO,!) TOK(Method,Method,MT)
TOK(MgcId,MgcIdToTry,MG) TOK(Mode,Mode,MO) TOK(Modify,Modify,MF) TOK(Modem,Modem,MD) TOK(Move,Move,MV)
TOK(Mux,Mux,MX) TOK(Notify,Notify,N) TOK(NotifyCompletion,NotifyCompletion,NC) TOK(ObservedEvents,ObservedEvents,OE) TOK(Oneway,Oneway,OW)
TOK(OnOff,OnOff,OO) TOK(OtherReason,OtherReason,OR) TOK(OutOfSvc,OutOfService,OS) TOK(Packages,Packages,PG) TOK(Pending,Pending,PN)
TOK(Priority,Priority,PR) TOK(Profile,Profile,PF) TOK(Reason,Reason,RE) TOK(Recvonly,ReceiveOnly,RC) TOK(Reply,Reply,P)
TOK(Restart,Restart,RS) TOK(Remote,Remote,R) TOK(ReservedGroup,ReservedGroup,RG) TOK(ReservedValue,ReservedValue,RV) TOK(Sendonly,SendOnly,SO)
TOK(Sendrecv,SendReceive,SR) TOK(Services,Services,SV) TOK(ServiceStates,ServiceStates,SI) TOK(ServiceChange,ServiceChange,SC)
TOK(ServiceChangeAddress,ServiceChangeAddress,AD) TOK(SignalList,SignalList,SL) TOK(Signals,Signals,SG) TOK(SignalType,SignalType,SY)
TOK(Stats,Statistics,SA) TOK(Stream,Stream,ST) TOK(Subtract,Subtract,S) TOK(SynchISDN,SynchISDN,SN) TOK(TerminationState,TerminationState,TS)
TOK(Test,Test,TE) TOK(TimeOut,TimeOut,TO) TOK(Topology,Topology,TP) TOK(Trans,Transaction,T) TOK(ResponseAck,TransactionResponseAck,K)
TOK(Version,Version,V) MTPToken, H221Token, H223Token, H226Token, V18Token, V22Token, V22bisToken,
V32Token, V32bisToken, V34Token, V76Token, V90Token, V91Token;
// The values 0x0, 0xFFFFFFFE and 0xFFFFFFFF are reserved.
struct SpecialContexts : qi::symbols<char, uint32_t> {
enum { NULL_, CHOOSE = 0xFFFFFFFEl, ALL = 0xFFFFFFFFl };
SpecialContexts() { this->add
("$", CHOOSE)
("*", ALL)
("-", NULL_);
}
} SpecialContext;
};
struct Parser : Tokens, qi::grammar<It> {
using Tokens::LWSP;
using Tokens::SEP;
using Tokens::SpecialContext;
using Tokens::NAME;
using Tokens::VALUE;
using Tokens::quotedString;
using Tokens::octetString;
Parser() : Parser::base_type(megacoMessage) {
using namespace qi;
auto LBRKT = copy(LWSP >> '{' >> LWSP);
auto RBRKT = copy(LWSP >> '}' >> LWSP);
auto EQUAL = copy(LWSP >> '=' >> LWSP);
auto COMMA = copy(LWSP >> ',' >> LWSP);
auto INEQUAL = copy(LWSP >> char_("><#") >> LWSP);
auto LSBRKT = copy(LWSP >> '[' >> LWSP);
auto RSBRKT = copy(LWSP >> ']' >> LWSP);
auto LPAREN = copy(LWSP >> '(' >> LWSP);
auto RPAREN = copy(LWSP >> ')' >> LWSP);
auto PIPE = copy(LWSP >> '|' >> LWSP);
megacoMessage = LWSP >> -(authenticationHeader >> SEP) >> message;
authenticationHeader
= Tokens::AuthToken
>> EQUAL
>> ("0x" >> repeat(8)[xdigit])
>> ':'
>> ("0x" >> repeat(8)[xdigit])
>> ':'
>> repeat(24,64)[xdigit]
;
message
= Tokens::MegacopToken >> '/' >> Version >> SEP >> mId >> SEP
>> messageBody;
mId
= ((domainAddress | domainName) >> -(':' >> portNumber))
| mtpAddress
| deviceName
;
domainName
= '<' >> alnum >> repeat(0,63)[char_("a-zA-Z0-9.-")] >> '>';
deviceName
= pathNAME.alias();
pathNAME // TODO total lenght limit according to RFC comment
= -lit('*') >> NAME >> *char_("/*a-zA-Z0-9_$") >> -('@' >> pathDomainName);
pathDomainName = raw [(alnum|'*') >> repeat(0,63)[alnum|'-'|'*'|'.'] ];
ContextID = SpecialContext | UINT32;
domainAddress = '[' >> (IPv4address | IPv6address) >> ']';
// RFC2373 contains the definition of IP6Addresses.
IPv6address = hexpart >> - (':' >> IPv4address);
IPv4address = V4hex >> '.' >> V4hex >> '.' >> V4hex >> '.' >> V4hex;
V4hex = qi::uint_parser<uint8_t, 10, 1, 3>{}; // "0".."255"
hexpart = raw [
"::" >> -hexseq
| hexseq >> "::" >> -hexseq
| hexseq
];
hexseq = raw[ uint_parser<uint16_t, 16, 1, 4>{} % ':' ];
portNumber = UINT16;
// TODO constraint checking?
// To octet align the mtpAddress the MSBs shall be encoded as 0s.
// An octet shall be represented by 2 hex digits.
mtpAddress
= Tokens::MTPToken
>> LBRKT
>> uint_parser<uint32_t, 16, 4, 8>{}
>> RBRKT
;
messageBody = errorDescriptor | transactionList;
transactionList = +( transactionRequest | transactionReply |
transactionPending | transactionResponseAck );
//Use of response acks is dependent on underlying transport
transactionPending = Tokens::PendingToken >> EQUAL >> transactionID >> LBRKT >> RBRKT;
transactionResponseAck = Tokens::ResponseAckToken
>> LBRKT >> (transactionAck % COMMA) >> RBRKT;
transactionAck = transactionID | (transactionID >> "-" >> transactionID);
transactionRequest = Tokens::TransToken >> EQUAL >> transactionID >> LBRKT
>> (actionRequest % COMMA) >> RBRKT;
actionRequest = Tokens::CtxToken >> EQUAL >> ContextID >> LBRKT >> ((
contextRequest >> -(COMMA >> commandRequestList))
| commandRequestList) >> RBRKT;
contextRequest = ((contextProperties >> -(COMMA >> contextAudit))
| contextAudit);
contextProperties = contextProperty % COMMA;
// at-most-once
contextProperty = (topologyDescriptor | priority | Tokens::EmergencyToken);
contextAudit = Tokens::ContextAuditToken
>> LBRKT >> (contextAuditProperties % COMMA) >> RBRKT;
// at-most-once
contextAuditProperties = ( Tokens::TopologyToken | Tokens::EmergencyToken | Tokens::PriorityToken );
// "O-" indicates an optional command
// "W-" indicates a wildcarded response to a command
commandRequestList = -lit("O-") >> -lit("W-") >> commandRequest
>> *(COMMA >> -lit("O-") >> -lit("W-") >> commandRequest);
commandRequest = ( ammRequest | subtractRequest | auditRequest |
notifyRequest | serviceChangeRequest);
transactionReply = Tokens::ReplyToken >> EQUAL >> transactionID >> LBRKT
>> -( Tokens::ImmAckRequiredToken >> COMMA)
>> ( errorDescriptor | actionReplyList ) >> RBRKT;
actionReplyList = actionReply % COMMA ;
actionReply = Tokens::CtxToken >> EQUAL >> ContextID >> LBRKT
>> ( ( errorDescriptor | commandReply ) |
(commandReply >> COMMA >> errorDescriptor) ) >> RBRKT;
commandReply = (( contextProperties >> -(COMMA >> commandReplyList) ) |
commandReplyList );
commandReplyList = commandReplys % COMMA ;
commandReplys = (serviceChangeReply | auditReply | ammsReply |
notifyReply );
//Add Move and Modify have the same request parameters
ammRequest = (Tokens::AddToken | Tokens::MoveToken | Tokens::ModifyToken ) >> EQUAL
>> TerminationID >>
-(LBRKT >> (ammParameter % COMMA) >> RBRKT);
//at-most-once
ammParameter = (mediaDescriptor | modemDescriptor |
muxDescriptor | eventsDescriptor |
signalsDescriptor | digitMapDescriptor |
eventBufferDescriptor | auditDescriptor);
ammsReply = (Tokens::AddToken | Tokens::MoveToken | Tokens::ModifyToken |
Tokens::SubtractToken ) >> EQUAL >> TerminationID >> -( LBRKT
>> terminationAudit >> RBRKT );
subtractRequest = Tokens::SubtractToken >> EQUAL >> TerminationID
>> -( LBRKT >> auditDescriptor >> RBRKT);
auditRequest = (Tokens::AuditValueToken | Tokens::AuditCapToken ) >> EQUAL
>> TerminationID >> LBRKT >> auditDescriptor >> RBRKT;
auditReply = (Tokens::AuditValueToken | Tokens::AuditCapToken )
>> ( contextTerminationAudit | auditOther);
auditOther = EQUAL >> TerminationID >> -(LBRKT >> terminationAudit >> RBRKT);
terminationAudit = auditReturnParameter % COMMA;
contextTerminationAudit = EQUAL >> Tokens::CtxToken >> ( terminationIDList |
LBRKT >> errorDescriptor >> RBRKT );
auditReturnParameter = (mediaDescriptor | modemDescriptor |
muxDescriptor | eventsDescriptor |
signalsDescriptor | digitMapDescriptor |
observedEventsDescriptor | eventBufferDescriptor |
statisticsDescriptor | packagesDescriptor |
errorDescriptor | auditItem);
auditDescriptor = Tokens::AuditToken >> LBRKT >> -( auditItem % COMMA ) >> RBRKT;
notifyRequest = Tokens::NotifyToken >> EQUAL >> TerminationID
>> LBRKT >> ( observedEventsDescriptor
>> -( COMMA >> errorDescriptor ) ) >> RBRKT;
notifyReply = Tokens::NotifyToken >> EQUAL >> TerminationID
>> -( LBRKT >> errorDescriptor >> RBRKT );
serviceChangeRequest = Tokens::ServiceChangeToken >> EQUAL >> TerminationID
>> LBRKT >> serviceChangeDescriptor >> RBRKT;
serviceChangeReply = Tokens::ServiceChangeToken >> EQUAL >> TerminationID
>> -(LBRKT >> (errorDescriptor | serviceChangeReplyDescriptor) >> RBRKT);
errorDescriptor = Tokens::ErrorToken >> EQUAL >> ErrorCode
>> LBRKT >> -quotedString >> RBRKT;
ErrorCode = repeat(1,4)[digit]; // could be extended
transactionID = UINT32;
// OTHER STUFF, DESCRIPTORS
terminationIDList = LBRKT >> (TerminationID % COMMA) >> RBRKT;
TerminationID = "ROOT" | pathNAME | "$" | "*";
mediaDescriptor = Tokens::MediaToken >> LBRKT >> (mediaParm % COMMA) >> RBRKT;
// at-most one terminationStateDescriptor
// and either streamParm(s) or streamDescriptor(s) but not both
mediaParm = (streamParm | streamDescriptor | terminationStateDescriptor);
// at-most-once per item
streamParm = (localDescriptor | remoteDescriptor | localControlDescriptor);
streamDescriptor = Tokens::StreamToken >> EQUAL >> StreamID
>> LBRKT >> streamParm >> *(COMMA >> streamParm) >> RBRKT;
localControlDescriptor = Tokens::LocalControlToken
>> LBRKT >> localParm >> *(COMMA >> localParm) >> RBRKT;
// at-most-once per item except for propertyParm
localParm = (streamMode | propertyParm | reservedValueMode | reservedGroupMode);
reservedValueMode = Tokens::ReservedValueToken >> EQUAL >> ( lit("ON") | "OFF" );
reservedGroupMode = Tokens::ReservedGroupToken >> EQUAL >> ( lit("ON") | "OFF" );
streamMode = Tokens::ModeToken >> EQUAL >> streamModes;
streamModes = (Tokens::SendonlyToken | Tokens::RecvonlyToken | Tokens::SendrecvToken |
Tokens::InactiveToken | Tokens::LoopbackToken );
propertyParm = pkgdName >> parmValue;
parmValue = (EQUAL >> alternativeValue | INEQUAL >> VALUE);
alternativeValue = ( VALUE
| LSBRKT >> (VALUE % COMMA) >> RSBRKT // sublist (i.e., A AND B AND ...)
| LBRKT >> (VALUE % COMMA) >> RBRKT // alternatives (i.e., A OR B OR ...)
| LSBRKT >> VALUE >> ':' >> VALUE >> RSBRKT ) // range
;
// Note - The octet zero is not among the permitted characters in
// octet string. As the current definition is limited to SDP, and a
// zero octet would not be a legal character in SDP, this is not a
// concern.
localDescriptor = Tokens::LocalToken >> LBRKT >> octetString >> RBRKT;
remoteDescriptor = Tokens::RemoteToken >> LBRKT >> octetString >> RBRKT;
eventBufferDescriptor= Tokens::EventBufferToken >> -( LBRKT >> eventSpec >> *( COMMA >> eventSpec) >> RBRKT );
eventSpec = pkgdName >> -( LBRKT >> (eventSpecParameter % COMMA) >> RBRKT );
eventSpecParameter = (eventStream | eventOther);
eventBufferControl = Tokens::BufferToken >> EQUAL >> ( "OFF" | Tokens::LockStepToken );
terminationStateDescriptor = Tokens::TerminationStateToken >> LBRKT
>> terminationStateParm >> *( COMMA >> terminationStateParm ) >> RBRKT;
// at-most-once per item except for propertyParm
terminationStateParm = (propertyParm | serviceStates | eventBufferControl);
serviceStates = Tokens::ServiceStatesToken >> EQUAL >> ( Tokens::TestToken | Tokens::OutOfSvcToken | Tokens::InSvcToken );
muxDescriptor = Tokens::MuxToken >> EQUAL >> MuxType >> terminationIDList;
MuxType = ( Tokens::H221Token | Tokens::H223Token | Tokens::H226Token | Tokens::V76Token
| extensionParameter );
StreamID = UINT16;
pkgdName = (PackageName >> '/' >> ItemID) //specific item
| (PackageName >> "/*") //all items in package
| "*/*" // all items supported by the MG
;
PackageName = NAME.alias();
ItemID = NAME.alias();
eventsDescriptor = Tokens::EventsToken >> -( EQUAL >> RequestID >> LBRKT >> requestedEvent >> *( COMMA >> requestedEvent ) >> RBRKT );
requestedEvent = pkgdName >> -( LBRKT >> eventParameter >> *( COMMA >> eventParameter ) >> RBRKT );
// at-most-once each of KeepActiveToken , eventDM and eventStream
//at most one of either embedWithSig or embedNoSig but not both
//KeepActiveToken and embedWithSig must not both be present
eventParameter = ( embedWithSig | embedNoSig | Tokens::KeepActiveToken | eventDM | eventStream | eventOther );
embedWithSig = Tokens::EmbedToken >> LBRKT >> signalsDescriptor
>> -(COMMA >> embedFirst ) >> RBRKT;
embedNoSig = Tokens::EmbedToken >> LBRKT >> embedFirst >> RBRKT;
// at-most-once of each
embedFirst = Tokens::EventsToken >> -( EQUAL >> RequestID >> LBRKT >> (secondRequestedEvent % COMMA) >> RBRKT );
secondRequestedEvent = pkgdName >> -( LBRKT >> secondEventParameter >> *( COMMA >> secondEventParameter ) >> RBRKT );
// at-most-once each of embedSig , KeepActiveToken, eventDM or
// eventStream
// KeepActiveToken and embedSig must not both be present
secondEventParameter = ( embedSig | Tokens::KeepActiveToken | eventDM |
eventStream | eventOther );
embedSig = Tokens::EmbedToken >> LBRKT >> signalsDescriptor >> RBRKT;
eventStream = Tokens::StreamToken >> EQUAL >> StreamID;
eventOther = eventParameterName >> parmValue;
eventParameterName = NAME.alias();
eventDM = Tokens::DigitMapToken >> EQUAL >> ( digitMapName |
(LBRKT >> digitMapValue >> RBRKT ));
signalsDescriptor = Tokens::SignalsToken >> LBRKT >> -( signalParm % COMMA) >> RBRKT;
signalParm = signalList | signalRequest;
signalRequest = signalName >> -( LBRKT >> (sigParameter % COMMA) >> RBRKT );
signalList = Tokens::SignalListToken >> EQUAL >> signalListId >> LBRKT
>> (signalListParm % COMMA) >> RBRKT;
signalListId = UINT16;
//exactly once signalType, at most once duration and every signal
//parameter
signalListParm = signalRequest.alias();
signalName = pkgdName.alias();
//at-most-once sigStream, at-most-once sigSignalType,
//at-most-once sigDuration, every signalParameterName at most once
sigParameter = sigStream | sigSignalType | sigDuration | sigOther
| notifyCompletion | Tokens::KeepActiveToken;
sigStream = Tokens::StreamToken >> EQUAL >> StreamID;
sigOther = sigParameterName >> parmValue;
sigParameterName = NAME.alias();
sigSignalType = Tokens::SignalTypeToken >> EQUAL >> signalType;
signalType = (Tokens::OnOffToken | Tokens::TimeOutToken | Tokens::BriefToken);
sigDuration = Tokens::DurationToken >> EQUAL >> UINT16;
notifyCompletion = Tokens::NotifyCompletionToken >> EQUAL >> (LBRKT
>> (notificationReason % COMMA) >> RBRKT);
notificationReason = ( Tokens::TimeOutToken | Tokens::InterruptByEventToken
| Tokens::InterruptByNewSignalsDescrToken
| Tokens::OtherReasonToken );
observedEventsDescriptor = Tokens::ObservedEventsToken >> EQUAL >> RequestID
>> LBRKT >> (observedEvent % COMMA) >> RBRKT;
//time per event, because it might be buffered
observedEvent = -( TimeStamp >> LWSP >> ':') >> LWSP
>> pkgdName >> -( LBRKT >> (observedEventParameter % COMMA) >> RBRKT );
//at-most-once eventStream, every eventParameterName at most once
observedEventParameter = eventStream | eventOther;
// For an AuditCapReply with all events, the RequestID should be ALL.
RequestID = ( UINT32 | "*" );
modemDescriptor = Tokens::ModemToken >> (( EQUAL >> modemType) |
(LSBRKT >> (modemType % COMMA) >> RSBRKT))
>> -( LBRKT >> (propertyParm % COMMA) >> RBRKT );
// at-most-once except for extensionParameter
modemType = (Tokens::V32bisToken | Tokens::V22bisToken | Tokens::V18Token |
Tokens::V22Token | Tokens::V32Token | Tokens::V34Token | Tokens::V90Token |
Tokens::V91Token | Tokens::SynchISDNToken | extensionParameter);
digitMapDescriptor = Tokens::DigitMapToken >> EQUAL
>> ( ( LBRKT >> digitMapValue >> RBRKT ) |
(digitMapName >> -( LBRKT >> digitMapValue >> RBRKT )) );
digitMapName = NAME.alias();
digitMapValue = -("T:" >> Timer >> COMMA) >> -("S:" >> Timer >> COMMA)
>> -("L:" >> Timer >> COMMA) >> digitMap;
Timer = repeat(1,2)[digit];
// Units are seconds for T, S, and L timers, and hundreds of
// milliseconds for Z timer. Thus T, S, and L range from 1 to 99
// seconds and Z from 100 ms to 9.9 s
digitMap = (digitString |
LPAREN >> digitStringList >> RPAREN);
digitStringList = digitString >> *( PIPE >> digitString );
digitString = +digitStringElement;
digitStringElement = digitPosition >> -lit('.');
digitPosition = digitMapLetter | digitMapRange;
digitMapRange = ("x" | (LSBRKT >> digitLetter >> RSBRKT));
digitLetter = *((digit >> "-" >> digit ) | digitMapLetter);
digitMapLetter = char_("0-9" //Basic event symbols
"a-kA-K"
"LS" // Inter-event timers (long, short)
"Z" //Long duration modifier
);
//at-most-once, and DigitMapToken and PackagesToken are not allowed
//in AuditCapabilities command
auditItem = ( Tokens::MuxToken | Tokens::ModemToken | Tokens::MediaToken |
Tokens::SignalsToken | Tokens::EventBufferToken |
Tokens::DigitMapToken | Tokens::StatsToken | Tokens::EventsToken |
Tokens::ObservedEventsToken | Tokens::PackagesToken );
serviceChangeDescriptor = Tokens::ServicesToken
>> LBRKT >> serviceChangeParm >> *(COMMA >> serviceChangeParm) >> RBRKT;
// each parameter at-most-once
// at most one of either serviceChangeAddress or serviceChangeMgcId
// but not both
// serviceChangeMethod and serviceChangeReason are REQUIRED
serviceChangeParm = (serviceChangeMethod | serviceChangeReason |
serviceChangeDelay | serviceChangeAddress |
serviceChangeProfile | extension | TimeStamp |
serviceChangeMgcId | serviceChangeVersion );
serviceChangeReplyDescriptor = Tokens::ServicesToken >> LBRKT
>> (servChgReplyParm % COMMA) >> RBRKT;
// at-most-once. Version is REQUIRED on first ServiceChange response
// at most one of either serviceChangeAddress or serviceChangeMgcId
// but not both
servChgReplyParm = (serviceChangeAddress | serviceChangeMgcId |
serviceChangeProfile | serviceChangeVersion |
TimeStamp);
serviceChangeMethod = Tokens::MethodToken >> EQUAL >> (Tokens::FailoverToken |
Tokens::ForcedToken | Tokens::GracefulToken | Tokens::RestartToken |
Tokens::DisconnectedToken | Tokens::HandOffToken |
extensionParameter);
// A serviceChangeReason consists of a numeric reason code
// and an optional text description.
// A serviceChangeReason MUST be encoded using the quotedString
// form of VALUE.
// The quotedString SHALL contain a decimal reason code,
// optionally followed by a single space character and a
// textual description string.
serviceChangeReason = Tokens::ReasonToken >> EQUAL >> VALUE;
serviceChangeDelay = Tokens::DelayToken >> EQUAL >> UINT32;
serviceChangeAddress = Tokens::ServiceChangeAddressToken >> EQUAL >> ( mId |
portNumber );
serviceChangeMgcId = Tokens::MgcIdToken >> EQUAL >> mId;
serviceChangeProfile = Tokens::ProfileToken >> EQUAL >> NAME >> '/' >> Version;
serviceChangeVersion = Tokens::VersionToken >> EQUAL >> Version;
extension = extensionParameter >> parmValue;
packagesDescriptor = Tokens::PackagesToken
>> LBRKT >> packagesItem >> *(COMMA >> packagesItem) >> RBRKT;
packagesItem = NAME >> "-" >> UINT16;
TimeStamp = Date >> "T" >> Time; // per ISO 8601:1988
// Date = yyyymmdd
Date = repeat(8)[digit];
// Time = hhmmssss
Time = repeat(8)[digit];
statisticsDescriptor = Tokens::StatsToken
>> LBRKT >> statisticsParameter >> *(COMMA >> statisticsParameter ) >> RBRKT;
//at-most-once per item
statisticsParameter = pkgdName >> -(EQUAL >> VALUE);
topologyDescriptor = Tokens::TopologyToken
>> LBRKT >> topologyTriple >> *(COMMA >> topologyTriple) >> RBRKT;
topologyTriple = terminationA >> COMMA >>
terminationB >> COMMA >> topologyDirection;
terminationA = TerminationID.alias();
terminationB = TerminationID.alias();
topologyDirection = Tokens::BothwayToken | Tokens::IsolateToken | Tokens::OnewayToken;
priority = Tokens::PriorityToken >> EQUAL >> UINT16;
extensionParameter = "X" >> char_("-+") >> repeat(1,6)[alnum];
BOOST_SPIRIT_DEBUG_NODES(
// snipped for Stack Overflow, see Coliru
)
}
private:
qi::rule<It> megacoMessage, message;
qi::rule<It, uint32_t()> mtpAddress, ContextID;
qi::rule<It, std::string()> mId, domainName, deviceName, pathNAME, domainAddress, pathDomainName,
IPv4address, IPv6address, hexpart, hexseq;
qi::rule<It, uint16_t()> portNumber;
qi::rule<It, uint8_t()> V4hex;
// implicit lexemes (no implicit whitespace allowed):
qi::rule<It> authenticationHeader;
qi::uint_parser<int, 10, 1, 2> Version;
qi::uint_parser<uint32_t, 10, 1, 10> UINT32;
qi::uint_parser<uint16_t, 10, 1, 5> UINT16;
// message payload
qi::rule<It> messageBody,
transactionList, transactionPending, transactionResponseAck, transactionAck, transactionRequest,
actionRequest,
contextRequest, contextProperties, contextProperty, contextAudit, contextAuditProperties,
commandRequestList, commandRequest,
transactionReply,
actionReplyList, actionReply, commandReply,
commandReplyList, commandReplys,
ammRequest, ammParameter, ammsReply,
subtractRequest,
auditRequest, auditReply, auditOther,
terminationAudit,
contextTerminationAudit,
auditReturnParameter, auditDescriptor, notifyRequest,
notifyReply,
serviceChangeRequest, serviceChangeReply,
errorDescriptor, ErrorCode, transactionID;
// OTHER STUFF, DESCRIPTORS
qi::rule<It>
terminationIDList,
TerminationID, mediaDescriptor,
mediaParm, streamParm,
streamDescriptor, localControlDescriptor, localParm,
reservedValueMode, reservedGroupMode,
streamMode, streamModes,
propertyParm, parmValue, alternativeValue,
localDescriptor, remoteDescriptor,
eventBufferDescriptor, eventSpec, eventSpecParameter, eventBufferControl,
terminationStateDescriptor, terminationStateParm,
serviceStates,
muxDescriptor, MuxType,
StreamID,
pkgdName, PackageName,
ItemID,
eventsDescriptor, requestedEvent, eventParameter,
embedWithSig, embedNoSig, embedFirst,
secondRequestedEvent, secondEventParameter,
embedSig,
eventStream, eventOther, eventParameterName, eventDM,
signalsDescriptor, signalParm, signalRequest, signalList, signalListId, signalListParm, signalName,
sigParameter, sigStream, sigOther, sigParameterName, sigSignalType, signalType, sigDuration,
notifyCompletion, notificationReason,
observedEventsDescriptor, observedEvent, observedEventParameter,
RequestID,
modemDescriptor, modemType,
digitMapDescriptor, digitMapName, digitMapValue,
Timer,
digitMap, digitStringList, digitString, digitStringElement, digitPosition, digitMapRange, digitLetter, digitMapLetter,
auditItem,
serviceChangeDescriptor, serviceChangeParm, serviceChangeReplyDescriptor,
servChgReplyParm,
serviceChangeMethod, serviceChangeReason, serviceChangeDelay, serviceChangeAddress, serviceChangeMgcId, serviceChangeProfile, serviceChangeVersion,
extension,
packagesDescriptor,
packagesItem,
TimeStamp, Date, Time,
statisticsDescriptor, statisticsParameter,
topologyDescriptor, topologyTriple,
terminationA, terminationB,
topologyDirection,
priority,
extensionParameter;
};
};
int main() {
std::string const sample = R"(
!/3 [15.232.33.21]:2134
T=173619123
{
C=230234621
{
PR=9,
MF=ip/187/6/23045241
{
MD=V90
},
MF=ip/187/6/23045242
{
MD=V90
}
}
})";
using It = std::string::const_iterator;
Megaco<It>::Parser parser;
It f = sample.begin(), l = sample.end();
bool ok = qi::parse(f, l, parser);
if (ok)
std::cout << "Parse success\n";
else
std::cout << "Parse failed\n";
if (f != l)
std::cout << "Remaining unparsed input: '" << std::string(f,l) << "'\n";
}
版画
Parse success
¹ 即大部分产品的愚蠢转换,特别是几乎没有属性处理并且非常繁重(例如调制解调器类型等没有 symbols<>
)