/* ------------------------------------------------------------------------- */
/* Definition of expression tree branch */
/* ------------------------------------------------------------------------- */
typedef struct treenode {
int up; /* Node above */
int word; /* Token number in source line */
int type; /* Type of node (there are eight: see above) */
int priority; /* Eg, + has a low priority, * a higher one */
int label_number; /* For code blocks in && and || expressions */
int yes_label; /* Condition true */
int no_label; /* Condition false */
int arity; /* Number of branches expected */
int b[MAX_ARITY]; /* Nodes below (the branches)... */
int branches_made; /* ...from 0 up to this-1 */
char *op; /* Name of opcode to achieve this operation */
} treenode;
/* ------------------------------------------------------------------------- */
/* Compiler expression and assignment evaluator */
/* ------------------------------------------------------------------------- */
/* */
/* This is easily the most complicated algorithm in Inform, for two */
/* reasons - firstly, because I invented it in a hurry and know absolutely */
/* nothing about compiler theory, and second, because it tries to avoid */
/* recursion which uses the C stack. (Some machines to which Inform has */
/* been ported have had problems with the size of the C stack.) */
/* */
/* The code generator is also complicated by some awkward features of the */
/* Z-machine: for instance, the safe way to extract property lengths is */
/* tricky to get right. Anyway, enough excuses: */
/* */
/* The algorithm first makes a tree out of the expression and then */
/* clears it off again by clipping off nodes and stacking up corresponding */
/* assembly lines. This needs to be in the right order, since the stack */
/* can't very conveniently be re-ordered. */
/* */
/* The algorithm is iterative rather than recursive. In the first phase, */
/* we imagine a tree gradually growing as a dryad scampers over it. She */
/* is either resting on a leaf already grown, or else hovering in midair */
/* over the space where one will grow. Every so often she casts an easy */
/* spell to grow a leaf where she is, or a more powerful one to grow her */
/* leaf into a branch, and she then clambers along or down. */
/* */
/* The dryad is also sometimes awaiting a comma, since she knows that the */
/* line 45+1, ... ends with the comma, whereas i=fn(j,k) does not. Sadly */
/* she can get distracted while waiting, for instance in the expression */
/* (after reading j) i=fn(j+5*(l+1),2), but being conscientious she ties */
/* her veil where she was so as to be able to find the place again later. */
/* */
/* To follow her movements, try Informing with full expression tracing on */
/* (using "etrace full"): to look simply at the tree she grows, and the */
/* code it results in, try just "etrace". */
/* */
/* The main data structure (treenode) is commented at the top of the file. */
/* (Most of the variables have been more sensibly named since early */
/* releases: in particular now you can't see the woods[] for the tree[].) */
/* ------------------------------------------------------------------------- */
static void show_leaf(int ln, int depth)
{ char sl_buffer[100];
int j;
for (j=0; j<2*depth+2; j++) printf(" ");
if (ln==-1) { printf("..\n"); return; }
else if (etrace_mode==1) printf("%02d ",ln);
switch(tree[ln].type)
{ case ROOT_NODE: printf("<root>"); break;
case BLANK_NODE: printf("<blank>"); break;
case CDONE_NODE: printf("<condition done>"); break;
case SP_NODE: printf("<sp>"); break;
case LEAF_NODE: word(sl_buffer,tree[ln].word);
printf("%s",sl_buffer); break;
default: printf("%s",tree[ln].op); break;
}
if (etrace_mode==1)
{ if (tree[ln].priority!=0)
{ printf(" (%d)",tree[ln].priority); }
if (tree[ln].type>=STORE_NODE)
{ switch(tree[ln].type)
{ case STORE_NODE: printf(" [assignment] "); break;
case ARITH_NODE: printf(" [arithmetic] "); break;
case FCALL_NODE: printf(" [function call] "); break;
case COND_NODE: printf(" [condition] "); break;
case NCOND_NODE: printf(" [negated condition] "); break;
case LOGICAND_NODE: printf(" [conjunction] "); break;
case LOGICOR_NODE: printf(" [disjunction] "); break;
case OR_NODE: printf(" [or] "); break;
case ALTERA_NODE: printf(" [read then alter] "); break;
case ALTERB_NODE: printf(" [alter then read] "); break;
}
}
printf(" ^%d ",tree[ln].up);
if (ln==dryad) printf("(dryad here) ");
}
if ((tree[ln].type==LOGICAND_NODE)
||(tree[ln].type==LOGICOR_NODE))
printf(" {%d/%d/%d} ",tree[ln].label_number,
tree[ln].yes_label, tree[ln].no_label);
printf("\n");
for (j=0; j<tree[ln].arity; j++)
{ if (j<tree[ln].branches_made) show_leaf(tree[ln].b[j],depth+1);
else show_leaf(-1,depth+1);
}
}
static void show_tree(char *c)
{ printf("%s\n",c); show_leaf(tree[0].b[0],0);
if (etrace_mode==1)
{ printf("The dryad is at %d and is %s%s\n",
dryad,(resting==1)?"resting":"hovering in midair",
(comma_mode==0)?"":"\nwhile waiting for a comma");
if (dryad_veil!=-1)
printf("with her veil tied to %d\n",dryad_veil);
}
}
/* ------------------------------------------------------------------------- */
/* Growing the tree */
/* ------------------------------------------------------------------------- */
static void grow_branch(int a, int wn, int type, char *opcde, int prio)
{ int i, above_dryad, rflag=0, prefix=0, postfix=0;
if ((a>=5)&&(actual_version==3))
{ error("A function may be called with at most 3 arguments");
return;
}
if ((a>=9)&&(actual_version>3))
{ error("A function may be called with at most 7 arguments");
return;
}
above_dryad=tree[dryad].up;
if ((a==1)&&(type!=FCALL_NODE)&&(sofar_empty==1))
{ prefix=1;
if (type==ALTERA_NODE) type=ALTERB_NODE;
}
if (type==ALTERA_NODE) postfix=1;
if ((a==1)&&(type==FCALL_NODE)) postfix=1;
if (etrace_mode==1) printf("%s%s%s operator '%s'\n",
(prefix==1)?"prefix":"",(postfix==1)?"postfix":"",
((prefix==0)&&(postfix==0))?"infix":"",opcde);
sofar_empty=1;
if ((resting==0)&&(comma_mode==0)&&(prefix==0))
{ error("Operator has too few arguments"); return;
}
if (type==OR_NODE)
{ if ((strcmp(tree[above_dryad].op,"je")==0)
|| (strcmp(tree[above_dryad].op,"vje")==0))
{ if (tree[above_dryad].arity < 5)
{ tree[above_dryad].arity++; rflag=1; }
tree[above_dryad].op="vje";
if (tree[above_dryad].arity==5)
{ error("At most three values can be separated by 'or'");
rflag=0;
}
if (rflag==1)
{ if (tree_size==MAX_EXPRESSION_NODES) goto TooComplex;
dryad=tree_size++;
tree[dryad].up=above_dryad;
tree[dryad].type=BLANK_NODE;
tree[above_dryad].b[tree[above_dryad].arity-1]=dryad;
resting=0;
return;
}
}
else
error("'or' can only be used with the conditions '==' and '~='");
}
if (comma_mode==1)
{ comma_mode=0;
if (prefix==0) dryad_veil=dryad;
}
if (prefix==0)
{ while (tree[dryad].type== BLANK_NODE)
{ for (i=1; i<tree[above_dryad].arity; i++)
if (tree[above_dryad].b[i]==dryad)
{ dryad=tree[above_dryad].b[i-1]; break; }
}
}
if (prefix==1) tree[above_dryad].branches_made++;
for (i=0; i<tree[above_dryad].arity; i++)
if (tree[above_dryad].b[i]==dryad)
tree[above_dryad].b[i]=tree_size;
tree[above_dryad].branches_made++;
if (tree[above_dryad].branches_made<tree[above_dryad].arity)
{ dryad=tree[above_dryad].b[tree[above_dryad].branches_made];
resting=0;
}
else resting=1;
if (etrace_mode==1) show_tree("grow_leaf to");
return;
}
static void fix_yesno_labels(int n, int flag)
{ int i;
if ((tree[n].type!=LOGICOR_NODE)&&(tree[n].type!=LOGICAND_NODE))
return;
if (flag==1)
{
tree[n].yes_label = tree[tree[n].up].yes_label;
tree[n].no_label = tree[tree[n].up].no_label;
}
else
switch(tree[tree[n].up].type)
{ case ROOT_NODE:
tree[n].yes_label = no_dummy_labels-1;
tree[n].no_label = -1;
break;
case LOGICAND_NODE:
tree[n].yes_label = tree[n].label_number;
tree[n].no_label = tree[tree[n].up].no_label;
break;
case LOGICOR_NODE:
tree[n].yes_label = tree[tree[n].up].yes_label;
tree[n].no_label = tree[n].label_number;
break;
}
for (i=0; i<tree[n].branches_made; i++)
fix_yesno_labels(tree[n].b[i], (i==tree[n].branches_made-1)?1:0);
}
/* ------------------------------------------------------------------------- */
/* Main expression (and assignment) evaluator routine: */
/* */
/* expression(from) compiles code to evaluate the expression starting at */
/* token from, leaves next_token at the first not part of the expression */
/* (or -1 if the line was all used up); and returns */
/* */
/* -3 if the result was thrown away (eg. a void context function call) */
/* -2 if there is no result (eg, from "i=4" there is none) */
/* -1 if the result is on the stack */
/* n if the result is the constant term in token n */
/* ------------------------------------------------------------------------- */
static void eword(char *b, int bn)
{ if (tree[bn].type==SP_NODE) strcpy(b,"sp");
else word(b,tree[bn].word);
/* printf("Eword %d -> %d <%s>\n",bn,tree[bn].word,b); */
}
#define Op_(type,name,priority) grow_branch(2,token-1,type,name,\
priority+current_priority)
#define Op1_(type,name,priority) grow_branch(1,token-1,type,name,\
priority+current_priority)
#define OnCS_(x) if ((condition_context==1)&&(strcmp(sub_buffer,x)==0))
#define OnBS_(x) if ((bracket_level>0)&&(strcmp(sub_buffer,x)==0))
int next_token, void_context, condition_context=0,
assign_context=0, lines_compiled;
char condition_label[64];
static int brackets[MAX_EXPRESSION_BRACKETS], bracket_level;
extern int expression(int fromword)
{ int c, i, j, k, t, token, current_priority=0, wd_tk,
sysfun_arity, dummy_branch_flag,
call_level, call_flag, call_args, call_i, tosp_flag,
revise_flag, last_was_leaf,
its_void=0, thrown_away_flag=0, indirect_flag=0,
last_was_comma=0, last_was_openb=0;
/* --------------------------------------------------------------------- */
/* 1. A little optimisation: can we tell at once this will not be a */
/* full-blown expression, but just a single constant term? */
/* --------------------------------------------------------------------- */
/* --------------------------------------------------------------------- */
/* 2. Initially the tree is just a root node plus one blank branch */
/* --------------------------------------------------------------------- */
if (resting==1)
{ if (sub_buffer[0]=='-')
{ Op_(ARITH_NODE,"add",4);
grow_leaf(token-1); sofar_empty=0;
}
else
{ if (bracket_level>0)
error("Operator has too many arguments");
goto BigBreak;
}
}
else if (tree[dryad].type==BLANK_NODE)
{
if ((sub_buffer[0]=='-')
&&(last_was_openb==0)&&(last_was_comma==0))
{ Op_(ARITH_NODE,"add",4);
grow_leaf(token-1); sofar_empty=0;
goto AtomDone;
}
AtomDone:
last_was_leaf=0;
UnlessItWas: ;
} while (1==1);
BigBreak:
if ((wd_tk==-1)&&(sub_buffer[0]==0)) next_token= -1;
else next_token=token-1;
if (bracket_level>0) error("Too many brackets '(' in expression");
/* --------------------------------------------------------------------- */
/* 4. If necessary, revise the tree to allow for get_prop_len and the */
/* different contexts of the "=" assignment */
/* --------------------------------------------------------------------- */
if (revise_flag==1)
{
for (i=0; i<tree_size; i++)
{ j=tree[i].b[0];
if ((tree[i].type==STORE_NODE)&&(tree[j].type==ARITH_NODE)
&&(tree[i].op!=NULL)&&(strcmp(tree[i].op,"store")==0))
{ k=0;
if (tree[j].op!=NULL)
{ if (strcmp(tree[j].op,"get_prop")==0) k=1;
if (strcmp(tree[j].op,"loadb")==0) k=2;
if (strcmp(tree[j].op,"loadw")==0) k=3;
}
if (k!=0)
{
if (etrace_mode==1)
printf("Sub type %d nodes %d and %d\n",k,i,j);
if (etrace_mode==1) show_tree("Substituting from");
tree[j].b[tree[j].arity++] = tree[i].b[1];
tree[j].branches_made++;
tree[j].type=STORE_NODE;
tree[j].up=tree[i].up;
tree[i]=tree[j];
switch(k)
{ case 1: tree[i].op="put_prop"; break;
case 2: tree[i].op="storeb"; break;
case 3: tree[i].op="storew"; break;
}
if (etrace_mode==1) show_tree("Substituting to");
}
}
if ((tree[i].op!=NULL)&&(strcmp(tree[i].op,"_get_prop_len")==0))
{
tree[i].op="get_prop_addr";
tree[tree_size]=tree[i];
tree[tree_size].up=i;
tree[i].b[0]=tree_size;
tree[i].arity=1;
tree[i].branches_made=1;
tree[i].op="get_prop_len";
if (tree_size==MAX_EXPRESSION_NODES) goto TooComplexToo;
tree_size++;
if (etrace_mode==1) show_tree("Fixing a _get_prop_len to");
}
if (((tree[i].type==COND_NODE)||(tree[i].type==NCOND_NODE))
&&(tree[i].op!=NULL)&&(strcmp(tree[i].op,"in")==0))
{ if (etrace_mode==1) show_tree("Substituting 'in' from");
j=tree[i].b[0];
tree[i].op="je";
tree[tree_size]=tree[j];
tree[tree_size].up=j;
tree[j].b[0]=tree_size;
tree[j].arity=1;
tree[j].type=ARITH_NODE;
tree[j].branches_made=1;
tree[j].op="get_parent";
if (tree_size==MAX_EXPRESSION_NODES) goto TooComplexToo;
tree_size++;
if (etrace_mode==1) show_tree("Substituting to");
}
}
}
if (condition_context==1)
{ fix_yesno_labels(tree[0].b[0],0);
}
if (etrace_mode>=1) show_tree("Made the tree:");
/* --------------------------------------------------------------------- */
/* 5. Recursively cut off branches into assembly language */
/* --------------------------------------------------------------------- */
if (etrace_mode==2) printf("Compiling code:\n");
do
{ i=0;
DownDown:
if ((tree[i].type!=LOGICAND_NODE)&&(tree[i].type!=LOGICOR_NODE))
{ for (j=tree[i].branches_made-1; j>=0; j--)
{ t=tree[tree[i].b[j]].type;
if ((t!=LEAF_NODE)&&(t!=SP_NODE)&&(t!=CDONE_NODE))
{ i=tree[i].b[j]; goto DownDown;
}
}
}
else
{ for (j=0; j<tree[i].branches_made; j++)
{ t=tree[tree[i].b[j]].type;
if ((t!=LEAF_NODE)&&(t!=SP_NODE)&&(t!=CDONE_NODE))
{ i=tree[i].b[j]; goto DownDown;
}
}
}
if (etrace_mode==1) printf("Detaching %d\n",i);
if (i==0)
{ if (tree[tree[0].b[0]].type==SP_NODE) j=-1;
else if (tree[tree[0].b[0]].type==CDONE_NODE) j=-3;
else j=tree[tree[0].b[0]].word;
if (its_void==1) j=-2;
if ((condition_context==0)&&(j==-3))
{ error("Unexpected condition"); return(-2); }
if ((condition_context==1)&&(j!=-3))
{ error("Expected condition but found expression");
return(-2);
}
if (etrace_mode>=1)
{ if (next_token==-1) printf("Completed: line used up: ");
else
{ word(sub_buffer,next_token);
printf("Completed: next word '%s': ",sub_buffer);
}
if (j>=0) word(sub_buffer,j);
if (thrown_away_flag==1) printf("result thrown away\n");
else if (j==-1) printf("result on stack\n");
else if (j==-2) printf("no resulting value\n");
else if (j==-3) printf("a condition\n");
else printf("result in %s\n",sub_buffer);
}
if (thrown_away_flag==1) return(-3);
return(j);
}
if (tree[i].branches_made<tree[i].arity)
error("Operator has too few arguments");
SysF_("indirect",12)
{ if (tree[i].branches_made<2)
{ error("'indirect' takes at least one argument"); }
sysfun_arity=1; tree[i].op="icall"; indirect_flag=1;
}
else
if (sysfun_arity!=0)
{ if (sysfun_arity+1!=tree[i].branches_made)
{ error("Wrong number of arguments to system function");
}
}
}
else call_flag=0;
t=tree[i].type;
if ((t==LOGICAND_NODE)||(t==LOGICOR_NODE))
{ for (j=0; j<tree[i].branches_made; j++)
{ if (tree[tree[i].b[j]].type != CDONE_NODE)
{ error("Expected condition but found expression");
return(-2);
}
}
sprintf(rewrite,"@._x%d",tree[i].label_number);
estack_line(rewrite);
tree[i].type=CDONE_NODE;
goto Detached;
}
if (t==ALTERB_NODE)
{ if (tree[tree[i].b[0]].type!=LEAF_NODE)
{ error("'++' and '--' can only apply directly to variables");
return(-2);
}
eword(sub_buffer,tree[i].b[0]);
sprintf(rewrite,"@%s %s",tree[i].op,sub_buffer);
estack_line(rewrite);
tree[i].type=LEAF_NODE;
tree[i].word=tree[tree[i].b[0]].word;
tree[i].arity=0;
tree[i].branches_made=0;
if (tree[i].up==0)
if (assign_context==1) its_void=1;
goto Detached;
}
if (t==ALTERA_NODE)
{ if (tree[tree[i].b[0]].type!=LEAF_NODE)
{ error("'++' and '--' can only apply directly to variables");
return(-2);
}
if ((tree[i].up==0)&&(assign_context==1))
{ eword(sub_buffer,tree[i].b[0]);
sprintf(rewrite,"@%s %s",tree[i].op,sub_buffer);
estack_line(rewrite);
tree[i].type=LEAF_NODE;
tree[i].word=tree[tree[i].b[0]].word;
tree[i].arity=0;
tree[i].branches_made=0;
if (assign_context==1) its_void=1;
goto Detached;
}
eword(sub_buffer,tree[i].b[0]);
sprintf(rewrite,"@push %s",sub_buffer);
estack_line(rewrite);
sprintf(rewrite,"@%s %s",tree[i].op,sub_buffer);
estack_line(rewrite);
tree[i].type=SP_NODE;
tree[i].arity=0;
tree[i].branches_made=0;
goto Detached;
}
extern void assignment(int from, int flag)
{ int i;
next_token=from;
do
{ assign_context=1; i=expression(next_token); assign_context=0;
if ((i!=-2)&&(i!=from))
{ error("Expected an assignment but found an expression");
return;
}
if (i==from)
{ word(sub_buffer,from);
if (flag==1)
error_named("Expected an assignment, command, directive \
or opcode but found",sub_buffer);
else
error_named("Expected an assignment but found",sub_buffer);
return;
}
if (next_token==-1) return;
word(sub_buffer,next_token);
if (strcmp(sub_buffer,",")!=0) return;
next_token++;
} while (1==1);
}
extern void init_express_vars(void)
{ int i;
dryad_veil = -1;
condition_context = 0;
assign_context = 0;
for (i=0;i<16;i++) sys_functions[i]=0;
#ifdef ALLOCATE_BIG_ARRAYS
tree = NULL;
#endif
}