]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5017] Dhcp4 bison parser implemented.
authorTomek Mrugalski <tomasz@isc.org>
Wed, 14 Dec 2016 23:15:49 +0000 (00:15 +0100)
committerTomek Mrugalski <tomasz@isc.org>
Wed, 14 Dec 2016 23:15:49 +0000 (00:15 +0100)
doc/examples/kea4/classify.json
doc/examples/kea4/dhcpv4-over-dhcpv6.json
src/bin/dhcp4/Makefile.am
src/bin/dhcp4/dhcp4_lexer.ll [new file with mode: 0644]
src/bin/dhcp4/dhcp4_parser.yy [new file with mode: 0644]
src/bin/dhcp4/parser_context.cc [new file with mode: 0644]
src/bin/dhcp4/parser_context.h [new file with mode: 0644]
src/bin/dhcp4/parser_context_decl.h [new file with mode: 0644]
src/bin/dhcp4/tests/Makefile.am
src/bin/dhcp4/tests/parser_unittest.cc [new file with mode: 0644]

index 68cca11d196f6be3bb72348bb7dbcb70c07f7cca..a8f5b208f62952d764baf5507bac699737b2c933 100644 (file)
@@ -55,7 +55,7 @@
   {
       "name": "VoIP",
       "test": "substring(option[60].hex,0,6) == 'Aastra'"
-  },
+  }
 
   ],
 
index 1b4d7ed30da87a0ab325db2d5e894f5f4a474123..5a41f8afc51d295bc39e4a5fc83f91f64cf13c4c 100644 (file)
 
   "subnet4": [
    {    "subnet": "10.10.10.0/24",
-        # Don't forget the "4o6-" before "interface" here!
+# Don't forget the "4o6-" before "interface" here!
         "4o6-interface": "eno33554984",
         "4o6-subnet": "2001:db8:1:1::/64",
         "pools": [ { "pool": "10.10.10.100 - 10.10.10.199" } ] }
   ],
 
-  # This enables DHCPv4-over-DHCPv6 support
+# This enables DHCPv4-over-DHCPv6 support
   "dhcp4o6-port": 6767
 
 },
index 8360677861bf8a3e87a420956c210c58d342d481..451bbb1055dd05ada1d51f1af2b906d79f1d5344 100644 (file)
@@ -63,6 +63,10 @@ libdhcp4_la_SOURCES += dhcp4_log.cc dhcp4_log.h
 libdhcp4_la_SOURCES += dhcp4_srv.cc dhcp4_srv.h
 libdhcp4_la_SOURCES += dhcp4to6_ipc.cc dhcp4to6_ipc.h
 
+libdhcp4_la_SOURCES += dhcp4_lexer.ll location.hh position.hh stack.hh
+libdhcp4_la_SOURCES += dhcp4_parser.cc dhcp4_parser.h
+libdhcp4_la_SOURCES += parser_context.cc parser_context.h
+
 libdhcp4_la_SOURCES += kea_controller.cc
 
 nodist_libdhcp4_la_SOURCES = dhcp4_messages.h dhcp4_messages.cc
@@ -104,3 +108,30 @@ endif
 
 kea_dhcp4dir = $(pkgdatadir)
 kea_dhcp4_DATA = dhcp4.spec
