aboutsummaryrefslogtreecommitdiffstats
path: root/awkgram.y
diff options
context:
space:
mode:
Diffstat (limited to 'awkgram.y')
-rw-r--r--awkgram.y284
1 files changed, 258 insertions, 26 deletions
diff --git a/awkgram.y b/awkgram.y
index 08ee381c..44a4a48c 100644
--- a/awkgram.y
+++ b/awkgram.y
@@ -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