14enum class Tok { Ident, Str, Num, Punct, Eof };
22std::string floatToString(
double v) {
24 std::snprintf(buf,
sizeof(buf),
"%g",
v);
28std::string scalarToString(
const DataValue &
v) {
33 return std::to_string(
v.i);
35 return floatToString(
v.f);
37 return v.b ?
"true" :
"false";
43DataValue numValue(
const std::string &raw) {
44 if (raw.find_first_of(
".eE") != std::string::npos)
49DataValue negNumValue(
const std::string &raw) {
50 DataValue
v = numValue(raw);
58 Parser(std::string source, std::string path) : source_(std::move(source)), path_(std::move(path)) {}
60 bool parse(DataValue &out, std::string &
error) {
65 }
catch (
const ParseError &e) {
72 struct ParseError : std::runtime_error {
73 explicit ParseError(
const std::string &msg) : std::runtime_error(msg) {}
78 std::vector<Token> toks_;
81 const Token &cur()
const {
return toks_[pos_]; }
83 Token t = toks_[pos_];
84 if (pos_ + 1 < toks_.size()) ++pos_;
87 bool isPunct(
const std::string &
p)
const {
88 return cur().kind == Tok::Punct && cur().value ==
p;
90 bool isIdent()
const {
return cur().kind == Tok::Ident; }
92 [[noreturn]]
void fail(
const std::string &msg)
const {
93 const std::string at = cur().kind == Tok::Eof ?
"<结束>" : cur().value;
94 throw ParseError(path_ +
":" + std::to_string(cur().
line) +
": " + msg +
95 "(实际是 '" + at +
"')");
97 void expectPunct(
const std::string &
p) {
98 if (!isPunct(
p)) fail(
"期望 '" +
p +
"'");
101 std::string expectIdent(
const std::string &what) {
102 if (!isIdent()) fail(
"期望标识符 " + what);
108 const std::string &src = source_;
109 const size_t n = src.size();
112 const auto add = [&](Tok k, std::string
v) {
113 toks_.push_back(Token{k, std::move(
v),
line});
117 const char c = src[i];
123 if (
c ==
' ' ||
c ==
'\t' ||
c ==
'\r') {
127 if (
c ==
'/' && i + 1 <
n && src[i + 1] ==
'/') {
128 while (i <
n && src[i] !=
'\n') ++i;
131 if (
c ==
'/' && i + 1 <
n && src[i + 1] ==
'*') {
132 const int startLine =
line;
134 while (i <
n && !(src[i] ==
'*' && i + 1 <
n && src[i + 1] ==
'/')) {
135 if (src[i] ==
'\n') ++
line;
139 throw ParseError(path_ +
":" + std::to_string(startLine) +
": 未闭合的块注释");
143 if (
c ==
'"' ||
c ==
'\'') {
144 const char quote =
c;
145 const int startLine =
line;
148 while (i <
n && src[i] != quote) {
149 if (src[i] ==
'\\' && i + 1 <
n) {
151 const char e = src[i];
153 case 'n':
s +=
'\n';
break;
154 case 't':
s +=
'\t';
break;
155 case 'r':
s +=
'\r';
break;
156 case '"':
s +=
'"';
break;
157 case '\'':
s +=
'\'';
break;
158 case '\\':
s +=
'\\';
break;
159 case '{':
s +=
'{';
break;
160 case '}':
s +=
'}';
break;
161 default:
s += e;
break;
169 throw ParseError(path_ +
":" + std::to_string(startLine) +
": 未闭合的字符串");
171 add(Tok::Str, std::move(
s));
174 if (std::isdigit(
static_cast<unsigned char>(
c)) ||
175 (
c ==
'.' && i + 1 <
n && std::isdigit(
static_cast<unsigned char>(src[i + 1])))) {
176 const size_t start = i;
178 const char d = src[i];
179 if (
d >=
'0' &&
d <=
'9') {
181 }
else if (
d ==
'.') {
183 }
else if ((
d ==
'e' ||
d ==
'E') && i + 1 <
n &&
184 ((src[i + 1] >=
'0' && src[i + 1] <=
'9') ||
185 src[i + 1] ==
'+' || src[i + 1] ==
'-')) {
191 add(Tok::Num, src.substr(start, i - start));
194 if (std::isalpha(
static_cast<unsigned char>(
c)) ||
c ==
'_') {
195 const size_t start = i;
197 const char d = src[i];
198 if (std::isalnum(
static_cast<unsigned char>(
d)) ||
d ==
'_' ||
d ==
'.') ++i;
201 add(Tok::Ident, src.substr(start, i - start));
204 const std::string two = (i + 1 <
n) ? src.substr(i, 2) :
"";
205 if (two ==
"==" || two ==
"!=" || two ==
">=" || two ==
"<=" ||
206 two ==
"&&" || two ==
"||") {
207 add(Tok::Punct, two);
211 if (
c ==
'{' ||
c ==
'}' ||
c ==
'(' ||
c ==
')' ||
c ==
'[' ||
c ==
']' ||
212 c ==
':' ||
c ==
',' ||
c ==
'=' ||
c ==
'>' ||
c ==
'<' ||
c ==
'!' ||
214 add(Tok::Punct, std::string(1,
c));
218 throw ParseError(path_ +
":" + std::to_string(
line) +
": 无法识别的字符 '" +
219 std::string(1,
c) +
"'");
221 toks_.push_back(Token{Tok::Eof,
"",
line});
224 DataValue parseLiteralValue() {
225 const Token &t = cur();
226 if (t.kind == Tok::Str) {
230 if (t.kind == Tok::Num) {
232 return numValue(t.value);
234 if (t.kind == Tok::Ident) {
235 if (t.value ==
"true") {
239 if (t.value ==
"false") {
243 fail(
"条件字面量只支持字符串/数字/true/false");
247 const Token &tt = cur();
248 if (tt.kind == Tok::Num) {
250 return negNumValue(tt.value);
257 DataValue parseComparison() {
258 if (!isIdent()) fail(
"条件左侧应为变量名");
259 const std::string varName = adv().value;
260 const Token &op = cur();
261 if (op.kind != Tok::Punct ||
262 (op.value !=
"==" && op.value !=
"!=" && op.value !=
">" && op.value !=
"<" &&
263 op.value !=
">=" && op.value !=
"<="))
264 fail(
"期望比较运算符(== != > < >= <=)");
266 DataValue
value = parseLiteralValue();
268 if (op.value ==
"==") mapped =
"eq";
269 else if (op.value ==
"!=") mapped =
"ne";
270 else if (op.value ==
">") mapped =
"gt";
271 else if (op.value ==
"<") mapped =
"lt";
272 else if (op.value ==
">=") mapped =
"ge";
277 {
"value", std::move(
value)},
281 DataValue parseNot() {
288 DataValue e = parseOr();
292 return parseComparison();
295 DataValue parseAnd() {
296 DataValue left = parseNot();
297 while (isPunct(
"&&")) {
299 DataValue right = parseNot();
300 bool appended =
false;
302 for (
auto &kv : left.obj) {
304 kv.second.arr.push_back(std::move(right));
318 DataValue parseOr() {
319 DataValue left = parseAnd();
320 while (isPunct(
"||")) {
322 DataValue right = parseAnd();
323 bool appended =
false;
325 for (
auto &kv : left.obj) {
327 kv.second.arr.push_back(std::move(right));
341 void parseAttrs(
const int lineNum, std::vector<std::pair<std::string, DataValue>> &out) {
342 while (cur().
line == lineNum && !isPunct(
"}") && cur().
kind != Tok::Eof) {
343 if (!isIdent()) fail(
"期望属性名");
344 const std::string
name = adv().value;
345 if (
name ==
"meta" && isPunct(
"(")) {
347 std::vector<std::pair<std::string, DataValue>> metaFields;
348 while (!isPunct(
")")) {
349 if (!isIdent()) fail(
"meta 键应为标识符");
350 const std::string k = adv().value;
352 DataValue
v = parseLiteralValue();
357 if (isPunct(
",")) adv();
358 else if (!isPunct(
")")) fail(
"meta 内期望 ',' 或 ')'");
364 if (
name ==
"tags") {
366 if (!isPunct(
"[")) fail(
"tags 后应为 [");
368 std::vector<DataValue> arr;
369 while (!isPunct(
"]")) {
370 if (cur().
kind != Tok::Str) fail(
"tags 元素应为字符串");
372 if (isPunct(
",")) adv();
373 else if (!isPunct(
"]")) fail(
"tags 内期望 ',' 或 ']'");
380 DataValue
v = parseLiteralValue();
381 if (
name ==
"weight" ||
name ==
"i18n" ||
name ==
"id") {
382 out.emplace_back(
name, std::move(
v));
384 fail(
"未知属性 '" +
name +
"'");
389 DataValue parseLine(
const std::string &poolId,
int idx,
const DataValue *inheritWhen) {
390 const int lineNum = cur().line;
394 }
else if (isIdent()) {
395 if (cur().
value ==
"when") fail(
"when 分组不允许嵌套");
396 speaker = adv().value;
401 if (cur().
kind != Tok::Str) fail(
"期望台词字符串");
404 std::vector<std::pair<std::string, DataValue>> fields;
407 if (inheritWhen) fields.emplace_back(
"when", *inheritWhen);
408 std::vector<std::pair<std::string, DataValue>> attrs;
409 parseAttrs(lineNum, attrs);
411 for (
auto &kv : attrs) {
412 if (kv.first ==
"id") hasId =
true;
413 fields.push_back(std::move(kv));
419 void parsePool(std::vector<std::pair<std::string, DataValue>> &pools) {
421 const std::string poolId = expectIdent(
"pool 名称");
422 long long noRepeat = -1;
423 while (isIdent() && cur().
value ==
"noRepeat") {
426 const Token &t = cur();
427 if (t.kind != Tok::Num) fail(
"noRepeat 应为数字");
428 noRepeat = std::strtoll(adv().
value.c_str(),
nullptr, 10);
432 std::vector<DataValue> lines;
434 while (!isPunct(
"}")) {
435 if (cur().
kind == Tok::Eof) fail(
"未闭合的 pool 块");
436 if (isIdent() && cur().
value ==
"pool") fail(
"pool 块不允许嵌套");
437 if (isIdent() && cur().
value ==
"when") {
439 DataValue cond = parseOr();
441 while (!isPunct(
"}")) {
442 if (cur().
kind == Tok::Eof) fail(
"未闭合的 when 块");
443 lines.push_back(parseLine(poolId,
idx, &cond));
449 lines.push_back(parseLine(poolId,
idx,
nullptr));
454 std::vector<std::pair<std::string, DataValue>> poolFields;
460 DataValue parsePools() {
461 std::vector<std::pair<std::string, DataValue>> pools;
462 while (cur().
kind != Tok::Eof) parsePool(pools);
470 std::string &
error) {
471 Parser parser(source, path);
472 return parser.parse(outRoot,
error);
bool parseDnut(const std::string &source, const std::string &path, DataValue &outRoot, std::string &error)
WidgetDesc text(std::string content, std::string id)
Static text label.
Generic JSON-like value tree used by the Squirrel bridge: dialogue pools and conditions arrive as Squ...
static DataValue string(std::string v)
static DataValue integer(long long v)
static DataValue boolean(bool v)
static DataValue array(std::vector< DataValue > v)
static DataValue object(std::vector< std::pair< std::string, DataValue > > v)
static DataValue number(double v)