aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Janouch <p.janouch@gmail.com>2017-05-26 15:13:34 +0200
committerPřemysl Janouch <p.janouch@gmail.com>2017-05-26 15:20:14 +0200
commit8414e07010b2265db3cbbd2854d8dc49acc63ac8 (patch)
treec3e7d969d792082a3cbeec6b74538453238cbaad
parentfa892b99e7e612f2414ea2ff1da1f945da3ecad1 (diff)
downloadell-8414e07010b2265db3cbbd2854d8dc49acc63ac8.tar.gz
ell-8414e07010b2265db3cbbd2854d8dc49acc63ac8.tar.xz
ell-8414e07010b2265db3cbbd2854d8dc49acc63ac8.zip
Implement dynamic scoping
Okay, that was a PITA to not have. But I think I'm set now, feature-wise.
-rw-r--r--README.adoc18
-rw-r--r--ell.c108
-rw-r--r--repl.c2
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 [<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
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)