Remove suck

struct context::arguments stank, the "arg" special form stank.

The amount of lines this adds can be counted on one hand.
This commit is contained in:
Přemysl Eric Janouch 2017-05-26 19:44:03 +02:00
parent 8414e07010
commit 3e68a09ae1
Signed by: p
GPG Key ID: B715679E3A361BE6
5 changed files with 99 additions and 102 deletions

View File

@ -52,8 +52,8 @@ For a slightly more realistic example have a look at 'greet.ell'.
Runtime
-------
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.
of lists) are assigned to local variables named `1`, `2`, etc., and the full
list of them is stored in `*`.
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.
@ -64,19 +64,18 @@ Special Forms
-------------
`quote [<arg>]...`
Returns the arguments without any evaluation.
`arg [<name>]...`
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.
Like `values` but returns the arguments without any evaluation.
Standard library
----------------
The standard library interprets the empty list and the empty string as false
values, everything else is taken as true.
`local <names> [<value>]...`
Create local variables in the current block. Names for which there are no
values left default to `()`.
`set <name> [<value>]`
Retrieve or set a named variable. The syntax sugar for retrieval is `@`.
@ -167,6 +166,7 @@ Install development packages for GNU Readline to get a REPL for toying around:
Possible Ways of Complicating
-----------------------------
* `local [_a _b _rest] @*` would elegantly solve the problem of varargs
* reference counting: currently all values are always copied as needed, which
is good enough for all imaginable use cases, simpler and less error-prone

166
ell.c
View File

