PageRenderTime 57ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/cwxeditor_src/cwx/script.d

https://bitbucket.org/k4nagatsuki/cwxeditor
D | 3617 lines | 3511 code | 32 blank | 74 comment | 700 complexity | 444ab6f057a2e301dd6f3fa17ba6be2f MD5 | raw file
Possible License(s): LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. module cwx.script;
  2. import cwx.props;
  3. import cwx.types;
  4. import cwx.summary;
  5. import cwx.utils;
  6. import cwx.event;
  7. import cwx.motion;
  8. import cwx.background;
  9. import cwx.area;
  10. import cwx.card;
  11. import cwx.coupon;
  12. import cwx.structs;
  13. import std.algorithm;
  14. import std.conv;
  15. import std.array;
  16. import std.ascii;
  17. import std.stdio;
  18. import std.string;
  19. import std.regex;
  20. import std.exception;
  21. import std.traits;
  22. import std.range : ElementType;
  23. /// ???????????????????
  24. struct CWXSError {
  25. /// ?????????
  26. string message;
  27. /// ???????
  28. int errLine;
  29. /// ??????
  30. int errPos;
  31. /// ?????????? __FILE__
  32. string file;
  33. /// ?????????? __LINE__
  34. size_t line;
  35. }
  36. /// ditto
  37. class CWXScriptException : Exception {
  38. this (string file, size_t line, string text, const CWXSError[] errors, bool over100) { mixin(S_TRACE);
  39. super ("cwx script error", file, line);
  40. _text = text;
  41. _errors = errors;
  42. _over100 = over100;
  43. }
  44. private string _text;
  45. private const(CWXSError[]) _errors;
  46. private bool _over100;
  47. /// ??????????
  48. @property
  49. const
  50. string text() {return _text;}
  51. /// ???????????
  52. @property
  53. const
  54. const(CWXSError[]) errors() {return _errors;}
  55. /// ????100???????
  56. @property
  57. const
  58. bool over100() {return _over100;}
  59. }
  60. /// ?????????
  61. struct VarSet {
  62. string var; /// ????
  63. string value; /// ????
  64. }
  65. /// ???????????
  66. struct CompileOption {
  67. bool linkId = false; /// ?????????????
  68. int startLine = 0; /// ?????????????(??0)?
  69. int startPos = 0; /// ??????????????(??0)?
  70. int addLines = 0; /// ????????????(??0)?
  71. }
  72. /// ??????????????????????
  73. /// ??????????????CWXScriptException?????
  74. Content[] compile(const(CProps) prop, const(Summary) summ, string script, in CompileOption opt) { mixin(S_TRACE);
  75. auto compiler = new CWXScript(prop, summ, script);
  76. auto tokens = compiler.tokenize(script, opt);
  77. if (compiler.errors.length) throw new CWXScriptException(__FILE__, __LINE__, script, compiler.errors, false);
  78. auto nodes = compiler.analyzeSyntax(tokens);
  79. if (compiler.errors.length) throw new CWXScriptException(__FILE__, __LINE__, script, compiler.errors, false);
  80. auto r = compiler.analyzeSemantics(nodes, opt);
  81. if (compiler.errors.length) throw new CWXScriptException(__FILE__, __LINE__, script, compiler.errors, false);
  82. return r;
  83. }
  84. /// ??????????????????????????????
  85. /// ?????????????-1????
  86. CType firstContentType(const(CProps) prop, const(Summary) summ, string script) { mixin(S_TRACE);
  87. CompileOption opt;
  88. auto compiler = new CWXScript(prop, summ, script);
  89. auto tokens = compiler.tokenize(script, opt);
  90. if (compiler.errors.length) return cast(CType)-1;
  91. foreach (ref token; tokens) { mixin(S_TRACE);
  92. if (token.kind is CWXScript.Kind.START) return CType.START;
  93. if (token.kind is CWXScript.Kind.SYMBOL) { mixin(S_TRACE);
  94. auto p = token.value.toLower() in CWXScript.KEYS.keywords;
  95. if (p) { mixin(S_TRACE);
  96. return *p;
  97. }
  98. }
  99. }
  100. return cast(CType)-1;
  101. }
  102. /// ?????????????????????
  103. class CWXScript {
  104. private const(CProps) _prop;
  105. private const(Summary) _summ;
  106. private string _text;
  107. private size_t _maxError;
  108. private size_t _autoWrap;
  109. /// ???????????
  110. this (const(CProps) prop, const(Summary) summ, string text = "", size_t maxError = 100, size_t autoWrap = 250) { mixin(S_TRACE);
  111. _prop = prop;
  112. _summ = summ;
  113. _text = text;
  114. _maxError = maxError;
  115. _autoWrap = autoWrap;
  116. }
  117. private CWXSError[] _errors;
  118. /// ??????????????????????
  119. @property
  120. const
  121. const(CWXSError[]) errors() {return _errors;}
  122. /// s????????????????????
  123. static string createString(string s) { mixin(S_TRACE);
  124. return "\"" ~ std.array.replace(s, "\"", "\"\"") ~ "\"";
  125. }
  126. private void throwError(string File = __FILE__, size_t Line = __LINE__)
  127. (lazy string message, in Token tok) { mixin(S_TRACE);
  128. throwErrorToken!(File, Line)(message, tok.line, tok.pos, tok.value);
  129. }
  130. private void throwErrorToken(string File = __FILE__, size_t Line = __LINE__)
  131. (lazy string message, int line, int pos, string value) { mixin(S_TRACE);
  132. if (!_maxError) return;
  133. if (_errors.length && _errors[$ - 1].errLine == line && _errors[$ - 1].errPos == pos) { mixin(S_TRACE);
  134. // ?????????????????
  135. return;
  136. }
  137. string msg;
  138. if (!_prop || !_prop.msgs) { mixin(S_TRACE);
  139. msg = "";
  140. } else { mixin(S_TRACE);
  141. msg = message;
  142. }
  143. if (_maxError <= _errors.length) { mixin(S_TRACE);
  144. throw new CWXScriptException(__FILE__, __LINE__, _text, errors, true);
  145. }
  146. _errors ~= CWXSError(msg, line, pos, File, Line);
  147. }
  148. /// Token????
  149. static enum Kind {
  150. START, /// start
  151. IF, /// if
  152. FI, /// fi
  153. ELIF, /// elif
  154. SIF, /// sif
  155. O_BRA, /// [
  156. C_BRA, /// ]
  157. SYMBOL, /// ????????????????
  158. NUMBER, /// ???
  159. VAR_NAME, /// ????
  160. EQ, /// =
  161. COMMA, /// comma
  162. STRING, /// ????
  163. PLU, /// +
  164. MIN, /// -
  165. MUL, /// *
  166. DIV, /// /
  167. RES, /// %
  168. CAT, /// ~
  169. O_PAR, /// (
  170. C_PAR, /// )
  171. COMMENT /// ????
  172. }
  173. /// ???????????
  174. static struct Token {
  175. int line; /// ?????????
  176. int pos; /// ??????
  177. size_t index; /// ????????????
  178. Kind kind; /// ???
  179. string value; /// ??
  180. string comment = ""; /// ????????
  181. /// ??????
  182. const
  183. string toString() { mixin(S_TRACE);
  184. return .format("Token {line %d : %d, %d, %s, %s, %s}", line, pos, index, to!(string)(kind), value, comment);
  185. }
  186. /// o??????
  187. const
  188. bool opEquals(ref const(Token) o) { mixin(S_TRACE);
  189. return line == o.line && pos == o.pos && index == o.index && kind == o.kind && value == o.value && comment == o.comment;
  190. }
  191. }
  192. private static string[] wrap(string line, size_t width) { mixin(S_TRACE);
  193. if (width > 0) { mixin(S_TRACE);
  194. string[] lines;
  195. while (lengthJ(line) > width) { mixin(S_TRACE);
  196. auto l = sliceJ(line, 0, width);
  197. if (!l.length) { mixin(S_TRACE);
  198. l = sliceJ(line, 0, width + 1);
  199. }
  200. lines ~= l;
  201. line = line[l.length .. $];
  202. }
  203. lines ~= line;
  204. return lines;
  205. }
  206. return [line];
  207. }
  208. const
  209. private size_t stringCenter(string[] linesBase, size_t width) { mixin(S_TRACE);
  210. string[] lines;
  211. if (width > 0) { mixin(S_TRACE);
  212. foreach (line; linesBase) { mixin(S_TRACE);
  213. lines ~= wrap(line, width);
  214. }
  215. } else { mixin(S_TRACE);
  216. lines = linesBase;
  217. }
  218. int ln;
  219. int lc = cast(int) lineCount(lines);
  220. if (lc > 0 && lc < _prop.looks.messageLine) { mixin(S_TRACE);
  221. int lnt = cast(int) _prop.looks.messageLine - (lc - 1);
  222. ln = lnt / 2 + 1;
  223. } else { mixin(S_TRACE);
  224. ln = 0;
  225. }
  226. return ln > 0 ? ln : 0;
  227. }
  228. /// Token????????????????
  229. /// ????????????
  230. /// ????????????????????????
  231. private string stringValue(in Token tok, size_t width) { mixin(S_TRACE);
  232. string decode(in char[] s, char esc) { mixin(S_TRACE);
  233. char[] buf = new char[s.length];
  234. size_t len = 0;
  235. bool escape = false;
  236. foreach (char c; s) { mixin(S_TRACE);
  237. if (!escape && c == esc) { mixin(S_TRACE);
  238. escape = true;
  239. } else { mixin(S_TRACE);
  240. buf[len] = c;
  241. len++;
  242. escape = false;
  243. }
  244. }
  245. if (escape) { mixin(S_TRACE);
  246. buf[len] = esc;
  247. len++;
  248. }
  249. buf = buf[0 .. len];
  250. return assumeUnique(buf);
  251. }
  252. if (tok.kind !is Kind.STRING || tok.value.length < 2) { mixin(S_TRACE);
  253. throwError(_prop.msgs.scriptErrorInvalidString, tok);
  254. }
  255. if (tok.value[0] == '@') { mixin(S_TRACE);
  256. char[] buf;
  257. auto linesBase = .splitLines!string(tok.value[0 .. $ - 1].idup);
  258. string firstLine = linesBase[0];
  259. string[] lines;
  260. string[] resultLines;
  261. foreach (i, line; linesBase[1 .. $]) { mixin(S_TRACE);
  262. line = .astripl(line);
  263. line = decode(line, tok.value[0]);
  264. if (line.length >= 1 && line[0] == '\\') { mixin(S_TRACE);
  265. line = line[1 .. $];
  266. }
  267. resultLines ~= line;
  268. lines ~= wrap(line, width);
  269. }
  270. if (firstLine.length > 1) { mixin(S_TRACE);
  271. auto lnStr = std.string.toLower(.astrip(firstLine[1 .. $]));
  272. bool isNum = std.string.isNumeric(lnStr);
  273. if (!isNum && icmp(lnStr, "c") != 0 && icmp(lnStr, "center") != 0) { mixin(S_TRACE);
  274. throwError(_prop.msgs.scriptErrorInvalidStr, tok);
  275. }
  276. int ln;
  277. if (isNum) { mixin(S_TRACE);
  278. ln = .to!(int)(lnStr);
  279. } else { mixin(S_TRACE);
  280. ln = stringCenter(lines, 0);
  281. }
  282. if (ln > 0) { mixin(S_TRACE);
  283. buf.length = ln - 1;
  284. }
  285. buf[] = '\n';
  286. }
  287. foreach (i, line; resultLines) { mixin(S_TRACE);
  288. if (i > 0) buf ~= '\n';
  289. buf ~= line;
  290. }
  291. return assumeUnique(buf);
  292. }
  293. return decode(tok.value[1 .. $ - 1], tok.value[0]);
  294. } unittest { mixin(S_TRACE);
  295. debug mixin(UTPerf);
  296. auto s = new CWXScript(new CProps("", null), null);
  297. assert (s.stringValue(Token(0, 0, 0, Kind.STRING, `"abc"`), 0) == "abc");
  298. assert (s.stringValue(Token(0, 0, 0, Kind.STRING, `"a""bc"`), 0) == "a\"bc");
  299. assert (s.stringValue(Token(0, 0, 0, Kind.STRING, `'ab''c'`), 0) == "ab'c");
  300. assert (s.stringValue(Token(0, 2, 0, Kind.STRING, "@ 3\n\t\tte@@st\n t\\e\\st\n\\ a\n\\\\ a@"), false)
  301. == "\n\nte@st\nt\\e\\st\n a\n\\ a");
  302. assert (s.stringValue(Token(0, 0, 0, Kind.STRING, "@\nabcabc\n@"), 3) == "abcabc", s.stringValue(Token(0, 0, 0, Kind.STRING, "@\nabcabc\n@"), 3));
  303. }
  304. /// text???????????????
  305. /// ?????????????text???????
  306. string[] eatEmptyVars(ref string text, ref CompileOption opt) { mixin(S_TRACE);
  307. auto tokens = tokenizeImpl(text, true, opt);
  308. // ?????????
  309. Token[] tokens2;
  310. foreach (tok; tokens) { mixin(S_TRACE);
  311. if (tok.kind !is Kind.COMMENT) { mixin(S_TRACE);
  312. tokens2 ~= tok;
  313. }
  314. }
  315. string[] r;
  316. size_t index = 0;
  317. size_t len = 0;
  318. opt.startLine = 0;
  319. opt.startPos = 0;
  320. // ??????????????'='?????????????
  321. foreach (i, tok; tokens2) { mixin(S_TRACE);
  322. if (tok.kind is Kind.EQ) { mixin(S_TRACE);
  323. throwError(_prop.msgs.scriptErrorInvalidSyntax, tok);
  324. break;
  325. } else if (tok.kind is Kind.VAR_NAME) { mixin(S_TRACE);
  326. if (i + 1 < tokens2.length && tokens2[i + 1].kind is Kind.EQ) { mixin(S_TRACE);
  327. break;
  328. }
  329. index = tok.index;
  330. len = tok.value.length;
  331. opt.startLine = tok.line;
  332. opt.startPos = tok.pos + len;
  333. r ~= tok.value;
  334. }
  335. }
  336. text = text[index + len .. $];
  337. return r;
  338. } unittest { mixin(S_TRACE);
  339. debug mixin(UTPerf);
  340. CompileOption opt;
  341. auto s = new CWXScript(new CProps("", null), null);
  342. auto str = "/*c1*/ $test1 $test2 //$test3\n$test4// aaa\n$test5 = a $test6 endsc true";
  343. auto vars = s.eatEmptyVars(str, opt);
  344. assert (vars == ["$test1", "$test2", "$test4"], .text(vars));
  345. assert (str == "// aaa\n$test5 = a $test6 endsc true", str);
  346. assert (opt.startLine == 1);
  347. assert (opt.startPos == 6);
  348. }
  349. /// text??? = ?????????????
  350. static string pushVars(string text, in VarSet[] varTable, ref CompileOption opt) { mixin(S_TRACE);
  351. string[] lines;
  352. opt.addLines = 0;
  353. foreach (t; varTable) { mixin(S_TRACE);
  354. string v = t.value;
  355. if (!v.length) v = `""`;
  356. auto l = t.var ~ " = " ~ v;
  357. lines ~= l;
  358. opt.addLines += v.splitLines().length;
  359. }
  360. lines ~= text;
  361. opt.startLine -= opt.addLines;
  362. return lines.join("\n");
  363. } unittest { mixin(S_TRACE);
  364. CompileOption opt;
  365. VarSet[] varSet = [VarSet("$a", "1"), VarSet("$b", "2")];
  366. auto r = pushVars("aaa bbb", varSet, opt);
  367. assert (r == "$a = 1\n$b = 2\naaa bbb", r);
  368. assert (opt.startLine == -2, .text(opt.startLine));
  369. }
  370. /// text?Token??????
  371. Token[] tokenize(string text, in CompileOption opt = CompileOption.init) { mixin(S_TRACE);
  372. return tokenizeImpl(text, false, opt);
  373. } unittest { mixin(S_TRACE);
  374. debug mixin(UTPerf);
  375. auto s = new CWXScript(new CProps("", null), null);
  376. auto tokens = s.tokenize("");
  377. assert (tokens == [], to!string(tokens));
  378. tokens = s.tokenize("/*/*\n*/*/");
  379. assert (tokens == [Token(0, 0, 0, Kind.COMMENT, "/*/*\n*/*/", "")], to!string(tokens));
  380. tokens = s.tokenize("/*c*/start, 12.3 \ntest1 [$void] =\"str\ning//\"\n\r //comment\nELIF if\n1/2+3*4%(5-6)");
  381. assert (tokens
  382. == [
  383. Token(0, 0, 0, Kind.COMMENT, "/*c*/", ""),
  384. Token(0, 5, 5, Kind.START, "start", "c"),
  385. Token(0, 10, 10, Kind.COMMA, ","),
  386. Token(0, 12, 12, Kind.NUMBER, "12.3"),
  387. Token(1, 0, 18, Kind.SYMBOL, "test1"),
  388. Token(1, 6, 24, Kind.O_BRA, "["),
  389. Token(1, 7, 25, Kind.VAR_NAME, "$void"),
  390. Token(1, 12, 30, Kind.C_BRA, "]"),
  391. Token(1, 14, 32, Kind.EQ, "="),
  392. Token(1, 15, 33, Kind.STRING, "\"str\ning//\""),
  393. Token(4, 1, 47, Kind.COMMENT, "//comment\n", ""),
  394. Token(5, 0, 57, Kind.ELIF, "ELIF", "comment\n"),
  395. Token(5, 5, 62, Kind.IF, "if"),
  396. Token(6, 0, 65, Kind.NUMBER, "1"),
  397. Token(6, 1, 66, Kind.DIV, "/"),
  398. Token(6, 2, 67, Kind.NUMBER, "2"),
  399. Token(6, 3, 68, Kind.PLU, "+"),
  400. Token(6, 4, 69, Kind.NUMBER, "3"),
  401. Token(6, 5, 70, Kind.MUL, "*"),
  402. Token(6, 6, 71, Kind.NUMBER, "4"),
  403. Token(6, 7, 72, Kind.RES, "%"),
  404. Token(6, 8, 73, Kind.O_PAR, "("),
  405. Token(6, 9, 74, Kind.NUMBER, "5"),
  406. Token(6, 10, 75, Kind.MIN, "-"),
  407. Token(6, 11, 76, Kind.NUMBER, "6"),
  408. Token(6, 12, 77, Kind.C_PAR, ")")
  409. ], to!string(tokens));
  410. }
  411. private Token[] tokenizeImpl(ref string text, bool eatEmptyVarMode, in CompileOption opt) { mixin(S_TRACE);
  412. if (!text.length) return [];
  413. Token[] r;
  414. text = std.array.replace(text, "\r\n", "\n");
  415. text = std.array.replace(text, "\r", "\n");
  416. string dtext = text;
  417. auto reg = .regex("(" ~ std.string.join(TOKENS.dup, ")|(") ~ ")", "gi");
  418. size_t i = 0;
  419. size_t hits = 0;
  420. size_t pos = 0;
  421. size_t index = 0;
  422. int commentLevel = 0;
  423. size_t lastCommentLine = 0;
  424. size_t lastCommentPos = 0;
  425. size_t lastCommentIndex = 0;
  426. string post;
  427. bool spaceAfter = false;
  428. string docComment = "";
  429. string fullComment = "";
  430. @property int sLine() {return cast(int) i + opt.startLine;}
  431. @property int sPos() {return (cast(int) i == opt.addLines) ? (cast(int) pos + opt.startPos) : pos;}
  432. foreach (token; .match(dtext, reg)) { mixin(S_TRACE);
  433. post = token.post;
  434. string pre = token.pre;
  435. index = pre.length;
  436. auto dstr = token.hit;
  437. void retCount2(string dstr) { mixin(S_TRACE);
  438. size_t count = .count(dstr, "\n");
  439. if (count > 0) { mixin(S_TRACE);
  440. pos = dstr.length - std.string.lastIndexOf(dstr, '\n') - 1;
  441. i += count;
  442. } else { mixin(S_TRACE);
  443. pos += dstr.length;
  444. }
  445. }
  446. void retCount() { mixin(S_TRACE);
  447. retCount2(dstr);
  448. }
  449. auto c = dstr[0];
  450. string str = to!string(dstr);
  451. if (cast(int) pre.length - cast(int) hits > 0) { mixin(S_TRACE);
  452. if (0 < commentLevel) { mixin(S_TRACE);
  453. /// in comment
  454. retCount2(pre[hits .. $]);
  455. hits = pre.length;
  456. } else { mixin(S_TRACE);
  457. string lpre = pre;
  458. if (lpre.length && (lpre[$ - 1] == '@' || lpre[$ - 1] == '"' || lpre[$ - 1] == '\'')) { mixin(S_TRACE);
  459. throwErrorToken(_prop.msgs.scriptErrorUnCloseString, sLine, sPos, "");
  460. return r;
  461. } else { mixin(S_TRACE);
  462. throwErrorToken(_prop.msgs.scriptErrorInvalidToken, sLine, sPos, "");
  463. }
  464. }
  465. }
  466. if (0 < commentLevel) { mixin(S_TRACE);
  467. fullComment ~= .text(pre[hits .. $]) ~ str;
  468. }
  469. bool commentStart = false;
  470. if (str == "/*") { mixin(S_TRACE);
  471. // multi line comment (open)
  472. spaceAfter = true;
  473. if (commentLevel == 0) { mixin(S_TRACE);
  474. commentStart = true;
  475. lastCommentLine = i;
  476. lastCommentPos = pos;
  477. lastCommentIndex = index;
  478. }
  479. pos += dstr.length;
  480. if (0 == commentLevel) fullComment = str;
  481. commentLevel++;
  482. } else if (str == "*/") { mixin(S_TRACE);
  483. // multi line comment (close)
  484. spaceAfter = true;
  485. pos += dstr.length;
  486. if (commentLevel <= 0) { mixin(S_TRACE);
  487. throwErrorToken(_prop.msgs.scriptErrorUnOpenComment, sLine, sPos, str);
  488. }
  489. commentLevel--;
  490. if (0 == commentLevel) { mixin(S_TRACE);
  491. r ~= Token(lastCommentLine, lastCommentPos, lastCommentIndex, Kind.COMMENT, fullComment, "");
  492. }
  493. } else if (0 < commentLevel) { mixin(S_TRACE);
  494. spaceAfter = true;
  495. retCount();
  496. } else if (std.ascii.isAlpha(c) || c == '_') { mixin(S_TRACE);
  497. if (eatEmptyVarMode) return r;
  498. spaceAfter = false;
  499. // symbol
  500. switch (std.string.toLower(dstr)) {
  501. case "start":
  502. r ~= Token(sLine, sPos, index, Kind.START, str, docComment);
  503. break;
  504. case "if":
  505. r ~= Token(sLine, sPos, index, Kind.IF, str, docComment);
  506. break;
  507. case "elif":
  508. r ~= Token(sLine, sPos, index, Kind.ELIF, str, docComment);
  509. break;
  510. case "fi":
  511. r ~= Token(sLine, sPos, index, Kind.FI, str, docComment);
  512. break;
  513. case "sif":
  514. r ~= Token(sLine, sPos, index, Kind.SIF, str, docComment);
  515. break;
  516. default:
  517. r ~= Token(sLine, sPos, index, Kind.SYMBOL, str, docComment);
  518. break;
  519. }
  520. pos += dstr.length;
  521. docComment = "";
  522. } else if (c == '$') { mixin(S_TRACE);
  523. spaceAfter = false;
  524. r ~= Token(sLine, sPos, index, Kind.VAR_NAME, str, docComment);
  525. pos += dstr.length;
  526. docComment = "";
  527. } else if (c == '=') { mixin(S_TRACE);
  528. spaceAfter = false;
  529. r ~= Token(sLine, sPos, index, Kind.EQ, str, docComment);
  530. pos += dstr.length;
  531. docComment = "";
  532. } else if (c == '[') { mixin(S_TRACE);
  533. if (eatEmptyVarMode) return r;
  534. // open bracket
  535. spaceAfter = false;
  536. r ~= Token(sLine, sPos, index, Kind.O_BRA, str, docComment);
  537. pos += dstr.length;
  538. docComment = "";
  539. } else if (c == ']') { mixin(S_TRACE);
  540. if (eatEmptyVarMode) return r;
  541. // close bracket
  542. spaceAfter = false;
  543. r ~= Token(sLine, sPos, index, Kind.C_BRA, str, docComment);
  544. pos += dstr.length;
  545. docComment = "";
  546. } else if (isDigit(c)) { mixin(S_TRACE);
  547. if (eatEmptyVarMode) return r;
  548. // number
  549. spaceAfter = false;
  550. r ~= Token(sLine, sPos, index, Kind.NUMBER, str, docComment);
  551. pos += dstr.length;
  552. docComment = "";
  553. } else if (c == '@' || c == '"' || c == '\'') { mixin(S_TRACE);
  554. if (eatEmptyVarMode) return r;
  555. // string
  556. if (!spaceAfter && r.length && r[$ - 1].kind is Kind.STRING
  557. && r[$ - 1].value[$ - 1] == c) { mixin(S_TRACE);
  558. // ???string???
  559. r[$ - 1].value ~= str;
  560. } else { mixin(S_TRACE);
  561. r ~= Token(sLine, sPos, index, Kind.STRING, str, docComment);
  562. }
  563. spaceAfter = false;
  564. retCount();
  565. docComment = "";
  566. } else if (std.ascii.isWhite(c)) { mixin(S_TRACE);
  567. // whitespace
  568. spaceAfter = true;
  569. retCount();
  570. } else if (c == '+') { mixin(S_TRACE);
  571. if (eatEmptyVarMode) return r;
  572. // plus
  573. spaceAfter = false;
  574. r ~= Token(sLine, sPos, index, Kind.PLU, str, docComment);
  575. pos += dstr.length;
  576. docComment = "";
  577. } else if (c == '-') { mixin(S_TRACE);
  578. if (eatEmptyVarMode) return r;
  579. // minus
  580. spaceAfter = false;
  581. r ~= Token(sLine, sPos, index, Kind.MIN, str, docComment);
  582. pos += dstr.length;
  583. docComment = "";
  584. } else if (c == '*') { mixin(S_TRACE);
  585. if (eatEmptyVarMode) return r;
  586. // multiply
  587. spaceAfter = false;
  588. r ~= Token(sLine, sPos, index, Kind.MUL, str, docComment);
  589. pos += dstr.length;
  590. docComment = "";
  591. } else if (c == '/') { mixin(S_TRACE);
  592. if (dstr.length >= 2 && str[1] == '/') { mixin(S_TRACE);
  593. // line comment
  594. spaceAfter = true;
  595. r ~= Token(sLine, sPos, index, Kind.COMMENT, str, "");
  596. i++;
  597. pos = 0;
  598. if (2 < str.length) docComment ~= str[2 .. $];
  599. } else { mixin(S_TRACE);
  600. if (eatEmptyVarMode) return r;
  601. // divide
  602. spaceAfter = false;
  603. r ~= Token(sLine, sPos, index, Kind.DIV, str, docComment);
  604. pos += dstr.length;
  605. docComment = "";
  606. }
  607. } else if (c == '%') { mixin(S_TRACE);
  608. if (eatEmptyVarMode) return r;
  609. // residue
  610. spaceAfter = false;
  611. r ~= Token(sLine, sPos, index, Kind.RES, str, docComment);
  612. pos += dstr.length;
  613. docComment = "";
  614. } else if (c == '~') { mixin(S_TRACE);
  615. if (eatEmptyVarMode) return r;
  616. // cat
  617. spaceAfter = false;
  618. r ~= Token(sLine, sPos, index, Kind.CAT, str, docComment);
  619. pos += dstr.length;
  620. docComment = "";
  621. } else if (c == '(') { mixin(S_TRACE);
  622. if (eatEmptyVarMode) return r;
  623. // open paren
  624. spaceAfter = false;
  625. r ~= Token(sLine, sPos, index, Kind.O_PAR, str, docComment);
  626. pos += dstr.length;
  627. docComment = "";
  628. } else if (c == ')') { mixin(S_TRACE);
  629. if (eatEmptyVarMode) return r;
  630. // close paren
  631. spaceAfter = false;
  632. r ~= Token(sLine, sPos, index, Kind.C_PAR, str, docComment);
  633. pos += dstr.length;
  634. docComment = "";
  635. } else if (c == ',') { mixin(S_TRACE);
  636. if (eatEmptyVarMode) return r;
  637. // comma
  638. spaceAfter = false;
  639. r ~= Token(sLine, sPos, index, Kind.COMMA, str, docComment);
  640. pos += dstr.length;
  641. docComment = "";
  642. } else { mixin(S_TRACE);
  643. assert (0);
  644. }
  645. if (!commentStart && 0 < commentLevel) { mixin(S_TRACE);
  646. docComment ~= str;
  647. }
  648. hits += dstr.length;
  649. }
  650. if (0 < commentLevel) { mixin(S_TRACE);
  651. throwErrorToken(_prop.msgs.scriptErrorUnCloseComment, lastCommentLine, lastCommentPos, "");
  652. }
  653. if (post.length) throwErrorToken(_prop.msgs.scriptErrorInvalidToken, sLine, sPos, "");
  654. return r;
  655. }
  656. private enum CRKind {STR, INT, REAL}
  657. private class CalcResult {
  658. CRKind kind = CRKind.INT;
  659. union {
  660. string str;
  661. long numInt;
  662. real numReal;
  663. }
  664. this () { mixin(S_TRACE);
  665. numInt = 0;
  666. }
  667. void cat(in CProps prop, in Token tok, in CalcResult rval) { mixin(S_TRACE);
  668. switch (kind) {
  669. case CRKind.STR: break;
  670. case CRKind.INT: str = to!(string)(numInt); break;
  671. case CRKind.REAL: str = to!(string)(numReal); break;
  672. default: assert (0);
  673. }
  674. switch (rval.kind) {
  675. case CRKind.STR:
  676. str ~= rval.str;
  677. break;
  678. case CRKind.INT:
  679. str ~= to!(string)(rval.numInt);
  680. break;
  681. case CRKind.REAL:
  682. str ~= to!(string)(rval.numReal);
  683. break;
  684. default: assert (0);
  685. }
  686. kind = CRKind.STR;
  687. }
  688. private void calc(string Calc, bool ChkDiv)(in CProps prop, in Token tok, in CalcResult rval) { mixin(S_TRACE);
  689. if (kind is CRKind.STR || rval.kind is CRKind.STR) { mixin(S_TRACE);
  690. throwError(prop.msgs.scriptErrorInvalidNumber, tok);
  691. }
  692. static if (ChkDiv) {
  693. if ((rval.kind is CRKind.REAL ? rval.numReal : rval.numInt) == 0) { mixin(S_TRACE);
  694. throwError(prop.msgs.scriptErrorZeroDivision, tok);
  695. }
  696. }
  697. if (kind is CRKind.REAL || rval.kind is CRKind.REAL) { mixin(S_TRACE);
  698. real lvalue = kind is CRKind.REAL ? numReal : numInt;
  699. real rvalue = rval.kind is CRKind.REAL ? rval.numReal : rval.numInt;
  700. mixin ("numReal = lvalue " ~ Calc ~ " rvalue;");
  701. kind = CRKind.REAL;
  702. } else if (kind is CRKind.INT && rval.kind is CRKind.INT) { mixin(S_TRACE);
  703. mixin ("numInt " ~ Calc ~ "= rval.numInt;");
  704. } else { mixin(S_TRACE);
  705. throwError(prop.msgs.scriptErrorInvalidNumber, tok);
  706. }
  707. }
  708. public alias calc!("+", false) add;
  709. public alias calc!("-", false) min;
  710. public alias calc!("*", false) mul;
  711. public alias calc!("/", true) div;
  712. public alias calc!("%", true) res;
  713. const
  714. bool opEquals(ref const(CalcResult) val) { mixin(S_TRACE);
  715. if (kind !is val.kind) return false;
  716. final switch (kind) {
  717. case CRKind.STR: return str == val.str;
  718. case CRKind.INT: return numInt == val.numInt;
  719. case CRKind.REAL: return numReal == val.numReal;
  720. }
  721. }
  722. const
  723. bool opEquals(int val) { mixin(S_TRACE);
  724. return opEquals(cast(long) val);
  725. }
  726. const
  727. bool opEquals(long val) { mixin(S_TRACE);
  728. final switch (kind) {
  729. case CRKind.STR: return false;
  730. case CRKind.INT: return numInt == val;
  731. case CRKind.REAL: return numReal == val;
  732. }
  733. }
  734. const
  735. bool opEquals(real val) { mixin(S_TRACE);
  736. final switch (kind) {
  737. case CRKind.STR: return false;
  738. case CRKind.INT: return numInt == val;
  739. case CRKind.REAL: return numReal == val;
  740. }
  741. }
  742. const
  743. bool opEquals(string val) { mixin(S_TRACE);
  744. return kind is CRKind.STR && str == val;
  745. }
  746. override
  747. const
  748. string toString() { mixin(S_TRACE);
  749. final switch (kind) {
  750. case CRKind.STR: return str;
  751. case CRKind.INT: return to!(string)(numInt);
  752. case CRKind.REAL: return to!(string)(numReal);
  753. }
  754. }
  755. }
  756. private const OPE_LEVEL_MAX = 2;
  757. private CalcResult calcNum(in Token[] tokens, ref size_t i, in const(Node)[][string] varTable, size_t strWidth) { mixin(S_TRACE);
  758. assert (i < tokens.length);
  759. auto tok = tokens[i];
  760. if (tok.kind is Kind.VAR_NAME) { mixin(S_TRACE);
  761. i++;
  762. try { mixin(S_TRACE);
  763. auto vt = var(tok, varTable);
  764. auto r = new CalcResult;
  765. if (vt.kind is Kind.STRING) { mixin(S_TRACE);
  766. r.kind = CRKind.STR;
  767. r.str = stringValue(vt, strWidth);
  768. } else if (std.string.indexOf(vt.value, '.') != -1) { mixin(S_TRACE);
  769. r.kind = CRKind.REAL;
  770. r.numReal = to!(real)(vt.value);
  771. } else { mixin(S_TRACE);
  772. r.kind = CRKind.INT;
  773. r.numInt = to!(long)(vt.value);
  774. }
  775. return r;
  776. } catch (Exception e) {
  777. throwError(_prop.msgs.scriptErrorReqNumber, tok);
  778. auto r = new CalcResult;
  779. r.kind = CRKind.INT;
  780. r.numInt = 0;
  781. return r;
  782. }
  783. }
  784. bool min = false;
  785. if (tok.kind is Kind.PLU) { mixin(S_TRACE);
  786. i++;
  787. if (tokens[i].kind !is Kind.NUMBER) { mixin(S_TRACE);
  788. throwError(_prop.msgs.scriptErrorInvalidNumber, tok);
  789. }
  790. } else if (tok.kind is Kind.MIN) { mixin(S_TRACE);
  791. i++;
  792. if (tokens[i].kind !is Kind.NUMBER) { mixin(S_TRACE);
  793. throwError(_prop.msgs.scriptErrorInvalidNumber, tok);
  794. }
  795. min = true;
  796. }
  797. if (tokens.length <= i || !(tokens[i].kind is Kind.NUMBER || tokens[i].kind is Kind.STRING)) { mixin(S_TRACE);
  798. throwError(_prop.msgs.scriptErrorInvalidNumber, tok);
  799. }
  800. auto r = new CalcResult;
  801. try { mixin(S_TRACE);
  802. if (tokens[i].kind is Kind.NUMBER) { mixin(S_TRACE);
  803. if (std.string.indexOf(tokens[i].value, '.') != -1) { mixin(S_TRACE);
  804. r.kind = CRKind.REAL;
  805. r.numReal = to!(real)(tokens[i].value);
  806. if (min) r.numReal = -r.numReal;
  807. } else { mixin(S_TRACE);
  808. r.kind = CRKind.INT;
  809. r.numInt = to!(long)(tokens[i].value);
  810. if (min) r.numInt = -r.numInt;
  811. }
  812. } else if (tokens[i].kind is Kind.STRING) { mixin(S_TRACE);
  813. r.kind = CRKind.STR;
  814. r.str = stringValue(tokens[i], strWidth);
  815. } else { mixin(S_TRACE);
  816. throwError(_prop.msgs.scriptErrorReqNumber, tokens[i]);
  817. }
  818. i++;
  819. return r;
  820. } catch (Exception e) {
  821. throwError(_prop.msgs.scriptErrorReqNumber, tokens[i]);
  822. }
  823. return r;
  824. }
  825. private CalcResult calcPar(in Token[] tokens, ref size_t i, in const(Node)[][string] varTable, size_t strWidth) { mixin(S_TRACE);
  826. assert (i < tokens.length);
  827. auto tok = tokens[i];
  828. switch (tok.kind) {
  829. case Kind.O_PAR:
  830. i++;
  831. auto r = calcImpl(0, tokens, i, varTable, strWidth);
  832. if (tokens[i].kind !is Kind.C_PAR) { mixin(S_TRACE);
  833. throwError(_prop.msgs.scriptErrorCloseParenNotFound, tok);
  834. }
  835. i++;
  836. return r;
  837. default:
  838. return calcNum(tokens, i, varTable, strWidth);
  839. }
  840. }
  841. private CalcResult calcImpl(size_t opeLevel, in Token[] tokens, ref size_t i, in const(Node)[][string] varTable, size_t strWidth) { mixin(S_TRACE);
  842. assert (i < tokens.length);
  843. CalcResult r;
  844. if (opeLevel >= OPE_LEVEL_MAX) { mixin(S_TRACE);
  845. r = calcPar(tokens, i, varTable, strWidth);
  846. } else { mixin(S_TRACE);
  847. r = calcImpl(opeLevel + 1, tokens, i, varTable, strWidth);
  848. }
  849. while (i < tokens.length) { mixin(S_TRACE);
  850. auto tok = tokens[i];
  851. switch (opeLevel) {
  852. case 0:
  853. switch (tok.kind) {
  854. case Kind.CAT:
  855. i++;
  856. if (tokens.length < i) { mixin(S_TRACE);
  857. throwError(_prop.msgs.scriptErrorInvalidCalc, tok);
  858. return r;
  859. }
  860. r.cat(_prop, tok, calcImpl(1, tokens, i, varTable, strWidth));
  861. break;
  862. default:
  863. return r;
  864. }
  865. break;
  866. case 1:
  867. switch (tok.kind) {
  868. case Kind.PLU:
  869. i++;
  870. if (tokens.length < i) { mixin(S_TRACE);
  871. throwError(_prop.msgs.scriptErrorInvalidCalc, tok);
  872. return r;
  873. }
  874. r.add(_prop, tok, calcImpl(1, tokens, i, varTable, strWidth));
  875. break;
  876. case Kind.MIN:
  877. i++;
  878. if (tokens.length < i) { mixin(S_TRACE);
  879. throwError(_prop.msgs.scriptErrorInvalidCalc, tok);
  880. return r;
  881. }
  882. r.min(_prop, tok, calcImpl(1, tokens, i, varTable, strWidth));
  883. break;
  884. default:
  885. return r;
  886. }
  887. break;
  888. case 2:
  889. switch (tok.kind) {
  890. case Kind.MUL:
  891. i++;
  892. if (tokens.length <= i) { mixin(S_TRACE);
  893. throwError(_prop.msgs.scriptErrorInvalidCalc, tok);
  894. return r;
  895. }
  896. r.mul(_prop, tok, calcPar(tokens, i, varTable, strWidth));
  897. break;
  898. case Kind.DIV:
  899. i++;
  900. if (tokens.length <= i) { mixin(S_TRACE);
  901. throwError(_prop.msgs.scriptErrorInvalidCalc, tok);
  902. return r;
  903. }
  904. r.div(_prop, tok, calcPar(tokens, i, varTable, strWidth));
  905. break;
  906. case Kind.RES:
  907. i++;
  908. if (tokens.length <= i) { mixin(S_TRACE);
  909. throwError(_prop.msgs.scriptErrorInvalidCalc, tok);
  910. return r;
  911. }
  912. r.res(_prop, tok, calcPar(tokens, i, varTable, strWidth));
  913. break;
  914. default:
  915. return r;
  916. }
  917. break;
  918. default: assert (0);
  919. }
  920. }
  921. return r;
  922. }
  923. /// tokens???????????????????
  924. CalcResult calc(in Token[] tokens, ref size_t i, in const(Node)[][string] varTable, size_t strWidth) { mixin(S_TRACE);
  925. assert (i < tokens.length);
  926. return calcImpl(0, tokens, i, varTable, strWidth);
  927. } unittest { mixin(S_TRACE);
  928. debug mixin(UTPerf);
  929. size_t i;
  930. Token[] tokens;
  931. const(Node)[][string] varTable;
  932. auto tok = Token(0, 0, 0, Kind.NUMBER, "15");
  933. varTable["$abc"] = [Node(NodeType.VALUE, tok)];
  934. tok = Token(0, 0, 0, Kind.NUMBER, "0");
  935. varTable["$s"] = [Node(NodeType.VALUE, tok)];
  936. auto s = new CWXScript(new CProps("", null), null);
  937. i = 0;
  938. assert (s.calc(s.tokenize("10 * $abc"), i, varTable, 0) == 150);
  939. i = 0;
  940. assert (s.calc(s.tokenize("(-42)"), i, varTable, 0) == -42);
  941. i = 0;
  942. assert (s.calc(s.tokenize("2*2+3"), i, varTable, 0) == 7);
  943. i = 0;
  944. assert (s.calc(s.tokenize("2*(2+3)"), i, varTable, 0) == 10);
  945. i = 0;
  946. assert (s.calc(s.tokenize("1+2*3"), i, varTable, 0) == 7);
  947. i = 0;
  948. assert (s.calc(s.tokenize("(1+2)*3"), i, varTable, 0) == 9);
  949. i = 0;
  950. assert (s.calc(s.tokenize("-3-3"), i, varTable, 0) == -6);
  951. i = 0;
  952. assert (s.calc(s.tokenize("2 * 3 % 4"), i, varTable, 0) == 2);
  953. i = 0;
  954. assert (s.calc(s.tokenize("3 + -3-3"), i, varTable, 0) == -3);
  955. i = 0;
  956. assert (s.calc(s.tokenize("1+2 * 3 % 4"), i, varTable, 0) == 3);
  957. i = 0;
  958. assert (s.calc(s.tokenize("1+2 ~ 3 % 4"), i, varTable, 0) == "33");
  959. i = 0;
  960. tokens = s.tokenize("1 + $s");
  961. assert (s.calc(tokens, i, varTable, 0) == 1);
  962. i = 0;
  963. tokens = s.tokenize("$s + 1");
  964. assert (s.calc(tokens, i, varTable, 0) == 1);
  965. i = 0;
  966. tokens = s.tokenize("1+2 * 3 % 4 + -3-$abc $abc");
  967. assert (s.calc(tokens, i, varTable, 0) == -15);
  968. assert (tokens[i].value == "$abc");
  969. i = 0;
  970. tokens = s.tokenize("(1+2) * 3 % 4 + (-3-3) if");
  971. assert (s.calc(tokens, i, varTable, 0) == -5);
  972. assert (tokens[i].value == "if");
  973. }
  974. private static struct Keywords {
  975. immutable CType[string] keywords;
  976. immutable string[CType] commands;
  977. }
  978. private static shared immutable Keywords KEYS;
  979. shared static this () { mixin(S_TRACE);
  980. auto keywords = [
  981. cast(string) "start":CType.START,
  982. cast(string) "gobattle":CType.START_BATTLE,
  983. cast(string) "endsc":CType.END,
  984. cast(string) "gameover":CType.END_BAD_END,
  985. cast(string) "goarea":CType.CHANGE_AREA,
  986. cast(string) "chback":CType.CHANGE_BG_IMAGE,
  987. cast(string) "effect":CType.EFFECT,
  988. cast(string) "break":CType.EFFECT_BREAK,
  989. cast(string) "gostart":CType.LINK_START,
  990. cast(string) "gopack":CType.LINK_PACKAGE,
  991. cast(string) "msg":CType.TALK_MESSAGE,
  992. cast(string) "dialog":CType.TALK_DIALOG,
  993. cast(string) "bgm":CType.PLAY_BGM,
  994. cast(string) "se":CType.PLAY_SOUND,
  995. cast(string) "wait":CType.WAIT,
  996. cast(string) "elapse":CType.ELAPSE_TIME,
  997. cast(string) "callstart":CType.CALL_START,
  998. cast(string) "callpack":CType.CALL_PACKAGE,
  999. cast(string) "brflag":CType.BRANCH_FLAG,
  1000. cast(string) "brstepm":CType.BRANCH_MULTI_STEP,
  1001. cast(string) "brstept":CType.BRANCH_STEP,
  1002. cast(string) "selmember":CType.BRANCH_SELECT,
  1003. cast(string) "brability":CType.BRANCH_ABILITY,
  1004. cast(string) "brrandom":CType.BRANCH_RANDOM,
  1005. cast(string) "brlevel":CType.BRANCH_LEVEL,
  1006. cast(string) "brstatus":CType.BRANCH_STATUS,
  1007. cast(string) "brcount":CType.BRANCH_PARTY_NUMBER,
  1008. cast(string) "brarea":CType.BRANCH_AREA,
  1009. cast(string) "brbattle":CType.BRANCH_BATTLE,
  1010. cast(string) "bronbattle":CType.BRANCH_IS_BATTLE,
  1011. cast(string) "brcast":CType.BRANCH_CAST,
  1012. cast(string) "britem":CType.BRANCH_ITEM,
  1013. cast(string) "brskill":CType.BRANCH_SKILL,
  1014. cast(string) "brinfo":CType.BRANCH_INFO,
  1015. cast(string) "brbeast":CType.BRANCH_BEAST,
  1016. cast(string) "brmoney":CType.BRANCH_MONEY,
  1017. cast(string) "brcoupon":CType.BRANCH_COUPON,
  1018. cast(string) "brstamp":CType.BRANCH_COMPLETE_STAMP,
  1019. cast(string) "brgossip":CType.BRANCH_GOSSIP,
  1020. cast(string) "setflag":CType.SET_FLAG,
  1021. cast(string) "setstep":CType.SET_STEP,
  1022. cast(string) "stepup":CType.SET_STEP_UP,
  1023. cast(string) "stepdown":CType.SET_STEP_DOWN,
  1024. cast(string) "revflag":CType.REVERSE_FLAG,
  1025. cast(string) "chkflag":CType.CHECK_FLAG,
  1026. cast(string) "getcast":CType.GET_CAST,
  1027. cast(string) "getitem":CType.GET_ITEM,
  1028. cast(string) "getskill":CType.GET_SKILL,
  1029. cast(string) "getinfo":CType.GET_INFO,
  1030. cast(string) "getbeast":CType.GET_BEAST,
  1031. cast(string) "getmoney":CType.GET_MONEY,
  1032. cast(string) "getcoupon":CType.GET_COUPON,
  1033. cast(string) "getstamp":CType.GET_COMPLETE_STAMP,
  1034. cast(string) "getgossip":CType.GET_GOSSIP,
  1035. cast(string) "losecast":CType.LOSE_CAST,
  1036. cast(string) "loseitem":CType.LOSE_ITEM,
  1037. cast(string) "loseskill":CType.LOSE_SKILL,
  1038. cast(string) "loseinfo":CType.LOSE_INFO,
  1039. cast(string) "losebeast":CType.LOSE_BEAST,
  1040. cast(string) "losemoney":CType.LOSE_MONEY,
  1041. cast(string) "losecoupon":CType.LOSE_COUPON,
  1042. cast(string) "losestamp":CType.LOSE_COMPLETE_STAMP,
  1043. cast(string) "losegossip":CType.LOSE_GOSSIP,
  1044. cast(string) "showparty":CType.SHOW_PARTY,
  1045. cast(string) "hideparty":CType.HIDE_PARTY,
  1046. cast(string) "redraw":CType.REDISPLAY,
  1047. cast(string) "cpstep":CType.SUBSTITUTE_STEP,
  1048. cast(string) "cpflag":CType.SUBSTITUTE_FLAG,
  1049. cast(string) "cmpstep":CType.BRANCH_STEP_CMP,
  1050. cast(string) "cmpflag":CType.BRANCH_FLAG_CMP,
  1051. cast(string) "selrandom":CType.BRANCH_RANDOM_SELECT,
  1052. cast(string) "brkeycode":CType.BRANCH_KEY_CODE,
  1053. cast(string) "chkstep":CType.CHECK_STEP,
  1054. cast(string) "brround":CType.BRANCH_ROUND,
  1055. cast(string) "mvback":CType.MOVE_BG_IMAGE,
  1056. cast(string) "rplback":CType.REPLACE_BG_IMAGE,
  1057. cast(string) "loseback":CType.LOSE_BG_IMAGE,
  1058. ];
  1059. string[CType] commands;
  1060. foreach (name, type; keywords) { mixin(S_TRACE);
  1061. commands[type] = name;
  1062. }
  1063. KEYS = Keywords(.assumeUnique(keywords), .assumeUnique(commands));
  1064. }
  1065. /// ??????
  1066. enum NodeType {
  1067. VAR_SET, /// ??????
  1068. START, /// ???????????
  1069. COMMAND, /// ??????????
  1070. VALUES, /// VALUE????
  1071. VALUE, /// ?????
  1072. }
  1073. /// ??????????????????????
  1074. struct Node {
  1075. NodeType type; /// ??
  1076. Token token; /// ???Token?
  1077. const(Node)[] texts = []; /// ?????
  1078. const(Node)[] attr; /// ???
  1079. const(Node)[] childs; /// ?????
  1080. bool nextIsChild; /// ??????????????
  1081. alias childs values; /// ????VALUES?????????VALUE???????
  1082. const(Token)[] calc; /// ????
  1083. alias calc var; /// ???
  1084. alias texts value; /// ????
  1085. const(Node)[] beforeVars; /// ????????????????
  1086. /// o??????
  1087. const
  1088. bool opEquals(ref const(Node) o) { mixin(S_TRACE);
  1089. return type == o.type && token == o.token && texts == o.texts
  1090. && attr == o.attr && childs == o.childs && calc == o.calc
  1091. && beforeVars == o.beforeVars;
  1092. }
  1093. /// ?????????
  1094. @property
  1095. const
  1096. Node dup() { mixin(S_TRACE);
  1097. return Node(type, token, texts.dup, attr.dup, childs.dup, nextIsChild, calc.dup, beforeVars.dup);
  1098. }
  1099. /// ??????
  1100. const
  1101. string toString() {return token.toString();}
  1102. /// ???????????????????
  1103. static string code(string indent, in Node[] array) {return code(" ", "", "", array, "sif");}
  1104. private static string code(string indent, string bIndentValue, string indentValue, in Node[] array, string ifString) { mixin(S_TRACE);
  1105. string calcCode(in Node[] calc) { mixin(S_TRACE);
  1106. char[] calcBuf;
  1107. enforce(1 == calc.length);
  1108. foreach (i, tok; calc[0].calc) { mixin(S_TRACE);
  1109. if ((tok.kind is Kind.C_PAR)
  1110. || (i > 0 && calc[0].calc[i - 1].kind is Kind.O_PAR)) { mixin(S_TRACE);
  1111. calcBuf ~= tok.value;
  1112. } else { mixin(S_TRACE);
  1113. if (i > 0) calcBuf ~= " ";
  1114. calcBuf ~= tok.value;
  1115. }
  1116. }
  1117. return assumeUnique(calcBuf);
  1118. }
  1119. string buf = "";
  1120. if (!array.length) return buf;
  1121. foreach (node; array) { mixin(S_TRACE);
  1122. if (buf.length) { mixin(S_TRACE);
  1123. buf ~= "\n";
  1124. }
  1125. if (node.type is NodeType.COMMAND && node.texts.length) { mixin(S_TRACE);
  1126. buf ~= .format("%s%s %s\n", ifString == "sif" ? indentValue : bIndentValue, ifString, calcCode(node.texts));
  1127. ifString = "sif";
  1128. }
  1129. if (node.type is NodeType.VAR_SET) { mixin(S_TRACE);
  1130. enforce(node.value.length > 0, new Exception("Invalid node", __FILE__, __LINE__));
  1131. if (node.value[0].token.kind is Kind.STRING) { mixin(S_TRACE);
  1132. buf ~= .format("%s%s = %s", indentValue, node.token.value, node.value[0].token.value);
  1133. } else { mixin(S_TRACE);
  1134. buf ~= .format("%s%s = %s", node.token.value, indentValue, calcCode(node.value));
  1135. }
  1136. continue;
  1137. } else if (node.type is NodeType.VALUE) { mixin(S_TRACE);
  1138. if (node.var.length <= 1) { mixin(S_TRACE);
  1139. buf ~= node.token.value;
  1140. } else { mixin(S_TRACE);
  1141. buf ~= calcCode([node]);
  1142. }
  1143. continue;
  1144. } else if (node.type is NodeType.VALUES) { mixin(S_TRACE);
  1145. string vals = "[";
  1146. foreach (i, c; node.values) { mixin(S_TRACE);
  1147. if (i > 0) vals ~= ", ";
  1148. vals ~= Node.code(indent, [c]);
  1149. }
  1150. vals ~= "]";
  1151. buf ~= vals;
  1152. continue;
  1153. }
  1154. foreach (i, var; node.beforeVars) { mixin(S_TRACE);
  1155. buf ~= .format("%s%s\n", indentValue, Node.code(indent, [var]));
  1156. }
  1157. string attrs = "";
  1158. if (node.token.kind is Kind.START) { mixin(S_TRACE);
  1159. attrs = " " ~ calcCode(node.texts);
  1160. } else { mixin(S_TRACE);
  1161. foreach (i, a; node.attr) { mixin(S_TRACE);
  1162. auto ac = Node.code(indent, [a]);
  1163. if (a.type is NodeType.VALUES && i > 0) { mixin(S_TRACE);
  1164. attrs ~= "\n";
  1165. attrs ~= indentValue;
  1166. attrs ~= .rightJustify("", node.token.value.length + 1);
  1167. attrs ~= ac;
  1168. } else { mixin(S_TRACE);
  1169. attrs ~= i == 0 ? " " : ", ";
  1170. attrs ~= ac;
  1171. }
  1172. }
  1173. }
  1174. buf ~= .format("%s%s%s", indentValue, node.token.value, attrs);
  1175. if (node.nextIsChild) continue;
  1176. bool startBlock = true;
  1177. bool nextIsStartPoint = true;
  1178. const(Node)[][] block;
  1179. foreach (i, c; node.childs) { mixin(S_TRACE);
  1180. if (startBlock && nextIsStartPoint) { mixin(S_TRACE);
  1181. block ~= new const(Node)[0];
  1182. }
  1183. startBlock = false;
  1184. if (c.type !is NodeType.VAR_SET) { mixin(S_TRACE);
  1185. nextIsStartPoint = !c.nextIsChild;
  1186. startBlock = true;
  1187. }
  1188. block[$ - 1] ~= c;
  1189. }
  1190. if (block.length > 1) { mixin(S_TRACE);
  1191. foreach (i, b; block) { mixin(S_TRACE);
  1192. string f = i == 0 ? "if" : "elif";
  1193. buf ~= "\n";
  1194. buf ~= Node.code(indent, indentValue, indentValue ~ indent, b, f);
  1195. }
  1196. buf ~= "\n";
  1197. buf ~= indentValue ~ "fi";
  1198. } else if (block.length == 1) { mixin(S_TRACE);
  1199. buf ~= "\n";
  1200. buf ~= Node.code(indent, indentValue, node.token.kind is Kind.START ? indentValue ~ indent : indentValue, block[0], "sif");
  1201. }
  1202. }
  1203. return buf;
  1204. }
  1205. }
  1206. /// ?????????????
  1207. private string attrValue(in Node node, in const(Node)[][string] varTable, size_t strWidth) { mixin(S_TRACE);
  1208. switch (node.token.kind) {
  1209. case Kind.SYMBOL: return std.string.toLower(node.token.value);
  1210. case Kind.VAR_NAME:
  1211. if (1 < node.calc.length) goto case Kind.STRING;
  1212. auto nodes = var(node, varTable);
  1213. if (!nodes.length) return "";
  1214. auto tok = nodes[0].token;
  1215. if (tok.kind is Kind.STRING) { mixin(S_TRACE);
  1216. return stringValue(tok, strWidth);
  1217. } else if (tok.kind is Kind.SYMBOL) { mixin(S_TRACE);
  1218. return std.string.toLower(tok.value);
  1219. }
  1220. return attrValue(nodes[0], varTable, strWidth);
  1221. case Kind.STRING, Kind.NUMBER, Kind.PLU, Kind.MIN, Kind.O_PAR:
  1222. size_t i = 0;
  1223. auto r = calc(node.calc, i, varTable, strWidth);
  1224. final switch (r.kind) {
  1225. case CRKind.STR: return r.str;
  1226. case CRKind.INT: return to!(string)(r.numInt);
  1227. case CRKind.REAL: return to!(string)(r.numReal);
  1228. }
  1229. case Kind.O_BRA: return "";
  1230. case Kind.COMMA: return "";
  1231. default:
  1232. throwError(_prop.msgs.scriptErrorInvalidAttr, node.token);
  1233. return "";
  1234. }
  1235. assert (0);
  1236. }
  1237. /// ???????
  1238. private const(Node)[] varValue(in Node node, const(Node)[] values, in const(Node)[][string] varTable, size_t strWidth) { mixin(S_TRACE);
  1239. if (!values.length) { mixin(S_TRACE);
  1240. throwError(_prop.msgs.scriptErrorInvalidVar, node.token);
  1241. return values;
  1242. }
  1243. const(Node)[] r;
  1244. foreach (v; values) { mixin(S_TRACE);
  1245. const(Node)[] vs;
  1246. if (v.token.kind is Kind.VAR_NAME) { mixin(S_TRACE);
  1247. vs ~= var(v, varTable);
  1248. } else { mixin(S_TRACE);
  1249. vs = [v];
  1250. }
  1251. foreach (v2; vs) { mixin(S_TRACE);
  1252. switch (v2.token.kind) {
  1253. case Kind.STRING, Kind.NUMBER, Kind.PLU, Kind.MIN, Kind.O_PAR:
  1254. // ??????????
  1255. size_t i = 0;
  1256. Node rNode = v2.dup;
  1257. auto cr = calc(v2.calc, i, varTable, strWidth);
  1258. rNode.token.kind = cr.kind is CRKind.STR ? Kind.STRING : Kind.NUMBER;
  1259. final switch (cr.kind) {
  1260. case CRKind.STR:
  1261. rNode.token.value = createString(cr.str);
  1262. break;
  1263. case CRKind.INT:
  1264. rNode.token.value = to!(string)(cr.numInt);
  1265. break;
  1266. case CRKind.REAL:
  1267. rNode.token.value = to!(string)(cr.numReal);
  1268. break;
  1269. }
  1270. r ~= rNode;
  1271. break;
  1272. default:
  1273. r ~= v2;
  1274. break;
  1275. }
  1276. }
  1277. }
  1278. return r;
  1279. }
  1280. private const(Node)[] var(in Node[] nodes, in const(Node)[][string] varTable) { mixin(S_TRACE);
  1281. const(Node)[] r;
  1282. foreach (node; nodes) { mixin(S_TRACE);
  1283. r ~= var(node, varTable);
  1284. }
  1285. return r;
  1286. }
  1287. private const(Node)[] var(in Node node, in const(Node)[][string] varTable) { mixin(S_TRACE);
  1288. if (node.token.kind is Kind.VAR_NAME) { mixin(S_TRACE);
  1289. auto ptr = std.string.toLower(node.token.value) in varTable;
  1290. if (ptr) { mixin(S_TRACE);
  1291. const(Node)[] r;
  1292. foreach (v; *ptr) { mixin(S_TRACE);
  1293. r ~= var(v, varTable);
  1294. }
  1295. return r;
  1296. }
  1297. throwError(_prop.msgs.scriptErrorUndefinedVar, node.token);
  1298. return [];
  1299. }
  1300. return [node];
  1301. }
  1302. private Token var(in Token tok, in const(Node)[][string] varTable) { mixin(S_TRACE);
  1303. if (tok.kind is Kind.VAR_NAME) { mixin(S_TRACE);
  1304. auto ptr = std.string.toLower(tok.value) in varTable;
  1305. if (ptr && ptr.length) { mixin(S_TRACE);
  1306. return var((*ptr)[0].token, varTable);
  1307. }
  1308. throwError(_prop.msgs.scriptErrorUndefinedVar, tok);
  1309. }
  1310. return tok;
  1311. }
  1312. /// tokens?????Node???????????
  1313. Node[] analyzeSyntax(in Token[] tokens) { mixin(S_TRACE);
  1314. Node[] r;
  1315. size_t i = 0;
  1316. Node[] vars;
  1317. const(Token)[] tokens2;
  1318. foreach (ref tok; tokens) { mixin(S_TRACE);
  1319. if (tok.kind !is Kind.COMMENT) { mixin(S_TRACE);
  1320. tokens2 ~= tok;
  1321. }
  1322. }
  1323. while (i < tokens2.length) { mixin(S_TRACE);
  1324. vars ~= eatVarSet(tokens2, i, KEYS);
  1325. if (tokens2.length <= i) { mixin(S_TRACE);
  1326. break;
  1327. }
  1328. Token tok = tokens2[i];
  1329. if (tok.kind !is Kind.START) { mixin(S_TRACE);
  1330. r ~= analyzeSyntaxBranch(tokens2, i, KEYS, vars);
  1331. continue;
  1332. }
  1333. i++;
  1334. Node node;
  1335. node.type = NodeType.START;
  1336. node.token = tok;
  1337. node.texts = analyzeSyntaxAttr(tokens2, i, KEYS);
  1338. if (!node.texts.length) { mixin(S_TRACE);
  1339. throwError(_prop.msgs.scriptErrorNoStartText, tok);
  1340. }
  1341. node.beforeVars = vars;
  1342. vars = [];
  1343. node.nextIsChild = false;
  1344. c: while (i < tokens2.length) { mixin(S_TRACE);
  1345. vars ~= eatVarSet(tokens2, i, KEYS);
  1346. string cText = "";
  1347. switch (tokens2[i].kind) {
  1348. case Kind.START, Kind.FI: break c;
  1349. case Kind.IF, Kind.ELIF, Kind.SYMBOL, Kind.SIF:
  1350. node.childs ~= analyzeSyntaxBranch(tokens2, i, KEYS, vars);
  1351. continue;
  1352. default:
  1353. throwError(_prop.msgs.scriptErrorInvalidStatement, tokens2[i]);
  1354. i++;
  1355. }
  1356. }
  1357. r ~= node;
  1358. }
  1359. return r;
  1360. } unittest { mixin(S_TRACE);
  1361. debug mixin(UTPerf);
  1362. auto s = new CWXScript(new CProps("", null), null);
  1363. string statement
  1364. = `
  1365. $var1 = 'oops'
  1366. Start "First start"
  1367. if 'abc'
  1368. chback ['mapofwirth.bmp', '', 0, 0, 632, 420]
  1369. ['definn.bmp', '', 50, 50, 200*2, 260]
  1370. ['card.bmp', 'card\mate1', 230, 50, 74, 94, mask],
  1371. 1
  1372. hideparty
  1373. $var2 = 3.5
  1374. wait ($var2 + 1.5)
  1375. msg M
  1376. @ 3
  1377. Talk!
  1378. Talk!
  1379. Talk!
  1380. @
  1381. sif "Single IF"
  1382. brflag 'card\mate1'
  1383. if true
  1384. $var3 = 'what?'
  1385. showparty
  1386. endsc true
  1387. $var_dummy = nothing
  1388. elif false
  1389. gameover // comment1
  1390. fi
  1391. elif 'def' effect 1, 2, 3 + 2
  1392. fi
  1393. // comment2
  1394. $var4 = true
  1395. start "second start"
  1396. showparty
  1397. getskill 1`;
  1398. auto tokens = s.tokenize(statement);
  1399. auto starts = s.analyzeSyntax(tokens);
  1400. assert (Node.code(" ", starts)
  1401. == "$var1 = 'oops'\n"
  1402. ~ "Start \"First start\"\n"
  1403. ~ "if 'abc'\n"
  1404. ~ " chback ['mapofwirth.bmp', '', 0, 0, 632, 420]\n"
  1405. ~ " ['definn.bmp', '', 50, 50, 200 * 2, 260]\n"
  1406. ~ " ['card.bmp', 'card\\mate1', 230, 50, 74, 94, mask], 1\n"
  1407. ~ " hideparty\n"
  1408. ~ " $var2 = 3.5\n"
  1409. ~ " wait ($var2 + 1.5)\n"
  1410. ~ " msg M, @ 3\n"
  1411. ~ " Talk!\n"
  1412. ~ " Talk!\n"
  1413. ~ " Talk!\n"
  1414. ~ " @\n"
  1415. ~ " sif \"Single IF\"\n"
  1416. ~ " brflag 'card\\mate1'\n"
  1417. ~ " if true\n"
  1418. ~ " $var3 = 'what?'\n"
  1419. ~ " showparty\n"
  1420. ~ " endsc true\n"
  1421. ~ " elif false\n"
  1422. ~ " $var_dummy = nothing\n" /* ???????????????? */
  1423. ~ " gameover\n"
  1424. ~ " fi\n"
  1425. ~ "elif 'def'\n"
  1426. ~ " effect 1, 2, 3 + 2\n"
  1427. ~ "fi\n"
  1428. ~ "$var4 = true\n"
  1429. ~ "start \"second start\"\n"
  1430. ~ " showparty\n"
  1431. ~ " getskill 1", Node.code(" ", starts));
  1432. string statement2
  1433. = `
  1434. brflag 'card\mate1'
  1435. if true
  1436. $var3 = 'what?'
  1437. showparty
  1438. endsc true
  1439. $var_dummy = nothing
  1440. elif false
  1441. gameover // comment1
  1442. fi`;
  1443. auto tokens2 = s.tokenize(statement2);
  1444. auto contents = s.analyzeSyntax(tokens2);
  1445. assert (Node.code(" ", contents)
  1446. == "brflag 'card\\mate1'\n"
  1447. ~ "if true\n"
  1448. ~ " $var3 = 'what?'\n"
  1449. ~ " showparty\n"
  1450. ~ " endsc true\n"
  1451. ~ "elif false\n"
  1452. ~ " $var_dummy = nothing\n"
  1453. ~ " gameover\n"
  1454. ~ "fi");
  1455. // ????????????????
  1456. s.analyzeSyntax(s.tokenize("dialog M, @c\n...\n@]"));
  1457. string statement3 = `chback [] goarea 1`;
  1458. auto tokens3 = s.tokenize(statement3);
  1459. auto contents2 = s.analyzeSyntax(tokens3);
  1460. assert (contents2.length == 2);
  1461. assert (contents2[0].nextIsChild);
  1462. assert (!contents2[1].nextIsChild);
  1463. }
  1464. private Node[] analyzeSyntaxBranch(in Token[] tokens, ref size_t i, in Keywords keys, ref Node[] vars) { mixin(S_TRACE);
  1465. Node[] r;
  1466. auto tok = tokens[i];
  1467. switch (tok.kind) {
  1468. case Kind.START: return r;
  1469. case Kind.IF, Kind.VAR_NAME:
  1470. while (i < tokens.length) { mixin(S_TRACE);
  1471. Node[] texts;
  1472. if (tokens[i].kind is Kind.IF || tokens[i].kind is Kind.ELIF) { mixin(S_TRACE);
  1473. i++;
  1474. texts = analyzeSyntaxAttr(tokens, i, keys);
  1475. if (!texts.length) { mixin(S_TRACE);
  1476. throwError(_prop.msgs.scriptErrorNoIfText, tok);
  1477. }
  1478. if (tokens.length <= i) { mixin(S_TRACE);
  1479. throwError(_prop.msgs.scriptErrorNoIfContents, tok);
  1480. }
  1481. }
  1482. auto node = analyzeSyntaxStatement(tokens, i, keys, vars);
  1483. if (node.length) { mixin(S_TRACE);
  1484. node[0].texts = texts;
  1485. }
  1486. r ~= node;
  1487. if (tokens.length <= i) return r;
  1488. switch (tokens[i].kind) {
  1489. case Kind.START: return r;
  1490. case Kind.FI:
  1491. i++;
  1492. return r;
  1493. case Kind.ELIF: continue;
  1494. case Kind.VAR_NAME: continue;
  1495. default:
  1496. throwError(_prop.msgs.scriptErrorInvalidStatement, tokens[i]);
  1497. i++;
  1498. }
  1499. }
  1500. break;
  1501. case Kind.SYMBOL, Kind.SIF:
  1502. r ~= analyzeSyntaxStatement(tokens, i, keys, vars);
  1503. break;
  1504. default:
  1505. throwError(_prop.msgs.scriptErrorInvalidBranch, tok);
  1506. i++;
  1507. break;
  1508. }
  1509. return r;
  1510. }
  1511. private Node[] eatVarSet(in Token[] tokens, ref size_t i, in Keywords keys) { mixin(S_TRACE);
  1512. Node[] r;
  1513. while (i < tokens.length && tokens[i].kind is Kind.VAR_NAME) { mixin(S_TRACE);
  1514. r ~= analyzeSyntaxVar(tokens, i, keys);
  1515. }
  1516. return r;
  1517. }
  1518. private Node[] analyzeSyntaxStatement(in Token[] tokens, ref size_t i, in Keywords keys, ref Node[] vars) { mixin(S_TRACE);
  1519. Node[] r;
  1520. while (true) { mixin(S_TRACE);
  1521. assert (i < tokens.length);
  1522. vars ~= eatVarSet(tokens, i, keys);
  1523. Token tok = tokens[i];
  1524. Node[] sifTexts;
  1525. if (tok.kind is Kind.SIF) { mixin(S_TRACE);
  1526. i++;
  1527. if (tokens.length <= i) { mixin(S_TRACE);
  1528. throwError(_prop.msgs.scriptErrorNoSifText, tok);
  1529. return r;
  1530. }
  1531. sifTexts = analyzeSyntaxAttr(tokens, i, keys);
  1532. if (tokens.length <= i) { mixin(S_TRACE);
  1533. throwError(_prop.msgs.scriptErrorInvalidSif, tok);
  1534. }
  1535. tok = tokens[i];
  1536. } else if (tok.kind !is Kind.SYMBOL) { mixin(S_TRACE);
  1537. throwError(_prop.msgs.scriptErrorInvalidStatement, tok);
  1538. }
  1539. Node node;
  1540. node.type = NodeType.COMMAND;
  1541. node.token = tok;
  1542. node.texts = sifTexts;
  1543. node.nextIsChild = false;
  1544. auto symbol = std.string.toLower(tok.value);
  1545. if (!(symbol in keys.keywords)) { mixin(S_TRACE);
  1546. throwError(_prop.msgs.scriptErrorInvalidKeyword, tok);
  1547. }
  1548. node.beforeVars = vars;
  1549. vars = [];
  1550. i++;
  1551. if (tokens.length <= i) return r ~ node;
  1552. node.attr = analyzeSyntaxAttr(tokens, i, keys);
  1553. vars ~= eatVarSet(tokens, i, keys);
  1554. if (tokens.length <= i) return r ~ node;
  1555. switch (tokens[i].kind) {
  1556. case Kind.START, Kind.ELIF, Kind.FI:
  1557. return r ~ node;
  1558. case Kind.IF:
  1559. node.childs ~= analyzeSyntaxBranch(tokens, i, keys, vars);
  1560. return r ~ node;
  1561. case Kind.SIF, Kind.SYMBOL:
  1562. node.nextIsChild = true;
  1563. r ~= node;
  1564. continue;
  1565. default:
  1566. throwError(_prop.msgs.scriptErrorInvalidStatement, tok);
  1567. return r ~ node;
  1568. }
  1569. }
  1570. return r;
  1571. }
  1572. private Node[] analyzeSyntaxAttr(in Token[] tokens, ref size_t i, in Keywords keys) { mixin(S_TRACE);

Large files files are truncated, but you can click here to view the full file