// file: ( «line» )* // line: ( «comment» | ( «section» | «assignment» )? ( «comment» )? ) ( '\n' | EOF ) // section: '[' «name» ( ";" «name» )* ']' // name: ( /[^\] \t\n]/ | /\\[\] \t\n\\]/ )+ // assignment: «key» '=' «value» // key: ( /[^= \t\n]/ | /\\[= \t\n\\]/ )+ // value: ( «single_string» | «double_string» | «bare_string» ) // single_string: "'" ( /[^']/ | /\\['\\]/ )* "'" // double_string: '"' ( /[^"]/ | /\\["\\]/ )* '"' // bare_string: ( /[^ \t\n]/ | /\\[ \t\n\\]/ )+ // comment: '#' ( /[^\n]/ )* #include "parser.h" static bool backtrack (char const*& p, char const* bt) { return (p = bt), false; } static bool parse_ws (char const*& p, char const* pe) { while(p != pe && strchr(" \t", *p)) ++p; return true; } static bool parse_char (char const*& p, char const*& pe, char const* ch) { return parse_ws(p, pe) && p != pe && strchr(ch, *p) ? (++p, true) : false; } static bool parse_until (char const*& p, char const*& pe, char const* stopChars, std::string& res) { res.clear(); for(; p != pe && !strchr(stopChars, *p); ++p) { if(*p == '\\' && p+1 != pe && (p[1] == '\\' || strchr(stopChars, p[1]))) ++p; res += *p; } return true; } static bool parse_comment (char const*& p, char const*& pe) { std::string commentString; if(parse_char(p, pe, "#") && !parse_until(p, pe, "\n", commentString)) p = pe; // failed to find ‘\n’ (at EOF) so ‘parse_until’ backtracked to start of comment return true; } static bool parse_string_single (char const*& p, char const* pe, std::string& res) { char const* bt = p; return parse_ws(p, pe) && parse_char(p, pe, "'") && parse_until(p, pe, "'\n", res) && parse_char(p, pe, "'") || backtrack(p, bt); } static bool parse_string_double (char const*& p, char const* pe, std::string& res) { char const* bt = p; return parse_ws(p, pe) && parse_char(p, pe, "\"") && parse_until(p, pe, "\"\n", res) && parse_char(p, pe, "\"") || backtrack(p, bt); } static bool parse_string_unquoted (char const*& p, char const* pe, std::string& res) { char const* bt = p; return parse_ws(p, pe) && p != pe && *p != '\n' && parse_until(p, pe, " \t\n", res) && !res.empty() || backtrack(p, bt); } static bool parse_string (char const*& p, char const* pe, std::string& res) { return parse_string_single(p, pe, res) || parse_string_double(p, pe, res) || parse_string_unquoted(p, pe, res); } static bool parse_key (char const*& p, char const* pe, std::string& res) { char const* bt = p; bool match = parse_ws(p, pe) && p != pe && *p != '\n' && parse_until(p, pe, "= \t\n", res) && !res.empty() || backtrack(p, bt); return match; } static bool parse_assignment (char const*& p, char const* pe, std::pair& res) { char const* bt = p; return parse_key(p, pe, res.first) && parse_char(p, pe, "=") && parse_string(p, pe, res.second) || backtrack(p, bt); } static bool parse_header (char const*& p, char const* pe, std::vector& res) { char const* bt = p; if(parse_char(p, pe, "[")) { std::string name; while(parse_string_single(p, pe, name) || parse_string_double(p, pe, name) || (parse_ws(p, pe) && parse_until(p, pe, ";] \t\n", name))) { res.push_back(name); if(parse_char(p, pe, "]")) return true; if(!parse_char(p, pe, ";")) break; } } res.clear(); return backtrack(p, bt); } static bool parse_line (char const*& p, char const* pe, ini_file_t& res, int n = 0) { std::vector header; std::pair assignment; char const* bt = p; parse_ws(p, pe); parse_comment(p, pe); if(parse_header(p, pe, header)) res.new_section(header); else if(parse_assignment(p, pe, assignment)) res.insert_value(assignment.first, assignment.second); if(parse_comment(p, pe) && (p == pe || parse_char(p, pe, "\n"))) return true; std::string trailing; parse_until(p, pe, "\n", trailing); fprintf(stderr, "%s:%d: error: incorrect syntax ‘%.*s’\n", res.path.c_str(), n, (int)((strchr(bt, '\n') ?: pe) - bt), bt); parse_char(p, pe, "\n"); return false; } char const* parse_ini (char const* p, char const* pe, ini_file_t& res) { for(int n = 1; p != pe; ++n) { // fprintf(stderr, "parse line %d: ‘%.*s’ (%s)\n", n, (strchr(p, '\n') ?: pe) - p, p, res.path.c_str()); parse_line(p, pe, res, n); } return p; }