
Constant MAX_LIST_ARGS 4;

Array list_args --> MAX_LIST_ARGS;
Global num_list_args;

Array aname_quote string "quote";
Array aname_internal_dict string "internal-dict";
Array aname_error string "error";
Array aname_nil string "nil";
Array aname_t string "t";
Array aname_s string "s";
Array aname_s2 string "s2";
Array aname_s3 string "s3";
Array aname_not string "not";
Array aname_eqvp string "eqv?";
Array aname_equalp string "equal?";
Array aname_nullp string "null?";
Array aname_listp string "list?";
Array aname_eqnum string "=";
Array aname_gt string ">";
Array aname_lt string "<";
Array aname_gte string ">=";
Array aname_lte string "<=";
Array aname_plus string "+";
Array aname_minus string "-";
Array aname_car string "car";
Array aname_cdr string "cdr";
Array aname_cons string "cons";
Array aname_length string "length";
Array aname_cond string "cond";
Array aname_lambda string "lambda";
Array aname_define string "define";
Array aname_let string "let";
Array aname_letstar string "let*";
Array aname_letrec string "letrec";
Array aname_list string "list";
Array aname_eval string "eval";

Global atom_quote;
Global atom_t;
Global atom_s;
Global atom_s2;
Global atom_s3;

! --- startup code

