From 8414e07010b2265db3cbbd2854d8dc49acc63ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Fri, 26 May 2017 15:13:34 +0200 Subject: Implement dynamic scoping Okay, that was a PITA to not have. But I think I'm set now, feature-wise. --- README.adoc | 18 ++++------ ell.c | 108 +++++++++++++++++++++++++++++++++++++++++------------------- repl.c | 2 +- 3 files changed, 81 insertions(+), 47 deletions(-) diff --git a/README.adoc b/README.adoc index ade07f2..c31b576 100644 --- a/README.adoc +++ b/README.adoc @@ -51,10 +51,9 @@ For a slightly more realistic example have a look at 'greet.ell'. Runtime ------- -All variables are put in a single global namespace with no further scoping. -Arguments to a block (which is a list of lists) must be assigned to variables -first using the `arg` special form, and that must happen before they get -overriden by execution of a different block. +Variables use per-block dynamic scoping. Arguments to a block (which is a list +of lists) must be assigned to variables first using the `arg` special form, and +that must happen before they get overriden by execution of a different block. When evaluating a command, the first argument is typically a string with its name and it is resolved as if `set` was called on it. @@ -69,8 +68,9 @@ Returns the arguments without any evaluation. `arg []...` -Assigns arguments to the current call in order to given names. -Names for which there are no values left are set to `[]`. +Assigns arguments to the current block in order to given names. Names for which +there are no values left default to `[]`. This form can effectively be used to +declare local variables. Standard library ---------------- @@ -167,12 +167,6 @@ Install development packages for GNU Readline to get a REPL for toying around: Possible Ways of Complicating ----------------------------- - * variable scoping: lexical scoping is deemed too complex. The simplest is to - look up and set values in the nearest dynamic scope they can be found in, - or globally if not found, and have `arg` create the scopes, which also makes - AWK-style local variables work. A convention of starting locally bound names - with an underscore can keep the global namespace always accessible, and even - overridable if needed. * reference counting: currently all values are always copied as needed, which is good enough for all imaginable use cases, simpler and less error-prone diff --git a/ell.c b/ell.c index af65e85..0b1d400 100644 --- a/ell.c +++ b/ell.c @@ -638,7 +638,8 @@ parser_run (struct parser *self, const char **e) { // --- Runtime ----------------------------------------------------------------- struct context { - struct item *variables; ///< List of variables + struct item *globals; ///< List of global variables + struct item *scopes; ///< Dynamic scopes from newest struct native_fn *native; ///< Maps strings to C functions struct item *arguments; ///< Arguments to last executed block @@ -667,7 +668,8 @@ context_free (struct context *ctx) { next = iter->next; free (iter); } - item_free_list (ctx->variables); + item_free_list (ctx->globals); + item_free_list (ctx->scopes); item_free_list (ctx->arguments); free (ctx->error); } @@ -677,27 +679,17 @@ check (struct context *ctx, struct item *item) { return !(ctx->memory_failure |= !item); } -static struct item * -get (struct context *ctx, const char *name) { - for (struct item *iter = ctx->variables; iter; iter = iter->next) - if (!strcmp (iter->head->value, name)) - return iter->head->next; +static struct item ** +scope_find (struct item **scope, const char *name) { + for (; *scope; scope = &(*scope)->next) + if (!strcmp ((*scope)->head->value, name)) + return scope; return NULL; } static bool -set (struct context *ctx, const char *name, struct item *value) { - struct item **p; - for (p = &ctx->variables; *p; p = &(*p)->next) - if (!strcmp ((*p)->head->value, name)) { - struct item *tmp = *p; - *p = (*p)->next; - item_free (tmp); - break; - } - if (!value) - return true; - +scope_prepend (struct context *ctx, struct item **scope, + const char *name, struct item *value) { struct item *key, *pair; if (!check (ctx, (key = new_string (name, strlen (name)))) || !check (ctx, (pair = new_list (key)))) { @@ -705,11 +697,43 @@ set (struct context *ctx, const char *name, struct item *value) { return false; } key->next = value; - pair->next = ctx->variables; - ctx->variables = pair; + pair->next = *scope; + *scope = pair; return true; } +static struct item * +get (struct context *ctx, const char *name) { + struct item **item; + for (struct item *scope = ctx->scopes; scope; scope = scope->next) + if ((item = scope_find (&scope->head, name))) + return (*item)->head->next; + if (!(item = scope_find (&ctx->globals, name))) + return NULL; + return (*item)->head->next; +} + +static bool +set (struct context *ctx, const char *name, struct item *value) { + struct item **item; + for (struct item *scope = ctx->scopes; scope; scope = scope->next) { + if ((item = scope_find (&scope->head, name))) { + item_free_list ((*item)->head->next); + (*item)->head->next = NULL; + return !value + || check (ctx, ((*item)->head->next = new_clone (value))); + } + } + + // Variables only get deleted by "arg" or from the global scope + if ((item = scope_find (&ctx->globals, name))) { + struct item *tmp = *item; + *item = (*item)->next; + item_free (tmp); + } + return !value || scope_prepend (ctx, &ctx->globals, name, value); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static struct native_fn * @@ -757,6 +781,10 @@ can_modify_error (struct context *ctx) { static bool assign_arguments (struct context *ctx, struct item *names) { + struct item **scope = &ctx->scopes->head; + item_free_list (*scope); + *scope = NULL; + struct item *arg = ctx->arguments; for (; names; names = names->next) { if (names->type != ITEM_STRING) @@ -765,7 +793,8 @@ assign_arguments (struct context *ctx, struct item *names) { struct item *value = NULL; if (arg && !check (ctx, (value = new_clone (arg)))) return false; - if (!set (ctx, names->value, value)) + // Duplicates don't really matter to us, user's problem + if (!scope_prepend (ctx, scope, names->value, value)) return false; if (arg) arg = arg->next; @@ -893,16 +922,27 @@ execute_statement return false; } -// Execute a block and return whatever the last statement returned +/// Execute a block and return whatever the last statement returned static bool execute (struct context *ctx, struct item *body, struct item **result) { + struct item *scope; + if (!check (ctx, (scope = new_list (NULL)))) + return false; + + scope->next = ctx->scopes; + ctx->scopes = scope; + + bool ok = true; for (; body; body = body->next) { item_free_list (*result); *result = NULL; - if (!execute_statement (ctx, body, result)) - return false; + + if (!(ok = execute_statement (ctx, body, result))) + break; } - return true; + ctx->scopes = scope->next; + item_free (scope); + return ok; } // --- Runtime library --------------------------------------------------------- @@ -1269,14 +1309,14 @@ const char init_program[] = "set break { throw _break }\n" // TODO: we should be able to apply them to all arguments - "set ne? { arg _ne1 _ne2; not (eq? @_ne1 @_ne2) }\n" - "set ge? { arg _ge1 _ge2; not (lt? @_ge1 @_ge2) }\n" - "set le? { arg _le1 _le2; ge? @_le2 @_le1 }\n" - "set gt? { arg _gt1 _gt2; lt? @_gt2 @_gt1 }\n" - "set <> { arg _<>1 _<>2; not (= @_<>1 @_<>2) }\n" - "set >= { arg _>=1 _>=2; not (< @_>=1 @_>=2) }\n" - "set <= { arg _<=1 _<=2; >= @_<=2 @_<=1 }\n" - "set > { arg _>1 _>2; < @_>2 @_>1 }\n"; + "set ne? { arg _1 _2; not (eq? @_1 @_2) }\n" + "set ge? { arg _1 _2; not (lt? @_1 @_2) }\n" + "set le? { arg _1 _2; ge? @_2 @_1 }\n" + "set gt? { arg _1 _2; lt? @_2 @_1 }\n" + "set <> { arg _1 _2; not (= @_1 @_2) }\n" + "set >= { arg _1 _2; not (< @_1 @_2) }\n" + "set <= { arg _1 _2; >= @_2 @_1 }\n" + "set > { arg _1 _2; < @_2 @_1 }\n"; static bool init_runtime_library (struct context *ctx) { diff --git a/repl.c b/repl.c index d374d13..42d6bd1 100644 --- a/repl.c +++ b/repl.c @@ -61,7 +61,7 @@ complete (const char *text, int start, int end) { static char *buf[128]; size_t n = 1, len = strlen (text); - for (struct item *item = ctx.variables; item; item = item->next) + for (struct item *item = ctx.globals; item; item = item->next) if (n < 127 && !strncmp (item->head->value, text, len)) buf[n++] = format ("%s", item->head->value); for (struct native_fn *iter = ctx.native; iter; iter = iter->next) -- cgit v1.2.3