summaryrefslogtreecommitdiffstats
path: root/src/bin/netconf/netconf_parser.yy
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/bin/netconf/netconf_parser.yy762
1 files changed, 762 insertions, 0 deletions
diff --git a/src/bin/netconf/netconf_parser.yy b/src/bin/netconf/netconf_parser.yy
new file mode 100644
index 0000000..db3e79a
--- /dev/null
+++ b/src/bin/netconf/netconf_parser.yy
@@ -0,0 +1,762 @@
+/* Copyright (C) 2018-2023 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.3.0"
+%defines
+%define api.parser.class {NetconfParser}
+%define api.prefix {netconf_}
+%define api.token.constructor
+%define api.value.type variant
+%define api.namespace {isc::netconf}
+%define parse.assert
+%code requires
+{
+#include <cc/data.h>
+#include <netconf/parser_context_decl.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <sstream>
+#include <string>
+
+using namespace isc::netconf;
+using namespace isc::data;
+using namespace std;
+}
+// The parsing context.
+%param { isc::netconf::ParserContext& ctx }
+%locations
+%define parse.trace
+%define parse.error verbose
+%code
+{
+#include <netconf/parser_context.h>
+
+// Avoid warnings with the error counter.
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
+#endif
+}
+
+
+%define api.token.prefix {TOKEN_}
+// Tokens in an order which makes sense and related to the intended use.
+// Actual regexps for tokens are defined in netconf_lexer.ll.
+%token
+ END 0 "end of file"
+ COMMA ","
+ COLON ":"
+ LSQUARE_BRACKET "["
+ RSQUARE_BRACKET "]"
+ LCURLY_BRACKET "{"
+ RCURLY_BRACKET "}"
+ NULL_TYPE "null"
+
+ NETCONF "Netconf"
+
+ USER_CONTEXT "user-context"
+ COMMENT "comment"
+
+ BOOT_UPDATE "boot-update"
+ SUBSCRIBE_CHANGES "subscribe-changes"
+ VALIDATE_CHANGES "validate-changes"
+
+ MANAGED_SERVERS "managed-servers"
+ DHCP4_SERVER "dhcp4"
+ DHCP6_SERVER "dhcp6"
+ D2_SERVER "d2"
+ CA_SERVER "ca"
+ MODEL "model"
+ CONTROL_SOCKET "control-socket"
+ SOCKET_TYPE "socket-type"
+ UNIX "unix"
+ HTTP "http"
+ STDOUT "stdout"
+ SOCKET_NAME "socket-name"
+ SOCKET_URL "socket-url"
+
+ HOOKS_LIBRARIES "hooks-libraries"
+ LIBRARY "library"
+ PARAMETERS "parameters"
+
+ LOGGERS "loggers"
+ NAME "name"
+ OUTPUT_OPTIONS "output_options"
+ OUTPUT "output"
+ DEBUGLEVEL "debuglevel"
+ SEVERITY "severity"
+ FLUSH "flush"
+ MAXSIZE "maxsize"
+ MAXVER "maxver"
+ PATTERN "pattern"
+
+ // Not real tokens, just a way to signal what the parser is expected to
+ // parse. This define the starting point. It either can be full grammar
+ // (START_NETCONF), part of the grammar related to Netconf (START_SUB_NETCONF)
+ // or can be any valid JSON (START_JSON)
+ START_JSON
+ START_NETCONF
+ START_SUB_NETCONF
+;
+
+%token <string> STRING "constant string"
+%token <int64_t> INTEGER "integer"
+%token <double> FLOAT "floating point"
+%token <bool> BOOLEAN "boolean"
+
+%type <ElementPtr> value
+%type <ElementPtr> map_value
+%type <ElementPtr> socket_type_value
+
+%printer { yyoutput << $$; } <*>;
+
+%%
+
+// The whole grammar starts with a map, because the config file
+// consists of Netconf, DhcpX, Logger and DhcpDdns entries in one big { }.
+%start start;
+
+// The starting token can be one of those listed below. Note these are
+// "fake" tokens. They're produced by the lexer before any input text
+// is parsed.
+start: START_JSON { ctx.ctx_ = ctx.NO_KEYWORDS; } json
+ | START_NETCONF { ctx.ctx_ = ctx.CONFIG; } netconf_syntax_map
+ | START_SUB_NETCONF { ctx.ctx_ = ctx.NETCONF; } sub_netconf
+ ;
+
+// This rule defines a "shortcut". Instead of specifying the whole structure
+// expected by full grammar, we can tell the parser to start from content of
+// the Netconf. This is very useful for unit-testing, so we don't need
+// to repeat the outer map and "Netconf" map. We can simply provide
+// the contents of that map.
+sub_netconf: LCURLY_BRACKET {
+ // Parse the Netconf map
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} global_params RCURLY_BRACKET {
+ // parsing completed
+};
+
+// --- generic JSON parser -----------------------------------------------------
+
+// json expression can be a value. What value means is defined below.
+json: value {
+ // Push back the JSON value on the stack
+ ctx.stack_.push_back($1);
+};
+
+// Rules for value. This can be one of the primary types allowed in JSON.
+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))); }
+ | map { $$ = ctx.stack_.back(); ctx.stack_.pop_back(); }
+ | list_generic { $$ = ctx.stack_.back(); ctx.stack_.pop_back(); }
+ ;
+
+// Rule for map. It will start with {, have some content and will end with }.
+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);
+} 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.
+};
+
+map_value: map { $$ = ctx.stack_.back(); ctx.stack_.pop_back(); };
+
+// Rule for map content. In some cases it is allowed to have an empty map,
+// so we should say that explicitly. In most cases, though, there will
+// be some actual content inside. That's defined by not_empty_map
+map_content: %empty // empty map
+ | not_empty_map
+ ;
+
+// Rule for content of the map. It can have one of two formats:
+// 1) string: value
+// 2) non_empty_map , string: value
+// The first case covers a single entry, while the second case
+// covers all longer lists recursively.
+not_empty_map: STRING COLON value {
+ // map containing a single entry
+ ctx.unique($1, ctx.loc2pos(@1));
+ 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.unique($3, ctx.loc2pos(@3));
+ ctx.stack_.back()->set($3, $5);
+ }
+ | not_empty_map COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+list_generic: LSQUARE_BRACKET {
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(l);
+} list_content RSQUARE_BRACKET {
+};
+
+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);
+ }
+ | not_empty_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+// --- generic JSON parser ends here -------------------------------------------
+
+// --- syntax checking parser starts here --------------------------------------
+
+// Unknown keyword in a map. This clever rule can be added to any map
+// if you want to have a nice expression printed when unknown (mistyped?)
+// parameter is found.
+unknown_map_entry: STRING COLON {
+ const string& where = ctx.contextName();
+ const string& keyword = $1;
+ error(@1,
+ "got unexpected keyword \"" + keyword + "\" in " + where + " map.");
+};
+
+// This defines the top-level { } that holds Netconf object.
+netconf_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_object 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 the single top level entry, e.g. Netconf.
+global_object: NETCONF {
+ // Let's create a MapElement that will represent it, add it to the
+ // top level map (that's already on the stack) and put the new map
+ // on the stack as well, so child elements will be able to add
+ // themselves to it.
+
+ // Prevent against duplicate.
+ ctx.unique("Netconf", ctx.loc2pos(@1));
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("Netconf", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.NETCONF);
+} COLON LCURLY_BRACKET global_params RCURLY_BRACKET {
+ // Ok, we're done with parsing Netconf. Let's take the map
+ // off the stack.
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+ | global_object_comma
+ ;
+
+global_object_comma: global_object COMMA {
+ ctx.warnAboutExtraCommas(@2);
+};
+
+
+global_params: %empty
+ | not_empty_global_params
+ ;
+
+not_empty_global_params: global_param
+ | not_empty_global_params COMMA global_param
+ | not_empty_global_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+// These are the parameters that are allowed in the top-level for
+// Netconf.
+global_param: boot_update
+ | subscribe_changes
+ | validate_changes
+ | managed_servers
+ | hooks_libraries
+ | loggers
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+boot_update: BOOT_UPDATE COLON BOOLEAN {
+ ctx.unique("boot-update", ctx.loc2pos(@1));
+ ElementPtr flag(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("boot-update", flag);
+};
+
+subscribe_changes: SUBSCRIBE_CHANGES COLON BOOLEAN {
+ ctx.unique("subscribe-changes", ctx.loc2pos(@1));
+ ElementPtr flag(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("subscribe-changes", flag);
+};
+
+validate_changes: VALIDATE_CHANGES COLON BOOLEAN {
+ ctx.unique("validate-changes", ctx.loc2pos(@1));
+ ElementPtr flag(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("validate-changes", flag);
+};
+
+user_context: USER_CONTEXT {
+ ctx.enter(ctx.NO_KEYWORDS);
+} COLON map_value {
+ ElementPtr parent = ctx.stack_.back();
+ ElementPtr user_context = $4;
+ ConstElementPtr old = parent->get("user-context");
+
+ // Handle already existing user context
+ if (old) {
+ // Check if it was a comment or a duplicate
+ if ((old->size() != 1) || !old->contains("comment")) {
+ stringstream msg;
+ msg << "duplicate user-context entries (previous at "
+ << old->getPosition().str() << ")";
+ error(@1, msg.str());
+ }
+ // Merge the comment
+ user_context->set("comment", old->get("comment"));
+ }
+
+ // Set the user context
+ parent->set("user-context", user_context);
+ ctx.leave();
+};
+
+comment: COMMENT {
+ ctx.enter(ctx.NO_KEYWORDS);
+} COLON STRING {
+ ElementPtr parent = ctx.stack_.back();
+ ElementPtr user_context(new MapElement(ctx.loc2pos(@1)));
+ ElementPtr comment(new StringElement($4, ctx.loc2pos(@4)));
+ user_context->set("comment", comment);
+
+ // Handle already existing user context
+ ConstElementPtr old = parent->get("user-context");
+ if (old) {
+ // Check for duplicate comment
+ if (old->contains("comment")) {
+ stringstream msg;
+ msg << "duplicate user-context/comment entries (previous at "
+ << old->getPosition().str() << ")";
+ error(@1, msg.str());
+ }
+ // Merge the user context in the comment
+ merge(user_context, old);
+ }
+
+ // Set the user context
+ parent->set("user-context", user_context);
+ ctx.leave();
+};
+
+// --- hooks-libraries ---------------------------------------------------------
+hooks_libraries: HOOKS_LIBRARIES {
+ ctx.unique("hooks-libraries", ctx.loc2pos(@1));
+ 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
+ | not_empty_hooks_libraries_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+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();
+};
+
+hooks_params: hooks_param
+ | hooks_params COMMA hooks_param
+ | hooks_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ | unknown_map_entry
+ ;
+
+hooks_param: library
+ | parameters
+ ;
+
+library: LIBRARY {
+ ctx.unique("library", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORDS);
+} COLON STRING {
+ ElementPtr lib(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("library", lib);
+ ctx.leave();
+};
+
+parameters: PARAMETERS {
+ ctx.unique("parameters", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORDS);
+} COLON map_value {
+ ctx.stack_.back()->set("parameters", $4);
+ ctx.leave();
+};
+
+// --- hooks-libraries end here ------------------------------------------------
+
+// --- managed-servers starts here ---------------------------------------------
+managed_servers: MANAGED_SERVERS COLON LCURLY_BRACKET {
+ ctx.unique("managed-servers", ctx.loc2pos(@1));
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("managed-servers", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.MANAGED_SERVERS);
+} servers_entries RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+servers_entries: %empty
+ | not_empty_servers_entries
+ ;
+
+not_empty_servers_entries: server_entry
+ | not_empty_servers_entries COMMA server_entry
+ | not_empty_servers_entries COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+
+// We currently support four types of servers: DHCPv4, DHCPv6, D2 and CA
+// (even though D2 socket support is not yet merged).
+server_entry: dhcp4_server
+ | dhcp6_server
+ | d2_server
+ | ca_server
+ | unknown_map_entry
+ ;
+
+// That's an entry for dhcp4 server.
+dhcp4_server: DHCP4_SERVER {
+ ctx.unique("dhcp4", ctx.loc2pos(@1));
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("dhcp4", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.SERVER);
+} COLON LCURLY_BRACKET managed_server_params RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+// That's an entry for dhcp6 server.
+dhcp6_server: DHCP6_SERVER {
+ ctx.unique("dhcp6", ctx.loc2pos(@1));
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("dhcp6", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.SERVER);
+} COLON LCURLY_BRACKET managed_server_params RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+// That's an entry for d2 server.
+d2_server: D2_SERVER {
+ ctx.unique("d2", ctx.loc2pos(@1));
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("d2", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.SERVER);
+} COLON LCURLY_BRACKET managed_server_params RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+// That's an entry for ca server.
+ca_server: CA_SERVER {
+ ctx.unique("ca", ctx.loc2pos(@1));
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("ca", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.SERVER);
+} COLON LCURLY_BRACKET managed_server_params RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+// Server parameters consist of one or more parameters.
+managed_server_params: managed_server_param
+ | managed_server_params COMMA managed_server_param
+ | managed_server_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+// We currently support two server parameters: model and control-socket.
+managed_server_param: model
+ | boot_update
+ | subscribe_changes
+ | validate_changes
+ | control_socket
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+// YANG model
+model: MODEL {
+ ctx.unique("model", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORDS);
+} COLON STRING {
+ ElementPtr model(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("model", model);
+ ctx.leave();
+};
+
+// Control socket.
+control_socket: CONTROL_SOCKET {
+ ctx.unique("control-socket", ctx.loc2pos(@1));
+ 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 parameters
+control_socket_params: control_socket_param
+ | control_socket_params COMMA control_socket_param
+ | control_socket_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+control_socket_param: socket_type
+ | socket_name
+ | socket_url
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+socket_type: SOCKET_TYPE {
+ ctx.unique("socket-type", ctx.loc2pos(@1));
+ ctx.enter(ctx.SOCKET_TYPE);
+} COLON socket_type_value {
+ ctx.stack_.back()->set("socket-type", $4);
+ ctx.leave();
+};
+
+// We currently allow unix, http and stdout control socket types.
+socket_type_value : UNIX { $$ = ElementPtr(new StringElement("unix", ctx.loc2pos(@1))); }
+ | HTTP { $$ = ElementPtr(new StringElement("http", ctx.loc2pos(@1))); }
+ | STDOUT { $$ = ElementPtr(new StringElement("stdout", ctx.loc2pos(@1))); }
+ ;
+// Unix name.
+socket_name: SOCKET_NAME {
+ ctx.unique("socket-name", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORDS);
+} COLON STRING {
+ ElementPtr name(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("socket-name", name);
+ ctx.leave();
+};
+
+// HTTP url.
+socket_url: SOCKET_URL {
+ ctx.unique("socket-url", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORDS);
+} COLON STRING {
+ ElementPtr url(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("socket-url", url);
+ ctx.leave();
+};
+
+// --- managed-servers end here ------------------------------------------------
+
+// --- Loggers starts here -----------------------------------------------------
+
+loggers: LOGGERS {
+ ctx.unique("loggers", ctx.loc2pos(@1));
+ 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
+ | loggers_entries COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+// This defines a single entry defined in loggers.
+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_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+logger_param: name
+ | output_options_list
+ | debuglevel
+ | severity
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+name: NAME {
+ ctx.unique("name", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORDS);
+} COLON STRING {
+ ElementPtr name(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("name", name);
+ ctx.leave();
+};
+
+debuglevel: DEBUGLEVEL COLON INTEGER {
+ ctx.unique("debuglevel", ctx.loc2pos(@1));
+ ElementPtr dl(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("debuglevel", dl);
+};
+
+severity: SEVERITY {
+ ctx.unique("severity", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORDS);
+} COLON STRING {
+ ElementPtr sev(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("severity", sev);
+ ctx.leave();
+};
+
+output_options_list: OUTPUT_OPTIONS {
+ ctx.unique("output_options", ctx.loc2pos(@1));
+ 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_options_list_content COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+output_entry: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+} output_params_list RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+};
+
+output_params_list: output_params
+ | output_params_list COMMA output_params
+ | output_params_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+output_params: output
+ | flush
+ | maxsize
+ | maxver
+ | pattern
+ ;
+
+output: OUTPUT {
+ ctx.unique("output", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORDS);
+} COLON STRING {
+ ElementPtr sev(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("output", sev);
+ ctx.leave();
+};
+
+flush: FLUSH COLON BOOLEAN {
+ ctx.unique("flush", ctx.loc2pos(@1));
+ ElementPtr flush(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("flush", flush);
+};
+
+maxsize: MAXSIZE COLON INTEGER {
+ ctx.unique("maxsize", ctx.loc2pos(@1));
+ ElementPtr maxsize(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("maxsize", maxsize);
+};
+
+maxver: MAXVER COLON INTEGER {
+ ctx.unique("maxver", ctx.loc2pos(@1));
+ ElementPtr maxver(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("maxver", maxver);
+};
+
+pattern: PATTERN {
+ ctx.unique("pattern", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORDS);
+} COLON STRING {
+ ElementPtr sev(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("pattern", sev);
+ ctx.leave();
+};
+
+%%
+
+void
+isc::netconf::NetconfParser::error(const location_type& loc,
+ const string& what) {
+ ctx.error(loc, what);
+}