diff options
Diffstat (limited to 'awkgram.y')
-rw-r--r-- | awkgram.y | 284 |
1 files changed, 258 insertions, 26 deletions
@@ -44,7 +44,11 @@ static int yylex(void); int yyparse(void); static INSTRUCTION *snode(INSTRUCTION *subn, INSTRUCTION *op); static char **check_params(char *fname, int pcount, INSTRUCTION *list); +static void check_param(const char *fname, const char *name, INSTRUCTION *parm); +static void check_local(const char *fname, const char *name, INSTRUCTION *local); +static char *gensym(const char *prefix); static int install_function(char *fname, INSTRUCTION *fi, INSTRUCTION *plist); +static bool add_let(INSTRUCTION *fi, INSTRUCTION *parm); static NODE *mk_rexp(INSTRUCTION *exp); static void param_sanity(INSTRUCTION *arglist); static int parms_shadow(INSTRUCTION *pc, bool *shadow); @@ -120,7 +124,10 @@ static enum { FUNC_BODY, DONT_CHECK } want_param_names = DONT_CHECK; /* ditto */ -static bool in_function; /* parsing kludge */ +static INSTRUCTION *in_function; /* parsing kludge */ +static int in_loop; /* parsing kludge */ +static NODE *let_free; /* free list of lexical vars */ +static NODE *let_stack; /* stack of allocated lexicals */ static int rule = 0; const char *const ruletab[] = { @@ -205,7 +212,7 @@ extern double fmod(double x, double y); %token LEX_AND LEX_OR INCREMENT DECREMENT %token LEX_BUILTIN LEX_LENGTH %token LEX_EOF -%token LEX_INCLUDE LEX_EVAL LEX_LOAD LEX_NAMESPACE +%token LEX_INCLUDE LEX_EVAL LEX_LOAD LEX_NAMESPACE LEX_LET %token NEWLINE /* Lowest to highest */ @@ -531,9 +538,11 @@ function_prologue $1->source_file = source; $1->comment = func_comment; + /* Clear out lexical allocator, just in case */ + let_stack = let_free = NULL; if (install_function($2->lextok, $1, $5) < 0) YYABORT; - in_function = true; + in_function = $1; $2->lextok = NULL; bcfree($2); /* $5 already free'd in install_function */ @@ -782,6 +791,8 @@ statement INSTRUCTION *ip, *tbreak, *tcont; + in_loop--; + tbreak = instruction(Op_no_op); add_lint($3, LINT_assign_in_cond); tcont = $3->nexti; @@ -832,6 +843,8 @@ statement INSTRUCTION *ip, *tbreak, *tcont; + in_loop--; + tbreak = instruction(Op_no_op); tcont = $6->nexti; add_lint($6, LINT_assign_in_cond); @@ -871,6 +884,8 @@ statement INSTRUCTION *ip; char *var_name = $3->lextok; + in_loop--; + if ($8 != NULL && $8->lasti->opcode == Op_K_delete && $8->lasti->expr_count == 1 @@ -994,6 +1009,8 @@ regular_loop: } | LEX_FOR '(' opt_simple_stmt semi opt_nls exp semi opt_nls opt_simple_stmt r_paren opt_nls statement { + in_loop--; + if ($5 != NULL) { merge_comments($5, NULL); $1->comment = $5; @@ -1016,6 +1033,8 @@ regular_loop: } | LEX_FOR '(' opt_simple_stmt semi opt_nls semi opt_nls opt_simple_stmt r_paren opt_nls statement { + in_loop--; + if ($5 != NULL) { merge_comments($5, NULL); $1->comment = $5; @@ -1035,6 +1054,45 @@ regular_loop: break_allowed--; continue_allowed--; } + | '@' LEX_LET '(' + { + /* Trick: remember current let stack top in LEX_LET token, + * which is an INSTRUCTION of Op_type_sym. All the lets get + * pushed onto this stack. If we know the old top, we can then + * tear them down. + */ + $2->memory = let_stack; + } + let_var_list_opt r_paren opt_nls statement + { + NODE *old_let_stack = $2->memory; + + if ($7 != NULL) { + merge_comments($7, NULL); + $2->comment = $7; + } + + if ($5 == NULL) + $$ = $8; + else if ($8 == NULL) + $$ = $5; + else + $$ = list_merge($5, $8); + + /* Let block is processed; remove the variables */ + while (let_stack != old_let_stack) { + NODE *let = let_stack; + /* pop from let stack */ + let_stack = let->nxparam; + /* push onto free list */ + let->nxparam = let_free; + let_free = let; + /* scrub from symbol table */ + remove_let(let); + } + + yyerrok; + } | non_compound_stmt { if (do_pretty_print) @@ -1535,6 +1593,80 @@ param_list { $$ = $1; } ; +let_var_list_opt + : /* empty */ + { $$ = NULL; } + | let_var_list + { $$ = $1; } + ; + +let_var_list + : NAME + { + bool is_reused_location = add_let(in_function, $1); + + /* If we are not in a loop, and the variable is using + a fresh location, then we can count on that being + clear. Otherwise we have to generate code to clear it */ + if (!in_loop && !is_reused_location) { + $$ = NULL; + } else { + $1->opcode = Op_clear_var; + $1->memory = variable($1->source_line, $1->lextok, + Node_var_new); + $$ = list_create($1); + } + } + | let_var_list comma NAME + { + bool is_reused_location = add_let(in_function, $3); + + /* If we are not in a loop, and the variable is using + a fresh location, then we can count on that being + clear. Otherwise we have to generate code to clear it */ + if (!in_loop && !is_reused_location) { + $$ = $1; + } else { + $3->opcode = Op_clear_var; + $3->memory = variable($3->source_line, $3->lextok, + Node_var_new); + + if ($1 == NULL) + $$ = list_create($3); + else + $$ = list_append($1, $3); + } + } + | NAME ASSIGN exp + { + add_let(in_function, $1); + $1->opcode = Op_push; + $1->memory = variable($1->source_line, $1->lextok, Node_var_new); + $$ = list_append(mk_assignment(list_create($1), $3, $2), + instruction(Op_pop)); + + } + | let_var_list comma NAME ASSIGN exp + { + INSTRUCTION *assn; + add_let(in_function, $3); + $3->opcode = Op_push; + $3->memory = variable($3->source_line, $3->lextok, Node_var_new); + assn = list_append(mk_assignment(list_create($3), $5, $4), + instruction(Op_pop)); + if ($1 == NULL) + $$ = assn; + else + $$ = list_merge($1, assn); + } + | error + { $$ = NULL; } + | let_var_list error + { $$ = $1; } + | let_var_list comma error + { $$ = $1; } + ; + /* optional expression, as in for loop */ opt_exp : /* empty */ @@ -2325,6 +2457,7 @@ static const struct token tokentab[] = { #endif {"isarray", Op_builtin, LEX_BUILTIN, GAWKX|A(1), do_isarray, 0}, {"length", Op_builtin, LEX_LENGTH, A(0)|A(1), do_length, 0}, +{"let", Op_symbol, LEX_LET, GAWKX, 0, 0}, {"load", Op_symbol, LEX_LOAD, GAWKX, 0, 0}, {"log", Op_builtin, LEX_BUILTIN, A(1), do_log, MPF(log)}, {"lshift", Op_builtin, LEX_BUILTIN, GAWKX|A(2), do_lshift, MPF(lshift)}, @@ -4375,6 +4508,7 @@ retry: switch (class) { case LEX_EVAL: case LEX_INCLUDE: + case LEX_LET: case LEX_LOAD: case LEX_NAMESPACE: if (lasttok != '@') @@ -4464,6 +4598,8 @@ retry: case LEX_FOR: case LEX_WHILE: case LEX_DO: + in_loop++; + /* falltrhough */ case LEX_SWITCH: if (! do_pretty_print) return lasttok = class; @@ -4869,9 +5005,9 @@ snode(INSTRUCTION *subn, INSTRUCTION *r) static int parms_shadow(INSTRUCTION *pc, bool *shadow) { - int pcount, i; + int pcount, lcount, i; bool ret = false; - NODE *func, *fp; + NODE *func, **fp; char *fname; func = pc->func_body; @@ -4884,8 +5020,9 @@ parms_shadow(INSTRUCTION *pc, bool *shadow) #endif pcount = func->param_cnt; + lcount = func->frame_cnt; - if (pcount == 0) /* no args, no problem */ + if (lcount == 0) /* no locals, no problem */ return 0; source = pc->source_file; @@ -4894,11 +5031,12 @@ parms_shadow(INSTRUCTION *pc, bool *shadow) * Use warning() and not lintwarn() so that can warn * about all shadowed parameters. */ - for (i = 0; i < pcount; i++) { - if (lookup(fp[i].param) != NULL) { - warning( - _("function `%s': parameter `%s' shadows global variable"), - fname, fp[i].param); + for (i = 0; i < lcount; i++) { + if (lookup(fp[i]->param) != NULL) { + warning((i < pcount) + ? _("function `%s': parameter `%s' shadows global variable") + : _("function `%s': local `%s' shadows global variable"), + fname, fp[i]->param); ret = true; } } @@ -5046,8 +5184,8 @@ mk_function(INSTRUCTION *fi, INSTRUCTION *def) /* update lint table info */ func_use(thisfunc->vname, FUNC_DEFINE); - /* remove params from symbol table */ - remove_params(thisfunc); + /* remove params/locals from symbol table */ + remove_locals(thisfunc); return fi; } @@ -5078,7 +5216,12 @@ install_function(char *fname, INSTRUCTION *fi, INSTRUCTION *plist) } fi->func_body = f; - f->param_cnt = pcount; + /* + * param_cnt and frame_cnt stay the same if there are no @local + * variables. add_let increments frame_cnt, and frame_cnt + * is what is allocated when a function is invoked. + */ + f->frame_cnt = f->param_cnt = pcount; f->code_ptr = fi; f->fparms = NULL; if (pcount > 0) { @@ -5091,6 +5234,59 @@ install_function(char *fname, INSTRUCTION *fi, INSTRUCTION *plist) return 0; } +static bool +add_let(INSTRUCTION *fi, INSTRUCTION *local) +{ + NODE *f = fi != NULL ? fi->func_body : NULL; + const char *fname = f != NULL ? f->vname : NULL; + const char *name = estrdup(local->lextok, strlen(local->lextok)); + + /* Basic checks:*/ + check_local(fname, name, local); + + /* No duplicate check for lexicals */ + + /* + * Try to get lexical from the free list. + */ + if (let_free) { + /* pop let from stack */ + NODE *let = let_free; + let_free = let_free->nxparam; + /* register in param or alias table under the given name */ + install_let(let, name); + /* push onto let stack */ + let->nxparam = let_stack; + let_stack = let; + return true; /* Reused frame slot */ + } else if (f != NULL) { /* allocate new local in function */ + NODE **parms = f->fparms; + int lcount = f->frame_cnt, i; + NODE *let; + + /* Reallocate the function's param vector to accommodate + * the new one, or allocate if null. + */ + lcount++; + f->fparms = extend_locals(parms, name, lcount); + f->frame_cnt = lcount; + + let = f->fparms[lcount - 1]; + let->nxparam = let_stack; + let_stack = let; + + return false; /* Fresh, not re-used frame slot */ + } else { /* allocate new let outside of function as alias for anon global */ + char *var = gensym("let"); + NODE *anon_global = variable(local->source_line, var, Node_var_new); + NODE *let = install_global_let(name, anon_global); + + let->nxparam = let_stack; + let_stack = let; + + return false; + } +} /* check_params --- build a list of function parameter names after * making sure that the names are valid and there are no duplicates. @@ -5113,18 +5309,7 @@ check_params(char *fname, int pcount, INSTRUCTION *list) name = p->lextok; p->lextok = NULL; - if (strcmp(name, fname) == 0) { - /* check for function foo(foo) { ... }. bleah. */ - error_ln(p->source_line, - _("function `%s': cannot use function name as parameter name"), fname); - } else if (is_std_var(name)) { - error_ln(p->source_line, - _("function `%s': cannot use special variable `%s' as a function parameter"), - fname, name); - } else if (strchr(name, ':') != NULL) - error_ln(p->source_line, - _("function `%s': parameter `%s' cannot contain a namespace"), - fname, name); + check_param(fname, name, p); /* check for duplicate parameters */ for (j = 0; j < i; j++) { @@ -5143,6 +5328,53 @@ check_params(char *fname, int pcount, INSTRUCTION *list) return pnames; } +/* check_param --- perform basic checks on one parameter. + */ +static void +check_param(const char *fname, const char *name, INSTRUCTION *parm) +{ + if (strcmp(name, fname) == 0) { + /* check for function foo(foo) { ... }. bleah. */ + error_ln(parm->source_line, + _("function `%s': cannot use function name as parameter name"), fname); + } else if (is_std_var(name)) { + error_ln(parm->source_line, + _("function `%s': cannot use special variable `%s' as a function parameter"), + fname, name); + } else if (strchr(name, ':') != NULL) { + error_ln(parm->source_line, + _("function `%s': parameter `%s' cannot contain a namespace"), + fname, name); + } +} + +/* check_local == like check_param but with wording about locals + */ +static void +check_local(const char *fname, const char *name, INSTRUCTION *local) +{ + if (fname && strcmp(name, fname) == 0) { + /* check for function foo(foo) { ... }. bleah. */ + error_ln(local->source_line, + _("function `%s': cannot use function name as local variable name"), fname); + } else if (is_std_var(name)) { + error_ln(local->source_line, + _("cannot use special variable `%s' as a local variable"), name); + } else if (strchr(name, ':') != NULL) { + error_ln(local->source_line, + _("local variable `%s' cannot contain a namespace"), name); + } +} + +static char * +gensym(const char *prefix) +{ + char buf[64]; + static unsigned int gensym_counter; + + size_t len = snprintf(buf, sizeof buf, "$%s%04d", prefix, ++gensym_counter); + return estrdup(buf, len); +} #ifdef HASHSIZE undef HASHSIZE |