c++ 如何在boost spirit的名称字段中保留一组关键字?

bzzcjhmw  于 2023-04-13  发布在  其他
关注(0)|答案(1)|浏览(106)

我在PureData中有以下对象记录的定义,我需要能够将其解析为我的通用PdObject结构:

Description:
Defines an object
Syntax:
#X obj [x_pos] [y_pos] [object_name] [p1] [p2] [p3] [...];\r\n
Parameters:
[x_pos] - horizontal position within the window
[y_pos] - vertical position within the window
[object_name] - name of the object (optional)
[p1] [p2] [p3] [...] the parameters of the object (optional)
Example:
#X obj 55 50;
#X obj 132 72 trigger bang float;

我创建了以下经过测试有效的增强精神规则:

template <typename Iterator> struct PdObjectGrammar : qi::grammar<Iterator, PdObject()> { 
    PdObjectGrammar() : PdObjectGrammar::base_type(start) { 
        using namespace qi; 
        start = skip(space)[objectRule]; 
        pdStringRule = +(('\\'  >> space) | (graph-lit(";"))); 
        objectRule = "#X obj" >> int_ >> int_ >> -(pdStringRule) >> *(pdStringRule) >> ";"; 
        BOOST_SPIRIT_DEBUG_NODES((start)(objectRule)(pdStringRule))
    }
    private: 
    qi::rule<Iterator, std::string()> pdStringRule; 
    qi::rule<Iterator, PdObject()> start; 
    qi::rule<Iterator, PdObject(), qi::space_type> objectRule; 

};

但是,也有一些特殊的“保留名称”不能使用,例如 “bng”,“tgl”,“nbx”, 等。
例如,下面是另一种类型的“obj”,它使用了一个保留名称关键字,必须通过不同的规则单独解析:

#X obj 92 146 bng 20 250 50 0 empty empty empty 0 -10 0 12 #fcfcfc #000000 #000000;

我如何修改我之前的qi规则来 * 不 * 解析上面的字符串,并将其留给另一个语法来检查(这将把它解析为不同的结构体)?

后记:

我对PdObjectGrammar的完整测试是:

#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>

#include <string> 
#include <vector>
#include <fstream>

namespace qi = boost::spirit::qi;

struct PdObject {
int xPos;
int yPos;
std::string name;
std::vector<std::string> params;

};

BOOST_FUSION_ADAPT_STRUCT(
    PdObject,
    xPos,
    yPos,
    name,
    params
)

template <typename Iterator> struct PdObjectGrammar : qi::grammar<Iterator, PdObject()> { 
    PdObjectGrammar() : PdObjectGrammar::base_type(start) { 
        using namespace qi; 
        start = skip(space)[objectRule]; 
        pdStringRule = +(('\\'  >> space) | (graph-lit(";"))); 
        objectRule = "#X obj" >> int_ >> int_ >> -(pdStringRule) >> *(pdStringRule) >> ";"; 
        BOOST_SPIRIT_DEBUG_NODES((start)(objectRule)(pdStringRule))
    }
    private: 
    qi::rule<Iterator, std::string()> pdStringRule; 
    qi::rule<Iterator, PdObject()> start; 
    qi::rule<Iterator, PdObject(), qi::space_type> objectRule; 

};

int main(int argc, char** argv)
{
  if(argc != 2)
    {
        std::cout << "Usage: "  <<argv[0] << " <PatchFile>" << std::endl;
        exit(1); 
    }

    std::ifstream inputFile(argv[1]); 
    std::string inputString(std::istreambuf_iterator<char>(inputFile), {}); 

    PdObject msg;
    PdObjectGrammar<std::string::iterator> parser; 

    bool success = qi::phrase_parse(inputString.begin(), inputString.end(), parser, boost::spirit::ascii::space, msg); 
    std::cout << "Success: " << success << std::endl;

    return 0; 

}
8yparm6h

