From 77891fa5b2713384969886772e7ee47904f6ba79 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Thu, 3 Nov 2011 11:13:38 -0400 Subject: [PATCH] Scattering `for' notation This is a clean rewrite of scattering `for' notation, not based on the old implementation from 2000. It only affects the parser and unparser, compiling to the same ast/bytecode as would be with a temporary variable. --- ChangeLog.txt | 4 +++ parser.y | 96 ++++++++++++++++++++++++++++++++++++++++++++++----- unparse.c | 32 +++++++++++++++-- 3 files changed, 121 insertions(+), 11 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index b36fa8a2f..420580166 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -2295,3 +2295,7 @@ Version 1.8.0p5, 12 May 1996 36789.foo (an understandable typo for #6789.foo) stopped compiling when found in a database made by a pre-1.8.0 server. + +Version X, in progress +**** Changes relevant to programmers / wizards: +-- Added support for scattering in `for' statements. diff --git a/parser.y b/parser.y index 508f60a86..4e3ded51a 100644 --- a/parser.y +++ b/parser.y @@ -51,19 +51,21 @@ static DB_Version language_version; static void error(const char *, const char *); static void warning(const char *, const char *); static int find_id(char *name); +static const char *get_name_from_id(int); static void yyerror(const char *s); static int yylex(void); static Scatter *scatter_from_arglist(Arg_List *); static Scatter *add_scatter_item(Scatter *, Scatter *); static void vet_scatter(Scatter *); static void push_loop_name(const char *); +static void push_loop_name_with_id(const char *, int); static void pop_loop_name(void); static void suspend_loop_scope(void); static void resume_loop_scope(void); enum loop_exit_kind { LOOP_BREAK, LOOP_CONTINUE }; -static void check_loop_name(const char *, enum loop_exit_kind); +static int check_loop_name(char *, enum loop_exit_kind); %} %union { @@ -86,7 +88,7 @@ static void check_loop_name(const char *, enum loop_exit_kind); %type arglist ne_arglist codes %type except excepts %type opt_id -%type scatter scatter_item +%type scatter_any scatter scatter_item %token tINTEGER %token tOBJECT @@ -140,6 +142,47 @@ statement: $$->s.cond.arms->next = $6; $$->s.cond.otherwise = $7; } + | tFOR '{' scatter_any '}' tIN '(' expr ')' + { + const char *vname; + Stream *s; + int id; + + if (!$3) { + yyerror("Empty list in scattering for."); + return 0; + } + + vet_scatter($3); + vname = get_name_from_id($3->id); + s = new_stream(8); + stream_printf(s, "@%s", vname); + id = find_id(alloc_string(stream_contents(s))); + free_stream(s); + push_loop_name_with_id(vname, id); + + $$ = id; + } + statements tENDFOR + { + Stmt *s; + Expr *lhs, *rhs; + int id = $9; + + lhs = alloc_expr(EXPR_SCATTER); + lhs->e.scatter = $3; + rhs = alloc_expr(EXPR_ID); + rhs->e.id = id; + s = alloc_stmt(STMT_EXPR); + s->s.expr = alloc_binary(EXPR_ASGN, lhs, rhs); + s->next = $10; + + $$ = alloc_stmt(STMT_LIST); + $$->s.list.id = id; + $$->s.list.expr = $7; + $$->s.list.body = s; + pop_loop_name(); + } | tFOR tID tIN '(' expr ')' { push_loop_name($2); @@ -226,9 +269,10 @@ statement: } | tBREAK tID ';' { + int id = check_loop_name($2, LOOP_BREAK); $$ = alloc_stmt(STMT_BREAK); - $$->s.exit = find_id($2); + $$->s.exit = id; } | tCONTINUE ';' { @@ -238,9 +282,10 @@ statement: } | tCONTINUE tID ';' { + int id = check_loop_name($2, LOOP_CONTINUE); $$ = alloc_stmt(STMT_CONTINUE); - $$->s.exit = find_id($2); + $$->s.exit = id; } | tRETURN expr ';' { @@ -640,6 +685,17 @@ ne_arglist: } ; +scatter_any: + arglist + { + $$ = scatter_from_arglist($1); + } + | scatter + { + $$ = $1; + } + ; + scatter: ne_arglist ',' scatter_item { @@ -695,6 +751,12 @@ find_id(char *name) return slot; } +static const char* +get_name_from_id(int id) +{ + return local_names->names[id]; +} + static void yyerror(const char *s) { @@ -1008,21 +1070,29 @@ struct loop_entry { struct loop_entry *next; const char *name; int is_barrier; + int id; }; static struct loop_entry *loop_stack; static void -push_loop_name(const char *name) +push_loop_name_with_id(const char *name, int id) { struct loop_entry *entry = mymalloc(sizeof(struct loop_entry), M_AST); entry->next = loop_stack; entry->name = (name ? str_dup(name) : 0); entry->is_barrier = 0; + entry->id = id; loop_stack = entry; } +static void +push_loop_name(const char *name) +{ + push_loop_name_with_id(name, -1); +} + static void pop_loop_name(void) { @@ -1066,8 +1136,8 @@ resume_loop_scope(void) } } -static void -check_loop_name(const char *name, enum loop_exit_kind kind) +static int +check_loop_name(char *name, enum loop_exit_kind kind) { struct loop_entry *entry; @@ -1078,17 +1148,25 @@ check_loop_name(const char *name, enum loop_exit_kind kind) else yyerror("No enclosing loop for `continue' statement"); } - return; + return -1; } for (entry = loop_stack; entry && !entry->is_barrier; entry = entry->next) if (entry->name && mystrcasecmp(entry->name, name) == 0) - return; + { + if (entry->id == -1) + entry->id = find_id(name); + else + dealloc_string(name); + return entry->id; + } if (kind == LOOP_BREAK) error("Invalid loop name in `break' statement: ", name); else error("Invalid loop name in `continue' statement: ", name); + dealloc_string(name); + return -1; } Program * diff --git a/unparse.c b/unparse.c index ec68fe1c5..1356ee9f0 100644 --- a/unparse.c +++ b/unparse.c @@ -271,12 +271,40 @@ unparse_stmt_cond(Stream *str, struct Stmt_Cond cond, int indent) static void unparse_stmt_list(Stream *str, struct Stmt_List list, int indent) { - stream_printf(str, "for %s in (", prog->var_names[list.id]); + const char *vname = prog->var_names[list.id]; + Stmt *body = list.body; + + stream_add_string(str, "for "); + if (vname[0] == '@' && + body->kind == STMT_EXPR && + body->s.expr->kind == EXPR_ASGN && + body->s.expr->e.bin.rhs->kind == EXPR_ID && + body->s.expr->e.bin.rhs->e.id == list.id && + body->s.expr->e.bin.lhs->kind == EXPR_SCATTER) { + unparse_expr(str, body->s.expr->e.bin.lhs); + /* NOTE: if programmers are ever allowed to put '@' in their own + * break/continue statements, this could protentially lead to a + * decompile-then-recompile inconsistency. For example: + * for {a, b} in (c) + * for a in (a) would continue the *outer* loop + * continue @a; <-- initially, but decompile as "continue + * endfor a" and then recompile to continue the + * endfor inner loop instead + * This could also happen without extending the syntax, by + * allowing programmers to modify the bytecode other ways (eg, + * an assembler/debugger). + */ + prog->var_names[list.id] = &prog->var_names[list.id][1]; + body = body->next; + } else + stream_add_string(str, vname); + stream_add_string(str, " in ("); unparse_expr(str, list.expr); stream_add_char(str, ')'); output(str); - unparse_stmt(list.body, indent + 2); + unparse_stmt(body, indent + 2); indent_stmt(str, indent); + prog->var_names[list.id] = vname; stream_add_string(str, "endfor"); output(str); }