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



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

Description:
Defines an object
Syntax:
#X obj [x_pos] [y_pos] [object_name] [p1] [p2] [p3] [...];rn
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; 
}

在某种程度上不是语法的一部分。这是语义检查

语法处理关键字没有标准的方式。例如,c++有许多标识符仅在上下文中保留。

简而言之,您只需要在代码中表达约束,或者在事后(在解析的结果上)验证语义。

天真:生活

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 >> ";";

或者

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 >> ";";

<<h2>不同的关键词/h2>有一个缺陷。当用户将其对象命名为以内置列表开头的东西时,例如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) >> ";"; //

哦,noes,现在整个(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类型和相同的规则解析不同的对象类型。

对于真正高级/可插拔的语法,您可以根据名称符号分派规则。这就是所谓的纳比亚莱克把戏。

让我们推广object规则:

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

现在让我们演示VSL规则,除了通用对象:

definition = vslider | 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;
};

把它们放在一起:

<<h2>完整演示/h2>Live On Coliru

// #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] [...];rn
* 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复制粘贴。

当然,我的手指痒要删除重复的init,send,receive,label,x_off,y_off,font,fontsize,bg_color,fg_colorlabel_color…但我将把它留给读者作为一种驱魔。

最新更新