8yparm6h1#

在某种程度上,“关键词”不是语法的一部分,而是一种语义检查。
语法处理关键字的方式并不统一,例如,C++有许多标识符,它们只是在上下文中保留的。
简而言之,你只需要在代码中表达你的约束,或者在事后(对解析的结果)验证语义。
标签:Live

string     = +('\\' >> qi::space | qi::graph - ";");
name       = string - "bng" - "tgl" - "nbx" - "vsl" - "hsl" - "vradio" - "hradio" - "vu" - "cnv";
object     = "#X obj"       //
    >> qi::int_ >> qi::int_ //
    >> -name                //
    >> *string >> ";";

Live

string     = +('\\' >> qi::space | qi::graph - ";");
builtin    = qi::lit("bng") | "tgl" | "nbx" | "vsl" | "hsl" | "vradio" | "hradio" - "vu" | "cnv";
object     = "#X obj"        //
    >> qi::int_ >> qi::int_  //
    >> -(!builtin >> string) //
    >> *string >> ";";

符号

你可以通过为它定义一个符号来使它更优雅、更可维护、更高效:Live

qi::symbols<char> builtin;

// ...
builtin += "bng", "tgl", "nbx", "vsl", "hsl", "vradio", "hradio", "vu", "cnv";

string = +('\\' >> qi::space | qi::graph - ";");
object = "#X obj"                //
         >> qi::int_ >> qi::int_ //
         >> -(string - builtin)    //
         >> *string >> ";";

区分关键词

这里有一个缺陷。当用户以内置列表开始命名对象时,例如bngalorevslander,内置列表将匹配,因此名称将被拒绝:Live
为了说明这一点,请确保我们在词素边界上:Live

auto kw = [](auto const& p) { return qi::copy(qi::lexeme[p >> !(qi::graph - ';')]); };
string = +('\\' >> qi::space | qi::graph - ";");
object = "#X obj"                //
    >> qi::int_ >> qi::int_      //
    >> -(!kw(builtin) >> string) //
    >> *string >> ";";

不管用!

那是因为语法有缺陷。在你的辩护中,规范是非常草率的。它是 * 那些语法之一 * 好吧。
由于所有这些都是可选的,你应该问自己,当有参数时,解析器是如何知道name被省略的?就我所知,解析器永远不会知道,所以当名字被省略时,就不可能有参数了。
我们可以表示:Live

string = +('\\' >> qi::space | qi::graph - ";");
object = "#X obj"                                   //
    >> qi::int_ >> qi::int_                         //
    >> !kw(builtin) >> -(string >> *string) >> ";"; //

哦,不,现在整个(string >> *string)都兼容 * 只是 * name属性...:

Input: "#X obj 132 72 trigger bang float;"
 -> (132 72 "triggerbangfloat" { })

这里我建议调整AST以反映解析的语法:

struct GenericObject {
    String              name;
    std::vector<String> params;
};

struct PdObject {
    int           xPos, yPos;
    GenericObject generic;
};

BOOST_FUSION_ADAPT_STRUCT(PdObject, xPos, yPos, generic)
BOOST_FUSION_ADAPT_STRUCT(GenericObject, name, params)

现在,它确实正确地传播了属性:Live,注意输出中的额外子对象(()):

Input: "#X obj 132 72 trigger bang float;"
 -> (132 72 ("trigger" { "bang" "float" }))

一路走来

作为一个专业提示,不要像规范那样草率地实现解析器。很可能,你只是想用专用的AST类型和同上规则解析不同的对象类型。
对于非常高级的/可插入的语法,您可以根据名称符号分派规则,这就是众所周知的Nabialek技巧。
让我们推广我们的object规则:

object = "#X obj"           //
    >> qi::int_ >> qi::int_ //
    >> definition           //
    >> ";"                  //
    ;

现在让我们演示VSL规则,以及泛型对象:

definition = vslider | generic;

