nginxconfig
C++libraryforparsingandprintingnginx.conf
nginxconfig/parse.cpp
Go to the documentation of this file.
00001 
00011 #include <nginxconfig/config.hpp>
00012 
00013 #ifndef NGINXCONFIG_DEBUG
00014 #   define NGINXCONFIG_DEBUG 0
00015 #endif
00016 
00022 #ifndef NGINXCONFIG_USE_BOOST_REGEX
00023 #   define NGINXCONFIG_USE_BOOST_REGEX 0
00024 #endif
00025 
00031 #ifndef NGINXCONFIG_CHECK_REGEX_IMPLEMENTATION
00032 #   define NGINXCONFIG_CHECK_REGEX_IMPLEMENTATION 1
00033 #endif
00034 
00035 #include <nginxconfig/ast.hpp>
00036 #include <nginxconfig/parse.hpp>
00037 #include <nginxconfig/parse_types.hpp>
00038 
00039 #include <algorithm>
00040 #include <cassert>
00041 #include <fstream>
00042 #include <sstream>
00043 
00044 #if NGINXCONFIG_DEBUG
00045 #   include <iostream>
00046 #   define NGINXCONFIG_DEBUG_PRINT(x) std::cerr << x << std::endl;
00047 #else
00048 #   define NGINXCONFIG_DEBUG_PRINT(x)
00049 #endif
00050 
00051 #if NGINXCONFIG_USE_BOOST_REGEX
00052 #   include <boost/regex.hpp>
00053 #else
00054 #   if NGINXCONFIG_CHECK_REGEX_IMPLEMENTATION
00055 #       ifdef __GNUC__
00056 #           if (__GNUC__ == 4) && (__GNUC_MINOR__ < 9)
00057 #               error "Cannot use Standard Library regex implementation with GCC < 4.9! Please compile with NGINXCONFIG_USE_BOOST_REGEX=1 (or disable this check with NGINXCONFIG_CHECK_REGEX_IMPLEMENTATION=0)."
00058 #           endif
00059 #       endif
00060 #   endif
00061 #   include <regex>
00062 #endif
00063 
00064 namespace nginxconfig
00065 {
00066 
00068 // parse_error                                                                                                        //
00070 
00071 static std::string parse_error_what(parse_error::size_type line,
00072                                     parse_error::size_type column,
00073                                     parse_error::size_type character,
00074                                     const std::string&     message
00075                                    )
00076 {
00077     std::ostringstream stream;
00078     stream << "On line " << line;
00079     if (column != parse_error::no_column)
00080         stream << " column " << column;
00081     
00082     stream << " (char " << character << "): "
00083            << message;
00084     return stream.str();
00085 }
00086 
00087 parse_error::parse_error(size_type line_, size_type column_, size_type character_, std::string message_) :
00088         std::runtime_error(parse_error_what(line_, column_, character_, message_)),
00089         _line(line_),
00090         _column(column_),
00091         _character(character_),
00092         _message(std::move(message_))
00093 { }
00094 
00095 parse_error::~parse_error() noexcept = default;
00096 
00098 // Parser Implementation                                                                                              //
00100 
00101 namespace parser
00102 {
00103 
00104 bool context::next()
00105 {
00106     character_no = character_no_next;
00107     if (std::getline(input, current))
00108     {
00109         NGINXCONFIG_DEBUG_PRINT("LINE:\t" << current);
00110         character_no_next = character_no + current.size();
00111         ++line_no;
00112         return true;
00113     }
00114     else
00115     {
00116         return false;
00117     }
00118 }
00119 
00120 ast_entry::attribute_list split_attributes(const std::string& attrs)
00121 {
00122     ast_entry::attribute_list out;
00123     std::string::size_type pos = 0;
00124     while ((pos = attrs.find_first_not_of(" \t", pos)) != std::string::npos)
00125     {
00126         auto pos_end = attrs.find_first_of(" \t", pos);
00127         out.emplace_back(attrs.substr(pos, pos_end - pos));
00128         pos = pos_end;
00129     }
00130     return out;
00131 }
00132 
00133 line_components line_components::create_from_line(const std::string& line)
00134 {
00135     #if NGINXCONFIG_USE_BOOST_REGEX
00136     using regex = boost::regex;
00137     using smatch = boost::smatch;
00138     using boost::regex_match;
00139     namespace regex_constants = boost::regex_constants;
00140     #else
00141     using regex = std::regex;
00142     using smatch = std::smatch;
00143     using std::regex_match;
00144     namespace regex_constants = std::regex_constants;
00145     #endif
00146     
00147     // All lines follow the same basic format:
00148     //  0) whole match
00149     //  1) name
00150     //  2) attributes
00151     //  3) either {, } or ;
00152     //  4) comment including #
00153     //  5) comment not including #
00154     static const regex line_regex(R"([ \t]*([A-Za-z_][A-Za-z0-9_]*)?[ \t]*([^{};#]*)([{};]?)[ \t]*(#(.*))?)",
00155                                   regex_constants::syntax_option_type(regex_constants::ECMAScript | regex_constants::optimize)
00156                                  );
00157     
00158     smatch results;
00159     line_components out;
00160     if (regex_match(line, results, line_regex))
00161     {
00162         #if NGINXCONFIG_DEBUG
00163         NGINXCONFIG_DEBUG_PRINT("REGEX MATCH sz=" << results.size());
00164         for (const auto& x : results)
00165         {
00166             NGINXCONFIG_DEBUG_PRINT("\t|" << x << '|');
00167         }
00168         #endif
00169         assert(results.size() == 6);
00170         out.name = results[1];
00171         out.attributes = split_attributes(results[2]);
00172         const std::string& tok = results[3];
00173         out.category = tok.size() == 0 ? line_kind::comment
00174                      : tok[0] == '{'    ? line_kind::complex_start
00175                      : tok[0] == '}'    ? line_kind::complex_end
00176                      : tok[0] == ';'    ? line_kind::simple
00177                      :                    line_kind::unknown;
00178         out.comment = results[5];
00179         NGINXCONFIG_DEBUG_PRINT(out.comment);
00180     }
00181     return out;
00182 }
00183 
00184 bool parse_generic(context& cxt, ast_entry& owner)
00185 {
00186     while (cxt.next())
00187     {
00188         line_components components = line_components::create_from_line(cxt.current);
00189         switch (components.category)
00190         {
00191             case line_kind::comment:
00192                 owner.children().emplace_back(ast_entry::make_comment(std::move(components.comment)));
00193                 break;
00194             case line_kind::simple:
00195                 owner.children().emplace_back(ast_entry::make_simple(std::move(components.name),
00196                                                                      std::move(components.attributes),
00197                                                                      std::move(components.comment)
00198                                                                     )
00199                                              );
00200                 break;
00201             case line_kind::complex_start:
00202             {
00203                 ast_entry child = ast_entry::make_complex(std::move(components.name),
00204                                                           std::move(components.attributes)
00205                                                          );
00206                 while (parse_generic(cxt, child))
00207                 {
00208                     // do nothing
00209                 }
00210                 owner.children().emplace_back(std::move(child));
00211                 break;
00212             }
00213             case line_kind::complex_end:
00214                 if (owner.kind() == ast_entry_kind::document)
00215                     throw cxt.create_parse_error(parse_error::no_column, "Unmatched end of nested entry");
00216                 else
00217                     return false;
00218             case line_kind::unknown:
00219             default:
00220                 throw cxt.create_parse_error(parse_error::no_column, "Indecipherable line: \"", cxt.current, '\"');
00221         }
00222     }
00223     if (owner.kind() == ast_entry_kind::complex)
00224         throw cxt.create_parse_error(parse_error::no_column, "EOF reached while inside nested entry");
00225     else
00226         return false;
00227 }
00228 
00229 }
00230 
00232 // Entry Points                                                                                                       //
00234 
00235 ast_entry parse(std::istream& input)
00236 {
00237     parser::context cxt(input);
00238     auto out = ast_entry::make_document({});
00239     parser::parse_generic(cxt, out);
00240     return out;
00241 }
00242 
00244 ast_entry parse_file(const std::string& filename)
00245 {
00246     std::ifstream file(filename.c_str());
00247     return parse(file);
00248 }
00249 
00250 }
 All Classes Files Functions Variables Friends Defines