char_t *ejEvalFile(int eid, char_t *path, char_t **emsg) { gstat_t sbuf; ej_t *ep; char_t *script, *rs; char *fileBuf; int fd; a_assert(path && *path); if (emsg) { *emsg = NULL; } if ((ep = ejPtr(eid)) == NULL) { return NULL; } if ((fd = gopen(path, O_RDONLY | O_BINARY, 0666)) < 0) { ejError(ep, T("Bad handle %d"), eid); return NULL; } if (gstat(path, &sbuf) < 0) { gclose(fd); ejError(ep, T("Cant stat %s"), path); return NULL; } if ((fileBuf = balloc(B_L, sbuf.st_size + 1)) == NULL) { gclose(fd); ejError(ep, T("Cant malloc %d"), sbuf.st_size); return NULL; } if (gread(fd, fileBuf, sbuf.st_size) != (int)sbuf.st_size) { gclose(fd); bfree(B_L, fileBuf); ejError(ep, T("Error reading %s"), path); return NULL; } fileBuf[sbuf.st_size] = '\0'; gclose(fd); if ((script = ballocAscToUni(fileBuf, sbuf.st_size)) == NULL) { bfree(B_L, fileBuf); ejError(ep, T("Cant malloc %d"), sbuf.st_size + 1); return NULL; } bfree(B_L, fileBuf); rs = ejEvalBlock(eid, script, emsg); bfree(B_L, script); return rs; }
static int evalCond(ej_t *ep, char_t *lhs, int rel, char_t *rhs) { char_t buf[16]; int l, r, lval; a_assert(lhs); a_assert(rhs); a_assert(rel > 0); lval = 0; if (gisdigit((int)*lhs) && gisdigit((int)*rhs)) { l = gatoi(lhs); r = gatoi(rhs); switch (rel) { case COND_AND: lval = l && r; break; case COND_OR: lval = l || r; break; default: ejError(ep, T("Bad operator %d"), rel); return -1; } } else { if (!gisdigit((int)*lhs)) { ejError(ep, T("Conditional must be numeric"), lhs); } else { ejError(ep, T("Conditional must be numeric"), rhs); } } stritoa(lval, buf, sizeof(buf)); setString(B_L, &ep->result, buf); return 0; }
static int tokenAddChar(ej_t *ep, int c) { ejinput_t* ip; a_assert(ep); ip = ep->input; a_assert(ip); if (ringqPutc(&ip->tokbuf, (char_t) c) < 0) { ejError(ep, T("Token too big")); return -1; } * ((char_t*) ip->tokbuf.endp) = '\0'; ep->token = (char_t*) ip->tokbuf.servp; return 0; }
static int parseStmt(ej_t *ep, int state, int flags) { ejfunc_t func; ejfunc_t *saveFunc; ejinput_t condScript, endScript, bodyScript, incrScript; char_t *value, *identifier; int done, expectSemi, thenFlags, elseFlags, tid, cond, forFlags; int ejVarType; a_assert(ep); /* * Set these to NULL, else we try to free them if an error occurs. */ endScript.putBackToken = NULL; bodyScript.putBackToken = NULL; incrScript.putBackToken = NULL; condScript.putBackToken = NULL; expectSemi = 0; saveFunc = NULL; for (done = 0; !done; ) { tid = ejLexGetToken(ep, state); switch (tid) { default: ejLexPutbackToken(ep, TOK_EXPR, ep->token); done++; break; case TOK_ERR: state = STATE_ERR; done++; break; case TOK_EOF: state = STATE_EOF; done++; break; case TOK_NEWLINE: break; case TOK_SEMI: /* * This case is when we discover no statement and just a lone ';' */ if (state != STATE_STMT) { ejLexPutbackToken(ep, tid, ep->token); } done++; break; case TOK_ID: /* * This could either be a reference to a variable or an assignment */ identifier = NULL; setString(B_L, &identifier, ep->token); /* * Peek ahead to see if this is an assignment */ tid = ejLexGetToken(ep, state); if (tid == TOK_ASSIGNMENT) { if (parse(ep, STATE_RELEXP, flags) != STATE_RELEXP_DONE) { clearString(&identifier); goto error; } if (flags & FLAGS_EXE) { if ( state == STATE_DEC ) { ejSetLocalVar(ep->eid, identifier, ep->result); } else { ejVarType = ejGetVar(ep->eid, identifier, &value); if (ejVarType > 0) { ejSetLocalVar(ep->eid, identifier, ep->result); } else { ejSetGlobalVar(ep->eid, identifier, ep->result); } } } } else if (tid == TOK_INC_DEC ) { value = NULL; if (flags & FLAGS_EXE) { ejVarType = ejGetVar(ep->eid, identifier, &value); if (ejVarType < 0) { ejError(ep, T("Undefined variable %s\n"), identifier); goto error; } setString(B_L, &ep->result, value); if (evalExpr(ep, value, (int) *ep->token, T("1")) < 0) { state = STATE_ERR; break; } if (ejVarType > 0) { ejSetLocalVar(ep->eid, identifier, ep->result); } else { ejSetGlobalVar(ep->eid, identifier, ep->result); } } } else { /* * If we are processing a declaration, allow undefined vars */ value = NULL; if (state == STATE_DEC) { if (ejGetVar(ep->eid, identifier, &value) > 0) { ejError(ep, T("Variable already declared"), identifier); clearString(&identifier); goto error; } ejSetLocalVar(ep->eid, identifier, NULL); } else { if ( flags & FLAGS_EXE ) { if (ejGetVar(ep->eid, identifier, &value) < 0) { ejError(ep, T("Undefined variable %s\n"), identifier); clearString(&identifier); goto error; } } } setString(B_L, &ep->result, value); ejLexPutbackToken(ep, tid, ep->token); } clearString(&identifier); if (state == STATE_STMT) { expectSemi++; } done++; break; case TOK_LITERAL: /* * Set the result to the literal (number or string constant) */ setString(B_L, &ep->result, ep->token); if (state == STATE_STMT) { expectSemi++; } done++; break; case TOK_FUNCTION: /* * We must save any current ep->func value for the current stack frame */ if (ep->func) { saveFunc = ep->func; } memset(&func, 0, sizeof(ejfunc_t)); setString(B_L, &func.fname, ep->token); ep->func = &func; setString(B_L, &ep->result, T("")); if (ejLexGetToken(ep, state) != TOK_LPAREN) { freeFunc(&func); goto error; } if (parse(ep, STATE_ARG_LIST, flags) != STATE_ARG_LIST_DONE) { freeFunc(&func); ep->func = saveFunc; goto error; } /* * Evaluate the function if required */ if (flags & FLAGS_EXE && evalFunction(ep) < 0) { freeFunc(&func); ep->func = saveFunc; goto error; } freeFunc(&func); ep->func = saveFunc; if (ejLexGetToken(ep, state) != TOK_RPAREN) { goto error; } if (state == STATE_STMT) { expectSemi++; } done++; break; case TOK_IF: if (state != STATE_STMT) { goto error; } if (ejLexGetToken(ep, state) != TOK_LPAREN) { goto error; } /* * Evaluate the entire condition list "(condition)" */ if (parse(ep, STATE_COND, flags) != STATE_COND_DONE) { goto error; } if (ejLexGetToken(ep, state) != TOK_RPAREN) { goto error; } /* * This is the "then" case. We need to always parse both cases and * execute only the relevant case. */ if (*ep->result == '1') { thenFlags = flags; elseFlags = flags & ~FLAGS_EXE; } else { thenFlags = flags & ~FLAGS_EXE; elseFlags = flags; } /* * Process the "then" case. Allow for RETURN statement */ switch (parse(ep, STATE_STMT, thenFlags)) { case STATE_RET: return STATE_RET; case STATE_STMT_DONE: break; default: goto error; } /* * check to see if there is an "else" case */ ejRemoveNewlines(ep, state); tid = ejLexGetToken(ep, state); if (tid != TOK_ELSE) { ejLexPutbackToken(ep, tid, ep->token); done++; break; } /* * Process the "else" case. Allow for return. */ switch (parse(ep, STATE_STMT, elseFlags)) { case STATE_RET: return STATE_RET; case STATE_STMT_DONE: break; default: goto error; } done++; break; case TOK_FOR: /* * Format for the expression is: * * for (initial; condition; incr) { * body; * } */ if (state != STATE_STMT) { goto error; } if (ejLexGetToken(ep, state) != TOK_LPAREN) { goto error; } /* * Evaluate the for loop initialization statement */ if (parse(ep, STATE_EXPR, flags) != STATE_EXPR_DONE) { goto error; } if (ejLexGetToken(ep, state) != TOK_SEMI) { goto error; } /* * The first time through, we save the current input context just * to each step: prior to the conditional, the loop increment and the * loop body. */ ejLexSaveInputState(ep, &condScript); if (parse(ep, STATE_COND, flags) != STATE_COND_DONE) { goto error; } cond = (*ep->result != '0'); if (ejLexGetToken(ep, state) != TOK_SEMI) { goto error; } /* * Don't execute the loop increment statement or the body first time */ forFlags = flags & ~FLAGS_EXE; ejLexSaveInputState(ep, &incrScript); if (parse(ep, STATE_EXPR, forFlags) != STATE_EXPR_DONE) { goto error; } if (ejLexGetToken(ep, state) != TOK_RPAREN) { goto error; } /* * Parse the body and remember the end of the body script */ ejLexSaveInputState(ep, &bodyScript); if (parse(ep, STATE_STMT, forFlags) != STATE_STMT_DONE) { goto error; } ejLexSaveInputState(ep, &endScript); /* * Now actually do the for loop. Note loop has been rotated */ while (cond && (flags & FLAGS_EXE) ) { /* * Evaluate the body */ ejLexRestoreInputState(ep, &bodyScript); switch (parse(ep, STATE_STMT, flags)) { case STATE_RET: return STATE_RET; case STATE_STMT_DONE: break; default: goto error; } /* * Evaluate the increment script */ ejLexRestoreInputState(ep, &incrScript); if (parse(ep, STATE_EXPR, flags) != STATE_EXPR_DONE) { goto error; } /* * Evaluate the condition */ ejLexRestoreInputState(ep, &condScript); if (parse(ep, STATE_COND, flags) != STATE_COND_DONE) { goto error; } cond = (*ep->result != '0'); } ejLexRestoreInputState(ep, &endScript); done++; break; case TOK_VAR: if (parse(ep, STATE_DEC_LIST, flags) != STATE_DEC_LIST_DONE) { goto error; } done++; break; case TOK_COMMA: ejLexPutbackToken(ep, TOK_EXPR, ep->token); done++; break; case TOK_LPAREN: if (state == STATE_EXPR) { if (parse(ep, STATE_RELEXP, flags) != STATE_RELEXP_DONE) { goto error; } if (ejLexGetToken(ep, state) != TOK_RPAREN) { goto error; } return STATE_EXPR_DONE; } done++; break; case TOK_RPAREN: ejLexPutbackToken(ep, tid, ep->token); return STATE_EXPR_DONE; case TOK_LBRACE: /* * This handles any code in braces except "if () {} else {}" */ if (state != STATE_STMT) { goto error; } /* * Parse will return STATE_STMT_BLOCK_DONE when the RBRACE is seen */ do { state = parse(ep, STATE_STMT, flags); } while (state == STATE_STMT_DONE); /* * Allow return statement. */ if (state == STATE_RET) { return state; } if (ejLexGetToken(ep, state) != TOK_RBRACE) { goto error; } return STATE_STMT_DONE; case TOK_RBRACE: if (state == STATE_STMT) { ejLexPutbackToken(ep, tid, ep->token); return STATE_STMT_BLOCK_DONE; } goto error; case TOK_RETURN: if (parse(ep, STATE_RELEXP, flags) != STATE_RELEXP_DONE) { goto error; } if (flags & FLAGS_EXE) { while ( ejLexGetToken(ep, state) != TOK_EOF ); done++; return STATE_RET; } break; } } if (expectSemi) { tid = ejLexGetToken(ep, state); if (tid != TOK_SEMI && tid != TOK_NEWLINE) { goto error; } /* * Skip newline after semi-colon */ ejRemoveNewlines(ep, state); } /* * Free resources and return the correct status */ doneParse: if (tid == TOK_FOR) { ejLexFreeInputState(ep, &condScript); ejLexFreeInputState(ep, &incrScript); ejLexFreeInputState(ep, &endScript); ejLexFreeInputState(ep, &bodyScript); } if (state == STATE_STMT) { return STATE_STMT_DONE; } else if (state == STATE_DEC) { return STATE_DEC_DONE; } else if (state == STATE_EXPR) { return STATE_EXPR_DONE; } else if (state == STATE_EOF) { return state; } else { return STATE_ERR; } /* * Common error exit */ error: state = STATE_ERR; goto doneParse; }
static int parse(ej_t *ep, int state, int flags) { a_assert(ep); switch (state) { /* * Any statement, function arguments or conditional expressions */ case STATE_STMT: if ((state = parseStmt(ep, state, flags)) != STATE_STMT_DONE && state != STATE_EOF && state != STATE_STMT_BLOCK_DONE && state != STATE_RET) { state = STATE_ERR; } break; case STATE_DEC: if ((state = parseStmt(ep, state, flags)) != STATE_DEC_DONE && state != STATE_EOF) { state = STATE_ERR; } break; case STATE_EXPR: if ((state = parseStmt(ep, state, flags)) != STATE_EXPR_DONE && state != STATE_EOF) { state = STATE_ERR; } break; /* * Variable declaration list */ case STATE_DEC_LIST: state = parseDeclaration(ep, state, flags); break; /* * Function argument string */ case STATE_ARG_LIST: state = parseArgs(ep, state, flags); break; /* * Logical condition list (relational operations separated by &&, ||) */ case STATE_COND: state = parseCond(ep, state, flags); break; /* * Expression list */ case STATE_RELEXP: state = parseExpr(ep, state, flags); break; } if (state == STATE_ERR && ep->error == NULL) { ejError(ep, T("Syntax error")); } return state; }
char_t *ejEval(int eid, char_t *script, char_t **emsg) { ej_t *ep; ejinput_t *oldBlock; int state; void *endlessLoopTest; int loopCounter; a_assert(script); if (emsg) { *emsg = NULL; } if ((ep = ejPtr(eid)) == NULL) { return NULL; } setString(B_L, &ep->result, T("")); /* * Allocate a new evaluation block, and save the old one */ oldBlock = ep->input; ejLexOpenScript(ep, script); /* * Do the actual parsing and evaluation */ loopCounter = 0; endlessLoopTest = NULL; do { state = parse(ep, STATE_BEGIN, FLAGS_EXE); if (state == STATE_RET) { state = STATE_EOF; } /* * prevent parser from going into infinite loop. If parsing the same * line 10 times then fail and report Syntax error. Most normal error * are caught in the parser itself. */ if (endlessLoopTest == ep->input->script.servp) { if (loopCounter++ > 10) { state = STATE_ERR; ejError(ep, T("Syntax error")); } } else { endlessLoopTest = ep->input->script.servp; loopCounter = 0; } } while (state != STATE_EOF && state != STATE_ERR); ejLexCloseScript(ep); /* * Return any error string to the user */ if (state == STATE_ERR && emsg) { *emsg = bstrdup(B_L, ep->error); } /* * Restore the old evaluation block */ ep->input = oldBlock; if (state == STATE_EOF) { return ep->result; } if (state == STATE_ERR) { return NULL; } return ep->result; }
static int evalExpr(ej_t *ep, char_t *lhs, int rel, char_t *rhs) { char_t *cp, buf[16]; int numeric, l, r, lval; a_assert(lhs); a_assert(rhs); a_assert(rel > 0); /* * All of the characters in the lhs and rhs must be numeric */ numeric = 1; for (cp = lhs; *cp; cp++) { if (!gisdigit((int)*cp)) { numeric = 0; break; } } if (numeric) { for (cp = rhs; *cp; cp++) { if (!gisdigit((int)*cp)) { numeric = 0; break; } } } if (numeric) { l = gatoi(lhs); r = gatoi(rhs); switch (rel) { case EXPR_PLUS: lval = l + r; break; case EXPR_INC: lval = l + 1; break; case EXPR_MINUS: lval = l - r; break; case EXPR_DEC: lval = l - 1; break; case EXPR_MUL: lval = l * r; break; case EXPR_DIV: if (r != 0) { lval = l / r; } else { lval = 0; } break; case EXPR_MOD: if (r != 0) { lval = l % r; } else { lval = 0; } break; case EXPR_LSHIFT: lval = l << r; break; case EXPR_RSHIFT: lval = l >> r; break; case EXPR_EQ: lval = l == r; break; case EXPR_NOTEQ: lval = l != r; break; case EXPR_LESS: lval = (l < r) ? 1 : 0; break; case EXPR_LESSEQ: lval = (l <= r) ? 1 : 0; break; case EXPR_GREATER: lval = (l > r) ? 1 : 0; break; case EXPR_GREATEREQ: lval = (l >= r) ? 1 : 0; break; case EXPR_BOOL_COMP: lval = (r == 0) ? 1 : 0; break; default: ejError(ep, T("Bad operator %d"), rel); return -1; } } else { switch (rel) {
static int getLexicalToken(ej_t* ep, int state) { ringq_t *inq, *tokq; ejinput_t* ip; int done, tid, c, quote, style; a_assert(ep); ip = ep->input; a_assert(ip); inq = &ip->script; tokq = &ip->tokbuf; ep->tid = -1; tid = -1; ep->token = T(""); ringqFlush(tokq); if (ip->putBackTokenId > 0) { ringqPutStr(tokq, ip->putBackToken); tid = ip->putBackTokenId; ip->putBackTokenId = 0; ep->token = (char_t*) tokq->servp; return tid; } if ((c = inputGetc(ep)) < 0) { return TOK_EOF; } for (done = 0; !done; ) { switch (c) { case -1: return TOK_EOF; case ' ': case '\t': case '\r': do { if ((c = inputGetc(ep)) < 0) break; } while (c == ' ' || c == '\t' || c == '\r'); break; case '\n': return TOK_NEWLINE; case '(': tokenAddChar(ep, c); return TOK_LPAREN; case ')': tokenAddChar(ep, c); return TOK_RPAREN; case '{': tokenAddChar(ep, c); return TOK_LBRACE; case '}': tokenAddChar(ep, c); return TOK_RBRACE; case '+': if ((c = inputGetc(ep)) < 0) { ejError(ep, T("Syntax Error")); return TOK_ERR; } if (c != '+' ) { inputPutback(ep, c); tokenAddChar(ep, EXPR_PLUS); return TOK_EXPR; } tokenAddChar(ep, EXPR_INC); return TOK_INC_DEC; case '-': if ((c = inputGetc(ep)) < 0) { ejError(ep, T("Syntax Error")); return TOK_ERR; } if (c != '-' ) { inputPutback(ep, c); tokenAddChar(ep, EXPR_MINUS); return TOK_EXPR; } tokenAddChar(ep, EXPR_DEC); return TOK_INC_DEC; case '*': tokenAddChar(ep, EXPR_MUL); return TOK_EXPR; case '%': tokenAddChar(ep, EXPR_MOD); return TOK_EXPR; case '/': /* * Handle the division operator and comments */ if ((c = inputGetc(ep)) < 0) { ejError(ep, T("Syntax Error")); return TOK_ERR; } if (c != '*' && c != '/') { inputPutback(ep, c); tokenAddChar(ep, EXPR_DIV); return TOK_EXPR; } style = c; /* * Eat comments. Both C and C++ comment styles are supported. */ while (1) { if ((c = inputGetc(ep)) < 0) { ejError(ep, T("Syntax Error")); return TOK_ERR; } if (c == '\n' && style == '/') { break; } else if (c == '*') { c = inputGetc(ep); if (style == '/') { if (c == '\n') { break; } } else { if (c == '/') { break; } } } } /* * Continue looking for a token, so get the next character */ if ((c = inputGetc(ep)) < 0) { return TOK_EOF; } break; case '<': /* < and <= */ if ((c = inputGetc(ep)) < 0) { ejError(ep, T("Syntax Error")); return TOK_ERR; } if (c == '<') { tokenAddChar(ep, EXPR_LSHIFT); return TOK_EXPR; } else if (c == '=') { tokenAddChar(ep, EXPR_LESSEQ); return TOK_EXPR; } tokenAddChar(ep, EXPR_LESS); inputPutback(ep, c); return TOK_EXPR; case '>': /* > and >= */ if ((c = inputGetc(ep)) < 0) { ejError(ep, T("Syntax Error")); return TOK_ERR; } if (c == '>') { tokenAddChar(ep, EXPR_RSHIFT); return TOK_EXPR; } else if (c == '=') { tokenAddChar(ep, EXPR_GREATEREQ); return TOK_EXPR; } tokenAddChar(ep, EXPR_GREATER); inputPutback(ep, c); return TOK_EXPR; case '=': /* "==" */ if ((c = inputGetc(ep)) < 0) { ejError(ep, T("Syntax Error")); return TOK_ERR; } if (c == '=') { tokenAddChar(ep, EXPR_EQ); return TOK_EXPR; } inputPutback(ep, c); return TOK_ASSIGNMENT; case '!': /* "!=" or "!"*/ if ((c = inputGetc(ep)) < 0) { ejError(ep, T("Syntax Error")); return TOK_ERR; } if (c == '=') { tokenAddChar(ep, EXPR_NOTEQ); return TOK_EXPR; } inputPutback(ep, c); tokenAddChar(ep, EXPR_BOOL_COMP); return TOK_EXPR; case ';': tokenAddChar(ep, c); return TOK_SEMI; case ',': tokenAddChar(ep, c); return TOK_COMMA; case '|': /* "||" */ if ((c = inputGetc(ep)) < 0 || c != '|') { ejError(ep, T("Syntax Error")); return TOK_ERR; } tokenAddChar(ep, COND_OR); return TOK_LOGICAL; case '&': /* "&&" */ if ((c = inputGetc(ep)) < 0 || c != '&') { ejError(ep, T("Syntax Error")); return TOK_ERR; } tokenAddChar(ep, COND_AND); return TOK_LOGICAL; case '\"': /* String quote */ case '\'': quote = c; if ((c = inputGetc(ep)) < 0) { ejError(ep, T("Syntax Error")); return TOK_ERR; } while (c != quote) { /* * check for escape sequence characters */ if (c == '\\') { c = inputGetc(ep); if (gisdigit(c)) { /* * octal support, \101 maps to 65 = 'A'. put first char * back so converter will work properly. */ inputPutback(ep, c); c = charConvert(ep, OCTAL, 3); } else { switch (c) { case 'n': c = '\n'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'x': /* * hex support, \x41 maps to 65 = 'A' */ c = charConvert(ep, HEX, 2); break; case 'u': /* * unicode support, \x0401 maps to 65 = 'A' */ c = charConvert(ep, HEX, 2); c = c*16 + charConvert(ep, HEX, 2); break; case '\'': case '\"': case '\\': break; default: ejError(ep, T("Invalid Escape Sequence")); return TOK_ERR; } } if (tokenAddChar(ep, c) < 0) { return TOK_ERR; } } else { if (tokenAddChar(ep, c) < 0) { return TOK_ERR; } } if ((c = inputGetc(ep)) < 0) { ejError(ep, T("Unmatched Quote")); return TOK_ERR; } } return TOK_LITERAL; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': do { if (tokenAddChar(ep, c) < 0) { return TOK_ERR; } if ((c = inputGetc(ep)) < 0) break; } while (gisdigit(c)); inputPutback(ep, c); return TOK_LITERAL; default: /* * Identifiers or a function names */ while (1) { if (c == '\\') { /* * just ignore any \ characters. */ } else if (tokenAddChar(ep, c) < 0) { break; } if ((c = inputGetc(ep)) < 0) { break; } if (!gisalnum(c) && c != '$' && c != '_' && c != '\\') { break; } } if (! gisalpha(*tokq->servp) && *tokq->servp != '$' && *tokq->servp != '_') { ejError(ep, T("Invalid identifier %s"), tokq->servp); return TOK_ERR; } /* * Check for reserved words (only "if", "else", "var", "for" * and "return" at the moment) */ if (state == STATE_STMT) { if (gstrcmp(ep->token, T("if")) == 0) { return TOK_IF; } else if (gstrcmp(ep->token, T("else")) == 0) { return TOK_ELSE; } else if (gstrcmp(ep->token, T("var")) == 0) { return TOK_VAR; } else if (gstrcmp(ep->token, T("for")) == 0) { return TOK_FOR; } else if (gstrcmp(ep->token, T("return")) == 0) { if ((c == ';') || (c == '(')) { inputPutback(ep, c); } return TOK_RETURN; } } /* * Skip white space after token to find out whether this is * a function or not. */ while (c == ' ' || c == '\t' || c == '\r' || c == '\n') { if ((c = inputGetc(ep)) < 0) break; } tid = (c == '(') ? TOK_FUNCTION : TOK_ID; done++; } } /* * Putback the last extra character for next time */ inputPutback(ep, c); return tid; }