Generic仍然是我们以前拥有的:

generic           //
    = opt(string) // name
    >> *string;   // params

让我们粗略地看看Vslider

vslider                             //
    = qi::lexeme["vsl" >> boundary] //
    >> opt(qi::uint_)               // width
    >> opt(qi::uint_)               // height
    >> opt(qi::double_)             // bottom
    >> opt(qi::double_)             // top
    >> opt(bool_)                   // log
    >> opt(bool_)                   // init
    >> opt(string)                  // send
    >> opt(string)                  // receive
    >> opt(string)                  // label
    >> opt(qi::int_)                // x_off
    >> opt(qi::int_)                // y_off
    >> opt(string)                  // font
    >> opt(qi::uint_)               // fontsize
    >> opt(rgb)                     // bg_color
    >> opt(rgb)                     // fg_colo
    >> opt(rgb)                     // label_color
    >> opt(qi::double_)             // default_value
    >> opt(bool_)                   // steady_on_click
    ;

当然,我们需要一些帮手:

qi::uint_parser<int32_t, 16, 6, 6> hex6{};
rgb = ('#' >> hex6) | qi::int_;

auto boundary = qi::copy(!(qi::graph - ';'));
auto opt = [](auto const& p) { return qi::copy(p | &qi::lit(';')); };

bool_ = qi::bool_ | qi::uint_parser<bool, 2, 1, 1>{};

AST类型:

struct RGB {
    int32_t rgb;
};

namespace Defs {
    using boost::optional;

    struct Generic {
        String              name;
        std::vector<String> params;
    };

    struct Vslider {
        optional<unsigned> width;           // horizontal size of gui element
        optional<unsigned> height;          // vertical size of gui element
        optional<double>   bottom;          // minimum value
        optional<double>   top;             // maximum value
        bool               log = false;     // when set the slider range is outputted
                                            // logarithmically, otherwise it's output
                                            // is linair
        String           init;              // sends default value on patch load
        String           send;              // send symbol name
        String           receive;           // receive symbol name
        optional<String> label;             // label
        int              x_off = 0;         // horizontal position of the label
                                            // text relative to the upperleft
                                            // corner of the object
        int y_off = 0;                      // vertical position of the label
                                            // text relative to the upperleft
                                            // corner of the object
        optional<String>   font;            // font type
        optional<unsigned> fontsize;        // font size
        optional<RGB>      bg_color;        // background color
        optional<RGB>      fg_color;        // foreground color
        optional<RGB>      label_color;     // label color
        optional<double>   default_value;   // default value times hundred
        optional<bool>     steady_on_click; // when set, fader is steady on click,
                                            // otherwise it jumps on click
    };

    using Definition = boost::variant<Vslider, Generic>;
} // namespace Defs

using Defs::Definition;

struct PdObject {
    int        xPos, yPos;
    Definition definition;
};

把这些放在一起:

完整Demo

**第一次

// #define BOOST_SPIRIT_DEBUG
#include <boost/core/demangle.hpp>
#include <boost/fusion/adapted.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/optional/optional_io.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>

namespace Ast {
    // C++ makes it hard to pretty-print containers...
    struct print_hack : std::char_traits<char> {};
    using String = std::basic_string<char, print_hack>;
    static inline std::ostream& operator<<(std::ostream& os, String const& s) { return os << quoted(s); }
    static inline std::ostream& operator<<(std::ostream& os, std::vector<String> const& ss) {
        os << "{";
        for (auto& s : ss) os << " " << s;
        return os << " }";
    }

    struct RGB {
        int32_t rgb;
    };

    namespace Defs {
        using boost::optional;

        struct Generic {
            String              name;
            std::vector<String> params;
        };

