在 x3 中动态切换符号表

Dynamically switching symbol tables in x3

鉴于以下 x3 语法正确解析,我想添加参数、限定符和属性的验证。这似乎表明某种方法可以动态切换在各种规则中使用的符号 table。实现这个的最佳方法是什么?它似乎是语义动作和属性的某种混合体,但我不清楚是如何混合的。

#include <string>
#include <vector>
#include <iostream>
#include <iomanip>
#include <map>

#include <boost/config/warning_disable.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/variant.hpp>
#include <boost/fusion/adapted/struct.hpp>

namespace x3 = boost::spirit::x3;

namespace scl
    {

    //
    // This will take a symbol value and return the string associated with that value. From an example by sehe
    // TODO: There is probably a better C++14/17 way to do this with the symbol.for_each operator and a lambda,  
    // but I haven't figured it out yet
    //

    template <typename T>
    struct SYMBOL_LOOKUP
        {
        SYMBOL_LOOKUP (T Symbol, std::string& String) : _sought (Symbol), _found (String)
            {
            }

        void operator () (std::basic_string <char> s, T ct)
            {

            if (_sought == ct)
                {
                _found = s;
                }

            }

        std::string         found () const { return _found; }

        private:
            T               _sought;
            std::string&    _found;
        };


    //
    // This section describes the valid verbs, the parameters that are valid for each verb, and
    // the qualifiers that are valid for each verb or parameter of a verb. 
    // TODO: There is probably some complicated C++11/14/17 expression template for generating all 
    // of this as a set of linked tables, where each verb points to a parameter table, which points
    // to a qualifier table, but that is currently beyond my ability to implement, so each structure 
    // is implemented discretely
    //

    //
    // Legal verbs
    //

    enum class VERBS
        {
        load,                                           //
        set,                                            //
        show,                                           //
        };

    struct VALID_VERBS : x3::symbols <VERBS>
        {

        VALID_VERBS ()
            {
            add
                ("load", VERBS::load)                   //
                ("set", VERBS::set)                     //
                ("show", VERBS::show)                   //
            ;
            }

        } const valid_verbs;

    //
    // LOAD parameter 1
    //

    enum class LOAD_PARAMETER1
        {
        dll,                                            // LOAD DLL <file-spec>
        pdb,                                            // LOAD PDB <file-spec>
        };

    struct VALID_LOAD_PARAMETER1 : x3::symbols <LOAD_PARAMETER1>
        {

        VALID_LOAD_PARAMETER1 ()
            {
            add
                ("dll", LOAD_PARAMETER1::dll)           //
                ("pdb", LOAD_PARAMETER1::pdb)           //
            ;
            }

        } const valid_load_parameter1;

    //
    // SET parameter 1
    //

    enum class SET_PARAMETER1
        {
        debug,                                          // SET DEBUG {/ON | /OFF}
        trace,                                          // SET TRACE {/ON | OFF}
        };

    struct VALID_SET_PARAMETER1 : x3::symbols <SET_PARAMETER1>
        {

        VALID_SET_PARAMETER1 ()
            {
            add
                ("debug", SET_PARAMETER1::debug)        //
                ("trace", SET_PARAMETER1::trace)        //
            ;
            }

        } const valid_set_parameter1;

    //
    // SET qualifiers
    //

    enum class SET_QUALIFIERS
        {
        off,                                            //
        on                                              //
        };

    struct VALID_SET_QUALIFIERS : x3::symbols <SET_QUALIFIERS>
        {

        VALID_SET_QUALIFIERS ()
            {
            add
                ("off", SET_QUALIFIERS::off)            //
                ("on", SET_QUALIFIERS::on)              //
            ;
            }

        } const valid_set_qualifiers;

    //
    // SHOW parameter 1
    //

    enum class SHOW_PARAMETER1
        {
        debug,                                          // SHOW DEBUG
        module,                                         // SHOW MODULE <wildcard-expression> [/SYMBOLS]
        symbols,                                        // SHOW SYMBOLS *{/ALL /FULL /OUT=<file-spec> /TYPE=(+{all,exports,imports})} [wild-card-expression]
        trace,                                          // SHOW TRACE
        };

    struct VALID_SHOW_PARAMETER1 : x3::symbols <SHOW_PARAMETER1>
        {

        VALID_SHOW_PARAMETER1 ()
            {
            add
                ("debug", SHOW_PARAMETER1::debug)       //
                ("module", SHOW_PARAMETER1::module)     //
                ("symbols", SHOW_PARAMETER1::symbols)   //
                ("trace", SHOW_PARAMETER1::trace)       //
                ;
            }

        } const valid_show_parameter1;

    //
    // SHOW qualifiers
    //

    enum class SHOW_QUALIFIERS
        {
        all,                                            // Display all objects of the specified type
        full,                                           // Display all information about the specified object(s)
        out,                                            // Write output to the specified file (/out=<file spec>)
        type,                                           // List of properties to display
        };

    struct VALID_SHOW_QUALIFIERS : x3::symbols <SHOW_QUALIFIERS>
        {

        VALID_SHOW_QUALIFIERS ()
            {
            add
                ("all", SHOW_QUALIFIERS::all)           //
                ("full", SHOW_QUALIFIERS::full)         //
                ("out", SHOW_QUALIFIERS::out)           //
                ("type", SHOW_QUALIFIERS::type)         // Valid properties in VALID_SHOW_TYPE_PROPERTIES
                ;
            }

        } const valid_show_qualifiers;

    //
    // SHOW /TYPE=(property_list)
    //

    enum class SHOW_TYPE_PROPERTIES
        {
        all,                                                //
        exports,                                            //
        imports,                                            //
        };

    struct VALID_SHOW_TYPE_PROPERTIES : x3::symbols <SHOW_TYPE_PROPERTIES>
        {

        VALID_SHOW_TYPE_PROPERTIES ()
            {
            add
                ("all", SHOW_TYPE_PROPERTIES::all)              //
                ("exports", SHOW_TYPE_PROPERTIES::exports)      //
                ("imports", SHOW_TYPE_PROPERTIES::imports)      //
                ;
            }

        } const valid_show_type_properties;

    //
    // Convert a verb value to its string representation
    //

    std::string to_string (const VERBS Verb)

        {
        std::string             result;
        SYMBOL_LOOKUP <VERBS>   lookup (Verb, result);


        //
        // Loop through all the entries in the symbol table looking for the specified value
        // Is there a better way to use this for_each with a lambda?
        //

        valid_verbs.for_each (lookup);

        return result;
        }   // End to_string

    }   // End namespace scl

