Implement dynamic scoping

Okay, that was a PITA to not have.

But I think I'm set now, feature-wise.
This commit is contained in:
Přemysl Eric Janouch 2017-05-26 15:13:34 +02:00
parent fa892b99e7
commit 8414e07010
Signed by: p
GPG Key ID: B715679E3A361BE6
3 changed files with 81 additions and 47 deletions

View File

@ -51,10 +51,9 @@ For a slightly more realistic example have a look at 'greet.ell'.
Runtime Runtime
------- -------
All variables are put in a single global namespace with no further scoping. Variables use per-block dynamic scoping. Arguments to a block (which is a list
Arguments to a block (which is a list of lists) must be assigned to variables of lists) must be assigned to variables first using the `arg` special form, and
first using the `arg` special form, and that must happen before they get that must happen before they get overriden by execution of a different block.
overriden by execution of a different block.
When evaluating a command, the first argument is typically a string with its 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. name and it is resolved as if `set` was called on it.
@ -69,8 +68,9 @@ Returns the arguments without any evaluation.
`arg [<name>]...` `arg [<name>]...`
Assigns arguments to the current call in order to given names. Assigns arguments to the current block in order to given names. Names for which
Names for which there are no values left are set to `[]`. there are no values left default to `[]`. This form can effectively be used to
declare local variables.
Standard library Standard library
---------------- ----------------
@ -167,12 +167,6 @@ Install development packages for GNU Readline to get a REPL for toying around:
Possible Ways of Complicating 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 * reference counting: currently all values are always copied as needed, which
is good enough for all imaginable use cases, simpler and less error-prone is good enough for all imaginable use cases, simpler and less error-prone

108
ell.c
View File

@ -638,7 +638,8 @@ parser_run (struct parser *self, const char **e) {
// --- Runtime ----------------------------------------------------------------- // --- Runtime -----------------------------------------------------------------
struct context { 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 native_fn *native; ///< Maps strings to C functions
struct item *arguments; ///< Arguments to last executed block struct item *arguments; ///< Arguments to last executed block
@ -667,7 +668,8 @@ context_free (struct context *ctx) {
next = iter->next; next = iter->next;
free (iter); free (iter);
} }
item_free_list (ctx->variables); item_free_list (ctx->globals);
item_free_list (ctx->scopes);
item_free_list (ctx->arguments); item_free_list (ctx->arguments);
free (ctx->error); free (ctx->error);
} }
@ -677,27 +679,17 @@ check (struct context *ctx, struct item *item) {
return !(ctx->memory_failure |= !item); return !(ctx->memory_failure |= !item);
} }
static struct item * static struct item **
get (struct context *ctx, const char *name) { scope_find (struct item **scope, const char *name) {
for (struct item *iter = ctx->variables; iter; iter = iter->next) for (; *scope; scope = &(*scope)->next)
if (!strcmp (iter->head->value, name)) if (!strcmp ((*scope)->head->value, name))
return iter->head->next; return scope;
return NULL; return NULL;
} }
static bool static bool
set (struct context *ctx, const char *name, struct item *value) { scope_prepend (struct context *ctx, struct item **scope,
struct item **p; const char *name, struct item *value) {
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;
struct item *key, *pair; struct item *key, *pair;
if (!check (ctx, (key = new_string (name, strlen (name)))) if (!check (ctx, (key = new_string (name, strlen (name))))
|| !check (ctx, (pair = new_list (key)))) { || !check (ctx, (pair = new_list (key)))) {
@ -705,11 +697,43 @@ set (struct context *ctx, const char *name, struct item *value) {
return false; return false;
} }
key->next = value; key->next = value;
pair->next = ctx->variables; pair->next = *scope;
ctx->variables = pair; *scope = pair;
return true; 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 * static struct native_fn *
@ -757,6 +781,10 @@ can_modify_error (struct context *ctx) {
static bool static bool
assign_arguments (struct context *ctx, struct item *names) { 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; struct item *arg = ctx->arguments;
for (; names; names = names->next) { for (; names; names = names->next) {
if (names->type != ITEM_STRING) if (names->type != ITEM_STRING)
@ -765,7 +793,8 @@ assign_arguments (struct context *ctx, struct item *names) {
struct item *value = NULL; struct item *value = NULL;
if (arg && !check (ctx, (value = new_clone (arg)))) if (arg && !check (ctx, (value = new_clone (arg))))
return false; 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; return false;
if (arg) if (arg)
arg = arg->next; arg = arg->next;
@ -893,16 +922,27 @@ execute_statement
return false; return false;
} }
// Execute a block and return whatever the last statement returned /// Execute a block and return whatever the last statement returned
static bool static bool
execute (struct context *ctx, struct item *body, struct item **result) { 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) { for (; body; body = body->next) {
item_free_list (*result); item_free_list (*result);
*result = NULL; *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 --------------------------------------------------------- // --- Runtime library ---------------------------------------------------------
@ -1269,14 +1309,14 @@ const char init_program[] =
"set break { throw _break }\n" "set break { throw _break }\n"
// TODO: we should be able to apply them to all arguments // TODO: we should be able to apply them to all arguments
"set ne? { arg _ne1 _ne2; not (eq? @_ne1 @_ne2) }\n" "set ne? { arg _1 _2; not (eq? @_1 @_2) }\n"
"set ge? { arg _ge1 _ge2; not (lt? @_ge1 @_ge2) }\n" "set ge? { arg _1 _2; not (lt? @_1 @_2) }\n"
"set le? { arg _le1 _le2; ge? @_le2 @_le1 }\n" "set le? { arg _1 _2; ge? @_2 @_1 }\n"
"set gt? { arg _gt1 _gt2; lt? @_gt2 @_gt1 }\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; 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 > { arg _>1 _>2; < @_>2 @_>1 }\n"; "set > { arg _1 _2; < @_2 @_1 }\n";
static bool static bool
init_runtime_library (struct context *ctx) { init_runtime_library (struct context *ctx) {

2
repl.c
View File

@ -61,7 +61,7 @@ complete (const char *text, int start, int end) {
static char *buf[128]; static char *buf[128];
size_t n = 1, len = strlen (text); 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)) if (n < 127 && !strncmp (item->head->value, text, len))
buf[n++] = format ("%s", item->head->value); buf[n++] = format ("%s", item->head->value);
for (struct native_fn *iter = ctx.native; iter; iter = iter->next) for (struct native_fn *iter = ctx.native; iter; iter = iter->next)