        struct Vslider {
            optional<unsigned> width;           // horizontal size of gui element
            optional<unsigned> height;          // vertical size of gui element
            optional<double>   bottom;          // minimum value
            optional<double>   top;             // maximum value
            bool               log = false;     // when set the slider range is outputted
                                                // logarithmically, otherwise it's output
                                                // is linair
            String           init;              // sends default value on patch load
            String           send;              // send symbol name
            String           receive;           // receive symbol name
            optional<String> label;             // label
            int              x_off = 0;         // horizontal position of the label
                                                // text relative to the upperleft
                                                // corner of the object
            int y_off = 0;                      // vertical position of the label
                                                // text relative to the upperleft
                                                // corner of the object
            optional<String>   font;            // font type
            optional<unsigned> fontsize;        // font size
            optional<RGB>      bg_color;        // background color
            optional<RGB>      fg_color;        // foreground color
            optional<RGB>      label_color;     // label color
            optional<double>   default_value;   // default value times hundred
            optional<bool>     steady_on_click; // when set, fader is steady on click,
                                                // otherwise it jumps on click
        };

        using Definition = boost::variant<Generic, Vslider>;

        using boost::fusion::operator<<;
    } // namespace Defs

    using Defs::Definition;

    struct PdObject {
        int        xPos, yPos;
        Definition definition;
    };

    using boost::fusion::operator<<;
}

BOOST_FUSION_ADAPT_STRUCT(Ast::Defs::Vslider, width, height, bottom, top, log, init, send, receive, label,
                          x_off, y_off, font, fontsize, bg_color, fg_color, label_color, default_value,
                          steady_on_click)
BOOST_FUSION_ADAPT_STRUCT(Ast::Defs::Generic, name, params)
BOOST_FUSION_ADAPT_STRUCT(Ast::RGB, rgb)
BOOST_FUSION_ADAPT_STRUCT(Ast::PdObject, xPos, yPos, definition)

namespace qi = boost::spirit::qi;

template <typename Iterator> struct PdObjectGrammar : qi::grammar<Iterator, Ast::PdObject()> {
    PdObjectGrammar() : PdObjectGrammar::base_type(start) {
        start = qi::skip(qi::blank)[ object ];

        /* #X obj [x_pos] [y_pos] [object_name] [p1] [p2] [p3] [...];\r\n
         * Parameters:
         *  [x_pos] - horizontal position within the window
         *  [y_pos] - vertical position within the window
         *  [object_name] - name of the object (optional)
         *  [p1] [p2] [p3] [...] the parameters of the object (optional)
         */
        qi::uint_parser<int32_t, 16, 6, 6> hex6{};
        rgb = ('#' >> hex6) | qi::int_;

        auto boundary = qi::copy(!(qi::graph - ';'));
        auto opt = [](auto const& p) { return qi::copy(p | &qi::lit(';')); };

        bool_ = qi::bool_ | qi::uint_parser<bool, 2, 1, 1>{};

        vslider                             //
            = qi::lexeme["vsl" >> boundary] //
            >> opt(qi::uint_)               // width
            >> opt(qi::uint_)               // height
            >> opt(qi::double_)             // bottom
            >> opt(qi::double_)             // top
            >> opt(bool_)                   // log
            >> opt(bool_)                   // init
            >> opt(string)                  // send
            >> opt(string)                  // receive
            >> opt(string)                  // label
            >> opt(qi::int_)                // x_off
            >> opt(qi::int_)                // y_off
            >> opt(string)                  // font
            >> opt(qi::uint_)               // fontsize
            >> opt(rgb)                     // bg_color
            >> opt(rgb)                     // fg_colo
            >> opt(rgb)                     // label_color
            >> opt(qi::double_)             // default_value
            >> opt(bool_)                   // steady_on_click
            ;

        generic           //
            = opt(string) // name
            >> *string;   // params

        definition = vslider | generic;

        string = +('\\' >> qi::space | qi::graph - ";");
        object = "#X obj"           //
            >> qi::int_ >> qi::int_ //
            >> definition           //
            >> ";"                  //
            ;

        BOOST_SPIRIT_DEBUG_NODES(          //
            (start)(object)(string)(rgb)   //
            (definition)(vslider)(generic) //
            (bool_))                       //
    }

  private:
    using Skipper = qi::blank_type;
    qi::rule<Iterator, Ast::PdObject(),         Skipper> object;
    qi::rule<Iterator, Ast::Defs::Vslider(),    Skipper> vslider;
    qi::rule<Iterator, Ast::Defs::Generic(),    Skipper> generic;
    qi::rule<Iterator, Ast::Defs::Definition(), Skipper> definition;

    // lexemes
    qi::rule<Iterator, bool()>          bool_;
    qi::rule<Iterator, Ast::RGB()>      rgb;
    qi::rule<Iterator, Ast::String()>   string;
    qi::rule<Iterator, Ast::PdObject()> start;
};

