parser_context.h 9.81 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
// Copyright (C) 2018 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 NETCONF_PARSER_CONTEXT_H
#define NETCONF_PARSER_CONTEXT_H
#include <string>
#include <map>
#include <vector>
#include <netconf/netconf_parser.h>
#include <netconf/parser_context_decl.h>
#include <cc/dhcp_config_error.h>
#include <exceptions/exceptions.h>

// Tell Flex the lexer's prototype ...
#define YY_DECL isc::netconf::NetconfParser::symbol_type netconf_lex (ParserContext& driver)

// ... and declare it for the parser's sake.
YY_DECL;

namespace isc {
namespace netconf {

/// @brief Parser context is a wrapper around flex/bison instances dedicated to
///        Netconf-agent config file parser.
///
/// It follows the same principle as other components. The primary interface
/// are @ref parseString and @ref parseFile methods. All other methods are
/// public for testing purposes only. This interface allows parsing the
/// whole configuration with syntactic checking (which is by far the most
/// frequent use), but it also allows parsing input as generic JSON or
/// parse only content of the Netconf-agent object, which is a subset
/// of full grammar (this will be very useful for unit-tests to not duplicate
/// unnecessary parts of the config file).
class ParserContext
{
public:

    /// @brief Defines currently supported scopes
    ///
    /// NetconfParser is able to parse several types of scope. Usually,
    /// when it parses a config file, it expects the data to have a map
    /// with Netconf-agent in it and all the parameters within that map.
    /// However, sometimes the parser is expected to parse only a subset
    /// of that information.
    typedef enum {
        /// This parser will parse the content as generic JSON.
        PARSER_JSON,

        /// This parser will expect the content as Netconf config wrapped
        /// in a map (that's the regular config file)
        PARSER_NETCONF,

        /// This parser will expect only the content of Netconf.
        PARSER_SUB_NETCONF
    } ParserType;

    /// @brief Default constructor.
    ParserContext();

    /// @brief destructor
    virtual ~ParserContext();

    /// @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 (usually NETCONF or generic JSON)
    /// @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 (usually PARSER_NETCONF
    ///                                                or PARSER_JSON)
    /// @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 ParseError
    void error(const isc::netconf::location& loc, const std::string& what);

    /// @brief Error handler
    ///
    /// This is a simplified error reporting tool for possible future
    /// cases when the NetconfParser is not able to handle the packet.
    ///
    /// @param what string explaining the nature of the error.
    /// @throw ParseError
    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 ParseError
    static void fatal(const std::string& what);

    /// @brief Converts bison's position to one understandable by isc::data::Element
    ///
    /// Convert a bison location into an element position
    /// (take the begin, the end is lost)
    ///
    /// @param loc location in bison format
    /// @return Position in format accepted by Element
    isc::data::Element::Position loc2pos(isc::netconf::location& loc);

    /// @brief Check if a required parameter is present
    ///
    /// Check if a required parameter is present in the map at the top
    /// of the stack and raise an error when it is not.
    ///
    /// @param name name of the parameter to check
    /// @param open_loc location of the opening curly bracket
    /// @param close_loc ocation of the closing curly bracket
    /// @throw ParseError
    void require(const std::string& name,
                 isc::data::Element::Position open_loc,
                 isc::data::Element::Position close_loc);
    
    /// @brief Defines syntactic contexts for lexical tie-ins
    typedef enum {
        ///< This one is used in pure JSON mode.
        NO_KEYWORDS,

        ///< Used while parsing top level (that contains Netconf, Logging and others)
        CONFIG,

        ///< Used while parsing content of Netconf.
        NETCONF,

        ///< Used while parsing content of Logging.
        LOGGING,

        /// Used while parsing Netconf/managed-servers.
        MANAGED_SERVERS,

        ///< Used while parsing Netconf/managed-servers/*.
        SERVER,

        ///< Used while parsing Netconf/manages-servers/*/control-socket
        CONTROL_SOCKET,

        ///< Used while parsing Netconf/managed-servers/*/control-socket/type.
        SOCKET_TYPE,

        ///< Used while parsing Netconf/hooks-libraries.
        HOOKS_LIBRARIES,

        ///< Used while parsing Logging/loggers structures.
        LOGGERS,

        ///< Used while parsing Logging/loggers/output_options structures.
        OUTPUT_OPTIONS

    } LexerContext;

    /// @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::netconf::location loc_;

    /// @brief Location stack
    std::vector<isc::netconf::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
    LexerContext 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. Netconf Agent parser uses simplified
    /// contexts: either it recognizes keywords (value set to KEYWORDS)
    /// or not (value set to NO_KEYWORDS).
    ///
    /// Make sure to call @ref leave() once the parsing of your
    /// context is complete.
    ///
    /// @param ctx the syntactic context to enter into
    void enter(const LexerContext& ctx);

    /// @brief Leave a syntactic context
    ///
    /// @ref enter() must be called before (when entering a new scope
    /// or context). Once you complete the parsing, this method
    /// should be called.
    ///
    /// @throw isc::Unexpected if unbalanced (more leave() than enter() calls)
    void leave();

    /// @brief Get the syntactic context name
    ///
    /// @return printable name of the context.
    const std::string contextName();

 private:
    /// @brief Flag determining scanner debugging.
    bool trace_scanning_;

    /// @brief Flag determining parser debugging.
    bool trace_parsing_;

    /// @brief Syntactic context stack
    std::vector<LexerContext> cstack_;

    /// @brief Common part of parseXXX
    ///
    /// @return Element structure representing parsed text.
    isc::data::ElementPtr parseCommon();
};

}; // end of isc::netconf namespace
}; // end of isc namespace

#endif