Browse Source

[5110] Added basic bison parser and SimpleParser to D2

D2 now uses bison parsing to parse its config file and has starter
files for SimpleParser. This check-in uses only generic JSON context
parsing (i.e. no D2 specific grammar).

Added new bison/flex parsing files:
    d2_lexer.ll
    d2_parser.yy
    parser_context.cc
    parser_context.h
    parser_context_decl.h

Added new generated files:
    d2_parser.cc
    d2_parser.h
    location.hh
    position.hh
    stack.hh

Added new SimpleParser related files:
    d2_simple_parser.cc
    d2_simple_parser.h

src/bin/d2/Makefile.am
    Added bison/flex related entries and rules

src/bin/d2/d2_controller.h
src/bin/d2/d2_controller.cc
    D2Controller::parseFile() - new method which overrides base class
    version and calls new bison parsing to parse JSON config file.
Thomas Markwalder 8 years ago
parent
commit
a80a3a6dc0

+ 32 - 0
src/bin/d2/Makefile.am

@@ -64,7 +64,10 @@ libd2_la_SOURCES += d2_log.cc d2_log.h
 libd2_la_SOURCES += d2_process.cc d2_process.h
 libd2_la_SOURCES += d2_process.cc d2_process.h
 libd2_la_SOURCES += d2_config.cc d2_config.h
 libd2_la_SOURCES += d2_config.cc d2_config.h
 libd2_la_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
 libd2_la_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
+libd2_la_SOURCES += d2_lexer.ll location.hh position.hh stack.hh
+libd2_la_SOURCES += d2_parser.cc d2_parser.h
 libd2_la_SOURCES += d2_queue_mgr.cc d2_queue_mgr.h
 libd2_la_SOURCES += d2_queue_mgr.cc d2_queue_mgr.h
+libd2_la_SOURCES += d2_simple_parser.cc d2_simple_parser.h
 libd2_la_SOURCES += d2_update_message.cc d2_update_message.h
 libd2_la_SOURCES += d2_update_message.cc d2_update_message.h
 libd2_la_SOURCES += d2_update_mgr.cc d2_update_mgr.h
 libd2_la_SOURCES += d2_update_mgr.cc d2_update_mgr.h
 libd2_la_SOURCES += d2_zone.cc d2_zone.h
 libd2_la_SOURCES += d2_zone.cc d2_zone.h
@@ -73,6 +76,7 @@ libd2_la_SOURCES += nc_add.cc nc_add.h
 libd2_la_SOURCES += nc_remove.cc nc_remove.h
 libd2_la_SOURCES += nc_remove.cc nc_remove.h
 libd2_la_SOURCES += nc_trans.cc nc_trans.h
 libd2_la_SOURCES += nc_trans.cc nc_trans.h
 libd2_la_SOURCES += d2_controller.cc d2_controller.h
 libd2_la_SOURCES += d2_controller.cc d2_controller.h
+libd2_la_SOURCES += parser_context.cc parser_context.h parser_context_decl.h
 
 
 nodist_libd2_la_SOURCES = d2_messages.h d2_messages.cc
 nodist_libd2_la_SOURCES = d2_messages.h d2_messages.cc
 EXTRA_DIST += d2_messages.mes
 EXTRA_DIST += d2_messages.mes
@@ -115,3 +119,31 @@ endif
 
 
 kea_dhcp_ddnsdir = $(pkgdatadir)
 kea_dhcp_ddnsdir = $(pkgdatadir)
 kea_dhcp_ddns_DATA = dhcp-ddns.spec
 kea_dhcp_ddns_DATA = dhcp-ddns.spec
+
+if GENERATE_PARSER
+
+parser: d2_lexer.cc location.hh position.hh stack.hh d2_parser.cc d2_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 d2_parser.cc d2_parser.h: d2_parser.yy
+	$(YACC) --defines=d2_parser.h --report=all --report-file=d2_parser.report -o d2_parser.cc d2_parser.yy
+
+d2_lexer.cc: d2_lexer.ll
+	$(LEX) --prefix d2_parser_ -o d2_lexer.cc d2_lexer.ll
+
+else
+
+parser location.hh position.hh stack.hh d2_parser.cc d2_parser.h d2_lexer.cc:
+	@echo Parser generation disabled. Configure with --enable-generate-parser to enable it.
+
+endif
+                                                                                        

+ 15 - 0
src/bin/d2/d2_controller.cc

@@ -8,6 +8,7 @@
 
 
 #include <d2/d2_controller.h>
 #include <d2/d2_controller.h>
 #include <d2/d2_process.h>
 #include <d2/d2_process.h>
+#include <d2/parser_context.h>
 #include <process/spec_config.h>
 #include <process/spec_config.h>
 
 
 #include <stdlib.h>
 #include <stdlib.h>
@@ -54,6 +55,20 @@ D2Controller::D2Controller()
     }
     }
 }
 }
 
 
+isc::data::ConstElementPtr 
+D2Controller::parseFile(const std::string& file_name) {
+    isc::data::ConstElementPtr elements;
+
+    // Read contents of the file and parse it as JSON
+    D2ParserContext parser;
+    elements = parser.parseFile(file_name, D2ParserContext::PARSER_JSON);
+    if (!elements) {
+        isc_throw(isc::BadValue, "no configuration found in file");
+    }
+
+    return (elements);
+}
+
 D2Controller::~D2Controller() {
 D2Controller::~D2Controller() {
 }
 }
 
 

+ 10 - 0
src/bin/d2/d2_controller.h