@ -641,7 +641,6 @@ struct context {
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
char *error; ///< Error information
bool memory_failure; ///< Memory allocation failure
@ -670,7 +669,6 @@ context_free (struct context *ctx) {
}
item_free_list (ctx->globals);
item_free_list (ctx->scopes);
item_free_list (ctx->arguments);
free (ctx->error);
}
@ -779,34 +777,12 @@ can_modify_error (struct context *ctx) {
return !ctx->memory_failure && ctx->error[0] != '_';
}
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)
return set_error (ctx, "argument names must be strings");
struct item *value = NULL;
if (arg && !check (ctx, (value = new_clone (arg))))
return false;
// 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;
}
return true;
}
static bool execute_statement (struct context *, struct item *, struct item **);
static bool execute (struct context *ctx, struct item *body, struct item **);
static bool execute_block (struct context *,
struct item *, struct item *, struct item **);
static bool
execute_args (struct context *ctx, struct item *args) {
execute_args (struct context *ctx, struct item *args, struct item **result) {
size_t i = 0;
struct item *res = NULL, **out = &res;
for (; args; args = args->next) {
@ -820,8 +796,7 @@ execute_args (struct context *ctx, struct item *args) {
out = &(*out = evaluated)->next;
i++;
}
item_free_list (ctx->arguments);
ctx->arguments = res;
*result = res;
return true;
error:
@ -842,14 +817,13 @@ execute_native (struct context *ctx, const char *name, struct item *args,
struct native_fn *fn = native_find (ctx, name);
if (!fn)
return set_error (ctx, "unknown function");
if (!execute_args (ctx, args))
struct item *arguments = NULL;
if (!execute_args (ctx, args, &arguments))
return false;
// "ctx->arguments" is for assign_arguments() only
args = ctx->arguments;
ctx->arguments = NULL;
bool ok = fn->handler (ctx, args, result);
item_free_list (args);
bool ok = fn->handler (ctx, arguments, result);
item_free_list (arguments);
return ok;
}
@ -859,8 +833,9 @@ execute_resolved (struct context *ctx, struct item *body, struct item *args,
// Resolving names ecursively could be pretty fatal, let's not do that
if (body->type == ITEM_STRING)
return check (ctx, (*result = new_clone (body)));
return execute_args (ctx, args)
&& execute (ctx, body->head, result);
struct item *arguments = NULL;
return execute_args (ctx, args, &arguments)
&& execute_block (ctx, body->head, arguments, result);
}
static bool
@ -868,11 +843,8 @@ execute_item (struct context *ctx, struct item *body, struct item **result) {
struct item *args = body->next;
if (body->type == ITEM_STRING) {
const char *name = body->value;
// These could be just regular handlers, only top priority
if (!strcmp (name, "quote"))
return !args || check (ctx, (*result = new_clone_list (args)));
if (!strcmp (name, "arg"))
return assign_arguments (ctx, args);
if ((body = get (ctx, name)))
return execute_resolved (ctx, body, args, result);
return execute_native (ctx, name, args, result);
@ -922,13 +894,34 @@ execute_statement
return false;
}
/// 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))))
args_to_scope (struct context *ctx, struct item *args, struct item **scope) {
if (!check (ctx, (args = new_list (args)))
|| !scope_prepend (ctx, scope, "*", args))
return false;
size_t i = 0;
for (args = args->head; args; args = args->next) {
char buf[16] = "";
(void) snprintf (buf, sizeof buf, "%zu", ++i);
struct item *copy = NULL;
if ((args && !check (ctx, (copy = new_clone (args))))
|| !scope_prepend (ctx, scope, buf, copy))
return false;
}
return check (ctx, (*scope = new_list (*scope)));
}
/// Execute a block and return whatever the last statement returned, eats args
static bool
execute_block (struct context *ctx, struct item *body, struct item *args,
struct item **result) {
struct item *scope = NULL;
if (!args_to_scope (ctx, args, &scope)) {
item_free_list (scope);
return false;
}
scope->next = ctx->scopes;
ctx->scopes = scope;
@ -951,20 +944,13 @@ execute (struct context *ctx, struct item *body, struct item **result) {
(struct context *ctx, struct item *args, struct item **result)
static bool
set_single_argument (struct context *ctx, struct item *item) {
struct item *single;
if (!check (ctx, (single = new_clone (item))))
return false;
item_free_list (ctx->arguments);
ctx->arguments = single;
return true;
}
static bool
execute_any (struct context *ctx, struct item *body, struct item **result) {
execute_any (struct context *ctx, struct item *body, struct item *arg,
struct item **result) {
if (body->type == ITEM_STRING)
return check (ctx, (*result = new_clone (body)));
return execute (ctx, body->head, result);
if (arg && !check (ctx, (arg = new_clone (arg))))
return false;
return execute_block (ctx, body->head, arg, result);
}
static struct item *
@ -993,6 +979,27 @@ static struct item * new_boolean (bool b) { return new_string ("1", b); }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
defn (fn_local) {
struct item *names = args;
if (!names || names->type != ITEM_LIST)
return set_error (ctx, "first argument must be a list");
// Duplicates or non-strings don't really matter to us, user's problem
struct item **scope = &ctx->scopes->head;
(void) result;
struct item *values = names->next;
for (names = names->head; names; names = names->next) {
struct item *value = NULL;
if ((values && !check (ctx, (value = new_clone (values))))
|| !scope_prepend (ctx, scope, names->value, value))
return false;
if (values)
values = values->next;
}
return true;
}
defn (fn_set) {
struct item *name = args;
if (!name || name->type != ITEM_STRING)
@ -1030,12 +1037,12 @@ defn (fn_if) {
return set_error (ctx, "missing body");
struct item *res = NULL;
if (!execute_any (ctx, cond, &res))
if (!execute_any (ctx, cond, NULL, &res))
return false;
bool match = truthy (res);
item_free_list (res);
if (match)
return execute_any (ctx, body, result);
return execute_any (ctx, body, NULL, result);
if (!(keyword = body->next))
break;
@ -1045,7 +1052,7 @@ defn (fn_if) {
if (!strcmp (keyword->value, "else")) {
if (!(body = keyword->next))
return set_error (ctx, "missing body");
return execute_any (ctx, body, result);
return execute_any (ctx, body, NULL, result);
}
if (strcmp (keyword->value, "elif"))
return set_error (ctx, "invalid keyword: %s", keyword->value);
@ -1062,8 +1069,7 @@ defn (fn_map) {
struct item *res = NULL, **out = &res;
for (struct item *v = values->head; v; v = v->next) {
if (!set_single_argument (ctx, v)
|| !execute_any (ctx, body, out)) {
if (!execute_any (ctx, body, v, out)) {
item_free_list (res);
return false;
}
@ -1129,7 +1135,7 @@ defn (fn_try) {
return set_error (ctx, "first argument must be a function");
if (!(handler = body->next))
return set_error (ctx, "second argument must be a function");
if (execute_any (ctx, body, result))
if (execute_any (ctx, body, NULL, result))
return true;
struct item *message;
@ -1140,8 +1146,7 @@ defn (fn_try) {
free (ctx->error); ctx->error = NULL;
item_free_list (*result); *result = NULL;
bool ok = set_single_argument (ctx, message)
&& execute_any (ctx, handler, result);
bool ok = execute_any (ctx, handler, message, result);
item_free (message);
return ok;
}
@ -1217,7 +1222,7 @@ defn (fn_and) {
item_free_list (*result);
*result = NULL;
if (!execute_any (ctx, args, result))
if (!execute_any (ctx, args, NULL, result))
return false;
if (!truthy (*result))
return check (ctx, (*result = new_boolean (false)));
@ -1227,7 +1232,7 @@ defn (fn_and) {
defn (fn_or) {
for (; args; args = args->next) {
if (!execute_any (ctx, args, result))
if (!execute_any (ctx, args, NULL, result))
return false;
if (truthy (*result))
return true;
@ -1300,27 +1305,24 @@ defn (fn_less) {
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const char init_program[] =
"set unless { arg _cond _body; if (not (@_cond)) @_body }\n"
"set filter { arg _body _list\n"
" map { arg _i; if (@_body @_i) { @_i } } @_list }\n"
"set for { arg _list _body\n"
" try { map { arg _i; @_body @_i } @_list } {\n"
" arg _e; if (ne? @_e _break) { throw @e } } }\n"
"set unless { if (not (@1)) @2 }\n"
"set filter { local [_body _list] @1 @2\n"
" map { if (@_body @1) { @1 } } @_list }\n"
"set for { local [_list _body] @1 @2\n"
" try { map { @_body @1 } @_list } {\n"
" if (ne? @1 _break) { throw @1 } } }\n"
"set break { throw _break }\n"
// TODO: we should be able to apply them to all arguments
"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";
"set ne? { not (eq? @1 @2) }\n" "set le? { ge? @2 @1 }\n"
"set ge? { not (lt? @1 @2) }\n" "set gt? { lt? @2 @1 }\n"
"set <> { not (= @1 @2) }\n" "set <= { >= @2 @1 }\n"
"set >= { not (< @1 @2) }\n" "set > { < @2 @1 }\n";
static bool
init_runtime_library (struct context *ctx) {
if (!native_register (ctx, "set", fn_set)
if (!native_register (ctx, "local", fn_local)
|| !native_register (ctx, "set", fn_set)
|| !native_register (ctx, "list", fn_list)
|| !native_register (ctx, "values", fn_values)
|| !native_register (ctx, "if", fn_if)
@ -1350,7 +1352,7 @@ init_runtime_library (struct context *ctx) {
const char *e = NULL;
struct item *result = NULL;
struct item *program = parser_run (&parser, &e);
bool ok = !e && execute (ctx, program, &result);
bool ok = !e && execute_block (ctx, program, NULL, &result);
parser_free (&parser);
item_free_list (program);
item_free_list (result);

View File

@ -1,19 +1,13 @@
set greet {
arg _name
print 'hello ' @_name '\n'
}
set decr {
arg _name
set @_name (- @@_name 1)
set @1 (- @@1 1)
}
set limit 2
for (map { arg _x; .. @_x ! } [
for (map { .. @1 ! } [
world
creator
'darkness, my old friend'
]) {
arg _whom
greet @_whom
{ print 'hello ' @1 '\n' } @1
if (= 0 (decr limit)) { break }
}

View File

@ -51,7 +51,8 @@ main (int argc, char *argv[]) {
printf ("%s\n", "runtime library initialization failed");
struct item *result = NULL;
(void) execute (&ctx, program, &result);
// TODO: pass argv as the list of arguments
(void) execute_block (&ctx, program, NULL, &result);
item_free_list (result);
item_free_list (program);

2
repl.c
View File

@ -24,7 +24,7 @@
static void
run (struct context *ctx, struct item *program) {
struct item *result = NULL;
(void) execute (ctx, program, &result);
(void) execute_block (ctx, program, NULL, &result);
item_free_list (program);
const char *failure = ctx->error;