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:
parent
8414e07010
commit
3e68a09ae1
18
README.adoc
18
README.adoc
@ -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
166
ell.c
@ -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);
|
||||
|
12
greet.ell
12
greet.ell
@ -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 }
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user