@@ -53,6 +53,16 @@ private:
     /// pointer.
     /// pointer.
     virtual process::DProcessBase* createProcess();
     virtual process::DProcessBase* createProcess();
 
 
+    ///@brief Parse a given file into Elements
+    ///
+    /// Uses bison parsing to parse a JSON configruation file into an
+    /// a element map.
+    ///
+    /// @param file_name pathname of the file to parse
+    ///
+    /// @return pointer to the map of elements created
+    virtual isc::data::ConstElementPtr parseFile(const std::string& file_name);
+
     /// @brief Constructor is declared private to maintain the integrity of
     /// @brief Constructor is declared private to maintain the integrity of
     /// the singleton instance.
     /// the singleton instance.
     D2Controller();
     D2Controller();

File diff suppressed because it is too large
+ 3110 - 0
src/bin/d2/d2_lexer.cc


+ 568 - 0
src/bin/d2/d2_lexer.ll

@@ -0,0 +1,568 @@
+/* Copyright (C) 2017 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 <d2/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::D2ParserContext::ParserType start_token_value;
+unsigned int comment_start_line = 0;
+
+};
+
+// To avoid the call to exit... oops!
+#define YY_FATAL_ERROR(msg) isc::dhcp::D2ParserContext::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 D2ParserContext::PARSER_JSON:
+        default:
+            return isc::dhcp::D2Parser::make_TOPLEVEL_JSON(driver.loc_);
+        case D2ParserContext::PARSER_DHCPDDNS:
+            return isc::dhcp::D2Parser::make_TOPLEVEL_DHCPDDNS(driver.loc_);
+        }
+    }
+%}
+
+#.* ;
+
+"//"(.*) ;
+
+"/*" {
+  BEGIN(COMMENT);
+  comment_start_line = driver.loc_.end.line;;
+}
+
+<COMMENT>"*/" BEGIN(INITIAL);
+<COMMENT>. ;
+<COMMENT><<EOF>> {
+    isc_throw(D2ParseError, "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(D2ParseError, "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();
+}
+
+\"ip-address\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::D2ParserContext::DHCPDDNS:
+        return isc::dhcp::D2Parser::make_IP_ADDRESS(driver.loc_);
+    default:
+        return isc::dhcp::D2Parser::make_STRING("ip-address", driver.loc_);
+    }
+}
+
+\"port\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::D2ParserContext::DHCPDDNS:
+        return isc::dhcp::D2Parser::make_PORT(driver.loc_);
+    default:
+        return isc::dhcp::D2Parser::make_STRING("port", driver.loc_);
+    }
+}
+
+\"ncr-protocol\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::D2ParserContext::DHCPDDNS:
+        return isc::dhcp::D2Parser::make_NCR_PROTOCOL(driver.loc_);
+    default:
+        return isc::dhcp::D2Parser::make_STRING("ncr-protocol", driver.loc_);
+    }
+}
+
+\"ncr-format\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::D2ParserContext::DHCPDDNS:
+        return isc::dhcp::D2Parser::make_NCR_FORMAT(driver.loc_);
+    default:
+        return isc::dhcp::D2Parser::make_STRING("ncr-format", driver.loc_);
+    }
+}
+
+(?i:\"UDP\") {
+    /* dhcp-ddns value keywords are case insensitive */
+    if (driver.ctx_ == isc::dhcp::D2ParserContext::NCR_PROTOCOL) {
+        return isc::dhcp::D2Parser::make_UDP(driver.loc_);
+    }
+    std::string tmp(yytext+1);
+    tmp.resize(tmp.size() - 1);
+    return isc::dhcp::D2Parser::make_STRING(tmp, driver.loc_);
+}
+
+(?i:\"TCP\") {
+    /* dhcp-ddns value keywords are case insensitive */
+    if (driver.ctx_ == isc::dhcp::D2ParserContext::NCR_PROTOCOL) {
+        return isc::dhcp::D2Parser::make_TCP(driver.loc_);
+    }
+    std::string tmp(yytext+1);
+    tmp.resize(tmp.size() - 1);
+    return isc::dhcp::D2Parser::make_STRING(tmp, driver.loc_);
+}
+
+(?i:\"JSON\") {
+    /* dhcp-ddns value keywords are case insensitive */
+    if (driver.ctx_ == isc::dhcp::D2ParserContext::NCR_FORMAT) {
+        return isc::dhcp::D2Parser::make_JSON(driver.loc_);
+    }
+    std::string tmp(yytext+1);
+    tmp.resize(tmp.size() - 1);
+    return isc::dhcp::D2Parser::make_STRING(tmp, driver.loc_);
+}
+
+\"Logging\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::D2ParserContext::CONFIG:
+        return isc::dhcp::D2Parser::make_LOGGING(driver.loc_);
+    default:
+        return isc::dhcp::D2Parser::make_STRING("Logging", driver.loc_);
+    }
+}
+
+\"loggers\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::D2ParserContext::LOGGING:
+        return isc::dhcp::D2Parser::make_LOGGERS(driver.loc_);
+    default:
+        return isc::dhcp::D2Parser::make_STRING("loggers", driver.loc_);
+    }
+}
+
+\"output_options\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::D2ParserContext::LOGGERS:
+        return isc::dhcp::D2Parser::make_OUTPUT_OPTIONS(driver.loc_);
+    default:
+        return isc::dhcp::D2Parser::make_STRING("output_options", driver.loc_);
+    }
+}
+
+\"output\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::D2ParserContext::OUTPUT_OPTIONS:
+        return isc::dhcp::D2Parser::make_OUTPUT(driver.loc_);
+    default:
+        return isc::dhcp::D2Parser::make_STRING("output", driver.loc_);
+    }
+}
+
+\"debuglevel\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::D2ParserContext::LOGGERS:
+        return isc::dhcp::D2Parser::make_DEBUGLEVEL(driver.loc_);
+    default:
+        return isc::dhcp::D2Parser::make_STRING("debuglevel", driver.loc_);
+    }
+}
+
+\"severity\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::D2ParserContext::LOGGERS:
+        return isc::dhcp::D2Parser::make_SEVERITY(driver.loc_);
+    default:
+        return isc::dhcp::D2Parser::make_STRING("severity", 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) {
+        int b = 0;
+        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':
+                // support only \u0000 to \u00ff
+                ++pos;
+                if (pos + 4 > len) {
+                    // impossible condition
+                    driver.error(driver.loc_,
+                                 "Overflow unicode escape in \"" + raw + "\"");
+                }
+                if ((raw[pos] != '0') || (raw[pos + 1] != '0')) {
+                    driver.error(driver.loc_, "Unsupported unicode escape in \"" + raw + "\"");
+                }
+                pos += 2;
+                c = raw[pos];
+                if ((c >= '0') && (c <= '9')) {
+                    b = (c - '0') << 4;
+                } else if ((c >= 'A') && (c <= 'F')) {
+                    b = (c - 'A' + 10) << 4;
+                } else if ((c >= 'a') && (c <= 'f')) {
+                    b = (c - 'a' + 10) << 4;
+                } else {
+                    // impossible condition
+                    driver.error(driver.loc_, "Not hexadecimal in unicode escape in \"" + raw + "\"");
+                }
+                pos++;
+                c = raw[pos];
+                if ((c >= '0') && (c <= '9')) {
+                    b |= c - '0';
+                } else if ((c >= 'A') && (c <= 'F')) {
+                    b |= c - 'A' + 10;
+                } else if ((c >= 'a') && (c <= 'f')) {
+                    b |= c - 'a' + 10;
+                } else {
+                    // impossible condition
+                    driver.error(driver.loc_, "Not hexadecimal in unicode escape in \"" + raw + "\"");
+                }
+                decoded.push_back(static_cast<char>(b & 0xff));
+                break;
+            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::D2Parser::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::D2Parser::make_LSQUARE_BRACKET(driver.loc_); }
+"]"    { return isc::dhcp::D2Parser::make_RSQUARE_BRACKET(driver.loc_); }
+"{"    { return isc::dhcp::D2Parser::make_LCURLY_BRACKET(driver.loc_); }
+"}"    { return isc::dhcp::D2Parser::make_RCURLY_BRACKET(driver.loc_); }
+","    { return isc::dhcp::D2Parser::make_COMMA(driver.loc_); }
+":"    { return isc::dhcp::D2Parser::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::D2Parser::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::D2Parser::make_FLOAT(fp, driver.loc_);
+}
+
+true|false {
+    string tmp(yytext);
+    return isc::dhcp::D2Parser::make_BOOLEAN(tmp == "true", driver.loc_);
+}
+
+null {
+   return isc::dhcp::D2Parser::make_NULL_TYPE(driver.loc_);
+}
+
+(?i:true) driver.error (driver.loc_, "JSON true reserved keyword is lower case only");
+
+(?i:false) driver.error (driver.loc_, "JSON false reserved keyword is lower case only");
+
+(?i:null) driver.error (driver.loc_, "JSON null reserved keyword is lower case only");
+
+<*>.   driver.error (driver.loc_, "Invalid character: " + std::string(yytext));
+
+<<EOF>> {
+    if (driver.states_.empty()) {
+        return isc::dhcp::D2Parser::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();
+    }
+    d2_parser__delete_buffer(YY_CURRENT_BUFFER);
+    d2_parser__switch_to_buffer(driver.states_.back());
+    driver.states_.pop_back();
+
+    BEGIN(DIR_EXIT);
+}
+
+%%
+
+using namespace isc::dhcp;
+
+void
+D2ParserContext::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
+D2ParserContext::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 d2_lexer.cc header for available definitions
+    buffer = d2_parser__create_buffer(f, 65536 /*buffer size*/);
+    if (!buffer) {
+        fatal("cannot scan file " + filename);
+    }
+    d2_parser__switch_to_buffer(buffer);
+}
+
+void
+D2ParserContext::scanEnd() {
+    if (sfile_)
+        fclose(sfile_);
+    sfile_ = 0;
+    static_cast<void>(d2_parser_lex_destroy());
+    // Close files
+    while (!sfiles_.empty()) {
+        FILE* f = sfiles_.back();
+        if (f) {
+            fclose(f);
+        }
+        sfiles_.pop_back();
+    }
+    // Delete states
+    while (!states_.empty()) {
+        d2_parser__delete_buffer(states_.back());
+        states_.pop_back();
+    }
+}
+
+void
+D2ParserContext::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 = d2_parser__create_buffer(f, 65536 /*buffer size*/);
+    if (!buffer) {
+        fatal( "Can't scan include file " + filename);
+    }
+    d2_parser__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?"); }
+};
+}