[ make_initial_stuff
	dict atm ix;
	
	dict = 0;
	top_level_env = 0;
	
	atm = string_to_atom(aname_quote);
	if (atm == tok_Error)
		return tok_Error;
	atom_quote = atm;
	dict = alloc_cons(atm, dict);

	atm = string_to_atom(aname_s);
	if (atm == tok_Error)
		return tok_Error;
	atom_s = atm;
	dict = alloc_cons(atm, dict);
	
	atm = string_to_atom(aname_s2);
	if (atm == tok_Error)
		return tok_Error;
	atom_s2 = atm;
	dict = alloc_cons(atm, dict);
	
	atm = string_to_atom(aname_s3);
	if (atm == tok_Error)
		return tok_Error;
	atom_s3 = atm;
	dict = alloc_cons(atm, dict);
	
	if (dict == tok_Error)
		return tok_Error;

	! finished with dictionary. Now add it, and other stuff, to the top_level_env.
	
	atm = string_to_atom(aname_internal_dict);
	if (atm == tok_Error)
		return tok_Error;
	ix = alloc_cons(atm, dict);
	top_level_env = alloc_cons(ix, top_level_env);
	if (top_level_env == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Form, aname_error, -1, #r$fn_error) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Function, aname_plus, -1, #r$fn_plus) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Function, aname_minus, -1, #r$fn_minus) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Function, aname_gt, -1, #r$fn_gt) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Function, aname_lt, -1, #r$fn_lt) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Function, aname_gte, -1, #r$fn_gte) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Function, aname_lte, -1, #r$fn_lte) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Function, aname_eqnum, -1, #r$fn_eqnum) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Function, aname_eqvp, 2, #r$fn_eqvp) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Function, aname_equalp, 2, #r$fn_equalp) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Function, aname_not, 1, #r$fn_not) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Function, aname_nullp, 1, #r$fn_nullp) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Function, aname_listp, 1, #r$fn_listp) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Function, aname_list, -1, #r$fn_list) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Function, aname_length, 1, #r$fn_length) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Form, aname_cond, -1, #r$fn_cond) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Function, aname_eval, 1, #r$fn_eval) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Form, aname_define, 2, #r$fn_define) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Form, aname_let, 2, #r$fn_let) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Form, aname_letrec, 2, #r$fn_letrec) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Form, aname_letstar, 2, #r$fn_letstar) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Form, aname_lambda, 2, #r$fn_lambda) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Function, aname_car, 1, #r$fn_car) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Function, aname_cdr, 1, #r$fn_cdr) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Function, aname_cons, 2, #r$fn_cons) == tok_Error)
		return tok_Error;
	
	if (build_function(bt_Form, aname_quote, 1, #r$fn_quote) == tok_Error)
		return tok_Error;
	
	atm = string_to_atom(aname_nil);
	if (atm == tok_Error)
		return tok_Error;
	ix = alloc_cons(atm, 0);
	top_level_env = alloc_cons(ix, top_level_env);
	if (top_level_env == tok_Error)
		return tok_Error;
	
	atm = string_to_atom(aname_t);
	if (atm == tok_Error)
		return tok_Error;
	ix = alloc_cons(atm, atm);
	top_level_env = alloc_cons(ix, top_level_env);
	if (top_level_env == tok_Error)
		return tok_Error;
	atom_t = atm;
	
	return 0;
];

[ build_function funcform fname args fptr
	atm ix val;
	atm = string_to_atom(fname);
	if (atm == tok_Error)
		return tok_Error;
	val = alloc_node(funcform, 0, 
		alloc_node(bt_Builtin, num_to_atom(args), num_to_atom(fptr)));
	if (val == tok_Error)
		return tok_Error;
	ix = alloc_cons(atm, val);
	ix = alloc_cons(ix, top_level_env);
	if (ix == tok_Error)
		return tok_Error;
	top_level_env = ix;
	return 0;
];

! --- the built-in functions and forms. Note that the supplied arguments
!       will never be tok_Error, and there will be the right number of them.

[ fn_debug 
	ix;
	print "[debug got ", num_list_args, " args:^";
	for (ix=0 : ix < num_list_args : ix++) {
		print "  ", ix, ": ";
		write_obj(list_args-->ix);
		new_line;
	}
	print "]^";
	return 0;
];

[ fn_quote;
	return list_args-->0;
];

[ fn_list;
	return list_args-->0;
];

[ fn_lambda env
	v;
	v = list_args-->0;
	if (v ~= 0 && v->0 ~= bt_Cons && v->0 ~= bt_Atom) {
		show_error("lambda: bad argument template", v, 1);
		return tok_Error;
	}
	v = alloc_node(bt_Dynamic, v, list_args-->1);
	if (v == tok_Error)
		return tok_Error;
	v = alloc_node(bt_Function, env, v);
	if (v == tok_Error)
		return tok_Error;
	return v;
];

[ fn_define env
	envp namat def s;
	namat = list_args-->0;
	def = list_args-->1;
	if (namat == 0 || namat->0 ~= bt_Atom) {
		show_error("define: first argument is not an atom", namat, 1);
		return tok_Error;
	}
	def = eval_obj(def, env);
	if (def == tok_Error)
		return tok_Error;
	envp = top_level_env;
	for ( : envp ~= 0 : envp = envp-->2) {
		s = envp-->1;
		if ((s-->1)-->1 == namat-->1) {
			break;
		}
	}
	if (envp == 0) {
		! didn't find the atom; add it to top_level_env
		s = alloc_cons(namat, def);
		s = alloc_cons(s, top_level_env);
		if (s == tok_Error)
			return tok_Error;
		top_level_env = s;
	}
	else {
		! found it; it's s.
		s-->2 = def;
	}
	return def;
];

[ fn_eval env
	s;
	s = list_args-->0;
	s = eval_obj(s, env);
	return s;
];

[ fn_let env
	defs expr s atm adef newenv;
	defs = list_args-->0;
	expr = list_args-->1;
	if (defs ~= 0 && defs->0 ~= bt_Cons) {
		show_error("let: first argument is not a list of lists", defs, 1);
		return tok_Error;
	}
	newenv = env;
	for ( : defs~=0 : defs=defs-->2 ) {
		s = defs-->1;
		if (s ~= 0 && s->0 ~= bt_Cons) {
			show_error("let: binding is not a list", s, 1);
			return tok_Error;
		}
		atm = s-->1;
		if (atm == 0 || atm->0 ~= bt_Atom) {
			show_error("let: binding must start with an atom", s, 1);
			return tok_Error;
		}
		adef = s-->2;
		if (adef == 0 || adef->0 ~= bt_Cons) {
			show_error("let: binding must contain a definition", s, 1);
			return tok_Error;
		}
		adef = adef-->1;
		adef = eval_obj(adef, env);
		if (adef == tok_Error)
			return tok_Error;
		newenv = alloc_cons(alloc_cons(atm, adef), newenv);
		if (newenv == tok_Error)
			return tok_Error;
	}
	s = eval_obj(expr, newenv);
	return s;
];

[ fn_letrec env
	origdefs defs expr s atm adef newenv tmpenv;
	origdefs = list_args-->0;
	expr = list_args-->1;
	if (origdefs ~= 0 && origdefs->0 ~= bt_Cons) {
		show_error("letrec: first argument is not a list of lists", origdefs, 1);
		return tok_Error;
	}
	newenv = env;
	for ( defs = origdefs : defs~=0 : defs=defs-->2 ) {
		s = defs-->1;
		if (s ~= 0 && s->0 ~= bt_Cons) {
			show_error("letrec: binding is not a list", s, 1);
			return tok_Error;
		}
		atm = s-->1;
		if (atm == 0 || atm->0 ~= bt_Atom) {
			show_error("letrec: binding must start with an atom", s, 1);
			return tok_Error;
		}
		adef = 0;
		newenv = alloc_cons(alloc_cons(atm, adef), newenv);
		if (newenv == tok_Error)
			return tok_Error;
	}
	tmpenv = newenv;
	for ( defs = origdefs : defs~=0 : defs=defs-->2 ) {
		s = defs-->1;
		atm = s-->1;
		adef = s-->2;
		if (adef == 0 || adef->0 ~= bt_Cons) {
			show_error("letrec: binding must contain a definition", s, 1);
			return tok_Error;
		}
		adef = adef-->1;
		adef = eval_obj(adef, newenv);
		if (adef == tok_Error)
			return tok_Error;
		(tmpenv-->1)-->1 = atm;
		(tmpenv-->1)-->2 = adef;
		tmpenv = tmpenv-->2;
	}
	s = eval_obj(expr, newenv);
	return s;
];

[ fn_letstar env
	defs expr s atm adef;
	defs = list_args-->0;
	expr = list_args-->1;
	if (defs ~= 0 && defs->0 ~= bt_Cons) {
		show_error("let: first argument is not a list of lists", defs, 1);
		return tok_Error;
	}
	for ( : defs~=0 : defs=defs-->2 ) {
		s = defs-->1;
		if (s ~= 0 && s->0 ~= bt_Cons) {
			show_error("let: binding is not a list", s, 1);
			return tok_Error;
		}
		atm = s-->1;
		if (atm == 0 || atm->0 ~= bt_Atom) {
			show_error("let: binding must start with an atom", s, 1);
			return tok_Error;
		}
		adef = s-->2;
		if (adef == 0 || adef->0 ~= bt_Cons) {
			show_error("let: binding must contain a definition", s, 1);
			return tok_Error;
		}
		adef = adef-->1;
		adef = eval_obj(adef, env);
		if (adef == tok_Error)
			return tok_Error;
		env = alloc_cons(alloc_cons(atm, adef), env);
		if (env == tok_Error)
			return tok_Error;
	}
	s = eval_obj(expr, env);
	return s;
];

[ fn_length
	s len;
	len = 0;
	for (s = list_args-->0 : s ~= 0 : s = s-->2, len++ ) {
		if (s->0 ~= bt_Cons) {
			show_error("length: not a proper list", list_args-->0, 1);
			return tok_Error;
		}
	}
	return num_to_atom(len);
];

[ fn_cons;
	return alloc_cons(list_args-->0, list_args-->1);
];

[ fn_car
	s;
	s = list_args-->0;
	if (s == 0 || s->0 ~= bt_Cons) {
		show_error("car: bad argument", s, 1);
		return tok_Error;
	}
	return (s-->1);
];

[ fn_cdr
	s;
	s = list_args-->0;
	if (s == 0 || s->0 ~= bt_Cons) {
		show_error("cdr: bad argument", s, 1);
		return tok_Error;
	}
	return (s-->2);
];

[ fn_not
	;
	if (is_true(list_args-->0) ~= 0)
		return 0;
	else
		return atom_t;
];

[ fn_nullp
	;
	if (list_args-->0 ~= 0)
		return 0;
	else
		return atom_t;
];

[ fn_listp
	s;
	s = list_args-->0;
	if (s == 0)
		return atom_t;
	if (s->0 == bt_Cons)
		return atom_t;
	return 0;
];

[ fn_cond env
	s cl tex cle;
	s = list_args-->0;
	for ( : s ~= 0 : s = s-->2) {
		if (s->0 ~= bt_Cons) {
			show_error("cond: argument is not a list", s, 1);
			return tok_Error;
		}
		cl = s-->1;
		if (cl->0 ~= bt_Cons) {
			show_error("cond: clause is not a list", cl, 1);
			return tok_Error;
		}
		tex = cl-->1;
		tex = eval_obj(tex, env);
		if (tex == tok_Error)
			return tok_Error;
		if (is_true(tex) ~= 0) {
			cle = cl-->2;
			if (cle == 0) 
				return tex;
			if (cle->0 ~= bt_Cons) {
				show_error("cond: clause does not end in an expression", cl, 1);
				return tok_Error;
			}
			tex = eval_obj(cle-->1, env);
			return tex;
		}
	}
	return 0;
];

[ fn_eqvp
	s1 s2;
	s1 = list_args-->0;
	s2 = list_args-->1;
	if (s1 == s2)
		return atom_t;
	if (s1 == 0 || s2 == 0)
		return 0;
	if (s1->0 ~= s2->0)
		return 0;
	switch (s1->0) {
		bt_Atom, bt_Num:
			if (s1-->1 == s2-->1)
				return atom_t;
			return 0;
		bt_Cons:
			return 0;
		default:
			return 0;
	}
];

[ fn_equalp
	;
	return is_equalp(list_args-->0, list_args-->1);
];

[ is_equalp s1 s2;
	if (s1 == s2)
		return atom_t;
	if (s1 == 0 || s2 == 0)
		return 0;
	if (s1->0 ~= s2->0)
		return 0;
	switch (s1->0) {
		bt_Atom, bt_Num:
			if (s1-->1 == s2-->1)
				return atom_t;
			return 0;
		bt_Cons:
			if (is_equalp(s1-->1, s2-->1) == 0)
				return 0;
			if (is_equalp(s1-->2, s2-->2) == 0)
				return 0;
			return atom_t;
		default:
			return 0;
	}
];

[ fn_gt;
	return fn_numcompare(aname_gt);
];

[ fn_lt;
	return fn_numcompare(aname_lt);
];

[ fn_gte;
	return fn_numcompare(aname_gte);
];

[ fn_lte;
	return fn_numcompare(aname_lte);
];

[ fn_eqnum;
	return fn_numcompare(aname_eqnum);
];

[ fn_numcompare op
	s v cur;
	s = list_args-->0;
	if (s == 0) {
		show_error("numeric compare: must have at least one argument");
		return tok_Error;
	}
	v = s-->1;
	if (v == 0 || v->0 ~= bt_Num) {
		show_error("numeric compare: non-numeric argument", v, 1);
		return tok_Error;
	}
	cur = v-->1;
	for ( s = s-->2 : s ~= 0 : s = s-->2 ) {
		v = s-->1;
		if (v == 0 || v->0 ~= bt_Num) {
			show_error("numeric compare: non-numeric argument", v, 1);
			return tok_Error;
		}
		switch (op) {
			aname_gt:
				if (cur <= v-->1) return 0;
			aname_lt:
				if (cur >= v-->1) return 0;
			aname_gte:
				if (cur < v-->1) return 0;
			aname_lte:
				if (cur > v-->1) return 0;
			aname_eqnum:
				if (cur ~= v-->1) return 0;
		}
		cur = v-->1;
	}
	return atom_t;
];

[ fn_plus
	sum ptr v;
	sum = 0;
	for ( ptr = list_args-->0 : ptr~=0 : ptr=ptr-->2 ) {
		v = ptr-->1;
		if (v == 0 || v->0 ~= bt_Num) {
			show_error("+: non-numeric argument", v, 1);
			return tok_Error;
		}
		sum = sum + v-->1;
	}
	return num_to_atom(sum);
];

[ fn_minus
	sum ptr v pos;
	sum = 0;
	pos = 0;
	for ( ptr = list_args-->0 : ptr~=0 : ptr=ptr-->2, pos++ ) {
		v = ptr-->1;
		if (v == 0 || v->0 ~= bt_Num) {
			show_error("+: non-numeric argument", v, 1);
			return tok_Error;
		}
		if (pos == 0) {
			sum = sum + v-->1;
		}
		else {
			sum = sum - v-->1;
		}
	}
	if (pos == 1)
		sum = 0-sum;
	return num_to_atom(sum);
];

[ fn_error;
	show_error();
	return tok_Error;
];