int main()
{
    PdObjectGrammar<std::string::const_iterator> const parser;

    for (std::string const input :
         {
             "#X obj 55 50;",
             "#X obj 92 146 bng 20 250 50 0 empty empty empty 0 -10 0 12 #fcfcfc #000000 #000000;",
             "#X obj 50 38 vsl 15 128 0 127 0 0 empty empty empty 0 -8 0 8 -262144 -1 -1 0 1;",
         }) //
    {
        Ast::PdObject msg;

        auto f = input.begin(), l = input.end();
        std::cout << "Input: " << quoted(input) << std::endl;
        if (qi::parse(f, l, parser, msg)) {
            std::cout << " -> " << boost::core::demangle(msg.definition.type().name()) << std::endl;
            std::cout << " -> " << msg << std::endl;
        } else
            std::cout << " -> FAILED" << std::endl;

        if (f != l)
            std::cout << " Remaining: " << quoted(std::string(f, l)) << std::endl;
    }
}

打印

Input: "#X obj 55 50;"
 -> Ast::Defs::Generic
 -> (55 50 ("" { }))
Input: "#X obj 92 146 bng 20 250 50 0 empty empty empty 0 -10 0 12 #fcfcfc #000000 #000000;"
 -> Ast::Defs::Generic
 -> (92 146 ("bng" { "20" "250" "50" "0" "empty" "empty" "empty" "0" "-10" "0" "12" "#fcfcfc" "#000000" "#000000" }))
Input: "#X obj 50 38 vsl 15 128 0 127 0 0 empty empty empty 0 -8 0 8 -262144 -1 -1 0 1;"
 -> Ast::Defs::Vslider
 -> (50 38 ( 15  128  0  127 0 "" "empty" "empty"  "empty" 0 -8  "0"  8  (-262144)  (-1)  (-1)  0  1))

注意我们默认情况下是如何将bng解析为Generic的,这仅仅是因为我们还没有为它添加定义规则。Live

Input: "#X obj 55 50;"
 -> Ast::Defs::Generic
 -> (55 50 ("" { }))
Input: "#X obj 92 146 bng 20 250 50 0 empty empty empty 0 -10 0 12 #fcfcfc #000000 #000000;"
 -> Ast::Defs::Bang
 -> (92 146 ( 20  250  2 "" "empty" "empty" "empty"  0  -10  "0"  12  (16579836)  (0)  (0)))
Input: "#X obj 50 38 vsl 15 128 0 127 0 0 empty empty empty 0 -8 0 8 -262144 -1 -1 0 1;"
 -> Ast::Defs::Vslider
 -> (50 38 ( 15  128  0  127 0 "" "empty" "empty"  "empty" 0 -8  "0"  8  (-262144)  (-1)  (-1)  0  1))

这基本上是从PureData语法文档中1:1复制粘贴。

  • 当然,我的手指渴望删除initsendreceivelabelx_offy_offfontfontsizebg_colorfg_colorlabel_color的重复......但我将把它留给读者作为驱魔。*

相关问题