File diff suppressed because it is too large
+ 1617 - 0
src/bin/d2/d2_parser.cc


File diff suppressed because it is too large
+ 1382 - 0
src/bin/d2/d2_parser.h


+ 424 - 0
src/bin/d2/d2_parser.yy

@@ -0,0 +1,424 @@
+/* Copyright (C) 2017 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 {D2Parser}
+%define api.prefix {d2_parser_}
+%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 <d2/d2_config.h>
+#include <boost/lexical_cast.hpp>
+#include <d2/parser_context_decl.h>
+
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace std;
+}
+// The parsing context.
+%param { isc::dhcp::D2ParserContext& ctx }
+%locations
+%define parse.trace
+%define parse.error verbose
+%code
+{
+#include <d2/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 dhcp6_lexer.ll.
+%token
+  END  0  "end of file"
+  COMMA ","
+  COLON ":"
+  LSQUARE_BRACKET "["
+  RSQUARE_BRACKET "]"
+  LCURLY_BRACKET "{"
+  RCURLY_BRACKET "}"
+  NULL_TYPE "null"
+
+  DHCP6 "Dhcp6"
+  DHCP4 "Dhcp4"
+
+  DHCPDDNS "DhcpDdns"
+  IP_ADDRESS "ip-address"
+  PORT "port"
+  DNS_SERVER_TIMEOUT "dns-server-timeout"
+  NCR_PROTOCOL "ncr-protocol"
+  UDP "UDP"
+  TCP "TCP"
+  NCR_FORMAT "ncr-format"
+  JSON "JSON"
+
+  LOGGING "Logging"
+  LOGGERS "loggers"
+  NAME "name"
+  OUTPUT_OPTIONS "output_options"
+  OUTPUT "output"
+  DEBUGLEVEL "debuglevel"
+  SEVERITY "severity"
+
+  // Not real tokens, just a way to signal what the parser is expected to
+  // parse.
+  TOPLEVEL_JSON
+  TOPLEVEL_DHCPDDNS
+  SUB_DHCPDDNS
+;
+
+%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> ncr_protocol_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_DHCPDDNS { ctx.ctx_ = ctx.CONFIG; } syntax_map
+     | SUB_DHCPDDNS { ctx.ctx_ = ctx.DHCPDDNS; } sub_dhcpddns
+     ;
+
+// ---- 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: dhcp6_json_object
+             | logging_object
+             | dhcp4_json_object
+             | dhcpddns_object
+             | unknown_map_entry
+             ;
+
+// --- dhcp ddns ---------------------------------------------
+
+dhcpddns_object: DHCPDDNS {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("DhcpDdns", m);
+    ctx.stack_.push_back(m);
+    ctx.enter(ctx.DHCPDDNS);
+} COLON LCURLY_BRACKET dhcpddns_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+sub_dhcpddns: LCURLY_BRACKET {
+    // Parse the dhcpddns map
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(m);
+} dhcpddns_params RCURLY_BRACKET {
+    // parsing completed
+};
+
+dhcpddns_params: dhcpddns_param
+                | dhcpddns_params COMMA dhcpddns_param
+                ;
+// These are teh top-level parameters allowed for DhcpDdns
+dhcpddns_param: ip_address
+               | port
+               | dns_server_timeout
+               | ncr_protocol
+               | ncr_format
+               | unknown_map_entry
+               ;
+
+ip_address: IP_ADDRESS {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr s(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("ip-address", s);
+    ctx.leave();
+};
+
+port: PORT COLON INTEGER {
+    ElementPtr i(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("port", i);
+};
+
+dns_server_timeout: DNS_SERVER_TIMEOUT COLON INTEGER {
+    ElementPtr i(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("dns-server-timeout", i);
+};
+
+ncr_protocol: NCR_PROTOCOL {
+    ctx.enter(ctx.NCR_PROTOCOL);
+} COLON ncr_protocol_value {
+    ctx.stack_.back()->set("ncr-protocol", $4);
+    ctx.leave();
+};
+
+ncr_protocol_value:
+    UDP { $$ = ElementPtr(new StringElement("UDP", ctx.loc2pos(@1))); }
+  | TCP { $$ = ElementPtr(new StringElement("TCP", ctx.loc2pos(@1))); }
+  ;
+
+ncr_format: NCR_FORMAT {
+    ctx.enter(ctx.NCR_FORMAT);
+} COLON JSON {
+    ElementPtr json(new StringElement("JSON", ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("ncr-format", json);
+    ctx.leave();
+};
+
+dhcp6_json_object: DHCP6 {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON value {
+    ctx.stack_.back()->set("Dhcp6", $4);
+    ctx.leave();
+};
+
+dhcp4_json_object: DHCP4 {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON value {
+    ctx.stack_.back()->set("Dhcp4", $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
+            ;
+
+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();
+};
+
+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::D2Parser::error(const location_type& loc,
+                              const std::string& what)
+{
+    ctx.error(loc, what);
+}

+ 58 - 0
src/bin/d2/d2_simple_parser.cc

@@ -0,0 +1,58 @@
+// Copyright (C) 2017 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 <d2/d2_simple_parser.h>
+#include <cc/data.h>
+#include <boost/foreach.hpp>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+/// @brief This sets of arrays define the default values and
+///        values inherited (derived) between various scopes.
+///
+/// Each of those is documented in @file d2_simple_parser.cc. This
+/// is different than most other comments in Kea code. The reason
+/// for placing those in .cc rather than .h file is that it
+/// is expected to be one centralized place to look at for
+/// the default values. This is expected to be looked at also by
+/// people who are not skilled in C or C++, so they may be
+/// confused with the differences between declaration and definition.
+/// As such, there's one file to look at that hopefully is readable
+/// without any C or C++ skills.
+///
+/// @{
+
+/// @brief This table defines default global values for D2 
+///
+/// Some of the global parameters defined in the global scope (i.e. directly
+/// in DhcpDdns) are optional. If not defined, the following values will be
+/// used.
+const SimpleDefaults D2SimpleParser::D2_GLOBAL_DEFAULTS = {
+    { "ip-address",         Element::string, "127.0.0.1" },
+    { "port",               Element::integer, "53001" },
+    { "dns-server-timeout", Element::integer, "100" },
+    { "ncr-protocol",       Element::integer, "UDP" },
+    { "ncr-format",         Element::integer, "JSON" }
+};
+
+/// @}
+
+/// ---------------------------------------------------------------------------
+/// --- end of default values -------------------------------------------------
+/// ---------------------------------------------------------------------------
+
+size_t D2SimpleParser::setAllDefaults(isc::data::ElementPtr global) {
+    size_t cnt = 0;
+
+    // Set global defaults first.
+    cnt = setDefaults(global, D2_GLOBAL_DEFAULTS);
+    return (cnt);
+}
+
+};
+};

+ 39 - 0
src/bin/d2/d2_simple_parser.h

@@ -0,0 +1,39 @@
+// Copyright (C) 2017 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 D2_SIMPLE_PARSER_H
+#define D2_SIMPLE_PARSER_H
+
+#include <cc/simple_parser.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief SimpleParser specialized for D2
+///
+/// This class is a @ref isc::data::SimpleParser dedicated to D2.
+/// In particular, it contains all the default values and names of the
+/// parameters that are to be derived (inherited) between scopes.
+/// For the actual values, see @file d2_simple_parser.cc
+class D2SimpleParser : public isc::data::SimpleParser {
+public:
+
+    /// @brief Sets all defaults for D2 configuration
+    ///
+    /// This method sets global and element defaults.
+    ///
+    /// @param global scope to be filled in with defaults.
+    /// @return number of default values added
+    static size_t setAllDefaults(isc::data::ElementPtr global);
+
+    // see d2_simple_parser.cc for comments for those parameters
+    static const isc::data::SimpleDefaults D2_GLOBAL_DEFAULTS;
+};
+
+};
+};
+
+#endif

+ 192 - 0
src/bin/d2/location.hh

@@ -0,0 +1,192 @@
+// A Bison parser, made by GNU Bison 3.0.4.
+
+// Locations for Bison parsers in C++
+
+// Copyright (C) 2002-2015 Free Software Foundation, Inc.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+// As a special exception, you may create a larger work that contains
+// part or all of the Bison parser skeleton and distribute that work
+// under terms of your choice, so long as that work isn't itself a
+// parser generator using the skeleton or a modified version thereof
+// as a parser skeleton.  Alternatively, if you modify or redistribute
+// the parser skeleton itself, you may (at your option) remove this
+// special exception, which will cause the skeleton and the resulting
+// Bison output files to be licensed under the GNU General Public
+// License without this special exception.
+
+// This special exception was added by the Free Software Foundation in
+// version 2.2 of Bison.
+
+/**
+ ** \file location.hh
+ ** Define the isc::dhcp::location class.
+ */
+
+#ifndef YY_D2_PARSER_LOCATION_HH_INCLUDED
+# define YY_D2_PARSER_LOCATION_HH_INCLUDED
+
+# include "position.hh"
+
+#line 14 "d2_parser.yy" // location.cc:337
+namespace isc { namespace dhcp {
+#line 46 "location.hh" // location.cc:337
+  /// Abstract a location.
+  class location
+  {
+  public:
+
+    /// Construct a location from \a b to \a e.
+    location (const position& b, const position& e)
+      : begin (b)
+      , end (e)
+    {
+    }
+
+    /// Construct a 0-width location in \a p.
+    explicit location (const position& p = position ())
+      : begin (p)
+      , end (p)
+    {
+    }
+
+    /// Construct a 0-width location in \a f, \a l, \a c.
+    explicit location (std::string* f,
+                       unsigned int l = 1u,
+                       unsigned int c = 1u)
+      : begin (f, l, c)
+      , end (f, l, c)
+    {
+    }
+
+
+    /// Initialization.
+    void initialize (std::string* f = YY_NULLPTR,
+                     unsigned int l = 1u,
+                     unsigned int c = 1u)
+    {
+      begin.initialize (f, l, c);
+      end = begin;
+    }
+
+    /** \name Line and Column related manipulators
+     ** \{ */
+  public:
+    /// Reset initial location to final location.
+    void step ()
+    {
+      begin = end;
+    }
+
+    /// Extend the current location to the COUNT next columns.
+    void columns (int count = 1)
+    {
+      end += count;
+    }
+
+    /// Extend the current location to the COUNT next lines.
+    void lines (int count = 1)
+    {
+      end.lines (count);
+    }
+    /** \} */
+
+
+  public:
+    /// Beginning of the located region.
+    position begin;
+    /// End of the located region.
+    position end;
+  };
+
+  /// Join two locations, in place.
+  inline location& operator+= (location& res, const location& end)
+  {
+    res.end = end.end;
+    return res;
+  }
+
+  /// Join two locations.
+  inline location operator+ (location res, const location& end)
+  {
+    return res += end;
+  }
+
+  /// Add \a width columns to the end position, in place.
+  inline location& operator+= (location& res, int width)
+  {
+    res.columns (width);
+    return res;
+  }
+
+  /// Add \a width columns to the end position.
+  inline location operator+ (location res, int width)
+  {
+    return res += width;
+  }
+
+  /// Subtract \a width columns to the end position, in place.
+  inline location& operator-= (location& res, int width)
+  {
+    return res += -width;
+  }
+
+  /// Subtract \a width columns to the end position.
+  inline location operator- (location res, int width)
+  {
+    return res -= width;
+  }
+
+  /// Compare two location objects.
+  inline bool
+  operator== (const location& loc1, const location& loc2)
+  {
+    return loc1.begin == loc2.begin && loc1.end == loc2.end;
+  }
+
+  /// Compare two location objects.
+  inline bool
+  operator!= (const location& loc1, const location& loc2)
+  {
+    return !(loc1 == loc2);
+  }
+
+  /** \brief Intercept output stream redirection.
+   ** \param ostr the destination output stream
+   ** \param loc a reference to the location to redirect
+   **
+   ** Avoid duplicate information.
+   */
+  template <typename YYChar>
+  inline std::basic_ostream<YYChar>&
+  operator<< (std::basic_ostream<YYChar>& ostr, const location& loc)
+  {
+    unsigned int end_col = 0 < loc.end.column ? loc.end.column - 1 : 0;
+    ostr << loc.begin;
+    if (loc.end.filename
+        && (!loc.begin.filename
+            || *loc.begin.filename != *loc.end.filename))
+      ostr << '-' << loc.end.filename << ':' << loc.end.line << '.' << end_col;
+    else if (loc.begin.line < loc.end.line)
+      ostr << '-' << loc.end.line << '.' << end_col;
+    else if (loc.begin.column < end_col)
+      ostr << '-' << end_col;
+    return ostr;
+  }
+
+#line 14 "d2_parser.yy" // location.cc:337
+} } // isc::dhcp
+#line 192 "location.hh" // location.cc:337
+#endif // !YY_D2_PARSER_LOCATION_HH_INCLUDED

