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/>.
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
{
48 this() (in auto ref Loc aloc
, string msg
, string file
=__FILE__
, usize line
=__LINE__
) nothrow @safe @nogc {
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 // ////////////////////////////////////////////////////////////////////////// //
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
);
88 // delimiter (sval is char or two chars)
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
{
158 abstract char getch (); // 0: eof
160 final void readToken (ref Token ctk
) {
162 ctk
.type
= Token
.T
.LitNone
;
165 if (mCurCh
== 0) return;
168 if (mCurCh
>= '0' && mCurCh
<= '9') {
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;
184 if (digitInBase(mCurCh
, base
) < 0) errorAt(ctk
.loc
, "invalid number");
189 if (digitInBase(mCurCh
, base
) < 0) break;
190 n
= n
*base
+digitInBase(mCurCh
, base
);
191 if (n
< 0) errorAt(ctk
.loc
, "integer overflow");
195 if (mCurCh
>= 127 ||
isalpha(mCurCh
)) errorAt(ctk
.loc
, "invalid number");
196 ctk
.type
= ctk
.T
.LitInt
;
202 if (mCurCh
== '_' ||
isalpha(mCurCh
) || mCurCh
>= 127) {
203 while (mCurCh
&& (mCurCh
== '_' ||
isalnum(mCurCh
) || mCurCh
>= 127)) {
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")) {
213 if (ctk
.sval
.length
== mname
.length
&& ctk
.sval
[0] == cast(char)(mname
[0]+32)) {
215 foreach (immutable idx
, immutable char ch
; mname
[1..$]) {
216 if (ctk
.sval
[idx
+1] != ch
) { ok
= false; break; }
219 ctk
.type
= __traits(getMember
, ctk
.T
, mname
);
225 if (ctk
.sval
== "Actor") ctk
.type
= Token
.T
.Actor
;
230 // backslash is skipped
231 char parseEscapeChar () {
233 if (!mCurCh
) errorAt(ctk
.loc
, "invalid escape");
235 case 't': res
= '\t'; break;
236 case 'n': res
= '\n'; break;
237 case 'r': res
= '\r'; break;
238 case 'e': res
= '\x1b'; break;
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);
250 if (isalnum(mCurCh
)) errorAt(ctk
.loc
, "invalid escape");
261 while (mCurCh
&& mCurCh
!= '"') {
262 if (mCurCh
== '\\') {
264 ctk
.sval
~= parseEscapeChar();
270 if (mCurCh
!= '"') errorAt(ctk
.loc
, "invalid string");
272 ctk
.type
= ctk
.T
.LitStr
;
277 if (mCurCh
== '\'') {
279 if (!mCurCh
) errorAt(ctk
.loc
, "invalid char");
281 if (mCurCh
== '\\') {
283 ch
= parseEscapeChar();
288 if (mCurCh
!= '\'') errorAt(ctk
.loc
, "\"'\" expected");
290 ctk
.type
= ctk
.T
.LitInt
;
291 ctk
.ival
= cast(int)ch
;
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
;
323 readToken(mCurToken
);
329 mCurLoc
.fname
= filename
;
331 mNextCh
= (mCurCh ?
getch() : '\0');
335 static struct SavedState
{
341 this (ParserBase pr
) nothrow @safe @nogc { saveFrom(pr
); }
343 void saveFrom (ParserBase pr
) nothrow @safe @nogc {
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 {
354 pr
.mCurLoc
= this.mCurLoc
;
355 pr
.mNextCh
= this.mNextCh
;
356 pr
.mCurCh
= this.mCurCh
;
357 pr
.mCurToken
= this.mCurToken
;
363 // first token is NOT read after creation
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
;
384 if (mNextCh
!= 0) mNextCh
= getch();
387 final void skipSpaces () {
389 // single-line comment
390 if (mCurCh
== '/' && mNextCh
== '/') {
391 while (mCurCh
&& mCurCh
!= '\n') skipch();
395 if (mCurCh
== '/' && mNextCh
== '*') {
399 if (mCurCh
== '*' && mNextCh
== '/') {
408 // multiline nested comment
409 if (mCurCh
== '/' && mNextCh
== '+') {
414 if (mCurCh
== '+' && mNextCh
== '/') {
417 if (--level
== 0) break;
420 if (mCurCh
== '/' && mNextCh
== '+') {
430 if (mCurCh
> ' ') break;
435 final void next() () { nextToken(); }
439 // ////////////////////////////////////////////////////////////////////////// //
440 public final class FileParser
: ParserBase
{
443 string curfname
= null;
446 static struct MySave
{
455 if (fstack
.length
>= 32) error("too many nested includes");
457 fstack
[$-1].st
.saveFrom(this);
462 if (fstack
.length
== 0) error("inclide stack underflow");
464 fstack
[$-1].st
.restoreTo(this);
466 fstack
.assumeSafeAppend
;
471 override char getch () {
473 if (fl
.rawRead((&res
)[0..1]).length
!= 0) {
474 if (res
== 0) res
= ' ';
482 void delegate (const(char)[] fname
) onInclude
;
485 // first token is NOT read after creation
486 this (VFile afl
, void delegate (const(char)[] fname
) aOnInclude
=null) {
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
;
498 override void nextToken () {
501 if (mCurToken
.empty
) {
503 if (fstack
.length
== 0) return;
505 } else if (mCurToken
.delim("#")) {
507 if (mCurToken
.empty
) error("parser directive expected");
508 if (!mCurToken
.id("include")) error("inknown compiler directive");
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];
517 auto nfl
= VFile(oldname
);
520 if (onInclude
!is null) onInclude(fl
.name
);
531 // ////////////////////////////////////////////////////////////////////////// //
535 Nop
, // 3 bytes: payload
536 CheckStack
, // u16: number of stack slots this function is using
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)
556 // integer comparisons
561 // string comparisons
566 // the following are ok for any types
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)
580 LogBNorm
, // dest, op0 -- normalize boolean value
582 GLoad
, // dest, 2 bytes: index (offset in global area)
583 GStore
, // src, 2 bytes: index (offset in global area)
585 ALoad
, // dest (slot), src (argnum)
586 AStore
, // dest (argnum), src (slot)
587 ACount
, // dest -- push number of arguments
589 FLoad
, // dest (slot), actid, fldidx -- load field value to dest
590 FStore
, // src (slot), actid, fldidx -- store field value from src
592 StrLen
, // dest, src (string idx)
593 StrLoadChar
, // dest, src (string idx), strofs
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]
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)
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
615 Ret
, // slot 0 is result
616 Call
, // vm call; addrslot; argslot; argcount
620 static assert(OpCode
.Nop
== 0);
623 align(1) struct VMOp
{
628 public nothrow @safe @nogc:
629 this (uint ainstr
) pure {
630 pragma(inline
, true);
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));
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
);
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>";
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
);
717 return "%s %s,%s".format(opc
, decodeSlot(v0
.dest
), decodeSlot(v0
.op0
));
730 case OpCode
.ILessEqu
:
731 case OpCode
.IGreatEqu
:
734 case OpCode
.SLessEqu
:
735 case OpCode
.SGreatEqu
:
740 return "%s %s,%s,%s".format(opc
, decodeSlot(v0
.dest
), decodeSlot(v0
.op0
), decodeSlot(v0
.op1
));
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
:
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>";
782 // ////////////////////////////////////////////////////////////////////////// //
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
;
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)) {
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
) {
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)) {
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
);
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; }
840 static if (abortonerror
) static assert(0, "invalid argument type: "~ptype
.stringof
); else return null;
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
) {
856 assert(fullname
.length
);
857 if (builtinlist
.length
== 0) {
859 builtinlist
.length
= 1;
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;
867 return -cast(int)bidx
;
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");
883 // no string returns yet (we need string GC)
884 public void mesRegisterBuiltin(T
:const(char)[], DG
) (T name
, DG dg
) if (isCallable
!DG
) {
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
;
898 if (idx
>= 0) throw new Exception("cannot overload non-builtin '"~name
.idup
~"'");
900 assert(idx
>= 0 && idx
< builtinlist
.length
);
907 // if nothing was found, this in "inb4 builtin"
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
) {
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
];
934 static assert(0, "invalid argument type: "~ptype
.stringof
);
937 static if (is(RT
== void)) {
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)) {
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 () {
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
);
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
);
980 // ////////////////////////////////////////////////////////////////////////// //
981 __gshared
int[] vmglobals
;
982 __gshared VMOp
[] vmcode
;
983 __gshared
int[65536] vmstack
;
984 __gshared
int vmbp
, vmargc
;
986 uint pc
; // uint.max: no more
987 int bp
, argc
; // top arg will contain result
989 __gshared RStackItem
[32768] vmrstack
;
991 // vmbp points to the first slot *after* all args; this is `result` slot
994 // ////////////////////////////////////////////////////////////////////////// //
995 __gshared TypeBase
[] vatypes
;
997 int putVAType (TypeBase t
) {
999 if (vatypes
.length
== 0) vatypes
.reserve(128);
1000 foreach (immutable idx
, TypeBase vt
; vatypes
) if (vt
is t
) return cast(int)idx
;
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
;
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
;
1028 int v
= vargs
[$-aidx
*2-2];
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
);
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
;
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
);
1095 } else static if (is(VT
== ActorId
)) return ActorId
.init
;
1096 else static assert(0, "wtf?!");
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");
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) ||
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)) {
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
;
1132 if (error
) throw new Exception("unknown enum '"~enumname
~"'");
1134 static assert(0, "invalid argument type: "~ptype
.stringof
);
1137 auto oldargc
= vmargc
;
1139 scope(exit
) { vmbp
= oldbp
; vmargc
= oldargc
; }
1140 vmargc
= cast(int)args
.length
;
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
);
1151 //stderr.writeln("vmrsp=", vmrsp, "; oldrsp=", oldrsp);
1152 if (vmrsp
< oldrsp
) assert(0, "internal vm error");
1153 if (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");
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");
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
;
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
;
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;
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
];
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
];
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
;
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
;
1208 vmstack
.ptr
[vmbp
+op
.dest
] = vmstack
.ptr
[vmbp
+op
.op0
]&vmstack
.ptr
[vmbp
+op
.op1
];
1211 vmstack
.ptr
[vmbp
+op
.dest
] = vmstack
.ptr
[vmbp
+op
.op0
]|vmstack
.ptr
[vmbp
+op
.op1
];
1214 vmstack
.ptr
[vmbp
+op
.dest
] = vmstack
.ptr
[vmbp
+op
.op0
]^vmstack
.ptr
[vmbp
+op
.op1
];
1217 vmstack
.ptr
[vmbp
+op
.dest
] = (vmstack
.ptr
[vmbp
+op
.op0
] < vmstack
.ptr
[vmbp
+op
.op1
] ?
1 : 0);
1220 vmstack
.ptr
[vmbp
+op
.dest
] = (vmstack
.ptr
[vmbp
+op
.op0
] > vmstack
.ptr
[vmbp
+op
.op1
] ?
1 : 0);
1222 case OpCode
.ILessEqu
:
1223 vmstack
.ptr
[vmbp
+op
.dest
] = (vmstack
.ptr
[vmbp
+op
.op0
] <= vmstack
.ptr
[vmbp
+op
.op1
] ?
1 : 0);
1225 case OpCode
.IGreatEqu
:
1226 vmstack
.ptr
[vmbp
+op
.dest
] = (vmstack
.ptr
[vmbp
+op
.op0
] >= vmstack
.ptr
[vmbp
+op
.op1
] ?
1 : 0);
1229 vmstack
.ptr
[vmbp
+op
.dest
] = (strlist
.ptr
[vmstack
.ptr
[vmbp
+op
.op0
]] < strlist
.ptr
[vmstack
.ptr
[vmbp
+op
.op1
]] ?
1 : 0);
1232 vmstack
.ptr
[vmbp
+op
.dest
] = (strlist
.ptr
[vmstack
.ptr
[vmbp
+op
.op0
]] > strlist
.ptr
[vmstack
.ptr
[vmbp
+op
.op1
]] ?
1 : 0);
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);
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);
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]);
1245 vmstack
.ptr
[vmbp
+op
.dest
] = (vmstack
.ptr
[vmbp
+op
.op0
] != vmstack
.ptr
[vmbp
+op
.op1
] ?
1 : 0);
1248 vmstack
.ptr
[vmbp
+op
.dest
] = (vmstack
.ptr
[vmbp
+op
.op0
] && vmstack
.ptr
[vmbp
+op
.op1
] ?
1 : 0);
1251 vmstack
.ptr
[vmbp
+op
.dest
] = (vmstack
.ptr
[vmbp
+op
.op0
] || vmstack
.ptr
[vmbp
+op
.op1
] ?
1 : 0);
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
);
1258 case OpCode
.Jump
: // s16: offset (from next opcode)
1261 case OpCode
.JTrue
: // src: slot; s16: offset (from next opcode)
1262 if (vmstack
.ptr
[vmbp
+op
.src
]) pc
+= op
.s16
;
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
;
1269 vmstack
.ptr
[vmbp
+op
.dest
] = (vmstack
.ptr
[vmbp
+op
.op0
] ?
0 : 1);
1271 case OpCode
.LogBNorm
:
1272 vmstack
.ptr
[vmbp
+op
.dest
] = (vmstack
.ptr
[vmbp
+op
.op0
] ?
1 : 0);
1274 case OpCode
.GLoad
: // dest, 2 bytes: index (offset in global area)
1275 vmstack
.ptr
[vmbp
+op
.dest
] = vmglobals
.ptr
[op
.u16
];
1277 case OpCode
.GStore
: // src, 2 bytes: index (offset in global area)
1278 vmglobals
.ptr
[op
.u16
] = vmstack
.ptr
[vmbp
+op
.src
];
1281 vmstack
.ptr
[vmbp
+op
.dest
] = vmstack
.ptr
[vmbp
-1-op
.op0
];
1284 vmstack
.ptr
[vmbp
-1-op
.dest
] = vmstack
.ptr
[vmbp
+op
.op0
];
1287 vmstack
.ptr
[vmbp
+op
.dest
] = vmargc
;
1289 case OpCode
.FLoad
: // dest (slot), actid, fldidx -- load field value to dest
1290 auto aid
= ActorId(cast(uint)vmstack
.ptr
[vmbp
+op
.op0
]);
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
]];
1295 // it is not a bug to access dead actor
1296 vmstack
.ptr
[vmbp
+op
.dest
] = 0;
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
1303 actorGetById(aid
).ptr
[vmstack
.ptr
[vmbp
+op
.op1
]] = vmstack
.ptr
[vmbp
+op
.src
];
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
;
1311 vmstack
.ptr
[vmbp
+op
.dest
] = 0;
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);
1321 vmstack
.ptr
[vmbp
+op
.dest
] = 0;
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
~")");
1331 case OpCode
.LArrClear
: // dest is first slot; u16 is cell count
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;
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
];
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
];
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
];
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
];
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)
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);
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
;
1395 pval
&= 0x3fff_ffff
;
1396 if (pval
+ofs
>= vmglobals
.length
) assert(0, "internal compiler error");
1397 xptr
= vmglobals
.ptr
+pval
+ofs
;
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
;
1407 case 3: assert(0, "internal compiler error");
1409 vmstack
.ptr
[vmbp
+op
.dest
] = *xptr
;
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
];
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
;
1422 pval
&= 0x3fff_ffff
;
1423 if (pval
+ofs
>= vmglobals
.length
) assert(0, "internal compiler error");
1424 xptr
= vmglobals
.ptr
+pval
+ofs
;
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
;
1434 case 3: assert(0, "internal compiler error");
1436 *xptr
= vmstack
.ptr
[vmbp
+op
.src
];
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;
1443 case OpCode
.MakeAPtr
: // dest, slot: argnum -- make pointer to argument
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
;
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;
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
;
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
;
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
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) {
1480 vmrstack
.ptr
[vmrsp
++] = RStackItem(pc
, vmbp
, vmargc
);
1481 pc
= vmstack
.ptr
[vmbp
+op
.src
];
1483 vmbp
+= op
.op0
+vmargc
;
1485 throw new Exception("cannot call null function");
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__
) {
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]));
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]));
1522 // fits into 3 unsigned bytes?
1523 if (ival
>= 0 && ival
<= 0x00ff_ffff
) {
1524 vmcode
~= VMOp((OpCode
.LitNU
)|
(dest
<<8));
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 // ////////////////////////////////////////////////////////////////////////// //
1541 public class TypeBase
{
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 {
1575 if (t0
is null || t1
is null) return false;
1576 if (t0
is t1
) return true;
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;
1590 return false; // something is very wrong here
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
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
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
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
{
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
{
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
{
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 // ////////////////////////////////////////////////////////////////////////// //
1685 public class TypeEnum
: TypeBase
{
1689 int[string
] members
; // lucky me, strings can be encoded as integers too
1690 int mDefValue
; // default value
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
1702 foreach (immutable int v
; members
.byValue
) if (res
> v
) res
= v
;
1705 final int maxval () const nothrow @trusted @nogc {
1706 if (members
.length
== 0) return mDefValue
; // just in case
1708 foreach (immutable int v
; members
.byValue
) if (res
< v
) res
= v
;
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
1719 override string
toPrettyString () const { return (mName
.length ? mName
: "__anonymous_enum"); }
1723 // ////////////////////////////////////////////////////////////////////////// //
1725 public class TypeStruct
: TypeBase
{
1727 static struct Member
{
1733 @property bool valid () const pure nothrow @safe @nogc { pragma(inline
, true); return (type
!is null); }
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
;
1762 override string
toPrettyString () const { return (mName
.length ? mName
: "__anonymous_struct"); }
1766 // ////////////////////////////////////////////////////////////////////////// //
1767 public class TypeVoid
: TypeBase
{
1770 override @property bool isVoid () const nothrow @safe @nogc { return true; }
1772 override string
toPrettyString () const { return "void"; }
1775 public class TypeBool
: TypeBase
{
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
{
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
{
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
{
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
{
1837 Value defval
; // can be only literal of the corresponding type
1838 bool ptrref
; // true: this argument is reference
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
; }
1846 TypeBase aux
; // return type
1848 bool hasrest
; // has '...'; rest args are in format: <type,value>*
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?
1867 // t1 should be either null, or compatible function pointer
1868 return (t1
.isNull ||
(t1
.isFuncPtr
&& t1
.base
.same(t0
)));
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
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
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;
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); }
1907 // all mandatory args are ok; we can call this when:
1908 return (hasrest || args
.length
== f1
.args
.length
);
1911 override string
toPrettyString () const {
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
;
1921 if (arg
.name
.length
) res
~= arg
.name
; else res
~= "_";
1929 // ////////////////////////////////////////////////////////////////////////// //
1930 struct VMCallFixup
{
1932 int ovidx
; // overload index
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;
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
;
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]));
1963 if (vmcallfixes
.length
!= dpos
) {
1964 vmcallfixes
.length
= dpos
;
1965 vmcallfixes
.assumeSafeAppend
;
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
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
);
1990 while (cpos
< vmcallfixes
.length
) {
1991 if (vmcallfixes
[cpos
].vmpc
< oldpc
) {
1992 // shift this fixup (if necessary)
1994 assert(cpos
> dpos
);
1995 vmcallfixes
[dpos
] = vmcallfixes
[cpos
];
2001 vmcallfixes
.length
= dpos
;
2002 vmcallfixes
.assumeSafeAppend
;
2004 // remove generated code
2005 vmcode
.length
= oldpc
;
2006 vmcode
.assumeSafeAppend
;
2010 // ////////////////////////////////////////////////////////////////////////// //
2012 int basepc
= -1; // "base" PC (i.e. PC at the moment of `start*()` call
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 {
2021 assert(vmcode
.length
);
2022 res
.basepc
= res
.addr
= cast(int)vmcode
.length
;
2027 // this starts forward jump point (all jumps will be directed to some PC in the future)
2028 static JumpChain
startForward () nothrow @trusted @nogc {
2030 assert(vmcode
.length
);
2031 res
.basepc
= cast(int)vmcode
.length
;
2032 res
.addr
= 0; // end-of-chain
2037 // call this when you'll unwind codegen
2038 void resetAndClear () nothrow @safe @nogc {
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
);
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");
2057 int jaddr
= cast(int)vmcode
.length
;
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"
2073 // finish and reset jump chain; fill fix addresses for forward jumps
2074 void finishHere () nothrow @trusted @nogc {
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
);
2083 // remember, generate fake
2085 auto naddr
= cast(int)vmcode
.length
;
2086 emitCode(VMOp
.makeU16(opc
, src
, cast(ushort)addr
));
2089 // generate real jump
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
{
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 ()`)
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
); }
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
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
); }
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;
2142 foreach (immutable idx
, ref FuncInfo fni
; ovloads
) {
2143 if (!fni
.type
.hasrest
&& fni
.type
.callCompatibleNoAux(targs
)) {
2144 if (normres
!= -1) {
2146 static if (showVariants
) {
2148 compilerMessage("ERROR: conflicting overloads for '%s' found", name
);
2149 compilerMessage(" %s", ovloads
[normres
].type
.toPrettyString
);
2151 compilerMessage(" %s", fni
.type
.toPrettyString
);
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
2166 foreach (immutable idx
, ref FuncInfo fni
; ovloads
) {
2167 if (fni
.type
.hasrest
&& fni
.type
.callCompatibleNoAux(targs
)) {
2168 if (restres
!= -1) {
2170 static if (showVariants
) {
2172 compilerMessage("ERROR: conflicting overloads for '%s' found", name
);
2173 compilerMessage(" %s", ovloads
[restres
].type
.toPrettyString
);
2175 compilerMessage(" %s", fni
.type
.toPrettyString
);
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
;
2196 // for enums, `ival` and `sval` doesn't matter
2202 // ////////////////////////////////////////////////////////////////////////// //
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;
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
;
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;
2257 shared static this () {
2258 strlist
~= ""; // predefined empty string
2262 // ////////////////////////////////////////////////////////////////////////// //
2263 void enterScope () {
2264 auto sc
= new Scope();
2265 sc
.parent
= curscope
;
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
~"'");
2297 compileErrorAt(loc
, "variable '"~name
~"' shadows another variable with the same name at "~var
.firstloc
.toString
);
2302 auto var
= new Variable();
2304 if (type
.cellSize
== 1) {
2305 var
.idx
= Slot
.makeTemp();
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
;
2313 curscope
.vars
~= 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
;
2326 Variable
findGlobalVar (const(char)[] name
) {
2327 if (auto gv
= name
in globals
) return *gv
;
2328 if (auto fp
= name
in funclist
) return *fp
;
2333 Constant
findConst (const(char)[] name
) {
2334 if (auto cp
= name
in gconsts
) return *cp
;
2339 // ////////////////////////////////////////////////////////////////////////// //
2346 static ubyte allocTempSlot () {
2347 foreach (immutable idx
, int sc
; curslots
[]) {
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
;
2365 foreach (immutable int v
; curslots
[idx
..idx
+len
]) n |
= v
;
2366 // no: alas, cannot allocate block
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);
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
); }
2395 if (curslots
[idx
] > 0) --curslots
[idx
];
2403 // direct ctor won't change refcount
2404 @disable this (int aidx
);
2406 this (this) nothrow @nogc {
2407 pragma(inline
, true);
2411 ~this () nothrow @nogc {
2412 pragma(inline
, true);
2416 bool opCast(T
) () const pure nothrow @safe @nogc if (is(T
== bool)) {
2417 pragma(inline
, true);
2421 void opAssign() (in auto ref Slot v
) nothrow @nogc {
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
); }
2434 void reset () nothrow @nogc {
2435 pragma(inline
, true);
2439 void clear () nothrow @nogc {
2440 pragma(inline
, true);
2445 static Slot
makeTemp () {
2447 res
.ridx
= allocTempSlot();
2451 static Slot
makeTempBlock (int len
) {
2453 res
.ridx
= allocTempSlotBlock(len
);
2457 static Slot
acquire (int aidx
) nothrow @nogc {
2458 if (aidx
< 0 || aidx
> 255) assert(0, "internal slot manager error");
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");
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`
2481 static struct VPtr
{
2483 int rc
; // refcounter
2489 this() (auto ref Value av
) nothrow {
2498 this (ValueRef avr
) nothrow {
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 {
2523 vp
.v
= Value
.init
; // free slots and other shit
2526 vp
= null; // just in case
2530 void opAssign() (auto ref Value av
) nothrow {
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
;
2563 Kind kind
= Kind
.Invalid
;
2565 int index
; // global/fieldoffset/argumentidx (suitable for ALoad/AStore)
2566 Slot slot
; // local/stack
2567 alias lit
= index
; // literal value
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
2578 @property bool isStrIndexing () const pure nothrow @safe @nogc { pragma(inline
, true); return (arrsize
== -666 && arridx
.valid
); }
2581 bool opCast(T
) () const pure nothrow @safe @nogc if (is(T
== bool)) {
2582 pragma(inline
, true);
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");
2601 res
.kind
= Kind
.Stack
;
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");
2611 res
.type
= cast(TypeBase
)v
.type
;
2612 res
.kind
= Kind
.Stack
;
2618 static Value
makeVoid() (in auto ref Loc aloc
) {
2620 res
.type
= typeVoid
;
2621 res
.kind
= Kind
.Global
;
2626 static Value
makeTemp() (in auto ref Value v
) {
2628 res
.type
= cast(TypeBase
)v
.type
;
2629 res
.kind
= Kind
.Stack
;
2630 res
.slot
= Slot
.makeTemp();
2635 static Value
makeTemp() (TypeBase t
, in auto ref Loc aloc
) {
2636 if (t
is null) assert(0, "fatal internal error");
2639 res
.kind
= Kind
.Stack
;
2640 res
.slot
= Slot
.makeTemp();
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");
2650 res
.kind
= Kind
.Stack
;
2651 res
.slot
= Slot
.makeTempBlock(len
); // first slot is refed, others aren't
2656 static Value
makeVar() (Variable var
, in auto ref Loc aloc
) {
2657 if (var
is null) assert(0, "fatal internal error");
2659 res
.type
= var
.type
;
2661 res
.kind
= Kind
.Global
;
2662 res
.index
= var
.idx
;
2663 } else if (var
.idx
>= 0 && var
.idx
<= 255) {
2665 assert(curslots
[var
.idx
] == -1);
2666 res
.kind
= Kind
.Local
;
2667 res
.slot
= Slot
.acquire(var
.idx
);
2670 assert(var
.idx
< 0);
2671 res
.kind
= Kind
.Argument
;
2672 res
.index
= -var
.idx
-1;
2675 res
.ptrref
= var
.reference
;
2676 res
.readonly
= var
.readonly
;
2677 res
.varname
= var
.name
;
2681 static Value
makeNull() (in auto ref Loc aloc
) {
2683 res
.type
= typeNull
;
2684 res
.kind
= Kind
.Literal
;
2690 static Value
makeBool() (bool v
, in auto ref Loc aloc
) {
2692 res
.type
= typeBool
;
2693 res
.kind
= Kind
.Literal
;
2694 res
.lit
= (v ?
1 : 0);
2699 static Value
makeInt() (int v
, in auto ref Loc aloc
) {
2702 res
.kind
= Kind
.Literal
;
2708 static Value
makeActor() (uint v
, in auto ref Loc aloc
) {
2710 res
.type
= typeActor
;
2711 res
.kind
= Kind
.Literal
;
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");
2722 res
.kind
= Kind
.Literal
;
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");
2733 res
.kind
= Kind
.Literal
;
2734 res
.lit
= etp
.defval
;
2739 static Value
makeStr(T
:const(char)[]) (T v
, in auto ref Loc aloc
) {
2742 res
.kind
= Kind
.Literal
;
2743 if (v
.length
== 0) {
2747 foreach (immutable idx
, string s
; strlist
) if (s
== v
) { sidx
= cast(int)idx
; break; }
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
;
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
);
2763 res
.type
= cast(TypeBase
)fld.type
;
2764 res
.kind
= Kind
.Field
;
2765 res
.index
= fld.ofs
;
2766 res
.fldobj
= Value
.makeVar(actid
, aloc
);
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
);
2775 res
.type
= cast(TypeBase
)fld.type
;
2776 res
.kind
= Kind
.Field
;
2777 res
.index
= fld.ofs
;
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
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
;
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
;
2803 arrsize
= -1; // don't generate bounds checking anymore
2804 static if (forcelit
) arridx
= arridx
.value
.load
;
2805 return arridx
.value
;
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
2813 assert(elemsize
<= ushort.max
);
2814 auto tmpslot
= Slot
.makeTemp();
2815 emitILit(tmpslot
, elemsize
);
2816 emitCode(VMOp(OpCode
.Mul
, res
.slot
, res
.slot
, tmpslot
));
2819 // next loads will reuse this one
2821 arrsize
= -1; // don't generate bounds checking anymore
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
) {
2850 // already a reference, just copy a pointer
2852 assert(!arrayaccess
);
2853 assert(!fieldaccess
);
2854 emitCode(VMOp(OpCode
.ALoad
, slot
, cast(ubyte)index
));
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
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");
2880 assert(!fieldaccess
);
2881 emitCode(VMOp(OpCode
.MakeLPtr
, slot
, this.slot
));
2884 assert(fieldaccess
);
2886 assert(fldobj
.value
.slot
);
2887 auto tmpidxslot
= Slot
.makeTemp();
2888 emitILit(tmpidxslot
, index
);
2889 emitCode(VMOp(OpCode
.MakeFPtr
, slot
, fldobj
.value
.slot
, tmpidxslot
));
2893 // if array access, offset pointer
2896 if (isStrIndexing
) compileError("cannot load pointer to string");
2898 if (arridx
.value
.literal
&& arridx
.value
.lit
== 0) {
2899 arridxLoad
!false; // this does bounds checking
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");
2919 if (!slot
.valid
) assert(0, "cannot store value to invalid slot");
2921 if (v
.type
.isVoid
) assert(0, "cannot store void value");
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
);
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?
2938 // this cannot have array index
2939 assert(v
.slot
.valid
);
2940 assert(!v
.arrayaccess
);
2941 assert(!v
.fieldaccess
);
2942 static if (mode
== "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
);
2955 if (v
.slot
!= slot
) {
2956 emitCode(VMOp(OpCode
.Mov
, slot
, v
.slot
));
2957 return v
.makeStack(slot
, v
);
2964 // this cannot have array index
2965 assert(!v
.arrayaccess
);
2966 assert(!v
.fieldaccess
);
2967 static if (mode
== "bool") {
2969 if (!v
.type
.isBool
) {
2970 emitILit(slot
, (v
.lit ?
1 : 0));
2971 return v
.makeStack(slot
, typeBool
, v
.loc
);
2975 emitILit(slot
, v
.lit
);
2976 return v
.makeStack(slot
, v
);
2980 assert(!v
.fieldaccess
);
2981 if (v
.arrayaccess
) {
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
));
2989 // load global value
2990 auto tmpslot
= Slot
.makeTemp();
2991 emitCode(VMOp
.makeU16(OpCode
.GLoad
, tmpslot
, cast(ushort)v
.index
));
2993 emitCode(VMOp(OpCode
.StrLoadChar
, slot
, tmpslot
, v
.arridxLoad
!true.slot
));
2996 emitCode(VMOp
.makeU16(OpCode
.GLoad
, slot
, cast(ushort)v
.index
));
2998 static if (mode
== "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
);
3009 assert(!v
.fieldaccess
);
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");
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
);
3022 return v
.makeStack(slot
, v
);
3025 emitCode(VMOp(OpCode
.ALoad
, slot
, cast(ubyte)v
.index
));
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") {
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
);
3044 assert(!v
.fieldaccess
);
3045 assert(v
.slot
.valid
);
3046 assert(!v
.ptrref
); // not yet
3048 if (v
.arrayaccess
) {
3049 if (v
.isStrIndexing
) {
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
);
3056 return v
.makeStack(slot
, v
);
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
);
3067 emitCode(VMOp(OpCode
.Mov
, slot
, cast(ubyte)(v
.slot
.idx
+v
.arridx
.value
.lit
*v
.elemsize
)));
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
);
3081 if (v
.slot
== slot
) return v
; // nothing to do
3083 static if (mode
== "bool") {
3084 emitCode(VMOp(OpCode
.LogBNorm
, slot
, v
.slot
));
3085 return v
.makeStack(slot
, typeBool
, v
.loc
);
3087 emitCode(VMOp(OpCode
.Mov
, slot
, v
.slot
));
3088 return v
.makeStack(slot
, v
);
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
);
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
) {
3112 if (v
.arridx
.value
.literal
) {
3114 tmp
.lit
+= v
.arridx
.value
.lit
*v
.elemsize
;
3118 assert(v
.arridx
.value
.slot
); // it should be it
3119 emitCode(VMOp(OpCode
.Add
, tmp
.slot
, tmp
.slot
, v
.arridx
.value
.slot
));
3122 // string indexing; later
3127 assert(tmp
.slot
.valid
);
3128 assert(actid
.slot
.valid
);
3129 emitCode(VMOp(OpCode
.FLoad
, slot
, actid
.slot
, tmp
.slot
));
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
);
3138 return v
.makeStack(slot
, v
);
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");
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
);
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
) {
3165 if (dest
.literal
) return false;
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
3175 if (!dest
.type
.same(src
.type
.base
)) return false; // oops, bad type
3179 if (!dest
.type
.same(src
.type
)) {
3180 // convert to and from bool
3181 if (dest
.type
.isBool
) {
3184 src
= Value
.makeBool((src
.lit
!= 0), src
.loc
);
3186 src
= src
.load
!"bool";
3188 } else if (src
.type
.isBool
) {
3190 if (!dest
.type
.isInt
) return false;
3191 // ints and bools are the same for VM
3199 // no array access here
3200 if (dest
.arrayaccess
) return false;
3201 src
.storeToSlot(dest
.slot
);
3202 } else if (dest
.global
) {
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
));
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;
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
));
3228 // normal `by-value` argument
3229 emitCode(VMOp(OpCode
.AStore
, cast(ubyte)dest
.index
, src
.slot
));
3231 } else if (dest
.local
) {
3233 assert(dest
.slot
.valid
);
3235 if (dest
.arrayaccess
) {
3236 //TODO: optimize this
3237 if (dest
.isStrIndexing
) return false;
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
));
3244 emitCode(VMOp(OpCode
.LArrStore
, src
.slot
, dest
.slot
, dest
.arridxLoad
!true.slot
));
3247 src
.storeToSlot(dest
.slot
);
3249 } else if (dest
.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
);
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
) {
3271 tmp
.lit
+= dest
.arridx
.value
.lit
*dest
.elemsize
;
3275 assert(dest
.arridx
.value
.slot
); // it should be it
3276 emitCode(VMOp(OpCode
.Add
, tmp
.slot
, tmp
.slot
, dest
.arridx
.value
.slot
));
3281 assert(tmp
.slot
.valid
);
3283 emitCode(VMOp(OpCode
.FStore
, src
.slot
, actid
.slot
, tmp
.slot
));
3285 assert(0, "internal compiler error");
3292 // ////////////////////////////////////////////////////////////////////// //
3293 //TODO: multidimensional arrays
3294 TypeBase
parseTypeDecl(bool convertFuncToFuncPtr
) () {
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
) {
3307 if (t
is null) return null;
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
);
3315 auto tf
= parseFuncArgs(ismethod
);
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
3325 if (token
.delim("]")) {
3326 // size==0 means "automatically calculate"
3327 auto ta
= new TypeArray(t
, 0);
3332 auto szloc
= token
.loc
;
3333 auto vsz
= parseExpr();
3334 if (!token
.delim("]")) compileError("']' expected");
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
);
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
;
3354 if (mustBeNamed
&& !token
.id
) compileError("identifier expected");
3359 if (!token
.delim("{")) compileError("'{' expected");
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");
3368 stp
.appendMember(token
.sval
, tp
, token
.loc
);
3370 if (!token
.delim(",")) break;
3373 if (!token
.delim(";")) compileError("';' expected");
3376 if (!token
.delim("}")) compileError("'}' expected");
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
);
3397 static if (me
[$-1] == 'R') {
3398 auto rhs
= mixin(me
~"()");
3400 auto rhs
= mixin(upfunc
~"()");
3402 lhs
= T
[idx
+1](lhs
, rhs
);
3414 Value
parseExprPrimary () {
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
; }
3419 switch (token
.type
) {
3422 auto res
= Value
.makeBool(token
.kw(Token
.T
.True
), token
.loc
);
3426 auto res
= Value
.makeNull(token
.loc
);
3429 case Token
.T
.New
: // new ObjId[(args)]
3431 // Actor temp = spawn();
3434 auto newloc
= token
.loc
;
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
);
3443 // compile `Actor temp = spawn()`
3444 auto spfn
= "spawn" in funclist
;
3445 if (spfn
is null) compileErrorAt(newloc
, "`spawn` not found");
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) {
3454 // use result slot as tempact
3455 tempact
= cgFunCall(Value
.makeVar(*spfn
, newloc
), fci
);
3456 assert(tempact
.slot
);
3457 assert(tempact
.type
.isActor
);
3461 if (!tempact
.valid
) compileErrorAt(newloc
, "`Actor spawn ()` not found");
3463 res
= parseFunCall
!false(res
, tempact
);
3464 if (!res
.type
.isVoid
) compileErrorAt(newloc
, "`"~aid
~"::ctor` must be void");
3465 // return resulting actor
3468 auto loc
= token
.loc
;
3470 if (!token
.delim("(")) compileError("'(' expected");
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");
3477 auto eloc
= token
.loc
;
3478 auto val
= parseExprUnary();
3480 if (tp
.isVoid
) return Value
.makeVoid(loc
);
3483 if (!val
.type
.isInt
) compileErrorAt(eloc
, "integer expected");
3485 //??? do sanity checks
3486 return Value
.makeEnum(tp
, val
.lit
, eloc
);
3494 // need any conversion?
3495 if (!val
.type
.same(tp
)) {
3496 // possible conversions:
3503 val
= Value
.makeBool((val
.lit
!= 0), val
.loc
);
3505 val
= val
.load
!"bool";
3507 } else if (tp
.isInt
) {
3508 if (val
.type
.isEnum
) {
3512 val
.type
= val
.type
.base
;
3514 auto btp
= val
.type
.base
;
3520 } else if (val
.type
.isBool
) {
3523 val
= Value
.makeInt((val
.lit ?
1 : 0), val
.loc
);
3528 val
.kind
= Value
.Kind
.Stack
;
3529 val
.type
= typeBool
;
3532 compileErrorAt(loc
, "invalid type conversion");
3535 compileErrorAt(loc
, "invalid type conversion");
3540 auto loc
= token
.loc
;
3542 if (!token
.delim("(")) compileError("'(' expected");
3544 auto res
= parseExpr();
3545 if (!token
.delim(")")) compileError("')' expected");
3547 if (!res
.type
.isInt
) compileErrorAt(loc
, "integer argument expected");
3549 if (res
.lit
== 0x8000_0000) compileErrorAt(loc
, "cannot negate this integer");
3550 if (res
.lit
< 0) res
.lit
= -res
.lit
;
3553 emitCode(VMOp(OpCode
.IntAbs
, res
.slot
, res
.slot
));
3558 compileError("unexpected keyword");
3563 auto loc
= token
.loc
;
3564 string idname
= token
.sval
;
3565 // max alias chain length
3566 foreach (immutable _
; 0..64) {
3568 //stderr.writeln("step: ", _, "; id: <", idname, ">");
3569 if (auto var
= findLocalVar(idname
)) {
3570 auto res
= Value
.makeVar(var
, token
.loc
);
3574 // if we're in method, look for field first
3575 if (curfunc
.isMethod
) {
3576 auto fld = actorFindField(idname
);
3579 auto xthis
= findLocalVar("this");
3580 if (xthis
is null) compileError("`this` not found");
3581 auto res
= Value
.makeField(xthis
, fld, token
.loc
);
3587 if (auto var
= findGlobalVar(idname
)) {
3588 auto res
= Value
.makeVar(var
, token
.loc
);
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
)) {
3598 if (!token
.delim(".")) compileError("'.' expected");
3600 if (!token
.id
&& !token
.kw(Token
.T
.Default
)) compileError("identifier expected");
3601 if (token
.id("min")) {
3603 return Value
.makeEnum(etype
, etype
.minval
, loc
);
3604 } else if (token
.id("max")) {
3606 return Value
.makeEnum(etype
, etype
.maxval
, loc
);
3607 } else if (token
.kw(Token
.T
.Default
) || token
.id("init")) {
3609 return Value
.makeEnumDefault(etype
, loc
);
3610 } else if (auto mv
= token
.sval
in etype
.members
) {
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?!");
3620 compileError("undefined member '"~token
.sval
~"' in enum '"~etype
.name
~"'");
3623 compileError("cannot dot-access non-enum types");
3628 if (auto c
= findConst(idname
)) {
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");
3636 if (isKnownActorId(idname
)) {
3637 auto aidloc
= token
.loc
;
3639 string aid
= idname
;
3640 if (!token
.delim(".")) compileError("'.' expected");
3642 if (!token
.id
) compileError("identifier expected");
3643 string xname
= aid
~"::"~token
.sval
;
3645 if (auto var
= findGlobalVar(xname
)) {
3646 auto res
= Value
.makeVar(var
, token
.loc
);
3649 compileErrorAt(aidloc
, "undefined identifier '"~token
.sval
~"' in class '"~aid
~"'");
3655 for (Scope sc
= curscope
; sc
!is null; sc
= sc
.parent
) {
3656 if (auto asp
= idname
in sc
.aliases
) { idname
= *asp
; found
= true; break; }
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
);
3672 compileErrorAt(loc
, "undefined identifier '"~token
.sval
~"'");
3676 if (token
.delim("(")) {
3677 auto loc
= token
.loc
;
3679 auto res
= parseExpr();
3680 if (!token
.delim(")")) compileError("`)` expected for `(` at "~loc
.toString
);
3686 if (token
.delim(".")) compileError("no global scope access is supported yet");
3689 if (token
.delim("++") || token
.delim("--")) {
3690 bool isPP
= token
.delim("++");
3691 auto loc
= token
.loc
;
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
));
3702 val
.kind
= Value
.Kind
.Stack
;
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");
3712 compileError("primary expression expected");
3717 struct FunCallInfo
{
3718 enum MaxArgCount
= 64;
3720 Value
[MaxArgCount
] argv
;
3723 @disable this (this); // no copies
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
);
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
);
3749 foreach_reverse (immutable idx
; 1..argc
) argv
[idx
] = argv
[idx
-1];
3755 // `val` should be `.isFunc` or `.isFuncPtr`
3756 Value
cgFunCall (Value val
, ref FunCallInfo fci
) {
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
));
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
~"'");
3780 fci
.prependArg(Value
.makeVar(xthis
, val
.loc
));
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");
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
];
3804 if (fif
.type
.hasrest
) assert(0, "internal compiler error");
3806 if (fif
.pc
> ushort.max
) compileErrorAt(val
.loc
, "invalid function address");
3807 rv
= Value
.makeInt(fif
.pc
, val
.loc
).load
;
3809 if (fif
.pc
== 0) vmcallfixes
~= VMCallFixup(var
, fnidx
, cast(uint)vmcode
.length
-1, val
.loc
);
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);
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`
3827 if (prependThisOrActId()) error
= !cftype
.callCompatibleNoAux(fci
.targs
);
3829 compilerMessage("ERROR args: %s", fci
.targs
.toPrettyString
);
3830 compileErrorAt(val
.loc
, "invalid arguments for funcptr: "~cftype
.toPrettyString
);
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
;
3849 Value fargv
; // result will be placed here
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));
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");
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;
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);
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));
3890 fargv
= Value
.makeTempBlock(fci
.targs
.aux
, fci
.argc
, val
.loc
);
3891 // `fargv` slot is refed, others aren't
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
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
));
3904 if (fci
.targs
.aux
.isVoid
) return Value
.makeVoid(val
.loc
);
3905 assert(fargv
.stack
);
3906 assert(fargv
.type
.same(fci
.targs
.aux
));
3911 // `val.var is null` means "calling function field"; ufcs0 is Actor
3912 Value
parseFunCall(bool allowIsAssign
=true) (Value val
, Value ufcs0
=Value
.init
) {
3914 auto isloc
= token
.loc
;
3916 static if (allowIsAssign
) {
3917 if (token
.delim("!") || token
.kw(Token
.T
.Is
)) {
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("!")) {
3924 if (!token
.kw(Token
.T
.Is
)) compileError("'is' expected");
3926 assert(token
.kw(Token
.T
.Is
));
3928 if (!token
.kw(Token
.T
.Null
)) compileError("'null' expected");
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
));
3946 // put UFCS arguments
3947 if (ufcs0
.valid
) fci
.putArg(ufcs0
);
3950 static if (allowIsAssign
) {
3951 if (token
.delim("=")) {
3952 auto loc
= token
.loc
;
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");
3959 } else if (a2
.type
.isPointer
) {
3960 compileErrorAt(loc
, "invalid pointer type");
3968 if (token
.delim("(")) {
3970 while (!token
.delim(")")) {
3971 auto aloc
= token
.loc
;
3972 auto a
= parseExpr();
3974 if (!token
.delim(",")) break;
3977 if (!token
.delim(")")) compileError("')' expected");
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
3989 // builtin properties for strings
3990 if (val
.type
.isStr
) {
3991 if (token
.id("length")) {
3993 if (val
.literal
) return Value
.makeInt(cast(int)strlist
[val
.lit
].length
, val
.loc
);
3995 auto res
= Value
.makeTemp(typeInt
, val
.loc
);
3996 emitCode(VMOp(OpCode
.StrLen
, res
.slot
, val
.slot
));
4000 // builtin properties for arrays
4001 //TODO: dynamic arrays?
4002 if (val
.type
.isArray
) {
4003 if (token
.id("length")) {
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")) {
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")) {
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")) {
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~"'");
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
;
4051 } else if (val
.fieldaccess
) {
4052 // field: struct or array of structs
4053 val
.index
+= mbr
.ofs
;
4054 val
.type
= mbr
.type
;
4057 assert(!val
.ptrref
);
4058 assert(mbr
.ofs
>= 0);
4060 if (val
.local || val
.stack
) {
4063 val
.slot
= Slot
.acquire(val
.slot
+mbr
.ofs
);
4064 } else if (val
.global
) {
4066 val
.index
+= mbr
.ofs
;
4068 assert(0, "compiler doesn't know what to do");
4071 //HACK: force field type
4072 val
.type
= mbr
.type
;
4078 if (!token
.id
) compileError("identifier expected");
4079 Variable fvar
= null;
4080 if (auto fld = actorFindFieldPtr(token
.sval
)) {
4082 if (!val
.type
.isActor
) compileErrorAt(val
.loc
, "cannot access fields of non-actor");
4083 auto res
= Value
.makeField(val
, *fld, val
.loc
);
4085 if (allowufcs
&& res
.type
.isCallable
) return parseFunCall(res
);
4087 } else if (auto fp
= token
.sval
in funclist
) {
4088 // UFCS function call
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
);
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");
4111 auto vidx
= parseExpr();
4112 if (!token
.delim("]")) compileError("']' expected");
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
);
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
;
4127 val
.type
= val
.type
.base
;
4128 val
.arrsize
= arrsize
;
4131 assert(val
.type
.isStr
);
4132 //HACK: transform into array access
4135 val
.type
= typeInt
; // oops
4136 val
.arrsize
= -666; //HACK: special flag
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
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
);
4155 if (token
.delim("++") || token
.delim("--")) {
4156 bool isPP
= token
.delim("++");
4157 auto loc
= token
.loc
;
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
));
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");
4178 Value
parseExprUnary (bool allowufcs
=true) {
4181 if (token
.sval
== "+") {
4182 auto loc
= token
.loc
;
4184 res
= parseExprUnary(allowufcs
);
4185 if (!res
.type
.isInt
) compileErrorAt(loc
, "invalid unary math");
4186 } else if (token
.sval
== "-") {
4187 auto loc
= token
.loc
;
4189 res
= parseExprUnary(allowufcs
);
4190 if (!res
.type
.isInt
) compileErrorAt(loc
, "invalid unary math");
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
;
4202 res
= parseExprUnary(allowufcs
);
4203 if (!res
.type
.isInt
&& !res
.type
.isBool
&& !res
.type
.isStr
&& !res
.type
.isActor
) compileErrorAt(loc
, "invalid unary math");
4205 res
= Value
.makeBool((res
.lit
== 0), res
.loc
);
4209 emitCode(VMOp(OpCode
.LogNot
, res
.slot
, res
.slot
));
4211 } else if (token
.sval
== "&") {
4212 auto loc
= token
.loc
;
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
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
4229 if (var
.ovloads
[fnidx
].pc
>= 0) {
4230 if (var
.ovloads
[fnidx
].type
.hasrest
) compileErrorAt(loc
, "cannot take address of vararg builtin '"~var
.name
~"'");
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
;
4236 if (var
.ovloads
[fnidx
].pc
== 0) vmcallfixes
~= VMCallFixup(var
, fnidx
, cast(uint)vmcode
.length
-1, loc
);
4239 res
= Value
.makeInt(var
.ovloads
[fnidx
].pc
, loc
).load
;
4242 res
.type
= new TypePointer(var
.type
);
4244 allowufcs
= false; // don't call function pointer
4245 assert(res
.type
.isPointer
);
4247 res
= parseExprPrimary();
4249 // bitnot, bitneg, etc.
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");
4266 if (lhs
.literal
&& rhs
.literal
) {
4268 res
.lit
= mixin("lhs.lit"~mathop
~"rhs.lit");
4271 // check for other math optimizations
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
);
4304 auto res
= Value
.makeTemp(lhs
);
4305 emitCode(VMOp(opc
, res
.slot
, lhs
.slot
, rhs
.slot
));
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");
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]");
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?!");
4335 auto res
= Value
.makeTemp(lhs
);
4336 emitCode(VMOp(opc
, res
.slot
, lhs
.slot
, rhs
.slot
));
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");
4350 static if (opc
== OpCode
.BitOr || opc
== OpCode
.BitXor
) {
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
);
4360 static assert(opc
== OpCode
.BitAnd
);
4364 } else if (lhs
.literal
) {
4366 static if (opc
== OpCode
.BitOr || opc
== OpCode
.BitXor
) {
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
);
4376 static assert(opc
== OpCode
.BitAnd
);
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?!");
4392 auto res
= Value
.makeTemp(lhs
);
4393 emitCode(VMOp(opc
, res
.slot
, lhs
.slot
, rhs
.slot
));
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
);
4408 return Value
.makeBool(lhs
.lit
!= rhs
.lit
, lhs
.loc
);
4414 auto res
= Value
.makeTemp(typeBool
, lhs
.loc
);
4415 emitCode(VMOp(opc
, res
.slot
, lhs
.slot
, rhs
.slot
));
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");
4428 if (lhs
.literal
&& rhs
.literal
) {
4429 if (lhs
.type
.isInt
) {
4430 return Value
.makeBool(mixin("lhs.lit"~op
~"rhs.lit"), lhs
.loc
);
4432 return Value
.makeBool(mixin("strlist[lhs.lit]"~op
~"strlist[rhs.lit]"), lhs
.loc
);
4438 auto res
= Value
.makeTemp(typeBool
, lhs
.loc
);
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
));
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
;
4462 if (!lhs
.type
.isGoodForBool
) compileErrorAt(lhs
.loc
, "invalid type");
4463 // optimize some known cases
4465 enum LLMode
{ Nop
, DropRHS
, DropLHS
}
4466 LLMode llmode
= LLMode
.Nop
;
4467 static if (opc
== OpCode
.LogAnd
) {
4471 lhs
= Value
.makeBool(false, lhs
.loc
);
4472 llmode
= LLMode
.DropRHS
;
4475 llmode
= LLMode
.DropLHS
;
4481 llmode
= LLMode
.DropLHS
;
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();
4491 if (!rhs
.type
.isGoodForBool
) compileErrorAt(rhs
.loc
, "invalid type");
4492 final switch (llmode
) {
4494 assert(0, "internal compiler error");
4495 case LLMode
.DropRHS
:
4496 cgmark
.unwindToBase();
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
);
4509 lhs
.storeToSlot
!"bool"(res
.slot
);
4510 // generate jump and do rhs
4511 auto jumpc
= rewindpc
.startForward();
4512 jumpc
.putJump(JumpOp
, res
.slot
);
4514 auto rhs
= nextExpr();
4516 if (!rhs
.type
.isGoodForBool
) compileErrorAt(rhs
.loc
, "invalid type");
4517 // optimize literal case
4519 enum RLMode
{ Nop
, RewindAll
, RewindJump
}
4520 RLMode rlmode
= RLMode
.Nop
;
4521 static if (opc
== OpCode
.LogAnd
) {
4524 // smth && false: rewind everything
4525 rlmode
= RLMode
.RewindAll
;
4526 rhs
= Value
.makeBool(false, rhs
.loc
);
4528 // smth && true: rewind jump
4529 rlmode
= RLMode
.RewindJump
;
4534 // smth || false: rewind jump
4535 rlmode
= RLMode
.RewindJump
;
4537 // smth || true: rewind everything
4538 rlmode
= RLMode
.RewindAll
;
4539 rhs
= Value
.makeBool(true, rhs
.loc
);
4542 final switch (rlmode
) {
4544 assert(0, "internal compiler error");
4545 case RLMode
.RewindAll
: // and return rhs
4546 rewindpc
.unwindToBase();
4548 case RLMode
.RewindJump
: // and return res
4549 jumpc
.unwindToBase();
4553 // load rhs to result slot
4554 rhs
.storeToSlot
!"bool"(res
.slot
);
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
[]) {
4571 static if (idx
%2 == 0) {
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
4606 // ignore false part
4607 if (token
.delim(":")) {
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();
4617 auto etloc
= token
.loc
;
4618 auto et
= parseExpr();
4619 jumptofalse
= JumpChain
.startHere(); // mark unwind point
4620 if (!token
.delim(":")) compileError("':' expected");
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();
4631 if (token
.delim(":")) {
4633 skipToken(); // skip ':'
4634 auto efloc
= token
.loc
;
4635 return parseExpr(); // parse false part
4637 auto etloc
= token
.loc
;
4638 auto et
= parseExpr();
4639 if (!token
.delim(":")) compileError("':' expected");
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
4650 if (!res
.stack
) res
= res
.storeToSlot(Slot
.makeTemp
);
4652 if (token
.delim(":")) {
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");
4662 ef
.storeToSlot(res
.slot
);
4663 jumptofalse
.finishHere();
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");
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");
4683 ef
.storeToSlot(res
.slot
);
4684 jumpexit
.finishHere();
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
4701 auto val
= parseExpr();
4702 if (!lhs
.assign(val
)) compileError("cannot assign");
4703 } else if (token
.delim
&& token
.sval
.length
== 2 && token
.sval
[1] == '=') {
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
4721 auto val
= parseExpr();
4722 if (!val
.type
.isInt
) compileErrorAt(val
.loc
, "integer value expected");
4723 // optimize simple cases
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();
4730 if (val
.lit
== 1 && (opc
== OpCode
.Mul || opc
== OpCode
.Div
)) {
4731 if (!lhs
.arrayaccess
&& !lhs
.fieldaccess
) unwindmark
.unwindToBase();
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
) {
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
) {
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
) {
4744 } else if (lhs
.local
&& lhs
.slot
.valid
&& !lhs
.arrayaccess
&& !lhs
.fieldaccess
&& !lhs
.ptrref
) {
4746 emitCode(VMOp(opc
, lhs
.slot
, lhs
.slot
, val
.slot
));
4748 auto tmp
= lhs
.load
;
4752 emitCode(VMOp(opc
, tmp
.slot
, tmp
.slot
, val
.slot
));
4753 if (!lhs
.assign(tmp
)) compileErrorAt(lhs
.loc
, "assign failed");
4760 // ////////////////////////////////////////////////////////////////////////// //
4761 TypeFunc
parseFuncArgs (bool method
) {
4762 auto stloc
= token
.loc
;
4763 if (!token
.delim("(")) compileError("'(' expected");
4765 auto tf
= new TypeFunc();
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;
4775 if (token
.kw(Token
.T
.In
)) {
4776 if (hasIn
) compileError("duplicate 'in'");
4778 } else if (token
.kw(Token
.T
.Ref
)) {
4779 if (hasRef
) compileError("duplicate 'ref'");
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
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");
4811 if (!tp
.same(val
.type
)) compileErrorAt(dvloc
, "invalid default argument type");
4813 tf
.args
[$-1].defval
= val
;
4815 if (!token
.delim(",")) break;
4818 if (!token
.delim(")")) compileError("')' expected");
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");
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
);
4841 bool doStore
= true;
4842 if (token
.delim("=")) {
4843 if (tp
.isArray
) compileError("local array initializers aren't supported yet");
4845 auto iloc
= token
.loc
;
4847 if (!lastres
.assign(val
)) compileErrorAt(iloc
, "invalid initializer type");
4848 if (isfirst
&& val
.literal
) lastres
= val
; // so dead code elimination can work
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
) {
4861 emitCode(VMOp
.makeU16(OpCode
.LArrClear
, Slot
.acquire(var
.idx
), cast(ushort)tp
.cellSize
));
4862 } else if (tp
.isStruct
) {
4863 // create local struct
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?!");
4872 if (!lastres
.assign(val
)) compileErrorAt(lastres
.loc
, "invalid initializer type");
4873 if (isfirst
&& val
.literal
) lastres
= val
; // so dead code elimination can work
4876 if (!allowMulti
) break;
4877 if (!token
.delim(",")) break;
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;
4889 if (!token
.id
) compileErrorAt(token
.loc
, "identifier expected");
4891 if (!token
.id
) compileErrorAt(token
.loc
, "identifier expected");
4892 auto vartoken
= token
;
4895 if (!token
.delim("=")) compileErrorAt(token
.loc
, "initializer expected");
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
4907 compileErrorAt(iloc
, "invalid initializer type");
4910 if (!allowMulti
) break;
4911 if (!token
.delim(",")) break;
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;
4924 if (!parseAutoDecl(allowMulti
, res
) && !parseVarDecl(allowMulti
, res
)) {
4925 // not a declaration, parse expression
4926 static if (mode
== "for") {
4927 res
= parseAssExpr();
4936 // ////////////////////////////////////////////////////////////////////////// //
4939 // we may want to have more info later, so let's create structure now
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
) {
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
);
4973 val
.type
= curfunc
.type
.aux
;
4974 val
.storeToSlot(Slot
.acquire(0));
4977 val
.storeToSlot(Slot
.acquire(0));
4980 emitCode(VMOp(OpCode
.Ret
));
4981 if (!token
.delim(";")) compileError("';' expected");
4983 return ParseInfo
.makeReturn();
4987 ParseInfo
parseIf (in ref Loc statloc
) {
4990 auto eloc
= token
.loc
;
4991 if (!token
.delim("(")) compileError("'(' expected");
4993 enterScope(); // why not? it is easier this way
4994 scope(exit
) leaveScope();
4995 auto val
= parseExprWithPossibleDecl
!"if"();
4996 if (!token
.delim(")")) compileError("')' expected");
4998 if (!val
.type
.isGoodForBool
) compileErrorAt(eloc
, "boolean expression expected");
4999 // dead code elimination
5002 auto rewindpc
= JumpChain
.startHere();
5003 pires
= parseStatement();
5004 // rewind true branch if condition is false
5006 rewindpc
.unwindToBase();
5007 // ...and reset flags
5008 pires
= ParseInfo
.init
;
5011 if (token
.kw(Token
.T
.Else
)) {
5013 rewindpc
= JumpChain
.startHere();
5015 auto pri
= parseStatement();
5016 // rewind false branch if condition is false
5018 rewindpc
.unwindToBase();
5020 // ...or use it's ParserInfo as result
5027 // generate jump over true branch
5028 auto jovertrue
= JumpChain
.startForward();
5029 jovertrue
.putJump(OpCode
.JFalse
, val
.slot
);
5030 pires
= parseStatement();
5032 if (token
.kw(Token
.T
.Else
)) {
5034 // yep; gen jump over else
5035 auto joverfalse
= JumpChain
.startForward();
5036 joverfalse
.putJump(OpCode
.Jump
);
5037 // patch jump over true
5038 jovertrue
.finishHere();
5040 pires
.merge(parseStatement());
5041 // patch jump over else
5042 joverfalse
.finishHere();
5044 // nope, no `else`; patch jump over true here
5045 jovertrue
.finishHere();
5046 // reset ParserInfo, 'cause else branch is empty
5047 pires
= ParseInfo
.init
;
5054 ParseInfo
parseWhile (in ref Loc statloc
) {
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();
5064 auto condloc
= token
.loc
;
5065 if (!token
.delim("(")) compileError("'(' expected");
5067 auto cond
= parseExpr();
5068 if (!token
.delim(")")) compileError("')' expected");
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)
5076 curcontpc
.unwindToBase(); // remove the whole loop
5078 // generate condition check and "break" (if condition is not known)
5079 if (!cond
.literal
) {
5081 curbreakpc
.putJump(OpCode
.JFalse
, cond
.slot
);
5083 assert(cond
.lit
!= 0);
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;
5104 ParseInfo
parseBreak (in ref Loc statloc
) {
5105 if (!curbreakpc
.valid
) compileError("`break` outside of loop/switch");
5106 if (!token
.delim(";")) compileError("';' expected");
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");
5117 curcontpc
.putJump(OpCode
.Jump
);
5118 return ParseInfo
.makeContinue();
5122 ParseInfo
parseFor (in ref Loc statloc
) {
5124 if (!token
.delim("(")) compileError("'(' expected");
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");
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
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);
5154 // get out of here if condition is false
5157 curbreakpc
.putJump(OpCode
.JFalse
, cond
.slot
);
5159 // do we have "next" expression?
5160 if (!token
.delim(")")) {
5162 if (!token
.delim(";")) compileError("';' expected");
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
5173 if (!token
.delim(",")) break;
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
;
5182 // jump to condition
5183 if (docondition
) jumpToCond
.putJump(OpCode
.Jump
);
5184 // fix jump over "next"
5185 overnext
.finishHere();
5188 // no "next", so "continue" is the same as "cond"
5189 curcontpc
= jumpToCond
;
5192 // no "next", so "continue" is the same as "cond"
5193 curcontpc
= jumpToCond
;
5195 if (!token
.delim(")")) compileError("')' expected");
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
;
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;
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
;
5226 if (!token
.delim("=")) compileError("'=' expected");
5228 if (!token
.id
) compileError("identifier expected");
5229 string dstname
= token
.sval
;
5232 curscope
.aliases
[aname
] = dstname
;
5233 if (!token
.delim(",")) break;
5236 if (!token
.delim(";")) compileError("';' expected");
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();
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
;
5255 mixin("return parse"~mname
~"(loc);");
5260 auto eloc
= token
.loc
;
5261 auto val
= parseAssExpr();
5263 if (!token
.delim(";")) compileError("';' expected");
5265 return ParseInfo
.init
;
5269 ParseInfo
parseCodeBlock(bool openScope
=true) () {
5271 if (token
.delim("{")) {
5272 auto stloc
= token
.loc
;
5274 static if (openScope
) {
5276 scope(exit
) leaveScope();
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
;
5286 pires
= parseStatement();
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
5305 if (vmcode
[$-1].opcode
== OpCode
.Ret
) {
5307 checkloop
: foreach (immutable ofs
, const ref VMOp opc
; vmcode
[startpc
..$]) {
5308 switch (opc
.opcode
) {
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
; }
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");
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
);
5347 if (ismethod
) skipToken();
5348 // parse `method(actorid)`
5349 if (ismethod
&& token
.delim("(")) {
5351 if (!token
.id
) compileError("identifier expected");
5352 methodactid
= token
.sval
;
5354 if (!token
.delim(")")) compileError("')' expected");
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
;
5363 // it must be function (everything else was processed and removed in `scanTopLevelDecls()`
5364 auto tf
= parseFuncArgs(ismethod
);
5366 if (!token
.delim("{")) compileError("'{' expected");
5369 scope(exit
) curfunc
= FuncInfo
.init
;
5371 string fullname
= (methodactid
.length ? methodactid
~"::"~name
: name
);
5372 Variable var
= null;
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");
5383 // setup compiler and compile function body
5384 auto codeofs
= cast(int)vmcode
.length
;
5387 scope(exit
) leaveScope();
5388 curslots
[] = 0; // no used slots
5389 curslots
[0] = -1; // return value
5391 foreach (immutable aidx
, ref arg
; curfunc
.type
.args
) {
5392 auto av
= new Variable();
5393 av
.idx
= -(cast(int)aidx
+1);
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
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
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
;
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;
5450 if (curtokidx
>= cursource
.length
) {
5451 cursource
.length
= pos
;
5452 curtokidx
= cast(int)cursource
.length
;
5457 foreach (immutable cidx
; curtokidx
..cursource
.length
) cursource
.ptr
[dest
++] = cursource
.ptr
[cidx
];
5458 assert(dest
<= cursource
.length
);
5459 cursource
.length
= dest
;
5463 while (!token
.empty
) {
5464 auto tokmark
= markTokenPos();
5465 if (token
.delim(";")) {
5467 removeTokensFromMark(tokmark
);
5470 auto stloc
= token
.loc
;
5472 if (token
.kw(Token
.T
.Alias
)) {
5474 if (!token
.id
) compileError("identifier expected");
5475 while (!token
.delim(";")) {
5476 if (!token
.id
) compileError("identifier expected");
5477 string aname
= token
.sval
;
5479 if (!token
.delim("=")) compileError("'=' expected");
5481 if (!token
.id
) compileError("identifier expected");
5482 string dstname
= token
.sval
;
5485 aliases
[aname
] = dstname
;
5486 if (!token
.delim(",")) break;
5489 if (!token
.delim(";")) compileError("';' expected");
5491 removeTokensFromMark(tokmark
);
5495 if (token
.kw(Token
.T
.Enum
)) {
5496 auto defloc
= token
.loc
;
5499 TypeEnum etype
= null;
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
5506 auto c
= new Constant();
5507 c
.name
= etype
.name
;
5509 gconsts
[c
.name
] = c
;
5511 if (!token
.delim("{")) compileError("'{' expected");
5514 string membername
= 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");
5524 c
.name
= token
.sval
;
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");
5532 // parse initializer
5533 if (token
.delim("=")) {
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");
5541 // introduce new constant or member
5542 if (etype
is null) {
5545 gconsts
[c
.name
] = c
;
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;
5555 if (!token
.delim("}")) compileError("'}' expected");
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
);
5566 if (token
.kw(Token
.T
.Struct
)) {
5567 auto tpos
= curtokidx
;
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
);
5583 // rollback, so we'll parse type
5588 bool isfield
= token
.id("field");
5589 bool isconst
= token
.kw(Token
.T
.Const
);
5590 bool ismethod
= token
.kw(Token
.T
.Method
);
5592 if (isfield || isconst || ismethod
) skipToken();
5593 // parse `method(actorid)`
5594 if (ismethod
&& token
.delim("(")) {
5596 if (!token
.id
) compileError("identifier expected");
5597 methodactid
= token
.sval
;
5599 if (!token
.delim(")")) compileError("')' expected");
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
;
5608 // actor field? add it here, why not
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");
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
);
5620 auto af
= actorCreateField(name
, tp
, stloc
);
5621 if (!af
.valid
) compileError("cannot create field '"~name
~"'");
5623 if (!token
.delim(",")) break;
5625 if (!token
.id
) compileError("identifier expected");
5629 if (!token
.delim(";")) compileError("';' expected");
5631 removeTokensFromMark(tokmark
);
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");
5641 auto eloc
= token
.loc
;
5642 auto v
= parseExpr();
5643 if (!token
.delim(";")) compileError("';' expected");
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();
5651 if (v
.type
.isStr
) c
.sval
= strlist
[v
.lit
]; else c
.ival
= v
.lit
;
5652 gconsts
[c
.name
] = c
;
5653 removeTokensFromMark(tokmark
);
5657 if (!ismethod
&& !token
.delim("(")) {
5658 // name already eaten
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");
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
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");
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");
5675 if (!token
.delim("[")) compileError("'[' expected");
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;
5691 if (!token
.delim("]")) compileError("']' expected");
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
;
5699 vmglobals
.length
+= tp
.cellSize
;
5700 vmglobals
[var
.idx
..$] = 0; // just in case
5703 var
.kind
= Variable
.Kind
.Global
;
5704 globals
[var
.name
] = var
;
5705 if (!token
.delim(",")) break;
5707 if (!token
.id
) compileError("identifier expected");
5711 if (!token
.delim(";")) compileError("';' expected");
5713 removeTokensFromMark(tokmark
);
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
);
5722 string fullname
= (methodactid
.length ? methodactid
~"::"~name
: name
);
5723 auto fp
= fullname
in funclist
;
5725 foreach (immutable idx
, ref fni
; fp
.ovloads
) {
5726 if (fni
.type
.callCompatibleNoAux(tf
)) { oldfound
= cast(int)idx
; break; }
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
);
5735 if (!token
.delim(";")) compileError("';' expected");
5737 removeTokensFromMark(tokmark
);
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(";")) {
5744 removeTokensFromMark(tokmark
);
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();
5757 var
.kind
= Variable
.Kind
.Global
;
5758 var
.name
= fullname
;
5760 var
.firstloc
= stloc
;
5761 funclist
[fullname
] = var
;
5770 fi
.actorid
= methodactid
;
5771 fi
.shortname
= name
;
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
);
5780 if (!token
.delim(";")) compileError("';' expected");
5782 removeTokensFromMark(tokmark
);
5785 if (tf
.hasrest
) compileErrorAt(stloc
, "scriped vararg functions aren't supported yet");
5788 if (token
.delim(";")) {
5790 removeTokensFromMark(tokmark
);
5792 if (var
.ovloads
[oldfound
].hasBody
) compileError("duplicate body");
5793 // function with body, skip the body
5794 if (!token
.delim("{")) compileError("'{' expected");
5797 if (token
.empty
) compileErrorAt(stloc
, "unterminated function declaration");
5798 if (token
.delim("{")) { ++level
; }
5799 else if (token
.delim("}")) { if (--level
== 0) break; }
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)[])) {
5822 static if (is(CT
== string
)) c
.sval
= val
; else c
.sval
= val
.idup
;
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
;
5839 if (ename
.length
== 0) ename
= ET
.stringof
;
5840 alias xname
= ename
;
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
;
5850 gconsts
[c
.name
] = c
;
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
...) () {
5864 enum rn
= fullyQualifiedName
!RT
;
5865 res
[rpos
..rpos
+rn
.length
] = rn
[];
5866 rpos
+= cast(int)rn
.length
;
5867 res
[rpos
..rpos
+1] = "|";
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
{
5881 static struct FuncCache
{
5886 __gshared FuncCache
[][string
] fcache
;
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
);
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
;
5927 foreach (immutable fidx
, ref FuncInfo fi
; var
.ovloads
) {
5928 if (fi
.type
.callCompatible(tf
)) {
5930 if (auto fcp
= cacheStr
in fcache
) fc
= *fcp
;
5931 fc
~= FuncCache(cast(int)fidx
, var
);
5932 fcache
[cacheStr
] = fc
;
5936 static if (is(RT
== void)) {
5938 foreach (immutable fidx
, ref FuncInfo fi
; var
.ovloads
) {
5939 if (fi
.type
.callCompatibleNoAux(tf
)) {
5941 if (auto fcp
= cacheStr
in fcache
) fc
= *fcp
;
5942 fc
~= FuncCache(cast(int)fidx
, var
);
5943 fcache
[cacheStr
] = fc
;
5948 throw new OverloadNotFound("cannot find suitable overload of '"~var
.name
~"' to call");
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
;
5960 } else static if (is(RT
== int)) {
5961 if (var
.type
.isBool || var
.type
.isInt
) return vmglobals
[var
.idx
];
5963 } else static if (is(RT
== string
)) {
5964 if (var
.type
.isStr
) return strlist
[vmglobals
[var
.idx
]];
5966 } else static if (is(RT
== ActorId
)) {
5967 if (var
.type
.isActor
) return ActorId(vmglobals
[var
.idx
]);
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
~"'");
5997 static assert(0, "wtf?!");
6003 public struct VMIFace
{
6004 static VMIFaceId
opIndex (string
fld) {
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");
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;
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
) {