+
+if GENERATE_PARSER
+
+parser: dhcp4_lexer.cc location.hh position.hh stack.hh dhcp4_parser.cc dhcp4_parser.h
+       @echo "Flex/bison files regenerated"
+
+# --- Flex/Bison stuff below --------------------------------------------------
+# When debugging grammar issues, it's useful to add -v to bison parameters.
+# bison will generate parser.output file that explains the whole grammar.
+# It can be used to manually follow what's going on in the parser.
+# This is especially useful if yydebug_ is set to 1 as that variable
+# will cause parser to print out its internal state.
+# Call flex with -s to check that the default rule can be suppressed
+# Call bison with -W to get warnings like unmarked empty rules
+# Note C++11 deprecated register still used by flex < 2.6.0
+location.hh position.hh stack.hh dhcp4_parser.cc dhcp4_parser.h: dhcp4_parser.yy
+       $(YACC) --defines=dhcp4_parser.h --report=all --report-file=dhcp4_parser.report -o dhcp4_parser.cc dhcp4_parser.yy
+
+dhcp4_lexer.cc: dhcp4_lexer.ll
+       $(LEX) --prefix parser4_ -o dhcp4_lexer.cc dhcp4_lexer.ll
+
+else
+
+parser location.hh position.hh stack.hh dhcp4_parser.cc dhcp4_parser.h dhcp4_lexer.cc:
+       @echo Parser generation disabled. Configure with --enable-generate-parser to enable it.
+
+endif
diff --git a/src/bin/dhcp4/dhcp4_lexer.ll b/src/bin/dhcp4/dhcp4_lexer.ll
new file mode 100644 (file)
index 0000000..3ddf062
--- /dev/null
@@ -0,0 +1,1105 @@
+/* Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+
+   This Source Code Form is subject to the terms of the Mozilla Public
+   License, v. 2.0. If a copy of the MPL was not distributed with this
+   file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%{ /* -*- C++ -*- */
+#include <cerrno>
+#include <climits>
+#include <cstdlib>
+#include <string>
+#include <dhcp4/parser_context.h>
+#include <asiolink/io_address.h>
+#include <boost/lexical_cast.hpp>
+#include <exceptions/exceptions.h>
+
+// Work around an incompatibility in flex (at least versions
+// 2.5.31 through 2.5.33): it generates code that does
+// not conform to C89.  See Debian bug 333231
+// <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=333231>.
+# undef yywrap
+# define yywrap() 1
+
+namespace {
+
+bool start_token_flag = false;
+
+isc::dhcp::Parser4Context::ParserType start_token_value;
+unsigned int comment_start_line = 0;
+
+};
+
+// To avoid the call to exit... oops!
+#define YY_FATAL_ERROR(msg) isc::dhcp::Parser4Context::fatal(msg)
+%}
+
+/* noyywrap disables automatic rewinding for the next file to parse. Since we
+   always parse only a single string, there's no need to do any wraps. And
+   using yywrap requires linking with -lfl, which provides the default yywrap
+   implementation that always returns 1 anyway. */
+%option noyywrap
+
+/* nounput simplifies the lexer, by removing support for putting a character
+   back into the input stream. We never use such capability anyway. */
+%option nounput
+
+/* batch means that we'll never use the generated lexer interactively. */
+%option batch
+
+/* avoid to get static global variables to remain with C++. */
+/* in last resort %option reentrant */
+
+/* Enables debug mode. To see the debug messages, one needs to also set
+   yy_flex_debug to 1, then the debug messages will be printed on stderr. */
+%option debug
+
+/* I have no idea what this option does, except it was specified in the bison
+   examples and Postgres folks added it to remove gcc 4.3 warnings. Let's
+   be on the safe side and keep it. */
+%option noinput
+
+%x COMMENT
+%x DIR_ENTER DIR_INCLUDE DIR_EXIT
+
+/* These are not token expressions yet, just convenience expressions that
+   can be used during actual token definitions. Note some can match
+   incorrect inputs (e.g., IP addresses) which must be checked. */
+int   \-?[0-9]+
+blank [ \t\r]
+
+UnicodeEscapeSequence           u[0-9A-Fa-f]{4}
+JSONEscapeCharacter             ["\\/bfnrt]
+JSONEscapeSequence              {JSONEscapeCharacter}|{UnicodeEscapeSequence}
+JSONStandardCharacter           [^\x00-\x1f"\\]
+JSONStringCharacter             {JSONStandardCharacter}|\\{JSONEscapeSequence}
+JSONString                      \"{JSONStringCharacter}*\"
+
+/* for errors */
+
+BadUnicodeEscapeSequence        u[0-9A-Fa-f]{0,3}[^0-9A-Fa-f]
+BadJSONEscapeSequence           [^"\\/bfnrtu]|{BadUnicodeEscapeSequence}
+ControlCharacter                [\x00-\x1f]
+ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
+
+%{
+// This code run each time a pattern is matched. It updates the location
+// by moving it ahead by yyleng bytes. yyleng specifies the length of the
+// currently matched token.
+#define YY_USER_ACTION  driver.loc_.columns(yyleng);
+%}
+
+%%
+
+%{
+    // This part of the code is copied over to the verbatim to the top
+    // of the generated yylex function. Explanation:
+    // http://www.gnu.org/software/bison/manual/html_node/Multiple-start_002dsymbols.html
+
+    // Code run each time yylex is called.
+    driver.loc_.step();
+
+    if (start_token_flag) {
+        start_token_flag = false;
+        switch (start_token_value) {
+        case Parser4Context::PARSER_JSON:
+        default:
+            return isc::dhcp::Dhcp4Parser::make_TOPLEVEL_JSON(driver.loc_);
+        case Parser4Context::PARSER_DHCP4:
+            return isc::dhcp::Dhcp4Parser::make_TOPLEVEL_DHCP4(driver.loc_);
+        case Parser4Context::SUBPARSER_DHCP4:
+            return isc::dhcp::Dhcp4Parser::make_SUB_DHCP4(driver.loc_);
+        case Parser4Context::PARSER_INTERFACES:
+            return isc::dhcp::Dhcp4Parser::make_SUB_INTERFACES4(driver.loc_);
+        case Parser4Context::PARSER_SUBNET4:
+            return isc::dhcp::Dhcp4Parser::make_SUB_SUBNET4(driver.loc_);
+        case Parser4Context::PARSER_POOL4:
+            return isc::dhcp::Dhcp4Parser::make_SUB_POOL4(driver.loc_);
+        case Parser4Context::PARSER_HOST_RESERVATION:
+            return isc::dhcp::Dhcp4Parser::make_SUB_RESERVATION(driver.loc_);
+        case Parser4Context::PARSER_OPTION_DATA:
+            return isc::dhcp::Dhcp4Parser::make_SUB_OPTION_DATA(driver.loc_);
+        case Parser4Context::PARSER_HOOKS_LIBRARY:
+            return isc::dhcp::Dhcp4Parser::make_SUB_HOOKS_LIBRARY(driver.loc_);
+        }
+    }
+%}
+
+#.* ;
+
+"//"(.*) ;
+
+"/*" {
+  BEGIN(COMMENT);
+  comment_start_line = driver.loc_.end.line;;
+}
+
+<COMMENT>"*/" BEGIN(INITIAL);
+<COMMENT>. ;
+<COMMENT><<EOF>> {
+    isc_throw(Dhcp4ParseError, "Comment not closed. (/* in line " << comment_start_line);
+}
+
+"<?" BEGIN(DIR_ENTER);
+<DIR_ENTER>"include" BEGIN(DIR_INCLUDE);
+<DIR_INCLUDE>\"([^\"\n])+\" {
+    // Include directive.
+
+    // Extract the filename.
+    std::string tmp(yytext+1);
+    tmp.resize(tmp.size() - 1);
+
+    driver.includeFile(tmp);
+}
+<DIR_ENTER,DIR_INCLUDE,DIR_EXIT><<EOF>> {
+    isc_throw(Dhcp4ParseError, "Directive not closed.");
+}
+<DIR_EXIT>"?>" BEGIN(INITIAL);
+
+
+<*>{blank}+   {
+    // Ok, we found a with space. Let's ignore it and update loc variable.
+    driver.loc_.step();
+}
+
+<*>[\n]+      {
+    // Newline found. Let's update the location and continue.
+    driver.loc_.lines(yyleng);
+    driver.loc_.step();
+}
+
+
+\"Dhcp6\"  {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::CONFIG:
+        return isc::dhcp::Dhcp4Parser::make_DHCP6(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("Dhcp6", driver.loc_);
+    }
+}
+
+\"interfaces-config\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+        return  isc::dhcp::Dhcp4Parser::make_INTERFACES_CONFIG(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("interfaces-config", driver.loc_);
+    }
+}
+
+\"interfaces\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::INTERFACES_CONFIG:
+        return  isc::dhcp::Dhcp4Parser::make_INTERFACES(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("interfaces", driver.loc_);
+    }
+}
+
+\"lease-database\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+        return isc::dhcp::Dhcp4Parser::make_LEASE_DATABASE(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("lease-database", driver.loc_);
+    }
+}
+
+\"hosts-database\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+        return isc::dhcp::Dhcp4Parser::make_HOSTS_DATABASE(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("hosts-database", driver.loc_);
+    }
+}
+
+\"readonly\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+        return isc::dhcp::Dhcp4Parser::make_READONLY(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("readonly", driver.loc_);
+    }
+}
+
+\"type\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::LEASE_DATABASE:
+    case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+    case isc::dhcp::Parser4Context::OPTION_DEF:
+    case isc::dhcp::Parser4Context::SERVER_ID:
+        return isc::dhcp::Dhcp4Parser::make_TYPE(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("type", driver.loc_);
+    }
+}
+
+\"user\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::LEASE_DATABASE:
+    case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+        return isc::dhcp::Dhcp4Parser::make_USER(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("user", driver.loc_);
+    }
+}
+
+\"password\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::LEASE_DATABASE:
+    case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+        return isc::dhcp::Dhcp4Parser::make_PASSWORD(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("password", driver.loc_);
+    }
+}
+
+\"host\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::LEASE_DATABASE:
+    case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+        return isc::dhcp::Dhcp4Parser::make_HOST(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("host", driver.loc_);
+    }
+}
+
+\"persist\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::LEASE_DATABASE:
+    case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+    case isc::dhcp::Parser4Context::SERVER_ID:
+        return isc::dhcp::Dhcp4Parser::make_PERSIST(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("persist", driver.loc_);
+    }
+}
+
+\"lfc-interval\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::LEASE_DATABASE:
+    case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+        return isc::dhcp::Dhcp4Parser::make_LFC_INTERVAL(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("lfc-interval", driver.loc_);
+    }
+}
+
+\"valid-lifetime\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+    case isc::dhcp::Parser4Context::SUBNET4:
+        return isc::dhcp::Dhcp4Parser::make_VALID_LIFETIME(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("valid-lifetime", driver.loc_);
+    }
+}
+
+\"renew-timer\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+    case isc::dhcp::Parser4Context::SUBNET4:
+        return isc::dhcp::Dhcp4Parser::make_RENEW_TIMER(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("renew-timer", driver.loc_);
+    }
+}
+
+\"rebind-timer\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+    case isc::dhcp::Parser4Context::SUBNET4:
+        return isc::dhcp::Dhcp4Parser::make_REBIND_TIMER(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("rebind-timer", driver.loc_);
+    }
+}
+
+\"decline-probation-period\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+        return isc::dhcp::Dhcp4Parser::make_DECLINE_PROBATION_PERIOD(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("decline-probation-period", driver.loc_);
+    }
+}
+
+\"subnet4\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+        return isc::dhcp::Dhcp4Parser::make_SUBNET4(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("subnet4", driver.loc_);
+    }
+}
+
+\"option-def\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+        return isc::dhcp::Dhcp4Parser::make_OPTION_DEF(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("option-def", driver.loc_);
+    }
+}
+
+\"option-data\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+    case isc::dhcp::Parser4Context::SUBNET4:
+    case isc::dhcp::Parser4Context::POOLS:
+    case isc::dhcp::Parser4Context::RESERVATIONS:
+    case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+    case isc::dhcp::Parser4Context::CLIENT_CLASS:
+        return isc::dhcp::Dhcp4Parser::make_OPTION_DATA(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("option-data", driver.loc_);
+    }
+}
+
+\"name\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::LEASE_DATABASE:
+    case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+    case isc::dhcp::Parser4Context::OPTION_DEF:
+    case isc::dhcp::Parser4Context::OPTION_DATA:
+    case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+    case isc::dhcp::Parser4Context::CLIENT_CLASS:
+    case isc::dhcp::Parser4Context::LOGGERS:
+        return isc::dhcp::Dhcp4Parser::make_NAME(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("name", driver.loc_);
+    }
+}
+
+\"data\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::OPTION_DATA:
+        return isc::dhcp::Dhcp4Parser::make_DATA(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("data", driver.loc_);
+    }
+}
+
+\"pools\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::SUBNET4:
+        return isc::dhcp::Dhcp4Parser::make_POOLS(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("pools", driver.loc_);
+    }
+}
+
+\"pool\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::POOLS:
+        return isc::dhcp::Dhcp4Parser::make_POOL(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("pool", driver.loc_);
+    }
+}
+
+\"subnet\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::SUBNET4:
+        return isc::dhcp::Dhcp4Parser::make_SUBNET(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("subnet", driver.loc_);
+    }
+}
+
+\"interface\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::SUBNET4:
+        return isc::dhcp::Dhcp4Parser::make_INTERFACE(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("interface", driver.loc_);
+    }
+}
+
+\"interface-id\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::SUBNET4:
+        return isc::dhcp::Dhcp4Parser::make_INTERFACE_ID(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("interface-id", driver.loc_);
+    }
+}
+
+\"id\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::SUBNET4:
+        return isc::dhcp::Dhcp4Parser::make_ID(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("id", driver.loc_);
+    }
+}
+
+\"rapid-commit\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::SUBNET4:
+        return isc::dhcp::Dhcp4Parser::make_RAPID_COMMIT(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("rapid-commit", driver.loc_);
+    }
+}
+
+\"reservation-mode\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::SUBNET4:
+        return isc::dhcp::Dhcp4Parser::make_RESERVATION_MODE(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("reservation-mode", driver.loc_);
+    }
+}
+
+\"code\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::OPTION_DEF:
+    case isc::dhcp::Parser4Context::OPTION_DATA:
+        return isc::dhcp::Dhcp4Parser::make_CODE(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("code", driver.loc_);
+    }
+}
+
+\"host-reservation-identifiers\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+        return isc::dhcp::Dhcp4Parser::make_HOST_RESERVATION_IDENTIFIERS(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("host-reservation-identifiers", driver.loc_);
+    }
+}
+
+\"Logging\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::CONFIG:
+        return isc::dhcp::Dhcp4Parser::make_LOGGING(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("Logging", driver.loc_);
+    }
+}
+
+\"loggers\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::LOGGING:
+        return isc::dhcp::Dhcp4Parser::make_LOGGERS(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("loggers", driver.loc_);
+    }
+}
+
+\"output_options\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::LOGGERS:
+        return isc::dhcp::Dhcp4Parser::make_OUTPUT_OPTIONS(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("output_options", driver.loc_);
+    }
+}
+
+\"output\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::OUTPUT_OPTIONS:
+        return isc::dhcp::Dhcp4Parser::make_OUTPUT(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("output", driver.loc_);
+    }
+}
+
+\"debuglevel\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::LOGGERS:
+        return isc::dhcp::Dhcp4Parser::make_DEBUGLEVEL(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("debuglevel", driver.loc_);
+    }
+}
+
+\"severity\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::LOGGERS:
+        return isc::dhcp::Dhcp4Parser::make_SEVERITY(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("severity", driver.loc_);
+    }
+}
+
+\"client-classes\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+    case isc::dhcp::Parser4Context::RESERVATIONS:
+        return isc::dhcp::Dhcp4Parser::make_CLIENT_CLASSES(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("client-classes", driver.loc_);
+    }
+}
+
+\"client-class\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::SUBNET4:
+    case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+        return isc::dhcp::Dhcp4Parser::make_CLIENT_CLASS(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("client-class", driver.loc_);
+    }
+}
+
+\"test\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+    case isc::dhcp::Parser4Context::CLIENT_CLASS:
+        return isc::dhcp::Dhcp4Parser::make_TEST(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("test", driver.loc_);
+    }
+}
+
+\"reservations\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::SUBNET4:
+        return isc::dhcp::Dhcp4Parser::make_RESERVATIONS(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("reservations", driver.loc_);
+    }
+}
+
+\"duid\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS:
+    case isc::dhcp::Parser4Context::RESERVATIONS:
+        return isc::dhcp::Dhcp4Parser::make_DUID(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("duid", driver.loc_);
+    }
+}
+
+\"hw-address\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS:
+    case isc::dhcp::Parser4Context::RESERVATIONS:
+        return isc::dhcp::Dhcp4Parser::make_HW_ADDRESS(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("hw-address", driver.loc_);
+    }
+}
+
+\"client-id\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS:
+    case isc::dhcp::Parser4Context::RESERVATIONS:
+        return isc::dhcp::Dhcp4Parser::make_CLIENT_ID(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("client-id", driver.loc_);
+    }
+}
+
+\"circuit-id\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS:
+    case isc::dhcp::Parser4Context::RESERVATIONS:
+        return isc::dhcp::Dhcp4Parser::make_CIRCUIT_ID(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("circuit-id", driver.loc_);
+    }
+}
+
+\"hostname\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::RESERVATIONS:
+        return isc::dhcp::Dhcp4Parser::make_HOSTNAME(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("hostname", driver.loc_);
+    }
+}
+
+\"space\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::OPTION_DEF:
+    case isc::dhcp::Parser4Context::OPTION_DATA:
+        return isc::dhcp::Dhcp4Parser::make_SPACE(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("space", driver.loc_);
+    }
+}
+
+\"csv-format\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::OPTION_DATA:
+        return isc::dhcp::Dhcp4Parser::make_CSV_FORMAT(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("csv-format", driver.loc_);
+    }
+}
+
+\"record-types\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::OPTION_DEF:
+        return isc::dhcp::Dhcp4Parser::make_RECORD_TYPES(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("record-types", driver.loc_);
+    }
+}
+
+\"encapsulate\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::OPTION_DEF:
+        return isc::dhcp::Dhcp4Parser::make_ENCAPSULATE(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("encapsulate", driver.loc_);
+    }
+}
+
+\"array\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::OPTION_DEF:
+        return isc::dhcp::Dhcp4Parser::make_ARRAY(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("array", driver.loc_);
+    }
+}
+
+\"relay\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::SUBNET4:
+        return isc::dhcp::Dhcp4Parser::make_RELAY(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("relay", driver.loc_);
+    }
+}
+
+\"ip-address\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::RELAY:
+    case isc::dhcp::Parser4Context::RESERVATIONS:
+    return isc::dhcp::Dhcp4Parser::make_IP_ADDRESS(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("ip-address", driver.loc_);
+    }
+}
+
+\"hooks-libraries\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+        return isc::dhcp::Dhcp4Parser::make_HOOKS_LIBRARIES(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("hooks-libraries", driver.loc_);
+    }
+}
+
+
+\"parameters\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::HOOKS_LIBRARIES:
+        return isc::dhcp::Dhcp4Parser::make_PARAMETERS(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("parameters", driver.loc_);
+    }
+}
+
+\"library\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::HOOKS_LIBRARIES:
+        return isc::dhcp::Dhcp4Parser::make_LIBRARY(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("library", driver.loc_);
+    }
+}
+
+\"server-id\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+        return isc::dhcp::Dhcp4Parser::make_SERVER_ID(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("server-id", driver.loc_);
+    }
+}
+
+\"identifier\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::SERVER_ID:
+        return isc::dhcp::Dhcp4Parser::make_IDENTIFIER(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("identifier", driver.loc_);
+    }
+}
+
+\"htype\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::SERVER_ID:
+        return isc::dhcp::Dhcp4Parser::make_HTYPE(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("htype", driver.loc_);
+    }
+}
+
+\"time\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::SERVER_ID:
+        return isc::dhcp::Dhcp4Parser::make_TIME(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("time", driver.loc_);
+    }
+}
+
+\"enterprise-id\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::SERVER_ID:
+        return isc::dhcp::Dhcp4Parser::make_ENTERPRISE_ID(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("enterprise-id", driver.loc_);
+    }
+}
+
+\"expired-leases-processing\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+        return isc::dhcp::Dhcp4Parser::make_EXPIRED_LEASES_PROCESSING(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("expired-leases-processing", driver.loc_);
+    }
+}
+
+\"dhcp4o6-port\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+        return isc::dhcp::Dhcp4Parser::make_DHCP4O6_PORT(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("dhcp4o6-port", driver.loc_);
+    }
+}
+
+\"version\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+        return isc::dhcp::Dhcp4Parser::make_VERSION(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("version", driver.loc_);
+    }
+}
+
+\"control-socket\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+        return isc::dhcp::Dhcp4Parser::make_CONTROL_SOCKET(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("control-socket", driver.loc_);
+    }
+}
+
+\"socket-type\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::CONTROL_SOCKET:
+        return isc::dhcp::Dhcp4Parser::make_SOCKET_TYPE(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("socket-type", driver.loc_);
+    }
+}
+
+\"socket-name\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::CONTROL_SOCKET:
+        return isc::dhcp::Dhcp4Parser::make_SOCKET_NAME(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("socket-name", driver.loc_);
+    }
+}
+
+\"dhcp-ddns\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
+        return isc::dhcp::Dhcp4Parser::make_DHCP_DDNS(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("dhcp-ddns", driver.loc_);
+    }
+}
+
+\"Dhcp4\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::CONFIG:
+        return isc::dhcp::Dhcp4Parser::make_DHCP4(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("Dhcp4", driver.loc_);
+    }
+}
+
+\"DhcpDdns\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::CONFIG:
+        return isc::dhcp::Dhcp4Parser::make_DHCPDDNS(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("DhcpDdns", driver.loc_);
+    }
+}
+
+\"4o6-interface\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::SUBNET4:
+        return isc::dhcp::Dhcp4Parser::make_SUBNET_4O6_INTERFACE(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("4o6-interface", driver.loc_);
+    }
+}
+
+\"4o6-subnet\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::SUBNET4:
+        return isc::dhcp::Dhcp4Parser::make_SUBNET_4O6_SUBNET(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("4o6-interface", driver.loc_);
+    }
+}
+
+
+{JSONString} {
+    // A string has been matched. It contains the actual string and single quotes.
+    // We need to get those quotes out of the way and just use its content, e.g.
+    // for 'foo' we should get foo
+    std::string raw(yytext+1);
+    size_t len = raw.size() - 1;
+    raw.resize(len);
+    std::string decoded;
+    decoded.reserve(len);
+    for (size_t pos = 0; pos < len; ++pos) {
+        char c = raw[pos];
+        switch (c) {
+        case '"':
+            // impossible condition
+            driver.error(driver.loc_, "Bad quote in \"" + raw + "\"");
+        case '\\':
+            ++pos;
+            if (pos >= len) {
+                // impossible condition
+                driver.error(driver.loc_, "Overflow escape in \"" + raw + "\"");
+            }
+            c = raw[pos];
+            switch (c) {
+            case '"':
+            case '\\':
+            case '/':
+                decoded.push_back(c);
+                break;
+            case 'b':
+                decoded.push_back('\b');
+                break;
+            case 'f':
+                decoded.push_back('\f');
+                break;
+            case 'n':
+                decoded.push_back('\n');
+                break;
+            case 'r':
+                decoded.push_back('\r');
+                break;
+            case 't':
+                decoded.push_back('\t');
+                break;
+            case 'u':
+                // not yet implemented
+                driver.error(driver.loc_, "Unsupported unicode escape in \"" + raw + "\"");
+            default:
+                // impossible condition
+                driver.error(driver.loc_, "Bad escape in \"" + raw + "\"");
+            }
+            break;
+        default:
+            if ((c >= 0) && (c < 0x20)) {
+                // impossible condition
+                driver.error(driver.loc_, "Invalid control in \"" + raw + "\"");
+            }
+            decoded.push_back(c);
+        }
+    }
+
+    return isc::dhcp::Dhcp4Parser::make_STRING(decoded, driver.loc_);
+}
+
+\"{JSONStringCharacter}*{ControlCharacter}{ControlCharacterFill}*\" {
+    // Bad string with a forbidden control character inside
+    driver.error(driver.loc_, "Invalid control in " + std::string(yytext));
+}
+
+\"{JSONStringCharacter}*\\{BadJSONEscapeSequence}[^\x00-\x1f"]*\" {
+    // Bad string with a bad escape inside
+    driver.error(driver.loc_, "Bad escape in " + std::string(yytext));
+}
+
+\"{JSONStringCharacter}*\\\" {
+    // Bad string with an open escape at the end
+    driver.error(driver.loc_, "Overflow escape in " + std::string(yytext));
+}
+
+"["    { return isc::dhcp::Dhcp4Parser::make_LSQUARE_BRACKET(driver.loc_); }
+"]"    { return isc::dhcp::Dhcp4Parser::make_RSQUARE_BRACKET(driver.loc_); }
+"{"    { return isc::dhcp::Dhcp4Parser::make_LCURLY_BRACKET(driver.loc_); }
+"}"    { return isc::dhcp::Dhcp4Parser::make_RCURLY_BRACKET(driver.loc_); }
+","    { return isc::dhcp::Dhcp4Parser::make_COMMA(driver.loc_); }
+":"    { return isc::dhcp::Dhcp4Parser::make_COLON(driver.loc_); }
+
+{int} {
+    // An integer was found.
+    std::string tmp(yytext);
+    int64_t integer = 0;
+    try {
+        // In substring we want to use negative values (e.g. -1).
+        // In enterprise-id we need to use values up to 0xffffffff.
+        // To cover both of those use cases, we need at least
+        // int64_t.
+        integer = boost::lexical_cast<int64_t>(tmp);
+    } catch (const boost::bad_lexical_cast &) {
+        driver.error(driver.loc_, "Failed to convert " + tmp + " to an integer.");
+    }
+
+    // The parser needs the string form as double conversion is no lossless
+    return isc::dhcp::Dhcp4Parser::make_INTEGER(integer, driver.loc_);
+}
+
+[-+]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)? {
+    // A floating point was found.
+    std::string tmp(yytext);
+    double fp = 0.0;
+    try {
+        fp = boost::lexical_cast<double>(tmp);
+    } catch (const boost::bad_lexical_cast &) {
+        driver.error(driver.loc_, "Failed to convert " + tmp + " to a floating point.");
+    }
+
+    return isc::dhcp::Dhcp4Parser::make_FLOAT(fp, driver.loc_);
+}
+
+true|false {
+    string tmp(yytext);
+    return isc::dhcp::Dhcp4Parser::make_BOOLEAN(tmp == "true", driver.loc_);
+}
+
+null {
+   return isc::dhcp::Dhcp4Parser::make_NULL_TYPE(driver.loc_);
+}
+
+<*>.   driver.error (driver.loc_, "Invalid character: " + std::string(yytext));
+
+<<EOF>> {
+    if (driver.states_.empty()) {
+        return isc::dhcp::Dhcp4Parser::make_END(driver.loc_);
+    }
+    driver.loc_ = driver.locs_.back();
+    driver.locs_.pop_back();
+    driver.file_ = driver.files_.back();
+    driver.files_.pop_back();
+    if (driver.sfile_) {
+        fclose(driver.sfile_);
+        driver.sfile_ = 0;
+    }
+    if (!driver.sfiles_.empty()) {
+        driver.sfile_ = driver.sfiles_.back();
+        driver.sfiles_.pop_back();
+    }
+    parser4__delete_buffer(YY_CURRENT_BUFFER);
+    parser4__switch_to_buffer(driver.states_.back());
+    driver.states_.pop_back();
+
+    BEGIN(DIR_EXIT);
+}
+
+%%
+
+using namespace isc::dhcp;
+
+void
+Parser4Context::scanStringBegin(const std::string& str, ParserType parser_type)
+{
+    start_token_flag = true;
+    start_token_value = parser_type;
+
+    file_ = "<string>";
+    sfile_ = 0;
+    loc_.initialize(&file_);
+    yy_flex_debug = trace_scanning_;
+    YY_BUFFER_STATE buffer;
+    buffer = yy_scan_bytes(str.c_str(), str.size());
+    if (!buffer) {
+        fatal("cannot scan string");
+        // fatal() throws an exception so this can't be reached
+    }
+}
+
+void
+Parser4Context::scanFileBegin(FILE * f,
+                              const std::string& filename,
+                              ParserType parser_type)
+{
+    start_token_flag = true;
+    start_token_value = parser_type;
+
+    file_ = filename;
+    sfile_ = f;
+    loc_.initialize(&file_);
+    yy_flex_debug = trace_scanning_;
+    YY_BUFFER_STATE buffer;
+
+    // See dhcp6_lexer.cc header for available definitions
+    buffer = parser4__create_buffer(f, 65536 /*buffer size*/);
+    if (!buffer) {
+        fatal("cannot scan file " + filename);
+    }
+    parser4__switch_to_buffer(buffer);
+}
+
+void
+Parser4Context::scanEnd() {
+    if (sfile_)
+        fclose(sfile_);
+    sfile_ = 0;
+    static_cast<void>(parser4_lex_destroy());
+    // Close files
+    while (!sfiles_.empty()) {
+        FILE* f = sfiles_.back();
+        if (f) {
+            fclose(f);
+        }
+        sfiles_.pop_back();
+    }
+    // Delete states
+    while (!states_.empty()) {
+        parser4__delete_buffer(states_.back());
+        states_.pop_back();
+    }
+}
+
+void
+Parser4Context::includeFile(const std::string& filename) {
+    if (states_.size() > 10) {
+        fatal("Too many nested include.");
+    }
+
+    FILE* f = fopen(filename.c_str(), "r");
+    if (!f) {
+        fatal("Can't open include file " + filename);
+    }
+    if (sfile_) {
+        sfiles_.push_back(sfile_);
+    }
+    sfile_ = f;
+    states_.push_back(YY_CURRENT_BUFFER);
+    YY_BUFFER_STATE buffer;
+    buffer = parser4__create_buffer(f, 65536 /*buffer size*/);
+    if (!buffer) {
+        fatal( "Can't scan include file " + filename);
+    }
+    parser4__switch_to_buffer(buffer);
+    files_.push_back(file_);
+    file_ = filename;
+    locs_.push_back(loc_);
+    loc_.initialize(&file_);
+
+    BEGIN(INITIAL);
+}
+
+namespace {
+/// To avoid unused function error
+class Dummy {
+    // cppcheck-suppress unusedPrivateFunction
+    void dummy() { yy_fatal_error("Fix me: how to disable its definition?"); }
+};
+}
diff --git a/src/bin/dhcp4/dhcp4_parser.yy b/src/bin/dhcp4/dhcp4_parser.yy
new file mode 100644 (file)
index 0000000..a7fe3c8
--- /dev/null
@@ -0,0 +1,1466 @@
+/* Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+
+   This Source Code Form is subject to the terms of the Mozilla Public
+   License, v. 2.0. If a copy of the MPL was not distributed with this
+   file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%skeleton "lalr1.cc" /* -*- C++ -*- */
+%require "3.0.0"
+%defines
+%define parser_class_name {Dhcp4Parser}
+%define api.prefix {parser4_}
+%define api.token.constructor
+%define api.value.type variant
+%define api.namespace {isc::dhcp}
+%define parse.assert
+%code requires
+{
+#include <string>
+#include <cc/data.h>
+#include <dhcp/option.h>
+#include <boost/lexical_cast.hpp>
+#include <dhcp4/parser_context_decl.h>
+
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace std;
+}
+// The parsing context.
+%param { isc::dhcp::Parser4Context& ctx }
+%locations
+%define parse.trace
+%define parse.error verbose
+%code
+{
+#include <dhcp4/parser_context.h>
+}
+
+
+%define api.token.prefix {TOKEN_}
+// Tokens in an order which makes sense and related to the intented use.
+// Actual regexps for tokens are defined in dhcp4_lexer.ll.
+%token
+  END  0  "end of file"
+  COMMA ","
+  COLON ":"
+  LSQUARE_BRACKET "["
+  RSQUARE_BRACKET "]"
+  LCURLY_BRACKET "{"
+  RCURLY_BRACKET "}"
+  NULL_TYPE "null"
+
+  DHCP4 "Dhcp4"
+  INTERFACES_CONFIG "interfaces-config"
+  INTERFACES "interfaces"
+
+  LEASE_DATABASE "lease-database"
+  HOSTS_DATABASE "hosts-database"
+  TYPE "type"
+  USER "user"
+  PASSWORD "password"
+  HOST "host"
+  PERSIST "persist"
+  LFC_INTERVAL "lfc-interval"
+  READONLY "readonly"
+
+  VALID_LIFETIME "valid-lifetime"
+  RENEW_TIMER "renew-timer"
+  REBIND_TIMER "rebind-timer"
+  DECLINE_PROBATION_PERIOD "decline-probation-period"
+  SUBNET4 "subnet4"
+  SUBNET_4O6_INTERFACE "4o6-interface"
+  SUBNET_4O6_SUBNET "4o6-subnet"
+  OPTION_DEF "option-def"
+  OPTION_DATA "option-data"
+  NAME "name"
+  DATA "data"
+  CODE "code"
+  SPACE "space"
+  CSV_FORMAT "csv-format"
+  RECORD_TYPES "record-types"
+  ENCAPSULATE "encapsulate"
+  ARRAY "array"
+
+  POOLS "pools"
+  POOL "pool"
+
+  SUBNET "subnet"
+  INTERFACE "interface"
+  INTERFACE_ID "interface-id"
+  ID "id"
+  RAPID_COMMIT "rapid-commit"
+  RESERVATION_MODE "reservation-mode"
+
+  HOST_RESERVATION_IDENTIFIERS "host-reservation-identifiers"
+
+  CLIENT_CLASSES "client-classes"
+  TEST "test"
+  CLIENT_CLASS "client-class"
+
+  RESERVATIONS "reservations"
+  DUID "duid"
+  HW_ADDRESS "hw-address"
+  CIRCUIT_ID "circuit-id"
+  CLIENT_ID "client-id"
+  HOSTNAME "hostname"
+
+  RELAY "relay"
+  IP_ADDRESS "ip-address"
+
+  HOOKS_LIBRARIES "hooks-libraries"
+  LIBRARY "library"
+  PARAMETERS "parameters"
+
+  EXPIRED_LEASES_PROCESSING "expired-leases-processing"
+
+  SERVER_ID "server-id"
+  IDENTIFIER "identifier"
+  HTYPE "htype"
+  TIME "time"
+  ENTERPRISE_ID "enterprise-id"
+
+  DHCP4O6_PORT "dhcp4o6-port"
+  VERSION "version"
+
+  CONTROL_SOCKET "control-socket"
+  SOCKET_TYPE "socket-type"
+  SOCKET_NAME "socket-name"
+
+  DHCP_DDNS "dhcp-ddns"
+
+ /// @todo: Implement proper parsing for those parameters in Dhcp4/dhcp-ddns/*.
+ /// This should be part of the #5043 ticket. Listing the keywords here for
+ /// completeness.
+
+ // These are tokens defined in Dhcp4/dhcp-ddns/*
+ // They're not
+ //  ENABLE_UPDATES "enable-updates"
+ //  SERVER_IP "server-ip"
+ //  SENDER_IP "sender-ip"
+ //  SENDER_PORT "sender-port"
+ //  MAX_QUEUE_SIZE "max-queue-size"
+ //  NCR_PROTOCOL "ncr-protocol"
+ //  NCR_FORMAT "ncr-format"
+ //  ALWAYS_INCLUDE_FQDN "always-include-fqdn"
+ //  OVERRDIDE_NO_UPDATE "override-no-update"
+ //  OVERRDIDE_CLIENT_UPDATE "override-client-update"
+ //  REPLACE_CLIENT_NAME "replace-client-name"
+ //  GENERATED_PREFIX "generated-prefix"
+ //  QUALIFYING_SUFFIX "qualifying-suffix"
+
+  LOGGING "Logging"
+  LOGGERS "loggers"
+  OUTPUT_OPTIONS "output_options"
+  OUTPUT "output"
+  DEBUGLEVEL "debuglevel"
+  SEVERITY "severity"
+
+  DHCP6 "Dhcp6"
+  DHCPDDNS "DhcpDdns"
+
+ // Not real tokens, just a way to signal what the parser is expected to
+ // parse.
+  TOPLEVEL_JSON
+  TOPLEVEL_DHCP4
+  SUB_DHCP4
+  SUB_INTERFACES4
+  SUB_SUBNET4
+  SUB_POOL4
+  SUB_RESERVATION
+  SUB_OPTION_DEF
+  SUB_OPTION_DATA
+  SUB_HOOKS_LIBRARY
+;
+
+%token <std::string> STRING "constant string"
+%token <int64_t> INTEGER "integer"
+%token <double> FLOAT "floating point"
+%token <bool> BOOLEAN "boolean"
+
+%type <ElementPtr> value
+%type <ElementPtr> version_value
+
+%printer { yyoutput << $$; } <*>;
+
+%%
+
+// The whole grammar starts with a map, because the config file
+// constists of Dhcp, Logger and DhcpDdns entries in one big { }.
+// We made the same for subparsers at the exception of the JSON value.
+%start start;
+
+start: TOPLEVEL_JSON { ctx.ctx_ = ctx.NO_KEYWORD; } sub_json
+     | TOPLEVEL_DHCP4 { ctx.ctx_ = ctx.CONFIG; } syntax_map
+     | SUB_DHCP4 { ctx.ctx_ = ctx.DHCP4; } sub_dhcp4
+     | SUB_INTERFACES4 { ctx.ctx_ = ctx.INTERFACES_CONFIG; } sub_interfaces4
+     | SUB_SUBNET4 { ctx.ctx_ = ctx.SUBNET4; } sub_subnet4
+     | SUB_POOL4 { ctx.ctx_ = ctx.POOLS; } sub_pool4
+     | SUB_RESERVATION { ctx.ctx_ = ctx.RESERVATIONS; } sub_reservation
+     | SUB_OPTION_DEF { ctx.ctx_ = ctx.OPTION_DEF; } sub_option_def
+     | SUB_OPTION_DATA { ctx.ctx_ = ctx.OPTION_DATA; } sub_option_data
+     | SUB_HOOKS_LIBRARY { ctx.ctx_ = ctx.HOOKS_LIBRARIES; } sub_hooks_library
+     ;
+
+// ---- generic JSON parser ---------------------------------
+
+// Note that ctx_ is NO_KEYWORD here
+
+// Values rule
+value: INTEGER { $$ = ElementPtr(new IntElement($1, ctx.loc2pos(@1))); }
+     | FLOAT { $$ = ElementPtr(new DoubleElement($1, ctx.loc2pos(@1))); }
+     | BOOLEAN { $$ = ElementPtr(new BoolElement($1, ctx.loc2pos(@1))); }
+     | STRING { $$ = ElementPtr(new StringElement($1, ctx.loc2pos(@1))); }
+     | NULL_TYPE { $$ = ElementPtr(new NullElement(ctx.loc2pos(@1))); }
+     | map2 { $$ = ctx.stack_.back(); ctx.stack_.pop_back(); }
+     | list_generic { $$ = ctx.stack_.back(); ctx.stack_.pop_back(); }
+     ;
+
+sub_json: value {
+    // Push back the JSON value on the stack
+    ctx.stack_.push_back($1);
+};
+
+map2: LCURLY_BRACKET {
+    // This code is executed when we're about to start parsing
+    // the content of the map
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    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: %empty // empty map
+           | not_empty_map
+           ;
+
+not_empty_map: STRING COLON value {
+                  // map containing a single entry
+                  ctx.stack_.back()->set($1, $3);
+                  }
+             | not_empty_map COMMA STRING COLON value {
+                  // map consisting of a shorter map followed by
+                  // comma and string:value
+                  ctx.stack_.back()->set($3, $5);
+                  }
+             ;
+
+list_generic: LSQUARE_BRACKET {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(l);
+} list_content RSQUARE_BRACKET {
+    // list parsing complete. Put any sanity checking here
+};
+
+// This one is used in syntax parser.
+list2: LSQUARE_BRACKET {
+    // List parsing about to start
+} list_content RSQUARE_BRACKET {
+    // list parsing complete. Put any sanity checking here
+    //ctx.stack_.pop_back();
+};
+
+list_content: %empty // Empty list
+            | not_empty_list
+            ;
+
+not_empty_list: value {
+                  // List consisting of a single element.
+                  ctx.stack_.back()->add($1);
+                  }
+              | not_empty_list COMMA value {
+                  // List ending with , and a value.
+                  ctx.stack_.back()->add($3);
+                  }
+              ;
+
+// ---- generic JSON parser ends here ----------------------------------
+
+// ---- syntax checking parser starts here -----------------------------
+
+// Unknown keyword in a map
+unknown_map_entry: STRING COLON {
+    const std::string& where = ctx.contextName();
+    const std::string& keyword = $1;
+    error(@1,
+          "got unexpected keyword \"" + keyword + "\" in " + where + " map.");
+};
+
+
+// This defines the top-level { } that holds Dhcp6, Dhcp4, DhcpDdns or Logging
+// objects.
+syntax_map: LCURLY_BRACKET {
+    // This code is executed when we're about to start parsing
+    // the content of the map
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(m);
+} global_objects 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.
+};
+
+// This represents top-level entries: Dhcp6, Dhcp4, DhcpDdns, Logging
+global_objects: global_object
+              | global_objects COMMA global_object
+              ;
+
+// This represents a single top level entry, e.g. Dhcp6 or DhcpDdns.
+global_object: dhcp4_object
+             | logging_object
+             | dhcp6_json_object
+             | dhcpddns_json_object
+             | unknown_map_entry
+             ;
+
+dhcp4_object: DHCP4 {
+    // This code is executed when we're about to start parsing
+    // the content of the map
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("Dhcp4", m);
+    ctx.stack_.push_back(m);
+    ctx.enter(ctx.DHCP4);
+} COLON LCURLY_BRACKET global_params 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.
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+// subparser: similar to the corresponding rule but without parent
+// so the stack is empty at the rule entry.
+sub_dhcp4: LCURLY_BRACKET {
+    // Parse the Dhcp6 map
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(m);
+} global_params RCURLY_BRACKET {
+    // parsing completed
+};
+
+global_params: global_param
+             | global_params COMMA global_param
+             ;
+
+// These are the parameters that are allowed in the top-level for
+// Dhcp4.
+global_param: valid_lifetime
+            | renew_timer
+            | rebind_timer
+            | decline_probation_period
+            | subnet4_list
+            | interfaces_config
+            | lease_database
+            | hosts_database
+            | host_reservation_identifiers
+            | client_classes
+            | option_def_list
+            | option_data_list
+            | hooks_libraries
+            | expired_leases_processing
+            | server_id
+            | dhcp4o6_port
+            | version
+            | control_socket
+            | dhcp_ddns
+            | unknown_map_entry
+            ;
+
+valid_lifetime: VALID_LIFETIME COLON INTEGER {
+    ElementPtr prf(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("valid-lifetime", prf);
+};
+
+renew_timer: RENEW_TIMER COLON INTEGER {
+    ElementPtr prf(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("renew-timer", prf);
+};
+
+rebind_timer: REBIND_TIMER COLON INTEGER {
+    ElementPtr prf(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("rebind-timer", prf);
+};
+
+decline_probation_period: DECLINE_PROBATION_PERIOD COLON INTEGER {
+    ElementPtr dpp(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("decline-probation-period", dpp);
+};
+
+interfaces_config: INTERFACES_CONFIG {
+    ElementPtr i(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("interfaces-config", i);
+    ctx.stack_.push_back(i);
+    ctx.enter(ctx.INTERFACES_CONFIG);
+} COLON LCURLY_BRACKET interface_config_map RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+sub_interfaces4: LCURLY_BRACKET {
+    // Parse the interfaces-config map
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(m);
+} interface_config_map RCURLY_BRACKET {
+    // parsing completed
+};
+
+interface_config_map: INTERFACES {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("interfaces", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON list2 {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+lease_database: LEASE_DATABASE {
+    ElementPtr i(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("lease-database", i);
+    ctx.stack_.push_back(i);
+    ctx.enter(ctx.LEASE_DATABASE);
+} COLON LCURLY_BRACKET database_map_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+hosts_database: HOSTS_DATABASE {
+    ElementPtr i(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("hosts-database", i);
+    ctx.stack_.push_back(i);
+    ctx.enter(ctx.HOSTS_DATABASE);
+} COLON LCURLY_BRACKET database_map_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+database_map_params: database_map_param
+                   | database_map_params COMMA database_map_param
+                   ;
+
+database_map_param: type
+                  | user
+                  | password
+                  | host
+                  | name
+                  | persist
+                  | lfc_interval
+                  | readonly
+                  | unknown_map_entry
+;
+
+type: TYPE {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr prf(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("type", prf);
+    ctx.leave();
+};
+
+user: USER {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr user(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("user", user);
+    ctx.leave();
+};
+
+password: PASSWORD {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr pwd(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("password", pwd);
+    ctx.leave();
+};
+
+host: HOST {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr h(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("host", h);
+    ctx.leave();
+};
+
+name: NAME {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr name(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("name", name);
+    ctx.leave();
+};
+
+persist: PERSIST COLON BOOLEAN {
+    ElementPtr n(new BoolElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("persist", n);
+};
+
+lfc_interval: LFC_INTERVAL COLON INTEGER {
+    ElementPtr n(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("lfc-interval", n);
+};
+
+readonly: READONLY COLON BOOLEAN {
+    ElementPtr n(new BoolElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("readonly", n);
+};
+
+duid_id : DUID {
+    ElementPtr duid(new StringElement("duid", ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(duid);
+};
+
+host_reservation_identifiers: HOST_RESERVATION_IDENTIFIERS {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("host-reservation-identifiers", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.HOST_RESERVATION_IDENTIFIERS);
+} COLON LSQUARE_BRACKET host_reservation_identifiers_list RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+host_reservation_identifiers_list: host_reservation_identifier
+    | host_reservation_identifiers_list COMMA host_reservation_identifier
+    ;
+
+host_reservation_identifier: duid_id
+                           | hw_address_id
+                           | circuit_id
+                           | client_id
+                           ;
+
+hw_address_id : HW_ADDRESS {
+    ElementPtr hwaddr(new StringElement("hw-address", ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(hwaddr);
+};
+
+circuit_id : CIRCUIT_ID {
+    ElementPtr circuit(new StringElement("circuit-id", ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(circuit);
+};
+
+client_id : CLIENT_ID {
+    ElementPtr client(new StringElement("client-id", ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(client);
+};
+
+hooks_libraries: HOOKS_LIBRARIES {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("hooks-libraries", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.HOOKS_LIBRARIES);
+} COLON LSQUARE_BRACKET hooks_libraries_list RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+hooks_libraries_list: %empty
+                    | not_empty_hooks_libraries_list
+                    ;
+
+not_empty_hooks_libraries_list: hooks_library
+    | not_empty_hooks_libraries_list COMMA hooks_library
+    ;
+
+hooks_library: LCURLY_BRACKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(m);
+    ctx.stack_.push_back(m);
+} hooks_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+};
+
+sub_hooks_library: LCURLY_BRACKET {
+    // Parse the hooks-libraries list entry map
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(m);
+} hooks_params RCURLY_BRACKET {
+    // parsing completed
+};
+
+hooks_params: hooks_param
+            | hooks_params COMMA hooks_param
+            | unknown_map_entry
+            ;
+
+hooks_param: library
+           | parameters
+           ;
+
+library: LIBRARY {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr lib(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("library", lib);
+    ctx.leave();
+};
+
+parameters: PARAMETERS {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON value {
+    ctx.stack_.back()->set("parameters", $4);
+    ctx.leave();
+};
+
+// --- expired-leases-processing ------------------------
+expired_leases_processing: EXPIRED_LEASES_PROCESSING {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("expired-leases-processing", m);
+    ctx.stack_.push_back(m);
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON LCURLY_BRACKET expired_leases_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+expired_leases_params: expired_leases_param
+                     | expired_leases_params COMMA expired_leases_param
+                     ;
+
+// This is a bit of a simplification. But it can also serve as an example.
+// Instead of explicitly listing all allowed expired leases parameters, we
+// simply say that all of them as integers.
+expired_leases_param: STRING COLON INTEGER {
+    ElementPtr value(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set($1, value);
+};
+
+// --- subnet4 ------------------------------------------
+// This defines subnet4 as a list of maps.
+// "subnet4": [ ... ]
+subnet4_list: SUBNET4 {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("subnet4", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.SUBNET4);
+} COLON LSQUARE_BRACKET subnet4_list_content RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+// This defines the ... in "subnet6": [ ... ]
+// It can either be empty (no subnets defined), have one subnet
+// or have multiple subnets separate by comma.
+subnet4_list_content: %empty
+                    | not_empty_subnet4_list
+                    ;
+
+not_empty_subnet4_list: subnet4
+                      | not_empty_subnet4_list COMMA subnet4
+                      ;
+
+// --- Subnet definitions -------------------------------
+
+// This defines a single subnet, i.e. a single map with
+// subnet6 array.
+subnet4: LCURLY_BRACKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(m);
+    ctx.stack_.push_back(m);
+} subnet4_params RCURLY_BRACKET {
+    // Once we reached this place, the subnet parsing is now complete.
+    // If we want to, we can implement default values here.
+    // In particular we can do things like this:
+    // if (!ctx.stack_.back()->get("interface")) {
+    //     ctx.stack_.back()->set("interface", StringElement("loopback"));
+    // }
+    //
+    // We can also stack up one level (Dhcp6) and copy over whatever
+    // global parameters we want to:
+    // if (!ctx.stack_.back()->get("renew-timer")) {
+    //     ElementPtr renew = ctx_stack_[...].get("renew-timer");
+    //     if (renew) {
+    //         ctx.stack_.back()->set("renew-timer", renew);
+    //     }
+    // }
+    ctx.stack_.pop_back();
+};
+
+sub_subnet4: LCURLY_BRACKET {
+    // Parse the subnet6 list entry map
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(m);
+} subnet4_params RCURLY_BRACKET {
+    // parsing completed
+};
+
+// This defines that subnet can have one or more parameters.
+subnet4_params: subnet4_param
+              | subnet4_params COMMA subnet4_param
+              ;
+
+// This defines a list of allowed parameters for each subnet.
+subnet4_param: valid_lifetime
+             | renew_timer
+             | rebind_timer
+             | option_data_list
+             | pools_list
+             | subnet
+             | interface
+             | interface_id
+             | id
+             | rapid_commit
+             | client_class
+             | reservations
+             | reservation_mode
+             | relay
+             | subnet_4o6_interface
+             | subnet_4o6_subnet
+             | unknown_map_entry
+             ;
+
+subnet: SUBNET {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr subnet(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("subnet", subnet);
+    ctx.leave();
+};
+
+subnet_4o6_interface: SUBNET_4O6_INTERFACE {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr iface(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("4o6-interface", iface);
+    ctx.leave();
+};
+
+subnet_4o6_subnet: SUBNET_4O6_SUBNET {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr iface(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("4o6-subnet", iface);
+    ctx.leave();
+};
+
+interface: INTERFACE {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr iface(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("interface", iface);
+    ctx.leave();
+};
+
+interface_id: INTERFACE_ID {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr iface(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("interface-id", iface);
+    ctx.leave();
+};
+
+client_class: CLIENT_CLASS {
+    ctx.enter(ctx.CLIENT_CLASS);
+} COLON STRING {
+    ElementPtr cls(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("client-class", cls);
+    ctx.leave();
+};
+
+reservation_mode: RESERVATION_MODE {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr rm(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("reservation-mode", rm);
+    ctx.leave();
+};
+
+id: ID COLON INTEGER {
+    ElementPtr id(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("id", id);
+};
+
+rapid_commit: RAPID_COMMIT COLON BOOLEAN {
+    ElementPtr rc(new BoolElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("rapid-commit", rc);
+};
+
+// ---- option-def --------------------------
+
+// This defines the "option-def": [ ... ] entry that may appear
+// at a global option.
+option_def_list: OPTION_DEF {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("option-def", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.OPTION_DEF);
+} COLON LSQUARE_BRACKET option_def_list_content RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+// This defines the content of option-def. It may be empty,
+// have one entry or multiple entries separated by comma.
+option_def_list_content: %empty
+                       | not_empty_option_def_list
+                       ;
+
+not_empty_option_def_list: option_def_entry
+                         | not_empty_option_def_list COMMA option_def_entry
+                          ;
+
+// This defines the content of a single entry { ... } within
+// option-def list.
+option_def_entry: LCURLY_BRACKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(m);
+    ctx.stack_.push_back(m);
+} option_def_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+};
+
+// This defines the top level scope when the parser is told to parse a single
+// option definition. It's almost exactly the same as option_def_entry, except
+// that it does leave its value on stack.
+sub_option_def: LCURLY_BRACKET {
+    // Parse the option-def list entry map
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(m);
+} option_def_params RCURLY_BRACKET {
+    // parsing completed
+};
+
+// This defines parameters specified inside the map that itself
+// is an entry in option-def list.
+option_def_params: %empty
+                 | not_empty_option_def_params
+                 ;
+
+not_empty_option_def_params: option_def_param
+                           | not_empty_option_def_params COMMA option_def_param
+                           ;
+
+option_def_param: option_def_name
+                | option_def_code
+                | option_def_type
+                | option_def_record_types
+                | option_def_space
+                | option_def_encapsulate
+                | option_def_array
+                | unknown_map_entry
+                ;
+
+option_def_name: name;
+
+code: CODE COLON INTEGER {
+    ElementPtr code(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("code", code);
+};
+
+option_def_code: code;
+
+option_def_type: type;
+
+option_def_record_types: RECORD_TYPES {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr rtypes(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("record-types", rtypes);
+    ctx.leave();
+};
+
+space: SPACE {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr space(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("space", space);
+    ctx.leave();
+};
+
+option_def_space: space;
+
+option_def_encapsulate: ENCAPSULATE {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr encap(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("encapsulate", encap);
+    ctx.leave();
+};
+
+option_def_array: ARRAY COLON BOOLEAN {
+    ElementPtr array(new BoolElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("array", array);
+};
+
+// ---- option-data --------------------------
+
+// This defines the "option-data": [ ... ] entry that may appear
+// in several places, but most notably in subnet4 entries.
+option_data_list: OPTION_DATA {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("option-data", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.OPTION_DATA);
+} COLON LSQUARE_BRACKET option_data_list_content RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+// This defines the content of option-data. It may be empty,
+// have one entry or multiple entries separated by comma.
+option_data_list_content: %empty
+                        | not_empty_option_data_list
+                        ;
+
+// This defines the content of option-data list. It can either
+// be a single value or multiple entries separated by comma.
+not_empty_option_data_list: option_data_entry
+                          | not_empty_option_data_list COMMA option_data_entry
+                          ;
+
+// This defines th content of a single entry { ... } within
+// option-data list.
+option_data_entry: LCURLY_BRACKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(m);
+    ctx.stack_.push_back(m);
+} option_data_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+};
+
+// This defines the top level scope when the parser is told to parse a single
+// option data. It's almost exactly the same as option_data_entry, except
+// that it does leave its value on stack.
+sub_option_data: LCURLY_BRACKET {
+    // Parse the option-data list entry map
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(m);
+} option_data_params RCURLY_BRACKET {
+    // parsing completed
+};
+
+// This defines parameters specified inside the map that itself
+// is an entry in option-data list. It can either be empty
+// or have a non-empty list of parameters.
+option_data_params: %empty
+                  | not_empty_option_data_params
+                  ;
+
+// Those parameters can either be a single parameter or
+// a list of parameters separated by comma.
+not_empty_option_data_params: option_data_param
+    | not_empty_option_data_params COMMA option_data_param
+    ;
+
+// Each single option-data parameter can be one of the following
+// expressions.
+option_data_param: option_data_name
+                 | option_data_data
+                 | option_data_code
+                 | option_data_space
+                 | option_data_csv_format
+                 | unknown_map_entry
+                 ;
+
+option_data_name: name;
+
+option_data_data: DATA {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr data(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("data", data);
+    ctx.leave();
+};
+
+option_data_code: code;
+
+option_data_space: space;
+
+option_data_csv_format: CSV_FORMAT COLON BOOLEAN {
+    ElementPtr space(new BoolElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("csv-format", space);
+};
+
+// ---- pools ------------------------------------
+
+// This defines the "pools": [ ... ] entry that may appear in subnet4.
+pools_list: POOLS {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("pools", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.POOLS);
+} COLON LSQUARE_BRACKET pools_list_content RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+// Pools may be empty, contain a single pool entry or multiple entries
+// separate by commas.
+pools_list_content: %empty
+                  | not_empty_pools_list
+                  ;
+
+not_empty_pools_list: pool_list_entry
+                    | not_empty_pools_list COMMA pool_list_entry
+                    ;
+
+pool_list_entry: LCURLY_BRACKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(m);
+    ctx.stack_.push_back(m);
+} pool_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+};
+
+sub_pool4: LCURLY_BRACKET {
+    // Parse the pool list entry map
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(m);
+} pool_params RCURLY_BRACKET {
+    // parsing completed
+};
+
+pool_params: pool_param
+           | pool_params COMMA pool_param
+           ;
+
+pool_param: pool_entry
+          | option_data_list
+          | unknown_map_entry
+          ;
+
+pool_entry: POOL {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr pool(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("pool", pool);
+    ctx.leave();
+};
+
+// --- end of pools definition -------------------------------
+
+// --- reservations ------------------------------------------
+reservations: RESERVATIONS {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("reservations", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.RESERVATIONS);
+} COLON LSQUARE_BRACKET reservations_list RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+reservations_list: %empty
+                 | not_empty_reservations_list
+                 ;
+
+not_empty_reservations_list: reservation
+                           | not_empty_reservations_list COMMA reservation
+                           ;
+
+reservation: LCURLY_BRACKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(m);
+    ctx.stack_.push_back(m);
+} reservation_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+};
+
+sub_reservation: LCURLY_BRACKET {
+    // Parse the reservations list entry map
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(m);
+} reservation_params RCURLY_BRACKET {
+    // parsing completed
+};
+
+reservation_params: %empty
+                  | not_empty_reservation_params
+                  ;
+
+not_empty_reservation_params: reservation_param
+    | not_empty_reservation_params COMMA reservation_param
+    ;
+
+// @todo probably need to add mac-address as well here
+reservation_param: duid
+                 | reservation_client_classes
+                 | client_id_value
+                 | circuit_id_value
+                 | ip_address
+                 | hw_address
+                 | hostname
+                 | option_data_list
+                 | unknown_map_entry
+                 ;
+
+ip_address: IP_ADDRESS {
+} COLON STRING {
+    ElementPtr addr(new StringElement($4, ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("ip-address", addr);
+};
+
+duid: DUID {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr d(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("duid", d);
+    ctx.leave();
+};
+
+hw_address: HW_ADDRESS {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr hw(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("hw-address", hw);
+    ctx.leave();
+};
+
+client_id_value: CLIENT_ID {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr hw(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("client-id", hw);
+    ctx.leave();
+};
+
+circuit_id_value: CIRCUIT_ID {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr hw(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("circuit-id", hw);
+    ctx.leave();
+};
+
+
+hostname: HOSTNAME {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr host(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("hostname", host);
+    ctx.leave();
+};
+
+reservation_client_classes: CLIENT_CLASSES {
+    ElementPtr c(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("client-classes", c);
+    ctx.stack_.push_back(c);
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON list2 {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+// --- end of reservations definitions -----------------------
+
+// --- relay -------------------------------------------------
+relay: RELAY {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("relay", m);
+    ctx.stack_.push_back(m);
+    ctx.enter(ctx.RELAY);
+} COLON LCURLY_BRACKET relay_map RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+relay_map: IP_ADDRESS {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr ip(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("ip-address", ip);
+    ctx.leave();
+};
+
+// --- end of relay definitions ------------------------------
+
+// --- client classes ----------------------------------------
+client_classes: CLIENT_CLASSES {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("client-classes", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.CLIENT_CLASSES);
+} COLON LSQUARE_BRACKET client_classes_list RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+client_classes_list: client_class
+                   | client_classes_list COMMA client_class
+                   ;
+
+client_class: LCURLY_BRACKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(m);
+    ctx.stack_.push_back(m);
+} client_class_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+};
+
+client_class_params: %empty
+                   | not_empty_client_class_params
+                   ;
+
+not_empty_client_class_params: client_class_param
+    | not_empty_client_class_params COMMA client_class_param
+    ;
+
+client_class_param: client_class_name
+                  | client_class_test
+                  | option_data_list
+                  | unknown_map_entry
+                  ;
+
+client_class_name: name;
+
+client_class_test: TEST {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr test(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("test", test);
+    ctx.leave();
+};
+
+// --- end of client classes ---------------------------------
+
+// --- server-id ---------------------------------------------
+server_id: SERVER_ID {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("server-id", m);
+    ctx.stack_.push_back(m);
+    ctx.enter(ctx.SERVER_ID);
+} COLON LCURLY_BRACKET server_id_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+server_id_params: server_id_param
+                | server_id_params COMMA server_id_param
+                ;
+
+server_id_param: type
+               | identifier
+               | time
+               | htype
+               | enterprise_id
+               | persist
+               | unknown_map_entry
+               ;
+
+htype: HTYPE COLON INTEGER {
+    ElementPtr htype(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("htype", htype);
+};
+
+identifier: IDENTIFIER {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr id(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("identifier", id);
+    ctx.leave();
+};
+
+time: TIME COLON INTEGER {
+    ElementPtr time(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("time", time);
+};
+
+enterprise_id: ENTERPRISE_ID COLON INTEGER {
+    ElementPtr time(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("enterprise-id", time);
+};
+
+// --- end of server-id --------------------------------------
+
+dhcp4o6_port: DHCP4O6_PORT COLON INTEGER {
+    ElementPtr time(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("dhcp4o6-port", time);
+};
+
+// code says it is a string, unit test a number
+version: VERSION {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON version_value {
+    ctx.stack_.back()->set("version", $4);
+    ctx.leave();
+};
+
+version_value:
+    INTEGER { $$ = ElementPtr(new IntElement($1, ctx.loc2pos(@1))); }
+  | FLOAT { $$ = ElementPtr(new DoubleElement($1, ctx.loc2pos(@1))); }
+  | STRING { $$ = ElementPtr(new StringElement($1, ctx.loc2pos(@1))); }
+  ;
+
+// --- control socket ----------------------------------------
+
+control_socket: CONTROL_SOCKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("control-socket", m);
+    ctx.stack_.push_back(m);
+    ctx.enter(ctx.CONTROL_SOCKET);
+} COLON LCURLY_BRACKET control_socket_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+control_socket_params: control_socket_param
+                     | control_socket_params COMMA control_socket_param
+                     ;
+
+control_socket_param: socket_type
+                    | socket_name
+                    ;
+
+socket_type: SOCKET_TYPE {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr stype(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("socket-type", stype);
+    ctx.leave();
+};
+
+socket_name: SOCKET_NAME {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr name(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("socket-name", name);
+    ctx.leave();
+};
+
+// --- dhcp ddns ---------------------------------------------
+
+dhcp_ddns: DHCP_DDNS {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("dhcp-ddns", m);
+    ctx.stack_.push_back(m);
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON LCURLY_BRACKET not_empty_map RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+// JSON entries for Dhcp4 and DhcpDdns
+
+dhcp6_json_object: DHCP6 {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON value {
+    ctx.stack_.back()->set("Dhcp6", $4);
+    ctx.leave();
+};
+
+dhcpddns_json_object: DHCPDDNS {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON value {
+    ctx.stack_.back()->set("DhcpDdns", $4);
+    ctx.leave();
+};
+
+// --- logging entry -----------------------------------------
+
+// This defines the top level "Logging" object. It parses
+// the following "Logging": { ... }. The ... is defined
+// by logging_params
+logging_object: LOGGING {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("Logging", m);
+    ctx.stack_.push_back(m);
+    ctx.enter(ctx.LOGGING);
+} COLON LCURLY_BRACKET logging_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+// This defines the list of allowed parameters that may appear
+// in the top-level Logging object. It can either be a single
+// parameter or several parameters separated by commas.
+logging_params: logging_param
+              | logging_params COMMA logging_param
+              ;
+
+// There's currently only one parameter defined, which is "loggers".
+logging_param: loggers;
+
+// "loggers", the only parameter currently defined in "Logging" object,
+// is "Loggers": [ ... ].
+loggers: LOGGERS {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("loggers", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.LOGGERS);
+}  COLON LSQUARE_BRACKET loggers_entries RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+// These are the parameters allowed in loggers: either one logger
+// entry or multiple entries separate by commas.
+loggers_entries: logger_entry
+               | loggers_entries COMMA logger_entry
+               ;
+
+// This defines a single entry defined in loggers in Logging.
+logger_entry: LCURLY_BRACKET {
+    ElementPtr l(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(l);
+    ctx.stack_.push_back(l);
+} logger_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+};
+
+logger_params: logger_param
+             | logger_params COMMA logger_param
+             ;
+
+logger_param: name
+            | output_options_list
+            | debuglevel
+            | severity
+            | unknown_map_entry
+            ;
+
+debuglevel: DEBUGLEVEL COLON INTEGER {
+    ElementPtr dl(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("debuglevel", dl);
+};
+severity: SEVERITY {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr sev(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("severity", sev);
+    ctx.leave();
+};
+
+output_options_list: OUTPUT_OPTIONS {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("output_options", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.OUTPUT_OPTIONS);
+} COLON LSQUARE_BRACKET output_options_list_content RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+output_options_list_content: output_entry
+                           | output_options_list_content COMMA output_entry
+                           ;
+
+output_entry: LCURLY_BRACKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(m);
+    ctx.stack_.push_back(m);
+} output_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+};
+
+output_params: output_param
+             | output_params COMMA output_param
+             ;
+
+output_param: OUTPUT {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr sev(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("output", sev);
+    ctx.leave();
+};
+
+%%
+
+void
+isc::dhcp::Dhcp4Parser::error(const location_type& loc,
+                              const std::string& what)
+{
+    ctx.error(loc, what);
+}
diff --git a/src/bin/dhcp4/parser_context.cc b/src/bin/dhcp4/parser_context.cc
new file mode 100644 (file)
index 0000000..250f4db
--- /dev/null
@@ -0,0 +1,168 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <dhcp4/parser_context.h>
+#include <dhcp4/dhcp4_parser.h>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <boost/lexical_cast.hpp>
+#include <fstream>
+#include <limits>
+
+namespace isc {
+namespace dhcp {
+
+Parser4Context::Parser4Context()
+  : trace_scanning_(false), trace_parsing_(false)
+{
+}
+
+Parser4Context::~Parser4Context()
+{
+}
+
+isc::data::ConstElementPtr
+Parser4Context::parseString(const std::string& str, ParserType parser_type)
+{
+    scanStringBegin(str, parser_type);
+    return (parseCommon());
+}
+
+isc::data::ConstElementPtr
+Parser4Context::parseFile(const std::string& filename, ParserType parser_type) {
+    FILE* f = fopen(filename.c_str(), "r");
+    if (!f) {
+        isc_throw(Dhcp4ParseError, "Unable to open file " << filename);
+    }
+    scanFileBegin(f, filename, parser_type);
+    return (parseCommon());
+}
+
+isc::data::ConstElementPtr
+Parser4Context::parseCommon() {
+    isc::dhcp::Dhcp4Parser parser(*this);
+    // Uncomment this to get detailed parser logs.
+    // trace_parsing_ = true;
+    parser.set_debug_level(trace_parsing_);
+    try {
+        int res = parser.parse();
+        if (res != 0) {
+            isc_throw(Dhcp4ParseError, "Parser abort");
+        }
+        scanEnd();
+    }
+    catch (...) {
+        scanEnd();
+        throw;
+    }
+    if (stack_.size() == 1) {
+        return (stack_[0]);
+    } else {
+        isc_throw(Dhcp4ParseError, "Expected exactly one terminal Element expected, found "
+                  << stack_.size());
+    }
+}
+
+
+void
+Parser4Context::error(const isc::dhcp::location& loc, const std::string& what)
+{
+    isc_throw(Dhcp4ParseError, loc << ": " << what);
+}
+
+void
+Parser4Context::error (const std::string& what)
+{
+    isc_throw(Dhcp4ParseError, what);
+}
+
+void
+Parser4Context::fatal (const std::string& what)
+{
+    isc_throw(Dhcp4ParseError, what);
+}
+
+isc::data::Element::Position
+Parser4Context::loc2pos(isc::dhcp::location& loc)
+{
+    const std::string& file = *loc.begin.filename;
+    const uint32_t line = loc.begin.line;
+    const uint32_t pos = loc.begin.column;
+    return (isc::data::Element::Position(file, line, pos));
+}
+
+void
+Parser4Context::enter(const ParserContext& ctx)
+{
+    cstack_.push_back(ctx_);
+    ctx_ = ctx;
+}
+
+void
+Parser4Context::leave()
+{
+#if 1
+    if (cstack_.empty()) {
+        fatal("unbalanced syntactic context");
+    }
+#endif
+    ctx_ = cstack_.back();
+    cstack_.pop_back();
+}
+
+const std::string
+Parser4Context::contextName()
+{
+    switch (ctx_) {
+    case NO_KEYWORD:
+        return ("__no keyword__");
+    case CONFIG:
+        return ("toplevel");
+    case DHCP4:
+        return ("Dhcp4");
+    case LOGGING:
+        return ("Logging");
+    case INTERFACES_CONFIG:
+        return ("interfaces-config");
+    case LEASE_DATABASE:
+        return ("lease-database");
+    case HOSTS_DATABASE:
+        return ("hosts-database");
+    case HOST_RESERVATION_IDENTIFIERS:
+        return ("host-reservation-identifiers");
+    case HOOKS_LIBRARIES:
+        return ("hooks-librairies");
+    case SUBNET4:
+        return ("subnet4");
+    case OPTION_DEF:
+        return ("option-def");
+    case OPTION_DATA:
+        return ("option-data");
+    case CLIENT_CLASSES:
+        return ("client-classes");
+    case SERVER_ID:
+        return ("server-id");
+    case CONTROL_SOCKET:
+        return ("control-socket");
+    case POOLS:
+        return ("pools");
+    case RESERVATIONS:
+        return ("reservations");
+    case RELAY:
+        return ("relay");
+    case CLIENT_CLASS:
+        return ("client-class");
+    case LOGGERS:
+        return ("loggers");
+    case OUTPUT_OPTIONS:
+        return ("output-options");
+    default:
+        return ("__unknown__");
+    }
+}
+
+};
+};
diff --git a/src/bin/dhcp4/parser_context.h b/src/bin/dhcp4/parser_context.h
new file mode 100644 (file)
index 0000000..14e4015
--- /dev/null
@@ -0,0 +1,321 @@
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef PARSER_CONTEXT_H
+#define PARSER_CONTEXT_H
+#include <string>
+#include <map>
+#include <vector>
+#include <dhcp4/dhcp4_parser.h>
+#include <dhcp4/parser_context_decl.h>
+#include <exceptions/exceptions.h>
+
+// Tell Flex the lexer's prototype ...
+#define YY_DECL isc::dhcp::Dhcp4Parser::symbol_type parser4_lex (Parser4Context& driver)
+
+// ... and declare it for the parser's sake.
+YY_DECL;
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Evaluation error exception raised when trying to parse.
+///
+/// @todo: This probably should be common for Dhcp4 and Dhcp6.
+class Dhcp4ParseError : public isc::Exception {
+public:
+    Dhcp4ParseError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Evaluation context, an interface to the expression evaluation.
+class Parser4Context
+{
+public:
+
+    /// @brief Defines currently supported scopes
+    ///
+    /// Dhcp4Parser is able to parse several types of scope. Usually,
+    /// when it parses a config file, it expects the data to have a map
+    /// with Dhcp4 in it and all the parameters within that Dhcp4 map.
+    /// However, sometimes the parser is expected to parse only a subset
+    /// of that information. For example, it may be asked to parse
+    /// a structure that is host-reservation only, without the global
+    /// 'Dhcp4' or 'reservations' around it. In such case the parser
+    /// is being told to start parsing as PARSER_HOST_RESERVATION4.
+    typedef enum {
+        /// This parser will parse the content as generic JSON.
+        PARSER_JSON,
+
+        /// This parser will parse the content as Dhcp4 config wrapped in a map
+        /// (that's the regular config file)
+        PARSER_DHCP4,
+
+        /// This parser will parse the content of Dhcp4 (without outer { } and
+        /// without "Dhcp4"). It is mostly used in unit-tests as most of the
+        /// unit-tests do not define the outer map and Dhcp4 entity, just the
+        /// contents of it.
+        SUBPARSER_DHCP4,
+
+        /// This will parse the input as interfaces content.
+        PARSER_INTERFACES,
+
+        /// This will parse the input as Subnet6 content.
+        PARSER_SUBNET4,
+
+        /// This will parse the input as pool6 content.
+        PARSER_POOL4,
+
+        /// This will parse the input as host-reservation.
+        PARSER_HOST_RESERVATION,
+
+        /// This will parse the input as option definition.
+        PARSER_OPTION_DEF,
+
+        /// This will parse the input as option data.
+        PARSER_OPTION_DATA,
+
+        /// This will parse the input as hooks-library.
+        PARSER_HOOKS_LIBRARY
+    } ParserType;
+
+    /// @brief Default constructor.
+    Parser4Context();
+
+    /// @brief destructor
+    virtual ~Parser4Context();
+
+    /// @brief JSON elements being parsed.
+    std::vector<isc::data::ElementPtr> stack_;
+
+    /// @brief Method called before scanning starts on a string.
+    ///
+    /// @param str string to be parsed
+    /// @param parser_type specifies expected content
+    void scanStringBegin(const std::string& str, ParserType type);
+
+    /// @brief Method called before scanning starts on a file.
+    ///
+    /// @param f stdio FILE pointer
+    /// @param filename file to be parsed
+    /// @param parser_type specifies expected content
+    void scanFileBegin(FILE* f, const std::string& filename, ParserType type);
+
+    /// @brief Method called after the last tokens are scanned.
+    void scanEnd();
+
+    /// @brief Divert input to an include file.
+    ///
+    /// @param filename file to be included
+    void includeFile(const std::string& filename);
+
+    /// @brief Run the parser on the string specified.
+    ///
+    /// This method parses specified string. Depending on the value of
+    /// parser_type, parser may either check only that the input is valid
+    /// JSON, or may do more specific syntax checking. See @ref ParserType
+    /// for supported syntax checkers.
+    ///
+    /// @param str string to be parsed
+    /// @param parser_type specifies expected content (usually DHCP4 or generic JSON)
+    /// @return Element structure representing parsed text.
+    isc::data::ConstElementPtr parseString(const std::string& str,
+                                           ParserType parser_type);
+
+    /// @brief Run the parser on the file specified.
+    ///
+    /// This method parses specified file. Depending on the value of
+    /// parser_type, parser may either check only that the input is valid
+    /// JSON, or may do more specific syntax checking. See @ref ParserType
+    /// for supported syntax checkers.
+    ///
+    /// @param filename file to be parsed
+    /// @param parser_type specifies expected content (usually DHCP4 or generic JSON)
+    /// @return Element structure representing parsed text.
+    isc::data::ConstElementPtr parseFile(const std::string& filename,
+                                         ParserType parser_type);
+
+    /// @brief Error handler
+    ///
+    /// @param loc location within the parsed file when experienced a problem.
+    /// @param what string explaining the nature of the error.
+    /// @throw Dhcp4ParseError
+    void error(const isc::dhcp::location& loc, const std::string& what);
+
+    /// @brief Error handler
+    ///
+    /// This is a simplified error reporting tool for possible future
+    /// cases when the Dhcp4Parser is not able to handle the packet.
+    ///
+    /// @param what string explaining the nature of the error.
+    /// @throw Dhcp4ParseError
+    void error(const std::string& what);
+
+    /// @brief Fatal error handler
+    ///
+    /// This is for should not happen but fatal errors.
+    /// Used by YY_FATAL_ERROR macro so required to be static.
+    ///
+    /// @param what string explaining the nature of the error.
+    /// @throw Dhcp4ParseError
+    static void fatal(const std::string& what);
+
+    /// @brief Converts bison's position to one understandable by isc::data::Element
+    ///
+    /// Convert a bison location into an element position
+    /// (take the begin, the end is lost)
+    ///
+    /// @param loc location in bison format
+    /// @return Position in format accepted by Element
+    isc::data::Element::Position loc2pos(isc::dhcp::location& loc);
+
+    /// @brief Defines syntactic contexts for lexical tie-ins
+    typedef enum {
+        ///< This one is used in pure JSON mode.
+        NO_KEYWORD,
+
+        ///< Used while parsing top level (that contains Dhcp4, Logging and others)
+        CONFIG,
+
+        ///< Used while parsing content of Dhcp4.
+        DHCP4,
+
+        // not yet DHCP6,
+        // not yet DHCP_DDNS,
+
+        ///< Used while parsing content of Logging
+        LOGGING,
+
+        /// Used while parsing Dhcp4/interfaces structures.
+        INTERFACES_CONFIG,
+
+        /// Used while parsing Dhcp4/lease-database structures.
+        LEASE_DATABASE,
+
+        /// Used while parsing Dhcp4/hosts-database structures.
+        HOSTS_DATABASE,
+
+        /// Used while parsing Dhcp4/host-reservation-identifiers.
+        HOST_RESERVATION_IDENTIFIERS,
+
+        /// Used while parsing Dhcp4/hooks-libraries.
+        HOOKS_LIBRARIES,
+
+        /// Used while parsing Dhcp4/Subnet6 structures.
+        SUBNET4,
+
+        /// Used while parsing Dhcp4/option-def structures.
+        OPTION_DEF,
+
+        /// Used while parsing Dhcp4/option-data, Dhcp4/subnet6/option-data
+        /// or anywhere option-data is present (client classes, host
+        /// reservations and possibly others).
+        OPTION_DATA,
+
+        /// Used while parsing Dhcp4/client-classes structures.
+        CLIENT_CLASSES,
+
+        /// Used while parsing Dhcp4/server-id structures.
+        SERVER_ID,
+
+        /// Used while parsing Dhcp4/control-socket structures.
+        CONTROL_SOCKET,
+
+        /// Used while parsing Dhcp4/subnet6/pools structures.
+        POOLS,
+
+        /// Used while parsing Dhcp4/reservations structures.
+        RESERVATIONS,
+
+        /// Used while parsing Dhcp4/subnet6/relay structures.
+        RELAY,
+
+        /// Used while parsing Dhcp4/client-classes structures.
+        CLIENT_CLASS,
+
+        /// Used while parsing Logging/loggers structures.
+        LOGGERS,
+
+        /// Used while parsing Logging/loggers/output_options structures.
+        OUTPUT_OPTIONS
+    } ParserContext;
+
+    /// @brief File name
+    std::string file_;
+
+    /// @brief File name stack
+    std::vector<std::string> files_;
+
+    /// @brief Location of the current token
+    ///
+    /// The lexer will keep updating it. This variable will be useful
+    /// for logging errors.
+    isc::dhcp::location loc_;
+
+    /// @brief Location stack
+    std::vector<isc::dhcp::location> locs_;
+
+    /// @brief Lexer state stack
+    std::vector<struct yy_buffer_state*> states_;
+
+    /// @brief sFile (aka FILE)
+    FILE* sfile_;
+
+    /// @brief sFile (aka FILE) stack
+    ///
+    /// This is a stack of files. Typically there's only one file (the
+    /// one being currently parsed), but there may be more if one
+    /// file includes another.
+    std::vector<FILE*> sfiles_;
+
+    /// @brief Current syntactic context
+    ParserContext ctx_;
+
+    /// @brief Enter a new syntactic context
+    ///
+    /// Entering a new syntactic context is useful in several ways.
+    /// First, it allows the parser to avoid conflicts. Second, it
+    /// allows the lexer to return different tokens depending on
+    /// context (e.g. if "renew-timer" string is detected, the lexer
+    /// will return STRING token if in JSON mode or RENEW_TIMER if
+    /// in DHCP4 mode. Finally, the syntactic context allows the
+    /// error message to be more descriptive if the input string
+    /// does not parse properly.
+    ///
+    /// @param ctx the syntactic context to enter into
+    void enter(const ParserContext& ctx);
+
+    /// @brief Leave a syntactic context
+    ///
+    /// @throw isc::Unexpected if unbalanced
+    void leave();
+
+    /// @brief Get the syntactix context name
+    ///
+    /// @return printable name of the context.
+    const std::string contextName();
+
+ private:
+    /// @brief Flag determining scanner debugging.
+    bool trace_scanning_;
+
+    /// @brief Flag determing parser debugging.
+    bool trace_parsing_;
+
+    /// @brief Syntactic context stack
+    std::vector<ParserContext> cstack_;
+
+    /// @brief Common part of parseXXX
+    ///
+    /// @return Element structure representing parsed text.
+    isc::data::ConstElementPtr parseCommon();
+};
+
+}; // end of isc::eval namespace
+}; // end of isc namespace
+
+#endif
diff --git a/src/bin/dhcp4/parser_context_decl.h b/src/bin/dhcp4/parser_context_decl.h
new file mode 100644 (file)
index 0000000..367f163
--- /dev/null
@@ -0,0 +1,20 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef PARSER6_CONTEXT_DECL_H
+#define PARSER6_CONTEXT_DECL_H
+
+/// @file parser_context_decl.h Forward declaration of the ParserContext class
+
+namespace isc {
+namespace dhcp {
+
+class Parser4Context;
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif
index f0b5babb3df572ca89b78154e20b65a781b5e54b..ff78e5765ec8e968bcbb2d84276b303ff7530942 100644 (file)
@@ -23,6 +23,7 @@ AM_CPPFLAGS += -I$(top_srcdir)/src/bin
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp4/tests\"
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+AM_CPPFLAGS += -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/kea4\"
 
 CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
 CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt
@@ -88,6 +89,7 @@ dhcp4_unittests_SOURCES += inform_unittest.cc
 dhcp4_unittests_SOURCES += dora_unittest.cc
 dhcp4_unittests_SOURCES += host_options_unittest.cc
 dhcp4_unittests_SOURCES += release_unittest.cc
+dhcp4_unittests_SOURCES += parser_unittest.cc
 dhcp4_unittests_SOURCES += out_of_range_unittest.cc
 dhcp4_unittests_SOURCES += decline_unittest.cc
 dhcp4_unittests_SOURCES += kea_controller_unittest.cc
diff --git a/src/bin/dhcp4/tests/parser_unittest.cc b/src/bin/dhcp4/tests/parser_unittest.cc
new file mode 100644 (file)
index 0000000..46b0dfe
--- /dev/null
@@ -0,0 +1,475 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <gtest/gtest.h>
+#include <cc/data.h>
+#include <dhcp4/parser_context.h>
+
+using namespace isc::data;
+using namespace std;
+
+namespace {
+
+void compareJSON(ConstElementPtr a, ConstElementPtr b, bool print = true) {
+    ASSERT_TRUE(a);
+    ASSERT_TRUE(b);
+    if (print) {
+        // std::cout << "JSON A: -----" << endl << a->str() << std::endl;
+        // std::cout << "JSON B: -----" << endl << b->str() << std::endl;
+        // cout << "---------" << endl << endl;
+    }
+    EXPECT_EQ(a->str(), b->str());
+}
+
+void testParser(const std::string& txt, Parser4Context::ParserType parser_type) {
+    ElementPtr reference_json;
+    ConstElementPtr test_json;
+
+    ASSERT_NO_THROW(reference_json = Element::fromJSON(txt, true));
+    ASSERT_NO_THROW({
+            try {
+        Parser4Context ctx;
+        test_json = ctx.parseString(txt, parser_type);
+            } catch (const std::exception &e) {
+                cout << "EXCEPTION: " << e.what() << endl;
+                throw;
+            }
+
+    });
+
+    // Now compare if both representations are the same.
+    compareJSON(reference_json, test_json);
+}
+
+void testParser2(const std::string& txt, Parser4Context::ParserType parser_type) {
+    ConstElementPtr test_json;
+
+    ASSERT_NO_THROW({
+            try {
+        Parser4Context ctx;
+        test_json = ctx.parseString(txt, parser_type);
+            } catch (const std::exception &e) {
+                cout << "EXCEPTION: " << e.what() << endl;
+                throw;
+            }
+    });
+    /// @todo: Implement actual validation here. since the original
+    /// Element::fromJSON does not support several comment types, we don't
+    /// have anything to compare with.
+    /// std::cout << "Original text:" << txt << endl;
+    /// std::cout << "Parsed text  :" << test_json->str() << endl;
+}
+
+TEST(ParserTest, mapInMap) {
+    string txt = "{ \"xyzzy\": { \"foo\": 123, \"baz\": 456 } }";
+    testParser(txt, Parser4Context::PARSER_JSON);
+}
+
+TEST(ParserTest, listInList) {
+    string txt = "[ [ \"Britain\", \"Wales\", \"Scotland\" ], "
+                 "[ \"Pomorze\", \"Wielkopolska\", \"Tatry\"] ]";
+    testParser(txt, Parser4Context::PARSER_JSON);
+}
+
+TEST(ParserTest, nestedMaps) {
+    string txt = "{ \"europe\": { \"UK\": { \"London\": { \"street\": \"221B Baker\" }}}}";
+    testParser(txt, Parser4Context::PARSER_JSON);
+}
+
+TEST(ParserTest, nestedLists) {
+    string txt = "[ \"half\", [ \"quarter\", [ \"eighth\", [ \"sixteenth\" ]]]]";
+    testParser(txt, Parser4Context::PARSER_JSON);
+}
+
+TEST(ParserTest, listsInMaps) {
+    string txt = "{ \"constellations\": { \"orion\": [ \"rigel\", \"betelguese\" ], "
+                    "\"cygnus\": [ \"deneb\", \"albireo\"] } }";
+    testParser(txt, Parser4Context::PARSER_JSON);
+}
+
+TEST(ParserTest, mapsInLists) {
+    string txt = "[ { \"body\": \"earth\", \"gravity\": 1.0 },"
+                 " { \"body\": \"mars\", \"gravity\": 0.376 } ]";
+    testParser(txt, Parser4Context::PARSER_JSON);
+}
+
+TEST(ParserTest, types) {
+    string txt = "{ \"string\": \"foo\","
+                   "\"integer\": 42,"
+                   "\"boolean\": true,"
+                   "\"map\": { \"foo\": \"bar\" },"
+                   "\"list\": [ 1, 2, 3 ],"
+                   "\"null\": null }";
+    testParser(txt, Parser4Context::PARSER_JSON);
+}
+
+TEST(ParserTest, keywordJSON) {
+    string txt = "{ \"name\": \"user\","
+                   "\"type\": \"password\","
+                   "\"user\": \"name\","
+                   "\"password\": \"type\" }";
+    testParser(txt, Parser4Context::PARSER_JSON);
+}
+
+TEST(ParserTest, keywordDhcp4) {
+     string txt = "{ \"Dhcp4\": { \"interfaces-config\": {"
+                  " \"interfaces\": [ \"type\", \"htype\" ] },\n"
+                  "\"rebind-timer\": 2000, \n"
+                  "\"renew-timer\": 1000, \n"
+                  "\"subnet4\": [ { "
+                  "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+                  "    \"subnet\": \"2001:db8:1::/48\", "
+                  "    \"interface\": \"test\" } ],\n"
+                   "\"valid-lifetime\": 4000 } }";
+     testParser2(txt, Parser4Context::PARSER_DHCP4);
+}
+
+TEST(ParserTest, bashComments) {
+    string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
+                "  \"interfaces\": [ \"*\" ]"
+                "},\n"
+                "\"preferred-lifetime\": 3000,\n"
+                "# this is a comment\n"
+                "\"rebind-timer\": 2000, \n"
+                "# lots of comments here\n"
+                "# and here\n"
+                "\"renew-timer\": 1000, \n"
+                "\"subnet6\": [ { "
+                "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+                "    \"subnet\": \"2001:db8:1::/48\", "
+                "    \"interface\": \"eth0\""
+                " } ],"
+                "\"valid-lifetime\": 4000 } }";
+    testParser2(txt, Parser4Context::PARSER_DHCP4);
+}
+
+TEST(ParserTest, cComments) {
+    string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
+                "  \"interfaces\": [ \"*\" ]"
+                "},\n"
+                "\"preferred-lifetime\": 3000, // this is a comment \n"
+                "\"rebind-timer\": 2000, // everything after // is ignored\n"
+                "\"renew-timer\": 1000, // this will be ignored, too\n"
+                "\"subnet6\": [ { "
+                "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+                "    \"subnet\": \"2001:db8:1::/48\", "
+                "    \"interface\": \"eth0\""
+                " } ],"
+                "\"valid-lifetime\": 4000 } }";
+    testParser2(txt, Parser4Context::PARSER_DHCP4);
+}
+
+TEST(ParserTest, bashCommentsInline) {
+    string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
+                "  \"interfaces\": [ \"*\" ]"
+                "},\n"
+                "\"preferred-lifetime\": 3000, # this is a comment \n"
+                "\"rebind-timer\": 2000, # everything after # is ignored\n"
+                "\"renew-timer\": 1000, # this will be ignored, too\n"
+                "\"subnet6\": [ { "
+                "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+                "    \"subnet\": \"2001:db8:1::/48\", "
+                "    \"interface\": \"eth0\""
+                " } ],"
+                "\"valid-lifetime\": 4000 } }";
+    testParser2(txt, Parser4Context::PARSER_DHCP4);
+}
+
+TEST(ParserTest, multilineComments) {
+    string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
+                "  \"interfaces\": [ \"*\" ]"
+                "},\n"
+                "\"preferred-lifetime\": 3000, /* this is a C style comment\n"
+                "that\n can \n span \n multiple \n lines */ \n"
+                "\"rebind-timer\": 2000,\n"
+                "\"renew-timer\": 1000, \n"
+                "\"subnet6\": [ { "
+                "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+                "    \"subnet\": \"2001:db8:1::/48\", "
+                "    \"interface\": \"eth0\""
+                " } ],"
+                "\"valid-lifetime\": 4000 } }";
+    testParser2(txt, Parser4Context::PARSER_DHCP4);
+}
+
+
+void testFile(const std::string& fname, bool print) {
+    ElementPtr reference_json;
+    ConstElementPtr test_json;
+
+    cout << "Attempting to load file " << fname << endl;
+
+    EXPECT_NO_THROW(reference_json = Element::fromJSONFile(fname, true));
+
+    EXPECT_NO_THROW(
+    try {
+        Parser4Context ctx;
+        test_json = ctx.parseFile(fname, Parser4Context::PARSER_DHCP4);
+    } catch (const std::exception &x) {
+        cout << "EXCEPTION: " << x.what() << endl;
+        throw;
+    });
+
+    ASSERT_TRUE(reference_json);
+    ASSERT_TRUE(test_json);
+
+    compareJSON(reference_json, test_json, print);
+
+
+}
+
+// This test loads all available existing files. Each config is loaded
+// twice: first with the existing Element::fromJSONFile() and then
+// the second time with Parser6. Both JSON trees are then compared.
+TEST(ParserTest, file) {
+    vector<string> configs;
+    configs.push_back("backends.json");
+    configs.push_back("classify.json");
+    configs.push_back("dhcpv4-over-dhcpv6.json");
+    configs.push_back("hooks.json");
+    configs.push_back("leases-expiration.json");
+    configs.push_back("multiple-options.json");
+    configs.push_back("mysql-reservations.json");
+    configs.push_back("pgsql-reservations.json");
+    configs.push_back("reservations.json");
+    configs.push_back("several-subnets.json");
+    configs.push_back("single-subnet.json");
+
+    for (int i = 0; i<configs.size(); i++) {
+        testFile(string(CFG_EXAMPLES) + "/" + configs[i], false);
+    }
+}
+
+void testError(const std::string& txt,
+               Parser4Context::ParserType parser_type,
+               const std::string& msg)
+{
+    try {
+        Parser4Context ctx;
+        ConstElementPtr parsed = ctx.parseString(txt, parser_type);
+        FAIL() << "Expected Dhcp6ParseError but nothing was raised (expected: "
+               << msg << ")";
+    }
+    catch (const Dhcp4ParseError& ex) {
+        EXPECT_EQ(msg, ex.what());
+    }
+    catch (...) {
+        FAIL() << "Expected Dhcp6ParseError but something else was raised";
+    }
+}
+
+// Check errors
+TEST(ParserTest, errors) {
+    // no input
+    testError("", Parser4Context::PARSER_JSON,
+              "<string>:1.1: syntax error, unexpected end of file");
+    testError(" ", Parser4Context::PARSER_JSON,
+              "<string>:1.2: syntax error, unexpected end of file");
+    testError("\n", Parser4Context::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError("\t", Parser4Context::PARSER_JSON,
+              "<string>:1.2: syntax error, unexpected end of file");
+    testError("\r", Parser4Context::PARSER_JSON,
+              "<string>:1.2: syntax error, unexpected end of file");
+
+    // comments
+    testError("# nothing\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError(" #\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError("// nothing\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError("/* nothing */\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError("/* no\nthing */\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:3.1: syntax error, unexpected end of file");
+    testError("/* no\nthing */\n\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:4.1: syntax error, unexpected end of file");
+    testError("/* nothing\n",
+              Parser4Context::PARSER_JSON,
+              "Comment not closed. (/* in line 1");
+    testError("\n\n\n/* nothing\n",
+              Parser4Context::PARSER_JSON,
+              "Comment not closed. (/* in line 4");
+    testError("{ /* */*/ }\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.3-8: Invalid character: *");
+    testError("{ /* // *// }\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.3-11: Invalid character: /");
+    testError("{ /* // *///  }\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file, "
+              "expecting }");
+
+    // includes
+    testError("<?\n",
+              Parser4Context::PARSER_JSON,
+              "Directive not closed.");
+    testError("<?include\n",
+              Parser4Context::PARSER_JSON,
+              "Directive not closed.");
+    string file = string(CFG_EXAMPLES) + "/" + "single-subnet.json";
+    testError("<?include \"" + file + "\"\n",
+              Parser4Context::PARSER_JSON,
+              "Directive not closed.");
+    testError("<?include \"/foo/bar\" ?>/n",
+              Parser4Context::PARSER_JSON,
+              "Can't open include file /foo/bar");
+
+    // case sensitivity
+    testError("{ \"foo\": True }",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.10: Invalid character: T");
+    testError("{ \"foo\": NULL  }",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.10: Invalid character: N");
+
+    // numbers
+    testError("123",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.1-3: syntax error, unexpected integer, "
+              "expecting {");
+    testError("-456",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.1-4: syntax error, unexpected integer, "
+              "expecting {");
+    testError("-0001",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.1-5: syntax error, unexpected integer, "
+              "expecting {");
+    testError("1234567890123456789012345678901234567890",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1-40: Failed to convert "
+              "1234567890123456789012345678901234567890"
+              " to an integer.");
+    testError("-3.14e+0",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.1-8: syntax error, unexpected floating point, "
+              "expecting {");
+    testError("1e50000",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1-7: Failed to convert 1e50000 "
+              "to a floating point.");
+
+    // strings
+    testError("\"aabb\"",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.1-6: syntax error, unexpected constant string, "
+              "expecting {");
+    testError("{ \"aabb\"err",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.9: Invalid character: e");
+    testError("{ err\"aabb\"",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.3: Invalid character: e");
+    testError("\"a\n\tb\"",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1-6: Invalid control in \"a\n\tb\"");
+    testError("\"a\\n\\tb\"",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.1-8: syntax error, unexpected constant string, "
+              "expecting {");
+    testError("\"a\\x01b\"",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1-8: Bad escape in \"a\\x01b\"");
+    testError("\"a\\u0062\"",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1-9: Unsupported unicode escape in \"a\\u0062\"");
+    testError("\"a\\u062z\"",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1-9: Bad escape in \"a\\u062z\"");
+    testError("\"abc\\\"",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1-6: Overflow escape in \"abc\\\"");
+
+    // from data_unittest.c
+    testError("\\a",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1: Invalid character: \\");
+    testError("\\",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1: Invalid character: \\");
+    testError("\\\"\\\"",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1: Invalid character: \\");
+
+    // want a map
+    testError("[]\n",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.1: syntax error, unexpected [, "
+              "expecting {");
+    testError("[]\n",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.1: syntax error, unexpected [, "
+              "expecting {");
+    testError("{ 123 }\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.3-5: syntax error, unexpected integer, "
+              "expecting }");
+    testError("{ 123 }\n",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.3-5: syntax error, unexpected integer");
+    testError("{ \"foo\" }\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.9: syntax error, unexpected }, "
+              "expecting :");
+    testError("{ \"foo\" }\n",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.9: syntax error, unexpected }, expecting :");
+    testError("{ \"foo\":null }\n",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.3-7: got unexpected keyword "
+              "\"foo\" in toplevel map.");
+    testError("{ \"Dhcp4\" }\n",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.11: syntax error, unexpected }, "
+              "expecting :");
+    testError("{ \"Dhcp6\":[]\n",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:2.1: syntax error, unexpected end of file, "
+              "expecting \",\" or }");
+    testError("{}{}\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.3: syntax error, unexpected {, "
+              "expecting end of file");
+
+    // bad commas
+    testError("{ , }\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.3: syntax error, unexpected \",\", "
+              "expecting }");
+    testError("{ , \"foo\":true }\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.3: syntax error, unexpected \",\", "
+              "expecting }");
+    testError("{ \"foo\":true, }\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.15: syntax error, unexpected }, "
+              "expecting constant string");
+
+    // bad type
+    testError("{ \"Dhcp4\":{\n"
+              "  \"valid-lifetime\":false }}\n",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:2.20-24: syntax error, unexpected boolean, "
+              "expecting integer");
+
+    // unknown keyword
+    testError("{ \"Dhcp4\":{\n"
+              " \"valid_lifetime\":600 }}\n",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:2.2-17: got unexpected keyword "
+              "\"valid_lifetime\" in Dhcp4 map.");
+}
+
+};