From da0dab31c3c5af7d710064849a73f79b5311dec3 Mon Sep 17 00:00:00 2001 From: Tomek Mrugalski Date: Fri, 4 Nov 2016 14:04:44 +0100 Subject: [PATCH] [5014] Parser improved, unit-tests added. --- src/bin/dhcp6/Makefile.am | 2 +- src/bin/dhcp6/dhcp6_parser.yy | 41 +++++++++++------ src/bin/dhcp6/parser_context.cc | 11 ++++- src/bin/dhcp6/parser_context.h | 4 +- src/bin/dhcp6/tests/parser_unittest.cc | 62 +++++++++++++++++++++++--- 5 files changed, 96 insertions(+), 24 deletions(-) diff --git a/src/bin/dhcp6/Makefile.am b/src/bin/dhcp6/Makefile.am index fb1f9f6174..a7fc484843 100644 --- a/src/bin/dhcp6/Makefile.am +++ b/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 diff --git a/src/bin/dhcp6/dhcp6_parser.yy b/src/bin/dhcp6/dhcp6_parser.yy index 019d52b8a3..b9819650b7 100644 --- a/src/bin/dhcp6/dhcp6_parser.yy +++ b/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 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); } ; diff --git a/src/bin/dhcp6/parser_context.cc b/src/bin/dhcp6/parser_context.cc index fbf6d8b6dc..b58dc955ae 100644 --- a/src/bin/dhcp6/parser_context.cc +++ b/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 diff --git a/src/bin/dhcp6/parser_context.h b/src/bin/dhcp6/parser_context.h index efa8182f05..5402e211ca 100644 --- a/src/bin/dhcp6/parser_context.h +++ b/src/bin/dhcp6/parser_context.h @@ -14,7 +14,7 @@ #include // 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 stack_; + std::vector stack_; /// @brief Method called before scanning starts on a string. void scanStringBegin(); diff --git a/src/bin/dhcp6/tests/parser_unittest.cc b/src/bin/dhcp6/tests/parser_unittest.cc index c506395808..b2dbb62e75 100644 --- a/src/bin/dhcp6/tests/parser_unittest.cc +++ b/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); } }; -- 2.47.2