PageRenderTime 510ms CodeModel.GetById 81ms app.highlight 362ms RepoModel.GetById 20ms app.codeStats 2ms

/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

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

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

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