/* ------------------------------------------------------------------------- */ /* "express" : The expression evaluator */ /* */ /* Part of Inform release 5 */ /* */ /* ------------------------------------------------------------------------- */ #include "header.h" int sys_functions[16]; extern int replace_sf(char *b) { int x=-1; word(b,2); On_("parent") x=0; On_("sibling") x=1; On_("younger") x=2; On_("child") x=3; On_("eldest") x=4; On_("random") x=5; On_("prop_len") x=6; On_("prop_addr") x=7; On_("prop") x=8; On_("children") x=9; On_("youngest") x=10; On_("elder") x=11; On_("indirect") x=12; if (x==-1) return(0); sys_functions[x]=1; return(1); } /* ------------------------------------------------------------------------- */ /* 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; #ifndef ALLOCATE_BIG_ARRAYS static treenode tree[MAX_EXPRESSION_NODES]; #else static treenode *tree; #endif extern void express_allocate_arrays(void) { #ifdef ALLOCATE_BIG_ARRAYS tree = my_calloc(sizeof(treenode), MAX_EXPRESSION_NODES, "expression tree"); #endif } extern void express_free_arrays(void) { #ifdef ALLOCATE_BIG_ARRAYS my_free(&tree, "expression tree"); #endif } /* ------------------------------------------------------------------------- */ /* 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[].) */ /* ------------------------------------------------------------------------- */ #define ROOT_NODE -4 #define BLANK_NODE -3 #define CDONE_NODE -2 #define SP_NODE -1 #define LEAF_NODE 0 #define STORE_NODE 1 #define ARITH_NODE 2 #define FCALL_NODE 3 #define COND_NODE 4 #define NCOND_NODE 5 #define LOGICAND_NODE 6 #define LOGICOR_NODE 7 #define OR_NODE 8 #define ALTERA_NODE 9 #define ALTERB_NODE 10 static int tree_size, dryad, resting, comma_mode, dryad_veil=-1, sofar_empty; /* ------------------------------------------------------------------------- */ /* Pretty printing (for diagnostics) */ /* ------------------------------------------------------------------------- */ 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(""); break; case BLANK_NODE: printf(""); break; case CDONE_NODE: printf(""); break; case SP_NODE: printf(""); 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=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; } while ((tree[above_dryad].priority>prio) || ((tree[above_dryad].priority==prio) && (type==ARITH_NODE) && (tree[above_dryad].type==ARITH_NODE))) { dryad=above_dryad; above_dryad=tree[dryad].up; } if (prefix==0) { while (tree[dryad].type== BLANK_NODE) { for (i=1; i %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; static void estack_line(char *rewrite) { if (etrace_mode>=1) printf("%2d %s\n",++lines_compiled,rewrite+1); stack_line(rewrite); } 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? */ /* --------------------------------------------------------------------- */ t=word_token(fromword); if ((t!=OPENB_SEP) && (t!=MINUS_SEP) && (condition_context==0) && (t!=DEC_SEP) && (t!=INC_SEP)) { word(sub_buffer,fromword+1); c=sub_buffer[0]; if ((c==0) || (isalpha(c)) || (isdigit(c)) || (c=='#') || (c==',') || (c==':')) { next_token=fromword+1; if (c==0) next_token=-1; return(fromword); } } /* --------------------------------------------------------------------- */ /* 2. Initially the tree is just a root node plus one blank branch */ /* --------------------------------------------------------------------- */ tree[0].up= -1; tree[0].type= ROOT_NODE; tree[0].b[0]=1; tree[0].arity=1; tree[0].branches_made=0; tree[0].priority=0; tree[1].up=0; tree[1].type= BLANK_NODE; tree[1].priority=0; tree_size=2; comma_mode=0; dryad=1; resting=0; sofar_empty=1; last_was_leaf=0; bracket_level=0; token=fromword; revise_flag=0; if (etrace_mode==1) printf("\n++++++++++++++++++++++++++++++\n"); if (etrace_mode>=1) { printf("\nEvaluating expression %s%s%sat:\n ", (void_context==1)?"(in void context) ":"", (condition_context==1)?"(in condition context) ":"", (assign_context==1)?"(in assignment context) ":""); for (i=0; (i==0)||(sub_buffer[0]!=0); i++) { word(sub_buffer,fromword+i); printf("%s ",sub_buffer); } printf("\n"); } lines_compiled=0; /* --------------------------------------------------------------------- */ /* 3. Grow the tree */ /* --------------------------------------------------------------------- */ do { token++; wd_tk = word_token(token-1); sub_buffer[0]=0; if (wd_tk==-1) { word(sub_buffer,token-1); if (sub_buffer[0]==0) break; } if (last_was_comma>0) last_was_comma--; if (last_was_openb>0) last_was_openb--; if (etrace_mode==1) printf("Dryad reads '%s'\nSofar_empty=%d\n",sub_buffer,sofar_empty); switch(wd_tk) { case SETEQUALS_SEP: Op_(STORE_NODE, "store", 3); revise_flag=1; its_void=1; last_was_openb=2; break; case PLUS_SEP: Op_(ARITH_NODE, "add", 4); break; case MINUS_SEP: if (sofar_empty==0) Op_(ARITH_NODE, "sub", 4); else Op1_(ARITH_NODE, "sub 0", 8); break; case TIMES_SEP: Op_(ARITH_NODE, "mul", 5); break; case DIVIDE_SEP: Op_(ARITH_NODE, "div", 5); break; case REMAINDER_SEP: Op_(ARITH_NODE, "mod", 5); break; case ARTAND_SEP: Op_(ARITH_NODE, "and", 5); break; case ARTOR_SEP: Op_(ARITH_NODE, "or", 5); break; case ARROW_SEP: Op_(ARITH_NODE, "loadb", 6); break; case DARROW_SEP: Op_(ARITH_NODE, "loadw", 6); break; case PROPERTY_SEP: Op_(ARITH_NODE, "get_prop", 7); break; case PROPADD_SEP: Op_(ARITH_NODE, "get_prop_addr", 7); break; case PROPNUM_SEP: Op_(ARITH_NODE, "_get_prop_len", 7); revise_flag=1; break; case INC_SEP: Op1_(ALTERA_NODE, "inc", 9); break; case DEC_SEP: Op1_(ALTERA_NODE, "dec", 9); break; case OPENB_SEP: current_priority+=10; last_was_openb=2; if (last_was_leaf==0) { if (bracket_level==MAX_EXPRESSION_BRACKETS) { error("Brackets '(' too deeply nested"); goto BigBreak; } brackets[bracket_level++]=1; } else { sofar_empty=1; brackets[bracket_level++]=0; j=token; call_args=2; call_level=bracket_level; word(sub_buffer,token); OnS_(")") call_args=1; else do { word(sub_buffer,j++); if (sub_buffer[0]==0) { error("Missing bracket ')' in function call"); goto BigBreak; } if ((strcmp(sub_buffer,",")==0) &&(bracket_level==call_level)) call_args++; OnS_("(") call_level++; OnS_(")") call_level--; } while (call_level>=bracket_level); grow_branch(call_args,token-1,FCALL_NODE,"call", 1+current_priority); comma_mode=1; } sofar_empty=1; break; case CLOSEB_SEP: if (bracket_level--==0) goto BigBreak; current_priority-=10; break; case COMMA_SEP: if (bracket_level<=0) goto DefaultCase; if (brackets[bracket_level-1]==1) error("Spurious comma ','"); comma_mode=1; sofar_empty=1; last_was_comma=2; if (tree[dryad].type!=BLANK_NODE) { while (tree[dryad].type!=ROOT_NODE) { while (tree[dryad].type!=FCALL_NODE) dryad=tree[dryad].up; if (tree[dryad].branches_made0) 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; } sofar_empty=0; grow_leaf(token-1); last_was_leaf=1; goto UnlessItWas; } else if (sub_buffer[0]=='-') { Op_(ARITH_NODE,"add",4); grow_leaf(token-1); sofar_empty=0; } else goto BigBreak; 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=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=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_made0)?1:0; j3) rewrite[8]='2'; else rewrite[8]=' '; if ((tosp_flag==1)&&(void_context==1)&&(tree[call_i].up==0)) { sprintf(rewrite+(strlen(rewrite)-2), "temp_global"); thrown_away_flag=1; } } if (indirect_flag==1) if ((tosp_flag==1)&&(void_context==1)&&(tree[call_i].up==0)) { sprintf(rewrite+(strlen(rewrite)-2), "temp_global"); thrown_away_flag=1; } } if (actual_version >= 5) { if (sysfun_arity==0) { rewrite[7]='s'; switch(call_args) { case 0: rewrite[6]='1'; break; case 1: rewrite[6]='2'; break; default: rewrite[6]='v'; break; } if (call_args>3) rewrite[8]='2'; else rewrite[8]=' '; if ((tosp_flag==1)&&(void_context==1)&&(tree[call_i].up==0)) { rewrite[7]='n'; rewrite[strlen(rewrite)-3]=0; thrown_away_flag=1; } } if (indirect_flag==1) if ((tosp_flag==1)&&(void_context==1)&&(tree[call_i].up==0)) { sprintf(rewrite+(strlen(rewrite)-2), "temp_global"); thrown_away_flag=1; } } } estack_line(rewrite); if (dummy_branch_flag==1) { sprintf(rewrite,"@._x%d",no_dummy_labels++); estack_line(rewrite); } if (t==STORE_NODE) tree[i]=tree[tree[i].b[0]]; else { tree[i].arity=0; tree[i].type= SP_NODE; tree[i].branches_made=0; } switch(t) { default: tree[i].type = SP_NODE; break; case COND_NODE: case NCOND_NODE: case LOGICOR_NODE: case LOGICAND_NODE: tree[i].type = CDONE_NODE; break; } Detached: if (etrace_mode==1) show_tree("to"); } while (1==1); return(1); TooComplexToo: memoryerror("MAX_EXPRESSION_NODES", MAX_EXPRESSION_NODES); return(1); } 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 }