switched to GPLv3 ONLY, because i don't trust FSF anymore
[knightmare.git] / mesengine.d
blob1e4fd1082fad54efaa4fcfaa8d896cca01ee5952
1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 module mesengine;
17 private:
19 import iv.strex;
20 import iv.vfs;
21 import iv.vfs.io;
23 import mesactor;
26 version = mesdbg_dump_generated_code;
27 version = mesdbg_dump_compiled_code;
28 //version = mesdbg_dump_dump_execution;
29 //version = mes_paranoid_arrays; // always check array access (no need to, compiler will generate bounds checking)
30 public __gshared bool MESDisableDumps = false;
33 // ////////////////////////////////////////////////////////////////////////// //
34 public __gshared void delegate (string msg) mesOnCompilerMessage;
37 public void compilerMessage(A...) (string fmt, A args) {
38 if (mesOnCompilerMessage !is null) {
39 import std.format : format;
40 mesOnCompilerMessage(fmt.format(args));
45 // ////////////////////////////////////////////////////////////////////////// //
46 public class CompilerError : Exception {
47 Loc loc;
48 this() (in auto ref Loc aloc, string msg, string file=__FILE__, usize line=__LINE__) nothrow @safe @nogc {
49 loc = aloc;
50 super(msg, file, line);
55 // ////////////////////////////////////////////////////////////////////////// //
56 public class OverloadNotFound : Exception {
57 this (string msg, string file=__FILE__, usize line=__LINE__) nothrow @safe @nogc {
58 super(msg, file, line);
63 // ////////////////////////////////////////////////////////////////////////// //
64 public struct Loc {
65 int line, col;
66 string fname;
68 @property string toString () const {
69 import std.format : format;
70 return "(%d,%d)".format(line, col);
73 @property string toFullString () const {
74 import std.format : format;
75 if (fname.length) return "%s (%d,%d)".format(fname, line, col);
76 return "(%d,%d)".format(line, col);
81 public struct Token {
82 public:
83 enum T {
84 LitNone,
85 LitStr,
86 LitInt,
87 LitId,
88 // delimiter (sval is char or two chars)
89 LitDelim,
90 // keywords
91 Bool,
92 Ref,
93 Out,
94 In,
95 True,
96 False,
97 Int,
98 String,
99 Actor,
100 ActorDef,
101 Void,
102 Auto,
103 Struct,
104 Return,
106 Else,
107 While,
108 For,
109 Switch,
110 Case,
111 Const,
112 Enum,
113 Cast,
114 Method,
115 Function,
116 Alias,
117 Continue,
118 Break,
119 Null,
121 New,
122 Abs, //HACK
123 Default,
126 public:
127 T type = T.LitNone;
128 Loc loc;
129 string sval;
130 int ival;
132 public:
133 @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (type == T.LitNone); }
134 @property bool str () const pure nothrow @safe @nogc { pragma(inline, true); return (type == T.LitStr); }
135 @property bool num () const pure nothrow @safe @nogc { pragma(inline, true); return (type == T.LitInt); }
137 bool id () const pure nothrow @safe @nogc { pragma(inline, true); return (type == T.LitId); }
138 bool delim () const pure nothrow @safe @nogc { pragma(inline, true); return (type == T.LitDelim); }
139 bool kw () const pure nothrow @safe @nogc { pragma(inline, true); return (type >= T.Bool); }
141 bool id (const(char)[] v) const pure nothrow @safe @nogc { pragma(inline, true); return (type == T.LitId && sval == v); }
142 bool delim (const(char)[] v) const pure nothrow @safe @nogc { pragma(inline, true); return (type == T.LitDelim && sval == v); }
143 bool kw (T v) const pure nothrow @safe @nogc { pragma(inline, true); return (v >= T.Bool && type == v); }
147 public class ParserBase {
148 public:
150 protected:
151 Loc mTokLoc;
152 Loc mCurLoc;
153 char mNextCh;
154 char mCurCh;
155 Token mCurToken;
157 protected:
158 abstract char getch (); // 0: eof
160 final void readToken (ref Token ctk) {
161 ctk = ctk.init;
162 ctk.type = Token.T.LitNone;
163 skipSpaces();
164 ctk.loc = mCurLoc;
165 if (mCurCh == 0) return;
167 // number?
168 if (mCurCh >= '0' && mCurCh <= '9') {
169 int base = 0;
170 if (mCurCh == '0') {
171 switch (mNextCh) {
172 case 'x': case 'X': base = 16; break;
173 case 'o': case 'O': base = 8; break;
174 case 'b': case 'B': base = 2; break;
175 case 'd': case 'D': base = 10; break;
176 default: break;
179 if (base == 0) {
180 base = 10;
181 } else {
182 skipch();
183 skipch();
184 if (digitInBase(mCurCh, base) < 0) errorAt(ctk.loc, "invalid number");
186 int n = 0;
187 for (;;) {
188 if (mCurCh != '_') {
189 if (digitInBase(mCurCh, base) < 0) break;
190 n = n*base+digitInBase(mCurCh, base);
191 if (n < 0) errorAt(ctk.loc, "integer overflow");
193 skipch();
195 if (mCurCh >= 127 || isalpha(mCurCh)) errorAt(ctk.loc, "invalid number");
196 ctk.type = ctk.T.LitInt;
197 ctk.ival = n;
198 return;
201 // identifier?
202 if (mCurCh == '_' || isalpha(mCurCh) || mCurCh >= 127) {
203 while (mCurCh && (mCurCh == '_' || isalnum(mCurCh) || mCurCh >= 127)) {
204 ctk.sval ~= mCurCh;
205 skipch();
207 ctk.type = ctk.T.LitId;
208 if (isalpha(ctk.sval[0])) {
209 mainloop: foreach (string mname; __traits(allMembers, ctk.T)) {
210 static if (mname == "Actor" || (mname.length > 3 && mname[0..3] == "Lit")) {
211 // nothing
212 } else {
213 if (ctk.sval.length == mname.length && ctk.sval[0] == cast(char)(mname[0]+32)) {
214 bool ok = true;
215 foreach (immutable idx, immutable char ch; mname[1..$]) {
216 if (ctk.sval[idx+1] != ch) { ok = false; break; }
218 if (ok) {
219 ctk.type = __traits(getMember, ctk.T, mname);
220 break mainloop;
225 if (ctk.sval == "Actor") ctk.type = Token.T.Actor;
227 return;
230 // backslash is skipped
231 char parseEscapeChar () {
232 char res;
233 if (!mCurCh) errorAt(ctk.loc, "invalid escape");
234 switch (mCurCh) {
235 case 't': res = '\t'; break;
236 case 'n': res = '\n'; break;
237 case 'r': res = '\r'; break;
238 case 'e': res = '\x1b'; break;
239 case 'x':
240 skipch(); // 'x'
241 int n = digitInBase(mCurCh, 16);
242 if (n < 0) errorAt(ctk.loc, "invalid hex escape");
243 skipch(); // first hex digit
244 if (digitInBase(mCurCh, 16) >= 0) {
245 n = n*16+digitInBase(mCurCh, 16);
246 skipch();
248 return cast(char)n;
249 default:
250 if (isalnum(mCurCh)) errorAt(ctk.loc, "invalid escape");
251 res = mCurCh;
252 break;
254 skipch();
255 return res;
258 // string?
259 if (mCurCh == '"') {
260 skipch();
261 while (mCurCh && mCurCh != '"') {
262 if (mCurCh == '\\') {
263 skipch();
264 ctk.sval ~= parseEscapeChar();
265 } else {
266 ctk.sval ~= mCurCh;
267 skipch();
270 if (mCurCh != '"') errorAt(ctk.loc, "invalid string");
271 skipch();
272 ctk.type = ctk.T.LitStr;
273 return;
276 // char?
277 if (mCurCh == '\'') {
278 skipch();
279 if (!mCurCh) errorAt(ctk.loc, "invalid char");
280 char ch;
281 if (mCurCh == '\\') {
282 skipch();
283 ch = parseEscapeChar();
284 } else {
285 ch = mCurCh;
286 skipch();
288 if (mCurCh != '\'') errorAt(ctk.loc, "\"'\" expected");
289 skipch();
290 ctk.type = ctk.T.LitInt;
291 ctk.ival = cast(int)ch;
292 return;
295 // delimiter
296 ctk.type = ctk.T.LitDelim;
297 if (mCurCh == '<' && mNextCh == '=') { ctk.sval = "<="; skipch(); }
298 else if (mCurCh == '>' && mNextCh == '=') { ctk.sval = ">="; skipch(); }
299 else if (mCurCh == '!' && mNextCh == '=') { ctk.sval = "!="; skipch(); }
300 else if (mCurCh == '=' && mNextCh == '=') { ctk.sval = "=="; skipch(); }
301 else if (mCurCh == '+' && mNextCh == '+') { ctk.sval = "++"; skipch(); }
302 else if (mCurCh == '-' && mNextCh == '-') { ctk.sval = "--"; skipch(); }
303 else if (mCurCh == '&' && mNextCh == '&') { ctk.sval = "&&"; skipch(); }
304 else if (mCurCh == '|' && mNextCh == '|') { ctk.sval = "||"; skipch(); }
305 else if (mCurCh == '+' && mNextCh == '=') { ctk.sval = "+="; skipch(); }
306 else if (mCurCh == '-' && mNextCh == '=') { ctk.sval = "-="; skipch(); }
307 else if (mCurCh == '*' && mNextCh == '=') { ctk.sval = "*="; skipch(); }
308 else if (mCurCh == '/' && mNextCh == '=') { ctk.sval = "/="; skipch(); }
309 else if (mCurCh == '%' && mNextCh == '=') { ctk.sval = "%="; skipch(); }
310 else if (mCurCh == '&' && mNextCh == '=') { ctk.sval = "&="; skipch(); }
311 else if (mCurCh == '|' && mNextCh == '=') { ctk.sval = "|="; skipch(); }
312 else if (mCurCh == '^' && mNextCh == '=') { ctk.sval = "^="; skipch(); }
313 else if (mCurCh == '<' && mNextCh == '<') { ctk.sval = "<<"; skipch(); }
314 else if (mCurCh == '>' && mNextCh == '>') { ctk.sval = ">>"; skipch(); }
315 else if (mCurCh == '.' && mNextCh == '.') {
316 skipch(); // first dot
317 if (mNextCh == '.') { ctk.sval = "..."; skipch(); } else ctk.sval = "..";
318 } else ctk.sval ~= mCurCh;
319 skipch();
322 void nextToken () {
323 readToken(mCurToken);
326 void setup () {
327 mCurLoc.line = 1;
328 mCurLoc.col = 1;
329 mCurLoc.fname = filename;
330 mCurCh = getch();
331 mNextCh = (mCurCh ? getch() : '\0');
334 protected:
335 static struct SavedState {
336 Loc mCurLoc;
337 char mNextCh;
338 char mCurCh;
339 Token mCurToken;
341 this (ParserBase pr) nothrow @safe @nogc { saveFrom(pr); }
343 void saveFrom (ParserBase pr) nothrow @safe @nogc {
344 if (pr !is null) {
345 this.mCurLoc = pr.mCurLoc;
346 this.mNextCh = pr.mNextCh;
347 this.mCurCh = pr.mCurCh;
348 this.mCurToken = pr.mCurToken;
352 void restoreTo (ParserBase pr) nothrow @safe @nogc {
353 if (pr !is null) {
354 pr.mCurLoc = this.mCurLoc;
355 pr.mNextCh = this.mNextCh;
356 pr.mCurCh = this.mCurCh;
357 pr.mCurToken = this.mCurToken;
362 public:
363 // first token is NOT read after creation
364 this () { setup(); }
366 abstract @property string filename () nothrow @safe;
368 final @property ref Token tk () nothrow @safe @nogc { pragma(inline, true); return mCurToken; }
370 // at the location of the current token
371 final void error (string amsg, string file=__FILE__, usize line=__LINE__) {
372 throw new CompilerError(mCurToken.loc, amsg, file, line);
373 assert(0); // "noreturn" hack
376 final void errorAt() (in auto ref Loc aloc, string amsg, string file=__FILE__, usize line=__LINE__) {
377 throw new CompilerError(aloc, amsg, file, line);
378 assert(0); // "noreturn" hack
381 final void skipch () {
382 if (mCurCh == '\n') { mCurLoc.col = 1; ++mCurLoc.line; } else ++mCurLoc.col;
383 mCurCh = mNextCh;
384 if (mNextCh != 0) mNextCh = getch();
387 final void skipSpaces () {
388 while (mCurCh) {
389 // single-line comment
390 if (mCurCh == '/' && mNextCh == '/') {
391 while (mCurCh && mCurCh != '\n') skipch();
392 continue;
394 // multiline comment
395 if (mCurCh == '/' && mNextCh == '*') {
396 skipch();
397 skipch();
398 while (mCurCh) {
399 if (mCurCh == '*' && mNextCh == '/') {
400 skipch();
401 skipch();
402 break;
404 skipch();
406 continue;
408 // multiline nested comment
409 if (mCurCh == '/' && mNextCh == '+') {
410 skipch();
411 skipch();
412 int level = 1;
413 while (mCurCh) {
414 if (mCurCh == '+' && mNextCh == '/') {
415 skipch();
416 skipch();
417 if (--level == 0) break;
418 continue;
420 if (mCurCh == '/' && mNextCh == '+') {
421 skipch();
422 skipch();
423 ++level;
424 continue;
426 skipch();
428 continue;
430 if (mCurCh > ' ') break;
431 skipch();
435 final void next() () { nextToken(); }
439 // ////////////////////////////////////////////////////////////////////////// //
440 public final class FileParser : ParserBase {
441 private:
442 VFile fl;
443 string curfname = null;
445 private:
446 static struct MySave {
447 SavedState st;
448 VFile fl;
451 MySave[] fstack;
453 private:
454 void push () {
455 if (fstack.length >= 32) error("too many nested includes");
456 fstack.length += 1;
457 fstack[$-1].st.saveFrom(this);
458 fstack[$-1].fl = fl;
461 void pop () {
462 if (fstack.length == 0) error("inclide stack underflow");
463 fl = fstack[$-1].fl;
464 fstack[$-1].st.restoreTo(this);
465 fstack.length -= 1;
466 fstack.assumeSafeAppend;
467 curfname = null;
470 protected:
471 override char getch () {
472 char res;
473 if (fl.rawRead((&res)[0..1]).length != 0) {
474 if (res == 0) res = ' ';
475 } else {
476 res = 0;
478 return res;
481 private:
482 void delegate (const(char)[] fname) onInclude;
484 public:
485 // first token is NOT read after creation
486 this (VFile afl, void delegate (const(char)[] fname) aOnInclude=null) {
487 fl = afl;
488 super();
489 onInclude = aOnInclude;
490 if (onInclude !is null && fl.isOpen) onInclude(fl.name);
493 override @property string filename () nothrow @safe {
494 if (curfname is null && fl.isOpen) curfname = fl.name.idup;
495 return curfname;
498 override void nextToken () {
499 for (;;) {
500 super.nextToken();
501 if (mCurToken.empty) {
502 // eof, pop file
503 if (fstack.length == 0) return;
504 pop();
505 } else if (mCurToken.delim("#")) {
506 super.nextToken();
507 if (mCurToken.empty) error("parser directive expected");
508 if (!mCurToken.id("include")) error("inknown compiler directive");
509 super.nextToken();
510 if (!mCurToken.str) error("string expected");
511 string fname = mCurToken.sval;
512 if (fname.length == 0) error("empty file name");
513 string oldname = fl.name.idup;
514 auto lslash = oldname.lastIndexOf('/');
515 oldname = oldname[0..lslash+1];
516 oldname ~= fname;
517 auto nfl = VFile(oldname);
518 push();
519 fl = nfl;
520 if (onInclude !is null) onInclude(fl.name);
521 curfname = null;
522 setup();
523 } else {
524 return;
531 // ////////////////////////////////////////////////////////////////////////// //
532 // VIRTUAL MACHINE
534 enum OpCode {
535 Nop, // 3 bytes: payload
536 CheckStack, // u16: number of stack slots this function is using
537 // literals
538 LitNU, // dest; followed by Nop payload, unsigned
539 LitNS, // dest; followed by Nop payload, signed
540 LitU16, // dest; U16 literal
541 LitS16, // dest; S16 literal
542 Mov, // dest, op0 as src
543 Inc, // dest, op0 -- dest = op0+1
544 Dec, // dest, op0 -- dest = op0-1
545 // integer math (dest, op0, op1)
546 Add,
547 Sub,
548 Mul,
549 Div,
550 Mod,
551 Shl,
552 Shr,
553 BitAnd,
554 BitOr,
555 BitXor,
556 // integer comparisons
557 ILess,
558 IGreat,
559 ILessEqu,
560 IGreatEqu,
561 // string comparisons
562 SLess,
563 SGreat,
564 SLessEqu,
565 SGreatEqu,
566 // the following are ok for any types
567 Equ,
568 NotEqu,
569 // logical operators
570 LogAnd,
571 LogOr,
572 // one-op math
573 IntAbs, // dest, op0
574 // branching
575 Jump, // s16: offset (from next opcode)
576 JTrue, // src: slot; s16: offset (from next opcode)
577 JFalse, // src: slot; s16: offset (from next opcode)
578 // unary logical
579 LogNot, // dest, op0
580 LogBNorm, // dest, op0 -- normalize boolean value
581 // globals
582 GLoad, // dest, 2 bytes: index (offset in global area)
583 GStore, // src, 2 bytes: index (offset in global area)
584 // arguments
585 ALoad, // dest (slot), src (argnum)
586 AStore, // dest (argnum), src (slot)
587 ACount, // dest -- push number of arguments
588 // fields
589 FLoad, // dest (slot), actid, fldidx -- load field value to dest
590 FStore, // src (slot), actid, fldidx -- store field value from src
591 // strings
592 StrLen, // dest, src (string idx)
593 StrLoadChar, // dest, src (string idx), strofs
594 // arrays
595 Bounds, // ofs (slot), u16: maxidx
596 LArrClear, // dest is first slot; u16 is cell count
597 GArrLoad, // dest, globofs (slot), cellofs (slot): [dest] = gvars[[op0]+[op1]]
598 GArrStore, // src, globofs (slot), cellofs (slot): gvars[[op0]+[op1]] = [src]
599 LArrLoad, // dest, bpofs (slot), cellofs (slot): [dest] = [op0+[op1]]
600 LArrStore, // src, bpofs (slot), cellofs (slot): [op0+[op1]] = [src]
601 // pointers
602 // pointer type encoded in bits 30-31:
603 // 0: pointer to stack slot (from 0)
604 // 1: pointer to global
605 // 2: pointer to field (bits 0-13: field index; bits 14-29: actor index; so max field index is 16383)
606 // 3: reserved
607 PtrLoad, // dest, ptr, ofs
608 PtrStore, // src, ptr, ofs
609 // create pointers to various things
610 MakeGPtr, // dest, slot: index -- make pointer to global
611 MakeAPtr, // dest, slot: argnum -- make pointer to argument
612 MakeLPtr, // dest, slot -- make pointer to local (to slot)
613 MakeFPtr, // dest, actidslot, fldofs -- make pointer to field
614 // subroutines
615 Ret, // slot 0 is result
616 Call, // vm call; addrslot; argslot; argcount
618 Invalid,
620 static assert(OpCode.Nop == 0);
623 align(1) struct VMOp {
624 align(1):
625 public:
626 uint instr;
628 public nothrow @safe @nogc:
629 this (uint ainstr) pure {
630 pragma(inline, true);
631 instr = ainstr;
634 this (OpCode opc, ubyte d=0, ubyte op0=0, ubyte op1=0) pure {
635 pragma(inline, true);
636 instr = (cast(uint)opc)|(d<<8)|(op0<<16)|(op1<<24);
639 void setS16 (short v) {
640 pragma(inline, true);
641 instr = (instr&0xffff)|((cast(uint)v)<<16);
644 void setU16 (ushort v) {
645 pragma(inline, true);
646 instr = (instr&0xffff)|((cast(uint)v)<<16);
649 static VMOp makeU16 (OpCode opc, ubyte d, ushort v) {
650 pragma(inline, true);
651 return VMOp((cast(uint)opc)|(d<<8)|((cast(uint)v)<<16));
654 static VMOp makeS16 (OpCode opc, ubyte d, short v) {
655 pragma(inline, true);
656 return VMOp((cast(uint)opc)|(d<<8)|((cast(uint)v)<<16));
659 static VMOp makeIVal (OpCode opc, int ival) pure nothrow @safe @nogc {
660 pragma(inline, true);
661 if (((ival>>24)&0xff) != 0 && ((ival>>24)&0xff) != 0xff) assert(0, "integer value out of range");
662 return VMOp((cast(uint)opc)|(ival<<8));
665 const pure:
666 @property OpCode opcode () { pragma(inline, true); return ((instr&0xff) < OpCode.Invalid ? cast(OpCode)(instr&0xff) : OpCode.Invalid); }
668 @property ubyte dest () { pragma(inline, true); return (instr>>8)&0xff; }
669 @property ubyte src () { pragma(inline, true); return (instr>>8)&0xff; }
670 @property ubyte op0 () { pragma(inline, true); return (instr>>16)&0xff; }
671 @property ubyte op1 () { pragma(inline, true); return (instr>>24)&0xff; }
672 @property int ival () { pragma(inline, true); return cast(int)((instr>>8)|(instr&0x8000_0000u ? 0xff00_0000u : 0)); }
673 @property uint uval () { pragma(inline, true); return cast(uint)(instr>>8); }
674 @property ushort u16 () { pragma(inline, true); return cast(ushort)(instr>>16); }
675 @property short s16 () { pragma(inline, true); return cast(short)(instr>>16); }
679 string decodeSlot (int si) {
680 import std.format : format;
681 if (si < 0 || si > 255) return "R%d".format(si);
682 for (Scope sc = curscope; sc !is null; sc = sc.parent) {
683 foreach (Variable var; sc.vars) {
684 if (!var.global && var.idx == si) return "[%s]".format(var.name);
687 return "r%d".format(si);
690 string decodeArgSlot (int si) {
691 import std.format : format;
692 if (si < 0 || si > 255) return "R%d".format(si);
693 int asi = -(si+1);
694 for (Scope sc = curscope; sc !is null; sc = sc.parent) {
695 foreach (Variable var; sc.vars) {
696 if (!var.global && var.idx == asi) return "[%s]".format(var.name);
699 return "r%d".format(si);
703 string decodeOpcode (int pc, VMOp v0, VMOp v1=VMOp(0)) {
704 import std.format : format;
705 auto opc = v0.opcode;
706 if (opc == OpCode.Invalid) return "<invalid>";
707 final switch (opc) {
708 case OpCode.Nop: return "Nop (%d)".format(v0.ival);
709 case OpCode.CheckStack: return "CheckStack (%d)".format(v0.u16);
710 case OpCode.LitNU: return "LitNU %s,%u".format(decodeSlot(v0.dest), v1.uval);
711 case OpCode.LitNS: return "LitNS %s,%d".format(decodeSlot(v0.dest), v1.ival);
712 case OpCode.LitU16: return "LitU16 %s,%u".format(decodeSlot(v0.dest), v0.u16);
713 case OpCode.LitS16: return "LitS16 %s,%d".format(decodeSlot(v0.dest), v0.s16);
714 case OpCode.Mov:
715 case OpCode.Inc:
716 case OpCode.Dec:
717 return "%s %s,%s".format(opc, decodeSlot(v0.dest), decodeSlot(v0.op0));
718 case OpCode.Add:
719 case OpCode.Sub:
720 case OpCode.Mul:
721 case OpCode.Div:
722 case OpCode.Mod:
723 case OpCode.Shl:
724 case OpCode.Shr:
725 case OpCode.BitAnd:
726 case OpCode.BitOr:
727 case OpCode.BitXor:
728 case OpCode.ILess:
729 case OpCode.IGreat:
730 case OpCode.ILessEqu:
731 case OpCode.IGreatEqu:
732 case OpCode.SLess:
733 case OpCode.SGreat:
734 case OpCode.SLessEqu:
735 case OpCode.SGreatEqu:
736 case OpCode.Equ:
737 case OpCode.NotEqu:
738 case OpCode.LogAnd:
739 case OpCode.LogOr:
740 return "%s %s,%s,%s".format(opc, decodeSlot(v0.dest), decodeSlot(v0.op0), decodeSlot(v0.op1));
741 case OpCode.IntAbs:
742 return "%s %s,%s".format(opc, decodeSlot(v0.dest), decodeSlot(v0.op0));
743 case OpCode.Jump: // s16: offset (from next opcode)
744 return "Jump %04X".format(pc+1+v0.s16);
745 case OpCode.JTrue: // src: slot; s16: offset (from next opcode)
746 case OpCode.JFalse: // src: slot; s16: offset (from next opcode)
747 return "%s %s,%04X".format(opc, decodeSlot(v0.src), pc+1+v0.s16);
748 case OpCode.LogNot: return "%s %s,%s".format(opc, decodeSlot(v0.dest), decodeSlot(v0.op0));
749 case OpCode.LogBNorm: return "%s %s,%s".format(opc, decodeSlot(v0.dest), decodeSlot(v0.op0));
750 case OpCode.GLoad: return "GLoad %s,[%u]".format(decodeSlot(v0.dest), v0.u16);
751 case OpCode.GStore: return "GStore %s,[%u]".format(decodeSlot(v0.src), v0.u16);
752 case OpCode.ALoad: return "ALoad %s,%s".format(decodeSlot(v0.dest), v0.op0);
753 case OpCode.AStore: return "AStore %s,%s".format(decodeArgSlot(v0.dest), v0.op0);
754 case OpCode.ACount: return "ACount %s".format(decodeSlot(v0.dest));
755 case OpCode.FLoad: return "FLoad %s,%s,%s".format(decodeSlot(v0.dest), decodeSlot(v0.op0), decodeSlot(v0.op1));
756 case OpCode.FStore: return "FStore %s,%s,%s".format(decodeSlot(v0.src), decodeSlot(v0.op0), decodeSlot(v0.op1));
757 case OpCode.StrLen: return "StrLen %s,%s".format(decodeSlot(v0.dest), decodeSlot(v0.op0));
758 case OpCode.StrLoadChar: return "StrLoadChar %s,%s,%s".format(decodeSlot(v0.dest), decodeSlot(v0.op0), decodeSlot(v0.op1));
759 case OpCode.Bounds: return "Bounds %s,%u".format(decodeSlot(v0.dest), v0.u16);
760 case OpCode.LArrClear: return "LArrClear %s,%u".format(decodeSlot(v0.dest), v0.u16);
761 case OpCode.GArrLoad:
762 case OpCode.GArrStore:
763 case OpCode.LArrLoad:
764 case OpCode.LArrStore:
765 case OpCode.PtrLoad:
766 case OpCode.PtrStore:
767 return "%s %s,%s,%s".format(opc, decodeSlot(v0.dest), decodeSlot(v0.op0), decodeSlot(v0.op1));
768 case OpCode.MakeGPtr:
769 case OpCode.MakeAPtr:
770 case OpCode.MakeLPtr:
771 return "%s %s,%s".format(opc, decodeSlot(v0.dest), decodeSlot(v0.op0));
772 case OpCode.MakeFPtr:
773 return "%s %s,%s,%s".format(opc, decodeSlot(v0.dest), decodeSlot(v0.op0), decodeSlot(v0.op1));
774 case OpCode.Ret: return "Ret";
775 case OpCode.Call: return "Call %s,%s,%u".format(decodeSlot(v0.src), decodeSlot(v0.op0), v0.op1);
776 case OpCode.Invalid: return "<invalid>";
778 assert(0);
782 // ////////////////////////////////////////////////////////////////////////// //
783 struct BuiltinDg {
784 string name;
785 int delegate (in ref BuiltinDg bdg, int argslot, int argc) handler;
786 TypeFunc type; // so we can finish builtin registration later
789 __gshared BuiltinDg[] builtinlist;
792 import std.traits;
795 TypeFunc buildFuncType(bool allowvararg, bool abortonerror, RT, A...) () {
796 auto tf = new TypeFunc();
797 static if (is(RT == void)) tf.aux = typeVoid;
798 else static if (is(Unqual!RT == int)) tf.aux = typeInt;
799 else static if (is(Unqual!RT == bool)) tf.aux = typeBool;
800 else static if (is(RT == ActorId)) tf.aux = typeActor;
801 else static if (is(Unqual!RT == enum)) {
802 bool error = true;
803 enum enumname = (Unqual!RT).stringof;
804 if (auto etp = enumname in gtypes) {
805 if (auto tp = cast(TypeEnum)(*etp)) {
806 if (tp.name == enumname) {
807 tf.aux = tp;
808 error = false;
812 if (error) {
813 //static if (abortonerror) static assert(0, "invalid builtin return type: "~enumname); else return null;
814 if (abortonerror) assert(0, "invalid builtin return type: "~enumname); else return null;
816 } else { static if (abortonerror) static assert(0, "invalid builtin return type: "~RT.stringof); else return null; }
817 foreach (immutable idx, immutable ptype; A) {
818 static if (is(Unqual!ptype == int)) tf.args ~= TypeFunc.Arg(typeInt);
819 else static if (is(Unqual!ptype == bool)) tf.args ~= TypeFunc.Arg(typeBool);
820 else static if (is(ptype == ActorId)) tf.args ~= TypeFunc.Arg(typeActor);
821 else static if (is(ptype == string) || is(ptype == const(char)[])) tf.args ~= TypeFunc.Arg(typeStr);
822 else static if (is(Unqual!ptype == enum)) {
823 bool error = true;
824 enum enumname = (Unqual!ptype).stringof;
825 if (auto etp = enumname in gtypes) {
826 if (auto tp = cast(TypeEnum)(*etp)) {
827 if (tp.name == enumname) {
828 tf.args ~= TypeFunc.Arg(tp);
829 error = false;
833 if (error) {
834 static if (abortonerror) assert(0, "invalid argument type: "~enumname); else return null;
836 } else static if (allowvararg && is(ptype == const(int)[])) {
837 static if (idx != A.length-1) { static if (abortonerror) static assert(0, "'vargs' must be last"); else return null; }
838 tf.hasrest = true;
839 } else {
840 static if (abortonerror) static assert(0, "invalid argument type: "~ptype.stringof); else return null;
843 return tf;
847 TypeFunc buildFuncTypeFromDg(DG, bool allowvararg=false, bool abortonerror=true) (scope DG dg=null) if (isCallable!DG) {
848 return buildFuncType!(allowvararg, abortonerror, ReturnType!DG, Parameters!DG);
852 // fix builtin index (create new empty builtin if necessary)
853 private int fixBuiltin (string fullname, TypeFunc ft) {
854 assert(ft !is null);
855 assert(ft.isFunc);
856 assert(fullname.length);
857 if (builtinlist.length == 0) {
858 // dummy builtin
859 builtinlist.length = 1;
860 } else {
861 // find existing builtin
862 foreach (immutable bidx, ref BuiltinDg bdg; builtinlist) {
863 //stderr.writeln("looking for '", fullname, "'; bdg=", bdg);
864 if (fullname != bdg.name) continue;
865 if (bdg.type != ft) continue;
866 // i found her!
867 return -cast(int)bidx;
870 // create new one
871 if (builtinlist.length > short.max) assert(0, "too many builtins");
872 int bidx = cast(int)builtinlist.length;
873 builtinlist.length += 1;
874 builtinlist[bidx].name = fullname;
875 builtinlist[bidx].type = ft;
876 builtinlist[bidx].handler = delegate (in ref BuiltinDg bdg, int argslot, int argc) {
877 throw new Exception("builtin '"~bdg.name~"' is not defined");
879 return -bidx;
883 // no string returns yet (we need string GC)
884 public void mesRegisterBuiltin(T:const(char)[], DG) (T name, DG dg) if (isCallable!DG) {
885 assert(dg !is null);
887 int fnidx = -1;
888 auto tf = buildFuncTypeFromDg!(DG, true, true);
889 alias RT = ReturnType!DG;
890 alias pars = Parameters!DG;
892 if (auto fp = name in funclist) {
893 // find function overload
894 foreach (/*immutable ovidx,*/ ref fni; fp.ovloads) {
895 TypeFunc ovt = fni.type;
896 if (ovt == tf) {
897 int idx = fni.pc;
898 if (idx >= 0) throw new Exception("cannot overload non-builtin '"~name.idup~"'");
899 idx = -idx;
900 assert(idx >= 0 && idx < builtinlist.length);
901 fnidx = idx;
902 break;
907 // if nothing was found, this in "inb4 builtin"
908 if (fnidx == -1) {
909 // create new builtin record
910 if (builtinlist.length > short.max) assert(0, "too many builtins");
911 fnidx = cast(int)builtinlist.length;
912 builtinlist.length += 1;
913 static if (is(T == string)) builtinlist[fnidx].name = name; else builtinlist[fnidx].name = name.idup;
914 builtinlist[fnidx].type = tf;
917 builtinlist[fnidx].handler = delegate (in ref BuiltinDg bdg, int argslot, int argc) {
918 pars args;
919 foreach (immutable idx, immutable ptype; pars) {
920 static if (is(Unqual!ptype == int)) {
921 args[idx] = (idx < argc ? vmstack[argslot+argc-idx-1] : 0);
922 } else static if (is(Unqual!ptype == bool)) {
923 args[idx] = (idx < argc ? (vmstack[argslot+argc-idx-1] != 0) : false);
924 } else static if (is(ptype == ActorId)) {
925 args[idx] = ActorId(idx < argc ? vmstack[argslot+argc-idx-1] : 0);
926 } else static if (is(ptype == string) || is(ptype == const(char)[])) {
927 args[idx] = (idx < argc ? strlist[vmstack[argslot+argc-idx-1]] : null);
928 } else static if (is(Unqual!ptype == enum)) {
929 args[idx] = (idx < argc ? cast(ptype)vmstack[argslot+argc-idx-1] : ptype.min);
930 } else static if (is(ptype == const(int)[])) {
931 static assert(idx == pars.length-1, "'vargs' must be last");
932 args[idx] = vmstack[argslot..argslot+argc];
933 } else {
934 static assert(0, "invalid argument type: "~ptype.stringof);
937 static if (is(RT == void)) {
938 dg(args);
939 return 0;
940 } else {
941 auto res = dg(args);
942 static if (is(Unqual!RT == int)) return res;
943 else static if (is(Unqual!RT == bool)) return (res ? 1 : 0);
944 else static if (is(RT == ActorId)) return res.id;
945 else static if (is(Unqual!RT == enum)) {
946 return cast(RT)res;
947 } else static assert(0, "invalid builtin return type: "~RT.stringof);
953 // ////////////////////////////////////////////////////////////////////////// //
954 __gshared Token[] cursource; // first pass will collect all tokens here
955 __gshared int curtokidx;
956 __gshared Token emptytoken;
958 ref const(Token) token () nothrow @trusted @nogc { pragma(inline, true); return (curtokidx >= 0 && curtokidx < cursource.length ? cursource.ptr[curtokidx] : emptytoken); }
959 void skipToken () nothrow @trusted @nogc { if (curtokidx < cursource.length) ++curtokidx; }
961 void forgetSource () {
962 delete cursource;
963 curtokidx = 0;
967 public void compileError (string msg, string file=__FILE__, usize line=__LINE__) {
968 if (cursource.length == 0) throw new CompilerError(Loc.init, msg, file, line);
969 if (curtokidx >= 0 && curtokidx < cursource.length) throw new CompilerError(token.loc, msg, file, line);
970 throw new CompilerError(cursource[$-1].loc, msg, file, line);
971 assert(0);
974 public void compileErrorAt() (in auto ref Loc aloc, string msg, string file=__FILE__, usize line=__LINE__) {
975 throw new CompilerError(aloc, msg, file, line);
976 assert(0);
980 // ////////////////////////////////////////////////////////////////////////// //
981 __gshared int[] vmglobals;
982 __gshared VMOp[] vmcode;
983 __gshared int[65536] vmstack;
984 __gshared int vmbp, vmargc;
985 struct RStackItem {
986 uint pc; // uint.max: no more
987 int bp, argc; // top arg will contain result
989 __gshared RStackItem[32768] vmrstack;
990 __gshared int vmrsp;
991 // vmbp points to the first slot *after* all args; this is `result` slot
994 // ////////////////////////////////////////////////////////////////////////// //
995 __gshared TypeBase[] vatypes;
997 int putVAType (TypeBase t) {
998 assert(t !is null);
999 if (vatypes.length == 0) vatypes.reserve(128);
1000 foreach (immutable idx, TypeBase vt; vatypes) if (vt is t) return cast(int)idx;
1001 vatypes ~= t;
1002 return cast(int)vatypes.length-1;
1006 // can return `null`
1007 public int mesVAGetArgCount (const(int)[] vargs) nothrow @trusted @nogc { pragma(inline, true); return cast(int)vargs.length/2; }
1010 public TypeBase mesVAGetArgType (const(int)[] vargs, int aidx) nothrow @trusted @nogc {
1011 if (aidx < 0 || aidx >= vargs.length/2) return null;
1012 int t = vargs[$-aidx*2-1];
1013 return (t >= 0 && t < vatypes.length ? vatypes[t] : null);
1017 public VT mesVAGetArgValue(VT) (const(int)[] vargs, int aidx) nothrow @trusted
1018 if (is(VT == bool) || is(VT == int) || is(VT == string) || is(VT == enum) || is(VT == ActorId))
1020 static if (is(VT == enum)) enum DefVal = VT.min; else enum DefVal = VT.init;
1021 if (aidx < 0 || aidx >= vargs.length/2) return DefVal;
1022 // type
1023 int t = vargs[$-aidx*2-1];
1024 if (t < 0 || t >= vatypes.length) return DefVal;
1025 TypeBase tp = vatypes[t];
1026 if (tp.isVoid) return VT.init;
1027 // value
1028 int v = vargs[$-aidx*2-2];
1029 // convert
1030 if (tp.isBool) {
1031 static if (is(VT == bool)) return (v != 0);
1032 else static if (is(VT == int)) return v;
1033 else static if (is(VT == string)) return (v ? "true" : "false");
1034 else static if (is(VT == enum)) return VT.min;
1035 else static if (is(VT == ActorId)) return ActorId.init;
1036 else static assert(0, "wtf?!");
1037 } else if (tp.isInt) {
1038 static if (is(VT == bool)) return (v != 0);
1039 else static if (is(VT == int)) return v;
1040 else static if (is(VT == string)) { import std.conv; return v.to!string; }
1041 else static if (is(VT == enum)) {
1042 foreach (string mname; __traits(allMembers, VT)) {
1043 if (v == __traits(getMember, VT, mname)) return __traits(getMember, VT, mname);
1045 } else static if (is(VT == ActorId)) return ActorId.init;
1046 else static assert(0, "wtf?!");
1047 } else if (tp.isStr) {
1048 static if (is(VT == bool)) return (v >= 0 && v < strlist.length ? (strlist[v].length != 0) : false);
1049 else static if (is(VT == int)) return v;
1050 else static if (is(VT == string)) return (v >= 0 && v < strlist.length ? strlist[v] : null);
1051 else static if (is(VT == enum)) {
1052 if (v >= 0 && v < strlist.length) {
1053 string s = strlist[v];
1054 foreach (string mname; __traits(allMembers, VT)) {
1055 if (s == __traits(getMember, VT, mname)) return __traits(getMember, VT, mname);
1058 return VT.min;
1059 } else static if (is(VT == ActorId)) return ActorId.init;
1060 else static assert(0, "wtf?!");
1061 } else if (tp.isActor) {
1062 static if (is(VT == bool)) return ActorId(v).alive;
1063 else static if (is(VT == int)) return v;
1064 else static if (is(VT == string)) return "Actor";
1065 else static if (is(VT == enum)) return VT.min;
1066 else static if (is(VT == ActorId)) return ActorId(v);
1067 else static assert(0, "wtf?!");
1068 } else if (tp.isNull) {
1069 static if (is(VT == bool)) return false;
1070 else static if (is(VT == int)) return 0;
1071 else static if (is(VT == string)) return "null";
1072 else static if (is(VT == enum)) return VT.min;
1073 else static if (is(VT == ActorId)) return ActorId.init;
1074 else static assert(0, "wtf?!");
1075 } else if (tp.isEnum) {
1076 static if (is(VT == bool)) return (v != 0);
1077 else static if (is(VT == int)) return v;
1078 else static if (is(VT == string)) {
1079 auto et = cast(TypeEnum)tp;
1080 foreach (const ref kv; et.members.byKeyValue) if (kv.value == v) return kv.key;
1081 return "???";
1082 } else static if (is(VT == enum)) {
1083 auto et = cast(TypeEnum)tp;
1084 if (et.name == (Unqual!VT).stringof) {
1085 foreach (const ref kv; et.members.byKeyValue) {
1086 if (kv.value == v) {
1087 foreach (string mname; __traits(allMembers, VT)) {
1088 if (kv.key == __traits(getMember, VT, mname)) return __traits(getMember, VT, mname);
1090 return VT.min;
1094 return VT.min;
1095 } else static if (is(VT == ActorId)) return ActorId.init;
1096 else static assert(0, "wtf?!");
1098 return DefVal;
1102 // ////////////////////////////////////////////////////////////////////////// //
1103 public int vmExec(A...) (uint pc, A args) {
1104 if (vmbp >= vmstack.length || vmstack.length-vmbp < args.length+1) throw new Exception("out of vm stack for args");
1105 auto bp = vmbp;
1106 foreach_reverse (immutable idx, ref arg; args) {
1107 alias ptype = typeof(arg);
1108 static if (is(ptype == byte) || is(ptype == ubyte) ||
1109 is(ptype == short) || is(ptype == ushort) ||
1110 is(ptype == int))
1112 vmstack[bp++] = arg;
1113 } else static if (is(ptype == bool)) {
1114 vmstack[bp++] = (arg ? 1 : 0);
1115 } else static if (is(ptype == ActorId)) {
1116 vmstack[bp++] = arg.id;
1117 } else static if (is(ptype : const(char)[])) {
1118 static assert(0, "no string args yet");
1119 } else static if (is(Unqual!ptype == enum)) {
1120 bool error = true;
1121 enum enumname = (Unqual!ptype).stringof;
1122 if (auto etp = enumname in gtypes) {
1123 if (auto tp = cast(TypeEnum)(*etp)) {
1124 if (tp.name == enumname) {
1125 if (cast(int)arg >= ptype.min && cast(int)arg <= ptype.max) {
1126 vmstack[bp++] = cast(int)arg;
1127 error = false;
1132 if (error) throw new Exception("unknown enum '"~enumname~"'");
1133 } else {
1134 static assert(0, "invalid argument type: "~ptype.stringof);
1137 auto oldargc = vmargc;
1138 auto oldbp = vmbp;
1139 scope(exit) { vmbp = oldbp; vmargc = oldargc; }
1140 vmargc = cast(int)args.length;
1141 vmbp += vmargc;
1142 return vmExecIntr(pc);
1146 private int vmExecIntr (uint pc) {
1147 if (vmrsp >= vmrstack.length-2) assert(0, "internal vm error");
1148 auto oldrsp = vmrsp;
1149 vmrstack.ptr[vmrsp++] = RStackItem(uint.max, vmbp, vmargc);
1150 scope(exit) {
1151 //stderr.writeln("vmrsp=", vmrsp, "; oldrsp=", oldrsp);
1152 if (vmrsp < oldrsp) assert(0, "internal vm error");
1153 if (vmrsp > oldrsp) {
1154 vmrsp = oldrsp;
1155 vmbp = vmrstack.ptr[vmrsp+1].bp;
1156 vmargc = vmrstack.ptr[vmrsp+1].argc;
1159 if (vmbp >= vmstack.length || vmstack.length-vmbp < 256+3) throw new Exception("vm stack overflow");
1160 //vmargc = 0;
1161 while (pc < vmcode.length) {
1162 auto op = vmcode.ptr[pc++];
1163 version(mesdbg_dump_dump_execution) {
1164 if (!MESDisableDumps) stderr.writefln("%04X: %s bp=%d; argc=%d", pc-1, decodeOpcode(pc-1, op), vmbp, vmargc);
1166 final switch (op.opcode) {
1167 case OpCode.Nop: break;
1168 case OpCode.CheckStack:
1169 if (vmbp >= vmstack.length || vmstack.length-vmbp < op.u16) throw new Exception("vm stack overflow");
1170 break;
1171 case OpCode.LitNU: // next Nop is value
1172 if (pc >= vmcode.length || vmcode.ptr[pc].opcode != OpCode.Nop) assert(0, "internal vm error");
1173 vmstack.ptr[vmbp+op.dest] = vmcode.ptr[pc].uval;
1174 ++pc;
1175 break;
1176 case OpCode.LitNS: // next Nop is value
1177 if (pc >= vmcode.length || vmcode.ptr[pc].opcode != OpCode.Nop) assert(0, "internal vm error");
1178 vmstack.ptr[vmbp+op.dest] = vmcode.ptr[pc].ival;
1179 ++pc;
1180 break;
1181 case OpCode.LitU16: vmstack.ptr[vmbp+op.dest] = op.u16; break;
1182 case OpCode.LitS16: vmstack.ptr[vmbp+op.dest] = op.s16; break;
1183 case OpCode.Mov: vmstack.ptr[vmbp+op.dest] = vmstack.ptr[vmbp+op.op0]; break;
1184 case OpCode.Inc: vmstack.ptr[vmbp+op.dest] = vmstack.ptr[vmbp+op.op0]+1; break;
1185 case OpCode.Dec: vmstack.ptr[vmbp+op.dest] = vmstack.ptr[vmbp+op.op0]-1; break;
1186 case OpCode.Add: vmstack.ptr[vmbp+op.dest] = vmstack.ptr[vmbp+op.op0]+vmstack.ptr[vmbp+op.op1]; break;
1187 case OpCode.Sub: vmstack.ptr[vmbp+op.dest] = vmstack.ptr[vmbp+op.op0]-vmstack.ptr[vmbp+op.op1]; break;
1188 case OpCode.Mul: vmstack.ptr[vmbp+op.dest] = vmstack.ptr[vmbp+op.op0]*vmstack.ptr[vmbp+op.op1]; break;
1189 case OpCode.Div:
1190 if (vmstack.ptr[vmbp+op.op1] == 0) throw new Exception("division by zero");
1191 vmstack.ptr[vmbp+op.dest] = vmstack.ptr[vmbp+op.op0]/vmstack.ptr[vmbp+op.op1];
1192 break;
1193 case OpCode.Mod:
1194 if (vmstack.ptr[vmbp+op.op1] == 0) throw new Exception("division by zero");
1195 vmstack.ptr[vmbp+op.dest] = vmstack.ptr[vmbp+op.op0]%vmstack.ptr[vmbp+op.op1];
1196 break;
1197 case OpCode.Shl:
1198 int sft = vmstack.ptr[vmbp+op.op1];
1199 if (sft < 0 || sft > 31) throw new Exception("invalid shift range");
1200 vmstack.ptr[vmbp+op.dest] = vmstack.ptr[vmbp+op.op0]<<sft;
1201 break;
1202 case OpCode.Shr:
1203 int sft = vmstack.ptr[vmbp+op.op1];
1204 if (sft < 0 || sft > 31) throw new Exception("invalid shift range");
1205 vmstack.ptr[vmbp+op.dest] = vmstack.ptr[vmbp+op.op0]>>sft;
1206 break;
1207 case OpCode.BitAnd:
1208 vmstack.ptr[vmbp+op.dest] = vmstack.ptr[vmbp+op.op0]&vmstack.ptr[vmbp+op.op1];
1209 break;
1210 case OpCode.BitOr:
1211 vmstack.ptr[vmbp+op.dest] = vmstack.ptr[vmbp+op.op0]|vmstack.ptr[vmbp+op.op1];
1212 break;
1213 case OpCode.BitXor:
1214 vmstack.ptr[vmbp+op.dest] = vmstack.ptr[vmbp+op.op0]^vmstack.ptr[vmbp+op.op1];
1215 break;
1216 case OpCode.ILess:
1217 vmstack.ptr[vmbp+op.dest] = (vmstack.ptr[vmbp+op.op0] < vmstack.ptr[vmbp+op.op1] ? 1 : 0);
1218 break;
1219 case OpCode.IGreat:
1220 vmstack.ptr[vmbp+op.dest] = (vmstack.ptr[vmbp+op.op0] > vmstack.ptr[vmbp+op.op1] ? 1 : 0);
1221 break;
1222 case OpCode.ILessEqu:
1223 vmstack.ptr[vmbp+op.dest] = (vmstack.ptr[vmbp+op.op0] <= vmstack.ptr[vmbp+op.op1] ? 1 : 0);
1224 break;
1225 case OpCode.IGreatEqu:
1226 vmstack.ptr[vmbp+op.dest] = (vmstack.ptr[vmbp+op.op0] >= vmstack.ptr[vmbp+op.op1] ? 1 : 0);
1227 break;
1228 case OpCode.SLess:
1229 vmstack.ptr[vmbp+op.dest] = (strlist.ptr[vmstack.ptr[vmbp+op.op0]] < strlist.ptr[vmstack.ptr[vmbp+op.op1]] ? 1 : 0);
1230 break;
1231 case OpCode.SGreat:
1232 vmstack.ptr[vmbp+op.dest] = (strlist.ptr[vmstack.ptr[vmbp+op.op0]] > strlist.ptr[vmstack.ptr[vmbp+op.op1]] ? 1 : 0);
1233 break;
1234 case OpCode.SLessEqu:
1235 vmstack.ptr[vmbp+op.dest] = (strlist.ptr[vmstack.ptr[vmbp+op.op0]] <= strlist.ptr[vmstack.ptr[vmbp+op.op1]] ? 1 : 0);
1236 break;
1237 case OpCode.SGreatEqu:
1238 vmstack.ptr[vmbp+op.dest] = (strlist.ptr[vmstack.ptr[vmbp+op.op0]] >= strlist.ptr[vmstack.ptr[vmbp+op.op1]] ? 1 : 0);
1239 break;
1240 case OpCode.Equ:
1241 vmstack.ptr[vmbp+op.dest] = (vmstack.ptr[vmbp+op.op0] == vmstack.ptr[vmbp+op.op1] ? 1 : 0);
1242 //version(mesdbg_dump_dump_execution) stderr.writefln(" dest=%u; op0=%u; op1=%u; [%u]=%u", op.dest, op.op0, op.op1, op.dest, vmstack.ptr[vmbp+op.dest]);
1243 break;
1244 case OpCode.NotEqu:
1245 vmstack.ptr[vmbp+op.dest] = (vmstack.ptr[vmbp+op.op0] != vmstack.ptr[vmbp+op.op1] ? 1 : 0);
1246 break;
1247 case OpCode.LogAnd:
1248 vmstack.ptr[vmbp+op.dest] = (vmstack.ptr[vmbp+op.op0] && vmstack.ptr[vmbp+op.op1] ? 1 : 0);
1249 break;
1250 case OpCode.LogOr:
1251 vmstack.ptr[vmbp+op.dest] = (vmstack.ptr[vmbp+op.op0] || vmstack.ptr[vmbp+op.op1] ? 1 : 0);
1252 break;
1253 case OpCode.IntAbs:
1254 int v = vmstack.ptr[vmbp+op.op0];
1255 if (v == 0x8000_0000) throw new Exception("cannot make positive integer number");
1256 vmstack.ptr[vmbp+op.dest] = (v < 0 ? -v : v);
1257 break;
1258 case OpCode.Jump: // s16: offset (from next opcode)
1259 pc += op.s16;
1260 break;
1261 case OpCode.JTrue: // src: slot; s16: offset (from next opcode)
1262 if (vmstack.ptr[vmbp+op.src]) pc += op.s16;
1263 break;
1264 case OpCode.JFalse: // src: slot; s16: offset (from next opcode)
1265 //version(mesdbg_dump_dump_execution) stderr.writefln(" [%u]=%d", op.src, vmstack.ptr[vmbp+op.src]);
1266 if (!vmstack.ptr[vmbp+op.src]) pc += op.s16;
1267 break;
1268 case OpCode.LogNot:
1269 vmstack.ptr[vmbp+op.dest] = (vmstack.ptr[vmbp+op.op0] ? 0 : 1);
1270 break;
1271 case OpCode.LogBNorm:
1272 vmstack.ptr[vmbp+op.dest] = (vmstack.ptr[vmbp+op.op0] ? 1 : 0);
1273 break;
1274 case OpCode.GLoad: // dest, 2 bytes: index (offset in global area)
1275 vmstack.ptr[vmbp+op.dest] = vmglobals.ptr[op.u16];
1276 break;
1277 case OpCode.GStore: // src, 2 bytes: index (offset in global area)
1278 vmglobals.ptr[op.u16] = vmstack.ptr[vmbp+op.src];
1279 break;
1280 case OpCode.ALoad:
1281 vmstack.ptr[vmbp+op.dest] = vmstack.ptr[vmbp-1-op.op0];
1282 break;
1283 case OpCode.AStore:
1284 vmstack.ptr[vmbp-1-op.dest] = vmstack.ptr[vmbp+op.op0];
1285 break;
1286 case OpCode.ACount:
1287 vmstack.ptr[vmbp+op.dest] = vmargc;
1288 break;
1289 case OpCode.FLoad: // dest (slot), actid, fldidx -- load field value to dest
1290 auto aid = ActorId(cast(uint)vmstack.ptr[vmbp+op.op0]);
1291 if (aid.alive) {
1292 //vmstack.ptr[vmbp+op.dest] = actors[aid.id&0xffff][vmstack.ptr[vmbp+op.op1]];
1293 vmstack.ptr[vmbp+op.dest] = actorGetById(aid).ptr[vmstack.ptr[vmbp+op.op1]];
1294 } else {
1295 // it is not a bug to access dead actor
1296 vmstack.ptr[vmbp+op.dest] = 0;
1298 break;
1299 case OpCode.FStore: // src (slot), actid, fldidx -- store field value from src
1300 auto aid = ActorId(cast(uint)vmstack.ptr[vmbp+op.op0]);
1301 // it is not a bug to access dead actor; just do nothing
1302 if (aid.alive) {
1303 actorGetById(aid).ptr[vmstack.ptr[vmbp+op.op1]] = vmstack.ptr[vmbp+op.src];
1305 break;
1306 case OpCode.StrLen: // dest, src (string idx)
1307 int sidx = vmstack.ptr[vmbp+op.op0];
1308 if (sidx >= 0 && sidx < strlist.length) {
1309 vmstack.ptr[vmbp+op.dest] = cast(int)strlist.ptr[sidx].length;
1310 } else {
1311 vmstack.ptr[vmbp+op.dest] = 0;
1313 break;
1314 case OpCode.StrLoadChar: // dest, src (string idx), strofs
1315 int sidx = vmstack.ptr[vmbp+op.op0];
1316 if (sidx >= 0 && sidx < strlist.length) {
1317 string s = strlist.ptr[sidx];
1318 int sofs = vmstack.ptr[vmbp+op.op1];
1319 vmstack.ptr[vmbp+op.dest] = (sofs >= 0 && sofs < s.length ? cast(int)(s.ptr[sofs]) : 0);
1320 } else {
1321 vmstack.ptr[vmbp+op.dest] = 0;
1323 break;
1324 case OpCode.Bounds: // ofs (slot), u16: maxidx
1325 int aofs = vmstack.ptr[vmbp+op.src];
1326 if (aofs < 0 || aofs >= op.u16) {
1327 import std.conv : to;
1328 throw new Exception("array access out of bounds ("~aofs.to!string~")");
1330 break;
1331 case OpCode.LArrClear: // dest is first slot; u16 is cell count
1332 if (op.u16 > 0) {
1333 if (vmbp+op.dest+op.u16 > vmstack.length) assert(0, "internal codegen error");
1334 vmstack.ptr[vmbp+op.dest..vmbp+op.dest+op.u16] = 0;
1336 break;
1337 case OpCode.GArrLoad: // dest, globofs (slot), cellofs (slot): [dest] = gvars[[op0]+[op1]]
1338 int abase = vmstack.ptr[vmbp+op.op0];
1339 int aofs = vmstack.ptr[vmbp+op.op1];
1340 version(mes_paranoid_arrays) {
1341 if (aofs < 0 || aofs >= vmglobals.length || abase < 0 || abase >= vmglobals.length || aofs+abase >= vmglobals.length) {
1342 throw new Exception("array access out of bounds");
1345 vmstack.ptr[vmbp+op.dest] = vmglobals.ptr[abase+aofs];
1346 break;
1347 case OpCode.GArrStore: // src, globofs (slot), cellofs (slot): gvars[[op0]+[op1]] = [src]
1348 int abase = vmstack.ptr[vmbp+op.op0];
1349 int aofs = vmstack.ptr[vmbp+op.op1];
1350 version(mes_paranoid_arrays) {
1351 if (aofs < 0 || aofs >= vmglobals.length || abase < 0 || abase >= vmglobals.length || aofs+abase >= vmglobals.length) {
1352 throw new Exception("array access out of bounds");
1355 vmglobals.ptr[abase+aofs] = vmstack.ptr[vmbp+op.src];
1356 break;
1357 case OpCode.LArrLoad: // dest, bpofs (slot), cellofs (slot): [dest] = [op0+[op1]]
1358 int abase = vmbp+op.op0;
1359 int aofs = vmstack.ptr[vmbp+op.op1];
1360 version(mes_paranoid_arrays) {
1361 if (abase < 0 || abase >= vmstack.length || aofs < 0 || aofs >= vmstack.length || abase+aofs < 0 || abase+aofs >= vmstack.length) {
1362 throw new Exception("array access out of bounds");
1365 vmstack.ptr[vmbp+op.dest] = vmstack.ptr[abase+aofs];
1366 break;
1367 case OpCode.LArrStore: // src, bpofs (slot), cellofs (slot): [op0+[op1]] = [src]
1368 int abase = vmbp+op.op0;
1369 int aofs = vmstack.ptr[vmbp+op.op1];
1370 version(mes_paranoid_arrays) {
1371 if (abase < 0 || abase >= vmstack.length || aofs < 0 || aofs >= vmstack.length || abase+aofs < 0 || abase+aofs >= vmstack.length) {
1372 throw new Exception("array access out of bounds");
1375 vmstack.ptr[abase+aofs] = vmstack.ptr[vmbp+op.src];
1376 break;
1377 // pointer type encoded in bits 30-31:
1378 // 0: pointer to stack slot (from 0)
1379 // 1: pointer to global
1380 // 2: pointer to field (bits 0-13: field index; bits 14-29: actor index; so max field index is 16383)
1381 // 3: reserved
1382 //FIXME: REMOVE PASTA!
1383 case OpCode.PtrLoad: // dest, ptr, ofs
1384 int ofs = vmstack.ptr[vmbp+op.op1];
1385 if (ofs < short.min || ofs > short.max) assert(0, "internal compiler error");
1386 uint pval = cast(uint)vmstack.ptr[vmbp+op.op0];
1387 //stderr.writefln("pval=0x%08x; pv=%u; ofs=%d", pval, pval&0x3fff_ffff, ofs);
1388 int* xptr = null;
1389 final switch (pval>>30) {
1390 case 0: // pointer to stack slot
1391 if (pval >= vmstack.length || pval+ofs >= vmstack.length) assert(0, "internal compiler error");
1392 xptr = vmstack.ptr+pval+ofs;
1393 break;
1394 case 1:
1395 pval &= 0x3fff_ffff;
1396 if (pval+ofs >= vmglobals.length) assert(0, "internal compiler error");
1397 xptr = vmglobals.ptr+pval+ofs;
1398 break;
1399 case 2: // pointer to field (bits 0-13: field index; bits 14-29: actor index; so max field index is 16383)
1400 int aidx = (pval>>14)&0xffff;
1401 auto act = actorGetByIndex(aidx);
1402 if (act is null) assert(0, "internal compiler error");
1403 int fidx = (pval&0x3fff)+ofs;
1404 if (fidx < 0 || fidx >= act.length) assert(0, "internal compiler error");
1405 xptr = act.ptr+fidx;
1406 break;
1407 case 3: assert(0, "internal compiler error");
1409 vmstack.ptr[vmbp+op.dest] = *xptr;
1410 break;
1411 case OpCode.PtrStore: // src, ptr, ofs
1412 int ofs = vmstack.ptr[vmbp+op.op1];
1413 if (ofs < short.min || ofs > short.max) assert(0, "internal compiler error");
1414 uint pval = cast(uint)vmstack.ptr[vmbp+op.op0];
1415 int* xptr = null;
1416 final switch (pval>>30) {
1417 case 0: // pointer to stack slot
1418 if (pval >= vmstack.length || pval+ofs >= vmstack.length) assert(0, "internal compiler error");
1419 xptr = vmstack.ptr+pval+ofs;
1420 break;
1421 case 1:
1422 pval &= 0x3fff_ffff;
1423 if (pval+ofs >= vmglobals.length) assert(0, "internal compiler error");
1424 xptr = vmglobals.ptr+pval+ofs;
1425 break;
1426 case 2: // pointer to field (bits 0-13: field index; bits 14-29: actor index; so max field index is 16383)
1427 int aidx = (pval>>14)&0xffff;
1428 auto act = actorGetByIndex(aidx);
1429 if (act is null) assert(0, "internal compiler error");
1430 int fidx = (pval&0x3fff)+ofs;
1431 if (fidx < 0 || fidx >= act.length) assert(0, "internal compiler error");
1432 xptr = act.ptr+fidx;
1433 break;
1434 case 3: assert(0, "internal compiler error");
1436 *xptr = vmstack.ptr[vmbp+op.src];
1437 break;
1438 case OpCode.MakeGPtr: // dest, slot: index -- make pointer to global
1439 int gidx = vmstack.ptr[vmbp+op.op0];
1440 if (gidx < 0 || gidx >= vmglobals.length) assert(0, "internal compiler error");
1441 vmstack.ptr[vmbp+op.dest] = gidx|0x4000_0000;
1442 break;
1443 case OpCode.MakeAPtr: // dest, slot: argnum -- make pointer to argument
1444 assert(0);
1445 case OpCode.MakeLPtr: // dest, slot -- make pointer to local (to slot)
1446 int gidx = vmbp+op.op0;
1447 if (gidx < 0 || gidx >= vmstack.length) assert(0, "internal compiler error");
1448 vmstack.ptr[vmbp+op.dest] = gidx;
1449 break;
1450 case OpCode.MakeFPtr: // dest, actidslot, fldofs -- make pointer to field
1451 auto aid = ActorId(vmstack.ptr[vmbp+op.op0]);
1452 if (!aid.alive) throw new Exception("cannot create pointer to dead object");
1453 int fidx = vmstack.ptr[vmbp+op.op1];
1454 if (fidx < 0 || fidx > 0x3fff) assert(0, "internal compiler error");
1455 vmstack.ptr[vmbp+op.dest] = ((aid.id&0xffff)<<14)|fidx|0x8000_0000;
1456 break;
1457 case OpCode.Ret:
1458 // function result is in r0; copy it to the top argument slot, if there is any
1459 if (vmrsp == oldrsp) assert(0, "wtf?!");
1460 immutable int res = vmstack.ptr[vmbp];
1461 if (vmargc) vmstack.ptr[vmbp-vmargc] = res;
1462 --vmrsp;
1463 vmbp = vmrstack.ptr[vmrsp].bp;
1464 vmargc = vmrstack.ptr[vmrsp].argc;
1465 if (vmrstack.ptr[vmrsp].pc == uint.max) return res;
1466 pc = vmrstack.ptr[vmrsp].pc;
1467 break;
1468 case OpCode.Call: // addrslot; argslot; argcount
1469 if (vmrsp+1 > vmrstack.length) throw new Exception("vm call stack overflow");
1470 int newpc = cast(int)vmstack.ptr[vmbp+op.src];
1471 // check function type
1472 if (newpc < 0) {
1473 // builtin
1474 newpc = -newpc;
1475 assert(newpc > 0);
1476 if (newpc >= builtinlist.length) assert(0, "internal vm error");
1477 vmstack.ptr[vmbp+op.op0] = builtinlist.ptr[newpc].handler(builtinlist.ptr[newpc], vmbp+op.op0, op.op1);
1478 } else if (newpc > 0) {
1479 // normal
1480 vmrstack.ptr[vmrsp++] = RStackItem(pc, vmbp, vmargc);
1481 pc = vmstack.ptr[vmbp+op.src];
1482 vmargc = op.op1;
1483 vmbp += op.op0+vmargc;
1484 } else {
1485 throw new Exception("cannot call null function");
1487 break;
1488 case OpCode.Invalid:
1489 assert(0, "internal vm error");
1492 assert(0, "internal vm error");
1496 // ////////////////////////////////////////////////////////////////////////// //
1497 //mesdbg_dump_generated_code
1498 void emitCode (VMOp opc, size_t line=__LINE__) {
1499 vmcode ~= opc;
1500 version(mesdbg_dump_generated_code) {
1501 if (!MESDisableDumps) {
1502 stderr.writefln("%04X: %s (%u) %s", cast(uint)vmcode.length-1, decodeOpcode(cast(int)vmcode.length-1, opc), line, token.loc.toString);
1507 void emitILit (ubyte dest, int ival) {
1508 if (ival >= 0 && ival <= ushort.max) {
1509 vmcode ~= VMOp((OpCode.LitU16)|(dest<<8)|(ival<<16));
1510 version(mesdbg_dump_generated_code) {
1511 if (!MESDisableDumps) stderr.writefln("%04X: %s", cast(uint)vmcode.length-1, decodeOpcode(cast(int)vmcode.length-1, vmcode[$-1]));
1513 return;
1515 if (ival >= short.min && ival <= short.max) {
1516 vmcode ~= VMOp((OpCode.LitS16)|(dest<<8)|(ival<<16));
1517 version(mesdbg_dump_generated_code) {
1518 if (!MESDisableDumps) stderr.writefln("%04X: %s", cast(uint)vmcode.length-1, decodeOpcode(cast(int)vmcode.length-1, vmcode[$-1]));
1520 return;
1522 // fits into 3 unsigned bytes?
1523 if (ival >= 0 && ival <= 0x00ff_ffff) {
1524 vmcode ~= VMOp((OpCode.LitNU)|(dest<<8));
1525 } else {
1526 // fits into 3 signed bytes?
1527 if (((ival>>24)&0xff) != 0 && ((ival>>24)&0xff) != 0xff) assert(0, "integer value out of range");
1528 if ((ival>>23) != 0 && (ival>>23) != -1) assert(0, "integer value out of range");
1529 vmcode ~= VMOp((OpCode.LitNS)|(dest<<8));
1531 vmcode ~= VMOp((OpCode.Nop)|(ival<<8));
1532 version(mesdbg_dump_generated_code) {
1533 if (!MESDisableDumps) stderr.writefln("%04X: %s", cast(uint)vmcode.length-2, decodeOpcode(cast(int)vmcode.length-2, vmcode[$-2]), decodeOpcode(cast(int)vmcode.length-1, vmcode[$-1]));
1538 // ////////////////////////////////////////////////////////////////////////// //
1539 // TYPE DEFINITIONS
1541 public class TypeBase {
1542 public:
1543 this () {}
1545 @property bool isVoid () const nothrow @safe @nogc { return false; }
1546 @property bool isBool () const nothrow @safe @nogc { return false; }
1547 @property bool isInt () const nothrow @safe @nogc { return false; }
1548 @property bool isStr () const nothrow @safe @nogc { return false; }
1549 @property bool isArray () const nothrow @safe @nogc { return false; }
1550 @property bool isStruct () const nothrow @safe @nogc { return false; }
1551 @property bool isActor () const nothrow @safe @nogc { return false; }
1552 @property bool isEnum () const nothrow @safe @nogc { return false; }
1553 @property bool isFunc () const nothrow @safe @nogc { return false; }
1554 @property bool isMethod () const nothrow @safe @nogc { return false; }
1555 @property bool isPointer () const nothrow @safe @nogc { return false; }
1556 @property bool isFuncPtr () const nothrow @safe @nogc { return false; }
1557 @property bool isNull () const nothrow @safe @nogc { return false; }
1558 @property bool isCallable () const nothrow @safe @nogc { return false; }
1559 @property uint cellSize () const nothrow @safe @nogc { return 0; } // size in cells
1560 @property TypeBase base () nothrow @safe @nogc { return this; } // for arrays, structs and pointers
1562 abstract string toPrettyString () const;
1564 final @property bool isGoodForBool () const nothrow @safe @nogc {
1565 pragma(inline, true);
1566 return (isBool || isInt || isStr || isActor);
1569 override bool opEquals (Object o) const nothrow @safe @nogc {
1570 if (auto t = cast(TypeBase)o) return same(t); else return false;
1573 final bool same (in TypeBase t1) const nothrow @safe @nogc {
1574 alias t0 = this;
1575 if (t0 is null || t1 is null) return false;
1576 if (t0 is t1) return true;
1577 // functions?
1578 if (t0.isFunc || t1.isFunc) {
1579 if (auto f0 = cast(const(TypeFunc))t0) {
1580 if (auto f1 = cast(const(TypeFunc))t1) {
1581 if (f0.args.length == f1.args.length && f0.hasrest == f1.hasrest) {
1582 if (!f0.aux.same(f1.aux)) return false;
1583 foreach (immutable idx, const ref TypeFunc.Arg a0; f0.args) {
1584 if (!a0.type.same(f1.args[idx].type)) return false;
1586 return true;
1590 return false; // something is very wrong here
1592 // pointers?
1593 if (t0.isPointer || t1.isPointer) {
1594 if (auto p0 = cast(const(TypePointer))t0) {
1595 if (auto p1 = cast(const(TypePointer))t1) {
1596 if (p0.isNull || p1.isNull) return true; // `null` is compatible with everything
1597 return p0.mBase.same(p1.mBase);
1600 return false; // something is very wrong here
1602 // enums?
1603 if (t0.isEnum || t1.isEnum) {
1604 if (auto e0 = cast(const(TypeEnum))t0) {
1605 if (auto e1 = cast(const(TypeEnum))t1) {
1606 return (e0.name == e1.name); // && e0.constBase.same(e1.base));
1609 return false; // something is very wrong here
1611 // arrays?
1612 if (t0.isArray || t1.isArray) {
1613 if (auto a0 = cast(const(TypeArray))t0) {
1614 if (auto a1 = cast(const(TypeArray))t1) {
1615 return (a0.mSize == a1.mSize && a0.mBase.same(a1.mBase));
1618 return false; // something is very wrong here
1620 // oops
1621 return false;
1624 // `tt` `hasrest` is ignored
1625 bool callCompatible (TypeBase tt, bool checkaux=true) nothrow @safe @nogc { return false; }
1626 final callCompatibleNoAux (TypeBase tt) nothrow @safe @nogc { return callCompatible(tt, false); }
1630 // ////////////////////////////////////////////////////////////////////////// //
1631 public class TypePointer : TypeBase {
1632 private:
1633 TypeBase mBase;
1635 public:
1636 //this () { mBase = typeVoid; }
1637 this (TypeBase abase) { mBase = abase; }
1638 override @property bool isPointer () const nothrow @safe @nogc { return true; }
1639 override @property bool isFuncPtr () const nothrow @safe @nogc { return mBase.isFunc; }
1640 override @property bool isCallable () const nothrow @safe @nogc { return (mBase.isFunc && mBase.isCallable); } // pointer to pointer is not callable
1641 override @property uint cellSize () const nothrow @safe @nogc { return 1; }
1642 override @property TypeBase base () nothrow @safe @nogc { return mBase; }
1644 override string toPrettyString () const {
1645 //if (isFuncPtr) return mBase.toPrettyString;
1646 return "ptr to "~mBase.toPrettyString;
1651 public class TypeNull : TypePointer {
1652 public:
1653 this () { super(typeVoid); }
1654 override @property bool isNull () const nothrow @safe @nogc { return true; }
1655 override @property TypeBase base () nothrow @safe @nogc { return this; }
1657 override string toPrettyString () const { return "null"; }
1661 // ////////////////////////////////////////////////////////////////////////// //
1662 public class TypeArray : TypeBase {
1663 private:
1664 TypeBase mBase;
1665 int mSize;
1667 public:
1668 this (TypeBase abase, int asize) { mBase = abase; if (asize < 0) assert(0, "invalid size"); mSize = asize; }
1669 override @property bool isArray () const nothrow @safe @nogc { return true; }
1670 override @property TypeBase base () nothrow @safe @nogc { return mBase; }
1671 override @property uint cellSize () const nothrow @safe @nogc { return mSize*mBase.cellSize; } // size in cells
1672 final @property int size () const nothrow @safe @nogc { pragma(inline, true); return mSize; }
1673 // not a property -- by design
1674 final void setSize (int newsz) nothrow @safe @nogc { pragma(inline, true); if (newsz < 0) assert(0, "invalid size"); mSize = newsz; }
1676 override string toPrettyString () const {
1677 import std.format : format;
1678 return "%s[%d]".format(mBase.toPrettyString, mSize);
1683 // ////////////////////////////////////////////////////////////////////////// //
1684 // typed enum
1685 public class TypeEnum : TypeBase {
1686 private:
1687 string mName;
1688 TypeBase mBase;
1689 int[string] members; // lucky me, strings can be encoded as integers too
1690 int mDefValue; // default value
1692 public:
1693 this (string aname, TypeBase abase) { mName = aname; mBase = abase; }
1694 override @property bool isEnum () const nothrow @safe @nogc { return true; }
1695 override @property TypeBase base () nothrow @safe @nogc { return mBase; }
1696 override @property uint cellSize () const nothrow @safe @nogc { return mBase.cellSize; }
1697 final @property string name () const nothrow @safe @nogc { pragma(inline, true); return mName; }
1698 final int defval () const nothrow @safe @nogc { pragma(inline, true); return mDefValue; }
1699 final int minval () const nothrow @trusted @nogc {
1700 if (members.length == 0) return mDefValue; // just in case
1701 int res = int.max;
1702 foreach (immutable int v; members.byValue) if (res > v) res = v;
1703 return res;
1705 final int maxval () const nothrow @trusted @nogc {
1706 if (members.length == 0) return mDefValue; // just in case
1707 int res = int.min;
1708 foreach (immutable int v; members.byValue) if (res < v) res = v;
1709 return res;
1711 // returns `null` on missing member
1712 final string getMember (int val) const nothrow @trusted @nogc {
1713 foreach (const ref kv; members.byKeyValue) {
1714 if (kv.value == val) return (kv.key.length ? kv.key : ""); // just in case
1716 return null;
1719 override string toPrettyString () const { return (mName.length ? mName : "__anonymous_enum"); }
1723 // ////////////////////////////////////////////////////////////////////////// //
1724 // struct
1725 public class TypeStruct : TypeBase {
1726 public:
1727 static struct Member {
1728 string name;
1729 TypeBase type;
1730 int ofs;
1731 Loc defloc;
1733 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (type !is null); }
1736 private:
1737 string mName;
1738 Member[] members;
1739 int mFullSize;
1741 public:
1742 this (string aname) { mName = aname; }
1743 override @property bool isStruct () const nothrow @safe @nogc { return true; }
1744 override @property uint cellSize () const nothrow @safe @nogc { return mFullSize; }
1745 final @property string name () const nothrow @safe @nogc { pragma(inline, true); return mName; }
1747 void appendMember() (string aname, TypeBase atype, in auto ref Loc adefloc) {
1748 assert(atype !is null);
1749 foreach (ref Member m; members) {
1750 if (m.name == aname) compileErrorAt(adefloc, "duplicate member name '"~aname~"'");
1752 members ~= Member(aname, atype, mFullSize, adefloc);
1753 mFullSize += atype.cellSize;
1756 // check `.valid` on return to see if this is valid member
1757 final Member getMember (const(char)[] aname) nothrow @safe @nogc {
1758 foreach (ref Member m; members) if (m.name == aname) return m;
1759 return Member.init;
1762 override string toPrettyString () const { return (mName.length ? mName : "__anonymous_struct"); }
1766 // ////////////////////////////////////////////////////////////////////////// //
1767 public class TypeVoid : TypeBase {
1768 public:
1769 this () {}
1770 override @property bool isVoid () const nothrow @safe @nogc { return true; }
1772 override string toPrettyString () const { return "void"; }
1775 public class TypeBool : TypeBase {
1776 public:
1777 this () {}
1778 override @property bool isBool () const nothrow @safe @nogc { return true; }
1779 override @property uint cellSize () const nothrow @safe @nogc { return 1; }
1781 override string toPrettyString () const { return "bool"; }
1784 public class TypeInt : TypeBase {
1785 public:
1786 this () {}
1787 override @property bool isInt () const nothrow @safe @nogc { return true; }
1788 override @property uint cellSize () const nothrow @safe @nogc { return 1; }
1790 override string toPrettyString () const { return "int"; }
1793 public class TypeStr : TypeBase {
1794 public:
1795 this () {}
1796 override @property bool isStr () const nothrow @safe @nogc { return true; }
1797 override @property uint cellSize () const nothrow @safe @nogc { return 1; }
1799 override string toPrettyString () const { return "string"; }
1802 public class TypeActor : TypeBase {
1803 public:
1804 this () {}
1805 override @property bool isActor () const nothrow @safe @nogc { return true; }
1806 override @property uint cellSize () const nothrow @safe @nogc { return 1; }
1808 override string toPrettyString () const { return "Actor"; }
1812 //WARNING: don't change this vars!
1813 public __gshared TypeBase typeVoid;
1814 public __gshared TypeBase typeBool;
1815 public __gshared TypeBase typeInt;
1816 public __gshared TypeBase typeStr;
1817 public __gshared TypeBase typeActor;
1818 public __gshared TypeBase typeNull;
1821 shared static this () {
1822 typeVoid = new TypeVoid();
1823 typeBool = new TypeBool();
1824 typeInt = new TypeInt();
1825 typeStr = new TypeStr();
1826 typeActor = new TypeActor();
1827 typeNull = new TypeNull();
1831 // ////////////////////////////////////////////////////////////////////////// //
1832 public class TypeFunc : TypeBase {
1833 public:
1834 static struct Arg {
1835 string name;
1836 TypeBase type;
1837 Value defval; // can be only literal of the corresponding type
1838 bool ptrref; // true: this argument is reference
1839 bool readonly;
1841 this (TypeBase t) pure nothrow @safe @nogc { pragma(inline, true); type = t; }
1842 this (string aname, TypeBase t, bool aref, bool ro=false) pure nothrow @safe @nogc { pragma(inline, true); name = aname; type = t; ptrref = aref; readonly = ro; }
1845 public:
1846 TypeBase aux; // return type
1847 Arg[] args;
1848 bool hasrest; // has '...'; rest args are in format: <type,value>*
1850 public:
1851 this () {}
1852 override @property bool isFunc () const nothrow @safe @nogc { return true; }
1853 override @property bool isCallable () const nothrow @safe @nogc { return true; }
1854 override @property uint cellSize () const nothrow @safe @nogc { return 1; } // for function vars
1856 // method is anything with `Actor` as the first argument
1857 override @property bool isMethod () const nothrow @safe @nogc { return (args.length > 0 && args[0].type.isActor); }
1859 static final bool compatible (TypeBase t0, TypeBase t1) nothrow @safe @nogc {
1860 if (t0 is t1) return true;
1861 if (t0 is null || t1 is null) return false;
1862 if (t0.same(t1)) return true;
1863 // if t0 is null, nothing is compatible (FIXME?)
1864 if (t0.isNull) return false;
1865 // t0 is function type?
1866 if (t0.isFunc) {
1867 // t1 should be either null, or compatible function pointer
1868 return (t1.isNull || (t1.isFuncPtr && t1.base.same(t0)));
1870 // t0 is funcptr?
1871 if (t0.isFuncPtr) {
1872 // t1 should be either null, or compatible function pointer
1873 return (t1.isNull || (t1.isFuncPtr && t1.base.same(t0.base)));
1875 // t0 is pointer or actor?
1876 if (t0.isPointer || t0.isActor) return t1.isNull; // t1 should be null
1877 // alas
1878 return false;
1881 // `tt` `hasrest` is ignored
1882 override bool callCompatible (TypeBase tt, bool checkaux=true) nothrow @safe @nogc {
1883 if (tt is null) return false;
1884 if (tt is this) return true;
1885 auto f1 = cast(TypeFunc)tt;
1886 if (!hasrest && f1.args.length > args.length) return false; // too many arguments
1887 if (checkaux) {
1888 if (!compatible(aux, f1.aux)) return false;
1890 // check mandatory arguments
1891 assert(hasrest || f1.args.length <= args.length);
1892 foreach (immutable idx, ref TypeFunc.Arg a1; f1.args) {
1893 if (idx >= args.length) {
1894 if (!hasrest) return false;
1895 break;
1897 if (!compatible(args[idx].type, a1.type)) return false;
1899 // if we have more agruments, they should have default values
1900 if (f1.args.length < args.length) {
1901 foreach (ref TypeFunc.Arg a0; args[f1.args.length..$]) {
1902 if (!a0.defval.literal) return false;
1904 //{ import core.stdc.stdio; stderr.fprintf("hasrest=%u; f1l=%u; al=%u\n", cast(uint)hasrest, cast(uint)f1.args.length, cast(uint)args.length); }
1905 return true;
1907 // all mandatory args are ok; we can call this when:
1908 return (hasrest || args.length == f1.args.length);
1911 override string toPrettyString () const {
1912 string res;
1913 if (aux !is null) res ~= aux.toPrettyString; else res ~= "auto";
1914 res ~= " function (";
1915 foreach (immutable idx, const ref arg; args) {
1916 if (idx != 0) res ~= ", ";
1917 if (arg.readonly) res ~= "in ";
1918 if (arg.ptrref) res ~= "ref ";
1919 res ~= arg.type.toPrettyString;
1920 res ~= " ";
1921 if (arg.name.length) res ~= arg.name; else res ~= "_";
1923 res ~= ")";
1924 return res;
1929 // ////////////////////////////////////////////////////////////////////////// //
1930 struct VMCallFixup {
1931 Variable var;
1932 int ovidx; // overload index
1933 int vmpc;
1934 Loc loc;
1935 @property TypeFunc type () nothrow @safe @nogc { pragma(inline, true); return var.ovloads[ovidx].type; }
1936 @property int pc () nothrow @safe @nogc { pragma(inline, true); return var.ovloads[ovidx].pc; }
1939 __gshared VMCallFixup[] vmcallfixes;
1942 // returns `true` if there are some fixups left
1943 //TODO: make this faster
1944 bool fixupCalls () {
1945 if (vmcallfixes.length == 0) return false;
1946 int dpos = 0;
1947 int cpos = 0;
1948 while (cpos < vmcallfixes.length) {
1949 auto fix = vmcallfixes.ptr+cpos;
1950 if (fix.var.ovloads[fix.ovidx].pc == 0) {
1951 //compileErrorAt(fix.loc, "undefined function call");
1952 // move this up if necessary
1953 assert(cpos >= dpos);
1954 if (cpos != dpos) vmcallfixes.ptr[dpos++] = *fix;
1955 } else {
1956 assert(vmcode[fix.vmpc].opcode == OpCode.LitU16);
1957 vmcode[fix.vmpc] = VMOp.makeU16(OpCode.LitU16, vmcode[fix.vmpc].dest, cast(ushort)fix.var.ovloads[fix.ovidx].pc);
1958 //stderr.writefln("fixup at %04X: %04X %s", fix.vmpc, fix.var.fnpc[fix.ovidx], fix.var.name);
1959 //stderr.writefln("%04X: %s", fix.vmpc, decodeOpcode(fix.vmpc, vmcode[fix.vmpc]));
1961 ++cpos;
1963 if (vmcallfixes.length != dpos) {
1964 vmcallfixes.length = dpos;
1965 vmcallfixes.assumeSafeAppend;
1967 return (dpos != 0);
1971 void fixupDump (VFile fo) {
1972 if (vmcallfixes.length == 0) return;
1973 foreach (ref fix; vmcallfixes) {
1974 fo.writeln("undefined call to '", fix.var.name, "' at ", fix.loc);
1979 // ////////////////////////////////////////////////////////////////////////// //
1980 private void unwindCodegen (int oldpc) {
1981 if (oldpc < 1 || oldpc > vmcode.length) assert(0, "internal compiler error");
1982 if (oldpc == vmcode.length) return; // nothing to do
1983 // remove fixups in invalid code area
1984 int dpos = 0;
1985 while (dpos < vmcallfixes.length && vmcallfixes[dpos].vmpc < oldpc) ++dpos;
1986 if (dpos < vmcallfixes.length) {
1987 // has something to remove
1988 assert(vmcallfixes[dpos].vmpc >= oldpc);
1989 int cpos = dpos+1;
1990 while (cpos < vmcallfixes.length) {
1991 if (vmcallfixes[cpos].vmpc < oldpc) {
1992 // shift this fixup (if necessary)
1993 if (cpos != dpos) {
1994 assert(cpos > dpos);
1995 vmcallfixes[dpos] = vmcallfixes[cpos];
1997 ++dpos;
1999 ++cpos;
2001 vmcallfixes.length = dpos;
2002 vmcallfixes.assumeSafeAppend;
2004 // remove generated code
2005 vmcode.length = oldpc;
2006 vmcode.assumeSafeAppend;
2010 // ////////////////////////////////////////////////////////////////////////// //
2011 struct JumpChain {
2012 int basepc = -1; // "base" PC (i.e. PC at the moment of `start*()` call
2013 int addr = -1;
2014 bool chain; // false: addr is real jump point, otherwise it is last chain member
2016 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (addr >= 0 && basepc > 0); }
2018 // this starts backward jump point (all jumps will be directed to current PC)
2019 static JumpChain startHere () nothrow @trusted @nogc {
2020 JumpChain res;
2021 assert(vmcode.length);
2022 res.basepc = res.addr = cast(int)vmcode.length;
2023 res.chain = false;
2024 return res;
2027 // this starts forward jump point (all jumps will be directed to some PC in the future)
2028 static JumpChain startForward () nothrow @trusted @nogc {
2029 JumpChain res;
2030 assert(vmcode.length);
2031 res.basepc = cast(int)vmcode.length;
2032 res.addr = 0; // end-of-chain
2033 res.chain = true;
2034 return res;
2037 // call this when you'll unwind codegen
2038 void resetAndClear () nothrow @safe @nogc {
2039 basepc = addr = -1;
2040 chain = false;
2043 // reset jump chain, unwind generated code
2044 void unwindToBase () {
2045 if (!valid) assert(0, "cannot unwind codegen with invalid jump chain");
2046 if (vmcode.length < basepc) assert(0, "internal compiler error");
2047 unwindCodegen(basepc);
2048 resetAndClear();
2051 // convert forward jump chain to backward (i.e. direct all forward jumps to the current PC)
2052 // won' reset jump chain (i.e. it can be used after this call)
2053 void setHere () nothrow @trusted @nogc {
2054 if (!valid) assert(0, "cannot do anything with invalid jump chain");
2055 if (chain) {
2056 assert(addr >= 0);
2057 int jaddr = cast(int)vmcode.length;
2058 while (addr != 0) {
2059 int aprev = vmcode[addr].u16;
2060 assert(vmcode[addr].opcode == OpCode.Jump || vmcode[addr].opcode == OpCode.JTrue || vmcode[addr].opcode == OpCode.JFalse);
2061 int ofs = jaddr-(addr+1); // jump offset is "jump pc + 1"
2062 vmcode[addr].setS16(cast(short)ofs); // jump offset is "jump pc + 1"
2063 addr = aprev;
2065 addr = jaddr;
2066 chain = false;
2067 } else {
2068 assert(basepc > 0);
2069 assert(addr > 0);
2073 // finish and reset jump chain; fill fix addresses for forward jumps
2074 void finishHere () nothrow @trusted @nogc {
2075 setHere();
2076 resetAndClear();
2079 // put new jump into chain
2080 void putJump (OpCode opc, ubyte src=0) {
2081 assert(opc == OpCode.Jump || opc == OpCode.JTrue || opc == OpCode.JFalse);
2082 if (chain) {
2083 // remember, generate fake
2084 assert(addr >= 0);
2085 auto naddr = cast(int)vmcode.length;
2086 emitCode(VMOp.makeU16(opc, src, cast(ushort)addr));
2087 addr = naddr;
2088 } else {
2089 // generate real jump
2090 assert(addr > 0);
2091 int ofs = addr-(cast(int)vmcode.length+1); // jump offset is "jump pc + 1"
2092 emitCode(VMOp.makeS16(opc, src, cast(short)ofs));
2098 // ////////////////////////////////////////////////////////////////////////// //
2099 static struct FuncInfo {
2100 TypeFunc type;
2101 int pc; // 0: not known yet; <0: builtin
2102 string name; // may NOT be `null`! fully qualified name (`smth.func` for `method(smth) func ()`)
2103 string actorid; // for `method(smth) func ()`
2104 string shortname; // short name (`func` for `method(smth) func ()`)
2105 Loc loc;
2106 bool hasBody;
2108 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (type !is null); }
2109 @property bool isMethod () const nothrow @safe @nogc { pragma(inline, true); return (type !is null && type.isMethod); }
2113 class Variable {
2114 enum Kind {
2115 Global,
2116 Local,
2117 Reference, // always `Local`, but holds a reference (currently valid only for arguments)
2120 int idx; // Local, and <0: argument
2121 Kind kind = Kind.Local;
2122 string name; // fully qualified
2123 TypeBase type; // has litle meaning for functions, 'cause real types are in overload set
2124 FuncInfo[] ovloads;
2125 Loc firstloc;
2126 bool readonly;
2128 final @property bool global () const pure nothrow @safe @nogc { pragma(inline, true); return (kind == Kind.Global); }
2129 final @property bool local () const pure nothrow @safe @nogc { pragma(inline, true); return (kind == Kind.Local); }
2130 final @property bool reference () const pure nothrow @safe @nogc { pragma(inline, true); return (kind == Kind.Reference); }
2132 enum NotFound = -1;
2133 enum Conflict = -2;
2135 // will set `targs.aux` on success
2136 int findOverload(bool showVariants) (TypeFunc targs) {
2137 if (targs is null) return NotFound;
2138 auto oldaux = targs.aux;
2139 // find "normal" call
2140 static if (showVariants) bool conflict = false;
2141 int normres = -1;
2142 foreach (immutable idx, ref FuncInfo fni; ovloads) {
2143 if (!fni.type.hasrest && fni.type.callCompatibleNoAux(targs)) {
2144 if (normres != -1) {
2145 targs.aux = oldaux;
2146 static if (showVariants) {
2147 if (!conflict) {
2148 compilerMessage("ERROR: conflicting overloads for '%s' found", name);
2149 compilerMessage(" %s", ovloads[normres].type.toPrettyString);
2151 compilerMessage(" %s", fni.type.toPrettyString);
2152 conflict = true;
2153 continue;
2154 } else {
2155 return Conflict;
2158 normres = cast(int)idx;
2159 targs.aux = fni.type.aux;
2162 static if (showVariants) { if (conflict) return Conflict; }
2163 if (normres != -1) return normres;
2164 // no "normal" function found, try varargs
2165 int restres = -1;
2166 foreach (immutable idx, ref FuncInfo fni; ovloads) {
2167 if (fni.type.hasrest && fni.type.callCompatibleNoAux(targs)) {
2168 if (restres != -1) {
2169 targs.aux = oldaux;
2170 static if (showVariants) {
2171 if (!conflict) {
2172 compilerMessage("ERROR: conflicting overloads for '%s' found", name);
2173 compilerMessage(" %s", ovloads[restres].type.toPrettyString);
2175 compilerMessage(" %s", fni.type.toPrettyString);
2176 conflict = true;
2177 continue;
2178 } else {
2179 return Conflict;
2182 restres = cast(int)idx;
2183 targs.aux = fni.type.aux;
2186 static if (showVariants) { if (conflict) return Conflict; }
2187 if (restres == -1) targs.aux = oldaux;
2188 return restres;
2193 class Constant {
2194 string name;
2195 TypeBase type;
2196 // for enums, `ival` and `sval` doesn't matter
2197 int ival;
2198 string sval;
2202 // ////////////////////////////////////////////////////////////////////////// //
2203 class Scope {
2204 public:
2205 Scope parent;
2206 Variable[] vars;
2207 string[string] aliases;
2210 __gshared Scope curscope = null;
2211 __gshared int[128] curslots; // usecount for stack slots; <0: not temp (won't be freed)
2212 __gshared FuncInfo curfunc;
2213 __gshared int curmaxreg = 0;
2215 __gshared JumpChain curcontpc; // current "continue" PC (<0: none)
2216 __gshared JumpChain curbreakpc; // current "break" PC (<0: none)
2218 __gshared Constant[string] gconsts; // global variables
2219 __gshared TypeBase[string] gtypes; // for type aliases (not yet) and typed enums
2220 __gshared Variable[string] globals; // global variables
2221 __gshared Variable[string] funclist; // all known functions, fully qualified names
2222 __gshared string[string] aliases;
2223 __gshared string[] strlist; // list of all strings (searching is slow, so what?); [0] is always empty string
2226 //FIXME: make this faster!
2227 bool isKnownActorId (const(char)[] aid) nothrow @trusted @nogc {
2228 if (aid.length == 0) return false;
2229 foreach (Variable v; funclist.byValue) {
2230 if (v.ovloads[0].actorid == aid) return true;
2232 return false;
2236 //FIXME: make this faster!
2237 Variable findFQMethod (const(char)[] aid, const(char)[] mname) nothrow @trusted @nogc {
2238 if (aid.length == 0 || mname.length == 0) return null;
2239 foreach (Variable v; funclist.byValue) {
2240 //stderr.writeln("looking for <", aid, "::", mname, ">: [", v.ovloads[0].actorid, "]:[", v.ovloads[0].shortname, "] -- [", v.ovloads[0].name, "]");
2241 if (v.ovloads[0].actorid == aid && v.ovloads[0].shortname == mname) return v;
2243 return null;
2247 // can return `null`
2248 Variable findFunction (Value val) {
2249 if (!val.valid) return null;
2250 if (!val.type.isFunc) return null;
2251 auto fp = val.varname in funclist;
2252 if (fp is null) return null;
2253 return *fp;
2257 shared static this () {
2258 strlist ~= ""; // predefined empty string
2262 // ////////////////////////////////////////////////////////////////////////// //
2263 void enterScope () {
2264 auto sc = new Scope();
2265 sc.parent = curscope;
2266 curscope = sc;
2270 void leaveScope () {
2271 if (curscope is null) assert(0, "internal compiler error");
2272 // free allocated vars
2273 foreach (Variable v; curscope.vars) {
2274 if (v.idx > 0 && v.type.cellSize > 0) {
2275 foreach (immutable cidx; 0..v.type.cellSize) {
2276 if (v.idx+cidx >= curslots.length) assert(0, "internal compiler error");
2277 if (curslots[v.idx+cidx] != -1) assert(0, "internal compiler error");
2278 curslots[v.idx+cidx] = 0;
2282 curscope = curscope.parent;
2286 Variable enterVar(T:const(char)[]) (in auto ref Loc loc, T name, TypeBase type) {
2287 assert(curscope !is null);
2288 assert(type !is null && !type.isVoid);
2289 if (type.isArray && type.cellSize == 0) assert(0, "internal compiler error");
2290 if (name.length == 0) compileErrorAt(loc, "cannot create nameless variable");
2291 for (Scope sc = curscope; sc !is null; sc = sc.parent) {
2292 foreach (Variable var; sc.vars) {
2293 if (name == var.name) {
2294 if (sc is curscope) {
2295 compileErrorAt(loc, "duplicate variable name: '"~name~"'");
2296 } else {
2297 compileErrorAt(loc, "variable '"~name~"' shadows another variable with the same name at "~var.firstloc.toString);
2302 auto var = new Variable();
2303 var.firstloc = loc;
2304 if (type.cellSize == 1) {
2305 var.idx = Slot.makeTemp();
2306 } else {
2307 var.idx = Slot.makeTempBlock(type.cellSize);
2309 curslots[var.idx..var.idx+type.cellSize] = -1; // mark as var
2310 var.kind = Variable.Kind.Local;
2311 static if (is(T == string)) var.name = name; else var.name = name.idup;
2312 var.type = type;
2313 curscope.vars ~= var;
2314 return var;
2318 Variable findLocalVar (const(char)[] name) {
2319 for (Scope sc = curscope; sc !is null; sc = sc.parent) {
2320 foreach (Variable var; sc.vars) if (name == var.name) return var;
2322 return null;
2326 Variable findGlobalVar (const(char)[] name) {
2327 if (auto gv = name in globals) return *gv;
2328 if (auto fp = name in funclist) return *fp;
2329 return null;
2333 Constant findConst (const(char)[] name) {
2334 if (auto cp = name in gconsts) return *cp;
2335 return null;
2339 // ////////////////////////////////////////////////////////////////////////// //
2340 // stack slot
2341 struct Slot {
2342 private:
2343 int ridx = -1;
2345 private:
2346 static ubyte allocTempSlot () {
2347 foreach (immutable idx, int sc; curslots[]) {
2348 if (sc == 0) {
2349 curslots[idx] = 1;
2350 if (curmaxreg < idx) curmaxreg = cast(int)idx;
2351 //{ import core.stdc.stdio; stderr.fprintf("allocTempSlot(%d): %d\n", idx, curslots[idx]); }
2352 return cast(ubyte)idx;
2355 compileError("out of stack slots");
2356 assert(0, "out of stack slots");
2359 static ubyte allocTempSlotBlock (int len) {
2360 if (len == 1) return allocTempSlot();
2361 if (len > 0 && len < curslots.length-1) {
2362 // check if we have empty block at the end
2363 int idx = cast(int)curslots.length-len;
2364 int n = 0;
2365 foreach (immutable int v; curslots[idx..idx+len]) n |= v;
2366 // no: alas, cannot allocate block
2367 if (n == 0) {
2368 // yes, there is empty block; now move it up
2369 while (idx > 0 && curslots[idx-1] == 0) --idx;
2370 assert(idx > 0); // slot 0 is always occupied
2371 assert(curslots[idx] == 0);
2372 curslots[idx] = 1;
2373 return cast(ubyte)idx;
2376 compileError("out of stack slots");
2377 assert(0, "out of stack slots");
2380 static void refSlot (int idx) nothrow @nogc {
2381 if (idx >= 0 && idx < curslots.length) {
2382 //{ import core.stdc.stdio; stderr.fprintf("refSlot(%d): %d\n", idx, curslots[idx]); }
2383 //if (curslots[idx] == 0) assert(0, "wtf?!");
2384 if (curslots[idx] >= 0) ++curslots[idx];
2388 static void unrefSlot (int idx) nothrow @nogc {
2389 if (idx >= 0 && idx < curslots.length) {
2390 //{ import core.stdc.stdio; stderr.fprintf("unrefSlot(%d): %d\n", idx, curslots[idx]); }
2391 if (curslots[idx] == 0) {
2392 { import core.stdc.stdio; stderr.fprintf("trying to free unoccupied slot #%d\n", idx); }
2393 assert(0, "wtf?!");
2395 if (curslots[idx] > 0) --curslots[idx];
2399 public:
2400 alias idx this;
2402 public:
2403 // direct ctor won't change refcount
2404 @disable this (int aidx);
2406 this (this) nothrow @nogc {
2407 pragma(inline, true);
2408 refSlot(ridx);
2411 ~this () nothrow @nogc {
2412 pragma(inline, true);
2413 unrefSlot(ridx);
2416 bool opCast(T) () const pure nothrow @safe @nogc if (is(T == bool)) {
2417 pragma(inline, true);
2418 return valid;
2421 void opAssign() (in auto ref Slot v) nothrow @nogc {
2422 // order matters!
2423 refSlot(v.ridx);
2424 unrefSlot(ridx);
2425 ridx = v.ridx;
2428 bool opEquals() (in auto ref Slot v) const pure nothrow @safe @nogc { pragma(inline, true); return (v.ridx == ridx); }
2430 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (ridx >= 0 && ridx < 255); }
2431 @property ubyte idx () const pure nothrow @safe @nogc { pragma(inline, true); return cast(ubyte)(ridx); }
2433 // won't unref slot
2434 void reset () nothrow @nogc {
2435 pragma(inline, true);
2436 ridx = -1;
2439 void clear () nothrow @nogc {
2440 pragma(inline, true);
2441 unrefSlot(ridx);
2442 ridx = -1;
2445 static Slot makeTemp () {
2446 Slot res;
2447 res.ridx = allocTempSlot();
2448 return res;
2451 static Slot makeTempBlock (int len) {
2452 Slot res;
2453 res.ridx = allocTempSlotBlock(len);
2454 return res;
2457 static Slot acquire (int aidx) nothrow @nogc {
2458 if (aidx < 0 || aidx > 255) assert(0, "internal slot manager error");
2459 refSlot(aidx);
2460 Slot res;
2461 res.ridx = aidx;
2462 return res;
2465 static Slot acquireNoRef (int aidx) nothrow @nogc {
2466 if (aidx < 0 || aidx > 255) assert(0, "internal slot manager error");
2467 if (curslots[aidx] == 0) assert(0, "internal slot manager error");
2468 Slot res;
2469 res.ridx = aidx;
2470 return res;
2475 // ////////////////////////////////////////////////////////////////////////// //
2476 //HACK: we need dynamically allocated values with deterministic destruction
2477 //WARNING: don't create cycle references!
2478 //NOTE: despite being called `*Ref`, this thing *copies* assigned `Value`
2479 struct ValueRef {
2480 private:
2481 static struct VPtr {
2482 Value v;
2483 int rc; // refcounter
2485 private:
2486 VPtr* vp;
2488 public:
2489 this() (auto ref Value av) nothrow {
2490 if (av.valid) {
2491 // copy it
2492 vp = new VPtr();
2493 vp.rc = 1;
2494 vp.v = av;
2498 this (ValueRef avr) nothrow {
2499 if (avr.vp) {
2500 vp = avr.vp;
2501 ++vp.rc;
2505 this (this) nothrow @trusted @nogc {
2506 pragma(inline, true);
2507 if (vp !is null) ++vp.rc;
2510 ~this () nothrow { clear(); }
2512 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (vp !is null); }
2513 @property ref Value value () nothrow @safe @nogc { pragma(inline, true); if (vp is null) assert(0, "oops"); return vp.v; }
2515 bool opCast(T) () const pure nothrow @safe @nogc if (is(T == bool)) {
2516 pragma(inline, true);
2517 return (vp !is null);
2520 void clear () nothrow {
2521 if (vp !is null) {
2522 if (--vp.rc == 0) {
2523 vp.v = Value.init; // free slots and other shit
2524 delete vp;
2526 vp = null; // just in case
2530 void opAssign() (auto ref Value av) nothrow {
2531 clear();
2532 if (av.valid) {
2533 // copy it
2534 vp = new VPtr();
2535 vp.rc = 1;
2536 vp.v = av;
2540 void opAssign (ValueRef avr) nothrow {
2541 if (avr.vp is vp) return; // nothing to do
2542 if (avr.vp !is null) ++avr.vp.rc;
2543 clear();
2544 vp = avr.vp;
2549 struct Value {
2550 public:
2551 enum Kind {
2552 Invalid,
2553 Global,
2554 Local,
2555 Argument,
2556 Literal,
2557 Stack,
2558 Field,
2561 public:
2562 TypeBase type;
2563 Kind kind = Kind.Invalid;
2564 string varname;
2565 int index; // global/fieldoffset/argumentidx (suitable for ALoad/AStore)
2566 Slot slot; // local/stack
2567 alias lit = index; // literal value
2568 Loc loc;
2569 ValueRef arridx; // array index is always int, we'll put it into a slot; invalid slot means no array offset
2570 int arrsize = -1; // this will be set in expression parser, and will be used to generate bound checking; -666: string indexing
2571 int elemsize; // for arrays
2572 ValueRef fldobj; // object (actor) for field
2573 bool ptrref; // `ref` for arguments
2574 int ptridx; // index for ref struct access
2575 bool readonly; // for arguments
2577 private:
2578 @property bool isStrIndexing () const pure nothrow @safe @nogc { pragma(inline, true); return (arrsize == -666 && arridx.valid); }
2580 public:
2581 bool opCast(T) () const pure nothrow @safe @nogc if (is(T == bool)) {
2582 pragma(inline, true);
2583 return valid;
2586 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (kind != Kind.Invalid && type !is null); }
2587 @property bool literal () const pure nothrow @safe @nogc { pragma(inline, true); return (kind == Kind.Literal); }
2588 @property bool stack () const pure nothrow @safe @nogc { pragma(inline, true); return (kind == Kind.Stack); }
2589 @property bool global () const pure nothrow @safe @nogc { pragma(inline, true); return (kind == Kind.Global); }
2590 @property bool local () const pure nothrow @safe @nogc { pragma(inline, true); return (kind == Kind.Local); }
2591 @property bool argument () const pure nothrow @safe @nogc { pragma(inline, true); return (kind == Kind.Argument); }
2592 @property bool field () const pure nothrow @safe @nogc { pragma(inline, true); return (kind == Kind.Field); }
2593 @property bool arrayaccess () const pure nothrow @safe @nogc { pragma(inline, true); return arridx.valid; }
2594 @property bool fieldaccess () const pure nothrow @safe @nogc { pragma(inline, true); return fldobj.valid; }
2596 static Value makeStack() (Slot aslot, TypeBase t, in auto ref Loc aloc) {
2597 if (!aslot.valid) assert(0, "internal compiler error");
2598 if (t is null) assert(0, "internal compiler error");
2599 Value res;
2600 res.type = t;
2601 res.kind = Kind.Stack;
2602 res.slot = aslot;
2603 res.loc = aloc;
2604 return res;
2607 static Value makeStack() (Slot aslot, in auto ref Value v) {
2608 if (!aslot.valid) assert(0, "internal compiler error");
2609 if (v.type is null) assert(0, "internal compiler error");
2610 Value res;
2611 res.type = cast(TypeBase)v.type;
2612 res.kind = Kind.Stack;
2613 res.slot = aslot;
2614 res.loc = v.loc;
2615 return res;
2618 static Value makeVoid() (in auto ref Loc aloc) {
2619 Value res;
2620 res.type = typeVoid;
2621 res.kind = Kind.Global;
2622 res.loc = aloc;
2623 return res;
2626 static Value makeTemp() (in auto ref Value v) {
2627 Value res;
2628 res.type = cast(TypeBase)v.type;
2629 res.kind = Kind.Stack;
2630 res.slot = Slot.makeTemp();
2631 res.loc = v.loc;
2632 return res;
2635 static Value makeTemp() (TypeBase t, in auto ref Loc aloc) {
2636 if (t is null) assert(0, "fatal internal error");
2637 Value res;
2638 res.type = t;
2639 res.kind = Kind.Stack;
2640 res.slot = Slot.makeTemp();
2641 res.loc = aloc;
2642 return res;
2645 static Value makeTempBlock() (TypeBase t, int len, in auto ref Loc aloc) {
2646 if (t is null) assert(0, "fatal internal error");
2647 if (len < 1 || len > 128) assert(0, "fatal internal error");
2648 Value res;
2649 res.type = t;
2650 res.kind = Kind.Stack;
2651 res.slot = Slot.makeTempBlock(len); // first slot is refed, others aren't
2652 res.loc = aloc;
2653 return res;
2656 static Value makeVar() (Variable var, in auto ref Loc aloc) {
2657 if (var is null) assert(0, "fatal internal error");
2658 Value res;
2659 res.type = var.type;
2660 if (var.global) {
2661 res.kind = Kind.Global;
2662 res.index = var.idx;
2663 } else if (var.idx >= 0 && var.idx <= 255) {
2664 // local
2665 assert(curslots[var.idx] == -1);
2666 res.kind = Kind.Local;
2667 res.slot = Slot.acquire(var.idx);
2668 } else {
2669 // argument
2670 assert(var.idx < 0);
2671 res.kind = Kind.Argument;
2672 res.index = -var.idx-1;
2674 res.loc = aloc;
2675 res.ptrref = var.reference;
2676 res.readonly = var.readonly;
2677 res.varname = var.name;
2678 return res;
2681 static Value makeNull() (in auto ref Loc aloc) {
2682 Value res;
2683 res.type = typeNull;
2684 res.kind = Kind.Literal;
2685 res.lit = 0;
2686 res.loc = aloc;
2687 return res;
2690 static Value makeBool() (bool v, in auto ref Loc aloc) {
2691 Value res;
2692 res.type = typeBool;
2693 res.kind = Kind.Literal;
2694 res.lit = (v ? 1 : 0);
2695 res.loc = aloc;
2696 return res;
2699 static Value makeInt() (int v, in auto ref Loc aloc) {
2700 Value res;
2701 res.type = typeInt;
2702 res.kind = Kind.Literal;
2703 res.lit = v;
2704 res.loc = aloc;
2705 return res;
2708 static Value makeActor() (uint v, in auto ref Loc aloc) {
2709 Value res;
2710 res.type = typeActor;
2711 res.kind = Kind.Literal;
2712 res.lit = v;
2713 res.loc = aloc;
2714 return res;
2717 static Value makeEnum() (TypeBase tp, int val, in auto ref Loc aloc) {
2718 auto etp = cast(TypeEnum)tp;
2719 if (etp is null) assert(0, "cannot make enum literal from non-enum type");
2720 Value res;
2721 res.type = tp;
2722 res.kind = Kind.Literal;
2723 res.lit = val;
2724 res.loc = aloc;
2725 return res;
2728 static Value makeEnumDefault() (TypeBase tp, in auto ref Loc aloc) {
2729 auto etp = cast(TypeEnum)tp;
2730 if (etp is null) assert(0, "cannot make enum literal from non-enum type");
2731 Value res;
2732 res.type = tp;
2733 res.kind = Kind.Literal;
2734 res.lit = etp.defval;
2735 res.loc = aloc;
2736 return res;
2739 static Value makeStr(T:const(char)[]) (T v, in auto ref Loc aloc) {
2740 Value res;
2741 res.type = typeStr;
2742 res.kind = Kind.Literal;
2743 if (v.length == 0) {
2744 res.index = 0;
2745 } else {
2746 int sidx = -1;
2747 foreach (immutable idx, string s; strlist) if (s == v) { sidx = cast(int)idx; break; }
2748 if (sidx < 0) {
2749 if (strlist.length == ushort.max) assert(0, "too many strings");
2750 sidx = cast(int)strlist.length;
2751 static if (is(T == string)) strlist ~= v; else strlist ~= v.idup;
2753 res.index = sidx;
2755 res.loc = aloc;
2756 return res;
2759 static Value makeField() (Variable actid, const ref ActorField fld, in auto ref Loc aloc) {
2760 assert(actid !is null);
2761 assert(actid.type is typeActor);
2762 Value res;
2763 res.type = cast(TypeBase)fld.type;
2764 res.kind = Kind.Field;
2765 res.index = fld.ofs;
2766 res.fldobj = Value.makeVar(actid, aloc);
2767 res.loc = aloc;
2768 return res;
2771 static Value makeField() (auto ref Value actid, const ref ActorField fld, in auto ref Loc aloc) {
2772 assert(actid.valid);
2773 assert(actid.type is typeActor);
2774 Value res;
2775 res.type = cast(TypeBase)fld.type;
2776 res.kind = Kind.Field;
2777 res.index = fld.ofs;
2778 res.fldobj = actid;
2779 res.loc = aloc;
2780 return res;
2783 // this generates bounds checking, if necessary
2784 // it will normalize `arridx` too, and will set `elemsize` to `1`
2785 private Value arridxLoad(bool forcelit) () {
2786 if (!arrayaccess) assert(0, "internal compiler error");
2787 if (elemsize <= 0) assert(0, "internal compiler error");
2788 if (!arridx.value.type.isInt) compileErrorAt(arridx.value.loc, "invalid array index type");
2789 // array size is negative: no bounds checking, literal offset is fixed
2790 if (arrsize < 0) {
2791 assert(elemsize == 1);
2792 static if (!forcelit) {
2793 if (arridx.value.literal) return arridx.value;
2795 if (!arridx.value.stack) arridx = arridx.value.load;
2796 return arridx.value;
2798 // bounds checking
2799 if (arridx.value.literal) {
2800 if (arridx.value.lit >= arrsize) compileErrorAt(arridx.value.loc, "array index out of bounds");
2801 arridx.value.lit *= elemsize;
2802 elemsize = 1;
2803 arrsize = -1; // don't generate bounds checking anymore
2804 static if (forcelit) arridx = arridx.value.load;
2805 return arridx.value;
2806 } else {
2807 // generate bounds checking
2808 if (arrsize > ushort.max) assert(0, "internal compiler error");
2809 auto res = arridx.value.load;
2810 emitCode(VMOp.makeU16(OpCode.Bounds, res.slot, cast(ushort)arrsize));
2811 // multiply offset by element size
2812 if (elemsize > 1) {
2813 assert(elemsize <= ushort.max);
2814 auto tmpslot = Slot.makeTemp();
2815 emitILit(tmpslot, elemsize);
2816 emitCode(VMOp(OpCode.Mul, res.slot, res.slot, tmpslot));
2817 elemsize = 1;
2819 // next loads will reuse this one
2820 arridx = res;
2821 arrsize = -1; // don't generate bounds checking anymore
2822 return res;
2826 // this caches loaded value
2827 private Value fldobjLoad(bool forcelit) () {
2828 if (!fieldaccess) assert(0, "internal compiler error");
2829 if (!fldobj.value.type.isActor) compileErrorAt(fldobj.value.loc, "invalid object type");
2830 if (fldobj.value.literal) {
2831 static if (forcelit) fldobj = fldobj.value.load;
2832 return fldobj.value;
2833 } else if (!fldobj.value.stack) {
2834 // next loads will reuse this one
2835 fldobj = fldobj.value.load;
2837 // and do nothing for already loaded values
2838 return fldobj.value;
2841 void cachePostponedValues () {
2842 if (arrayaccess && !arridx.value.literal) arridxLoad!false();
2843 if (fieldaccess && !fldobj.value.literal) fldobjLoad!false();
2846 // ////////////////////////////////////////////////////////////////////// //
2847 // store pointer to variable into slot
2848 Value storePtrToSlot(string mode="move") (Slot slot) {
2849 if (ptrref) {
2850 // already a reference, just copy a pointer
2851 assert(argument);
2852 assert(!arrayaccess);
2853 assert(!fieldaccess);
2854 emitCode(VMOp(OpCode.ALoad, slot, cast(ubyte)index));
2855 // has offset?
2856 if (ptridx) {
2857 //FIXME: it is not safe to add to pointer this way!
2858 auto tmpidxslot = Slot.makeTemp();
2859 emitILit(tmpidxslot, ptridx);
2860 emitCode(VMOp(OpCode.Add, slot, slot, tmpidxslot));
2862 return Value.makeStack(slot, typeVoid, loc); // arbitrary type
2864 // create a pointer
2865 if (global) {
2866 // pointer to global
2867 assert(!fieldaccess);
2868 auto tmpidxslot = Slot.makeTemp();
2869 emitILit(tmpidxslot, index);
2870 emitCode(VMOp(OpCode.MakeGPtr, slot, tmpidxslot));
2871 } else if (argument) {
2872 // pointer to argument (the thing that cannot be)
2873 assert(!fieldaccess);
2874 auto tmpidxslot = Slot.makeTemp();
2875 emitILit(tmpidxslot, index);
2876 emitCode(VMOp(OpCode.MakeAPtr, slot, tmpidxslot));
2877 assert(0, "internal error in compiler");
2878 } else if (local) {
2879 // pointer to local
2880 assert(!fieldaccess);
2881 emitCode(VMOp(OpCode.MakeLPtr, slot, this.slot));
2882 } else if (field) {
2883 // pointer to field
2884 assert(fieldaccess);
2885 fldobjLoad!true;
2886 assert(fldobj.value.slot);
2887 auto tmpidxslot = Slot.makeTemp();
2888 emitILit(tmpidxslot, index);
2889 emitCode(VMOp(OpCode.MakeFPtr, slot, fldobj.value.slot, tmpidxslot));
2890 } else {
2891 assert(0, "wtf?!");
2893 // if array access, offset pointer
2894 //FIXME: unsafe!
2895 if (arrayaccess) {
2896 if (isStrIndexing) compileError("cannot load pointer to string");
2897 bool dogen = true;
2898 if (arridx.value.literal && arridx.value.lit == 0) {
2899 arridxLoad!false; // this does bounds checking
2900 dogen = false;
2901 } else {
2902 arridxLoad!true;
2904 if (dogen) {
2905 assert(arridx.value.slot);
2906 emitCode(VMOp(OpCode.Add, slot, slot, arridx.value.slot));
2909 return Value.makeStack(slot, typeVoid, loc); // arbitrary type
2912 // ////////////////////////////////////////////////////////////////////// //
2913 // mode=move: just Mov
2914 // move=bool: use LogBNorm to create normalized boolean (and return boolean Value)
2915 // WARNING: won't increase refcount for `slot`!
2916 Value storeToSlot(string mode="move") (Slot slot) {
2917 static assert(mode == "move" || mode == "bool");
2918 alias v = this;
2919 if (!slot.valid) assert(0, "cannot store value to invalid slot");
2920 // check types
2921 if (v.type.isVoid) assert(0, "cannot store void value");
2922 // typed enums
2923 if (v.type.isEnum) {
2924 if (!v.type.base.isInt && !v.type.base.isBool && !v.type.base.isStr) {
2925 assert(0, "cannot store value of type "~typeid(v.type.base).name);
2927 Value xv = v;
2928 //HACK
2929 xv.type = v.type.base;
2930 return xv.storeToSlot!mode(slot);
2932 //HACK: make pointers great again!
2933 if (!v.type.isBool && !v.type.isInt && !v.type.isStr && !v.type.isActor && !v.type.isPointer) {
2934 assert(0, "cannot store value of type "~typeid(v.type).name);
2936 // temporary stack slot?
2937 if (v.stack) {
2938 // this cannot have array index
2939 assert(v.slot.valid);
2940 assert(!v.arrayaccess);
2941 assert(!v.fieldaccess);
2942 static if (mode == "bool") {
2943 // normalized bool
2944 if (!v.type.isBool) {
2945 // not a boolean, normalize
2946 emitCode(VMOp(OpCode.LogBNorm, slot, v.slot));
2947 return v.makeStack(slot, typeBool, v.loc);
2948 } else if (v.slot != slot) {
2949 emitCode(VMOp(OpCode.Mov, slot, v.slot));
2950 return v.makeStack(slot, v);
2952 assert(v.type.isBool);
2953 } else {
2954 // move
2955 if (v.slot != slot) {
2956 emitCode(VMOp(OpCode.Mov, slot, v.slot));
2957 return v.makeStack(slot, v);
2960 return v;
2962 // literal?
2963 if (v.literal) {
2964 // this cannot have array index
2965 assert(!v.arrayaccess);
2966 assert(!v.fieldaccess);
2967 static if (mode == "bool") {
2968 // normalized bool
2969 if (!v.type.isBool) {
2970 emitILit(slot, (v.lit ? 1 : 0));
2971 return v.makeStack(slot, typeBool, v.loc);
2974 // move
2975 emitILit(slot, v.lit);
2976 return v.makeStack(slot, v);
2978 // global?
2979 if (v.global) {
2980 assert(!v.fieldaccess);
2981 if (v.arrayaccess) {
2982 // array indexing
2983 if (!v.isStrIndexing) {
2984 // global array indexing
2985 auto gitemp = Value.makeInt(v.index, v.loc).load;
2986 emitCode(VMOp(OpCode.GArrLoad, slot, gitemp.slot, v.arridxLoad!true.slot));
2987 } else {
2988 // string indexing
2989 // load global value
2990 auto tmpslot = Slot.makeTemp();
2991 emitCode(VMOp.makeU16(OpCode.GLoad, tmpslot, cast(ushort)v.index));
2992 // index string
2993 emitCode(VMOp(OpCode.StrLoadChar, slot, tmpslot, v.arridxLoad!true.slot));
2995 } else {
2996 emitCode(VMOp.makeU16(OpCode.GLoad, slot, cast(ushort)v.index));
2998 static if (mode == "bool") {
2999 // normalized bool
3000 if (!v.type.isBool) {
3001 emitCode(VMOp(OpCode.LogBNorm, slot, slot));
3002 return v.makeStack(slot, typeBool, v.loc);
3005 return v.makeStack(slot, v);
3007 // argument?
3008 if (v.argument) {
3009 assert(!v.fieldaccess);
3010 // array access?
3011 if (v.arrayaccess) {
3012 if (!v.isStrIndexing) compileErrorAt(v.loc, "array arguments aren't supported yet");
3013 if (v.ptrref) compileErrorAt(v.loc, "string references aren't supported yet");
3014 // string indexing
3015 auto tmpslot = Slot.makeTemp();
3016 emitCode(VMOp(OpCode.ALoad, tmpslot, cast(ubyte)v.index));
3017 emitCode(VMOp(OpCode.StrLoadChar, slot, tmpslot, v.arridxLoad!true.slot));
3018 static if (mode == "bool") {
3019 emitCode(VMOp(OpCode.LogBNorm, slot, slot));
3020 return v.makeStack(slot, typeBool, v.loc);
3021 } else {
3022 return v.makeStack(slot, v);
3024 } else {
3025 emitCode(VMOp(OpCode.ALoad, slot, cast(ubyte)v.index));
3026 if (v.ptrref) {
3027 // load value via pointer
3028 auto tmpidxslot = Slot.makeTemp();
3029 emitILit(tmpidxslot, v.ptridx);
3030 emitCode(VMOp(OpCode.PtrLoad, slot, slot, tmpidxslot));
3032 static if (mode == "bool") {
3033 // normalized bool
3034 if (!v.type.isBool) {
3035 emitCode(VMOp(OpCode.LogBNorm, slot, slot));
3036 return v.makeStack(slot, typeBool, v.loc);
3039 return v.makeStack(slot, v);
3042 // local?
3043 if (v.local) {
3044 assert(!v.fieldaccess);
3045 assert(v.slot.valid);
3046 assert(!v.ptrref); // not yet
3047 // array access?
3048 if (v.arrayaccess) {
3049 if (v.isStrIndexing) {
3050 // string indexing
3051 emitCode(VMOp(OpCode.StrLoadChar, slot, v.slot, v.arridxLoad!true.slot));
3052 static if (mode == "bool") {
3053 emitCode(VMOp(OpCode.LogBNorm, slot, slot));
3054 return v.makeStack(slot, typeBool, v.loc);
3055 } else {
3056 return v.makeStack(slot, v);
3058 } else {
3059 // local array indexing
3060 if (v.arridx.value.literal) {
3061 v.arridxLoad!false; // bounds checking, literal normalization
3062 assert(v.arridx.value.literal);
3063 static if (mode == "bool") {
3064 emitCode(VMOp(OpCode.LogBNorm, slot, cast(ubyte)(v.slot.idx+v.arridx.value.lit*v.elemsize)));
3065 return v.makeStack(slot, typeBool, v.loc);
3066 } else {
3067 emitCode(VMOp(OpCode.Mov, slot, cast(ubyte)(v.slot.idx+v.arridx.value.lit*v.elemsize)));
3069 } else {
3070 emitCode(VMOp(OpCode.LArrLoad, slot, v.slot, v.arridxLoad!true.slot));
3072 static if (mode == "bool") {
3073 emitCode(VMOp(OpCode.LogBNorm, slot, slot));
3074 return v.makeStack(slot, typeBool, v.loc);
3076 return v.makeStack(slot, v);
3078 assert(0);
3080 // normal local
3081 if (v.slot == slot) return v; // nothing to do
3082 // move
3083 static if (mode == "bool") {
3084 emitCode(VMOp(OpCode.LogBNorm, slot, v.slot));
3085 return v.makeStack(slot, typeBool, v.loc);
3086 } else {
3087 emitCode(VMOp(OpCode.Mov, slot, v.slot));
3088 return v.makeStack(slot, v);
3091 // field?
3092 if (v.field) {
3093 //FLoad, // dest (slot), actid, fldidx -- load field value to dest
3094 //HACK: don't load literal offset into register
3095 if (v.arrayaccess) {
3096 if (!v.arridx.value.type.isInt) compileErrorAt(v.arridx.value.loc, "invalid array index type");
3097 if (v.arridx.value.literal) {
3098 if (!v.isStrIndexing) {
3099 v.arridxLoad!false; // bounds checking, literal normalization
3100 assert(v.arridx.value.literal);
3102 } else {
3103 v.arridxLoad!true; // this generates bound checking code
3106 auto actid = v.fldobjLoad!true;
3107 auto tmp = Value.makeInt(v.index, v.loc);
3108 // if this is an array, fix field offset
3109 if (v.arrayaccess) {
3110 if (!v.isStrIndexing) {
3111 // array indexing
3112 if (v.arridx.value.literal) {
3113 // hack
3114 tmp.lit += v.arridx.value.lit*v.elemsize;
3115 tmp = tmp.load;
3116 } else {
3117 tmp = tmp.load;
3118 assert(v.arridx.value.slot); // it should be it
3119 emitCode(VMOp(OpCode.Add, tmp.slot, tmp.slot, v.arridx.value.slot));
3121 } else {
3122 // string indexing; later
3124 } else {
3125 tmp = tmp.load;
3127 assert(tmp.slot.valid);
3128 assert(actid.slot.valid);
3129 emitCode(VMOp(OpCode.FLoad, slot, actid.slot, tmp.slot));
3130 // string indexing?
3131 if (v.arrayaccess && v.isStrIndexing) {
3132 emitCode(VMOp(OpCode.StrLoadChar, slot, slot, v.arridx.value.load.slot));
3134 static if (mode == "bool") {
3135 emitCode(VMOp(OpCode.LogBNorm, slot, slot));
3136 return v.makeStack(slot, typeBool, v.loc);
3137 } else {
3138 return v.makeStack(slot, v);
3141 // oops
3142 assert(0, "internal compiler error: storeToSlot cannot load something");
3145 // mode=move: just Mov
3146 // move=bool: use LogBNorm to create normalized boolean (and return boolean Value)
3147 Value load(string mode="move") () {
3148 static assert(mode == "move" || mode == "bool");
3149 alias v = this;
3150 // check types
3151 if (v.type.isVoid) assert(0, "cannot load void value");
3152 if (v.type.isEnum) return v.storeToSlot!mode(Slot.makeTemp);
3153 if (!v.type.isBool && !v.type.isInt && !v.type.isStr && !v.type.isActor && !v.type.isPointer) {
3154 assert(0, "cannot load value of type "~typeid(v.type).name);
3156 // temporary stack slot?
3157 if (v.stack) return v.storeToSlot!mode(v.slot);
3158 // other kinds
3159 return v.storeToSlot!mode(Slot.makeTemp);
3162 // returns `false` if type conversion failed, or `this` is not a variable
3163 bool assign (Value src) {
3164 alias dest = this;
3165 if (dest.literal) return false;
3166 // pointer assign?
3167 if (src.type.isPointer) {
3168 // no array access here
3169 if (src.arrayaccess) return false;
3170 if (dest.type.isPointer) {
3171 if (!dest.type.same(src.type)) return false; // oops, bad pointers
3172 } else if (src.type.isNull) {
3173 if (!dest.type.isPointer && !dest.type.isActor) return false; // two special cases
3174 } else {
3175 if (!dest.type.same(src.type.base)) return false; // oops, bad type
3177 } else {
3178 // other assigns
3179 if (!dest.type.same(src.type)) {
3180 // convert to and from bool
3181 if (dest.type.isBool) {
3182 // to bool
3183 if (src.literal) {
3184 src = Value.makeBool((src.lit != 0), src.loc);
3185 } else {
3186 src = src.load!"bool";
3188 } else if (src.type.isBool) {
3189 // from bool to int
3190 if (!dest.type.isInt) return false;
3191 // ints and bools are the same for VM
3192 } else {
3193 return false;
3197 if (dest.stack) {
3198 // stack slot
3199 // no array access here
3200 if (dest.arrayaccess) return false;
3201 src.storeToSlot(dest.slot);
3202 } else if (dest.global) {
3203 // global
3204 src = src.load;
3205 // array access?
3206 if (dest.arrayaccess) {
3207 if (dest.isStrIndexing) return false;
3208 auto gitemp = Value.makeInt(dest.index, dest.loc).load;
3209 emitCode(VMOp(OpCode.GArrStore, src.slot, gitemp.slot, dest.arridxLoad!true.slot));
3210 } else {
3211 emitCode(VMOp.makeU16(OpCode.GStore, src.slot, cast(ushort)dest.index));
3213 } else if (dest.argument) {
3214 // function argument
3215 //FIXME: no array access here (yet)
3216 if (dest.arrayaccess) return false;
3217 // argument
3218 src = src.load;
3219 if (dest.ptrref) {
3220 // load pointer
3221 auto tmpptrslot = Slot.makeTemp();
3222 emitCode(VMOp(OpCode.ALoad, tmpptrslot, cast(ubyte)dest.index));
3223 // store value via pointer
3224 auto tmpidxslot = Slot.makeTemp();
3225 emitILit(tmpidxslot, dest.ptridx);
3226 emitCode(VMOp(OpCode.PtrStore, src.slot, tmpptrslot, tmpidxslot));
3227 } else {
3228 // normal `by-value` argument
3229 emitCode(VMOp(OpCode.AStore, cast(ubyte)dest.index, src.slot));
3231 } else if (dest.local) {
3232 // local
3233 assert(dest.slot.valid);
3234 // array access?
3235 if (dest.arrayaccess) {
3236 //TODO: optimize this
3237 if (dest.isStrIndexing) return false;
3238 src = src.load;
3239 if (dest.arridx.value.literal) {
3240 dest.arridxLoad!false; // bounds checking, literal normalization
3241 assert(dest.arridx.value.literal);
3242 emitCode(VMOp(OpCode.Mov, cast(ubyte)(dest.slot+dest.arridx.value.lit*dest.elemsize), src.slot));
3243 } else {
3244 emitCode(VMOp(OpCode.LArrStore, src.slot, dest.slot, dest.arridxLoad!true.slot));
3246 } else {
3247 src.storeToSlot(dest.slot);
3249 } else if (dest.field) {
3250 // field
3251 //FStore, // src (slot), actid, fldidx -- store field value from src
3252 //HACK: don't load literal offset into register
3253 if (dest.arrayaccess) {
3254 if (dest.isStrIndexing) return false;
3255 if (!dest.arridx.value.type.isInt) compileErrorAt(dest.arridx.value.loc, "invalid array index type");
3256 if (dest.arridx.value.literal) {
3257 dest.arridxLoad!false; // bounds checking, literal normalization
3258 assert(dest.arridx.value.literal);
3259 } else {
3260 dest.arridxLoad!true; // this generates bound checking code
3263 auto actid = dest.fldobjLoad!true;
3264 assert(actid.slot.valid);
3265 auto tmp = Value.makeInt(dest.index, dest.loc);
3266 // if this is an array, fix field offset
3267 if (dest.arrayaccess) {
3268 if (dest.isStrIndexing) return false;
3269 if (dest.arridx.value.literal) {
3270 // hack
3271 tmp.lit += dest.arridx.value.lit*dest.elemsize;
3272 tmp = tmp.load;
3273 } else {
3274 tmp = tmp.load;
3275 assert(dest.arridx.value.slot); // it should be it
3276 emitCode(VMOp(OpCode.Add, tmp.slot, tmp.slot, dest.arridx.value.slot));
3278 } else {
3279 tmp = tmp.load;
3281 assert(tmp.slot.valid);
3282 src = src.load;
3283 emitCode(VMOp(OpCode.FStore, src.slot, actid.slot, tmp.slot));
3284 } else {
3285 assert(0, "internal compiler error");
3287 return true;
3292 // ////////////////////////////////////////////////////////////////////// //
3293 //TODO: multidimensional arrays
3294 TypeBase parseTypeDecl(bool convertFuncToFuncPtr) () {
3295 TypeBase t = null;
3296 if (token.kw(Token.T.Bool)) t = typeBool;
3297 else if (token.kw(Token.T.Int)) t = typeInt;
3298 else if (token.kw(Token.T.String)) t = typeStr;
3299 else if (token.kw(Token.T.Actor)) t = typeActor;
3300 else if (token.kw(Token.T.Void)) t = typeVoid;
3301 else if (token.kw(Token.T.Struct)) t = parseStructDecl(false);
3302 else if (token.id) {
3303 if (auto etp = token.sval in gtypes) {
3304 t = *etp;
3307 if (t is null) return null;
3308 // skip type name
3309 skipToken();
3310 // check for function type
3311 if (token.kw(Token.T.Function) || token.kw(Token.T.Method)) {
3312 bool ismethod = token.kw(Token.T.Method);
3313 // skip keyword
3314 skipToken();
3315 auto tf = parseFuncArgs(ismethod);
3316 tf.aux = t;
3317 static if (convertFuncToFuncPtr) return new TypePointer(tf); else return tf;
3319 // check for array type
3320 if (token.delim("[")) {
3321 if (t.isVoid) compileError("cannot create void array");
3322 if (t.isFunc) t = new TypePointer(t); // convert `.isFunc` to funcptr
3323 skipToken();
3324 // `[]`?
3325 if (token.delim("]")) {
3326 // size==0 means "automatically calculate"
3327 auto ta = new TypeArray(t, 0);
3328 skipToken();
3329 return ta;
3331 // get array size
3332 auto szloc = token.loc;
3333 auto vsz = parseExpr();
3334 if (!token.delim("]")) compileError("']' expected");
3335 skipToken();
3336 // check size
3337 if (!vsz.literal && !vsz.type.isInt) compileErrorAt(szloc, "integer literal expected");
3338 if (vsz.lit < 1 || vsz.lit > 16384) compileErrorAt(szloc, "invalid array size");
3339 // looks like an array
3340 return new TypeArray(t, vsz.lit);
3342 // ordinary type
3343 return t;
3347 // should be at `struct` keyword
3348 // returns `null` if not
3349 TypeStruct parseStructDecl (bool mustBeNamed) {
3350 if (!token.kw(Token.T.Struct)) return null;
3351 auto stloc = token.loc;
3352 skipToken();
3353 string name;
3354 if (mustBeNamed && !token.id) compileError("identifier expected");
3355 if (token.id) {
3356 name = token.sval;
3357 skipToken();
3359 if (!token.delim("{")) compileError("'{' expected");
3360 skipToken();
3361 auto stp = new TypeStruct(name);
3362 while (!token.delim("}")) {
3363 auto tploc = token.loc;
3364 auto tp = parseTypeDecl!true();
3365 if (tp is null) compileErrorAt(tploc, "type declaration expected");
3366 if (!token.id) compileError("identifier expected");
3367 while (token.id) {
3368 stp.appendMember(token.sval, tp, token.loc);
3369 skipToken();
3370 if (!token.delim(",")) break;
3371 skipToken();
3373 if (!token.delim(";")) compileError("';' expected");
3374 skipToken();
3376 if (!token.delim("}")) compileError("'}' expected");
3377 skipToken();
3378 return stp;
3382 // ////////////////////////////////////////////////////////////////////// //
3383 // expression parser
3385 // `me` ends with 'R': right-associative
3386 Value parseAnyBinOp(string me, string upfunc, T...) () {
3387 auto lhs = mixin(upfunc~"()");
3388 mainloop: while (!token.empty) {
3389 foreach (immutable idx, immutable _; T) {
3390 static if (idx%2 == 0) {
3391 if (token.delim(T[idx])) {
3392 skipToken(); // skip operator
3393 static if (T[idx] == "||" || T[idx] == "&&") {
3394 lhs = T[idx+1](lhs);
3395 continue mainloop;
3396 } else {
3397 static if (me[$-1] == 'R') {
3398 auto rhs = mixin(me~"()");
3399 } else {
3400 auto rhs = mixin(upfunc~"()");
3402 lhs = T[idx+1](lhs, rhs);
3403 continue mainloop;
3408 break;
3410 return lhs;
3414 Value parseExprPrimary () {
3415 // literals and id
3416 if (token.num) { auto res = Value.makeInt(token.ival, token.loc); skipToken(); return res; }
3417 if (token.str) { auto res = Value.makeStr(token.sval, token.loc); skipToken(); return res; }
3418 if (token.kw) {
3419 switch (token.type) {
3420 case Token.T.True:
3421 case Token.T.False:
3422 auto res = Value.makeBool(token.kw(Token.T.True), token.loc);
3423 skipToken();
3424 return res;
3425 case Token.T.Null:
3426 auto res = Value.makeNull(token.loc);
3427 skipToken();
3428 return res;
3429 case Token.T.New: // new ObjId[(args)]
3430 // compile this to:
3431 // Actor temp = spawn();
3432 // temp.ctor(args);
3433 // result is temp
3434 auto newloc = token.loc;
3435 skipToken();
3436 if (!token.id) compileError("identifier expected");
3437 string aid = token.sval;
3438 string fullname = aid~"::ctor";
3439 auto fp = fullname in funclist;
3440 if (fp is null) compileError("`ctor` for `"~aid~"` not found");
3441 auto res = Value.makeVar(*fp, newloc);
3442 skipToken();
3443 // compile `Actor temp = spawn()`
3444 auto spfn = "spawn" in funclist;
3445 if (spfn is null) compileErrorAt(newloc, "`spawn` not found");
3446 Value tempact;
3447 assert(!tempact.valid);
3448 // find `Actor spawn ()`, and generate the call
3449 foreach (immutable fnidx, ref FuncInfo fif; spfn.ovloads) {
3450 if (fif.type.aux.isActor && fif.type.args.length == 0) {
3451 // i found her!
3452 FunCallInfo fci;
3453 fci.start();
3454 // use result slot as tempact
3455 tempact = cgFunCall(Value.makeVar(*spfn, newloc), fci);
3456 assert(tempact.slot);
3457 assert(tempact.type.isActor);
3458 break;
3461 if (!tempact.valid) compileErrorAt(newloc, "`Actor spawn ()` not found");
3462 // call ctor
3463 res = parseFunCall!false(res, tempact);
3464 if (!res.type.isVoid) compileErrorAt(newloc, "`"~aid~"::ctor` must be void");
3465 // return resulting actor
3466 return tempact;
3467 case Token.T.Cast:
3468 auto loc = token.loc;
3469 skipToken();
3470 if (!token.delim("(")) compileError("'(' expected");
3471 skipToken();
3472 TypeBase tp = parseTypeDecl!true();
3473 if (tp is null) compileError("type name expected");
3474 if (!tp.isInt && !tp.isBool && !tp.isEnum && !tp.isVoid) compileErrorAt(loc, "invalid cast destination type");
3475 if (!token.delim(")")) compileError("')' expected");
3476 skipToken();
3477 auto eloc = token.loc;
3478 auto val = parseExprUnary();
3479 // cast to void
3480 if (tp.isVoid) return Value.makeVoid(loc);
3481 // cast to enum
3482 if (tp.isEnum) {
3483 if (!val.type.isInt) compileErrorAt(eloc, "integer expected");
3484 if (val.literal) {
3485 //??? do sanity checks
3486 return Value.makeEnum(tp, val.lit, eloc);
3487 } else {
3488 //HACK
3489 val.type = tp;
3490 return val;
3492 assert(0, "wtf?!");
3494 // need any conversion?
3495 if (!val.type.same(tp)) {
3496 // possible conversions:
3497 // anything -> bool
3498 // bool -> int
3499 // enum -> int
3500 if (tp.isBool) {
3501 // anything -> bool
3502 if (val.literal) {
3503 val = Value.makeBool((val.lit != 0), val.loc);
3504 } else {
3505 val = val.load!"bool";
3507 } else if (tp.isInt) {
3508 if (val.type.isEnum) {
3509 // enum -> int
3510 if (val.literal) {
3511 //HACK
3512 val.type = val.type.base;
3513 } else {
3514 auto btp = val.type.base;
3515 val = val.load;
3516 assert(val.stack);
3517 //HACK
3518 val.type = btp;
3520 } else if (val.type.isBool) {
3521 // bool -> int
3522 if (val.literal) {
3523 val = Value.makeInt((val.lit ? 1 : 0), val.loc);
3524 } else {
3525 val = val.load;
3526 assert(val.stack);
3527 //HACK
3528 val.kind = Value.Kind.Stack;
3529 val.type = typeBool;
3531 } else {
3532 compileErrorAt(loc, "invalid type conversion");
3534 } else {
3535 compileErrorAt(loc, "invalid type conversion");
3538 return val;
3539 case Token.T.Abs:
3540 auto loc = token.loc;
3541 skipToken();
3542 if (!token.delim("(")) compileError("'(' expected");
3543 skipToken();
3544 auto res = parseExpr();
3545 if (!token.delim(")")) compileError("')' expected");
3546 skipToken();
3547 if (!res.type.isInt) compileErrorAt(loc, "integer argument expected");
3548 if (res.literal) {
3549 if (res.lit == 0x8000_0000) compileErrorAt(loc, "cannot negate this integer");
3550 if (res.lit < 0) res.lit = -res.lit;
3551 } else {
3552 res = res.load;
3553 emitCode(VMOp(OpCode.IntAbs, res.slot, res.slot));
3555 return res;
3556 default:
3558 compileError("unexpected keyword");
3559 assert(0);
3562 if (token.id) {
3563 auto loc = token.loc;
3564 string idname = token.sval;
3565 // max alias chain length
3566 foreach (immutable _; 0..64) {
3567 // try variable
3568 //stderr.writeln("step: ", _, "; id: <", idname, ">");
3569 if (auto var = findLocalVar(idname)) {
3570 auto res = Value.makeVar(var, token.loc);
3571 skipToken();
3572 return res;
3574 // if we're in method, look for field first
3575 if (curfunc.isMethod) {
3576 auto fld = actorFindField(idname);
3577 if (fld.valid) {
3578 // load `this`
3579 auto xthis = findLocalVar("this");
3580 if (xthis is null) compileError("`this` not found");
3581 auto res = Value.makeField(xthis, fld, token.loc);
3582 skipToken();
3583 return res;
3586 // try variable
3587 if (auto var = findGlobalVar(idname)) {
3588 auto res = Value.makeVar(var, token.loc);
3589 skipToken();
3590 return res;
3592 //HACK: try typed enum
3593 // this should be done in `parseDot()`, and we should have a special `Value` type for this, but meh...
3594 if (auto etp = idname in gtypes) {
3595 // typed enum access should be followed by dot, always
3596 if (auto etype = cast(TypeEnum)(*etp)) {
3597 skipToken();
3598 if (!token.delim(".")) compileError("'.' expected");
3599 skipToken();
3600 if (!token.id && !token.kw(Token.T.Default)) compileError("identifier expected");
3601 if (token.id("min")) {
3602 skipToken();
3603 return Value.makeEnum(etype, etype.minval, loc);
3604 } else if (token.id("max")) {
3605 skipToken();
3606 return Value.makeEnum(etype, etype.maxval, loc);
3607 } else if (token.kw(Token.T.Default) || token.id("init")) {
3608 skipToken();
3609 return Value.makeEnumDefault(etype, loc);
3610 } else if (auto mv = token.sval in etype.members) {
3611 skipToken();
3612 Value res;
3613 if (etype.base.isInt) res = Value.makeInt(*mv, loc);
3614 else if (etype.base.isStr) res = Value.makeStr(strlist[*mv], loc);
3615 else assert(0, "wtf?!");
3616 //HACK:
3617 res.type = etype;
3618 return res;
3619 } else {
3620 compileError("undefined member '"~token.sval~"' in enum '"~etype.name~"'");
3622 } else {
3623 compileError("cannot dot-access non-enum types");
3625 assert(0);
3627 // try constant
3628 if (auto c = findConst(idname)) {
3629 skipToken();
3630 if (c.type.isBool) return Value.makeBool((c.ival != 0), loc);
3631 if (c.type.isInt) return Value.makeInt(c.ival, loc);
3632 if (c.type.isStr) return Value.makeStr(c.sval, loc);
3633 compileError("invalid constant type");
3635 // try actorid
3636 if (isKnownActorId(idname)) {
3637 auto aidloc = token.loc;
3638 skipToken();
3639 string aid = idname;
3640 if (!token.delim(".")) compileError("'.' expected");
3641 skipToken();
3642 if (!token.id) compileError("identifier expected");
3643 string xname = aid~"::"~token.sval;
3644 skipToken();
3645 if (auto var = findGlobalVar(xname)) {
3646 auto res = Value.makeVar(var, token.loc);
3647 return res;
3648 } else {
3649 compileErrorAt(aidloc, "undefined identifier '"~token.sval~"' in class '"~aid~"'");
3650 assert(0);
3653 // try alias
3654 bool found = false;
3655 for (Scope sc = curscope; sc !is null; sc = sc.parent) {
3656 if (auto asp = idname in sc.aliases) { idname = *asp; found = true; break; }
3658 if (!found) {
3659 if (auto asp = idname in aliases) { idname = *asp; found = true; } // alias chain loop
3661 // if nothing fits, try CurActorId.id
3662 if (!found && curfunc.actorid.length) {
3663 string xname = curfunc.actorid~"::"~idname;
3664 if (auto var = findGlobalVar(xname)) {
3665 auto res = Value.makeVar(var, token.loc);
3666 skipToken();
3667 return res;
3670 if (!found) break;
3672 compileErrorAt(loc, "undefined identifier '"~token.sval~"'");
3675 // "(...)"
3676 if (token.delim("(")) {
3677 auto loc = token.loc;
3678 skipToken();
3679 auto res = parseExpr();
3680 if (!token.delim(")")) compileError("`)` expected for `(` at "~loc.toString);
3681 skipToken();
3682 return res;
3685 // global scope
3686 if (token.delim(".")) compileError("no global scope access is supported yet");
3688 // prefix
3689 if (token.delim("++") || token.delim("--")) {
3690 bool isPP = token.delim("++");
3691 auto loc = token.loc;
3692 skipToken();
3693 auto val = parseExprUnary();
3694 if (!val.local && !val.argument && !val.global && !val.field) compileErrorAt(val.loc, "cannot perform prefix inc/dec on non-var");
3695 if (val.readonly) compileError("cannot assign to read-only variable");
3696 if (!val.type.isInt) compileErrorAt(val.loc, "cannot perform prefix inc/dec on non-int");
3697 // little optimiations
3698 if (val.local && val.slot.valid && !val.arrayaccess && !val.fieldaccess && !val.ptrref) {
3699 // do direct inc/dec, but change `val` kind to `stack`
3700 emitCode(VMOp((isPP ? OpCode.Inc : OpCode.Dec), val.slot, val.slot));
3701 //HACK
3702 val.kind = Value.Kind.Stack;
3703 return val;
3704 } else {
3705 auto rhs = val.storeToSlot(Slot.makeTemp());
3706 emitCode(VMOp((isPP ? OpCode.Inc : OpCode.Dec), rhs.slot, rhs.slot));
3707 if (!val.assign(rhs)) compileErrorAt(loc, "cannot perform prefix inc/dec");
3708 return rhs;
3712 compileError("primary expression expected");
3713 assert(0);
3717 struct FunCallInfo {
3718 enum MaxArgCount = 64;
3719 TypeFunc targs;
3720 Value[MaxArgCount] argv;
3721 int argc;
3723 @disable this (this); // no copies
3725 void start () {
3726 if (targs !is null) assert(0, "duplicate call to `FunCallInfo.start()`");
3727 targs = new TypeFunc();
3730 void putArg (Value a) {
3731 //stderr.writeln("putarg(", argc, "): ", a.type.toPrettyString);
3732 if (targs is null) assert(0, "forgotten call to `FunCallInfo.start()`");
3733 if (!a.valid) assert(0, "fatal internal error");
3734 if (a.type.isVoid || a.type.isFunc) compileErrorAt(a.loc, "invalid argument type");
3735 if (argc >= argv.length) compileErrorAt(a.loc, "too many arguments");
3736 targs.args ~= TypeFunc.Arg(a.type);
3737 argv[argc++] = a;
3740 void prependArg (Value a) {
3741 if (targs is null) assert(0, "forgotten call to `FunCallInfo.start()`");
3742 if (!a.valid) assert(0, "fatal internal error");
3743 if (a.type.isVoid || a.type.isFunc) compileErrorAt(a.loc, "invalid argument type");
3744 if (argc >= argv.length) compileErrorAt(a.loc, "too many arguments");
3745 targs.args.length += 1;
3746 foreach_reverse (immutable idx; 1..targs.args.length) targs.args[idx] = targs.args[idx-1];
3747 targs.args[0] = TypeFunc.Arg(a.type);
3748 ++argc;
3749 foreach_reverse (immutable idx; 1..argc) argv[idx] = argv[idx-1];
3750 argv[0] = a;
3755 // `val` should be `.isFunc` or `.isFuncPtr`
3756 Value cgFunCall (Value val, ref FunCallInfo fci) {
3757 assert(val.valid);
3758 assert(val.type.isCallable);
3759 assert(fci.targs !is null);
3761 TypeFunc cftype = null; // exact type of the function we'll call
3762 Value rv; // loaded function address
3763 bool hasrest = false;
3765 bool prependThisOrActId () {
3766 // for `act.func` use `act` as `this`
3767 if (val.fieldaccess) {
3768 assert(val.fieldaccess);
3769 val.fldobjLoad!true;
3770 assert(val.fldobj.value.type.isActor);
3771 assert(val.fldobj.value.slot);
3772 fci.prependArg(Value.makeStack(val.fldobj.value.slot, val.fldobj.value.type, val.loc));
3773 return true;
3775 // for methods, use `this` arg
3776 if (curfunc.isMethod) {
3777 auto xthis = findLocalVar("this");
3778 if (xthis is null) compileErrorAt(val.loc, "`this` not found for method '"~val.varname~"'");
3779 assert(xthis.type);
3780 fci.prependArg(Value.makeVar(xthis, val.loc));
3781 return true;
3783 // otherwise fail
3784 return false;
3787 // try to resolve overloads if `val` is `.isFunc`
3788 if (val.type.isFunc) {
3789 auto fp = val.varname in funclist;
3790 if (fp is null) compileErrorAt(val.loc, "'"~val.varname~"' is not a function");
3791 Variable var = *fp;
3792 // find function
3793 auto fnidx = var.findOverload!false(fci.targs);
3794 if (fnidx < 0 && prependThisOrActId()) fnidx = var.findOverload!false(fci.targs);
3795 if (fnidx == Variable.NotFound) compileErrorAt(val.loc, "cannot find suitable overloaded function for '"~val.varname~"'");
3796 if (fnidx == Variable.Conflict) {
3797 var.findOverload!true(fci.targs);
3798 compileErrorAt(val.loc, "overload conflict for '"~val.varname~"'");
3800 assert(fnidx >= 0 && fnidx < var.ovloads.length);
3801 // load function address
3802 auto fif = &var.ovloads[fnidx];
3803 if (fif.pc >= 0) {
3804 if (fif.type.hasrest) assert(0, "internal compiler error");
3805 // vm call
3806 if (fif.pc > ushort.max) compileErrorAt(val.loc, "invalid function address");
3807 rv = Value.makeInt(fif.pc, val.loc).load;
3808 // add fixup
3809 if (fif.pc == 0) vmcallfixes ~= VMCallFixup(var, fnidx, cast(uint)vmcode.length-1, val.loc);
3810 } else {
3811 // builtin call
3812 rv = Value.makeInt(fif.pc, val.loc).load;
3813 hasrest = fif.type.hasrest;
3815 // real function type
3816 cftype = cast(TypeFunc)fif.type;
3817 assert(cftype !is null);
3818 } else {
3819 // this must be funcptr
3820 if (!val.type.isFuncPtr) compileErrorAt(val.loc, "trying to call something strange");
3821 cftype = cast(TypeFunc)val.type.base;
3822 assert(cftype !is null);
3823 // check if we can call this
3824 if (!cftype.callCompatibleNoAux(fci.targs)) {
3825 // try to prepend `this`
3826 bool error = true;
3827 if (prependThisOrActId()) error = !cftype.callCompatibleNoAux(fci.targs);
3828 if (error) {
3829 compilerMessage("ERROR args: %s", fci.targs.toPrettyString);
3830 compileErrorAt(val.loc, "invalid arguments for funcptr: "~cftype.toPrettyString);
3833 // load address
3834 rv = val.load;
3837 assert(rv.slot);
3839 // append defaults
3840 if (fci.targs.args.length < cftype.args.length) {
3841 foreach (ref TypeFunc.Arg a; cftype.args[fci.targs.args.length..$]) {
3842 if (!a.defval.literal) assert(0, "internal compiler error");
3843 fci.putArg(a.defval);
3846 fci.targs.aux = cftype.aux;
3848 // generate call
3849 Value fargv; // result will be placed here
3850 if (hasrest) {
3851 // builtin with varargs
3852 if (fci.argc == 0) {
3853 // we need this to store return value anyway
3854 fargv = Value.makeTempBlock(fci.targs.aux, 1, val.loc);
3855 emitCode(VMOp(OpCode.Call, rv.slot, fargv.slot, 0));
3856 } else {
3857 int extra = fci.argc-cast(int)cftype.args.length;
3858 int rqslots = cast(int)cftype.args.length+extra*2;
3859 if (rqslots > 128) compileErrorAt(val.loc, "too many varargs");
3860 // allocate slots
3861 fargv = Value.makeTempBlock(fci.targs.aux, rqslots, val.loc);
3862 // `fargv` slot is refed, others aren't
3863 int slotpos = fargv.slot+rqslots-1;
3864 // copy args
3865 Slot[FunCallInfo.MaxArgCount*2] sst; // array access can require additional temporary slots, so store all used slots here
3866 foreach (immutable aidx, ref Value a; fci.argv[0..fci.argc]) {
3867 assert(aidx*2+1 < sst.length);
3868 if (aidx >= cftype.args.length) {
3869 // put type, then arg
3870 assert(slotpos >= fargv.slot);
3871 assert(curslots[slotpos] == 0);
3872 // reserve slot
3873 sst[aidx*2] = Slot.acquire(slotpos--);
3874 Value.makeInt(putVAType(a.type), a.loc).storeToSlot(sst[aidx*2]);
3876 assert(slotpos >= fargv.slot);
3877 assert((slotpos == fargv.slot.idx ? (curslots[slotpos] == 1) : (curslots[slotpos] == 0)));
3878 sst[aidx*2+1] = Slot.acquire(slotpos--);
3879 a = a.storeToSlot(sst[aidx*2+1]); // store a, so argument slots will be occupied
3881 assert(slotpos == fargv.slot-1);
3882 emitCode(VMOp(OpCode.Call, rv.slot, fargv.slot, cast(ubyte)rqslots));
3884 } else if (fci.argc == 0) {
3885 // we need this to store return value anyway
3886 fargv = Value.makeTempBlock(fci.targs.aux, 1, val.loc);
3887 emitCode(VMOp(OpCode.Call, rv.slot, fargv.slot, 0));
3888 } else {
3889 // allocate slots
3890 fargv = Value.makeTempBlock(fci.targs.aux, fci.argc, val.loc);
3891 // `fargv` slot is refed, others aren't
3892 // copy args
3893 foreach (immutable aidx, ref Value a; fci.argv[0..fci.argc]) {
3894 if (a.type.isStruct) {
3895 // structs always passed by reference
3896 a = a.storePtrToSlot(Slot.acquire(cast(int)(fargv.slot+fci.argc-aidx-1))); // store a, so argument slots will be occupied
3897 } else {
3898 a = a.storeToSlot(Slot.acquire(cast(int)(fargv.slot+fci.argc-aidx-1))); // store a, so argument slots will be occupied
3901 emitCode(VMOp(OpCode.Call, rv.slot, fargv.slot, cast(ubyte)fci.argc));
3903 // result
3904 if (fci.targs.aux.isVoid) return Value.makeVoid(val.loc);
3905 assert(fargv.stack);
3906 assert(fargv.type.same(fci.targs.aux));
3907 return fargv;
3911 // `val.var is null` means "calling function field"; ufcs0 is Actor
3912 Value parseFunCall(bool allowIsAssign=true) (Value val, Value ufcs0=Value.init) {
3913 // `is` or `!is`?
3914 auto isloc = token.loc;
3916 static if (allowIsAssign) {
3917 if (token.delim("!") || token.kw(Token.T.Is)) {
3918 // null check
3919 if (ufcs0.valid) compileError("invalid 'is' check");
3920 //TODO: add `a is b` support
3921 bool isnull = token.kw(Token.T.Is);
3922 if (token.delim("!")) {
3923 skipToken();
3924 if (!token.kw(Token.T.Is)) compileError("'is' expected");
3926 assert(token.kw(Token.T.Is));
3927 skipToken();
3928 if (!token.kw(Token.T.Null)) compileError("'null' expected");
3929 skipToken();
3930 // generate check
3931 if (val.type.isFunc) {
3932 // assume that function is never `null`
3933 return Value.makeBool(!isnull, val.loc);
3935 if (!val.type.isPointer) compileErrorAt(val.loc, "pointer expected for `null` checks");
3936 val = val.load!"bool";
3937 assert(val.type.isBool);
3938 if (isnull) emitCode(VMOp(OpCode.LogNot, val.slot, val.slot));
3939 return val;
3943 FunCallInfo fci;
3944 fci.start();
3946 // put UFCS arguments
3947 if (ufcs0.valid) fci.putArg(ufcs0);
3949 // a.b = c
3950 static if (allowIsAssign) {
3951 if (token.delim("=")) {
3952 auto loc = token.loc;
3953 skipToken();
3954 auto a2 = parseExpr();
3955 if (a2.type.isFuncPtr || a2.type.isNull) {
3956 // assign function pointer to something
3957 if (!val.assign(a2)) compileErrorAt(loc, "cannot assign");
3958 return val;
3959 } else if (a2.type.isPointer) {
3960 compileErrorAt(loc, "invalid pointer type");
3961 assert(0);
3963 fci.putArg(a2);
3967 // parse other args
3968 if (token.delim("(")) {
3969 skipToken();
3970 while (!token.delim(")")) {
3971 auto aloc = token.loc;
3972 auto a = parseExpr();
3973 fci.putArg(a);
3974 if (!token.delim(",")) break;
3975 skipToken();
3977 if (!token.delim(")")) compileError("')' expected");
3978 skipToken();
3981 return cgFunCall(val, fci);
3985 // UFCS and field access support
3986 Value parseDot (Value val, bool allowufcs) {
3987 if (!token.delim(".")) return val; // just in case
3988 skipToken();
3989 // builtin properties for strings
3990 if (val.type.isStr) {
3991 if (token.id("length")) {
3992 skipToken();
3993 if (val.literal) return Value.makeInt(cast(int)strlist[val.lit].length, val.loc);
3994 val = val.load;
3995 auto res = Value.makeTemp(typeInt, val.loc);
3996 emitCode(VMOp(OpCode.StrLen, res.slot, val.slot));
3997 return res;
4000 // builtin properties for arrays
4001 //TODO: dynamic arrays?
4002 if (val.type.isArray) {
4003 if (token.id("length")) {
4004 skipToken();
4005 auto atp = cast(TypeArray)val.type;
4006 assert(atp !is null);
4007 return Value.makeInt(atp.size, val.loc);
4010 // builtin properties for enums
4011 if (val.type.isEnum) {
4012 if (token.id("min")) {
4013 skipToken();
4014 auto etp = cast(TypeEnum)val.type;
4015 assert(etp !is null);
4016 return Value.makeEnum(etp, etp.minval, val.loc);
4018 if (token.id("max")) {
4019 skipToken();
4020 auto etp = cast(TypeEnum)val.type;
4021 assert(etp !is null);
4022 return Value.makeEnum(etp, etp.maxval, val.loc);
4024 if (token.kw(Token.T.Default) || token.id("init")) {
4025 skipToken();
4026 auto etp = cast(TypeEnum)val.type;
4027 assert(etp !is null);
4028 return Value.makeEnumDefault(etp, val.loc);
4031 // dot access for struct?
4032 if (val.type.isStruct || (val.type.isArray && val.type.base.isStruct)) {
4033 if (val.type.isArray && !val.arrayaccess) compileError("did you forgot to index your array?");
4034 if (!token.id) compileError("identifier expected");
4035 auto xloc = token.loc;
4036 auto stp = cast(TypeStruct)(val.type.isStruct ? val.type : val.type.base);
4037 assert(stp !is null);
4038 auto mbr = stp.getMember(token.sval);
4039 //if (!mbr.valid) compileError("undefined struct field '"~token.sval~"'");
4040 //UFCS
4041 if (mbr.valid) {
4042 skipToken();
4043 if (val.argument) {
4044 // argument
4045 assert(val.ptrref); // structs can be passed only by reference for now
4046 assert(!val.arrayaccess);
4047 assert(!val.fieldaccess);
4048 val.ptridx += mbr.ofs;
4049 val.type = mbr.type;
4050 return val;
4051 } else if (val.fieldaccess) {
4052 // field: struct or array of structs
4053 val.index += mbr.ofs;
4054 val.type = mbr.type;
4055 return val;
4056 } else {
4057 assert(!val.ptrref);
4058 assert(mbr.ofs >= 0);
4059 if (mbr.ofs != 0) {
4060 if (val.local || val.stack) {
4061 // shift slot
4062 assert(val.slot);
4063 val.slot = Slot.acquire(val.slot+mbr.ofs);
4064 } else if (val.global) {
4065 // shift index
4066 val.index += mbr.ofs;
4067 } else {
4068 assert(0, "compiler doesn't know what to do");
4071 //HACK: force field type
4072 val.type = mbr.type;
4073 return val;
4077 // other
4078 if (!token.id) compileError("identifier expected");
4079 Variable fvar = null;
4080 if (auto fld = actorFindFieldPtr(token.sval)) {
4081 // field access
4082 if (!val.type.isActor) compileErrorAt(val.loc, "cannot access fields of non-actor");
4083 auto res = Value.makeField(val, *fld, val.loc);
4084 skipToken();
4085 if (allowufcs && res.type.isCallable) return parseFunCall(res);
4086 return res;
4087 } else if (auto fp = token.sval in funclist) {
4088 // UFCS function call
4089 fvar = *fp;
4090 } else {
4091 // function variable
4092 fvar = findLocalVar(token.sval);
4093 if (fvar is null) fvar = findGlobalVar(token.sval);
4094 if (fvar !is null) {
4095 if (!fvar.type.isCallable) compileError("variable '"~token.sval~"' is not a function");
4098 if (fvar is null) compileError("function '"~token.sval~"' not found");
4099 // should be fcall or null check
4100 Value fnv = Value.makeVar(fvar, token.loc);
4101 skipToken();
4102 return (allowufcs ? parseFunCall(fnv, val) : fnv);
4106 Value parseIndexing (Value val, bool allowufcs) {
4107 auto stloc = token.loc;
4108 if (!val.type.isArray && !val.type.isStr) compileError("trying to index something that is not an array");
4109 if (val.arrayaccess) compileError("arrays of arrays aren't supported yet");
4110 skipToken();
4111 auto vidx = parseExpr();
4112 if (!token.delim("]")) compileError("']' expected");
4113 skipToken();
4114 if (!vidx.type.isInt) compileErrorAt(stloc, "integer index expected");
4115 // generate bounds checking here, 'cause why not?
4116 if (val.type.isArray) {
4117 int arrsize = val.type.cellSize/val.type.base.cellSize;
4118 assert(arrsize > 0 && arrsize <= ushort.max);
4119 if (vidx.literal) {
4120 if (vidx.lit < 0 || vidx.lit >= arrsize) {
4121 compileErrorAt(stloc, "array index out of bounds");
4124 //HACK: transform into array access
4125 val.elemsize = val.type.base.cellSize;
4126 val.arridx = vidx;
4127 val.type = val.type.base;
4128 val.arrsize = arrsize;
4129 return val;
4130 } else {
4131 assert(val.type.isStr);
4132 //HACK: transform into array access
4133 val.elemsize = 1;
4134 val.arridx = vidx;
4135 val.type = typeInt; // oops
4136 val.arrsize = -666; //HACK: special flag
4137 return val;
4142 Value parseExprPostfix (Value val, bool allowufcs=true) {
4143 //stderr.writeln(allowufcs, ":val=", val, "; type=", val.type.toPrettyString);
4144 // paren-less function call
4145 if (val.type.isCallable && allowufcs && !token.delim("(")) val = parseFunCall(val);
4146 // other similar things
4147 for (;;) {
4148 if (!token.delim) break;
4149 if (token.sval == ".") val = parseDot(val, allowufcs);
4150 else if (token.sval == "(") val = parseFunCall(val);
4151 else if (token.sval == "[") val = parseIndexing(val, allowufcs);
4152 else break;
4154 // postfix inc/dec
4155 if (token.delim("++") || token.delim("--")) {
4156 bool isPP = token.delim("++");
4157 auto loc = token.loc;
4158 skipToken();
4159 if (!val.local && !val.argument && !val.global && !val.field) compileErrorAt(val.loc, "cannot perform postfix inc/dec on non-var");
4160 if (val.readonly) compileError("cannot assign to read-only variable");
4161 if (!val.type.isInt) compileErrorAt(val.loc, "cannot perform postfix inc/dec on non-int");
4162 auto res = Value.makeTemp(val);
4163 res = val.storeToSlot(res.slot);
4164 if (val.local && val.slot.valid && !val.arrayaccess && !val.fieldaccess && !val.ptrref) {
4165 // do direct inc/dec
4166 emitCode(VMOp((isPP ? OpCode.Inc : OpCode.Dec), val.slot, val.slot));
4167 } else {
4168 auto res1 = Value.makeTemp(val);
4169 emitCode(VMOp((isPP ? OpCode.Inc : OpCode.Dec), res1.slot, res.slot));
4170 if (!val.assign(res1)) compileErrorAt(val.loc, "cannot perform postfix inc/dec");
4172 val = res;
4174 return val;
4178 Value parseExprUnary (bool allowufcs=true) {
4179 Value res;
4180 if (token.delim) {
4181 if (token.sval == "+") {
4182 auto loc = token.loc;
4183 skipToken();
4184 res = parseExprUnary(allowufcs);
4185 if (!res.type.isInt) compileErrorAt(loc, "invalid unary math");
4186 } else if (token.sval == "-") {
4187 auto loc = token.loc;
4188 skipToken();
4189 res = parseExprUnary(allowufcs);
4190 if (!res.type.isInt) compileErrorAt(loc, "invalid unary math");
4191 if (res.literal) {
4192 res.lit = -res.lit;
4193 } else {
4194 // generate neg
4195 res = res.load;
4196 auto zero = Value.makeInt(0, loc).load;
4197 emitCode(VMOp(OpCode.Sub, res.slot, zero.slot, res.slot));
4199 } else if (token.sval == "!") {
4200 auto loc = token.loc;
4201 skipToken();
4202 res = parseExprUnary(allowufcs);
4203 if (!res.type.isInt && !res.type.isBool && !res.type.isStr && !res.type.isActor) compileErrorAt(loc, "invalid unary math");
4204 if (res.literal) {
4205 res = Value.makeBool((res.lit == 0), res.loc);
4206 } else {
4207 // generate lognot
4208 res = res.load;
4209 emitCode(VMOp(OpCode.LogNot, res.slot, res.slot));
4211 } else if (token.sval == "&") {
4212 auto loc = token.loc;
4213 skipToken();
4214 res = parseExprUnary(false);
4215 //stderr.writeln("func:", res.type.isFunc, "; funcptr:", res.type.isFuncPtr, "; ", res.type.toPrettyString, " -- ", res);
4216 if (!res.type.isCallable) compileErrorAt(loc, "cannot take address of non-function");
4217 // &fnvar means "take address of containing function" (i.e. just load variable value)
4218 if (res.type.isFuncPtr) {
4219 //TODO: "&&func" should fail
4220 //res.type = new TypePointer(res.type); //HACK
4221 } else {
4222 assert(res.type.isFunc);
4223 Variable var = findFunction(res);
4224 if (var is null) compileErrorAt(loc, "undefined function '"~res.varname~"'");
4225 if (var.ovloads.length == 0) assert(0, "something is very wrong in the compiler");
4226 if (var.ovloads.length != 1) compileErrorAt(loc, "cannot take address of overload set '"~var.name~"'");
4227 // load function address
4228 enum fnidx = 0;
4229 if (var.ovloads[fnidx].pc >= 0) {
4230 if (var.ovloads[fnidx].type.hasrest) compileErrorAt(loc, "cannot take address of vararg builtin '"~var.name~"'");
4231 // vm
4232 if (var.ovloads[fnidx].pc > ushort.max) compileErrorAt(loc, "invalid function address");
4233 //FIXME: leave it as literal, and genetate fixups in `load`
4234 res = Value.makeInt(var.ovloads[fnidx].pc, loc).load;
4235 // add fixup
4236 if (var.ovloads[fnidx].pc == 0) vmcallfixes ~= VMCallFixup(var, fnidx, cast(uint)vmcode.length-1, loc);
4237 } else {
4238 // builtin
4239 res = Value.makeInt(var.ovloads[fnidx].pc, loc).load;
4241 //HACK
4242 res.type = new TypePointer(var.type);
4244 allowufcs = false; // don't call function pointer
4245 assert(res.type.isPointer);
4246 } else {
4247 res = parseExprPrimary();
4249 // bitnot, bitneg, etc.
4250 } else {
4251 res = parseExprPrimary();
4254 return parseExprPostfix(res, allowufcs);
4258 // ////////////////////////////////////////////////////////////////////////// //
4259 Value cgNumMath(OpCode opc, string mathop) (Value lhs, Value rhs) {
4260 if (!lhs.type.isInt) compileErrorAt(lhs.loc, "number expected");
4261 if (!rhs.type.isInt) compileErrorAt(rhs.loc, "number expected");
4262 static if (opc == OpCode.Div || opc == OpCode.Mod) {
4263 if (rhs.literal && rhs.lit == 0) compileErrorAt(rhs.loc, "division by zero");
4265 // inline math
4266 if (lhs.literal && rhs.literal) {
4267 Value res = lhs;
4268 res.lit = mixin("lhs.lit"~mathop~"rhs.lit");
4269 return res;
4271 // check for other math optimizations
4272 if (rhs.literal) {
4273 static if (opc == OpCode.Mul) {
4274 if (rhs.lit == 1) return lhs;
4275 if (rhs.lit == 0) return Value.makeInt(0, lhs.loc);
4277 static if (opc == OpCode.Add || opc == OpCode.Sub) {
4278 if (rhs.lit == 0) return lhs;
4280 static if (opc == OpCode.Div) {
4281 if (rhs.lit == 1) return lhs;
4283 static if (opc == OpCode.Mod) {
4284 if (rhs.lit == 1) return Value.makeInt(0, lhs.loc);
4286 } else if (lhs.literal) {
4287 static if (opc == OpCode.Mul) {
4288 if (lhs.lit == 1) return rhs;
4289 if (lhs.lit == 0) return Value.makeInt(0, lhs.loc);
4291 static if (opc == OpCode.Add) {
4292 if (lhs.lit == 0) return lhs;
4294 static if (opc == OpCode.Div) {
4295 if (lhs.lit == 0) return Value.makeInt(0, lhs.loc);
4297 static if (opc == OpCode.Mod) {
4298 if (lhs.lit == 0) return Value.makeInt(0, lhs.loc);
4301 // generate mathop
4302 lhs = lhs.load;
4303 rhs = rhs.load;
4304 auto res = Value.makeTemp(lhs);
4305 emitCode(VMOp(opc, res.slot, lhs.slot, rhs.slot));
4306 return res;
4309 alias cgIAdd = cgNumMath!(OpCode.Add, "+");
4310 alias cgISub = cgNumMath!(OpCode.Sub, "-");
4311 alias cgIMul = cgNumMath!(OpCode.Mul, "*");
4312 alias cgIDiv = cgNumMath!(OpCode.Div, "/");
4313 alias cgIMod = cgNumMath!(OpCode.Mod, "%");
4316 // ////////////////////////////////////////////////////////////////////////// //
4317 Value cgIShlShr(OpCode opc) (Value lhs, Value rhs) {
4318 if (!lhs.type.isInt) compileErrorAt(lhs.loc, "number expected");
4319 if (!rhs.type.isInt) compileErrorAt(rhs.loc, "number expected");
4320 if (rhs.literal) {
4321 if (rhs.lit < 0) compileErrorAt(rhs.loc, "negative shift is not defined");
4322 if (rhs.lit == 0) return lhs; // nothing to do
4323 if (rhs.lit > 31) compileErrorAt(rhs.loc, "allowed shift range is [0..31]");
4325 // inline math
4326 if (lhs.literal && rhs.literal) {
4327 static if (opc == OpCode.Shl) lhs.lit <<= rhs.lit;
4328 else static if (opc == OpCode.Shr) lhs.lit >>= rhs.lit;
4329 else static assert(0, "wtf?!");
4330 return lhs;
4332 // generate mathop
4333 lhs = lhs.load;
4334 rhs = rhs.load;
4335 auto res = Value.makeTemp(lhs);
4336 emitCode(VMOp(opc, res.slot, lhs.slot, rhs.slot));
4337 return res;
4340 alias cgIShl = cgIShlShr!(OpCode.Shl);
4341 alias cgIShr = cgIShlShr!(OpCode.Shr);
4344 // ////////////////////////////////////////////////////////////////////////// //
4345 Value cgBitOp(OpCode opc) (Value lhs, Value rhs) {
4346 if (!lhs.type.isInt) compileErrorAt(lhs.loc, "number expected");
4347 if (!rhs.type.isInt) compileErrorAt(rhs.loc, "number expected");
4348 if (rhs.literal) {
4349 if (rhs.lit == 0) {
4350 static if (opc == OpCode.BitOr || opc == OpCode.BitXor) {
4351 return lhs;
4352 } else {
4353 static assert(opc == OpCode.BitAnd);
4354 return Value.makeInt(0, lhs.loc);
4356 } else if (rhs.lit == -1) {
4357 static if (opc == OpCode.BitOr || opc == OpCode.BitXor) {
4358 return Value.makeInt(-1, lhs.loc);
4359 } else {
4360 static assert(opc == OpCode.BitAnd);
4361 return lhs;
4364 } else if (lhs.literal) {
4365 if (lhs.lit == 0) {
4366 static if (opc == OpCode.BitOr || opc == OpCode.BitXor) {
4367 return rhs;
4368 } else {
4369 static assert(opc == OpCode.BitAnd);
4370 return Value.makeInt(0, lhs.loc);
4372 } else if (lhs.lit == -1) {
4373 static if (opc == OpCode.BitOr || opc == OpCode.BitXor) {
4374 return Value.makeInt(-1, lhs.loc);
4375 } else {
4376 static assert(opc == OpCode.BitAnd);
4377 return rhs;
4381 // inline math
4382 if (lhs.literal && rhs.literal) {
4383 static if (opc == OpCode.BitAnd) lhs.lit &= rhs.lit;
4384 else static if (opc == OpCode.BitOr) lhs.lit |= rhs.lit;
4385 else static if (opc == OpCode.BitXor) lhs.lit ^= rhs.lit;
4386 else static assert(0, "wtf?!");
4387 return lhs;
4389 // generate mathop
4390 lhs = lhs.load;
4391 rhs = rhs.load;
4392 auto res = Value.makeTemp(lhs);
4393 emitCode(VMOp(opc, res.slot, lhs.slot, rhs.slot));
4394 return res;
4397 alias cgBitAnd = cgBitOp!(OpCode.BitAnd);
4398 alias cgBitOr = cgBitOp!(OpCode.BitOr);
4399 alias cgBitXor = cgBitOp!(OpCode.BitXor);
4402 // ////////////////////////////////////////////////////////////////////////// //
4403 Value cgEquals(OpCode opc) (Value lhs, Value rhs) if (opc == OpCode.Equ || opc == OpCode.NotEqu) {
4404 if (lhs.literal && rhs.literal) {
4405 static if (opc == OpCode.Equ) {
4406 return Value.makeBool(lhs.lit == rhs.lit, lhs.loc);
4407 } else {
4408 return Value.makeBool(lhs.lit != rhs.lit, lhs.loc);
4411 // generate code
4412 lhs = lhs.load;
4413 rhs = rhs.load;
4414 auto res = Value.makeTemp(typeBool, lhs.loc);
4415 emitCode(VMOp(opc, res.slot, lhs.slot, rhs.slot));
4416 return res;
4419 alias cgEqu = cgEquals!(OpCode.Equ);
4420 alias cgNotEqu = cgEquals!(OpCode.NotEqu);
4423 Value cgCompare(string op) (Value lhs, Value rhs) if (op == "<" || op == ">" || op == "<=" || op == ">=") {
4424 if (!lhs.type.isInt && !lhs.type.isStr) compileErrorAt(lhs.loc, "invalid type");
4425 if (!rhs.type.isInt && !rhs.type.isStr) compileErrorAt(rhs.loc, "invalid type");
4426 if (!lhs.type.same(rhs.type)) compileErrorAt(lhs.loc, "cannot compare different types");
4427 // literals
4428 if (lhs.literal && rhs.literal) {
4429 if (lhs.type.isInt) {
4430 return Value.makeBool(mixin("lhs.lit"~op~"rhs.lit"), lhs.loc);
4431 } else {
4432 return Value.makeBool(mixin("strlist[lhs.lit]"~op~"strlist[rhs.lit]"), lhs.loc);
4435 // generate code
4436 lhs = lhs.load;
4437 rhs = rhs.load;
4438 auto res = Value.makeTemp(typeBool, lhs.loc);
4439 OpCode opc;
4440 static if (op == "<") opc = (lhs.type.isInt ? OpCode.ILess : OpCode.SLess);
4441 else static if (op == ">") opc = (lhs.type.isInt ? OpCode.IGreat : OpCode.SGreat);
4442 else static if (op == "<=") opc = (lhs.type.isInt ? OpCode.ILessEqu : OpCode.SLessEqu);
4443 else static if (op == ">=") opc = (lhs.type.isInt ? OpCode.IGreatEqu : OpCode.SGreatEqu);
4444 else static assert(0, "wtf?!");
4445 emitCode(VMOp(opc, res.slot, lhs.slot, rhs.slot));
4446 return res;
4449 alias cgLess = cgCompare!"<";
4450 alias cgGreat = cgCompare!">";
4451 alias cgLEq = cgCompare!"<=";
4452 alias cgGEq = cgCompare!">=";
4455 // ////////////////////////////////////////////////////////////////////////// //
4456 Value cgLogOp(OpCode opc) (Value lhs) if (opc == OpCode.LogAnd || opc == OpCode.LogOr) {
4457 // &&: parseExprCmp()
4458 // ||: parseExprLogAnd()
4459 static if (opc == OpCode.LogAnd) alias nextExpr = parseExprCmp; else alias nextExpr = parseExprLogAnd;
4460 static if (opc == OpCode.LogAnd) alias JumpOp = OpCode.JFalse; else alias JumpOp = OpCode.JTrue;
4461 // check lhs type
4462 if (!lhs.type.isGoodForBool) compileErrorAt(lhs.loc, "invalid type");
4463 // optimize some known cases
4464 if (lhs.literal) {
4465 enum LLMode { Nop, DropRHS, DropLHS }
4466 LLMode llmode = LLMode.Nop;
4467 static if (opc == OpCode.LogAnd) {
4468 // &&
4469 if (!lhs.lit) {
4470 // false && smth
4471 lhs = Value.makeBool(false, lhs.loc);
4472 llmode = LLMode.DropRHS;
4473 } else {
4474 // true && smth
4475 llmode = LLMode.DropLHS;
4477 } else {
4478 // ||
4479 if (!lhs.lit) {
4480 // false || smth
4481 llmode = LLMode.DropLHS;
4482 } else {
4483 // true || smth
4484 lhs = Value.makeBool(true, lhs.loc);
4485 llmode = LLMode.DropRHS;
4488 auto cgmark = JumpChain.startHere(); // we'll unwind to this point
4489 auto rhs = parseExprCmp();
4490 // check rhs type
4491 if (!rhs.type.isGoodForBool) compileErrorAt(rhs.loc, "invalid type");
4492 final switch (llmode) {
4493 case LLMode.Nop:
4494 assert(0, "internal compiler error");
4495 case LLMode.DropRHS:
4496 cgmark.unwindToBase();
4497 return lhs;
4498 case LLMode.DropLHS:
4499 if (rhs.type.isBool) return rhs;
4500 return (rhs.literal ? Value.makeBool(rhs.lit != 0, rhs.loc) : rhs.load!"bool");
4503 // mark current code point, so we can rewind if RHS is literal
4504 auto rewindpc = JumpChain.startHere();
4505 // create storage for result
4506 auto res = Value.makeTemp(typeBool, lhs.loc);
4507 assert(res.stack);
4508 // load value
4509 lhs.storeToSlot!"bool"(res.slot);
4510 // generate jump and do rhs
4511 auto jumpc = rewindpc.startForward();
4512 jumpc.putJump(JumpOp, res.slot);
4513 // get RHS
4514 auto rhs = nextExpr();
4515 // check rhs type
4516 if (!rhs.type.isGoodForBool) compileErrorAt(rhs.loc, "invalid type");
4517 // optimize literal case
4518 if (rhs.literal) {
4519 enum RLMode { Nop, RewindAll, RewindJump }
4520 RLMode rlmode = RLMode.Nop;
4521 static if (opc == OpCode.LogAnd) {
4522 // &&
4523 if (!rhs.lit) {
4524 // smth && false: rewind everything
4525 rlmode = RLMode.RewindAll;
4526 rhs = Value.makeBool(false, rhs.loc);
4527 } else {
4528 // smth && true: rewind jump
4529 rlmode = RLMode.RewindJump;
4531 } else {
4532 // ||
4533 if (!lhs.lit) {
4534 // smth || false: rewind jump
4535 rlmode = RLMode.RewindJump;
4536 } else {
4537 // smth || true: rewind everything
4538 rlmode = RLMode.RewindAll;
4539 rhs = Value.makeBool(true, rhs.loc);
4542 final switch (rlmode) {
4543 case RLMode.Nop:
4544 assert(0, "internal compiler error");
4545 case RLMode.RewindAll: // and return rhs
4546 rewindpc.unwindToBase();
4547 return rhs;
4548 case RLMode.RewindJump: // and return res
4549 jumpc.unwindToBase();
4550 return res;
4553 // load rhs to result slot
4554 rhs.storeToSlot!"bool"(res.slot);
4555 // patch jump
4556 jumpc.finishHere();
4557 // done
4558 return res;
4561 alias cgLogAnd = cgLogOp!(OpCode.LogAnd);
4562 alias cgLogOr = cgLogOp!(OpCode.LogOr);
4565 // ////////////////////////////////////////////////////////////////////////// //
4566 string BuildExprBinOp(string me, string upfunc, T...) () {
4567 string res = `Value parseExpr`~me~` () {`;
4568 res ~= `return parseAnyBinOp!("parseExpr`~me~`", "parseExpr`~upfunc~`"`;
4569 foreach (immutable idx, string s; T[]) {
4570 res ~= ",";
4571 static if (idx%2 == 0) {
4572 res ~= "`"~s~"`";
4573 } else {
4574 res ~= s;
4577 res ~= ")();";
4578 res ~= "}";
4579 return res;
4583 // name ends with 'R': right-associative
4584 // name upfunc delim,func
4585 mixin(BuildExprBinOp!("Mul", "Unary", "*", "cgIMul", "/", "cgIDiv", "%", "cgIMod"));
4586 mixin(BuildExprBinOp!("Add", "Mul", "+", "cgIAdd", "-", "cgISub")); // binop `~` is here too, but we don't have it
4587 mixin(BuildExprBinOp!("Shift", "Add", "<<", "cgIShl", ">>", "cgIShr"));
4588 mixin(BuildExprBinOp!("BitAnd", "Shift", "&", "cgBitAnd"));
4589 mixin(BuildExprBinOp!("BitOr", "BitAnd", "|", "cgBitOr", "^", "cgBitXor"));
4590 mixin(BuildExprBinOp!("Cmp", "BitOr", "<", "cgLess", ">", "cgGreat", "==", "cgEqu", "!=", "cgNotEqu", "<=", "cgLEq", ">=", "cgGEq")); // `a is b`, `a in b` are here too
4591 mixin(BuildExprBinOp!("LogAnd", "Cmp", "&&", "cgLogAnd"));
4592 mixin(BuildExprBinOp!("LogOr", "LogAnd", "||", "cgLogOr"/*, "^^", "cgLogXor"*/));
4595 Value parseExpr () {
4596 // we'll do ternaries here
4597 auto eloc = token.loc;
4598 auto res = parseExprLogOr();
4599 if (token.delim("?")) {
4600 if (!res.type.isGoodForBool) compileErrorAt(eloc, "boolean-compatible expression expected");
4601 skipToken(); // skip '?'
4602 auto jumptofalse = JumpChain.startForward();
4603 // dead code elimination
4604 if (res.literal) {
4605 if (res.lit) {
4606 // ignore false part
4607 if (token.delim(":")) {
4608 // "?:"
4609 skipToken(); // skip ':'
4610 auto efloc = token.loc;
4611 auto ef = parseExpr(); // parse false part
4612 if (!TypeFunc.compatible(res.type, ef.type) && !TypeFunc.compatible(ef.type, res.type)) compileErrorAt(efloc, "incompatible types");
4613 // unwind, and return literal
4614 jumptofalse.unwindToBase();
4615 return res;
4616 } else {
4617 auto etloc = token.loc;
4618 auto et = parseExpr();
4619 jumptofalse = JumpChain.startHere(); // mark unwind point
4620 if (!token.delim(":")) compileError("':' expected");
4621 skipToken();
4622 auto efloc = token.loc;
4623 auto ef = parseExpr(); // parse false part
4624 if (!TypeFunc.compatible(et.type, ef.type) && !TypeFunc.compatible(ef.type, et.type)) compileErrorAt(efloc, "incompatible types");
4625 // unwind, and return true part
4626 jumptofalse.unwindToBase();
4627 return et;
4629 } else {
4630 // ignore true part
4631 if (token.delim(":")) {
4632 // "?:"
4633 skipToken(); // skip ':'
4634 auto efloc = token.loc;
4635 return parseExpr(); // parse false part
4636 } else {
4637 auto etloc = token.loc;
4638 auto et = parseExpr();
4639 if (!token.delim(":")) compileError("':' expected");
4640 skipToken();
4641 jumptofalse.unwindToBase(); // unwind true part
4642 auto efloc = token.loc;
4643 auto ef = parseExpr(); // parse false part
4644 if (!TypeFunc.compatible(et.type, ef.type) && !TypeFunc.compatible(ef.type, et.type)) compileErrorAt(efloc, "incompatible types");
4645 return ef; // return false part
4649 // load condition
4650 if (!res.stack) res = res.storeToSlot(Slot.makeTemp);
4651 assert(res.slot);
4652 if (token.delim(":")) {
4653 // '?:'
4654 skipToken();
4655 // generate jump over false (sorry for misleading name)
4656 jumptofalse.putJump(OpCode.JTrue, res.slot);
4657 // compile false part
4658 auto efloc = token.loc;
4659 auto ef = parseExpr(); // parse false part
4660 if (!TypeFunc.compatible(res.type, ef.type) && !TypeFunc.compatible(ef.type, res.type)) compileErrorAt(efloc, "incompatible types");
4661 // put this to res
4662 ef.storeToSlot(res.slot);
4663 jumptofalse.finishHere();
4664 } else {
4665 // generate jump to false part
4666 jumptofalse.putJump(OpCode.JFalse, res.slot);
4667 // compile true part
4668 auto etloc = token.loc;
4669 auto et = parseExpr();
4670 if (!token.delim(":")) compileError("':' expected");
4671 skipToken();
4672 // put this to res
4673 auto newret = et.storeToSlot(res.slot);
4674 // generate jump to end
4675 auto jumpexit = JumpChain.startForward();
4676 jumpexit.putJump(OpCode.Jump);
4677 // compile false part
4678 jumptofalse.finishHere();
4679 auto efloc = token.loc;
4680 auto ef = parseExpr(); // parse false part
4681 if (!TypeFunc.compatible(et.type, ef.type) && !TypeFunc.compatible(ef.type, et.type)) compileErrorAt(efloc, "incompatible types");
4682 // put this to res
4683 ef.storeToSlot(res.slot);
4684 jumpexit.finishHere();
4685 res = newret;
4688 return res;
4692 // parse function call or assign
4693 Value parseAssExpr () {
4694 auto unwindmark = JumpChain.startHere();
4695 auto lhs = parseExpr();
4696 if (token.delim("=")) {
4697 if (lhs.literal || lhs.stack) compileError("cannot assign to non-variable");
4698 if (lhs.readonly) compileError("cannot assign to read-only variable");
4699 lhs.cachePostponedValues(); // to avoid side effects
4700 skipToken();
4701 auto val = parseExpr();
4702 if (!lhs.assign(val)) compileError("cannot assign");
4703 } else if (token.delim && token.sval.length == 2 && token.sval[1] == '=') {
4704 OpCode opc;
4705 switch (token.sval[0]) {
4706 case '+': opc = OpCode.Add; break;
4707 case '-': opc = OpCode.Sub; break;
4708 case '*': opc = OpCode.Mul; break;
4709 case '/': opc = OpCode.Div; break;
4710 case '%': opc = OpCode.Mod; break;
4711 case '&': opc = OpCode.BitAnd; break;
4712 case '|': opc = OpCode.BitOr; break;
4713 case '^': opc = OpCode.BitXor; break;
4714 default: return lhs;
4716 if (lhs.literal || lhs.stack) compileError("cannot assign to non-variable");
4717 if (lhs.readonly) compileError("cannot assign to read-only variable");
4718 if (!lhs.type.isInt) compileErrorAt(lhs.loc, "integer variable expected");
4719 lhs.cachePostponedValues(); // to avoid side effects
4720 skipToken();
4721 auto val = parseExpr();
4722 if (!val.type.isInt) compileErrorAt(val.loc, "integer value expected");
4723 // optimize simple cases
4724 if (val.literal) {
4725 if (val.lit == 0 && (opc == OpCode.Div || opc == OpCode.Mod)) compileErrorAt(val.loc, "division by zero");
4726 if (val.lit == 0 && (opc == OpCode.Add || opc == OpCode.Sub || opc == OpCode.BitOr || opc == OpCode.BitXor)) {
4727 if (!lhs.arrayaccess && !lhs.fieldaccess) unwindmark.unwindToBase();
4728 return lhs;
4730 if (val.lit == 1 && (opc == OpCode.Mul || opc == OpCode.Div)) {
4731 if (!lhs.arrayaccess && !lhs.fieldaccess) unwindmark.unwindToBase();
4732 return lhs;
4735 //TODO: optimize for more literal cases
4736 if (val.literal && opc == OpCode.Add && val.lit == 1 && lhs.local && lhs.slot.valid && !lhs.arrayaccess && !lhs.fieldaccess && !lhs.ptrref) {
4737 // +1 for local
4738 emitCode(VMOp(OpCode.Inc, lhs.slot, lhs.slot));
4739 } else if (val.literal && opc == OpCode.Add && val.lit == -1 && lhs.local && lhs.slot.valid && !lhs.arrayaccess && !lhs.fieldaccess && !lhs.ptrref) {
4740 // -1 for local
4741 emitCode(VMOp(OpCode.Dec, lhs.slot, lhs.slot));
4742 } else if (val.literal && val.lit == 0 && (opc == OpCode.Add || opc == OpCode.Sub || opc == OpCode.BitOr || opc == OpCode.BitXor) && !lhs.arrayaccess && !lhs.fieldaccess && !lhs.ptrref) {
4743 // do nothing
4744 } else if (lhs.local && lhs.slot.valid && !lhs.arrayaccess && !lhs.fieldaccess && !lhs.ptrref) {
4745 val = val.load;
4746 emitCode(VMOp(opc, lhs.slot, lhs.slot, val.slot));
4747 } else {
4748 auto tmp = lhs.load;
4749 assert(tmp.slot);
4750 val = val.load;
4751 assert(val.slot);
4752 emitCode(VMOp(opc, tmp.slot, tmp.slot, val.slot));
4753 if (!lhs.assign(tmp)) compileErrorAt(lhs.loc, "assign failed");
4756 return lhs;
4760 // ////////////////////////////////////////////////////////////////////////// //
4761 TypeFunc parseFuncArgs (bool method) {
4762 auto stloc = token.loc;
4763 if (!token.delim("(")) compileError("'(' expected");
4764 skipToken();
4765 auto tf = new TypeFunc();
4766 if (method) {
4767 // add hidden `this`
4768 tf.args ~= TypeFunc.Arg("this", typeActor, false);
4770 while (!token.delim(")")) {
4771 if (token.empty) compileErrorAt(stloc, "unterminated argument list");
4772 if (token.delim("...")) { tf.hasrest = true; skipToken(); break; } // dotdotdot must be last one
4773 bool hasIn = false, hasRef = false;
4774 for (;;) {
4775 if (token.kw(Token.T.In)) {
4776 if (hasIn) compileError("duplicate 'in'");
4777 hasIn = true;
4778 } else if (token.kw(Token.T.Ref)) {
4779 if (hasRef) compileError("duplicate 'ref'");
4780 hasRef = true;
4781 } else {
4782 break;
4784 skipToken();
4786 //if (hasIn) compileError("`in` arguments aren't supported yet");
4787 //TODO: `auto` type (kind of generics)
4788 TypeBase tp = parseTypeDecl!true();
4789 if (tp is null) compileError("type name expected");
4790 if (tp.isVoid) compileError("`void` is not allowed here");
4791 if (tp.isArray) compileError("arrays are not allowed here");
4792 if (!tp.isStruct && hasRef) compileError("only structs can be `ref` for now");
4793 if (tp.isStruct && !hasRef) compileError("struct arguments must be `ref`");
4794 if (tf.args.length >= 64) compileError("too many function arguments");
4795 if (!token.id) compileError("identifier expected");
4796 foreach (const ref arg; tf.args) if (arg.name == token.sval) compileError("duplicate argument name '"~token.sval~"'");
4797 // convert `isFunc` to funcptr
4798 if (tp.isFunc) tp = new TypePointer(tp);
4799 tf.args ~= TypeFunc.Arg(token.sval, tp, hasRef, hasIn);
4800 skipToken(); // skip argument name
4801 // has default value?
4802 if (token.delim("=")) {
4803 // parse default value
4804 skipToken();
4805 auto dvloc = token.loc;
4806 auto val = parseExpr();
4807 if (!val.literal) compileErrorAt(dvloc, "default argument must be literal");
4808 if (val.type.isNull) {
4809 if (!tp.isActor && !tp.isPointer) compileErrorAt(dvloc, "invalid default argument type");
4810 } else {
4811 if (!tp.same(val.type)) compileErrorAt(dvloc, "invalid default argument type");
4813 tf.args[$-1].defval = val;
4815 if (!token.delim(",")) break;
4816 skipToken();
4818 if (!token.delim(")")) compileError("')' expected");
4819 skipToken();
4820 return tf;
4824 // ////////////////////////////////////////////////////////////////////////// //
4825 // this also used in `for (auto n = ...` parser
4826 // won't eat terminator (so it should be explicitly checked)
4827 bool parseVarDecl (bool allowMulti, out Value lastres) {
4828 TypeBase tp = parseTypeDecl!true();
4829 if (tp is null) return false;
4830 if (tp.isVoid) compileError("`void` is not allowed here");
4831 if (tp.isArray && tp.cellSize == 0) compileError("dynamic arrays aren't supported yet");
4832 if (!token.id) compileErrorAt(token.loc, "identifier expected");
4833 for (;;) {
4834 if (!token.id) compileErrorAt(token.loc, "identifier expected");
4835 auto var = enterVar(token.loc, token.sval, tp);
4836 bool isfirst = !lastres.valid;
4837 lastres = Value.makeVar(var, token.loc);
4838 skipToken();
4839 // initializer
4840 Value val;
4841 bool doStore = true;
4842 if (token.delim("=")) {
4843 if (tp.isArray) compileError("local array initializers aren't supported yet");
4844 skipToken();
4845 auto iloc = token.loc;
4846 val = parseExpr();
4847 if (!lastres.assign(val)) compileErrorAt(iloc, "invalid initializer type");
4848 if (isfirst && val.literal) lastres = val; // so dead code elimination can work
4849 doStore = false;
4850 } else {
4851 if (tp.isBool) val = Value.makeBool(false, token.loc);
4852 else if (tp.isInt) val = Value.makeInt(0, token.loc);
4853 else if (tp.isStr) val = Value.makeStr("", token.loc);
4854 else if (tp.isActor) val = Value.makeActor(0, token.loc);
4855 else if (tp.isEnum) val = Value.makeEnumDefault(tp, token.loc);
4856 else if (tp.isPointer) val = Value.makeNull(token.loc);
4857 else if (tp.isArray) {
4858 // local array
4859 doStore = false;
4860 // clear slots
4861 emitCode(VMOp.makeU16(OpCode.LArrClear, Slot.acquire(var.idx), cast(ushort)tp.cellSize));
4862 } else if (tp.isStruct) {
4863 // create local struct
4864 doStore = false;
4865 // clear slots
4866 if (tp.cellSize == 0) compileError("cannot create empty struct");
4867 emitCode(VMOp.makeU16(OpCode.LArrClear, Slot.acquire(var.idx), cast(ushort)tp.cellSize));
4869 else assert(0, "wtf?!");
4871 if (doStore) {
4872 if (!lastres.assign(val)) compileErrorAt(lastres.loc, "invalid initializer type");
4873 if (isfirst && val.literal) lastres = val; // so dead code elimination can work
4875 // next one
4876 if (!allowMulti) break;
4877 if (!token.delim(",")) break;
4878 skipToken();
4880 return true;
4884 // this also used in `for (auto n = ...` parser
4885 // won't eat terminator (so it should be explicitly checked)
4886 bool parseAutoDecl (bool allowMulti, out Value lastres) {
4887 if (!token.kw(Token.T.Auto)) return false;
4888 skipToken();
4889 if (!token.id) compileErrorAt(token.loc, "identifier expected");
4890 for (;;) {
4891 if (!token.id) compileErrorAt(token.loc, "identifier expected");
4892 auto vartoken = token;
4893 skipToken();
4894 // initializer
4895 if (!token.delim("=")) compileErrorAt(token.loc, "initializer expected");
4896 skipToken();
4897 auto iloc = token.loc;
4898 Value val = parseExpr();
4899 if (val.type.isBool || val.type.isInt || val.type.isStr || val.type.isActor || val.type.isEnum || val.type.isFuncPtr) {
4900 //stderr.writeln("auto (", val.type.cellSize, "): ", val.type.toPrettyString);
4901 auto var = enterVar(vartoken.loc, vartoken.sval, val.type);
4902 bool isfirst = !lastres.valid;
4903 lastres = Value.makeVar(var, vartoken.loc);
4904 if (!lastres.assign(val)) compileErrorAt(iloc, "invalid initializer type");
4905 if (isfirst && val.literal) lastres = val; // so dead code elimination can work
4906 } else {
4907 compileErrorAt(iloc, "invalid initializer type");
4909 // next one
4910 if (!allowMulti) break;
4911 if (!token.delim(",")) break;
4912 skipToken();
4914 return true;
4918 // parse condition expression with possible declaration
4919 Value parseExprWithPossibleDecl(string mode) () {
4920 static assert(mode == "for" || mode == "if", "invalid mode");
4921 static if (mode == "for") enum allowMulti = true; else enum allowMulti = false;
4922 Value res;
4923 // auto or vardecl?
4924 if (!parseAutoDecl(allowMulti, res) && !parseVarDecl(allowMulti, res)) {
4925 // not a declaration, parse expression
4926 static if (mode == "for") {
4927 res = parseAssExpr();
4928 } else {
4929 res = parseExpr();
4932 return res;
4936 // ////////////////////////////////////////////////////////////////////////// //
4937 // keyword parsers
4939 // we may want to have more info later, so let's create structure now
4940 struct ParseInfo {
4941 bool returnSeen;
4942 bool breakSeen;
4943 bool contSeen;
4945 void merge() (in auto ref ParseInfo pi) pure nothrow @safe @nogc {
4946 returnSeen = returnSeen && pi.returnSeen;
4947 breakSeen = breakSeen || pi.breakSeen;
4948 contSeen = contSeen || pi.contSeen;
4951 static ParseInfo makeReturn () nothrow @safe @nogc { pragma(inline, true); return ParseInfo(true); }
4952 static ParseInfo makeBreak () nothrow @safe @nogc { pragma(inline, true); return ParseInfo(false, true); }
4953 static ParseInfo makeContinue () nothrow @safe @nogc { pragma(inline, true); return ParseInfo(false, false, true); }
4957 ParseInfo parseReturn (in ref Loc statloc) {
4958 auto eloc = token.loc;
4959 if (curfunc.type !is null && !curfunc.type.aux.isVoid) {
4960 auto val = parseExpr();
4961 if (!curfunc.type.aux.same(val.type)) {
4962 // return type conversion
4963 if (curfunc.type.aux.isBool) {
4964 // any -> bool
4965 val.storeToSlot!"bool"(Slot.acquire(0));
4966 } else if (val.type.isBool) {
4967 if (!curfunc.type.aux.isInt) compileErrorAt(eloc, "invalid return type");
4968 val.storeToSlot(Slot.acquire(0));
4969 } else if (val.type.isNull) {
4970 if (!curfunc.type.aux.isPointer && !curfunc.type.aux.isActor) compileErrorAt(eloc, "invalid return type");
4971 val = Value.makeInt(0, val.loc);
4972 //HACK
4973 val.type = curfunc.type.aux;
4974 val.storeToSlot(Slot.acquire(0));
4976 } else {
4977 val.storeToSlot(Slot.acquire(0));
4980 emitCode(VMOp(OpCode.Ret));
4981 if (!token.delim(";")) compileError("';' expected");
4982 skipToken();
4983 return ParseInfo.makeReturn();
4987 ParseInfo parseIf (in ref Loc statloc) {
4988 // parse condition
4989 ParseInfo pires;
4990 auto eloc = token.loc;
4991 if (!token.delim("(")) compileError("'(' expected");
4992 skipToken();
4993 enterScope(); // why not? it is easier this way
4994 scope(exit) leaveScope();
4995 auto val = parseExprWithPossibleDecl!"if"();
4996 if (!token.delim(")")) compileError("')' expected");
4997 skipToken();
4998 if (!val.type.isGoodForBool) compileErrorAt(eloc, "boolean expression expected");
4999 // dead code elimination
5000 if (val.literal) {
5001 // true branch
5002 auto rewindpc = JumpChain.startHere();
5003 pires = parseStatement();
5004 // rewind true branch if condition is false
5005 if (!val.lit) {
5006 rewindpc.unwindToBase();
5007 // ...and reset flags
5008 pires = ParseInfo.init;
5010 // has `else`?
5011 if (token.kw(Token.T.Else)) {
5012 // false branch
5013 rewindpc = JumpChain.startHere();
5014 skipToken();
5015 auto pri = parseStatement();
5016 // rewind false branch if condition is false
5017 if (val.lit) {
5018 rewindpc.unwindToBase();
5019 } else {
5020 // ...or use it's ParserInfo as result
5021 pires = pri;
5024 } else {
5025 val = val.load;
5026 assert(val.stack);
5027 // generate jump over true branch
5028 auto jovertrue = JumpChain.startForward();
5029 jovertrue.putJump(OpCode.JFalse, val.slot);
5030 pires = parseStatement();
5031 // has `else`?
5032 if (token.kw(Token.T.Else)) {
5033 skipToken();
5034 // yep; gen jump over else
5035 auto joverfalse = JumpChain.startForward();
5036 joverfalse.putJump(OpCode.Jump);
5037 // patch jump over true
5038 jovertrue.finishHere();
5039 // `else` branch
5040 pires.merge(parseStatement());
5041 // patch jump over else
5042 joverfalse.finishHere();
5043 } else {
5044 // nope, no `else`; patch jump over true here
5045 jovertrue.finishHere();
5046 // reset ParserInfo, 'cause else branch is empty
5047 pires = ParseInfo.init;
5050 return pires;
5054 ParseInfo parseWhile (in ref Loc statloc) {
5055 ParseInfo pires;
5056 auto oldcontpc = curcontpc;
5057 auto oldbreakpc = curbreakpc;
5058 scope(exit) { curcontpc = oldcontpc; curbreakpc = oldbreakpc; }
5059 // mark code start (so we can rewind codegen on dead code elimination step)
5060 // also, this is "continue" jump point
5061 curcontpc = JumpChain.startHere();
5062 curbreakpc = JumpChain.startForward();
5063 // parse condition
5064 auto condloc = token.loc;
5065 if (!token.delim("(")) compileError("'(' expected");
5066 skipToken();
5067 auto cond = parseExpr();
5068 if (!token.delim(")")) compileError("')' expected");
5069 skipToken();
5070 if (!cond.type.isGoodForBool) compileErrorAt(condloc, "boolean expression expected");
5071 // dead code elimination
5072 bool infiniloop = false;
5073 if (cond.literal && !cond.lit) {
5074 // the while loop can be ommited (and it doesn't matter what statement we've seen there)
5075 parseStatement();
5076 curcontpc.unwindToBase(); // remove the whole loop
5077 } else {
5078 // generate condition check and "break" (if condition is not known)
5079 if (!cond.literal) {
5080 cond = cond.load;
5081 curbreakpc.putJump(OpCode.JFalse, cond.slot);
5082 } else {
5083 assert(cond.lit != 0);
5084 infiniloop = true;
5086 pires = parseStatement();
5087 // jump back to start
5088 curcontpc.putJump(OpCode.Jump);
5089 // finish `break` and `continue` chains
5090 curcontpc.finishHere();
5091 curbreakpc.finishHere();
5093 // (rough) check if loop is infinite
5094 if (!infiniloop || pires.breakSeen) {
5095 // it doesn't matter if we had `return` in loop, as we had `break` too
5096 pires.returnSeen = false;
5098 pires.breakSeen = false;
5099 pires.contSeen = false;
5100 return pires;
5104 ParseInfo parseBreak (in ref Loc statloc) {
5105 if (!curbreakpc.valid) compileError("`break` outside of loop/switch");
5106 if (!token.delim(";")) compileError("';' expected");
5107 skipToken();
5108 curbreakpc.putJump(OpCode.Jump);
5109 return ParseInfo.makeBreak();
5113 ParseInfo parseContinue (in ref Loc statloc) {
5114 if (!curcontpc.valid) compileError("`continue` outside of loop");
5115 if (!token.delim(";")) compileError("';' expected");
5116 skipToken();
5117 curcontpc.putJump(OpCode.Jump);
5118 return ParseInfo.makeContinue();
5122 ParseInfo parseFor (in ref Loc statloc) {
5123 ParseInfo pires;
5124 if (!token.delim("(")) compileError("'(' expected");
5125 skipToken();
5126 auto oldcontpc = curcontpc;
5127 auto oldbreakpc = curbreakpc;
5128 enterScope(); // why not? it is easier this way
5129 scope(exit) { curcontpc = oldcontpc; curbreakpc = oldbreakpc; leaveScope(); }
5130 // compile init expression
5131 if (!token.delim(";")) parseExprWithPossibleDecl!"for"();
5132 if (!token.delim(";")) compileError("';' expected");
5133 skipToken();
5134 // mark code start (so we can rewind codegen on dead code elimination step)
5135 // also, this is "continue" jump point
5136 auto forstartpc = JumpChain.startHere(); // if condition is false, we'll unwind the whole loop
5137 curcontpc = JumpChain.startForward();
5138 curbreakpc = JumpChain.startForward();
5139 auto jumpToCond = JumpChain.startHere(); // address of condition: we should jump here after "next"
5140 bool docondition = true;
5141 // compile condition
5142 auto condloc = token.loc;
5143 Value cond = (token.delim(";") ? Value.makeBool(true, condloc) : parseExpr());
5144 if (!cond.type.isGoodForBool) compileErrorAt(condloc, "boolean expression expected");
5145 bool infiniloop = false;
5146 // dead code elimination
5147 if (cond.literal) {
5148 // expression evaluation can be ommited
5149 curcontpc.unwindToBase(); // rewind it, just in case
5150 curcontpc.startHere();
5151 docondition = false;
5152 infiniloop = (cond.lit != 0);
5153 } else {
5154 // get out of here if condition is false
5155 cond = cond.load;
5156 // emit break
5157 curbreakpc.putJump(OpCode.JFalse, cond.slot);
5159 // do we have "next" expression?
5160 if (!token.delim(")")) {
5161 // yep, compile it
5162 if (!token.delim(";")) compileError("';' expected");
5163 skipToken();
5164 // gnerate jump over next, and code for next
5165 if (!token.delim(")")) {
5166 auto overnext = JumpChain.startForward();
5167 overnext.putJump(OpCode.Jump);
5168 curcontpc.setHere(); // "continue" will jump here
5169 auto xpc = vmcode.length;
5170 // allow several expressions, delimited by comma
5171 for (;;) {
5172 parseAssExpr();
5173 if (!token.delim(",")) break;
5174 skipToken();
5176 if (xpc == vmcode.length) {
5177 // no interesting code was generated, remove jump
5178 overnext.unwindToBase();
5179 // no "next", so "continue" is the same as "cond"
5180 curcontpc = jumpToCond;
5181 } else {
5182 // jump to condition
5183 if (docondition) jumpToCond.putJump(OpCode.Jump);
5184 // fix jump over "next"
5185 overnext.finishHere();
5187 } else {
5188 // no "next", so "continue" is the same as "cond"
5189 curcontpc = jumpToCond;
5191 } else {
5192 // no "next", so "continue" is the same as "cond"
5193 curcontpc = jumpToCond;
5195 if (!token.delim(")")) compileError("')' expected");
5196 skipToken();
5197 pires = parseStatement();
5198 // jump back to start
5199 curcontpc.putJump(OpCode.Jump);
5200 // finish `break` and `continue` chains
5201 curcontpc.finishHere();
5202 curbreakpc.finishHere();
5203 // eliminate dead code
5204 if (cond.literal && !cond.lit) {
5205 forstartpc.unwindToBase();
5206 pires = ParseInfo.init;
5207 } else {
5208 // (rough) check if loop is infinite
5209 if (!infiniloop || pires.breakSeen) {
5210 // it doesn't matter if we had `return` in loop, as we had `break` too
5211 pires.returnSeen = false;
5214 pires.breakSeen = false;
5215 pires.contSeen = false;
5216 return pires;
5220 ParseInfo parseAlias (in ref Loc statloc) {
5221 if (!token.id) compileError("identifier expected");
5222 while (!token.delim(";")) {
5223 if (!token.id) compileError("identifier expected");
5224 string aname = token.sval;
5225 skipToken();
5226 if (!token.delim("=")) compileError("'=' expected");
5227 skipToken();
5228 if (!token.id) compileError("identifier expected");
5229 string dstname = token.sval;
5230 skipToken();
5231 // create new alias
5232 curscope.aliases[aname] = dstname;
5233 if (!token.delim(",")) break;
5234 skipToken();
5236 if (!token.delim(";")) compileError("';' expected");
5237 skipToken();
5238 return ParseInfo.init;
5242 ParseInfo parseStatement () {
5243 Value lres; // dummy
5244 // empty statements aren't allowed
5245 if (token.delim(";")) compileError("use '{}' as empty statement");
5246 if (token.delim("{")) return parseCodeBlock();
5247 // auto or vardecl?
5248 if (!parseAutoDecl(true, lres) && !parseVarDecl(true, lres)) {
5249 // do you love D metaprogramming? i do!
5250 foreach (string mname; __traits(allMembers, Token.T)) {
5251 static if (__traits(compiles, () => mixin("&parse"~mname))) {
5252 if (token.kw(__traits(getMember, Token.T, mname))) {
5253 auto loc = token.loc;
5254 skipToken();
5255 mixin("return parse"~mname~"(loc);");
5259 // parse expression
5260 auto eloc = token.loc;
5261 auto val = parseAssExpr();
5263 if (!token.delim(";")) compileError("';' expected");
5264 skipToken();
5265 return ParseInfo.init;
5269 ParseInfo parseCodeBlock(bool openScope=true) () {
5270 ParseInfo pires;
5271 if (token.delim("{")) {
5272 auto stloc = token.loc;
5273 skipToken();
5274 static if (openScope) {
5275 enterScope();
5276 scope(exit) leaveScope();
5278 bool first;
5279 for (;;) {
5280 if (token.empty) compileErrorAt(stloc, "unterminated code block");
5281 if (token.delim("}")) { skipToken(); break; }
5282 auto pri = parseStatement();
5283 pires.returnSeen = pires.returnSeen || pri.returnSeen;
5285 } else {
5286 pires = parseStatement();
5288 return pires;
5292 // `CheckStack` already generated (always generated)
5293 void parseFuncBody () {
5294 auto fstloc = token.loc;
5295 auto startpc = cast(int)vmcode.length; // for later checks
5296 if (!token.delim("{")) compileErrorAt(fstloc, "'{' expected");
5297 ParseInfo pires = parseCodeBlock!false();
5298 // check if we have `return` for non-void function
5299 if (!curfunc.type.aux.isVoid) {
5300 if (!pires.returnSeen) compileErrorAt(fstloc, "non-void function has undefined return value");
5302 // if previous instruction is `Ret`, and we have no jumps here...
5303 // WARNING: this assumes that no weird code was generated
5304 bool genret = true;
5305 if (vmcode[$-1].opcode == OpCode.Ret) {
5306 genret = false;
5307 checkloop: foreach (immutable ofs, const ref VMOp opc; vmcode[startpc..$]) {
5308 switch (opc.opcode) {
5309 case OpCode.Jump:
5310 case OpCode.JTrue:
5311 case OpCode.JFalse:
5312 int dest = startpc+cast(int)ofs+1+opc.s16;
5313 if (dest > vmcode.length) assert(0, "internal compiler error");
5314 if (dest == vmcode.length) { genret = true; break checkloop; }
5315 break;
5316 default: break;
5320 if (genret) emitCode(VMOp(OpCode.Ret));
5324 // this function should be called after `scanTopLevelDecls()`, 'cause alot of syntax checking is done there
5325 void compileSource () {
5326 // delimiter is skipped too
5327 void skipUntilDelim (string dm) {
5328 while (!token.delim(dm)) {
5329 if (token.empty) compileError("unexpected end of file");
5330 skipToken();
5332 skipToken();
5335 if (vmcode.length == 0) {
5336 vmcode.reserve(65536); // why not? we have plenty of memory anyway
5337 vmcode ~= VMOp(OpCode.Nop); // 0 is reserved
5340 while (!token.empty) {
5341 if (token.delim(";")) { skipToken(); continue; }
5342 auto stloc = token.loc;
5343 // top-level declarations should be removed by `scanTopLevelDecls()`
5344 if (token.kw(Token.T.Alias) || token.kw(Token.T.Enum) || token.kw(Token.T.Const) || token.id("field")) assert(0, "internal compiler error");
5345 bool ismethod = token.kw(Token.T.Method);
5346 string methodactid;
5347 if (ismethod) skipToken();
5348 // parse `method(actorid)`
5349 if (ismethod && token.delim("(")) {
5350 skipToken();
5351 if (!token.id) compileError("identifier expected");
5352 methodactid = token.sval;
5353 skipToken();
5354 if (!token.delim(")")) compileError("')' expected");
5355 skipToken();
5357 // get type
5358 TypeBase tp = parseTypeDecl!false();
5359 if (tp is null) compileError("type name expected");
5360 if (!token.id) compileError("identifier expected");
5361 string name = token.sval;
5362 skipToken();
5363 // it must be function (everything else was processed and removed in `scanTopLevelDecls()`
5364 auto tf = parseFuncArgs(ismethod);
5365 tf.aux = tp;
5366 if (!token.delim("{")) compileError("'{' expected");
5367 // function body
5369 scope(exit) curfunc = FuncInfo.init;
5370 // find function
5371 string fullname = (methodactid.length ? methodactid~"::"~name : name);
5372 Variable var = null;
5373 int fnidx = -1;
5374 if (auto fp = fullname in funclist) {
5375 foreach (immutable idx, ref fni; fp.ovloads) if (tf.same(fni.type)) { fnidx = cast(int)idx; break; }
5376 if (fnidx >= 0) var = *fp;
5378 if (var is null) compileErrorAt(stloc, "function '"~fullname~"' not found");
5379 curfunc = var.ovloads[fnidx];
5380 if (curfunc.pc < 0) compileErrorAt(stloc, "cannot implement builtins");
5381 if (curfunc.pc != 0) compileErrorAt(stloc, "duplicate function definition");
5382 curmaxreg = 0;
5383 // setup compiler and compile function body
5384 auto codeofs = cast(int)vmcode.length;
5386 enterScope();
5387 scope(exit) leaveScope();
5388 curslots[] = 0; // no used slots
5389 curslots[0] = -1; // return value
5390 // enter arguments
5391 foreach (immutable aidx, ref arg; curfunc.type.args) {
5392 auto av = new Variable();
5393 av.idx = -(cast(int)aidx+1);
5394 av.name = arg.name;
5395 av.type = arg.type;
5396 if (arg.ptrref) av.kind = Variable.Kind.Reference;
5397 av.readonly = arg.readonly;
5398 curscope.vars ~= av;
5400 //stderr.writeln("*********** ", curfunc.name, " *********** ", curslots);
5401 emitCode(VMOp(OpCode.CheckStack)); // will be patched later
5402 parseFuncBody();
5404 if (curmaxreg >= short.max-1) compileErrorAt(stloc, "too many stacks slots used");
5405 vmcode[codeofs].setU16(cast(ushort)(curmaxreg+1));
5406 version(mesdbg_dump_compiled_code) {
5407 if (!MESDisableDumps) {
5408 stderr.writeln("==== ", curfunc.name, " ==== (", curmaxreg+1, ")");
5409 foreach (immutable pc; codeofs..vmcode.length) {
5410 stderr.writefln("%04X: %s", pc, decodeOpcode(cast(int)pc, vmcode[pc]));
5412 stderr.writeln("----");
5415 if (vmcode.length >= ushort.max) compileErrorAt(stloc, "vm code section too big");
5416 var.ovloads[fnidx].pc = codeofs;
5419 //fixupDump(stderr);
5420 fixupCalls(); // why not
5424 // ////////////////////////////////////////////////////////////////////////// //
5425 // this collects source code tokens, and parses top-level declarations
5426 void scanTopLevelDecls (ParserBase curparser) {
5427 if (curparser.tk.empty) return; // nothing to do
5429 auto origstpos = curtokidx; // we'll rewind here, so `compileSource()` can process it all again
5430 scope(exit) {
5431 curtokidx = origstpos;
5432 cursource.assumeSafeAppend;
5435 // collect and remember all tokens
5436 // remember this, so we'll know the starting point
5437 while (!curparser.tk.empty) {
5438 cursource ~= curparser.tk;
5439 curparser.next();
5442 static int markTokenPos () nothrow @trusted @nogc { return cast(int)curtokidx; }
5444 static void removeTokensFromMark (int pos) /*nothrow*/ {
5445 assert(pos >= 0 && pos <= cursource.length);
5446 assert(curtokidx >= 0 && curtokidx <= cursource.length);
5447 if (pos == cursource.length) return;
5449 // easy case?
5450 if (curtokidx >= cursource.length) {
5451 cursource.length = pos;
5452 curtokidx = cast(int)cursource.length;
5453 return;
5455 // ok, move tokens
5456 auto dest = pos;
5457 foreach (immutable cidx; curtokidx..cursource.length) cursource.ptr[dest++] = cursource.ptr[cidx];
5458 assert(dest <= cursource.length);
5459 cursource.length = dest;
5460 curtokidx = pos;
5463 while (!token.empty) {
5464 auto tokmark = markTokenPos();
5465 if (token.delim(";")) {
5466 skipToken();
5467 removeTokensFromMark(tokmark);
5468 continue;
5470 auto stloc = token.loc;
5471 // alias?
5472 if (token.kw(Token.T.Alias)) {
5473 skipToken();
5474 if (!token.id) compileError("identifier expected");
5475 while (!token.delim(";")) {
5476 if (!token.id) compileError("identifier expected");
5477 string aname = token.sval;
5478 skipToken();
5479 if (!token.delim("=")) compileError("'=' expected");
5480 skipToken();
5481 if (!token.id) compileError("identifier expected");
5482 string dstname = token.sval;
5483 skipToken();
5484 // create new alias
5485 aliases[aname] = dstname;
5486 if (!token.delim(",")) break;
5487 skipToken();
5489 if (!token.delim(";")) compileError("';' expected");
5490 skipToken();
5491 removeTokensFromMark(tokmark);
5492 continue;
5494 // enum ?
5495 if (token.kw(Token.T.Enum)) {
5496 auto defloc = token.loc;
5497 skipToken();
5498 // typed enum ?
5499 TypeEnum etype = null;
5500 if (token.id) {
5501 if (token.sval in gconsts) compileErrorAt(stloc, "duplicate constant");
5502 if (token.sval in globals) compileErrorAt(stloc, "constant/global variable conflict");
5503 if (token.sval in funclist) compileErrorAt(stloc, "constant/function conflict");
5504 etype = new TypeEnum(token.sval, typeInt); // the only possible enum type for now
5505 skipToken();
5506 auto c = new Constant();
5507 c.name = etype.name;
5508 c.type = etype;
5509 gconsts[c.name] = c;
5511 if (!token.delim("{")) compileError("'{' expected");
5512 skipToken();
5513 int curval = 0;
5514 string membername = null;
5515 Constant c = null;
5516 while (!token.delim("}")) {
5517 if (!token.id) compileError("identifier expected");
5518 // get member name, check for conflicts
5519 if (etype is null) {
5520 if (token.sval in gconsts) compileError("duplicate constant");
5521 if (token.sval in globals) compileError("constant/global variable conflict");
5522 if (token.sval in funclist) compileError("constant/function conflict");
5523 c = new Constant();
5524 c.name = token.sval;
5525 c.type = typeInt;
5526 } else {
5527 if (token.sval in etype.members) compileError("duplicate enum member");
5528 membername = token.sval;
5529 if (membername == "min" || membername == "max") compileError("cannot redefine enum properties");
5531 skipToken();
5532 // parse initializer
5533 if (token.delim("=")) {
5534 skipToken();
5535 auto eloc = token.loc;
5536 auto v = parseExpr();
5537 if (!v.literal) compileErrorAt(eloc, "literal expected");
5538 if (!v.type.isInt) compileErrorAt(eloc, "integer literal expected");
5539 curval = v.lit;
5541 // introduce new constant or member
5542 if (etype is null) {
5543 // constant
5544 c.ival = curval++;
5545 gconsts[c.name] = c;
5546 } else {
5547 // member
5548 // if enum has no members, first one will become default
5549 if (etype.members.length == 0) etype.mDefValue = curval;
5550 etype.members[membername] = curval++;
5552 if (!token.delim(",")) break;
5553 skipToken();
5555 if (!token.delim("}")) compileError("'}' expected");
5556 skipToken();
5557 // register new typed enum
5558 if (etype !is null) {
5559 if (etype.name in gtypes) compileErrorAt(defloc, "duplicate type declaration");
5560 gtypes[etype.name] = etype;
5562 removeTokensFromMark(tokmark);
5563 continue;
5565 // struct?
5566 if (token.kw(Token.T.Struct)) {
5567 auto tpos = curtokidx;
5568 skipToken();
5569 if (token.id) {
5570 // rollback
5571 curtokidx = tpos;
5572 auto defloc = token.loc;
5573 auto stp = parseStructDecl(true);
5574 if (stp.name in gconsts) compileError("struct/constant conflict");
5575 if (stp.name in globals) compileError("struct/global variable conflict");
5576 if (stp.name in funclist) compileError("struct/function conflict");
5577 // register new struct type
5578 if (stp.name in gtypes) compileErrorAt(defloc, "duplicate type declaration");
5579 gtypes[stp.name] = stp;
5580 removeTokensFromMark(tokmark);
5581 continue;
5582 } else {
5583 // rollback, so we'll parse type
5584 curtokidx = tpos;
5587 // others
5588 bool isfield = token.id("field");
5589 bool isconst = token.kw(Token.T.Const);
5590 bool ismethod = token.kw(Token.T.Method);
5591 string methodactid;
5592 if (isfield || isconst || ismethod) skipToken();
5593 // parse `method(actorid)`
5594 if (ismethod && token.delim("(")) {
5595 skipToken();
5596 if (!token.id) compileError("identifier expected");
5597 methodactid = token.sval;
5598 skipToken();
5599 if (!token.delim(")")) compileError("')' expected");
5600 skipToken();
5602 // get type
5603 TypeBase tp = parseTypeDecl!false();
5604 if (tp is null) compileError("type name expected");
5605 if (!token.id) compileError("identifier expected");
5606 string name = token.sval;
5607 skipToken();
5608 // actor field? add it here, why not
5609 if (isfield) {
5610 if (tp.isVoid) compileError("`void` is not allowed here");
5611 if (tp.isFunc) tp = new TypePointer(tp); // convert `.isFunc` to funcptr
5612 //if (tp.isArray) compileError("array fields aren't supported yet");
5613 //if (tp.isStruct) compileError("struct fields aren't supported yet");
5614 for (;;) {
5615 if (auto fld = actorFindFieldPtr(name)) {
5616 // should be of exactly the same type
5617 if (!fld.type.same(tp)) compileErrorAt(stloc, "conflicting field type, previous definition at '"~fld.loc.fname.idup~"':"~fld.loc.toString);
5618 } else {
5619 // new field
5620 auto af = actorCreateField(name, tp, stloc);
5621 if (!af.valid) compileError("cannot create field '"~name~"'");
5623 if (!token.delim(",")) break;
5624 skipToken();
5625 if (!token.id) compileError("identifier expected");
5626 name = token.sval;
5627 skipToken();
5629 if (!token.delim(";")) compileError("';' expected");
5630 skipToken();
5631 removeTokensFromMark(tokmark);
5632 continue;
5634 // constant?
5635 if (isconst) {
5636 if (name in gconsts) compileErrorAt(stloc, "duplicate constant");
5637 if (name in globals) compileErrorAt(stloc, "constant/global variable conflict");
5638 if (name in funclist) compileErrorAt(stloc, "constant/function conflict");
5639 if (!token.delim("=")) compileError("'=' expected");
5640 skipToken();
5641 auto eloc = token.loc;
5642 auto v = parseExpr();
5643 if (!token.delim(";")) compileError("';' expected");
5644 skipToken();
5645 if (!v.literal) compileErrorAt(eloc, "literal expected");
5646 if (!v.type.isBool && !v.type.isInt && !v.type.isStr) compileErrorAt(eloc, "invalid constant type");
5647 if (!v.type.same(tp)) compileErrorAt(eloc, "invalid constant type");
5648 auto c = new Constant();
5649 c.name = name;
5650 c.type = v.type;
5651 if (v.type.isStr) c.sval = strlist[v.lit]; else c.ival = v.lit;
5652 gconsts[c.name] = c;
5653 removeTokensFromMark(tokmark);
5654 continue;
5656 // global var?
5657 if (!ismethod && !token.delim("(")) {
5658 // name already eaten
5659 if (tp.isArray) {
5660 if (!tp.base.isBool && !tp.base.isInt && !tp.base.isStr && !tp.base.isActor && !tp.base.isFuncPtr && !tp.base.isStruct && !tp.base.isEnum) compileErrorAt(stloc, "invalid type for global variable");
5661 } else {
5662 if (!tp.isBool && !tp.isInt && !tp.isStr && !tp.isActor && !tp.isFunc && !tp.isStruct && !tp.isEnum) compileErrorAt(stloc, "invalid type for global variable");
5664 if (tp.isFunc) tp = new TypePointer(tp); // convert `.isFunc` to funcptr
5665 for (;;) {
5666 if (name in gconsts) compileErrorAt(stloc, "global variable/constant conflict");
5667 if (name in globals) compileErrorAt(stloc, "duplicate global variable");
5668 if (name in funclist) compileErrorAt(stloc, "global variable/function conflict");
5669 Variable var;
5670 //TODO: initializers for arrays with fixed length
5671 if (tp.isArray && tp.cellSize == 0) {
5672 // array with initializer
5673 if (!token.delim("=")) compileError("array initializer expected");
5674 skipToken();
5675 if (!token.delim("[")) compileError("'[' expected");
5676 skipToken();
5677 var = new Variable();
5678 var.idx = cast(ushort)vmglobals.length;
5679 auto atp = new TypeArray(tp.base, 0);
5680 while (!token.delim("]")) {
5681 auto exprloc = token.loc;
5682 auto val = parseExpr();
5683 if (!val.literal) compileErrorAt(exprloc, "literal expected");
5684 if (!val.type.same(atp.base)) compileErrorAt(exprloc, "invalid initializer type");
5685 if (vmglobals.length+atp.cellSize >= short.max-1) compileError("too many global variables");
5686 vmglobals ~= val.lit;
5687 atp.setSize(atp.size+1);
5688 if (!token.delim(",")) break;
5689 skipToken();
5691 if (!token.delim("]")) compileError("']' expected");
5692 skipToken();
5693 var.type = atp;
5694 } else {
5695 if (vmglobals.length >= short.max || vmglobals.length+tp.cellSize > short.max) compileError("too many global variables");
5696 var = new Variable();
5697 var.idx = cast(ushort)vmglobals.length;
5698 var.type = tp;
5699 vmglobals.length += tp.cellSize;
5700 vmglobals[var.idx..$] = 0; // just in case
5702 var.name = name;
5703 var.kind = Variable.Kind.Global;
5704 globals[var.name] = var;
5705 if (!token.delim(",")) break;
5706 skipToken();
5707 if (!token.id) compileError("identifier expected");
5708 name = token.sval;
5709 skipToken();
5711 if (!token.delim(";")) compileError("';' expected");
5712 skipToken();
5713 removeTokensFromMark(tokmark);
5714 continue;
5716 // function/method
5717 if (tp.isArray) compileError("array return values aren't supported yet");
5718 if (tp.isStruct) compileError("struct return values aren't supported yet");
5719 auto tf = parseFuncArgs(ismethod);
5720 tf.aux = tp;
5721 int oldfound = -1;
5722 string fullname = (methodactid.length ? methodactid~"::"~name : name);
5723 auto fp = fullname in funclist;
5724 if (fp !is null) {
5725 foreach (immutable idx, ref fni; fp.ovloads) {
5726 if (fni.type.callCompatibleNoAux(tf)) { oldfound = cast(int)idx; break; }
5729 // skip prototypes
5730 if (oldfound >= 0) {
5731 if (!fp.ovloads[oldfound].type.aux.same(tf.aux) || !fp.ovloads[oldfound].type.same(tf)) compileError("found declaration with different return type, previous one is at "~fp.ovloads[oldfound].loc.toFullString);
5732 if (token.id("builtin")) {
5733 if (fp.ovloads[oldfound].pc >= 0) compileError("conflicting declaration, previous one is at "~fp.ovloads[oldfound].loc.toFullString);
5734 skipToken();
5735 if (!token.delim(";")) compileError("';' expected");
5736 skipToken();
5737 removeTokensFromMark(tokmark);
5738 continue;
5739 } else {
5740 if (fp.ovloads[oldfound].pc < 0) compileError("conflicting declaration, previous one is at "~fp.ovloads[oldfound].loc.toFullString);
5741 if (fp.ovloads[oldfound].hasBody && !token.delim(";")) compileError("';' expected");
5742 if (token.delim(";")) {
5743 skipToken();
5744 removeTokensFromMark(tokmark);
5745 continue;
5749 // new function
5750 Variable var;
5751 if (fp is null) {
5752 if (fullname in gconsts) compileErrorAt(stloc, "function/constant conflict");
5753 if (fullname in globals) compileErrorAt(stloc, "function/global variable conflict");
5754 if (funclist.length >= ushort.max) compileErrorAt(stloc, "too many functions");
5755 var = new Variable();
5756 var.idx = 0;
5757 var.kind = Variable.Kind.Global;
5758 var.name = fullname;
5759 var.type = tf;
5760 var.firstloc = stloc;
5761 funclist[fullname] = var;
5762 } else {
5763 var = *fp;
5765 if (oldfound < 0) {
5766 FuncInfo fi;
5767 fi.type = tf;
5768 fi.pc = 0;
5769 fi.name = fullname;
5770 fi.actorid = methodactid;
5771 fi.shortname = name;
5772 fi.loc = stloc;
5773 var.ovloads ~= fi;
5774 oldfound = cast(int)var.ovloads.length-1;
5776 if (token.id("builtin")) {
5777 if (var.ovloads[oldfound].hasBody) compileError("buildins cannot have body");
5778 var.ovloads[$-1].pc = fixBuiltin(var.ovloads[oldfound].name, var.ovloads[oldfound].type);
5779 skipToken();
5780 if (!token.delim(";")) compileError("';' expected");
5781 skipToken();
5782 removeTokensFromMark(tokmark);
5783 continue;
5784 } else {
5785 if (tf.hasrest) compileErrorAt(stloc, "scriped vararg functions aren't supported yet");
5787 // prototype?
5788 if (token.delim(";")) {
5789 skipToken();
5790 removeTokensFromMark(tokmark);
5791 } else {
5792 if (var.ovloads[oldfound].hasBody) compileError("duplicate body");
5793 // function with body, skip the body
5794 if (!token.delim("{")) compileError("'{' expected");
5795 int level = 0;
5796 for (;;) {
5797 if (token.empty) compileErrorAt(stloc, "unterminated function declaration");
5798 if (token.delim("{")) { ++level; }
5799 else if (token.delim("}")) { if (--level == 0) break; }
5800 skipToken();
5802 skipToken();
5808 // ////////////////////////////////////////////////////////////////////////// //
5809 public void mesIntroduceConstant(CT, NT:const(char)[]) (NT name, CT val)
5810 if (is(CT == int) || is(CT == bool) || is(CT : const(char)[]))
5812 if (name in gconsts) compileError("duplicate constant");
5813 if (name in globals) compileError("constant/global variable conflict");
5814 if (name in funclist) compileError("constant/function conflict");
5815 auto c = new Constant();
5816 static if (is(NT == string)) c.name = name; else c.name = name.idup;
5817 static if (is(CT == int)) { c.type = typeInt; c.ival = val; }
5818 else static if (is(CT == bool)) { c.type = typeBool; c.ival = (val ? 1 : 0); }
5819 else static if (is(CT : const(char)[])) {
5820 c.type = typeStr;
5821 if (val.length) {
5822 static if (is(CT == string)) c.sval = val; else c.sval = val.idup;
5823 } else {
5824 c.sval = "";
5826 } else {
5827 static assert(0, "oops");
5829 gconsts[c.name] = c;
5833 public void mesIntroduceEnum(ET, NT:const(char)[]) (NT ename=null)
5834 if (is(ET == enum) && is(typeof(ET.min) : int))
5836 static if (is(NT == typeof(null))) {
5837 string xname = ET.stringof;
5838 } else {
5839 if (ename.length == 0) ename = ET.stringof;
5840 alias xname = ename;
5842 // typed enum
5843 if (xname in gconsts) compileError("duplicate constant");
5844 if (xname in globals) compileError("constant/global variable conflict");
5845 if (xname in funclist) compileError("constant/function conflict");
5846 TypeEnum etype = new TypeEnum(xname, typeInt); // the only possible enum type for now
5847 auto c = new Constant();
5848 c.name = etype.name;
5849 c.type = etype;
5850 gconsts[c.name] = c;
5851 // register members
5852 foreach (string mname; __traits(allMembers, ET)) {
5853 etype.members[mname] = __traits(getMember, ET, mname);
5855 // register new typed enum
5856 gtypes[etype.name] = etype;
5860 // ////////////////////////////////////////////////////////////////////////// //
5861 private string buildCacheStr(RT, A...) () {
5862 char[1024] res;
5863 int rpos = 0;
5864 enum rn = fullyQualifiedName!RT;
5865 res[rpos..rpos+rn.length] = rn[];
5866 rpos += cast(int)rn.length;
5867 res[rpos..rpos+1] = "|";
5868 ++rpos;
5869 foreach (/*immutable*/ ptype; A) {
5870 enum tn = fullyQualifiedName!ptype;
5871 res[rpos..rpos+tn.length] = tn[];
5872 rpos += cast(int)tn.length;
5874 return res[0..rpos].idup;
5878 // ////////////////////////////////////////////////////////////////////////// //
5879 public struct VMIFaceId {
5880 private:
5881 static struct FuncCache {
5882 int fnidx;
5883 Variable var;
5886 __gshared FuncCache[][string] fcache;
5888 private:
5889 Variable var;
5891 public:
5892 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (var !is null); }
5894 void opCall(A...) (A args) { call(args); }
5896 auto call(RT=void, A...) (A args) {
5897 if (var is null) throw new OverloadNotFound("cannot call nothing");
5898 if (!var.type.isFunc) throw new OverloadNotFound("cannot call non-function '"~var.name~"'");
5899 enum cacheStr = buildCacheStr!(RT, A);
5900 //pragma(msg, cacheStr);
5901 mainloop: for (;;) {
5902 if (auto fcp = cacheStr in fcache) {
5903 foreach (ref FuncCache fc; (*fcp)[]) {
5904 if (fc.var is var) {
5905 //stderr.writeln("CACHED: '", cacheStr, "' : <", var.name, ">");
5906 static if (is(RT == void)) {
5907 vmExec(var.ovloads[fc.fnidx].pc, args);
5908 return;
5909 } else {
5910 auto res = vmExec(var.ovloads[fc.fnidx].pc, args);
5911 static if (is(RT == Unqual!int)) return res;
5912 else static if (is(RT == Unqual!bool)) return (res != 0);
5913 else static if (is(RT == ActorId)) return ActorId(res);
5914 else static if (is(Unqual!RT == enum)) return cast(RT)res;
5915 else static if (is(RT == string) || is(RT == const(char)[])) return strlist[res];
5916 else static assert(0, "wtf?!");
5921 // no cached function, try to find one
5922 auto tf = buildFuncType!(false, false, RT, A);
5923 if (tf is null) throw new OverloadNotFound("cannot find suitable overload of '"~var.name~"' to call");
5924 assert(tf !is null);
5925 scope(exit) delete tf;
5926 // try exact
5927 foreach (immutable fidx, ref FuncInfo fi; var.ovloads) {
5928 if (fi.type.callCompatible(tf)) {
5929 FuncCache[] fc;
5930 if (auto fcp = cacheStr in fcache) fc = *fcp;
5931 fc ~= FuncCache(cast(int)fidx, var);
5932 fcache[cacheStr] = fc;
5933 continue mainloop;
5936 static if (is(RT == void)) {
5937 // try inexact
5938 foreach (immutable fidx, ref FuncInfo fi; var.ovloads) {
5939 if (fi.type.callCompatibleNoAux(tf)) {
5940 FuncCache[] fc;
5941 if (auto fcp = cacheStr in fcache) fc = *fcp;
5942 fc ~= FuncCache(cast(int)fidx, var);
5943 fcache[cacheStr] = fc;
5944 continue mainloop;
5948 throw new OverloadNotFound("cannot find suitable overload of '"~var.name~"' to call");
5950 assert(0);
5953 auto get(RT=int) () if (is(RT == bool) || is(RT == int) || is(RT == string) || is(RT == ActorId)) {
5954 if (var is null) throw new OverloadNotFound("cannot get nothing");
5955 if (!var.global) assert(0, "wtf?!");
5956 static if (is(RT == bool)) {
5957 if (var.type.isBool || var.type.isInt || var.type.isStr) return (vmglobals[var.idx] != 0);
5958 if (var.type.isActor) return ActorId(vmglobals[var.idx]).alive;
5959 assert(0, "oops");
5960 } else static if (is(RT == int)) {
5961 if (var.type.isBool || var.type.isInt) return vmglobals[var.idx];
5962 assert(0, "oops");
5963 } else static if (is(RT == string)) {
5964 if (var.type.isStr) return strlist[vmglobals[var.idx]];
5965 assert(0, "oops");
5966 } else static if (is(RT == ActorId)) {
5967 if (var.type.isActor) return ActorId(vmglobals[var.idx]);
5968 assert(0, "oops");
5969 } else {
5970 static assert(0, "wtf?!");
5974 void set(VT) (VT v) if (is(VT == bool) || is(VT == int) || is(VT == ActorId) || is(VT == enum)) {
5975 if (var is null) throw new OverloadNotFound("cannot get nothing");
5976 if (!var.global) assert(0, "wtf?!");
5977 static if (is(VT == bool)) {
5978 if (var.type.isBool || var.type.isInt) vmglobals[var.idx] = (v ? 1 : 0);
5979 else assert(0, "oops");
5980 } else static if (is(VT == int)) {
5981 if (var.type.isBool) vmglobals[var.idx] = (v ? 1 : 0);
5982 else if (var.type.isInt) vmglobals[var.idx] = v;
5983 else assert(0, "oops");
5984 } else static if (is(VT == ActorId)) {
5985 if (var.type.isActor) vmglobals[var.idx] = v.id;
5986 else assert(0, "oops");
5987 } else static if (is(VT == enum)) {
5988 if (auto tpp = (Unqual!VT).stringof in gtypes) {
5989 if (auto et = cast(TypeEnum)(*tpp)) {
5990 //FIXME: more checks
5991 if (var.type.isInt) { vmglobals[var.idx] = cast(int)v; return; }
5992 if (var.type.isEnum && var.type.same(et)) { vmglobals[var.idx] = cast(int)v; return; }
5995 assert(0, "unknown enum '"~(Unqual!VT).stringof~"'");
5996 } else {
5997 static assert(0, "wtf?!");
6003 public struct VMIFace {
6004 static VMIFaceId opIndex (string fld) {
6005 VMIFaceId res;
6006 if (auto fp = fld in funclist) res.var = *fp;
6007 else if (auto vp = fld in globals) res.var = *vp;
6008 else throw new OverloadNotFound("identifier '"~fld~"' not found");
6009 return res;
6014 // ////////////////////////////////////////////////////////////////////////// //
6015 public string mesGetString (int idx) nothrow @trusted @nogc {
6016 return (idx >= 0 && idx < strlist.length ? strlist.ptr[idx] : null);
6020 // ////////////////////////////////////////////////////////////////////////// //
6021 public void mesCompileFile (VFile fl, void delegate (const(char)[] fname) aOnInclude=null) {
6022 scope(exit) forgetSource();
6024 auto curparser = new FileParser(fl, aOnInclude);
6025 scope(exit) delete curparser;
6026 curparser.next(); // warm it up
6027 scanTopLevelDecls(curparser);
6030 //foreach (const ref Token tk; cursource) stderr.write(tk, " "); stderr.writeln;
6032 compileSource();
6036 /// call this after you compiled all the files, to check if some unresolved function calls lefs
6037 /// returns `true` if everything is ok
6038 public bool mesCheckUndefined () {
6039 return !fixupCalls();
6043 public void mesDumpUndefined (VFile fo) {
6044 fixupDump(fo);