/******************* pilot.c v1.0 *****************/ /** This program is an interpreter for the Pilot CAI language as defined in the associated file pilot.bnf. (C) Copyright 1985, Dave Taylor http://www.intuitive.com/pilot/ This program is free software, considered as a library under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 2.1 of the License, or (at your option) any later version. You can redistribute it and/or modify it under those terms. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. **/ #include /* Standard C I/O library */ #include /* Character classify functions */ #define SLEN 256 /* string length */ #define NLEN 20 /* short string */ #define COLON ':' /* colon char */ #define STRINGDELIM '$' /* delimit stringvar*/ #define NUMDELIM '#' /* delimit num var */ #ifndef TRUE #define TRUE 1 /* boolean true */ #define FALSE 0 /* boolean false */ #endif #define MAXDEPTH 10 /* subroutine depth */ #define DEFAULT_DEBUG 0 /* 0=OFF, 1=ON */ #define NONFATAL 0 /* error */ #define FATAL 1 /* type */ #define NOERROR 0 /* error func. */ #define ERROR 1 /* return val */ #define UNKNOWN_STATEMENT 1 /* See */ #define BAD_IDENT 2 /* errmsg */ #define BAD_INSTRUCT 3 /* below */ #define DUP_LABEL 4 /* for */ #define UNKNOWN_LBL 5 /* a */ #define NO_LABEL 6 /* full */ #define UNEXPECT_EOF 7 /* description */ #define TOO_DEEP 8 /* of */ #define OUT_OF_MEM 9 /* each */ #define NAME_TOO_LONG 10 /* error */ #define UNDEF_VAR 11 /* code */ #define BAD_NUMBER 12 /* listed */ #define BAD_PARENS 13 /* here */ #define BAD_EXP 14 #define DIVIDE_BY_ZERO 15 #define BAD_LHS 16 #define MISSING_EQ 17 #define STRING_IN_EXP 18 #define BAD_REL_EXP 19 #define BAD_REL_OP 20 #define PLUS '+' #define MINUS '-' #define TIMES '*' #define DIVIDE '/' #define LEFT_PAREN '(' #define RIGHT_PAREN ')' /****** character classification routines *******/ #define end_of_line(c) (c == '\n' || c == '\r' || c == '\0') #define addops(c) (c == PLUS || c == MINUS ) #define mulops(c) (c == TIMES || c == DIVIDE ) #define mathops(c) (addops(c) || mulops(c)) #define relop(c) (c == '=' || c == '<' || c == '>') #define paren(c) (c == LEFT_PAREN || c == RIGHT_PAREN) #define special(c) (mathops(c) || relop(c) || paren(c)) #define stringvar(s) (s[0] == STRINGDELIM) #define numvar(s) (s[0] == NUMDELIM) #define whitespace(c) (c == ' ' || c == '\t') #define valid_char(c) (isalnum(c) || c == '_') /****** various one-line routines for the interpreter *******/ #define lastch(s) s[strlen(s)-1] #define remove_return(s) if (lastch(s) == '\n') lastch(s) = '\0' #define last_label() (last->name) #define init_get_token() line_loc = 0 #define unget_token() line_loc = last_line_loc char *errmsg[] = { /* GENERIC ERROR */ "Unknown generic error", /* UNKNOWN_STATEMENT */ "Unknown statement", /* BAD_IDENT */ "Bad identifier: '%s'", /* BAD_INSTRUCT */ "Badly formed instruction", /* DUP_LABEL */ "Duplicate label '%s'", /* UNKNOWN_LBL */ "Unknown label '%s'", /* NO_LABEL */ "Label '%s' not found in program", /* UNEXPECTED_EOF */ "End of File during search for label '%s'", /* TOO_DEEP */ "Routine calls nested too deeply", /* OUT_OF_MEM */ "Out of memory!", /* NAME_TOO_LONG */ "Name '%s' too long", /* UNDEF_VAR */ "Undefined variable: '%s'", /* BAD_NUMBER */ "Invalid format for numerical input!", /* BAD_PARENS */ "Badly formed expression: parenthesis", /* BAD_EXP */ "Badly formed expression", /* DIVIDE_BY_ZERO */ "Attempt to divide by zero!", /* BAD_LHS */ "Bad left-hand-side of expression", /* MISSING_EQ */ "Missing or misplaced '=' in expression", /* STRING_IN_EXP */ "String variable in numerical expression", /* BAD_REL_EXP */ "Bad relational expression", /* BAD_REL_OP */ "Bad relational operator: '%s'" }; struct a_label { /* The label table is a linked list */ char name[NLEN]; /* of label name, */ int loc; /* the line number it occurs on, */ struct a_label *next; /* and a link to the next element */ } *label_list, *last; struct symbol_entry { /* The symbol table is a binary tree */ char name[NLEN]; /* of symbol name */ char value[SLEN]; /* the printable current value */ int numvalue; /* the numeric value if number var */ struct symbol_entry *left, /* a left subnode link */ *right; /* and a right subnode link */ } *symbol_table, *symbol_node; int subroutine_stack[MAXDEPTH]; /* subroutine calls and returns */ FILE *fileid; /* input file descriptor */ char def_string[SLEN]; /* line read from stdin */ int current_line = 0, /* line being read from file */ line_loc, /* for line -> words transl. */ last_line_loc, /* for unget_token() routine */ boolean=TRUE, /* result of last match */ boolean_cont=FALSE, /* result of last bool test */ nesting_level = 0, /* how deep into 'use' calls */ error, /* error during exp parsing */ furthest_into_file = 0, /* furthest line read in file */ debug=DEFAULT_DEBUG; /* is debugging turned on? */ char *get_token(); /* forward declaration */ main(argc, argv) int argc; char *argv[]; { char line[SLEN]; /* input buffer for reading file */ if (argc == 3) debug++; else if (argc != 2) exit(printf("Usage: %s [-d] \n", argv[0])); init(argv[argc-1]); /* open file and such */ while (fgets(line, SLEN, fileid) != NULL) { remove_return(line); current_line++; if (debug) printf(" %2d > %s\n", current_line, line); if (strlen(line) > 0) parse(line); } wrapup(); /* close file and such */ } init(fname) char *fname; { /** initialize the interpreter - open file and related. **/ if ((fileid = fopen(fname, "r")) == NULL) wrapup(printf("** Fatal error: Could not open '%s'!\n", fname)); label_list = NULL; } wrapup() { /** End of interpreter session - close file & list if debug **/ if (debug) { printf("\nDump of symbol table:\n"); print_symbol_table(symbol_table); printf("\nDump of label table:\n"); last = label_list; while (last != NULL) { printf("%-20.20s at line %d\n", last->name, last->loc); last = last->next; } } fclose(fileid); exit(0); } parse(line) char *line; { /** Parse line, calling the appropriate routine depending on the statement type. **/ register int i=0; char *lineptr, buffer[SLEN]; while (whitespace(line[i])) i++; /* skip leading blanks */ lineptr = (char *) line + i+1; /* ...skip first char */ error = NOERROR; /* each line is new! */ switch (toupper(line[i])) { case 'A' : accept(lineptr); break; case 'C' : compute(lineptr); break; case 'E' : endit(lineptr); break; case 'J' : jump(lineptr); break; case 'M' : match(lineptr); break; case 'R' : /** remark **/ break; case 'T' : type(lineptr); break; case 'U' : use(lineptr); break; case '*' : label(line, current_line); break; case ':' : sprintf(buffer, "%c%s", boolean_cont==boolean? 'Y' : 'N', line); type(buffer); break; default : raise_error(UNKNOWN_STATEMENT, NONFATAL, line); } } type(line) char *line; { /** this routine outputs the given line to the screen. **/ if (! (boolean_cont = check_condition(line))) return; if (substitute_vars(line) != ERROR) printf("%s\n", line); } accept(line) char *line; { /** This routine accepts a line of input and sets def_string to the value. If a variable is specified, it uses that. **/ struct symbol_entry *entry, *add_symbol(); char *name; int defined_symbol = 0, value_buffer, i; if (! (boolean_cont = check_condition(line))) return; init_get_token(); if (strlen(line) > 0) /* variable name to use! */ if ((name = get_token(line)) != NULL) { if (! stringvar(name) && ! numvar(name)) return(raise_error(BAD_IDENT, NONFATAL, name)); symbol_table = add_symbol(symbol_table, name); entry = symbol_node; /* set to new node address */ defined_symbol++; } printf("> "); fgets(def_string, SLEN, stdin); /* avoid potential security prob */ remove_return(def_string); /* remove return, if any */ if (numvar(name)) { /* special processing for number */ i = 0; if (def_string[i] == '-') i++; for (; i < strlen(def_string); i++) if (! isdigit(def_string[i])) wrapup(raise_error(BAD_NUMBER, FATAL, NULL)); if (defined_symbol) { /* do we need save this? */ sscanf(def_string,"%d", &value_buffer); entry->numvalue = value_buffer; /* keep as a number and */ strcpy(entry->value, def_string); /* as string too! */ } } else if (defined_symbol) /* do we need to save it? */ strcpy(entry->value, def_string); /* then named = default */ } match(line) char *line; { /** This will try to match any of the list of words or string variables (delimited by spaces) in the list to the value of def_string, the last line input. Variables are expanded to their values. Possible errors: UNDEF_VAR **/ struct symbol_entry *entry, *find_symbol(); char *word; if (! (boolean_cont = check_condition(line))) return; init_get_token(); while ((word = get_token(line)) != NULL) { if (stringvar(word) || numvar(word)) { if ((entry = find_symbol(symbol_table, word)) == NULL) return(raise_error(UNDEF_VAR, NONFATAL, word)); else strcpy(word, entry->value); /* word = value of var */ } if (boolean = in_string(def_string, word)) return; /* done as soon as hit a match... */ } } jump(line) char *line; { /** This will jump to the indicated label, if possible **/ if (! (boolean_cont = check_condition(line))) return; get_to_label(find_label((char *) line + 1), (char *) line + 1); } endit(line) char *line; { /** This marks the end of a routine or of the program **/ if (! (boolean_cont = check_condition(line))) return; if (nesting_level == 0) wrapup(); /* done with entire program! */ else get_to_label(subroutine_stack[--nesting_level], NULL); } use(line) char *line; { /** Call the specified subroutine. Possible errors: TOO_DEEP **/ if (! (boolean_cont = check_condition(line))) return; line = (char *) line + 1; if (nesting_level == MAXDEPTH) wrapup(raise_error(TOO_DEEP, FATAL, NULL)); subroutine_stack[nesting_level++] = current_line; get_to_label(find_label(line), line); } compute(line) char *line; { /** Compute the indicated expression based on whether it's a numerical or string expression. Possible errors: BAD_LHS, MISSING_EQ **/ struct symbol_entry *node; char *ident, buffer[SLEN]; int value = 0, i=0, j=0; if (! (boolean_cont = check_condition(line))) return; init_get_token(); if ((ident = get_token(line)) == NULL) return(raise_error(BAD_LHS, NONFATAL, NULL)); if (!stringvar(ident) && !numvar(ident)) return(raise_error(BAD_LHS, NONFATAL, NULL)); symbol_table = add_symbol(symbol_table, ident); node = symbol_node; /* keep structure to save to.. */ if ((ident = get_token(line)) == NULL) return(raise_error(MISSING_EQ, NONFATAL, NULL)); if (ident[0] != '=') return(raise_error(MISSING_EQ, NONFATAL, NULL)); if (stringvar(node->name)) { /* string expression */ /* Get 'rest' of line for string substitution */ for (i=line_loc; ! end_of_line(line[i]); i++) buffer[j++] = line[i]++; buffer[j] = 0; if (substitute_vars(buffer) == ERROR) return; strcpy(node->value, buffer); } else { /* numerical expression */ value = evaluate(line); if (! error) { node->numvalue = value; sprintf(node->value, "%d", value); } } } label(line, loc) char *line; int loc; { /** add label to label table at line number loc, but only if we haven't read this part of program before! **/ if (loc > furthest_into_file) { add_label(line); furthest_into_file = loc; } } int raise_error(errno, errtype, arg) int errno, errtype; char *arg; { /** Display error 'errno', type FATAL or NONFATAL, arg, if present, is output too. **/ char buffer[SLEN]; if (errtype == FATAL) sprintf(buffer, "** FATAL Error: %s\n", errmsg[errno]); else sprintf(buffer, "** Error: %s on line %d\n", errmsg[errno], current_line); printf(buffer, arg); return(ERROR); } int in_string(buffer, pattern) char *buffer, *pattern; { /** Returns TRUE iff pattern occurs IN IT'S ENTIRETY in buffer. **/ register int i = 0, j = 0; while (buffer[i] != '\0') { while (buffer[i++] == pattern[j++]) if (pattern[j] == '\0') return(TRUE); i = i - j + 1; j = 0; } return(FALSE); } int check_condition(line) char *line; { /** This routine returns non-zero iff the indicated condition (if any) is TRUE. This routine will also remove the part of the line that contains the actual conditional and the colon. **/ char buffer[SLEN]; strcpy(buffer, line); /* save the line.. */ if (! remove_past_colon(line)) return(ERROR); if (buffer[0] == '(') /* relational expression! */ return(relation(buffer)); else if (buffer[0] == 'Y') /* if boolean... */ return(boolean); else if (buffer[0] == 'N') /* if ! boolean */ return(! boolean); else /* always... */ return(TRUE); } int remove_past_colon(line) char *line; { /** Remove up to and including the 'colon' from input string Returns zero iff no colon - Possible error: BAD_INSTRUCT **/ register int i = 0, index; while (line[i] != COLON && line[i] != '\0') i++; if (line[i] == COLON) i++; /* get past colon */ else /* no colon in line! Bad construct! */ return(raise_error(BAD_INSTRUCT, NONFATAL, line)); for (index = i; line[index] != '\0'; index++) line[index-i] = line[index]; line[index-i] = '\0'; return(TRUE); } int break_string(line, lhs, op, rhs) char *line, *lhs, *op, *rhs; { /** Breaks down line into left-hand-side, relational operator and right-hand-side. We need to strip out parens surrounding the expression too without saving them. Possible errors: BAD_REL_OP, BAD_REL_EXP **/ char *word; lhs[0] = rhs[0] = op[0] = '\0'; /* initialize them all */ init_get_token(); if (get_token(line) == NULL) /* no open parenthesis! */ return(raise_error(BAD_REL_EXP, NONFATAL, NULL)); /** get lhs ... **/ while ((word = get_token(line)) != NULL && ! relop(word[0]) && word[0] != COLON && ! paren(word[0])) sprintf(lhs, "%s%s", lhs, word); if (word == NULL) return(raise_error(BAD_REL_EXP, NONFATAL, NULL)); if (paren(word[0])) /* nonexpression relational */ return(NOERROR); /** get op ... **/ strcpy(op, word); /** get rhs ... **/ while ((word = get_token(line)) != NULL && word[0] != COLON) sprintf(rhs, "%s%s", rhs, word); lastch(rhs) = '\0'; /* remove last closing paren */ if (word == NULL) return(raise_error(BAD_REL_EXP, NONFATAL, NULL)); else return(NOERROR); } add_label(name) char *name; { /** Add given label to label list at end, if not found first. Possible errors: DUP_LABEL, OUT_OF_MEM **/ struct a_label *previous; previous = label_list; /* both previous and */ last = label_list; /* last are set to head */ while (last != NULL) { if (strcmp(last->name, name) == 0) return(raise_error(DUP_LABEL, NONFATAL, name)); previous = last; last = last->next; } /** at this point entry == NULL and previous == last valid entry **/ if ((last = (struct a_label *) malloc(sizeof *last)) == NULL) wrapup(raise_error(OUT_OF_MEM, FATAL, NULL)); /* no memory! */ strncpy(last->name, name, NLEN); last->loc = current_line; last->next= NULL; if (previous == NULL) label_list = last; /* first element in list */ else previous->next = last; } int find_label(name) char *name; { /** Returns line location of specified label or 0 if that label isn't currently in the label table. **/ struct a_label *entry; entry = label_list; /* set entry to label list head */ while (entry != NULL) { if (strcmp(entry->name, name) == 0) return(entry->loc); entry = entry->next; } return(0); } int get_to_label(loc, labelname) int loc; char *labelname; { /** Move file pointer to indicated line number. If loc is zero this indicates that we need to scan FORWARD in the file, so reads quickly forward, adding newly encountered labels to the label list as encounted. If loc is non-zero, get to specified line from current line in minimal movement possible. Possible errors: NO_LABEL, UNEXPECT_EOF **/ char buffer[SLEN]; if (loc == 0) { /** forward scan... **/ if (debug) printf("\tget_to_label(%s)\n", labelname); while (fgets(buffer, SLEN, fileid) != NULL) { remove_return(buffer); current_line++; if (debug) printf("%d >> %s\n", current_line, buffer); if (buffer[0] == '*') { if (label(buffer, current_line) == ERROR) wrapup(raise_error(UNEXPECT_EOF, FATAL, labelname)); if (strcmp(labelname, last_label()) == 0) return; /* label found! */ } } wrapup(raise_error(NO_LABEL, FATAL, labelname)); } else { /* get to specified line... */ if (loc < current_line) { /* if before, rewind file */ rewind(fileid); current_line = 0; } while (fgets(buffer, SLEN, fileid) != NULL) { current_line++; if (current_line == loc) return; } wrapup(raise_error(UNEXPECT_EOF, FATAL, NULL)); } } struct symbol_entry *add_symbol(node, symbol) struct symbol_entry *node; char *symbol; { /** This routine adds the specified symbol to the symbol table. The first character determines the type of the variable: '$' for strings and '#' for integers. Possible errors: OUT_OF_MEM **/ int cond; if (node == NULL) { if ((node = (struct symbol_entry *) malloc(sizeof *node)) == NULL) wrapup(raise_error(OUT_OF_MEM, FATAL, NULL)); strcpy(node->name, symbol); node->value[0] = '\0'; node->numvalue = 0; node->left = NULL; node->right = NULL; symbol_node = node; /* store address globally too, if needed! */ } else if ((cond = strcmp(symbol, node->name)) == 0) symbol_node = node; /* store address globally too, if needed! */ else if (cond < 0) node->left = add_symbol(node->left, symbol); else node->right= add_symbol(node->right, symbol); return(node); } print_symbol_table(node) struct symbol_entry *node; { /** Recursively lists all entries in the symbol table. Debug only **/ if (node != NULL) { print_symbol_table(node->left); printf("\t%-20.20s '%s'\n", node->name, node->value); print_symbol_table(node->right); } } struct symbol_entry *find_symbol(node, symbol) struct symbol_entry *node; char *symbol; { /** Returns either NULL if the symbol is not found or the address of the structure containing the specified symbol. This is a stan- dard recursive binary tree search... **/ int cond; if (node == NULL) return(NULL); else if ((cond = strcmp(symbol, node->name)) == 0) return(node); /* store if needed */ else if (cond < 0) return(find_symbol(node->left, symbol)); else return(find_symbol(node->right, symbol)); } int substitute_vars(line) char *line; { /** This routine substitutes the value for each variable it finds in the given line. Possible error: UNDEF_VAR **/ struct symbol_entry *entry, *find_symbol(); register int i = 0, j = 0, word_index; char word[NLEN], buffer[SLEN]; do { /** while not in variable copy to buffer... **/ while (line[i] != STRINGDELIM && line[i] != NUMDELIM && ! end_of_line(line[i])) buffer[j++] = line[i++]; /** get variable if it exists... **/ word_index = 0; if (! end_of_line(line[i])) word[word_index++] = line[i++]; /* copy in the delimiter */ while (! end_of_line(line[i]) && valid_char(line[i])) word[word_index++] = line[i++]; /* Have a variable? if so try to find and substitute */ if (word_index > 0) { word[word_index] = '\0'; if ((entry = find_symbol(symbol_table, word)) == NULL) return(raise_error(UNDEF_VAR, NONFATAL, word)); for (word_index=0; word_index < strlen(entry->value); word_index++) buffer[j++] = (entry->value)[word_index]; } } while (! end_of_line(line[i])); buffer[j] = '\0'; strcpy(line, buffer); /* copy it back in... */ return(NOERROR); } int relation(exp) char *exp; { /** Evaluate relational expression between a set of parenthesis. Returns TRUE or FALSE according to the results of the evaluation. If an error occurs this routine will always return FALSE. Possible errors: BAD_REL_OP **/ char word[NLEN], lhs_string[SLEN], rhs_string[SLEN]; int retval, lhs, rhs; /* left hand & right hand side */ if (break_string(exp, lhs_string, word, rhs_string) == ERROR) return(FALSE); /* default for error */ init_get_token(); /* new string: */ lhs = evaluate(lhs_string); /* left hand side */ if (error) return(FALSE); /* erroneous always fail */ if (word[0] == '\0' && rhs_string[0] == '\0') return(lhs != 0); /* accept no relation exp. */ init_get_token(); /* new string: */ rhs = evaluate(rhs_string); /* right hand side */ if (error) return(FALSE); /* erroneous always fail */ switch (word[0]) { /* compute return value */ case '=' : retval = (lhs == rhs); break; case '<' : switch (word[1]) { case '\0' : retval = (lhs < rhs); break; case '>' : retval = (lhs != rhs); break; case '=' : retval = (lhs <= rhs); break; default : return(raise_error(BAD_REL_OP, NONFATAL, word)); } break; case '>' : switch (word[1]) { case '\0' : retval = (lhs > rhs); break; case '=' : retval = (lhs >= rhs); break; default : return(raise_error(BAD_REL_OP, NONFATAL, word)); } break; default : return(raise_error(BAD_REL_OP, NONFATAL, word)); } return(retval); } int evaluate(exp) char *exp; { /** Evaluate expression and check that we've read all the tokens Returns value or ERROR. Possible errors: BAD_PARENS **/ int value; value = expression(exp); if (get_token(exp) != NULL) return((! error++) ? raise_error(BAD_PARENS, NONFATAL, NULL) : 0); else return(value); } int expression(exp) char *exp; { /** Recursively evaluate the expression given. **/ char *word; int val = 0; val = term(exp); if ((word = get_token(exp)) == NULL) return(val); if (! addops(word[0])) { unget_token(); return(val); } while (addops(word[0])) { if (word[0] == PLUS) val += term(exp); else /* must be MINUS */ val -= term(exp); if ((word = get_token(exp)) == NULL) return(val); if (! addops(word[0])) { unget_token(); return(val); } } return(val); } term(exp) char *exp; { /** Get a term (ie a multiply or divide) and return the results of computing it. Possible errors: DIVIDE_BY_ZERO **/ register int val = 0, value; char *word; val = factor(exp); if ((word = get_token(exp,3)) == NULL) return(val); if (! mulops(word[0])) { unget_token(); return(val); } while (mulops(word[0])) { if (word[0] == TIMES) val *= factor(exp); else if ((value = factor(exp)) == 0) { if (! error++) wrapup(raise_error(DIVIDE_BY_ZERO, FATAL, NULL)); else return(0); } else val /= value; if ((word = get_token(exp)) == NULL) return(val); if (! mulops(word[0])) { unget_token(); return(val); } } return(val); } factor(string) char *string; { /** Break down a string - either another expression in parentheses, a specific numerical value or a variable name Possible errors: BAD_EXP, BAD_PARENS, STRING_IN_EXP, UNDEF_VAR **/ struct symbol_entry *entry, *find_symbol(); int val = 0; char *word; if ((word = get_token(string)) == NULL) return((! error++)? raise_error(BAD_EXP, NONFATAL, NULL) : 0); if (word[0] == LEFT_PAREN) { val = expression(string); if ((word = get_token(string)) == NULL) return((! error++)? raise_error(BAD_PARENS, NONFATAL, NULL):0); else if (word[0] != RIGHT_PAREN) return((! error++)? raise_error(BAD_EXP, NONFATAL, NULL):0); } else if (stringvar(word)) /* what's THIS doing here?? */ return((! error++)? raise_error(STRING_IN_EXP, NONFATAL, NULL):0); else if (numvar(word)) { if ((entry = find_symbol(symbol_table, word)) == NULL) return((! error++)? raise_error(UNDEF_VAR, NONFATAL, word):0); val = entry->numvalue; } else if (word[0] == '-') { /* minus number */ if ((word = get_token(string)) == NULL) return((! error++)? raise_error(BAD_EXP, NONFATAL, NULL) : 0); val = -atoi(word); } else val = atoi(word); return(val); } char *get_token(line) char *line; { /** Return the next token in the line without surrounding white spaces. Return zero if at end-of-line **/ static char word[SLEN]; register int i = 0; while (whitespace(line[line_loc])) line_loc++; last_line_loc = line_loc; if (end_of_line(line[line_loc])) return(NULL); if (mathops(line[line_loc]) || paren(line[line_loc])) word[i++] = line[line_loc++]; else if (relop(line[line_loc])) { word[i++] = line[line_loc++]; if (relop(line[line_loc]) && line[line_loc] != line[line_loc-1]) word[i++] = line[line_loc++]; } else { while (! special(line[line_loc]) && ! whitespace(line[line_loc]) && ! end_of_line(line[line_loc])) if (i == NLEN-1) { word[i] = '\0'; wrapup(raise_error(NAME_TOO_LONG, FATAL, word)); } else word[i++] = line[line_loc++]; } word[i] = '\0'; return( (char *) word); }