# 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 dhcp6_parser.cc dhcp6_parser.h: dhcp6_parser.yy
$(YACC) --defines=dhcp6_parser.h --report=all --report-file=dhcp6_parser.report -o dhcp6_parser.cc dhcp6_parser.yy
bool start_token_flag = false;
isc::dhcp::Parser6Context::ParserType start_token_value;
-int comment_start_line = 0;
+unsigned int comment_start_line = 0;
};
/* 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
be on the safe side and keep it. */
%option noinput
-/* This line tells flex to track the line numbers. It's not really that
- useful for client classes, which typically are one-liners, but it may be
- useful in more complex cases. */
-%option yylineno
-
%x COMMENT
%x DIR_ENTER DIR_INCLUDE DIR_EXIT
if (start_token_flag) {
start_token_flag = false;
+ BEGIN(0);
switch (start_token_value) {
case Parser6Context::PARSER_DHCP6:
return isc::dhcp::Dhcp6Parser::make_TOPLEVEL_DHCP6(loc);
"/*" {
BEGIN(COMMENT);
- comment_start_line = yylineno;
+ comment_start_line = loc.end.line;;
}
<COMMENT>"*/" BEGIN(INITIAL);
<COMMENT>. ;
<COMMENT><<EOF>> {
- isc_throw(isc::BadValue, "Comment not closed. (/* in line " << comment_start_line);
+ isc_throw(Dhcp6ParseError, "Comment not closed. (/* in line " << comment_start_line);
}
"<?" BEGIN(DIR_ENTER);
driver.includeFile(tmp);
}
<DIR_ENTER,DIR_INCLUDE,DIR_EXIT><<EOF>> {
- isc_throw(isc::BadValue, "Directive not closed.");
+ isc_throw(Dhcp6ParseError, "Directive not closed.");
}
<DIR_EXIT>"?>" BEGIN(INITIAL);
\"type\" {
switch(driver.ctx_) {
- case isc::dhcp::Parser6Context::DATABASE:
+ case isc::dhcp::Parser6Context::LEASE_DATABASE:
+ case isc::dhcp::Parser6Context::HOSTS_DATABASE:
case isc::dhcp::Parser6Context::SERVER_ID:
return isc::dhcp::Dhcp6Parser::make_TYPE(loc);
default:
\"user\" {
switch(driver.ctx_) {
- case isc::dhcp::Parser6Context::DATABASE:
+ case isc::dhcp::Parser6Context::LEASE_DATABASE:
+ case isc::dhcp::Parser6Context::HOSTS_DATABASE:
return isc::dhcp::Dhcp6Parser::make_USER(loc);
default:
return isc::dhcp::Dhcp6Parser::make_STRING("user", loc);
\"password\" {
switch(driver.ctx_) {
- case isc::dhcp::Parser6Context::DATABASE:
+ case isc::dhcp::Parser6Context::LEASE_DATABASE:
+ case isc::dhcp::Parser6Context::HOSTS_DATABASE:
return isc::dhcp::Dhcp6Parser::make_PASSWORD(loc);
default:
return isc::dhcp::Dhcp6Parser::make_STRING("password", loc);
\"host\" {
switch(driver.ctx_) {
- case isc::dhcp::Parser6Context::DATABASE:
+ case isc::dhcp::Parser6Context::LEASE_DATABASE:
+ case isc::dhcp::Parser6Context::HOSTS_DATABASE:
return isc::dhcp::Dhcp6Parser::make_HOST(loc);
default:
return isc::dhcp::Dhcp6Parser::make_STRING("host", loc);
\"persist\" {
switch(driver.ctx_) {
- case isc::dhcp::Parser6Context::DATABASE:
+ case isc::dhcp::Parser6Context::LEASE_DATABASE:
+ case isc::dhcp::Parser6Context::HOSTS_DATABASE:
case isc::dhcp::Parser6Context::SERVER_ID:
return isc::dhcp::Dhcp6Parser::make_PERSIST(loc);
default:
\"lfc-interval\" {
switch(driver.ctx_) {
- case isc::dhcp::Parser6Context::DATABASE:
+ case isc::dhcp::Parser6Context::LEASE_DATABASE:
+ case isc::dhcp::Parser6Context::HOSTS_DATABASE:
return isc::dhcp::Dhcp6Parser::make_LFC_INTERVAL(loc);
default:
return isc::dhcp::Dhcp6Parser::make_STRING("lfc-interval", loc);
\"name\" {
switch(driver.ctx_) {
- case isc::dhcp::Parser6Context::DATABASE:
+ case isc::dhcp::Parser6Context::LEASE_DATABASE:
+ case isc::dhcp::Parser6Context::HOSTS_DATABASE:
case isc::dhcp::Parser6Context::OPTION_DATA:
case isc::dhcp::Parser6Context::CLIENT_CLASSES:
case isc::dhcp::Parser6Context::CLIENT_CLASS:
// ---- syntax checking parser starts here -----------------------------
+// Unknown keyword in a map
+unknown_map_entry: STRING COLON {
+ const std::string& where = ctx.context_name();
+ 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.
// ctx_ = CONFIG
| server_id
| dhcp4o6_port
| dhcp_ddns
+ | unknown_map_entry
;
preferred_lifetime: PREFERRED_LIFETIME COLON INTEGER {
ElementPtr i(new MapElement());
ctx.stack_.back()->set("lease-database", i);
ctx.stack_.push_back(i);
- ctx.enter(ctx.DATABASE);
+ ctx.enter(ctx.LEASE_DATABASE);
} COLON LCURLY_BRACKET database_map_params RCURLY_BRACKET {
ctx.stack_.pop_back();
ctx.leave();
ElementPtr i(new MapElement());
ctx.stack_.back()->set("hosts-database", i);
ctx.stack_.push_back(i);
- ctx.enter(ctx.DATABASE);
+ ctx.enter(ctx.HOSTS_DATABASE);
} COLON LCURLY_BRACKET database_map_params RCURLY_BRACKET {
ctx.stack_.pop_back();
ctx.leave();
| host
| name
| persist
- | lfc_interval;
+ | lfc_interval
+ | unknown_map_entry
;
type: TYPE {
};
hooks_params: hooks_param
- | hooks_params hooks_param
+ | hooks_params COMMA hooks_param
;
hooks_param: LIBRARY {
| id
| client_class
| reservations
+ | unknown_map_entry
;
subnet: SUBNET {
| option_data_code
| option_data_space
| option_data_csv_format
+ | unknown_map_entry
;
pool_param: pool_entry
| option_data_list
+ | unknown_map_entry
;
pool_entry: POOL {
| pd_prefix_len
| pd_delegated_len
| option_data_list
+ | unknown_map_entry
;
pd_prefix: PREFIX {
| hw_address
| hostname
| option_data_list
+ | unknown_map_entry
;
ip_addresses: IP_ADDRESSES {
client_class_param: client_class_name
| client_class_test
| option_data_list
+ | unknown_map_entry
;
client_class_name: name;
| htype
| enterprise_id
| persist
+ | unknown_map_entry
;
htype: HTYPE COLON INTEGER {
| output_options_list
| debuglevel
| severity
+ | unknown_map_entry
;
debuglevel: DEBUGLEVEL COLON INTEGER {
dhcp_ddns_param: enable_updates
| qualifying_suffix
+ | unknown_map_entry
;
enable_updates: ENABLE_UPDATES COLON BOOLEAN {
if (stack_.size() == 1) {
return (stack_[0]);
} else {
- isc_throw(BadValue, "Expected exactly one terminal Element expected, found "
+ isc_throw(Dhcp6ParseError, "Expected exactly one terminal Element expected, found "
<< stack_.size());
}
}
Parser6Context::parseFile(const std::string& filename, ParserType parser_type) {
FILE* f = fopen(filename.c_str(), "r");
if (!f) {
- isc_throw(BadValue, "Unable to open file " << filename);
+ isc_throw(Dhcp6ParseError, "Unable to open file " << filename);
}
scanFileBegin(f, filename, parser_type);
if (stack_.size() == 1) {
return (stack_[0]);
} else {
- isc_throw(BadValue, "Expected exactly one terminal Element expected, found "
+ isc_throw(Dhcp6ParseError, "Expected exactly one terminal Element expected, found "
<< stack_.size());
}
}
void
Parser6Context::error(const isc::dhcp::location& loc, const std::string& what)
{
- isc_throw(EvalParseError, loc << ": " << what);
+ isc_throw(Dhcp6ParseError, loc << ": " << what);
}
void
Parser6Context::error (const std::string& what)
{
- isc_throw(EvalParseError, what);
+ isc_throw(Dhcp6ParseError, what);
}
void
Parser6Context::fatal (const std::string& what)
{
- isc_throw(Unexpected, what);
+ isc_throw(Dhcp6ParseError, what);
}
void
cstack_.pop_back();
}
+const std::string
+Parser6Context::context_name()
+{
+ switch (ctx_) {
+ case NO_KEYWORD:
+ return ("__no keyword__");
+ case CONFIG:
+ return ("__config__");
+ case DHCP6:
+ return ("Dhcp6");
+ case LOGGING:
+ return ("Logging");
+ case INTERFACES_CONFIG:
+ return ("interfaces-config");
+ case LEASE_DATABASE:
+ return ("lease-database");
+ case HOSTS_DATABASE:
+ return ("hosts-database");
+ case MAC_SOURCES:
+ return ("mac-sources");
+ case HOST_RESERVATION_IDENTIFIERS:
+ return ("host-reservation-identifiers");
+ case HOOKS_LIBRARIES:
+ return ("hooks-librairies");
+ case SUBNET6:
+ return ("subnet6");
+ case OPTION_DATA:
+ return ("option-data");
+ case CLIENT_CLASSES:
+ return ("client-classes");
+ case SERVER_ID:
+ return ("server-id");
+ case DHCP_DDNS:
+ return ("dhcp-ddns");
+ case POOLS:
+ return ("pools");
+ case PD_POOLS:
+ return ("pd-pools");
+ case RESERVATIONS:
+ return ("reservations");
+ case CLIENT_CLASS:
+ return ("client-class");
+ case LOGGERS:
+ return ("loggers");
+ case OUTPUT_OPTIONS:
+ return ("output-options");
+ default:
+ return ("__unknown__");
+ }
+}
+
};
};
namespace isc {
namespace dhcp {
-/// @brief Evaluation error exception raised when trying to parse an axceptions.
-class EvalParseError : public isc::Exception {
+/// @brief Evaluation error exception raised when trying to parse.
+class Dhcp6ParseError : public isc::Exception {
public:
- EvalParseError(const char* file, size_t line, const char* what) :
+ Dhcp6ParseError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief Error handler
///
/// This is a simplified error reporting tool for possible future
- /// cases when the EvalParser is not able to handle the packet.
+ /// cases when the Dhcp6Parser is not able to handle the packet.
void error(const std::string& what);
/// @brief Fatal error handler
LOGGING,
/// Dhcp6
INTERFACES_CONFIG,
- DATABASE,
+ LEASE_DATABASE,
+ HOSTS_DATABASE,
MAC_SOURCES,
HOST_RESERVATION_IDENTIFIERS,
HOOKS_LIBRARIES,
/// @throw isc::Unexpected if unbalanced
void leave();
+ /// @brief Get the syntactix context name
+ const std::string context_name();
+
private:
/// @brief Flag determining scanner debugging.
bool trace_scanning_;
}
}
+void testError(const std::string& txt,
+ Parser6Context::ParserType parser_type,
+ const std::string& msg)
+{
+ try {
+ Parser6Context ctx;
+ ConstElementPtr parsed = ctx.parseString(txt, parser_type);
+ FAIL() << "Expected Dhcp6ParseError but nothing was raised";
+ }
+ catch (const Dhcp6ParseError& ex) {
+ EXPECT_EQ(msg, ex.what());
+ }
+ catch (...) {
+ FAIL() << "Expected Dhcp6ParseError but something else was raised";
+ }
+}
+
+// Check lexer errors
+TEST(ParserTest, lexerErrors) {
+ testError("",
+ Parser6Context::PARSER_GENERIC_JSON,
+ "<string>:1.1: syntax error, unexpected end of file, "
+ "expecting {");
+ testError(" ",
+ Parser6Context::PARSER_GENERIC_JSON,
+ "<string>:1.2: syntax error, unexpected end of file, "
+ "expecting {");
+ testError("\n",
+ Parser6Context::PARSER_GENERIC_JSON,
+ "<string>:2.1: syntax error, unexpected end of file, "
+ "expecting {");
+ testError("# nothing\n",
+ Parser6Context::PARSER_GENERIC_JSON,
+ "<string>:2.1: syntax error, unexpected end of file, "
+ "expecting {");
+ testError(" #\n",
+ Parser6Context::PARSER_GENERIC_JSON,
+ "<string>:2.1: syntax error, unexpected end of file, "
+ "expecting {");
+ testError("// nothing\n",
+ Parser6Context::PARSER_GENERIC_JSON,
+ "<string>:2.1: syntax error, unexpected end of file, "
+ "expecting {");
+ testError("/* nothing */\n",
+ Parser6Context::PARSER_GENERIC_JSON,
+ "<string>:2.1: syntax error, unexpected end of file, "
+ "expecting {");
+ testError("/* no\nthing */\n",
+ Parser6Context::PARSER_GENERIC_JSON,
+ "<string>:3.1: syntax error, unexpected end of file, "
+ "expecting {");
+ testError("/* no\nthing */\n\n",
+ Parser6Context::PARSER_GENERIC_JSON,
+ "<string>:4.1: syntax error, unexpected end of file, "
+ "expecting {");
+ testError("/* nothing\n",
+ Parser6Context::PARSER_GENERIC_JSON,
+ "Comment not closed. (/* in line 1");
+ fprintf(stderr, "got 124?\n");
+ testError("/**/\n\n\n/* nothing\n",
+ Parser6Context::PARSER_GENERIC_JSON,
+ "Comment not closed. (/* in line 4");
+ testError("<?\n",
+ Parser6Context::PARSER_GENERIC_JSON,
+ "Directive not closed.");
+ testError("<?include\n",
+ Parser6Context::PARSER_GENERIC_JSON,
+ "Directive not closed.");
+ string file = string(CFG_EXAMPLES) + "/" + "stateless.json";
+ fprintf(stderr, "doing include\n");
+ testError("<?include \"" + file + "\"\n",
+ Parser6Context::PARSER_GENERIC_JSON,
+ "Directive not closed.");
+}
+
};