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
-------
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 [<name>]...`
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

108
ell.c
View File

@ -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) {

2
repl.c
View File

@ -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)