#include "m2_tokvec.h" #include #include #include #include #include namespace fmt { namespace { std::string lower(std::string s) { std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return (char)std::tolower(c); }); return s; } bool ieq(const std::string& a, const char* b) { return lower(a) == b; } // 一行 -> token 列表。`"..."` 单 token;`#` / `//` 到行尾注释。 std::vector tokenize_line(const std::string& line) { std::vector out; size_t i = 0, n = line.size(); while (i < n) { char c = line[i]; if (c == '#' || (c == '/' && i + 1 < n && line[i + 1] == '/')) break; if (std::isspace((unsigned char)c)) { ++i; continue; } if (c == '"') { ++i; std::string s; while (i < n && line[i] != '"') s += line[i++]; if (i < n) ++i; // 收尾引号 out.push_back(s); continue; } std::string s; while (i < n && !std::isspace((unsigned char)line[i])) s += line[i++]; out.push_back(s); } return out; } } // namespace bool TokVecMap::has(const std::string& key) const { return m.count(key) != 0; } const std::vector* TokVecMap::find(const std::string& key) const { auto it = m.find(lower(key)); return it == m.end() ? nullptr : &it->second; } std::string TokVecMap::str(const std::string& key, const std::string& def) const { auto* v = find(key); return (v && !v->empty()) ? (*v)[0] : def; } long TokVecMap::inum(const std::string& key, long def) const { auto* v = find(key); return (v && !v->empty()) ? std::strtol((*v)[0].c_str(), nullptr, 10) : def; } double TokVecMap::num(const std::string& key, double def) const { auto* v = find(key); return (v && !v->empty()) ? std::strtod((*v)[0].c_str(), nullptr) : def; } bool parse_tokvec(const std::string& text, TokVecMap& out, std::string* err) { out = TokVecMap{}; std::istringstream in(text); std::string line; std::string block_key; // 非空 = 正在收块 std::vector block_tokens; std::vector seen_dups; auto commit = [&](const std::string& key, std::vector toks) { std::string k = lower(key); if (out.m.count(k)) { out.dup_count++; if (std::find(seen_dups.begin(), seen_dups.end(), k) == seen_dups.end()) { seen_dups.push_back(k); out.dup_keys.push_back(k); } return; // 保留第一个 } out.m.emplace(std::move(k), std::move(toks)); }; while (std::getline(in, line)) { if (!line.empty() && line.back() == '\r') line.pop_back(); auto toks = tokenize_line(line); if (toks.empty()) continue; if (!block_key.empty()) { if (ieq(toks[0], "end")) { commit(block_key, std::move(block_tokens)); block_key.clear(); block_tokens.clear(); } else { for (auto& t : toks) block_tokens.push_back(std::move(t)); } continue; } if (ieq(toks[0], "start")) { if (toks.size() < 2) { if (err) *err = "Start 缺块名"; return false; } block_key = toks[1]; block_tokens.clear(); continue; } // 顶层 `key v1 v2 ...` std::string key = toks[0]; std::vector vals(toks.begin() + 1, toks.end()); commit(key, std::move(vals)); } if (!block_key.empty()) { // 文件在块中截断 —— 宽容收尾但记 err commit(block_key, std::move(block_tokens)); if (err) *err = "文件在 Start '" + block_key + "' 块中截断(缺 End)"; } return true; } static FileReader g_file_reader = nullptr; void set_file_reader(FileReader r) { g_file_reader = r; } bool read_file(const std::string& path, std::string& out) { if (g_file_reader) return g_file_reader(path, out); std::ifstream f(path, std::ios::binary); if (!f) return false; std::ostringstream ss; ss << f.rdbuf(); out = ss.str(); return true; } bool parse_tokvec_file(const std::string& path, TokVecMap& out, std::string* err) { std::string text; if (!read_file(path, text)) { if (err) *err = "打不开文件: " + path; return false; } return parse_tokvec(text, out, err); } } // namespace fmt