Parcourir la source

[5014] Parser improved, unit-tests added.

Tomek Mrugalski il y a 8 ans
Parent
commit
32a96adb1f

+ 1 - 1
src/bin/dhcp6/Makefile.am

@@ -125,7 +125,7 @@ location.hh position.hh stack.hh dhcp6_parser.cc dhcp6_parser.h: dhcp6_parser.yy
 	$(YACC) --defines=dhcp6_parser.h -o dhcp6_parser.cc dhcp6_parser.yy
 
 dhcp6_lexer.cc: dhcp6_lexer.ll
-	$(LEX) -o dhcp6_lexer.cc dhcp6_lexer.ll
+	$(LEX) --prefix parser6_ -o dhcp6_lexer.cc dhcp6_lexer.ll
 
 else
 

+ 27 - 14
src/bin/dhcp6/dhcp6_parser.yy

@@ -8,6 +8,7 @@
 %require "3.0.0"
 %defines
 %define parser_class_name {Dhcp6Parser}
+%define api.prefix {parser6_}
 %define api.token.constructor
 %define api.value.type variant
 %define api.namespace {isc::dhcp}
@@ -55,7 +56,6 @@ using namespace std;
 
 %type <ElementPtr> value
 
-
 %printer { yyoutput << $$; } <*>;
 
 %%
@@ -69,37 +69,50 @@ value : INTEGER { $$ = ElementPtr(new IntElement($1)); }
      | BOOLEAN { $$ = ElementPtr(new BoolElement($1)); }
      | STRING { $$ = ElementPtr(new StringElement($1)); }
      | NULL_TYPE { $$ = ElementPtr(new NullElement()); }
-     | map { $$ = ElementPtr(new MapElement()); }
-     | list { $$ = ElementPtr(new ListElement()); }
+     | map { $$ = ctx.stack_.back(); ctx.stack_.pop_back(); }
+     | list { $$ = ctx.stack_.back(); ctx.stack_.pop_back(); }
     ;
 
 
 map: LCURLY_BRACKET {
-    ctx.stack_.push_back(ElementPtr(new MapElement()));
- } map_content RCURLY_BRACKET {
-     ctx.stack_.pop_back();
- };
+    // This code is executed when we're about to start parsing
+    // the content of the map
+    ElementPtr m(new MapElement());
+    ctx.stack_.push_back(m);
+} map_content RCURLY_BRACKET {
+    // map parsing completed. If we ever want to do any wrap up
+    // (maybe some sanity checking), this would be the best place
+    // for it.
+};
 
 // Assignments rule
 map_content:  { /* do nothing, it's an empty map */ }
     | STRING COLON value {
-        (*ctx.stack_.end())->set($1, $3);
+        // map containing a single entry
+        ctx.stack_.back()->set($1, $3);
     }
