I have the following definition for an object record in PureData that I need to be able to parse into my generic PdObject struct:
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;
And I have created the following boost spirit rule that has been tested to work:
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;
};
However, there are also special "reserved names" that cannot be used, such as "bng," "tgl," "nbx," etc...
For example, here is another type of "obj" using a reserved name keyword that must be parsed separately by a different rule:
#X obj 92 146 bng 20 250 50 0 empty empty empty 0 -10 0 12 #fcfcfc #000000 #000000;
How can I modify my previous qi rule to not parse the above string, and leave it for another grammar to check (which would parse it to a different struct)?
Postscript:
My full test for the PdObjectGrammar is:
#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;
}
In a way "keywordness" is not part of the grammar. It's a semantic check.
There's not a standard way in which grammars deal with keywords. For example C++ has a number of identifiers that are contextually reserved only.
The short story of it is you will just have to express your constraints in code or validate semantics after-the-fact (on the parsed result).
Naively: Live
Or Live
Symbols
You can make this a bit more elegant, maintainable and possibly more efficient by defining a symbol for it: Live
Distinct Keywords
There's a flaw. When the user names their object something starting with the builtin list, like
bngaloreorvslanderthe builtins will match so the name would be rejected: LiveTo account for this, make sure we're on a lexeme boundary: Live
It doesn't work!
That's because the grammar is flawed. In your defense, the specification is extremely sloppy. It's one of those grammars alright.
With all those things being optional, you should ask yourself, how does the parser know that
nameis omitted, when there are parameters? As far as I can see the parser could never tell, so when the name is omitted, there cannot be parameters?We can express that: Live
Oh noes, now the entire
(string >> *string)is compatible with just the name attribute...:Here I'd advise to adjust the AST to reflect the parsed grammar:
Now, it does propagate the attributes correctly: Live, note the extra sub-object (
()) in the output:Taking It All The Way
As a pro tip, don't implement the parser in the same sloppy fashion as the specification was done. Likely, you just want to parse different object types with dedicated AST types and ditto rules.
Let's generalize our
objectrule:Now let's demo the VSL rule, in addition to generic objects:
Generic is still what we had before:
Let's do a rough take on
Vslider:Of course we need a few helpers:
And the AST types:
Putting it all together:
Full Demo
Live On Coliru
Prints
Note how we parse
bngasGenericby default, simply because we didn't add a definition rule for it yet. Adding it: Live:That was basically 1:1 copy-paste from the PureData grammar docs.