namespace scl_ast
    {

    struct KEYWORD : std::string
        {
        using std::string::string;
        using std::string::operator=;
        };

    struct NIL
        {
        };

    using VALUE = boost::variant <NIL, std::string, int, double, KEYWORD>;

    struct PROPERTY
        {
        KEYWORD     name;
        VALUE       value;
        };

    struct QUALIFIER
        {
        enum KIND 
            {
            positive, 
            negative
            }                   kind;

        std::string             identifier;
        std::vector <PROPERTY>  properties;
        };

    struct PARAMETER
        {
        KEYWORD                 keyword;
        std::vector <QUALIFIER> qualifiers;
        };

    struct COMMAND
        {
        scl::VERBS              verb;
        std::vector <QUALIFIER> qualifiers;
        std::vector <PARAMETER> parameters;
        };

    //
    // Overloads for printing the AST to the console
    //
#pragma region debug

    static inline std::ostream& operator<< (std::ostream& os, VALUE const& v)
        {
        struct
            {
            std::ostream&   _os;
            void operator() (std::string const& s)  const { _os << std::quoted (s); }
            void operator() (int i)                 const { _os << i; }
            void operator() (double d)              const { _os << d; }
            void operator() (KEYWORD const& kwv)    const { _os << kwv; }
            void operator() (NIL)                   const { }
            } vis { os };

        boost::apply_visitor (vis, v);
        return os;
        }

    static inline std::ostream& operator<< (std::ostream& os, PROPERTY const& prop)
        {
        os << prop.name;

        if (prop.value.which ())
            {
            os << "=" << prop.value;
            }

        return os;
        }

    static inline std::ostream& operator<< (std::ostream& os, QUALIFIER const& q)
        {
        os << "/" << (q.kind == QUALIFIER::negative ? "no" : "") << q.identifier;

        if (!q.properties.empty ())
            {
            os << "=(";
            }

        for (auto const& prop : q.properties)
            {
            os << prop << " ";
            }

        if (!q.properties.empty ())
            {
            os << ")";
            }

        return os;
        }

    static inline std::ostream& operator<< (std::ostream& os, std::vector <QUALIFIER> const& qualifiers)
        {
        for (auto const& qualifier : qualifiers)
            {
            os << " " << qualifier;
            }

        return os;
        }

    static inline std::ostream& operator<< (std::ostream& os, PARAMETER const& p)
        {
        return os << p.keyword << " " << p.qualifiers;
        }

    static inline std::ostream& operator<< (std::ostream& os, COMMAND const& cmd)
        {
        os << scl::to_string (cmd.verb) << cmd.qualifiers;

        for (auto& param : cmd.parameters)
            {
            os << " " << param;
            }

        return os;
        }
#pragma endregion debug

    };  // End namespace scl_ast

