/* $Id: parse.y,v 1.27 2003/12/19 23:23:31 slobasso Exp $ */ %{ #ifdef HAVE_CONFIG_H #include "../config.h" #endif #include "parse.h" #include "textBuf.h" #include "nedit.h" #include "rbTree.h" #include "interpret.h" #include #include #include #include #include #include #ifdef VMS #include "../util/VMSparam.h" #else #ifndef __MVS__ #include #endif #endif /*VMS*/ #ifdef HAVE_DEBUG_H #include "../debug.h" #endif /* Macros to add error processing to AddOp and AddSym calls */ #define ADD_OP(op) if (!AddOp(op, &ErrMsg)) return 1 #define ADD_SYM(sym) if (!AddSym(sym, &ErrMsg)) return 1 #define ADD_IMMED(val) if (!AddImmediate(val, &ErrMsg)) return 1 #define ADD_BR_OFF(to) if (!AddBranchOffset(to, &ErrMsg)) return 1 #define SET_BR_OFF(from, to) *((int *)(from)) = ((Inst *)(to)) - ((Inst *)(from)) /* Max. length for a string constant (... there shouldn't be a maximum) */ #define MAX_STRING_CONST_LEN 5000 static const char CVSID[] = "$Id: parse.y,v 1.27 2003/12/19 23:23:31 slobasso Exp $"; static int yyerror(char *s); static int yylex(void); int yyparse(void); static int follow(char expect, int yes, int no); static int follow2(char expect1, int yes1, char expect2, int yes2, int no); static int follow_non_whitespace(char expect, int yes, int no); static Symbol *matchesActionRoutine(char **inPtr); static char *ErrMsg; static char *InPtr; extern Inst *LoopStack[]; /* addresses of break, cont stmts */ extern Inst **LoopStackPtr; /* to fill at the end of a loop */ %} %union { Symbol *sym; Inst *inst; int nArgs; } %token NUMBER STRING SYMBOL %token DELETE ARG_LOOKUP %token IF WHILE ELSE FOR BREAK CONTINUE RETURN %type arglist %type cond comastmts for while else and or arrayexpr %type evalsym %nonassoc IF_NO_ELSE %nonassoc ELSE %nonassoc SYMBOL ARG_LOOKUP %right '=' ADDEQ SUBEQ MULEQ DIVEQ MODEQ ANDEQ OREQ %left CONCAT %left OR %left AND %left '|' %left '&' %left GT GE LT LE EQ NE IN %left '+' '-' %left '*' '/' '%' %nonassoc UNARY_MINUS NOT %nonassoc DELETE %nonassoc INCR DECR %right POW %nonassoc '[' %nonassoc '(' %% /* Rules */ program: blank stmts { ADD_OP(OP_RETURN_NO_VAL); return 0; } | blank '{' blank stmts '}' { ADD_OP(OP_RETURN_NO_VAL); return 0; } | blank '{' blank '}' { ADD_OP(OP_RETURN_NO_VAL); return 0; } | error { return 1; } ; block: '{' blank stmts '}' blank | '{' blank '}' blank | stmt ; stmts: stmt | stmts stmt ; stmt: simpstmt '\n' blank | IF '(' cond ')' blank block %prec IF_NO_ELSE { SET_BR_OFF($3, GetPC()); } | IF '(' cond ')' blank block else blank block %prec ELSE { SET_BR_OFF($3, ($7+1)); SET_BR_OFF($7, GetPC()); } | while '(' cond ')' blank block { ADD_OP(OP_BRANCH); ADD_BR_OFF($1); SET_BR_OFF($3, GetPC()); FillLoopAddrs(GetPC(), $1); } | for '(' comastmts ';' cond ';' comastmts ')' blank block { FillLoopAddrs(GetPC()+2+($7-($5+1)), GetPC()); SwapCode($5+1, $7, GetPC()); ADD_OP(OP_BRANCH); ADD_BR_OFF($3); SET_BR_OFF($5, GetPC()); } | for '(' SYMBOL IN arrayexpr ')' { Symbol *iterSym = InstallIteratorSymbol(); ADD_OP(OP_BEGIN_ARRAY_ITER); ADD_SYM(iterSym); ADD_OP(OP_ARRAY_ITER); ADD_SYM($3); ADD_SYM(iterSym); ADD_BR_OFF(0); } blank block { ADD_OP(OP_BRANCH); ADD_BR_OFF($5+2); SET_BR_OFF($5+5, GetPC()); FillLoopAddrs(GetPC(), $5+2); } | BREAK '\n' blank { ADD_OP(OP_BRANCH); ADD_BR_OFF(0); if (AddBreakAddr(GetPC()-1)) { yyerror("break outside loop"); YYERROR; } } | CONTINUE '\n' blank { ADD_OP(OP_BRANCH); ADD_BR_OFF(0); if (AddContinueAddr(GetPC()-1)) { yyerror("continue outside loop"); YYERROR; } } | RETURN expr '\n' blank { ADD_OP(OP_RETURN); } | RETURN '\n' blank { ADD_OP(OP_RETURN_NO_VAL); } ; simpstmt: SYMBOL '=' expr { ADD_OP(OP_ASSIGN); ADD_SYM($1); } | evalsym ADDEQ expr { ADD_OP(OP_ADD); ADD_OP(OP_ASSIGN); ADD_SYM($1); } | evalsym SUBEQ expr { ADD_OP(OP_SUB); ADD_OP(OP_ASSIGN); ADD_SYM($1); } | evalsym MULEQ expr { ADD_OP(OP_MUL); ADD_OP(OP_ASSIGN); ADD_SYM($1); } | evalsym DIVEQ expr { ADD_OP(OP_DIV); ADD_OP(OP_ASSIGN); ADD_SYM($1); } | evalsym MODEQ expr { ADD_OP(OP_MOD); ADD_OP(OP_ASSIGN); ADD_SYM($1); } | evalsym ANDEQ expr { ADD_OP(OP_BIT_AND); ADD_OP(OP_ASSIGN); ADD_SYM($1); } | evalsym OREQ expr { ADD_OP(OP_BIT_OR); ADD_OP(OP_ASSIGN); ADD_SYM($1); } | DELETE arraylv '[' arglist ']' { ADD_OP(OP_ARRAY_DELETE); ADD_IMMED((void *)$4); } | initarraylv '[' arglist ']' '=' expr { ADD_OP(OP_ARRAY_ASSIGN); ADD_IMMED((void *)$3); } | initarraylv '[' arglist ']' ADDEQ expr { ADD_OP(OP_ARRAY_REF_ASSIGN_SETUP); ADD_IMMED((void *)1); ADD_IMMED((void *)$3); ADD_OP(OP_ADD); ADD_OP(OP_ARRAY_ASSIGN); ADD_IMMED((void *)$3); } | initarraylv '[' arglist ']' SUBEQ expr { ADD_OP(OP_ARRAY_REF_ASSIGN_SETUP); ADD_IMMED((void *)1); ADD_IMMED((void *)$3); ADD_OP(OP_SUB); ADD_OP(OP_ARRAY_ASSIGN); ADD_IMMED((void *)$3); } | initarraylv '[' arglist ']' MULEQ expr { ADD_OP(OP_ARRAY_REF_ASSIGN_SETUP); ADD_IMMED((void *)1); ADD_IMMED((void *)$3); ADD_OP(OP_MUL); ADD_OP(OP_ARRAY_ASSIGN); ADD_IMMED((void *)$3); } | initarraylv '[' arglist ']' DIVEQ expr { ADD_OP(OP_ARRAY_REF_ASSIGN_SETUP); ADD_IMMED((void *)1); ADD_IMMED((void *)$3); ADD_OP(OP_DIV); ADD_OP(OP_ARRAY_ASSIGN); ADD_IMMED((void *)$3); } | initarraylv '[' arglist ']' MODEQ expr { ADD_OP(OP_ARRAY_REF_ASSIGN_SETUP); ADD_IMMED((void *)1); ADD_IMMED((void *)$3); ADD_OP(OP_MOD); ADD_OP(OP_ARRAY_ASSIGN); ADD_IMMED((void *)$3); } | initarraylv '[' arglist ']' ANDEQ expr { ADD_OP(OP_ARRAY_REF_ASSIGN_SETUP); ADD_IMMED((void *)1); ADD_IMMED((void *)$3); ADD_OP(OP_BIT_AND); ADD_OP(OP_ARRAY_ASSIGN); ADD_IMMED((void *)$3); } | initarraylv '[' arglist ']' OREQ expr { ADD_OP(OP_ARRAY_REF_ASSIGN_SETUP); ADD_IMMED((void *)1); ADD_IMMED((void *)$3); ADD_OP(OP_BIT_OR); ADD_OP(OP_ARRAY_ASSIGN); ADD_IMMED((void *)$3); } | initarraylv '[' arglist ']' INCR { ADD_OP(OP_ARRAY_REF_ASSIGN_SETUP); ADD_IMMED((void *)0); ADD_IMMED((void *)$3); ADD_OP(OP_INCR); ADD_OP(OP_ARRAY_ASSIGN); ADD_IMMED((void *)$3); } | initarraylv '[' arglist ']' DECR { ADD_OP(OP_ARRAY_REF_ASSIGN_SETUP); ADD_IMMED((void *)0); ADD_IMMED((void *)$3); ADD_OP(OP_DECR); ADD_OP(OP_ARRAY_ASSIGN); ADD_IMMED((void *)$3); } | INCR initarraylv '[' arglist ']' { ADD_OP(OP_ARRAY_REF_ASSIGN_SETUP); ADD_IMMED((void *)0); ADD_IMMED((void *)$4); ADD_OP(OP_INCR); ADD_OP(OP_ARRAY_ASSIGN); ADD_IMMED((void *)$4); } | DECR initarraylv '[' arglist ']' { ADD_OP(OP_ARRAY_REF_ASSIGN_SETUP); ADD_IMMED((void *)0); ADD_IMMED((void *)$4); ADD_OP(OP_DECR); ADD_OP(OP_ARRAY_ASSIGN); ADD_IMMED((void *)$4); } | SYMBOL '(' arglist ')' { ADD_OP(OP_SUBR_CALL); ADD_SYM(PromoteToGlobal($1)); ADD_IMMED((void *)$3); } | INCR SYMBOL { ADD_OP(OP_PUSH_SYM); ADD_SYM($2); ADD_OP(OP_INCR); ADD_OP(OP_ASSIGN); ADD_SYM($2); } | SYMBOL INCR { ADD_OP(OP_PUSH_SYM); ADD_SYM($1); ADD_OP(OP_INCR); ADD_OP(OP_ASSIGN); ADD_SYM($1); } | DECR SYMBOL { ADD_OP(OP_PUSH_SYM); ADD_SYM($2); ADD_OP(OP_DECR); ADD_OP(OP_ASSIGN); ADD_SYM($2); } | SYMBOL DECR { ADD_OP(OP_PUSH_SYM); ADD_SYM($1); ADD_OP(OP_DECR); ADD_OP(OP_ASSIGN); ADD_SYM($1); } ; evalsym: SYMBOL { $$ = $1; ADD_OP(OP_PUSH_SYM); ADD_SYM($1); } ; comastmts: /* nothing */ { $$ = GetPC(); } | simpstmt { $$ = GetPC(); } | comastmts ',' simpstmt { $$ = GetPC(); } ; arglist: /* nothing */ { $$ = 0; } | expr { $$ = 1; } | arglist ',' expr { $$ = $1 + 1; } ; expr: numexpr %prec CONCAT | expr numexpr %prec CONCAT { ADD_OP(OP_CONCAT); } ; initarraylv: SYMBOL { ADD_OP(OP_PUSH_ARRAY_SYM); ADD_SYM($1); ADD_IMMED((void *)1); } | initarraylv '[' arglist ']' { ADD_OP(OP_ARRAY_REF); ADD_IMMED((void *)$3); } ; arraylv: SYMBOL { ADD_OP(OP_PUSH_ARRAY_SYM); ADD_SYM($1); ADD_IMMED((void *)0); } | arraylv '[' arglist ']' { ADD_OP(OP_ARRAY_REF); ADD_IMMED((void *)$3); } ; arrayexpr: numexpr { $$ = GetPC(); } ; numexpr: NUMBER { ADD_OP(OP_PUSH_SYM); ADD_SYM($1); } | STRING { ADD_OP(OP_PUSH_SYM); ADD_SYM($1); } | SYMBOL { ADD_OP(OP_PUSH_SYM); ADD_SYM($1); } | SYMBOL '(' arglist ')' { ADD_OP(OP_SUBR_CALL); ADD_SYM(PromoteToGlobal($1)); ADD_IMMED((void *)$3); ADD_OP(OP_FETCH_RET_VAL); } | '(' expr ')' | ARG_LOOKUP '[' numexpr ']' { ADD_OP(OP_PUSH_ARG); } | ARG_LOOKUP '[' ']' { ADD_OP(OP_PUSH_ARG_COUNT); } | ARG_LOOKUP { ADD_OP(OP_PUSH_ARG_ARRAY); } | numexpr '[' arglist ']' { ADD_OP(OP_ARRAY_REF); ADD_IMMED((void *)$3); } | numexpr '+' numexpr { ADD_OP(OP_ADD); } | numexpr '-' numexpr { ADD_OP(OP_SUB); } | numexpr '*' numexpr { ADD_OP(OP_MUL); } | numexpr '/' numexpr { ADD_OP(OP_DIV); } | numexpr '%' numexpr { ADD_OP(OP_MOD); } | numexpr POW numexpr { ADD_OP(OP_POWER); } | '-' numexpr %prec UNARY_MINUS { ADD_OP(OP_NEGATE); } | numexpr GT numexpr { ADD_OP(OP_GT); } | numexpr GE numexpr { ADD_OP(OP_GE); } | numexpr LT numexpr { ADD_OP(OP_LT); } | numexpr LE numexpr { ADD_OP(OP_LE); } | numexpr EQ numexpr { ADD_OP(OP_EQ); } | numexpr NE numexpr { ADD_OP(OP_NE); } | numexpr '&' numexpr { ADD_OP(OP_BIT_AND); } | numexpr '|' numexpr { ADD_OP(OP_BIT_OR); } | numexpr and numexpr %prec AND { ADD_OP(OP_AND); SET_BR_OFF($2, GetPC()); } | numexpr or numexpr %prec OR { ADD_OP(OP_OR); SET_BR_OFF($2, GetPC()); } | NOT numexpr { ADD_OP(OP_NOT); } | INCR SYMBOL { ADD_OP(OP_PUSH_SYM); ADD_SYM($2); ADD_OP(OP_INCR); ADD_OP(OP_DUP); ADD_OP(OP_ASSIGN); ADD_SYM($2); } | SYMBOL INCR { ADD_OP(OP_PUSH_SYM); ADD_SYM($1); ADD_OP(OP_DUP); ADD_OP(OP_INCR); ADD_OP(OP_ASSIGN); ADD_SYM($1); } | DECR SYMBOL { ADD_OP(OP_PUSH_SYM); ADD_SYM($2); ADD_OP(OP_DECR); ADD_OP(OP_DUP); ADD_OP(OP_ASSIGN); ADD_SYM($2); } | SYMBOL DECR { ADD_OP(OP_PUSH_SYM); ADD_SYM($1); ADD_OP(OP_DUP); ADD_OP(OP_DECR); ADD_OP(OP_ASSIGN); ADD_SYM($1); } | numexpr IN numexpr { ADD_OP(OP_IN_ARRAY); } ; while: WHILE { $$ = GetPC(); StartLoopAddrList(); } ; for: FOR { StartLoopAddrList(); $$ = GetPC(); } ; else: ELSE { ADD_OP(OP_BRANCH); $$ = GetPC(); ADD_BR_OFF(0); } ; cond: /* nothing */ { ADD_OP(OP_BRANCH_NEVER); $$ = GetPC(); ADD_BR_OFF(0); } | numexpr { ADD_OP(OP_BRANCH_FALSE); $$ = GetPC(); ADD_BR_OFF(0); } ; and: AND { ADD_OP(OP_DUP); ADD_OP(OP_BRANCH_FALSE); $$ = GetPC(); ADD_BR_OFF(0); } ; or: OR { ADD_OP(OP_DUP); ADD_OP(OP_BRANCH_TRUE); $$ = GetPC(); ADD_BR_OFF(0); } ; blank: /* nothing */ | blank '\n' ; %% /* User Subroutines Section */ /* ** Parse a null terminated string and create a program from it (this is the ** parser entry point). The program created by this routine can be ** executed using ExecuteProgram. Returns program on success, or NULL ** on failure. If the command failed, the error message is returned ** as a pointer to a static string in msg, and the length of the string up ** to where parsing failed in stoppedAt. */ Program *ParseMacro(char *expr, char **msg, char **stoppedAt) { Program *prog; BeginCreatingProgram(); /* call yyparse to parse the string and check for success. If the parse failed, return the error message and string index (the grammar aborts parsing at the first error) */ InPtr = expr; if (yyparse()) { *msg = ErrMsg; *stoppedAt = InPtr; FreeProgram(FinishCreatingProgram()); return NULL; } /* get the newly created program */ prog = FinishCreatingProgram(); /* parse succeeded */ *msg = ""; *stoppedAt = InPtr; return prog; } static int yylex(void) { int i, len; Symbol *s; static DataValue value = {NO_TAG, {0}}; static char escape[] = "\\\"ntbrfav"; static char replace[] = "\\\"\n\t\b\r\f\a\v"; /* skip whitespace, backslash-newline combinations, and comments, which are all considered whitespace */ for (;;) { if (*InPtr == '\\' && *(InPtr + 1) == '\n') InPtr += 2; else if (*InPtr == ' ' || *InPtr == '\t') InPtr++; else if (*InPtr == '#') while (*InPtr != '\n' && *InPtr != '\0') { /* Comments stop at escaped newlines */ if (*InPtr == '\\' && *(InPtr + 1) == '\n') { InPtr += 2; break; } InPtr++; } else break; } /* return end of input at the end of the string */ if (*InPtr == '\0') { return 0; } /* process number tokens */ if (isdigit((unsigned char)*InPtr)) { /* number */ char name[28]; sscanf(InPtr, "%d%n", &value.val.n, &len); sprintf(name, "const %d", value.val.n); InPtr += len; value.tag = INT_TAG; if ((yylval.sym=LookupSymbol(name)) == NULL) yylval.sym = InstallSymbol(name, CONST_SYM, value); return NUMBER; } /* process symbol tokens. "define" is a special case not handled by this parser, considered end of input. Another special case is action routine names which are allowed to contain '-' despite the ambiguity, handled in matchesActionRoutine. */ if (isalpha((unsigned char)*InPtr) || *InPtr == '$') { if ((s=matchesActionRoutine(&InPtr)) == NULL) { char symName[MAX_SYM_LEN+1], *p = symName; *p++ = *InPtr++; while (isalnum((unsigned char)*InPtr) || *InPtr=='_') { if (p >= symName + MAX_SYM_LEN) InPtr++; else *p++ = *InPtr++; } *p = '\0'; if (!strcmp(symName, "while")) return WHILE; if (!strcmp(symName, "if")) return IF; if (!strcmp(symName, "else")) return ELSE; if (!strcmp(symName, "for")) return FOR; if (!strcmp(symName, "break")) return BREAK; if (!strcmp(symName, "continue")) return CONTINUE; if (!strcmp(symName, "return")) return RETURN; if (!strcmp(symName, "in")) return IN; if (!strcmp(symName, "$args")) return ARG_LOOKUP; if (!strcmp(symName, "delete") && follow_non_whitespace('(', SYMBOL, DELETE) == DELETE) return DELETE; if (!strcmp(symName, "define")) { InPtr -= 6; return 0; } if ((s=LookupSymbol(symName)) == NULL) { s = InstallSymbol(symName, symName[0]=='$' ? (((symName[1] > '0' && symName[1] <= '9') && symName[2] == 0) ? ARG_SYM : GLOBAL_SYM) : LOCAL_SYM, value); s->value.tag = NO_TAG; } } yylval.sym = s; return SYMBOL; } /* process quoted strings w/ embedded escape sequences */ if (*InPtr == '\"') { char string[MAX_STRING_CONST_LEN], *p = string; InPtr++; while (*InPtr != '\0' && *InPtr != '\"' && *InPtr != '\n') { if (p >= string + MAX_STRING_CONST_LEN) { InPtr++; continue; } if (*InPtr == '\\') { InPtr++; if (*InPtr == '\n') { InPtr++; continue; } for (i=0; escape[i]!='\0'; i++) { if (escape[i] == '\0') { *p++= *InPtr++; break; } else if (escape[i] == *InPtr) { *p++ = replace[i]; InPtr++; break; } } } else *p++= *InPtr++; } *p = '\0'; InPtr++; yylval.sym = InstallStringConstSymbol(string); return STRING; } /* process remaining two character tokens or return single char as token */ switch(*InPtr++) { case '>': return follow('=', GE, GT); case '<': return follow('=', LE, LT); case '=': return follow('=', EQ, '='); case '!': return follow('=', NE, NOT); case '+': return follow2('+', INCR, '=', ADDEQ, '+'); case '-': return follow2('-', DECR, '=', SUBEQ, '-'); case '|': return follow2('|', OR, '=', OREQ, '|'); case '&': return follow2('&', AND, '=', ANDEQ, '&'); case '*': return follow2('*', POW, '=', MULEQ, '*'); case '/': return follow('=', DIVEQ, '/'); case '%': return follow('=', MODEQ, '%'); case '^': return POW; default: return *(InPtr-1); } } /* ** look ahead for >=, etc. */ static int follow(char expect, int yes, int no) { if (*InPtr++ == expect) return yes; InPtr--; return no; } static int follow2(char expect1, int yes1, char expect2, int yes2, int no) { char next = *InPtr++; if (next == expect1) return yes1; if (next == expect2) return yes2; InPtr--; return no; } static int follow_non_whitespace(char expect, int yes, int no) { char *localInPtr = InPtr; while (1) { if (*localInPtr == ' ' || *localInPtr == '\t') { ++localInPtr; } else if (*localInPtr == '\\' && *(localInPtr + 1) == '\n') { localInPtr += 2; } else if (*localInPtr == expect) { return(yes); } else { return(no); } } } /* ** Look (way) ahead for hyphenated routine names which begin at inPtr. A ** hyphenated name is allowed if it is pre-defined in the global symbol ** table. If a matching name exists, returns the symbol, and update "inPtr". ** ** I know this is horrible language design, but existing nedit action routine ** names contain hyphens. Handling them here in the lexical analysis process ** is much easier than trying to deal with it in the parser itself. (sorry) */ static Symbol *matchesActionRoutine(char **inPtr) { char *c, *symPtr; int hasDash = False; char symbolName[MAX_SYM_LEN+1]; Symbol *s; symPtr = symbolName; for (c = *inPtr; isalnum((unsigned char)*c) || *c=='_' || ( *c=='-' && isalnum((unsigned char)(*(c+1)))); c++) { if (*c == '-') hasDash = True; *symPtr++ = *c; } if (!hasDash) return NULL; *symPtr = '\0'; s = LookupSymbol(symbolName); if (s != NULL) *inPtr = c; return s; } /* ** Called by yacc to report errors (just stores for returning when ** parsing is aborted. The error token action is to immediate abort ** parsing, so this message is immediately reported to the caller ** of ParseExpr) */ static int yyerror(char *s) { ErrMsg = s; return 0; }