C++ 使用boost::spirit解析为复杂结构

C++ 使用boost::spirit解析为复杂结构,c++,boost-spirit,abnf,C++,Boost Spirit,Abnf,我有一个用ABNF语法规则编码的输入(它是MEGACO协议): 我想用boost::spirit将其解析为一个复杂的结构,伪代码: megaco { param1 param2 { param1 param2 list of param3 param3-1 { param4 ... }

我有一个用ABNF语法规则编码的输入(它是MEGACO协议):

我想用boost::spirit将其解析为一个复杂的结构,伪代码:

megaco
{
    param1
    param2
    {
        param1
        param2
        list of param3
            param3-1
            {
                param4
                ...
            }
            param3-2
            {
                ...
            }
    }
}
应该注意的是,语法是复杂的,包含了许多备选方案、级别和顺序。我不知道如何创建boost::spirit解析器来逐级解码这样的消息,从而在过程中保存必要的值

此外,我也不确定boost::spirit是否是合适的工具

那我该怎么做呢


更新:我想感谢sehe提供了一个很好的例子,不过主要任务还是用消息中的值填充一些结构。我现在看到的唯一可能的方法是将BOOST\u FUSION\u ADAPT\u STRUCT用于许多结构,使用BOOST::variant作为替代,std::vector作为项目列表。我说得对吗?

具有讽刺意味的是,你的示例代码片段是无效的,你现在要去实现它的大部分

缺少
参数
,一旦打开
{
,该参数就不是可选的:

因此,修复代码段,这里有一些东西可以让您开始。由于规范很长,所以只有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";
}


也就是说,大部分产品都是哑转换,特别是几乎没有属性处理,而且非常重(例如,modemtypes没有
符号等)

好吧。这里有一个“第一个”。我无法发布我的答案,因为代码太长。在旁注上,现在只需再刮除966个字符,为什么不使用现有的库?我不得不使用
TOK
宏进行一些丑陋的压缩,以使代码符合30000个字符的限制。请参阅。以获得预期的代码。成功提取了SPIRIT\u调试科里鲁:你真是太好了,但我不知道如何用提取的值填充输出结构?我看到你的答案越多,我就越相信你是个十足的疯子。你太好了。@NikolayKovalenko这里有一个例子,它连接了ASTs,为你的示例演示提供了足够的规则。我制作了mistak基于AST的想法会给我一个速度提升。相反,它们被证明是笨拙的(令人惊讶),并且很难映射到语法产品。如果我再次这样做,我会直接匹配语法(比较
PropertyParm
e.g.)。输出:
//#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