struct descriptor { short code; /* 1=int, 2=float, 3=string, 4=array */ short length; /* if string or array, this is its length */ union { int i; /* integer */ float f; /* float */ char *s; /* string */ struct descriptor *a; /* array */ } value; }
char global[globalsize];and then address a given variable at an offset of k as
(*(struct descriptor)(global+k)).Global variables might all be descriptors, in which case you might simplify this by declaring global to be an array of struct descriptor instead of an array of char.
char constant[constantsize];and then address a given variable at a byte-offset of k as
(*(struct descriptor)(constant+k)).The constant region would hold a mixture of descriptors and raw string data, unless you chose to implement a "string region", which might be a good idea.
struct descriptor stack[stacksize]; int stacktop; int framepointer; int PC; void push(struct descriptor *dp) { if (stacktop==stacksize) error("stack overflow"); stack[stacktop++] = *dp; } void pushregs(int stktop, int fp, int pc) { if (stacktop==stacksize) error("stack overflow"); stack[stacktop].code = stktop; stack[stacktop].length = fp; stack[stacktop].value.i = pc; framepointer = stacktop; stacktop++; } void pop(struct descriptor *dp) { if (stacktop==0) error("stack underflow"); *dp = stack[--stacktop]; } void popregs() { if (stacktop==0) error("stack underflow"); stacktop--; framepointer = stack[stacktop].length; PC = stack[stacktop].value.i; }
#define G(k) (*(struct descriptor)(global+k)) #define INTP(d) (d.code == 1) #define REALP(d) (d.code == 2) #define NUMP(d) ((d.code == 1)||(d.code == 2)) #define STRINGP(d) (d.code == 3) #define ARRAYP(d) (d.code == 4) #define INT(d) (d.value.i) #define REAL(d) (REALP(d)?(d.value.f):(INTP(d)?((float)(INT(d))))) #define STRING(d) (d.value.s) #define ARRAY(d) (d.value.a)
The following templates assume (for example purposes) that x, y, and z are at offsets 0, 8, and 16 in the global region.
intermediate code instruction | C equivalent | Comment |
---|---|---|
x := y + z (numbers) | if (INTP(8) && INTP(16)) { G(0).code = 1; INT(G(0)) = INT(G(8)) + INT(G(16)); } else { G(0).code = 2; REAL(G(0)) = REAL(G(8)) + REAL(G(16)); } | Typical example of adding two numeric variables. If one of the operands is not an integer, we generate code to add operands as reals. One could treat constants in the same way, or generate optimized code for them. |
x := y + z (strings) | call to built-in basic_strcat; see call instruction -or- G(0).code = 3; G(0).length = G(8).length + G(16).length; if (G(0).length < 0) error("string is too long"); STRING(G(0)) = malloc(G(0).length); if (STRING(G(0)) == NULL) error("out of memory"); strncpy(STRING(G(0)), STRING(G(8)), G(8).length); strncpy(STRING(G(0)+G(8).length), STRING(G(16)), G(16).length); | |
x := - y (local variables) | ? ? ? | |
x := y | G(0) = G(8); ? | |
x := &y x := *y *x := y | n/a | These TA instructions not used? |
goto L100 | goto L100; | |
if x < y then goto L100 | if (REAL(G(0)) < REAL(G(8))) goto L100; | |
if x then goto L | ? ? | |
if !x then goto L | ? ? | |
param x | bas_push(&G(0)) | |
call p,n,x | p(n, &(G(0))); | built-in call = C call |
call L100,n,x | pushregs(stacktop, framepointer, next(PC)); goto L100; | |
return | popregs(); goto gotoPC; ... gotoPC: switch(PC) | |
global x,n1,n2 | ||
proc x,n1,n2 | ||
local x,n | .limit locals n | no local declarations needed other than a count of how many |
label Ln | Ln: | |
end | .end method |