+ 142 - 0
src/bin/d2/parser_context.cc

@@ -0,0 +1,142 @@
+// Copyright (C) 2016-2017 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 <d2/d2_parser.h>
+#include <d2/parser_context.h>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <boost/lexical_cast.hpp>
+#include <fstream>
+#include <limits>
+
+namespace isc {
+namespace dhcp {
+
+D2ParserContext::D2ParserContext()
+  : ctx_(NO_KEYWORD), trace_scanning_(false), trace_parsing_(false)
+{
+}
+
+D2ParserContext::~D2ParserContext()
+{
+}
+
+isc::data::ElementPtr
+D2ParserContext::parseString(const std::string& str, ParserType parser_type)
+{
+    scanStringBegin(str, parser_type);
+    return (parseCommon());
+}
+
+isc::data::ElementPtr
+D2ParserContext::parseFile(const std::string& filename, ParserType parser_type) {
+    FILE* f = fopen(filename.c_str(), "r");
+    if (!f) {
+        isc_throw(D2ParseError, "Unable to open file " << filename);
+    }
+    scanFileBegin(f, filename, parser_type);
+    return (parseCommon());
+}
+
+isc::data::ElementPtr
+D2ParserContext::parseCommon() {
+    isc::dhcp::D2Parser 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(D2ParseError, "Parser abort");
+        }
+        scanEnd();
+    }
+    catch (...) {
+        scanEnd();
+        throw;
+    }
+    if (stack_.size() == 1) {
+        return (stack_[0]);
+    } else {
+        isc_throw(D2ParseError, "Expected exactly one terminal Element expected, found "
+                  << stack_.size());
+    }
+}
+
+
+void
+D2ParserContext::error(const isc::dhcp::location& loc, const std::string& what)
+{
+    isc_throw(D2ParseError, loc << ": " << what);
+}
+
+void
+D2ParserContext::error (const std::string& what)
+{
+    isc_throw(D2ParseError, what);
+}
+
+void
+D2ParserContext::fatal (const std::string& what)
+{
+    isc_throw(D2ParseError, what);
+}
+
+isc::data::Element::Position
+D2ParserContext::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
+D2ParserContext::enter(const ParserContext& ctx)
+{
+    cstack_.push_back(ctx_);
+    ctx_ = ctx;
+}
+
+void
+D2ParserContext::leave()
+{
+#if 1
+    if (cstack_.empty()) {
+        fatal("unbalanced syntactic context");
+    }
+#endif
+    ctx_ = cstack_.back();
+    cstack_.pop_back();
+}
+
+const std::string
+D2ParserContext::contextName()
+{
+    switch (ctx_) {
+    case NO_KEYWORD:
+        return ("__no keyword__");
+    case CONFIG:
+        return ("toplevel");
+    case DHCPDDNS:
+        return ("DhcpDdns");
+    case LOGGING:
+        return ("Logging");
+    case LOGGERS:
+        return ("loggers");
+    case OUTPUT_OPTIONS:
+        return ("output-options");
+    case NCR_PROTOCOL:
+        return ("ncr-protocol");
+    case NCR_FORMAT:
+        return ("ncr-format");
+    default:
+        return ("__unknown__");
+    }
+}
+
+};
+};

