Skip to content

Commit 4b0cce3

Browse files
server: adopt aldehir's line-oriented PEG parser
Complete rewrite of INI parser grammar and visitor: - Use p.chars(), p.negate(), p.any() instead of p.until() - Support end-of-line comments (key=value # comment) - Handle EOF without trailing newline correctly - Strict identifier validation ([a-zA-Z_][a-zA-Z0-9_.-]*) - Simplified visitor (no pending state, no trim needed) - Grammar handles whitespace natively via eol rule Business validation preserved: - Reject section names starting with LLAMA_ARG_* - Accept only keys starting with LLAMA_ARG_* - Require explicit section before key-value pairs Co-authored-by: aldehir <[email protected]>
1 parent 5bdf2c5 commit 4b0cce3

File tree

1 file changed

+50
-73
lines changed

1 file changed

+50
-73
lines changed

tools/server/server-config.cpp

Lines changed: 50 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
#include <cctype>
88
#include <fstream>
99
#include <functional>
10-
#include <optional>
1110
#include <set>
1211

1312
namespace {
@@ -16,19 +15,6 @@ bool is_option(const std::string & arg) {
1615
return !arg.empty() && arg[0] == '-';
1716
}
1817

19-
std::string trim(const std::string & value) {
20-
const auto is_space = [](unsigned char c) { return std::isspace(c) != 0; };
21-
size_t start = 0;
22-
while (start < value.size() && is_space(value[start])) {
23-
++start;
24-
}
25-
size_t end = value.size();
26-
while (end > start && is_space(value[end - 1])) {
27-
--end;
28-
}
29-
return value.substr(start, end - start);
30-
}
31-
3218
bool is_implicit_value(const std::vector<std::string> & args, size_t index) {
3319
return index + 1 < args.size() && !is_option(args[index + 1]);
3420
}
@@ -91,29 +77,44 @@ void server_config_manager::ensure_loaded() {
9177
std::string contents((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
9278

9379
static const auto parser = build_peg_parser([](auto & p) {
94-
const auto ws = p.space();
95-
const auto new_line = p.choice({p.literal("\r\n"), p.literal("\n"), p.literal("\r")});
80+
// newline ::= "\r\n" / "\n" / "\r"
81+
auto newline = p.rule("newline", p.literal("\r\n") | p.literal("\n") | p.literal("\r"));
82+
83+
// ws ::= [ \t]*
84+
auto ws = p.rule("ws", p.chars("[ \t]", 0, -1));
85+
86+
// comment ::= [;#] (!newline .)*
87+
auto comment = p.rule("comment", p.chars("[;#]", 1, 1) + p.zero_or_more(p.negate(newline) + p.any()));
88+
89+
// eol ::= ws comment? (newline / EOF)
90+
auto eol = p.rule("eol", ws + p.optional(comment) + (newline | p.end()));
9691

97-
const auto section_name = p.tag("section-name", p.until("]"));
98-
const auto section_line = ws + "[" + section_name + "]" + p.until_one_of({"\r", "\n"});
92+
// ident ::= [a-zA-Z_] [a-zA-Z0-9_.-]*
93+
auto ident = p.rule("ident", p.chars("[a-zA-Z_]", 1, 1) + p.chars("[a-zA-Z0-9_.-]", 0, -1));
9994

100-
const auto key = p.tag("key", p.until("="));
101-
const auto value = p.tag("value", p.until_one_of({"\r", "\n"}));
102-
const auto key_value_line = ws + key + ws + "=" + ws + value;
95+
// value ::= (!eol-start .)*
96+
auto eol_start = p.rule("eol-start", ws + (p.chars("[;#]", 1, 1) | newline | p.end()));
97+
auto value = p.rule("value", p.zero_or_more(p.negate(eol_start) + p.any()));
10398

104-
const auto comment = p.choice({p.literal(";"), p.literal("#")}) + p.until_one_of({"\r", "\n"});
105-
const auto comment_line = ws + comment;
99+
// header-line ::= "[" ws ident ws "]" eol
100+
auto header_line = p.rule("header-line", "[" + ws + p.tag("section-name", p.chars("[^]]")) + ws + "]" + eol);
106101

107-
const auto blank_line = ws + new_line;
102+
// kv-line ::= ident ws "=" ws value eol
103+
auto kv_line = p.rule("kv-line", p.tag("key", ident) + ws + "=" + ws + p.tag("value", value) + eol);
108104

109-
const auto line = p.choice({
110-
section_line + new_line,
111-
key_value_line + new_line,
112-
comment_line + new_line,
113-
blank_line,
114-
});
105+
// comment-line ::= ws comment (newline / EOF)
106+
auto comment_line = p.rule("comment-line", ws + comment + (newline | p.end()));
115107

116-
return p.rule("ini", p.zero_or_more(line) + p.optional(ws) + p.end());
108+
// blank-line ::= ws (newline / EOF)
109+
auto blank_line = p.rule("blank-line", ws + (newline | p.end()));
110+
111+
// line ::= header-line / kv-line / comment-line / blank-line
112+
auto line = p.rule("line", header_line | kv_line | comment_line | blank_line);
113+
114+
// ini ::= line* EOF
115+
auto ini = p.rule("ini", p.zero_or_more(line) + p.end());
116+
117+
return ini;
117118
});
118119

119120
common_peg_parse_context ctx(contents);
@@ -123,57 +124,33 @@ void server_config_manager::ensure_loaded() {
123124
}
124125

125126
std::map<std::string, std::map<std::string, std::string>> parsed;
126-
std::string current_section;
127-
std::optional<std::string> pending_key;
128-
129-
const auto flush_pending = [&](const std::string & value) {
130-
if (current_section.empty() || !pending_key) {
131-
return;
132-
}
133-
134-
const auto & key = *pending_key;
135-
if (key.rfind("LLAMA_ARG_", 0) != 0) {
136-
return;
137-
}
138127

139-
parsed[current_section][key] = value;
140-
};
128+
std::string current_section;
129+
std::string current_key;
141130

142-
ctx.ast.visit(result, [&](const common_peg_ast_node & node) {
131+
ctx.ast.visit(result, [&](const auto & node) {
143132
if (node.tag == "section-name") {
144-
if (pending_key) {
145-
flush_pending("");
146-
pending_key.reset();
147-
}
148-
149-
current_section = trim(std::string(node.text));
150-
return;
151-
}
152-
153-
if (node.tag == "key") {
154-
if (pending_key) {
155-
flush_pending("");
156-
}
157-
158-
pending_key = trim(std::string(node.text));
159-
return;
160-
}
161-
162-
if (node.tag == "value") {
163-
if (!pending_key) {
133+
const std::string section = std::string(node.text);
134+
if (section.rfind("LLAMA_ARG_", 0) == 0) {
135+
current_section.clear();
164136
return;
165137
}
166138

167-
flush_pending(trim(std::string(node.text)));
168-
pending_key.reset();
169-
return;
139+
current_section = section;
140+
parsed[current_section] = {};
141+
} else if (node.tag == "key") {
142+
const std::string key = std::string(node.text);
143+
if (key.rfind("LLAMA_ARG_", 0) == 0) {
144+
current_key = key;
145+
} else {
146+
current_key.clear();
147+
}
148+
} else if (node.tag == "value" && !current_key.empty() && !current_section.empty()) {
149+
parsed[current_section][current_key] = std::string(node.text);
150+
current_key.clear();
170151
}
171152
});
172153

173-
if (pending_key) {
174-
flush_pending("");
175-
}
176-
177154
data = std::move(parsed);
178155
}
179156

0 commit comments

Comments
 (0)