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:
parent
fa892b99e7
commit
8414e07010
18
README.adoc
18
README.adoc
|
@ -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
108
ell.c
|
@ -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
2
repl.c
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue