/* $Header: /belch_a/users/rearl/tinymuck/src/RCS/compile.c,v 1.12 90/09/28 12:18:33 rearl Exp $ */ /* * $Log: compile.c,v $ * Revision 1.12 90/09/28 12:18:33 rearl * Fixed an ELSE bug, added shared program strings. * * Revision 1.11 90/09/18 07:53:40 rearl * Took out redundant instructions; put primitives in a hash table. * * Revision 1.10 90/09/16 04:41:37 rearl * Preparation code added for disk-based MUCK. * * Revision 1.9 90/09/12 08:59:21 rearl * Added ELSE construct and quoted function handling with the EXECUTE * instruction. * * Revision 1.8 90/08/27 03:19:57 rearl * Changed P's to ?'s, added INSTR, RINSTR, OK?, TIME (tentative) * * Revision 1.7 90/08/11 03:50:46 rearl * *** empty log message *** * * Revision 1.6 90/08/02 17:38:36 rearl * *** empty log message *** * * Revision 1.5 90/07/29 23:44:45 rearl * Added string primitives: strncmp, strcut, strlen. * * Revision 1.4 90/07/29 17:30:58 rearl * Fixed compiler waning/error messages. * * Revision 1.3 90/07/23 14:52:00 casie * *** empty log message *** * * Revision 1.2 90/07/21 13:14:13 casie * ChupChups's changes checked in. * * Revision 1.1 90/07/19 23:02:12 casie * Initial revision * * */ #include "copyright.h" #include "config.h" #include "db.h" #include "interface.h" #include "inst.h" #include "externs.h" #include <ctype.h> /* This file contains code for doing "byte-compilation" of mud-forth programs. As such, it contains many internal data structures and other such which are not found in other parts of TinyMUCK. */ /* The IF_STACK is a stack for holding previous IF statements. Everytime a THEN is encountered, the next address is inserted into the code before the most recent IF. */ static hash_tab primitive_list[COMP_HASH_SIZE]; struct IF_STACK { struct INTERMEDIATE *place; struct IF_STACK *next; } *if_stack; /* This structure is an association list that contains both a procedure name and the place in the code that it belongs. A lookup to the procedure will see both it's name and it's number and so we can generate a reference to it. Since I want to disallow co-recursion, I will not allow forward referencing. */ struct PROC_LIST { const char *name; struct INTERMEDIATE *code; struct PROC_LIST *next; } *procs; /* The intermediate code is code generated as a linked list when there is no hint or notion of how much code there will be, and to help resolve all references. There is always a pointer to the current word that is being compiled kept. */ struct INTERMEDIATE { int no; /* which number instruction this is */ struct inst in; /* instruction itself */ struct INTERMEDIATE *next; /* next instruction */ }; static int nowords; /* number of words compiled */ static struct INTERMEDIATE *curr_word; /* word currently being compiled */ static struct INTERMEDIATE *first_word; /* first word of the list */ static struct INTERMEDIATE *curr_proc; /* first word of current procedure */ /* variable names. The index into variables give you what position * the variable holds. */ static const char *variables[MAX_VAR] = { "ME", "LOC", "TRIGGER" }; static struct line *curr_line; /* current line */ static int lineno; /* current line number */ static const char *next_char; /* next char * */ static dbref player, program; /* globalized player and program */ /* 1 if error occured */ static int compile_err; int primitive(const char *s); /* returns primitive_number if primitive */ void free_prog(struct inst *, int); const char *next_token(void); struct INTERMEDIATE *next_word(const char *); struct INTERMEDIATE *process_special(const char *); struct INTERMEDIATE *primitive_word(const char *); struct INTERMEDIATE *string_word(const char *); struct INTERMEDIATE *number_word(const char *); struct INTERMEDIATE *object_word(const char *); struct INTERMEDIATE *quoted_word(const char *); struct INTERMEDIATE *call_word(const char *); struct INTERMEDIATE *var_word(const char *); const char *do_string(void); void do_comment(void); struct INTERMEDIATE *new_inst(void); struct INTERMEDIATE *find_if(void); void cleanup(void); void add_proc(const char *, struct INTERMEDIATE *); void addif(struct INTERMEDIATE *); int add_variable(const char *); int special(const char *); int call(const char *); int quoted(const char *); int object(const char *); int string(const char *); int variable(const char *); int get_primitive(const char *); void copy_program(void); void set_start(void); char *line_copy = NULL; int macrosubs; /* Safeguard for macro-subst. infinite loops */ /* Character defines */ #define BEGINCOMMENT '(' #define ENDCOMMENT ')' #define BEGINSTRING '"' #define ENDSTRING '"' #define BEGINMACRO '.' #define SUBSTITUTIONS 20 /* How many nested macros will we allow? */ char my__buf[BUFFER_LEN]; /* abort compile macro */ #define abort_compile(C) { \ sprintf(my__buf, "Error in line %d: %s",lineno,C); \ if (line_copy) { \ free ((void *) line_copy); \ line_copy = NULL; \ } if (FLAGS(player) & INTERACTIVE){ \ notify(player, my__buf); } \ else { \ log_muf("MUF compiler warning in program %d:\n%s\n", (int) program, my__buf); } \ cleanup(); \ compile_err++; \ free_prog(DBFETCH(program)->sp.program.code, \ DBFETCH(program)->sp.program.siz); \ return 0; } /* for void functions */ #define v_abort_compile(C) { char _buf[BUFFER_LEN]; \ sprintf(_buf, "Error in line %d: %s",lineno,C); \ if (line_copy) { \ free ((void *) line_copy); \ line_copy = NULL; \ } if (FLAGS(player) & INTERACTIVE){ \ notify(player, _buf); } \ else { \ log_muf("MUF compiler warning in program %d:\n%s\n", (int) program, _buf); } \ cleanup(); \ compile_err++; \ free_prog(DBFETCH(program)->sp.program.code, \ DBFETCH(program)->sp.program.siz); \ (DBFETCH(program)->sp.program.code)= (struct inst *) 0; \ DBFETCH(program)->sp.program.siz=0; \ return; } /* overall control code. Does piece-meal tokenization parsing and backward checking. */ void do_compile(dbref player_in, dbref program_in) { const char *token; struct INTERMEDIATE *new_word; /* set all global variables */ nowords = 0; curr_word = first_word = curr_proc = 0; player = player_in; program = program_in; lineno = 1; curr_line = DBFETCH(program)->sp.program.first; if (curr_line) next_char = curr_line -> this_line; first_word = curr_word = NULL; procs = 0; compile_err = 0; if_stack = 0; /* free old stuff */ free_prog(DBFETCH(program)->sp.program.code, DBFETCH(program)->sp.program.siz); if (!curr_line) v_abort_compile("Missing program text."); /* do compilation */ while (token = next_token() ) { new_word = next_word(token); /* test for errors */ if (compile_err) return; if (new_word) { if (!first_word) first_word = curr_word = new_word; else { curr_word -> next = new_word; curr_word = curr_word -> next; } } while (curr_word && curr_word -> next) curr_word = curr_word -> next; free((void *) token); } if (curr_proc) v_abort_compile("Unexpected end of file."); if (!procs) v_abort_compile("Missing procedure definition."); /* do copying over */ copy_program(); if (compile_err) return; set_start(); cleanup(); } struct INTERMEDIATE * next_word(const char *token) { struct INTERMEDIATE *new_word; char buf[BUFFER_LEN]; if (!token) return 0; if (special(token)) new_word = process_special(token); else if (variable(token)) new_word = var_word(token); else if (primitive(token)) new_word = primitive_word(token); else if (string(token)) new_word = string_word(token + 1); else if (number(token)) new_word = number_word(token); else if (object(token)) new_word = object_word(token); else if (quoted(token)) new_word = quoted_word(token + 1); else if (call(token)) new_word = call_word(token); else { sprintf(buf, "Unrecognized word %s.", token); abort_compile(buf); } return new_word; } /* Little routine to do the line_copy handling right */ void advance_line() { curr_line = curr_line -> next; lineno++; macrosubs = 0; if (line_copy) { free ((void *) line_copy); line_copy = NULL; } if (curr_line) next_char = (line_copy = alloc_string(curr_line -> this_line)); else next_char = (line_copy = NULL); } /* Skips comments, grabs strings, returns NULL when no more tokens to grab. */ const char * next_token() { char buf[BUFFER_LEN]; char *expansion, *temp; int i; if (!curr_line) return (char *) 0; if (!next_char) return (char *) 0; /* skip white space */ while (*next_char && isspace(*next_char)) next_char++; if (!(*next_char)) { advance_line(); if (!curr_line) return (char *) 0; else return next_token(); } /* take care of comments */ if (*next_char == BEGINCOMMENT) { do_comment(); return next_token(); } if (*next_char == BEGINSTRING) return do_string(); /* macro */ if (*next_char == BEGINMACRO) { next_char++; for (i = 0; *next_char && !isspace(*next_char); i++) { buf[i] = *next_char; next_char++; } buf[i] = '\0'; if (!(expansion = (char *)macro_expansion(macrotop, buf))) { abort_compile ("Macro not defined."); } else { if (++macrosubs > SUBSTITUTIONS) { abort_compile ("Too many macro substitutions."); } else { temp = (char *) malloc(strlen(next_char) + strlen(expansion) + 21); strcpy(temp, expansion); strcat(temp, next_char); free ((void *) expansion); free ((void *) line_copy); next_char = line_copy = temp; return next_token(); } } } /* ordinary token */ for (i = 0; *next_char && !isspace(*next_char); i++) { buf[i] = *next_char; next_char++; } buf[i] = '\0'; return alloc_string(buf); } /* skip comments */ void do_comment() { while (*next_char && *next_char != ENDCOMMENT) next_char++; if (!(*next_char)) { advance_line(); if (!curr_line) { v_abort_compile("Unterminated comment."); } do_comment(); } else { next_char++; if (!(*next_char)) advance_line(); } } /* return string */ const char * do_string() { char buf[BUFFER_LEN]; int i = 0, quoted = 0; buf[i] = *next_char; next_char++; i++; while ((quoted || *next_char != ENDSTRING) && *next_char) if (*next_char == '\\' && !quoted) { quoted++; next_char++; } else { buf[i] = *next_char; i++; next_char++; quoted = 0; } if (!*next_char) { abort_compile("Unterminated string found at end of line."); } next_char++; buf[i] = '\0'; return alloc_string(buf); } /* process special. Performs special processing. It sets up FOR and IF structures. Remember --- for those, we've got to set aside an extra argument space. */ struct INTERMEDIATE * process_special(const char *token) { char buf[BUFFER_LEN]; const char *tok; struct INTERMEDIATE *new; if (!string_compare(token, ":")) { const char *proc_name; if (curr_proc) abort_compile("Definition within definition."); proc_name = next_token(); if (!proc_name) abort_compile("Unexpected end of file within procedure."); tok = next_token(); new = next_word(tok); if (tok) free((void *) tok); if (!new) { sprintf(buf, "Error in definition of %s.", proc_name); abort_compile(buf); } curr_proc = new; add_proc(proc_name, new); return new; } else if (!string_compare(token, ";")) { if (if_stack) abort_compile("Unexpected end of procedure definition."); if (!curr_proc) abort_compile("Procedure end without body."); curr_proc = 0; new = new_inst(); new -> no = nowords++; new -> in.type = PROG_PRIMITIVE; new -> in.data.number = IN_RET; return new; } else if (!string_compare(token, "IF")) { struct INTERMEDIATE *curr; new = new_inst(); new -> no = nowords++; new -> in.type = PROG_ADD; new -> in.data.call = 0; new -> next = new_inst(); curr = new -> next; curr -> no = nowords++; curr -> in.type = PROG_PRIMITIVE; curr -> in.data.number = IN_IF; addif(new); return new; } else if (!string_compare(token, "ELSE")) { struct INTERMEDIATE *eef; struct INTERMEDIATE *curr; struct INTERMEDIATE *after; eef = find_if(); if (!eef) abort_compile("ELSE without IF."); new = new_inst(); new -> no = nowords++; new -> in.type = PROG_ADD; new -> in.data.call = 0; new -> next = new_inst(); curr = new -> next; curr -> no = nowords++; curr -> in.type = PROG_PRIMITIVE; curr -> in.data.number = IN_JMP; addif(new); tok = next_token(); curr -> next = after = next_word(tok); if (tok) free((void *) tok); if (!after) abort_compile("Unexpected end of program."); eef -> in.data.number = after -> no; return new; } else if (!string_compare(token, "THEN")) { /* can't use 'if' because it's a reserved word */ struct INTERMEDIATE *eef; eef = find_if(); if (!eef) { abort_compile("THEN without IF."); } tok = next_token(); new = next_word(tok); if (tok) free((void *) tok); if (!new) { abort_compile("Unexpected end of program."); } eef -> in.data.number = new -> no; return new; } else if (!string_compare(token, "CALL")) { struct INTERMEDIATE *curr; new = new_inst(); new -> no = nowords++; new -> in.type = PROG_PRIMITIVE; new -> in.data.number = IN_CALL; new -> next = new_inst(); curr = new -> next; curr -> no = nowords++; curr -> in.type = PROG_OBJECT; curr -> in.data.objref = program; curr -> next = new_inst(); curr = curr -> next; curr -> no = nowords++; curr -> in.type = PROG_PRIMITIVE; curr -> in.data.number = IN_PROGRAM; return new; } else if (!string_compare(token, "VAR")) { if (curr_proc) abort_compile("Variable declared within procedure."); tok = next_token(); if (!tok || !add_variable(tok)) abort_compile("Variable limit exceeded."); if (tok) free((void *) tok); return 0; } else { sprintf(buf, "Unrecognized special form %s found in %d.", token, lineno); abort_compile(buf); } } /* return primitive word. */ struct INTERMEDIATE * primitive_word(const char *token) { struct INTERMEDIATE *new; new = new_inst(); new -> no = nowords++; new -> in.type = PROG_PRIMITIVE; new -> in.data.number = get_primitive(token); return new; } /* return self pushing word (string) */ struct INTERMEDIATE * string_word(const char *token) { struct INTERMEDIATE *new; new = new_inst(); new -> no = nowords++; new -> in.type = PROG_STRING; new -> in.data.string = alloc_prog_string(token); return new; } /* return self pushing word (number) */ struct INTERMEDIATE * number_word(const char *token) { struct INTERMEDIATE *new; new = new_inst(); new -> no = nowords++; new -> in.type = PROG_INTEGER; new -> in.data.number = atoi(token); return new; } /* do a subroutine call --- push address onto stack, then make a primitive CALL. */ struct INTERMEDIATE * call_word(const char *token) { struct INTERMEDIATE *new; struct PROC_LIST *p; new = new_inst(); new -> no = nowords++; new -> in.type = PROG_ADD; for (p = procs; p; p = p -> next) if (!string_compare(p -> name, token)) break; new -> in.data.number = p -> code -> no; new -> next = new_inst(); new -> next -> no = nowords++; new -> next -> in.type = PROG_PRIMITIVE; new -> next -> in.data.number = IN_EXECUTE; return new; } struct INTERMEDIATE * quoted_word(const char *token) { struct INTERMEDIATE *new; struct PROC_LIST *p; new = new_inst(); new -> no = nowords++; new -> in.type = PROG_ADD; for (p = procs; p; p = p -> next) if (!string_compare(p -> name, token)) break; new -> in.data.number = p -> code -> no; return new; } /* returns number corresponding to variable number. We assume that it DOES exist */ struct INTERMEDIATE * var_word(const char *token) { struct INTERMEDIATE *new; int var_no; new = new_inst(); new -> no = nowords++; new -> in.type = PROG_VAR; for (var_no = 0; var_no < MAX_VAR; var_no++) if (!string_compare(token, variables[var_no])) break; new -> in.data.number = var_no; return new; } /* check if object is in database before putting it in */ struct INTERMEDIATE * object_word(const char *token) { struct INTERMEDIATE *new; int objno; objno = atol(token + 1); new = new_inst(); new -> no = nowords++; new -> in.type = PROG_OBJECT; new -> in.data.objref = objno; return new; } /* support routines for internal data structures. */ /* add procedure to procedures list */ void add_proc(const char *proc_name, struct INTERMEDIATE *place) { struct PROC_LIST *new; new = (struct PROC_LIST *) malloc(sizeof(struct PROC_LIST)); new -> name = alloc_string(proc_name); new -> code = place; new -> next = procs; procs = new; } /* add if to if stack */ void addif(struct INTERMEDIATE *place) { struct IF_STACK *new; new = (struct IF_STACK *) malloc(sizeof(struct IF_STACK)); new -> place = place; new -> next = if_stack; if_stack = new; } /* pops topmost if off the stack */ struct INTERMEDIATE * find_if(void) { struct INTERMEDIATE *temp; struct IF_STACK *tofree; if (!if_stack) return 0; temp = if_stack -> place; tofree = if_stack; if_stack = if_stack -> next; free((void *) tofree); return temp; } /* adds variable. Return 0 if no space left */ int add_variable(const char *varname) { int i; for (i = RES_VAR; i < MAX_VAR; i++) if (!variables[i]) break; if (i == MAX_VAR) return 0; variables[i] = alloc_string(varname); return i; } /* predicates for procedure calls */ int special(const char *token) { return ( token && ! (string_compare(token, ":") && string_compare(token, ";") && string_compare(token, "IF") && string_compare(token, "ELSE") && string_compare(token, "THEN") && string_compare(token, "CALL") && string_compare(token, "VAR") ) ); } /* see if procedure call */ int call(const char *token) { struct PROC_LIST *i; for (i = procs; i; i = i -> next) if (!string_compare(i -> name, token)) return 1; return 0; } /* see if it's a quoted procedure name */ int quoted(const char *token) { return ( *token == '\'' && call(token + 1)); } /* see if it's an object # */ int object(const char *token) { if (*token == '#' && number(token + 1)) return 1; else return 0; } /* see if string */ int string(const char *token) { return ( token[0] == '"' ); } int variable(const char *token) { int i; for (i = 0; i < MAX_VAR && variables[i]; i++) if (!string_compare(token, variables[i])) return 1; return 0; } /* see if token is primitive */ int primitive(const char *token) { return get_primitive(token); } /* return primitive instruction */ int get_primitive(const char *token) { hash_data *hd; if ((hd = find_hash(token, primitive_list, COMP_HASH_SIZE)) == NULL) return 0; else { return (hd -> ival); } } /* clean up as nicely as we can. */ void cleanup(void) { struct INTERMEDIATE *wd, *tempword; struct IF_STACK *eef, *tempif; struct PROC_LIST *p, *tempp; int i; for (wd = first_word; wd; wd = tempword) { tempword = wd -> next; if (wd -> in.type == PROG_STRING) if (wd -> in.data.string) free((void *)wd -> in.data.string); free((void *) wd); } first_word = 0; for (eef = if_stack; eef; eef = tempif) { tempif = eef -> next; free((void *) eef); } if_stack = 0; for (p = procs; p; p = tempp) { tempp = p -> next; free((void *)p -> name); free((void *) p); } procs = 0; for (i = RES_VAR; i < MAX_VAR && variables[i]; i++) { free((void *) variables[i]); variables[i] = 0; } } /* copy program to an array */ void copy_program(void) { /* Everything should be peachy keen now, so we don't do any error checking */ struct INTERMEDIATE *curr; struct inst *code; int i; if (!first_word) v_abort_compile("Nothing to compile."); code = (struct inst *) malloc(sizeof(struct inst) * (nowords + 1)); i = 0; for (curr = first_word; curr; curr = curr -> next) { code[i].type = curr -> in.type; switch (code[i].type) { case PROG_PRIMITIVE: case PROG_INTEGER: case PROG_VAR: code[i].data.number = curr -> in.data.number; break; case PROG_STRING: code[i].data.string = curr -> in.data.string ? alloc_prog_string(curr -> in.data.string->data) : 0; break; case PROG_OBJECT: code[i].data.objref = curr -> in.data.objref; break; case PROG_ADD: code[i].data.call = code + curr -> in.data.number; break; default: v_abort_compile("Unknown type compile! Internal error."); break; } i++; } DBSTORE(program, sp.program.code, code); } void set_start(void) { DBSTORE(program, sp.program.siz, nowords); DBSTORE(program, sp.program.start, (DBFETCH(program)->sp.program.code + procs -> code -> no)); } /* allocate and initialize data linked structure. */ struct INTERMEDIATE * new_inst() { struct INTERMEDIATE *new; new = (struct INTERMEDIATE *) malloc(sizeof (struct INTERMEDIATE)); new -> next = 0; new -> no = 0; new -> in.type = 0; new -> in.data.number = 0; return new; } void free_prog(struct inst *c, int siz) { int i; for (i = 0; i < siz; i++) if (c[i].type == PROG_STRING && c[i].data.string) free((void *) c[i].data.string); if (c) free((void *) c); DBSTORE(program, sp.program.code, 0); DBSTORE(program, sp.program.siz, 0); } static void add_primitive(int val) { hash_data hd; hd.ival = val; if (add_hash(base_inst[val - BASE_MIN], hd, primitive_list, COMP_HASH_SIZE) == NULL) panic("Out of memory"); else return; } void clear_primitives(void) { kill_hash(primitive_list, COMP_HASH_SIZE); return; } void init_primitives(void) { int i; clear_primitives(); for (i = BASE_MIN; i <= BASE_MAX; i++) { add_primitive(i); } }