Commits

David Mugnai committed 9bf6564

slam/parser: new rules and test for identifiers and quoted strings

Comments (0)

Files changed (2)

     qi::rule<Iterator, Tag*()> tag;
     qi::rule<Iterator, int(int)> indent;
 
+    qi::rule<Iterator, std::string(), qi::locals<char>> quoted_string;
+    qi::symbols<char const, char const> escape_chars;
+    qi::rule<Iterator, std::string()> identifier;
+
     XSlamGrammar() : XSlamGrammar::base_type(expression) {
 
         // html         0 Node1
         using phx::bind;
         using phx::push_back;
 
+        expression %= block(0);
+
+        block =
+            indent(_r1)[_a = _1]
+            >> node[_val = _1]
+            >> *block(_a + 1)[push_back(phx::val(_val)->*&Node::children, _1)];
+
+        node %= (tag >> (qi::eol | qi::eoi));
+
+        tag = identifier[
+            _val = bind([](std::string const& name) -> Tag* {
+                return new Tag{name};
+            }, _1)
+        ];
+
         // matches at least _r1 spaces -> expose the number of matched spaces
         indent = (qi::repeat(_r1, qi::inf)[qi::blank])[_val = phx::size(_1)];
 
-        tag = (+qi::alpha >> *qi::alnum)[
-            _val = bind([](std::vector<char> const& part1, std::vector<char> const& part2) -> Tag* {
-                auto name = std::string(part1.data(), part1.size());
+        quoted_string =
+            qi::omit[qi::char_("'\"")[_a = _1]]
+            >> qi::lexeme[(*(escape_chars | (qi::char_ - qi::char_(_a)) ))]
+            >> qi::lit(_a);
+
+        escape_chars.add
+            ("\\a", '\a')
+            ("\\b", '\b')
+            ("\\f", '\f')
+            ("\\n", '\n')
+            ("\\r", '\r')
+            ("\\t", '\t')
+            ("\\v", '\v')
+            ("\\\\", '\\')
+            ("\\\'", '\'')
+            ("\\\"", '\"');
+
+        identifier = (+qi::alpha >> *qi::alnum)[
+            _val = bind([](std::vector<char> const& part1, std::vector<char> const& part2) -> std::string {
+                auto id = std::string(part1.data(), part1.size());
                 if(part2.size()) {
-                    name.append(std::string(part2.data(), part2.size()));
+                    id.append(std::string(part2.data(), part2.size()));
                 }
-                return new Tag{name};
+                return id;
             }, _1, qi::_2)
         ];
 
-        node %= (tag >> (qi::eol | qi::eoi));
-
-        block =
-            indent(_r1)[_a = _1]
-            >> node[_val = _1]
-            >> *block(_a + 1)[push_back(phx::val(_val)->*&Node::children, _1)];
-
-        expression %= block(0);
-
         this->expression.name("expression");
         this->block.name("block");
         this->node.name("node");

tests/test_slam_parser.cpp

 #include "slam.h"
 #include "gtest/gtest.h"
+#include <boost/spirit/include/qi.hpp>
 
 using slam::ast::Tag;
 using slam::parser::parse;
 
-TEST(BasicParser, just_one_tag) {
+namespace qi = boost::spirit::qi;
+using std::string;
+using slam::parser::XSlamGrammar;
+
+class GrammarRuleTest : public ::testing::Test {
+    public:
+        XSlamGrammar<string::const_iterator> grammar;
+
+        virtual void SetUp() {
+        }
+
+        template<typename T>
+        typename T::attr_type parse(string const& input, T rule, bool* success=nullptr) {
+            typename T::attr_type result;
+            auto parsed = qi::parse(input.begin(), input.end(), rule, result);
+            if(success) {
+                *success = parsed;
+            }
+            return result;
+        }
+};
+
+TEST_F(GrammarRuleTest, valid_identifiers) {
+    auto result1 = this->parse("html", grammar.identifier);
+    EXPECT_EQ(result1, "html");
+
+    auto result2 = this->parse("h1", grammar.identifier);
+    EXPECT_EQ(result2, "h1");
+}
+
+TEST_F(GrammarRuleTest, invalid_identifiers) {
+    auto result1 = this->parse("1h", grammar.identifier);
+    EXPECT_EQ(result1, "");
+}
+
+TEST_F(GrammarRuleTest, quoted_strings) {
+    auto result1 = this->parse("\"hello world\"", grammar.quoted_string);
+    EXPECT_EQ(result1, "hello world");
+
+    auto result2 = this->parse("\'hello world\'", grammar.quoted_string);
+    EXPECT_EQ(result2, "hello world");
+
+    auto result3 = this->parse("\"hello \\\"world\\\"\"", grammar.quoted_string);
+    EXPECT_EQ(result3, "hello \"world\"");
+
+    auto result4 = this->parse("\"hello \\\"world\\\'\"", grammar.quoted_string);
+    EXPECT_EQ(result4, "hello \"world\'");
+
+    auto result5 = this->parse("\"hello\\tworld\"", grammar.quoted_string);
+    EXPECT_EQ(result5, "hello\tworld");
+}
+
+TEST_F(GrammarRuleTest, invalid_quoted_strings) {
+    bool success;
+    this->parse("\"hello world", grammar.quoted_string, &success);
+    EXPECT_EQ(success, false);
+
+    this->parse("\'hello world", grammar.quoted_string, &success);
+    EXPECT_EQ(success, false);
+}
+
+TEST(Parsing, just_one_tag) {
     auto result = static_cast<Tag*>(parse("html"));
     ASSERT_NE(result, nullptr);
 
     EXPECT_EQ(result->attribs.size(), 0);
 }
 
-TEST(BasicParser, one_child) {
+TEST(Parsing, one_child) {
     auto result = static_cast<Tag*>(parse("html\n  head"));
     ASSERT_NE(result, nullptr);
 
     EXPECT_EQ(child->name, "head");
 }
 
-TEST(BasicParser, one_tag_one_attrib) {
+TEST(Parsing, one_tag_one_attrib) {
     auto result = static_cast<Tag*>(parse("html id=\"42\""));
     ASSERT_NE(result, nullptr);