+ 250 - 0
src/bin/d2/parser_context.h

@@ -0,0 +1,250 @@
+// Copyright (C) 2017 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 <d2/d2_parser.h>
+#include <d2/parser_context_decl.h>
+#include <exceptions/exceptions.h>
+
+// Tell Flex the lexer's prototype ...
+#define YY_DECL isc::dhcp::D2Parser::symbol_type d2_parser_lex (D2ParserContext& 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 D2ParseError : public isc::Exception {
+public:
+    D2ParseError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Evaluation context, an interface to the expression evaluation.
+class D2ParserContext
+{
+public:
+
+    /// @brief Defines currently supported scopes
+    ///
+    /// D2Parser may eventually support multiple levels of parsing scope. 
+    /// Currently it supports only the D2 module scope which expects the data
+    /// to be parsed to be a map containing the DhcpDdns element and its
+    /// constituents.
+    ///
+    typedef enum {
+        /// This parser will parse the content as generic JSON.
+        PARSER_JSON,
+
+        ///< Used while parsing top level (contains DhcpDdns, Logging, others)
+        PARSER_CONFIG,
+
+        ///< Used while parsing content of DhcpDdns.
+        PARSER_DHCPDDNS
+    } ParserType;
+
+    /// @brief Default constructor.
+    D2ParserContext();
+
+    /// @brief destructor
+    virtual ~D2ParserContext();
+
+    /// @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 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 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
+    /// @return Element structure representing parsed text.
+    isc::data::ElementPtr 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
+    /// @return Element structure representing parsed text.
+    isc::data::ElementPtr 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 D2ParseError
+    void error(const isc::dhcp::location& loc, const std::string& what);
+
+    /// @brief Error handler
+    ///
+    /// This is a simplified error reporting tool for reporting
+    /// parsing errors.
+    ///
+    /// @param what string explaining the nature of the error.
+    /// @throw D2ParseError
+    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 D2ParseError
+    static void fatal(const std::string& what);
+
+    /// @brief Converts bison's position to one understood 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 (contains DhcpDdns, Logging, ...)
+        CONFIG,
+
+        ///< Used while parsing content of DhcpDdns.
+        DHCPDDNS,
+
+        ///< Used while parsing content of Logging
+        LOGGING,
+
+        /// Used while parsing Logging/loggers structures.
+        LOGGERS,
+
+        /// Used while parsing Logging/loggers/output_options structures.
+        OUTPUT_OPTIONS,
+
+        /// Used while parsing Dhcp6/dhcp-ddns/ncr-protocol
+        NCR_PROTOCOL,
+
+        /// Used while parsing Dhcp6/dhcp-ddns/ncr-format
+        NCR_FORMAT
+
+    } 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 DHCP6 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::ElementPtr parseCommon();
+};
+
+}; // end of isc::eval namespace
+}; // end of isc namespace
+
+#endif

