diff options
| author | Bdale Garbee <bdale@gag.com> | 2017-12-05 13:23:09 -0700 | 
|---|---|---|
| committer | Bdale Garbee <bdale@gag.com> | 2017-12-05 13:23:09 -0700 | 
| commit | 2f8fce1cf6ce4bd12a836cc8ee15f4edbc95c95e (patch) | |
| tree | 19a1bcb93694eb259f29e9ef9690bb31a9b8fa60 /src/scheme/ao_scheme_eval.c | |
| parent | d314a5654fafa5eac86d8293f1197a2f2c2eac72 (diff) | |
| parent | 185b11367cd85948885fceafb5d46303b6f1356d (diff) | |
Merge branch 'master' of ssh://git.gag.com/scm/git/fw/altos
Diffstat (limited to 'src/scheme/ao_scheme_eval.c')
| -rw-r--r-- | src/scheme/ao_scheme_eval.c | 578 | 
1 files changed, 578 insertions, 0 deletions
| diff --git a/src/scheme/ao_scheme_eval.c b/src/scheme/ao_scheme_eval.c new file mode 100644 index 00000000..9b3cf63e --- /dev/null +++ b/src/scheme/ao_scheme_eval.c @@ -0,0 +1,578 @@ +/* + * Copyright © 2016 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + */ + +#include "ao_scheme.h" +#include <assert.h> + +struct ao_scheme_stack		*ao_scheme_stack; +ao_poly				ao_scheme_v; +uint8_t				ao_scheme_skip_cons_free; + +ao_poly +ao_scheme_set_cond(struct ao_scheme_cons *c) +{ +	ao_scheme_stack->state = eval_cond; +	ao_scheme_stack->sexprs = ao_scheme_cons_poly(c); +	return AO_SCHEME_NIL; +} + +static int +func_type(ao_poly func) +{ +	if (func == AO_SCHEME_NIL) +		return ao_scheme_error(AO_SCHEME_INVALID, "func is nil"); +	switch (ao_scheme_poly_type(func)) { +	case AO_SCHEME_BUILTIN: +		return ao_scheme_poly_builtin(func)->args & AO_SCHEME_FUNC_MASK; +	case AO_SCHEME_LAMBDA: +		return ao_scheme_poly_lambda(func)->args; +	case AO_SCHEME_STACK: +		return AO_SCHEME_FUNC_LAMBDA; +	default: +		ao_scheme_error(AO_SCHEME_INVALID, "not a func"); +		return -1; +	} +} + +/* + * Flattened eval to avoid stack issues + */ + +/* + * Evaluate an s-expression + * + * For a list, evaluate all of the elements and + * then execute the resulting function call. + * + * Each element of the list is evaluated in + * a clean stack context. + * + * The current stack state is set to 'formal' so that + * when the evaluation is complete, the value + * will get appended to the values list. + * + * For other types, compute the value directly. + */ + +static int +ao_scheme_eval_sexpr(void) +{ +	DBGI("sexpr: %v\n", ao_scheme_v); +	switch (ao_scheme_poly_type(ao_scheme_v)) { +	case AO_SCHEME_CONS: +		if (ao_scheme_v == AO_SCHEME_NIL) { +			if (!ao_scheme_stack->values) { +				/* +				 * empty list evaluates to empty list +				 */ +				ao_scheme_v = AO_SCHEME_NIL; +				ao_scheme_stack->state = eval_val; +			} else { +				/* +				 * done with arguments, go execute it +				 */ +				ao_scheme_v = ao_scheme_poly_cons(ao_scheme_stack->values)->car; +				ao_scheme_stack->state = eval_exec; +			} +		} else { +			if (!ao_scheme_stack->values) +				ao_scheme_stack->list = ao_scheme_v; +			/* +			 * Evaluate another argument and then switch +			 * to 'formal' to add the value to the values +			 * list +			 */ +			ao_scheme_stack->sexprs = ao_scheme_v; +			ao_scheme_stack->state = eval_formal; +			if (!ao_scheme_stack_push()) +				return 0; +			/* +			 * push will reset the state to 'sexpr', which +			 * will evaluate the expression +			 */ +			ao_scheme_v = ao_scheme_poly_cons(ao_scheme_v)->car; +		} +		break; +	case AO_SCHEME_ATOM: +		DBGI("..frame "); DBG_POLY(ao_scheme_frame_poly(ao_scheme_frame_current)); DBG("\n"); +		ao_scheme_v = ao_scheme_atom_get(ao_scheme_v); +		/* fall through */ +	case AO_SCHEME_BOOL: +	case AO_SCHEME_INT: +	case AO_SCHEME_BIGINT: +	case AO_SCHEME_FLOAT: +	case AO_SCHEME_STRING: +	case AO_SCHEME_BUILTIN: +	case AO_SCHEME_LAMBDA: +		ao_scheme_stack->state = eval_val; +		break; +	} +	DBGI(".. result "); DBG_POLY(ao_scheme_v); DBG("\n"); +	return 1; +} + +/* + * A value has been computed. + * + * If the value was computed from a macro, + * then we want to reset the current context + * to evaluate the macro result again. + * + * If not a macro, then pop the stack. + * If the stack is empty, we're done. + * Otherwise, the stack will contain + * the next state. + */ + +static int +ao_scheme_eval_val(void) +{ +	DBGI("val: "); DBG_POLY(ao_scheme_v); DBG("\n"); +	/* +	 * Value computed, pop the stack +	 * to figure out what to do with the value +	 */ +	ao_scheme_stack_pop(); +	DBGI("..state %d\n", ao_scheme_stack ? ao_scheme_stack->state : -1); +	return 1; +} + +/* + * A formal has been computed. + * + * If this is the first formal, then check to see if we've got a + * lamda, macro or nlambda. + * + * For lambda, go compute another formal.  This will terminate + * when the sexpr state sees nil. + * + * For macro/nlambda, we're done, so move the sexprs into the values + * and go execute it. + * + * Macros have an additional step of saving a stack frame holding the + * macro value execution context, which then gets the result of the + * macro to run + */ + +static int +ao_scheme_eval_formal(void) +{ +	ao_poly			formal; +	struct ao_scheme_stack	*prev; + +	DBGI("formal: "); DBG_POLY(ao_scheme_v); DBG("\n"); + +	/* Check what kind of function we've got */ +	if (!ao_scheme_stack->values) { +		switch (func_type(ao_scheme_v)) { +		case AO_SCHEME_FUNC_LAMBDA: +			DBGI(".. lambda\n"); +			break; +		case AO_SCHEME_FUNC_MACRO: +			/* Evaluate the result once more */ +			ao_scheme_stack->state = eval_macro; +			if (!ao_scheme_stack_push()) +				return 0; + +			/* After the function returns, take that +			 * value and re-evaluate it +			 */ +			prev = ao_scheme_poly_stack(ao_scheme_stack->prev); +			ao_scheme_stack->sexprs = prev->sexprs; + +			DBGI(".. start macro\n"); +			DBGI("\t.. sexprs       "); DBG_POLY(ao_scheme_stack->sexprs); DBG("\n"); +			DBGI("\t.. values       "); DBG_POLY(ao_scheme_stack->values); DBG("\n"); +			DBG_FRAMES(); + +			/* fall through ... */ +		case AO_SCHEME_FUNC_NLAMBDA: +			DBGI(".. nlambda or macro\n"); + +			/* use the raw sexprs as values */ +			ao_scheme_stack->values = ao_scheme_stack->sexprs; +			ao_scheme_stack->values_tail = AO_SCHEME_NIL; +			ao_scheme_stack->state = eval_exec; + +			/* ready to execute now */ +			return 1; +		case -1: +			return 0; +		} +	} + +	/* Append formal to list of values */ +	formal = ao_scheme__cons(ao_scheme_v, AO_SCHEME_NIL); +	if (!formal) +		return 0; + +	if (ao_scheme_stack->values_tail) +		ao_scheme_poly_cons(ao_scheme_stack->values_tail)->cdr = formal; +	else +		ao_scheme_stack->values = formal; +	ao_scheme_stack->values_tail = formal; + +	DBGI(".. values "); DBG_POLY(ao_scheme_stack->values); DBG("\n"); + +	/* +	 * Step to the next argument, if this is last, then +	 * 'sexpr' will end up switching to 'exec' +	 */ +	ao_scheme_v = ao_scheme_poly_cons(ao_scheme_stack->sexprs)->cdr; + +	ao_scheme_stack->state = eval_sexpr; + +	DBGI(".. "); DBG_POLY(ao_scheme_v); DBG("\n"); +	return 1; +} + +/* + * Start executing a function call + * + * Most builtins are easy, just call the function. + * 'cond' is magic; it sticks the list of clauses + * in 'sexprs' and switches to 'cond' state. That + * bit of magic is done in ao_scheme_set_cond. + * + * Lambdas build a new frame to hold the locals and + * then re-use the current stack context to evaluate + * the s-expression from the lambda. + */ + +static int +ao_scheme_eval_exec(void) +{ +	ao_poly v; +	struct ao_scheme_builtin	*builtin; + +	DBGI("exec: "); DBG_POLY(ao_scheme_v); DBG(" values "); DBG_POLY(ao_scheme_stack->values); DBG ("\n"); +	ao_scheme_stack->sexprs = AO_SCHEME_NIL; +	switch (ao_scheme_poly_type(ao_scheme_v)) { +	case AO_SCHEME_BUILTIN: +		ao_scheme_stack->state = eval_val; +		builtin = ao_scheme_poly_builtin(ao_scheme_v); +		v = ao_scheme_func(builtin) ( +			ao_scheme_poly_cons(ao_scheme_poly_cons(ao_scheme_stack->values)->cdr)); +		DBG_DO(if (!ao_scheme_exception && ao_scheme_poly_builtin(ao_scheme_v)->func == builtin_set) { +				struct ao_scheme_cons *cons = ao_scheme_poly_cons(ao_scheme_stack->values); +				ao_poly atom = ao_scheme_arg(cons, 1); +				ao_poly val = ao_scheme_arg(cons, 2); +				DBGI("set "); DBG_POLY(atom); DBG(" = "); DBG_POLY(val); DBG("\n"); +			}); +		builtin = ao_scheme_poly_builtin(ao_scheme_v); +		if (builtin && (builtin->args & AO_SCHEME_FUNC_FREE_ARGS) && !ao_scheme_stack_marked(ao_scheme_stack) && !ao_scheme_skip_cons_free) { +			struct ao_scheme_cons *cons = ao_scheme_poly_cons(ao_scheme_stack->values); +			ao_scheme_stack->values = AO_SCHEME_NIL; +			ao_scheme_cons_free(cons); +		} + +		ao_scheme_v = v; +		ao_scheme_stack->values = AO_SCHEME_NIL; +		ao_scheme_stack->values_tail = AO_SCHEME_NIL; +		DBGI(".. result "); DBG_POLY(ao_scheme_v); DBG ("\n"); +		DBGI(".. frame "); DBG_POLY(ao_scheme_frame_poly(ao_scheme_frame_current)); DBG("\n"); +		break; +	case AO_SCHEME_LAMBDA: +		DBGI(".. frame "); DBG_POLY(ao_scheme_frame_poly(ao_scheme_frame_current)); DBG("\n"); +		ao_scheme_stack->state = eval_begin; +		v = ao_scheme_lambda_eval(); +		ao_scheme_stack->sexprs = v; +		ao_scheme_stack->values = AO_SCHEME_NIL; +		ao_scheme_stack->values_tail = AO_SCHEME_NIL; +		DBGI(".. sexprs "); DBG_POLY(ao_scheme_stack->sexprs); DBG("\n"); +		DBGI(".. frame "); DBG_POLY(ao_scheme_frame_poly(ao_scheme_frame_current)); DBG("\n"); +		break; +	case AO_SCHEME_STACK: +		DBGI(".. stack "); DBG_POLY(ao_scheme_v); DBG("\n"); +		ao_scheme_v = ao_scheme_stack_eval(); +		DBGI(".. value "); DBG_POLY(ao_scheme_v); DBG("\n"); +		DBGI(".. frame "); DBG_POLY(ao_scheme_frame_poly(ao_scheme_frame_current)); DBG("\n"); +		break; +	} +	ao_scheme_skip_cons_free = 0; +	return 1; +} + +/* + * Finish setting up the apply evaluation + * + * The value is the list to execute + */ +static int +ao_scheme_eval_apply(void) +{ +	struct ao_scheme_cons	*cons = ao_scheme_poly_cons(ao_scheme_v); +	struct ao_scheme_cons	*cdr, *prev; + +	/* Glue the arguments into the right shape. That's all but the last +	 * concatenated onto the last +	 */ +	cdr = cons; +	for (;;) { +		prev = cdr; +		cdr = ao_scheme_poly_cons(prev->cdr); +		if (cdr->cdr == AO_SCHEME_NIL) +			break; +	} +	DBGI("before mangling: "); DBG_POLY(ao_scheme_v); DBG("\n"); +	prev->cdr = cdr->car; +	ao_scheme_stack->values = ao_scheme_v; +	ao_scheme_v = ao_scheme_poly_cons(ao_scheme_stack->values)->car; +	DBGI("apply: "); DBG_POLY(ao_scheme_stack->values); DBG ("\n"); +	ao_scheme_stack->state = eval_exec; +	ao_scheme_skip_cons_free = 1; +	return 1; +} + +/* + * Start evaluating the next cond clause + * + * If the list of clauses is empty, then + * the result of the cond is nil. + * + * Otherwise, set the current stack state to 'cond_test' and create a + * new stack context to evaluate the test s-expression. Once that's + * complete, we'll land in 'cond_test' to finish the clause. + */ +static int +ao_scheme_eval_cond(void) +{ +	DBGI("cond: "); DBG_POLY(ao_scheme_stack->sexprs); DBG("\n"); +	DBGI(".. frame "); DBG_POLY(ao_scheme_frame_poly(ao_scheme_frame_current)); DBG("\n"); +	DBGI(".. saved frame "); DBG_POLY(ao_scheme_stack->frame); DBG("\n"); +	if (!ao_scheme_stack->sexprs) { +		ao_scheme_v = _ao_scheme_bool_false; +		ao_scheme_stack->state = eval_val; +	} else { +		ao_scheme_v = ao_scheme_poly_cons(ao_scheme_stack->sexprs)->car; +		if (!ao_scheme_v || ao_scheme_poly_type(ao_scheme_v) != AO_SCHEME_CONS) { +			ao_scheme_error(AO_SCHEME_INVALID, "invalid cond clause"); +			return 0; +		} +		ao_scheme_v = ao_scheme_poly_cons(ao_scheme_v)->car; +		if (ao_scheme_v == _ao_scheme_atom_else) +			ao_scheme_v = _ao_scheme_bool_true; +		ao_scheme_stack->state = eval_cond_test; +		if (!ao_scheme_stack_push()) +			return 0; +	} +	return 1; +} + +/* + * Finish a cond clause. + * + * Check the value from the test expression, if + * non-nil, then set up to evaluate the value expression. + * + * Otherwise, step to the next clause and go back to the 'cond' + * state + */ +static int +ao_scheme_eval_cond_test(void) +{ +	DBGI("cond_test: "); DBG_POLY(ao_scheme_v); DBG(" sexprs "); DBG_POLY(ao_scheme_stack->sexprs); DBG("\n"); +	DBGI(".. frame "); DBG_POLY(ao_scheme_frame_poly(ao_scheme_frame_current)); DBG("\n"); +	DBGI(".. saved frame "); DBG_POLY(ao_scheme_stack->frame); DBG("\n"); +	if (ao_scheme_v != _ao_scheme_bool_false) { +		struct ao_scheme_cons *car = ao_scheme_poly_cons(ao_scheme_poly_cons(ao_scheme_stack->sexprs)->car); +		ao_poly c = car->cdr; + +		if (c) { +			ao_scheme_stack->state = eval_begin; +			ao_scheme_stack->sexprs = c; +		} else +			ao_scheme_stack->state = eval_val; +	} else { +		ao_scheme_stack->sexprs = ao_scheme_poly_cons(ao_scheme_stack->sexprs)->cdr; +		DBGI("next cond: "); DBG_POLY(ao_scheme_stack->sexprs); DBG("\n"); +		ao_scheme_stack->state = eval_cond; +	} +	return 1; +} + +/* + * Evaluate a list of sexprs, returning the value from the last one. + * + * ao_scheme_begin records the list in stack->sexprs, so we just need to + * walk that list. Set ao_scheme_v to the car of the list and jump to + * eval_sexpr. When that's done, it will land in eval_val. For all but + * the last, leave a stack frame with eval_begin set so that we come + * back here. For the last, don't add a stack frame so that we can + * just continue on. + */ +static int +ao_scheme_eval_begin(void) +{ +	DBGI("begin: "); DBG_POLY(ao_scheme_v); DBG(" sexprs "); DBG_POLY(ao_scheme_stack->sexprs); DBG("\n"); +	DBGI(".. frame "); DBG_POLY(ao_scheme_frame_poly(ao_scheme_frame_current)); DBG("\n"); +	DBGI(".. saved frame "); DBG_POLY(ao_scheme_stack->frame); DBG("\n"); + +	if (!ao_scheme_stack->sexprs) { +		ao_scheme_v = AO_SCHEME_NIL; +		ao_scheme_stack->state = eval_val; +	} else { +		ao_scheme_v = ao_scheme_poly_cons(ao_scheme_stack->sexprs)->car; +		ao_scheme_stack->sexprs = ao_scheme_poly_cons(ao_scheme_stack->sexprs)->cdr; + +		/* If there are more sexprs to do, then come back here, otherwise +		 * return the value of the last one by just landing in eval_sexpr +		 */ +		if (ao_scheme_stack->sexprs) { +			ao_scheme_stack->state = eval_begin; +			if (!ao_scheme_stack_push()) +				return 0; +		} +		ao_scheme_stack->state = eval_sexpr; +	} +	return 1; +} + +/* + * Conditionally execute a list of sexprs while the first is true + */ +static int +ao_scheme_eval_while(void) +{ +	DBGI("while: "); DBG_POLY(ao_scheme_stack->sexprs); DBG("\n"); +	DBGI(".. frame "); DBG_POLY(ao_scheme_frame_poly(ao_scheme_frame_current)); DBG("\n"); +	DBGI(".. saved frame "); DBG_POLY(ao_scheme_stack->frame); DBG("\n"); + +	ao_scheme_stack->values = ao_scheme_v; +	if (!ao_scheme_stack->sexprs) { +		ao_scheme_v = AO_SCHEME_NIL; +		ao_scheme_stack->state = eval_val; +	} else { +		ao_scheme_v = ao_scheme_poly_cons(ao_scheme_stack->sexprs)->car; +		ao_scheme_stack->state = eval_while_test; +		if (!ao_scheme_stack_push()) +			return 0; +	} +	return 1; +} + +/* + * Check the while condition, terminate the loop if nil. Otherwise keep going + */ +static int +ao_scheme_eval_while_test(void) +{ +	DBGI("while_test: "); DBG_POLY(ao_scheme_v); DBG(" sexprs "); DBG_POLY(ao_scheme_stack->sexprs); DBG("\n"); +	DBGI(".. frame "); DBG_POLY(ao_scheme_frame_poly(ao_scheme_frame_current)); DBG("\n"); +	DBGI(".. saved frame "); DBG_POLY(ao_scheme_stack->frame); DBG("\n"); + +	if (ao_scheme_v != _ao_scheme_bool_false) { +		ao_scheme_stack->values = ao_scheme_v; +		ao_scheme_v = ao_scheme_poly_cons(ao_scheme_stack->sexprs)->cdr; +		ao_scheme_stack->state = eval_while; +		if (!ao_scheme_stack_push()) +			return 0; +		ao_scheme_stack->state = eval_begin; +		ao_scheme_stack->sexprs = ao_scheme_v; +	} +	else +	{ +		ao_scheme_stack->state = eval_val; +		ao_scheme_v = ao_scheme_stack->values; +	} +	return 1; +} + +/* + * Replace the original sexpr with the macro expansion, then + * execute that + */ +static int +ao_scheme_eval_macro(void) +{ +	DBGI("macro: "); DBG_POLY(ao_scheme_v); DBG(" sexprs "); DBG_POLY(ao_scheme_stack->sexprs); DBG("\n"); + +	if (ao_scheme_v == AO_SCHEME_NIL) +		ao_scheme_abort(); +	if (ao_scheme_poly_type(ao_scheme_v) == AO_SCHEME_CONS) { +		*ao_scheme_poly_cons(ao_scheme_stack->sexprs) = *ao_scheme_poly_cons(ao_scheme_v); +		ao_scheme_v = ao_scheme_stack->sexprs; +		DBGI("sexprs rewritten to: "); DBG_POLY(ao_scheme_v); DBG("\n"); +	} +	ao_scheme_stack->sexprs = AO_SCHEME_NIL; +	ao_scheme_stack->state = eval_sexpr; +	return 1; +} + +static int (*const evals[])(void) = { +	[eval_sexpr] = ao_scheme_eval_sexpr, +	[eval_val] = ao_scheme_eval_val, +	[eval_formal] = ao_scheme_eval_formal, +	[eval_exec] = ao_scheme_eval_exec, +	[eval_apply] = ao_scheme_eval_apply, +	[eval_cond] = ao_scheme_eval_cond, +	[eval_cond_test] = ao_scheme_eval_cond_test, +	[eval_begin] = ao_scheme_eval_begin, +	[eval_while] = ao_scheme_eval_while, +	[eval_while_test] = ao_scheme_eval_while_test, +	[eval_macro] = ao_scheme_eval_macro, +}; + +const char * const ao_scheme_state_names[] = { +	[eval_sexpr] = "sexpr", +	[eval_val] = "val", +	[eval_formal] = "formal", +	[eval_exec] = "exec", +	[eval_apply] = "apply", +	[eval_cond] = "cond", +	[eval_cond_test] = "cond_test", +	[eval_begin] = "begin", +	[eval_while] = "while", +	[eval_while_test] = "while_test", +	[eval_macro] = "macro", +}; + +/* + * Called at restore time to reset all execution state + */ + +void +ao_scheme_eval_clear_globals(void) +{ +	ao_scheme_stack = NULL; +	ao_scheme_frame_current = NULL; +	ao_scheme_v = AO_SCHEME_NIL; +} + +int +ao_scheme_eval_restart(void) +{ +	return ao_scheme_stack_push(); +} + +ao_poly +ao_scheme_eval(ao_poly _v) +{ +	ao_scheme_v = _v; + +	ao_scheme_frame_init(); + +	if (!ao_scheme_stack_push()) +		return AO_SCHEME_NIL; + +	while (ao_scheme_stack) { +		if (!(*evals[ao_scheme_stack->state])() || ao_scheme_exception) { +			ao_scheme_stack_clear(); +			return AO_SCHEME_NIL; +		} +	} +	DBG_DO(if (ao_scheme_frame_current) {DBGI("frame left as "); DBG_POLY(ao_scheme_frame_poly(ao_scheme_frame_current)); DBG("\n");}); +	ao_scheme_frame_current = NULL; +	return ao_scheme_v; +} | 