-    | map COMMA STRING COLON value {
-        (*ctx.stack_.end())->set($3, $5);
+    | map_content COMMA STRING COLON value {
+        // map consisting of a shorter map followed by comma and string:value
+        ctx.stack_.back()->set($3, $5);
     }
     ;
 
-list: LSQUARE_BRACKET list_content RSQUARE_BRACKET { };
+list: LSQUARE_BRACKET {
+    // List parsing about to start
+    ElementPtr l(new ListElement());
+    ctx.stack_.push_back(l);
+} list_content RSQUARE_BRACKET {
+    // list parsing complete. Put any sanity checking here
+};
 
 list_content: { /* do nothing, it's an empty list */ }
     | value {
         // List consisting of a single element.
-        (*ctx.stack_.end())->add($1);
+        ctx.stack_.back()->add($1);
     }
-    | list COMMA value {
+    | list_content COMMA value {
         // List ending with , and a value.
-        (*ctx.stack_.end())->add($3);
+        ctx.stack_.back()->add($3);
     }
     ;
 

+ 9 - 2
src/bin/dhcp6/parser_context.cc

@@ -31,13 +31,20 @@ Parser6Context::parseString(const std::string& str)
     string_ = str;
     scanStringBegin();
     isc::dhcp::Dhcp6Parser parser(*this);
+    // Uncomment this to get detailed parser logs.
+    // trace_parsing_ = true;
     parser.set_debug_level(trace_parsing_);
     int res = parser.parse();
     if (res != 0) {
-
+        // @todo: handle exception here
     }
     scanStringEnd();
-    return (*stack_.end());
+    if (stack_.size() == 1) {
+        return (stack_[0]);
+    } else {
+        isc_throw(BadValue, "Expected exactly one terminal Element, found "
+                  << stack_.size());
+    }
 }
 
 void

+ 2 - 2
src/bin/dhcp6/parser_context.h

@@ -14,7 +14,7 @@
 #include <exceptions/exceptions.h>
 
 // Tell Flex the lexer's prototype ...
-#define YY_DECL isc::dhcp::Dhcp6Parser::symbol_type yylex (Parser6Context& driver)
+#define YY_DECL isc::dhcp::Dhcp6Parser::symbol_type parser6_lex (Parser6Context& driver)
 
 // ... and declare it for the parser's sake.
 YY_DECL;
@@ -45,7 +45,7 @@ public:
     virtual ~Parser6Context();
 
     /// @brief JSON elements being parsed.
-    std::vector<ElementPtr> stack_;
+    std::vector<isc::data::ElementPtr> stack_;
 
     /// @brief Method called before scanning starts on a string.
     void scanStringBegin();

+ 57 - 5
src/bin/dhcp6/tests/parser_unittest.cc

@@ -13,15 +13,67 @@ using namespace std;
 
 namespace {
 
-TEST(ParserTest, basic) {
+void compareJSON(ConstElementPtr a, ConstElementPtr b) {
+    std::cout << a->str() << std::endl;
+    std::cout << b->str() << std::endl;
+    EXPECT_EQ(a->str(), b->str());
+}
+
+void testParser(const std::string& txt) {
+    ElementPtr reference_json;
+    ConstElementPtr test_json;
+
+    EXPECT_NO_THROW(reference_json = Element::fromJSON(txt, true));
+    EXPECT_NO_THROW({
+        Parser6Context ctx;
+        test_json = ctx.parseString(txt);
+    });
+
+    // Now compare if both representations are the same.
+    compareJSON(reference_json, test_json);
+}
+
+TEST(ParserTest, mapInMap) {
+    string txt = "{ \"Dhcp6\": { \"foo\": 123, \"baz\": 456 } }";
+    testParser(txt);
+}
 
-    Parser6Context ctx;
+TEST(ParserTest, listInList) {
+    string txt = "{ \"countries\": [ [ \"Britain\", \"Wales\", \"Scotland\" ], "
+                                    "[ \"Pomorze\", \"Wielkopolska\", \"Tatry\"] ] }";
+    testParser(txt);
+}
+
+TEST(ParserTest, nestedMaps) {
+    string txt = "{ \"europe\": { \"UK\": { \"London\": { \"street\": \"221B Baker\" }}}}";
+    testParser(txt);
+}
 
-    string txt = "{ \"Dhcp6\": { } }";
+TEST(ParserTest, nestedLists) {
+    string txt = "{ \"unity\": [ \"half\", [ \"quarter\", [ \"eighth\", [ \"sixteenth\" ]]]] }";
+    testParser(txt);
+}
 
-    ConstElementPtr json = ctx.parseString(txt);
+TEST(ParserTest, listsInMaps) {
+    string txt = "{ \"constellations\": { \"orion\": [ \"rigel\", \"betelguese\" ], "
+                    "\"cygnus\": [ \"deneb\", \"albireo\"] } }";
+    testParser(txt);
+}
+
+TEST(ParserTest, mapsInLists) {
+    string txt = "{ \"solar-system\": [ { \"name\": \"earth\", \"gravity\": 1.0 },"
+                                      " { \"name\": \"mars\", \"gravity\": 0.376 } ] }";
+    testParser(txt);
+}
 
-    ASSERT_TRUE(json);
+TEST(ParserTest, types) {
+    string txt = "{ \"string\": \"foo\","
+                   "\"integer\": 42,"
+                   "\"boolean\": true,"
+                   "\"map\": { \"foo\": \"bar\" },"
+                   "\"list\": [ 1, 2, 3 ],"
+                   "\"null\": null }";
+    testParser(txt);
 }
 
 };