+ 20 - 0
src/bin/d2/parser_context_decl.h

@@ -0,0 +1,20 @@
+// Copyright (C) 2017 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 D2_PARSER_CONTEXT_DECL_H
+#define D2_PARSER_CONTEXT_DECL_H
+
+/// @file d2/parser_context_decl.h Forward declaration of the ParserContext class
+
+namespace isc {
+namespace dhcp {
+
+class D2ParserContext;
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif

+ 180 - 0
src/bin/d2/position.hh

@@ -0,0 +1,180 @@
+// A Bison parser, made by GNU Bison 3.0.4.
+
+// Positions for Bison parsers in C++
+
+// Copyright (C) 2002-2015 Free Software Foundation, Inc.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+// As a special exception, you may create a larger work that contains
+// part or all of the Bison parser skeleton and distribute that work
+// under terms of your choice, so long as that work isn't itself a
+// parser generator using the skeleton or a modified version thereof
+// as a parser skeleton.  Alternatively, if you modify or redistribute
+// the parser skeleton itself, you may (at your option) remove this
+// special exception, which will cause the skeleton and the resulting
+// Bison output files to be licensed under the GNU General Public
+// License without this special exception.
+
+// This special exception was added by the Free Software Foundation in
+// version 2.2 of Bison.
+
+/**
+ ** \file position.hh
+ ** Define the isc::dhcp::position class.
+ */
+
+#ifndef YY_D2_PARSER_POSITION_HH_INCLUDED
+# define YY_D2_PARSER_POSITION_HH_INCLUDED
+
+# include <algorithm> // std::max
+# include <iostream>
+# include <string>
+
+# ifndef YY_NULLPTR
+#  if defined __cplusplus && 201103L <= __cplusplus
+#   define YY_NULLPTR nullptr
+#  else
+#   define YY_NULLPTR 0
+#  endif
+# endif
+
+#line 14 "d2_parser.yy" // location.cc:337
+namespace isc { namespace dhcp {
+#line 56 "position.hh" // location.cc:337
+  /// Abstract a position.
+  class position
+  {
+  public:
+    /// Construct a position.
+    explicit position (std::string* f = YY_NULLPTR,
+                       unsigned int l = 1u,
+                       unsigned int c = 1u)
+      : filename (f)
+      , line (l)
+      , column (c)
+    {
+    }
+
+
+    /// Initialization.
+    void initialize (std::string* fn = YY_NULLPTR,
+                     unsigned int l = 1u,
+                     unsigned int c = 1u)
+    {
+      filename = fn;
+      line = l;
+      column = c;
+    }
+
+    /** \name Line and Column related manipulators
+     ** \{ */
+    /// (line related) Advance to the COUNT next lines.
+    void lines (int count = 1)
+    {
+      if (count)
+        {
+          column = 1u;
+          line = add_ (line, count, 1);
+        }
+    }
+
+    /// (column related) Advance to the COUNT next columns.
+    void columns (int count = 1)
+    {
+      column = add_ (column, count, 1);
+    }
+    /** \} */
+
+    /// File name to which this position refers.
+    std::string* filename;
+    /// Current line number.
+    unsigned int line;
+    /// Current column number.
+    unsigned int column;
+
+  private:
+    /// Compute max(min, lhs+rhs) (provided min <= lhs).
+    static unsigned int add_ (unsigned int lhs, int rhs, unsigned int min)
+    {
+      return (0 < rhs || -static_cast<unsigned int>(rhs) < lhs
+              ? rhs + lhs
+              : min);
+    }
+  };
+
+  /// Add \a width columns, in place.
+  inline position&
+  operator+= (position& res, int width)
+  {
+    res.columns (width);
+    return res;
+  }
+
+  /// Add \a width columns.
+  inline position
+  operator+ (position res, int width)
+  {
+    return res += width;
+  }
+
+  /// Subtract \a width columns, in place.
+  inline position&
+  operator-= (position& res, int width)
+  {
+    return res += -width;
+  }
+
+  /// Subtract \a width columns.
+  inline position
+  operator- (position res, int width)
+  {
+    return res -= width;
+  }
+
+  /// Compare two position objects.
+  inline bool
+  operator== (const position& pos1, const position& pos2)
+  {
+    return (pos1.line == pos2.line
+            && pos1.column == pos2.column
+            && (pos1.filename == pos2.filename
+                || (pos1.filename && pos2.filename
+                    && *pos1.filename == *pos2.filename)));
+  }
+
+  /// Compare two position objects.
+  inline bool
+  operator!= (const position& pos1, const position& pos2)
+  {
+    return !(pos1 == pos2);
+  }
+
+  /** \brief Intercept output stream redirection.
+   ** \param ostr the destination output stream
+   ** \param pos a reference to the position to redirect
+   */
+  template <typename YYChar>
+  inline std::basic_ostream<YYChar>&
+  operator<< (std::basic_ostream<YYChar>& ostr, const position& pos)
+  {
+    if (pos.filename)
+      ostr << *pos.filename << ':';
+    return ostr << pos.line << '.' << pos.column;
+  }
+
+#line 14 "d2_parser.yy" // location.cc:337
+} } // isc::dhcp
+#line 180 "position.hh" // location.cc:337
+#endif // !YY_D2_PARSER_POSITION_HH_INCLUDED

+ 157 - 0
src/bin/d2/stack.hh

@@ -0,0 +1,157 @@
+// A Bison parser, made by GNU Bison 3.0.4.
+
+// Stack handling for Bison parsers in C++
+
+// Copyright (C) 2002-2015 Free Software Foundation, Inc.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+// As a special exception, you may create a larger work that contains
+// part or all of the Bison parser skeleton and distribute that work
+// under terms of your choice, so long as that work isn't itself a
+// parser generator using the skeleton or a modified version thereof
+// as a parser skeleton.  Alternatively, if you modify or redistribute
+// the parser skeleton itself, you may (at your option) remove this
+// special exception, which will cause the skeleton and the resulting
+// Bison output files to be licensed under the GNU General Public
+// License without this special exception.
+
+// This special exception was added by the Free Software Foundation in
+// version 2.2 of Bison.
+
+/**
+ ** \file stack.hh
+ ** Define the isc::dhcp::stack class.
+ */
+
+#ifndef YY_D2_PARSER_STACK_HH_INCLUDED
+# define YY_D2_PARSER_STACK_HH_INCLUDED
+
+# include <vector>
+
+#line 14 "d2_parser.yy" // stack.hh:151
+namespace isc { namespace dhcp {
+#line 46 "stack.hh" // stack.hh:151
+  template <class T, class S = std::vector<T> >
+  class stack
+  {
+  public:
+    // Hide our reversed order.
+    typedef typename S::reverse_iterator iterator;
+    typedef typename S::const_reverse_iterator const_iterator;
+
+    stack ()
+      : seq_ ()
+    {
+      seq_.reserve (200);
+    }
+
+    stack (unsigned int n)
+      : seq_ (n)
+    {}
+
+    inline
+    T&
+    operator[] (unsigned int i)
+    {
+      return seq_[seq_.size () - 1 - i];
+    }
+
+    inline
+    const T&
+    operator[] (unsigned int i) const
+    {
+      return seq_[seq_.size () - 1 - i];
+    }
+
+    /// Steal the contents of \a t.
+    ///
+    /// Close to move-semantics.
+    inline
+    void
+    push (T& t)
+    {
+      seq_.push_back (T());
+      operator[](0).move (t);
+    }
+
+    inline
+    void
+    pop (unsigned int n = 1)
+    {
+      for (; n; --n)
+        seq_.pop_back ();
+    }
+
+    void
+    clear ()
+    {
+      seq_.clear ();
+    }
+
+    inline
+    typename S::size_type
+    size () const
+    {
+      return seq_.size ();
+    }
+
+    inline
+    const_iterator
+    begin () const
+    {
+      return seq_.rbegin ();
+    }
+
+    inline
+    const_iterator
+    end () const
+    {
+      return seq_.rend ();
+    }
+
+  private:
+    stack (const stack&);
+    stack& operator= (const stack&);
+    /// The wrapped container.
+    S seq_;
+  };
+
+  /// Present a slice of the top of a stack.
+  template <class T, class S = stack<T> >
+  class slice
+  {
+  public:
+    slice (const S& stack, unsigned int range)
+      : stack_ (stack)
+      , range_ (range)
+    {}
+
+    inline
+    const T&
+    operator [] (unsigned int i) const
+    {
+      return stack_[range_ - i];
+    }
+
+  private:
+    const S& stack_;
+    unsigned int range_;
+  };
+
+#line 14 "d2_parser.yy" // stack.hh:151
+} } // isc::dhcp
+#line 156 "stack.hh" // stack.hh:151
+
+#endif // !YY_D2_PARSER_STACK_HH_INCLUDED