BOOST_FUSION_ADAPT_STRUCT (scl_ast::PROPERTY, name, value);
BOOST_FUSION_ADAPT_STRUCT (scl_ast::QUALIFIER, kind, identifier, properties);
BOOST_FUSION_ADAPT_STRUCT (scl_ast::PARAMETER, keyword, qualifiers);
BOOST_FUSION_ADAPT_STRUCT (scl_ast::COMMAND, verb, qualifiers, parameters);

//
// Grammar for simple command language
//

namespace scl
    {
    using namespace x3;

    auto const  param = rule <struct _keyword, scl_ast::KEYWORD> { "param" }
                    = lexeme [+char_ ("a-zA-Z0-9$_.\*?+-")];

    auto const  identifier
                    = lexeme [+char_ ("a-zA-Z0-9_")];

    auto const  quoted_string 
                    = lexeme ['"' >> *('\' > char_ | ~char_ ('"')) >> '"'];

    auto const  property_value 
                    = quoted_string
                    | real_parser <double, x3::strict_real_policies <double>> {}
                    | int_
                    | param;

    auto const  property = rule <struct _property, scl_ast::PROPERTY> { "property" }
                    = identifier >> -('=' >> property_value);

    auto const  property_list = rule <struct _property_list, std::vector <scl_ast::PROPERTY>> { "property_list" }
                    = '(' >> property % ',' >> ')';

    auto const  qual 
                    = attr (scl_ast::QUALIFIER::positive) >> lexeme ['/' >> identifier] >> -( '=' >> (property_list | repeat (1) [property]));

    auto const  neg_qual
        = attr (scl_ast::QUALIFIER::negative) >> lexeme [no_case ["/no"] >> identifier] >> repeat (0) [property];   // Negated qualifiers never have properties (repeat(0) keeps the compiler happy)

    auto const  qualifier 
                    = neg_qual | qual;

    auto const  verb 
                    = no_case [valid_verbs];        // Uses static list of allowed verbs

    auto const  parameter = rule <struct _parameter, scl_ast::PARAMETER> { "parameter" } 
                    = param >> *qualifier;

    auto const  command = rule <struct _command, scl_ast::COMMAND> { "command" } 
                    = skip (blank) [verb >> *qualifier >> *parameter];

    };  // End namespace scl


int
main ()

{
std::vector <std::string>   input =
    {
    "load dll test.dll",
    "LOAD pdb test.pdb",
    "set debug /on",
    "show debug",
    "SHOW module test.dll/symbols",
    "show symbols/type=export test*",
    "show symbols test.dll/type=(import,export)",
    "show symbols s*/out=s.txt",
    "show symbols /all /full",
    };


    for (auto const& str : input)
        {
        scl_ast::COMMAND    cmd;
        auto                b = str.begin ();
        auto                e = str.end ();


        bool    ok = parse (b, e, scl::command, cmd);
        std::cout << (ok ? "OK" : "FAIL") << '\t' << std::quoted (str) << std::endl;

        if (ok)
            {
            std::cout << " -- Full AST: " << cmd << std::endl;
            std::cout << " -- Verb + Qualifiers: " << scl::to_string (cmd.verb) << cmd.qualifiers << std::endl;

            for (auto const& param : cmd.parameters)
                {
                std::cout << "      -- Parameter + Qualifiers: " << param << std::endl;
                }

            if (b != e)
                {
                std::cout << "*** Remaining unparsed: " << std::quoted (std::string (b, e)) << std::endl;
                }

            }

        std::cout << std::endl;
        }   // End for

    return 0;
}                           // End main

所以,我花了很多时间思考这个问题。

我承认大部分想法都逃不过头脑风暴。然而,我做了一个 proof-of-concept,从头开始,从 /just/ 最低限度开始:

/* Synopsis:
 *
 *    LOAD DLL <file-spec>
 *    LOAD PDB <file-spec>
 *    SET DEBUG {/ON | /OFF}
 *    SET TRACE {/ON | /OFF}
 *
 *    SHOW DEBUG
 *    SHOW MODULE <wildcard-expression> [/SYMBOLS]
 *    SHOW SYMBOLS { [/ALL] [/FULL] [/OUT=<file-spec>] [/TYPE=(+{all,exports,imports})] [wild-card-expression] }...
 *    SHOW TRACE
 */

实用程序

因为我们有几个域可以有一组选项,这些选项将被视为 (case-insensitive) 关键字标识符,我想为这些创建一个工具:

Note: for brevity this keeps all values as int for now. In that, it falls short of "Better Enum". But given a few macros you should be able to make Options::type (and Enum<TagType>) resolve to a proper enum type.

namespace util {
    template <typename Tag> struct FlavouredString : std::string {
        using std::string::string;
        using std::string::operator=;
    };

    template <typename Tag> struct Options {
        using type = int; // TODO typed enums? Requires macro tedium

        std::vector<char const*> _options;
        Options(std::initializer_list<char const*> options) : _options(options) {}
        Options(std::vector<char const*> options) : _options(options) {}

        std::string to_string(type id)              const { return _options.at(id); }
        type         to_id(std::string const& name) const { return find(_options.begin(), _options.end(), name) - _options.begin(); }
    };

    template <typename Tag> using Enum = typename Options<Tag>::type;
    template <typename Tag> struct IcOptions : Options<Tag> { using Options<Tag>::Options; };
}

为了支持我们的 AST 类型,我们将创建这些实用程序的实例,例如:

IcOptions<struct DllPdb>  static const dll_pdb { "DLL",   "PDB"   };
IcOptions<struct Setting> static const setting { "DEBUG", "TRACE" };
IcOptions<struct OnOff>   static const on_off  { "OFF",   "ON"    };
IcOptions<struct SymType> static const sym_type{ "all", "imports", "exports" };

using Wildcard = FlavouredString<struct _Wild>;
using Filespec = FlavouredString<struct _Filespec>;

AST 类型

这与之前的路线完全不同:我没有使用任意数量的 arbitrary-type 参数和值来定义 general-purpose AST,而是选择定义命令 strongly-typed :

namespace ast {

    struct LoadCommand {
        Enum<DllPdb> kind = {};
        Filespec     filespec;
    };

    struct SetCommand {
        Enum<Setting> setting = {};
        Enum<OnOff> value     = {};
    };

    struct ShowSettingCommand {
        Enum<Setting> setting;
    };

    struct ShowModuleCommand {
        Wildcard wildcard;
        bool symbols = false;
    };

    using SymbolTypes = std::vector<Enum<SymType> >; 

    struct ShowSymbolsCommand {
        bool all  = false;
        bool full = false;
        Filespec out;
        SymbolTypes types;
        Wildcard wildcard;
    };

    using Command = boost::variant<
        LoadCommand,
        SetCommand,
        ShowSettingCommand,
        ShowModuleCommand,
        ShowSymbolsCommand
    >;
}

适配如前:

BOOST_FUSION_ADAPT_STRUCT(scl::ast::LoadCommand,        kind, filespec)
BOOST_FUSION_ADAPT_STRUCT(scl::ast::SetCommand,         setting, value)
BOOST_FUSION_ADAPT_STRUCT(scl::ast::ShowSettingCommand, setting)
BOOST_FUSION_ADAPT_STRUCT(scl::ast::ShowModuleCommand,  wildcard, symbols)

Note that ShowSymbolsCommand is not adapted because the rule doesn't follow the struct layout

解析器实用程序

让我们用一些可组合的解析器工厂来支持我们的核心概念:

// (case insensitive) keyword handling
static auto kw        = [](auto p) { return x3::lexeme[p >> !(x3::graph - x3::char_("/=,()"))]; };
static auto ikw       = [](auto p) { return x3::no_case [kw(p)]; };
static auto qualifier = [](auto p) { return x3::lexeme['/' >> ikw(p)]; };

我可以解释这些,但是下面的用法会更清楚。因此,事不宜迟,展示允许我们直接在解析器表达式中使用任何 OptionsCiOptions 实例的技巧:

// Options and CiOptions
namespace util {
    template <typename Tag>
    auto as_spirit_parser(Options<Tag> const& o, bool to_lower = false) {
        x3::symbols<typename Options<Tag>::type> p;
        int n = 0;
        for (std::string el : o._options) {
            if (to_lower) boost::to_lower(el);
            p.add(el, n++);
        }
        return kw(p);
    }

    template <typename Tag>
    auto as_spirit_parser(IcOptions<Tag> const& o) {
        return x3::no_case [ as_spirit_parser(o, true) ];
    }
}

我想这没什么出乎意料的,但它确实允许优雅的规则定义:

规则定义

DEF_RULE(Filespec) = quoted_string | bare_string;
DEF_RULE(Wildcard) = lexeme[+char_("a-zA-Z0-9$_.\*?+-")];

DEF_RULE(LoadCommand)
    = ikw("load") >> ast::dll_pdb >> Filespec;

DEF_RULE(SetCommand)
    = ikw("set") >> ast::setting >> qualifier(ast::on_off);

DEF_RULE(ShowSettingCommand)
    = ikw("show") >> ast::setting;

DEF_RULE(ShowModuleCommand)
    = ikw("show") >> ikw("module") >> Wildcard >> matches[qualifier("symbols")];

// ... ShowSymbolsQualifiers (see below) ...

DEF_RULE(ShowSymbolsCommand)
    = ikw("show") >> ikw("symbols") >> *ShowSymbolsQualifiers;

DEF_RULE(Command)
    = skip(blank)[ LoadCommand | SetCommand | ShowSettingCommand | ShowModuleCommand | ShowSymbolsCommand ];

更重的提升

你会注意到我跳过了 ShowSymbolsQualifiers。那是因为这是唯一不能从自动属性传播中获益的规则,所以我求助于使用语义操作:

Note the IIFE idiom allows for "very local" helper definitions

DEF_RULE(SymbolTypes) = [] {
        auto type = as_parser(ast::sym_type);
        return '(' >> (type % ',') >> ')' | repeat(1) [ type ];
    }(); // IIFE pattern

RULE(ShowSymbolsQualifiers, ShowSymbolsCommand)
    = [] {
        auto set = [](auto member, auto p) {
            auto propagate = [member](auto& ctx) {
                traits::move_to(_attr(ctx), _val(ctx).*(member));
            };
            return as_parser(p)[propagate];
        };

        using T = ast::ShowSymbolsCommand;;
        return qualifier("all")  >> set(&T::all, attr(true))
             | qualifier("full") >> set(&T::full, attr(true))
             | qualifier("out")  >> set(&T::out, '=' >> Filespec)
             | qualifier("type") >> set(&T::types, '=' >> SymbolTypes)
             | set(&T::wildcard, Wildcard);
    }(); // IIFE pattern

完整演示

Live On Coliru

//#define BOOST_SPIRIT_X3_DEBUG
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

#include <boost/algorithm/string/case_conv.hpp> // to_lower
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/home/x3.hpp>

/* Synopsis:
 *
 *    LOAD DLL <file-spec>
 *    LOAD PDB <file-spec>
 *    SET DEBUG {/ON | /OFF}
 *    SET TRACE {/ON | /OFF}
 *
 *    SHOW DEBUG
 *    SHOW MODULE <wildcard-expression> [/SYMBOLS]
 *    SHOW SYMBOLS { [/ALL] [/FULL] [/OUT=<file-spec>] [/TYPE=(+{all,exports,imports})] [wild-card-expression] }...
 *    SHOW TRACE
 */

namespace scl {
    namespace util {
        template <typename Tag> struct FlavouredString : std::string {
            using std::string::string;
            using std::string::operator=;
        };

        template <typename Tag> struct Options {
            using type = int; // TODO typed enums? Requires macro tedium

            std::vector<char const*> _options;
            Options(std::initializer_list<char const*> options) : _options(options) {}
            Options(std::vector<char const*> options) : _options(options) {}

            std::string to_string(type id)              const { return _options.at(id); }
            type         to_id(std::string const& name) const { return find(_options.begin(), _options.end(), name) - _options.begin(); }
        };

        template <typename Tag> using Enum = typename Options<Tag>::type;
        template <typename Tag> struct IcOptions : Options<Tag> { using Options<Tag>::Options; };
    }

    namespace ast {

        using namespace util;
        IcOptions<struct DllPdb>  static const dll_pdb { "DLL",   "PDB"   };
        IcOptions<struct Setting> static const setting { "DEBUG", "TRACE" };
        IcOptions<struct OnOff>   static const on_off  { "OFF",   "ON"    };
        IcOptions<struct SymType> static const sym_type{ "all", "imports", "exports" };

        using Wildcard = FlavouredString<struct _Wild>;
        using Filespec = FlavouredString<struct _Filespec>;

        struct LoadCommand {
            Enum<DllPdb> kind = {};
            Filespec     filespec;
        };

        struct SetCommand {
            Enum<Setting> setting = {};
            Enum<OnOff> value     = {};
        };

        struct ShowSettingCommand {
            Enum<Setting> setting;
        };

        struct ShowModuleCommand {
            Wildcard wildcard;
            bool symbols = false;
        };

        using SymbolTypes = std::vector<Enum<SymType> >; 

        struct ShowSymbolsCommand {
            bool all  = false;
            bool full = false;
            Filespec out;
            SymbolTypes types;
            Wildcard wildcard;
        };

        using Command = boost::variant<
            LoadCommand,
            SetCommand,
            ShowSettingCommand,
            ShowModuleCommand,
            ShowSymbolsCommand
        >;
    }
}

#ifndef NDEBUG // for debug printing
namespace scl { namespace ast {
    static inline std::ostream &operator<<(std::ostream &os, Wildcard const &w) { return os << std::quoted(w); }
    static inline std::ostream &operator<<(std::ostream &os, Filespec const &s) { return os << std::quoted(s); }
    static inline std::ostream &operator<<(std::ostream &os, LoadCommand const &cmd) {
        return os << "LOAD " << dll_pdb.to_string(cmd.kind) << " " << cmd.filespec ;
    }
    static inline std::ostream &operator<<(std::ostream &os, SetCommand const &cmd) {
        return os << "SET " << setting.to_string(cmd.setting) << " /" << on_off.to_string(cmd.value);
    }
    static inline std::ostream &operator<<(std::ostream &os, ShowSettingCommand const &cmd) {
        return os << "SHOW " << setting.to_string(cmd.setting);
    }
    static inline std::ostream &operator<<(std::ostream &os, ShowModuleCommand const &cmd) {
        return os << "SHOW MODULE " << cmd.wildcard << (cmd.symbols?" /SYMBOLS":"");
    }
    static inline std::ostream &operator<<(std::ostream &os, ShowSymbolsCommand const &cmd) {
        os << "SHOW SYMBOLS";
        if (cmd.all)          os << " /ALL";
        if (cmd.full)         os << " /FULL";
        if (cmd.out.size())   os << " /OUT=" << cmd.out;
        if (cmd.types.size()) { 
            os << " /TYPE=(";
            bool first = true;
            for (auto type : cmd.types)
                os << (std::exchange(first, false)?"":",") << sym_type.to_string(type);
            os << ")";
        }
        return os << " " << cmd.wildcard;
    }
} } 
#endif

BOOST_FUSION_ADAPT_STRUCT(scl::ast::LoadCommand,        kind, filespec)
BOOST_FUSION_ADAPT_STRUCT(scl::ast::SetCommand,         setting, value)
BOOST_FUSION_ADAPT_STRUCT(scl::ast::ShowSettingCommand, setting)
BOOST_FUSION_ADAPT_STRUCT(scl::ast::ShowModuleCommand,  wildcard, symbols)

// Grammar for simple command language
namespace scl {
    namespace x3 = boost::spirit::x3;

    // (case insensitive) keyword handling
    static auto kw        = [](auto p) { return x3::lexeme[p >> !(x3::graph - x3::char_("/=,()"))]; };
    static auto ikw       = [](auto p) { return x3::no_case [kw(p)]; };
    static auto qualifier = [](auto p) { return x3::lexeme['/' >> ikw(p)]; };

    // Options and CiOptions
    namespace util {
        template <typename Tag>
        auto as_spirit_parser(Options<Tag> const& o, bool to_lower = false) {
            x3::symbols<typename Options<Tag>::type> p;
            int n = 0;
            for (std::string el : o._options) {
                if (to_lower) boost::to_lower(el);
                p.add(el, n++);
            }
            return kw(p);
        }

        template <typename Tag>
        auto as_spirit_parser(IcOptions<Tag> const& o) {
            return x3::no_case [ as_spirit_parser(o, true) ];
        }
    }

    // shorthand rule declarations
    #define RULE(name, Attr) static auto const name = x3::rule<struct _##Attr, ast::Attr>{#Attr}
    #define DEF_RULE(Attr) RULE(Attr, Attr)

    using namespace x3;

    auto const bare_string
        = lexeme[+char_("a-zA-Z0-9$_.\*?+-")]; // bare string taken from old "param" rule

    auto const quoted_string 
        = lexeme['"' >> *(('\' > char_) | ~char_('"')) >> '"'];

    DEF_RULE(Filespec) = quoted_string | bare_string;
    DEF_RULE(Wildcard) = lexeme[+char_("a-zA-Z0-9$_.\*?+-")];

    DEF_RULE(LoadCommand)
        = ikw("load") >> ast::dll_pdb >> Filespec;

    DEF_RULE(SetCommand)
        = ikw("set") >> ast::setting >> qualifier(ast::on_off);

    DEF_RULE(ShowSettingCommand)
        = ikw("show") >> ast::setting;

    DEF_RULE(ShowModuleCommand)
        = ikw("show") >> ikw("module") >> Wildcard >> matches[qualifier("symbols")];

    // Note the IIFE idiom allows for "very local" helper definitions
    DEF_RULE(SymbolTypes) = [] {
            auto type = as_parser(ast::sym_type);
            return '(' >> (type % ',') >> ')' | repeat(1) [ type ];
        }(); // IIFE idiom

    RULE(ShowSymbolsQualifiers, ShowSymbolsCommand)
        = [] {
            auto set = [](auto member, auto p) {
                auto propagate = [member](auto& ctx) {
                    traits::move_to(_attr(ctx), _val(ctx).*(member));
                };
                return as_parser(p)[propagate];
            };

            using T = ast::ShowSymbolsCommand;;
            return qualifier("all")  >> set(&T::all, attr(true))
                 | qualifier("full") >> set(&T::full, attr(true))
                 | qualifier("out")  >> set(&T::out, '=' >> Filespec)
                 | qualifier("type") >> set(&T::types, '=' >> SymbolTypes)
                 | set(&T::wildcard, Wildcard);
        }(); // IIFE idiom

    DEF_RULE(ShowSymbolsCommand)
        = ikw("show") >> ikw("symbols") >> *ShowSymbolsQualifiers;

    DEF_RULE(Command)
        = skip(blank)[ LoadCommand | SetCommand | ShowSettingCommand | ShowModuleCommand | ShowSymbolsCommand ];

#undef DEF_RULE
#undef RULE
} // End namespace scl

int main() {
    for (std::string const str : {
        "load dll test.dll",
        "LOAD pdb \"test special.pdb\"",
        "LOAD pDb test.pdb",
        "set debug /on",
        "show debug",
        "SHOW module test.dll/symbols",
        "SHOW MODULE TEST.DLL /SYMBOLS",
        "SHOW module test.dll / symbols",
        "SHOW module test.dll",
        "show symbols/type=exports test*",
        "show symbols/type=(exports,imports) test*",
        "show symbols test.dll/type=(imports,exports)",
        "show symbols test.dll/tyPE=(imports,exports)",
        "show symbols s*/out=s.txt",
        "show symbols /all /full",
    }) {
        std::cout << " ======== " << std::quoted(str) << std::endl;

        auto b = str.begin(), e = str.end();
        scl::ast::Command cmd;

        if (parse(b, e, scl::Command, cmd))
            std::cout << " - Parsed: " << cmd << std::endl;
        if (b != e)
            std::cout << " - Remaining unparsed: " << std::quoted(std::string(b, e)) << std::endl;
    }
}

版画

 ======== "load dll test.dll"
 - Parsed: LOAD DLL "test.dll"
 ======== "LOAD pdb \"test special.pdb\""
 - Parsed: LOAD PDB "test special.pdb"
 ======== "LOAD pDb test.pdb"
 - Parsed: LOAD PDB "test.pdb"
 ======== "set debug /on"
 - Parsed: SET DEBUG /ON
 ======== "show debug"
 - Parsed: SHOW DEBUG
 ======== "SHOW module test.dll/symbols"
 - Parsed: SHOW MODULE "test.dll" /SYMBOLS
 ======== "SHOW MODULE TEST.DLL /SYMBOLS"
 - Parsed: SHOW MODULE "TEST.DLL" /SYMBOLS
 ======== "SHOW module test.dll / symbols"
 - Parsed: SHOW MODULE "test.dll"
 - Remaining unparsed: "/ symbols"
 ======== "SHOW module test.dll"
 - Parsed: SHOW MODULE "test.dll"
 ======== "show symbols/type=exports test*"
 - Parsed: SHOW SYMBOLS /TYPE=(exports) "test*"
 ======== "show symbols/type=(exports,imports) test*"
 - Parsed: SHOW SYMBOLS /TYPE=(exports,imports) "test*"
 ======== "show symbols test.dll/type=(imports,exports)"
 - Parsed: SHOW SYMBOLS /TYPE=(imports,exports) "test.dll"
 ======== "show symbols test.dll/tyPE=(imports,exports)"
 - Parsed: SHOW SYMBOLS /TYPE=(imports,exports) "test.dll"
 ======== "show symbols s*/out=s.txt"
 - Parsed: SHOW SYMBOLS /OUT="s.txt" "s*"
 ======== "show symbols /all /full"
 - Parsed: SHOW SYMBOLS /ALL /FULL ""