diff --git a/toml/parser.hpp b/toml/parser.hpp index 3689b85..aa36d0b 100644 --- a/toml/parser.hpp +++ b/toml/parser.hpp @@ -632,7 +632,7 @@ result parse_local_time(location& loc) } else { - if(before_secfrac != loc.iter()) + if(before_secfrac != inner_loc.iter()) { throw internal_error(format_underline("[error]: " "toml::parse_local_time: invalid subsecond format", @@ -1096,11 +1096,219 @@ result parse_value(location& loc) if(auto r = parse_integer (loc)) {return ok(value(std::move(r.unwrap())));} const auto msg = format_underline("[error] toml::parse_value: " - "unknown token appeared", loc, "unknown", std::move(helps)); + "unknown token appeared", loc, "unknown"); loc.iter() = first; return err(msg); } +template +result, std::string> parse_table_key(location& loc) +{ + if(auto token = lex_std_table::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto open = lex_std_table_open::invoke(inner_loc); + if(!open || inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline("[error] " + "toml::parse_table_key: no `[`", inner_loc, "should be `[`")); + } + const auto keys = parse_key(inner_loc); + if(!keys) + { + throw internal_error(format_underline("[error] " + "toml::parse_table_key: invalid key", inner_loc, "not key")); + } + const auto close = lex_std_table_close::invoke(inner_loc); + if(!close) + { + throw internal_error(format_underline("[error] " + "toml::parse_table_key: no `]`", inner_loc, "should be `]`")); + } + return keys; + } + else + { + return err(token.unwrap_err()); + } +} + +template +result, std::string> +parse_array_table_key(location& loc) +{ + if(auto token = lex_array_table::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto open = lex_array_table_open::invoke(inner_loc); + if(!open || inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline("[error] " + "toml::parse_array_table_key: no `[[`", inner_loc, + "should be `[[`")); + } + const auto keys = parse_key(inner_loc); + if(!keys) + { + throw internal_error(format_underline("[error] " + "toml::parse_array_table_key: invalid key", inner_loc, + "not key")); + } + const auto close = lex_array_table_close::invoke(inner_loc); + if(!close) + { + throw internal_error(format_underline("[error] " + "toml::parse_table_key: no `]]`", inner_loc, "should be `]]`")); + } + return keys; + } + else + { + return err(token.unwrap_err()); + } +} + +// parse table body (key-value pairs until the iter hits the next [tablekey]) +template +result parse_ml_table(location& loc) +{ + const auto first = loc.iter(); + if(first == loc.end()) + { + return err(std::string("toml::parse_ml_table: input is empty")); + } + + using skip_line = repeat< + sequence, maybe, lex_newline>, unlimited>; + skip_line::invoke(loc); + + table tab; + while(loc.iter() != loc.end()) + { + lex_ws::invoke(loc); + const auto before = loc.iter(); + if(const auto tmp = parse_array_table_key(loc)) // next table found + { + loc.iter() = before; + return ok(tab); + } + if(const auto tmp = parse_table_key(loc)) // next table found + { + loc.iter() = before; + return ok(tab); + } + if(const auto kv = parse_key_value_pair(loc)) + { + const std::vector& keys = kv.unwrap().first; + const value& val = kv.unwrap().second; + const auto inserted = + insert_nested_key(tab, val, keys.begin(), keys.end()); + if(!inserted) + { + return err(inserted.unwrap_err()); + } + } + else + { + return err(kv.unwrap_err()); + } + + skip_line::invoke(loc); + // comment lines are skipped by the above function call. + // However, if the file ends with comment without newline, + // it might cause parsing error because skip_line matches + // `comment + newline`, not `comment` itself. to skip the + // last comment, call lex_comment one more time. + lex_comment::invoke(loc); + } + return ok(tab); +} + +template +result parse_toml_file(location& loc) +{ + const auto first = loc.iter(); + if(first == loc.end()) + { + return err(std::string("toml::detail::parse_toml_file: input is empty")); + } + + table data; + /* root object is also table, but without [tablename] */ + if(auto tab = parse_ml_table(loc)) + { + data = std::move(tab.unwrap()); + } + else // failed (empty table is also success) + { + return err(tab.unwrap_err()); + } + while(loc.iter() != loc.end()) + { + if(const auto tabkey = parse_array_table_key(loc)) + { + const auto tab = parse_ml_table(loc); + if(!tab){return err(tab.unwrap_err());} + + const auto inserted = insert_nested_key(data, tab.unwrap(), + tabkey.unwrap().begin(), tabkey.unwrap().end(), true); + if(!inserted) {return err(inserted.unwrap_err());} + + continue; + } + if(const auto tabkey = parse_table_key(loc)) + { + const auto tab = parse_ml_table(loc); + if(!tab){return err(tab.unwrap_err());} + + const auto inserted = insert_nested_key(data, tab.unwrap(), + tabkey.unwrap().begin(), tabkey.unwrap().end()); + if(!inserted) {return err(inserted.unwrap_err());} + + continue; + } + return err(format_underline("[error]: toml::parse_toml_file: " + "unknown line appeared", loc, "unknown format")); + } + return ok(data); +} + } // detail + +inline table parse(std::istream& is, std::string fname = "unknown file") +{ + const auto beg = is.tellg(); + is.seekg(0, std::ios::end); + const auto end = is.tellg(); + const auto fsize = end - beg; + is.seekg(beg); + + // read whole file as a sequence of char + std::vector letters(fsize); + is.read(letters.data(), fsize); + + detail::location> + loc(std::move(fname), std::move(letters)); + + const auto data = detail::parse_toml_file(loc); + if(!data) + { + throw syntax_error(data.unwrap_err()); + } + return data.unwrap(); +} + +inline table parse(const std::string& fname) +{ + std::ifstream ifs(fname.c_str()); + if(!ifs.good()) + { + throw std::runtime_error("toml::parse: file open error -> " + fname); + } + return parse(ifs, fname); +} + } // toml #endif// TOML11_PARSER_HPP