shithub: pprolog

ref: e9f5f2ffcc62eee564d37d5776e701bab548a496
dir: /builtins.c/

View raw version
#include <u.h>
#include <libc.h>
#include <bio.h>

#include "dat.h"
#include "fns.h"

#define BuiltinProto(name) int name(Term *, Binding **, Module *)
#define Match(X, Y) (runestrcmp(name, X) == 0 && arity == Y)
#define Throw(What) do{\
	Goal *g = gmalloc(sizeof(Goal)); \
	g->goal = What; \
	g->module = usermodule; \
	g->catcher = nil; \
	g->next = goalstack; \
	goalstack = g; \
	return 1; \
}while(0)

BuiltinProto(builtinfail);
BuiltinProto(builtincall);
BuiltinProto(builtincut);
BuiltinProto(builtinvar);
BuiltinProto(builtinatom);
BuiltinProto(builtininteger);
BuiltinProto(builtinfloat);
BuiltinProto(builtinatomic);
BuiltinProto(builtincompound);
BuiltinProto(builtinnonvar);
BuiltinProto(builtinnumber);
BuiltinProto(builtincompare);
BuiltinProto(builtinfunctor);
BuiltinProto(builtinarg);
BuiltinProto(builtinuniv);
BuiltinProto(builtincopyterm);
BuiltinProto(builtinis);
BuiltinProto(builtincatch);
BuiltinProto(builtinthrow);
BuiltinProto(builtinsetprologflag);
BuiltinProto(builtincurrentprologflag);
BuiltinProto(builtinopen);
BuiltinProto(builtinclose);
BuiltinProto(builtincurrentinput);
BuiltinProto(builtincurrentoutput);
BuiltinProto(builtinsetinput);
BuiltinProto(builtinsetoutput);
BuiltinProto(builtinreadterm);
BuiltinProto(builtinwriteterm);
BuiltinProto(builtingeq);
BuiltinProto(builtinclause);

int compareterms(Term *, Term *);

Builtin
findbuiltin(Term *goal)
{
	int arity;
	Rune *name;

	switch(goal->tag){
	case AtomTerm:
		arity = 0;
		name = goal->text;
		break;
	case CompoundTerm:
		arity = goal->arity;
		name = goal->text;
		break;
	default:
		return nil;
	}

	/* Rewrite this so its not just a long if chain */
	if(Match(L"fail", 0))
		return builtinfail;
	if(Match(L"call", 1))
		return builtincall;
	if(Match(L"!", 0))
		return builtincut;
	if(Match(L"var", 1))
		return builtinvar;
	if(Match(L"atom", 1))
		return builtinatom;
	if(Match(L"integer", 1))
		return builtininteger;
	if(Match(L"float", 1))
		return builtinfloat;
	if(Match(L"atomic", 1))
		return builtinatomic;
	if(Match(L"compound", 1))
		return builtincompound;
	if(Match(L"nonvar", 1))
		return builtinnonvar;
	if(Match(L"number", 1))
		return builtinnumber;
	if(Match(L"compare", 3))
		return builtincompare;
	if(Match(L"functor", 3))
		return builtinfunctor;
	if(Match(L"arg", 3))
		return builtinarg;
	if(Match(L"=..", 2))
		return builtinuniv;
	if(Match(L"copy_term", 2))
		return builtincopyterm;
	if(Match(L"is", 2))
		return builtinis;
	if(Match(L"catch", 3))
		return builtincatch;
	if(Match(L"throw", 1))
		return builtinthrow;
	if(Match(L"set_prolog_flag", 2))
		return builtinsetprologflag;
	if(Match(L"current_prolog_flag", 2))
		return builtincurrentprologflag;
	if(Match(L"open", 4))
		return builtinopen;
	if(Match(L"close", 2))
		return builtinclose;
	if(Match(L"current_input", 1))
		return builtincurrentinput;
	if(Match(L"current_output", 1))
		return builtincurrentoutput;
	if(Match(L"set_input", 1))
		return builtinsetinput;
	if(Match(L"set_output", 1))
		return builtinsetoutput;
	if(Match(L"read_term", 3))
		return builtinreadterm;
	if(Match(L"write_term", 3))
		return builtinwriteterm;
	if(Match(L">=", 2))
		return builtingeq;
	if(Match(L"clause", 3))
		return builtinclause;

	return nil;
}

int
builtinfail(Term *goal, Binding **bindings, Module *module)
{
	USED(goal);
	USED(bindings);
	USED(module);
	return 0;
}

int
builtincall(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	goalstack = addgoals(goalstack, goal->children, module);
	return 1;
}

int
builtincut(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);

	Choicepoint *cp = choicestack;

	/* Cut all choicepoints with an id larger or equal to the goal clause number, since they must have been introduced
	   after this goal's parent.
	*/
	while(cp != nil && cp->id >= goal->clausenr)
		cp = cp->next;
	choicestack = cp;
	return 1;
}

int
builtinvar(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);
	Term *arg = goal->children;
	return (arg->tag == VariableTerm);
}

int
builtinatom(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);
	Term *arg = goal->children;
	return (arg->tag == AtomTerm);
}

int
builtininteger(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);
	Term *arg = goal->children;
	return (arg->tag == IntegerTerm);
}

int
builtinfloat(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);
	Term *arg = goal->children;
	return (arg->tag == FloatTerm);
}

int
builtinatomic(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);
	Term *arg = goal->children;
	return (arg->tag == AtomTerm || arg->tag == FloatTerm || arg->tag == IntegerTerm);
}

int
builtincompound(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);
	Term *arg = goal->children;
	return (arg->tag == CompoundTerm);
}

int
builtinnonvar(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);
	Term *arg = goal->children;
	return (arg->tag != VariableTerm);
}

int
builtinnumber(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);
	Term *arg = goal->children;
	return (arg->tag == FloatTerm || arg->tag == IntegerTerm);
}

#define Compare(A, B) ((A < B) ? -1 : ((A > B) ? 1 : 0))

int
compareterms(Term *t1, Term *t2)
{
	int result = 0;

	if(t1->tag != t2->tag)
		result = Compare(t1->tag, t2->tag);
	else{
		/* Same type term */
		switch(t1->tag){
		case VariableTerm:
			if(runestrcmp(t1->text, L"_") == 0 && runestrcmp(t2->text, L"_") == 0)
				result = 1; /* Special case since _ and _ are always different */
			else if(t1->clausenr == t2->clausenr)
				result = runestrcmp(t1->text, t2->text);
			else
				result = Compare(t1->clausenr, t2->clausenr);
			break;
		case FloatTerm:
			result = Compare(t1->dval, t2->dval);
			break;
		case IntegerTerm:
			result = Compare(t1->ival, t2->ival);
			break;
		case AtomTerm:
			result = runestrcmp(t1->text, t2->text);
			break;
		case CompoundTerm:
			result = Compare(t1->arity, t2->arity);
			if(result != 0)
				break;

			result = runestrcmp(t1->text, t2->text);
			if(result != 0)
				break;

			t1 = t1->children;
			t2 = t2->children;
			while(t1 != nil && t2 != nil){
				result = compareterms(t1, t2);
				if(result != 0)
					break;
				else
					t1 = t1->next;
					t2 = t2->next;
			}
			break;
		}
	}
	return result;
}

int
builtincompare(Term *goal, Binding **bindings, Module *module)
{
	USED(module);
	Term *order = goal->children;
	Term *t1 = order->next;
	Term *t2 = t1->next;

	int result = compareterms(t1, t2);

	Term *resultorder;
	if(result == -1)
		resultorder = mkatom(L"<");
	else if(result == 0)
		resultorder = mkatom(L"=");
	else
		resultorder = mkatom(L">");

	return unify(order, resultorder, bindings);
}

int
builtinfunctor(Term *goal, Binding **bindings, Module *module)
{
	USED(module);
	Term *term = goal->children;
	Term *name = term->next;
	Term *arity = name->next;

	if(term->tag == VariableTerm && name->tag == VariableTerm)
		Throw(instantiationerror());
	if(term->tag == VariableTerm && arity->tag == VariableTerm)
		Throw(instantiationerror());
	if(term->tag == VariableTerm && !(name->tag == VariableTerm || name->tag == AtomTerm || name->tag == IntegerTerm || name->tag == FloatTerm))
		Throw(typeerror(L"atomic", name));
	if(term->tag == VariableTerm && !(arity->tag == VariableTerm || arity->tag == IntegerTerm))
		Throw(typeerror(L"integer", arity));
	if(term->tag == VariableTerm && name->tag != VariableTerm && name->tag != AtomTerm && arity->tag == IntegerTerm && arity->ival > 0)
		Throw(typeerror(L"atom", name));
	if(term->tag == VariableTerm && arity->tag == IntegerTerm && arity->ival < 0)
		Throw(domainerror(L"not_less_than_zero", arity));


	if(term->tag == VariableTerm){
		if(arity->ival == 0)
			return unify(term, name, bindings);
		else{
			/* Make arity many fresh variables */
			int i;
			Term *args = nil;
			for(i = 0; i < arity->ival; i++){
				Term *arg = mkvariable(L"_");
				args = appendterm(args, arg);
			}
			Term *realterm = mkcompound(name->text, arity->ival, args);
			return unify(term, realterm, bindings);
		}
	}else{
		Rune *namestr;
		int arityint;

		if(term->tag == CompoundTerm){
			namestr = term->text;
			arityint = term->arity;
		}else{
			namestr = prettyprint(term, 0, 0, 0);
			arityint = 0;
		}
		Term *realname = mkatom(namestr);
		Term *realarity = mkinteger(arityint);
		if(unify(name, realname, bindings) && unify(arity, realarity, bindings))
			return 1;
	}
	return 0;
}

int
builtinarg(Term *goal, Binding **bindings, Module *module)
{
	USED(module);
	Term *n = goal->children;
	Term *term = n->next;
	Term *arg = term->next;

	if(n->tag == VariableTerm || term->tag == VariableTerm)
		Throw(instantiationerror());
	if(n->tag != IntegerTerm)
		Throw(typeerror(L"integer", n));
	if(term->tag != CompoundTerm)
		Throw(typeerror(L"compound", term));
	if(n->ival < 0)
		Throw(domainerror(L"not_less_than_zero", n));

	if(n->ival > term->arity)
		return 0;

	int i;
	Term *t;
	for(i = 1, t = term->children; i < n->ival; i++, t = t->next);
	return unify(arg, t, bindings);
}

int
listlength(Term *term)
{
	if(term->tag == AtomTerm && runestrcmp(term->text, L"[]") == 0)
		return 0;
	else if(term->tag == CompoundTerm && term->arity == 2 && runestrcmp(term->text, L".") == 0){
		int taillength = listlength(term->children->next);
		return (taillength == -1) ? -1 : 1 + taillength;
	}else
		return -1;
}

int
builtinuniv(Term *goal, Binding **bindings, Module *module)
{
	USED(module);
	Term *term = goal->children;
	Term *list = term->next;

	if(term->tag == VariableTerm && ispartiallist(list))
		Throw(instantiationerror());
	if(!(ispartiallist(list) || islist(list)))
		Throw(typeerror(L"list", list));

	Term *head = listhead(list);
	Term *tail = listtail(list);

	if(term->tag == VariableTerm && head->tag == VariableTerm)
		Throw(instantiationerror());
	if(islist(list) && !(head->tag == AtomTerm || head->tag == VariableTerm) && !isemptylist(tail))
		Throw(typeerror(L"atom", head));
	if(islist(list) && head->tag == CompoundTerm && isemptylist(tail))
		Throw(typeerror(L"atomic", head));
	if(term->tag == VariableTerm && isemptylist(list))
		Throw(domainerror(L"non_empty_list", list));

	int len;
	if(term->tag == VariableTerm){
		Rune *name;
		Term *elems = nil;
		Term *realterm;
		int i;

		len = listlength(list);
		if(len < 1)
			return 0;
		if(list->children->tag != AtomTerm)
			return 0;
		name = list->children->text;

		list = list->children->next;
		for(i = 1; i < len; i++){
			Term *t = copyterm(list->children, nil);
			elems = appendterm(elems, t);
			list = list->children->next;
		}
		realterm = mkcompound(name, len-1, elems);
		return unify(term, realterm, bindings);
	}else if(term->tag == CompoundTerm){
		Term *elems = mkatom(term->text);
		elems->next = term->children;
		Term *reallist = mklist(elems);
		return unify(list, reallist, bindings);
	}else{
		Term *t = copyterm(term, nil);
		t->next = mkatom(L"[]");
		Term *reallist = mkcompound(L".", 2, t);
		return unify(list, reallist, bindings);
	}
}

int
builtincopyterm(Term *goal, Binding **bindings, Module *module)
{
	USED(module);
	Term *term1 = goal->children;
	Term *term2 = term1->next;
	Term *t = copyterm(term1, &clausenr);
	clausenr++;
	return unify(term2, t, bindings);
}

int
builtinis(Term *goal, Binding **bindings, Module *module)
{
	USED(module);
	Term *result = goal->children;
	Term *expr = result->next;

	int waserror;
	Term *realresult = aritheval(expr, &waserror);
	if(waserror)
		Throw(realresult);
	return unify(result, realresult, bindings);
}

int
builtincatch(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);

	Term *catchgoal = goal->children;
	Term *catcher = catchgoal->next;
	Term *recover = catcher->next;

	Goal *catchframe = gmalloc(sizeof(Goal));
	catchframe->goal = recover;
	catchframe->module = module;
	catchframe->catcher = catcher;
	catchframe->next = goalstack;
	goalstack = catchframe;

	Goal *g = gmalloc(sizeof(Goal));
	g->goal = catchgoal;
	g->module = module;
	g->catcher = nil;
	g->next = goalstack;
	goalstack = g;

	return 1;
}

int
builtinthrow(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);

	Term *ball = goal->children;

	print("Throwing: %S\n", prettyprint(ball, 0, 0, 0));
	Goal *g;
	for(g = goalstack; g != nil; g = g->next){
		if(g->catcher == nil)
			continue;

		if(unify(g->catcher, ball, bindings)){
			if(g->goal == nil){
				/* As soon as we have print facilities as builtins, we can avoid this by having the protector frame have a unhandled exception handler*/
				print("Unhandled exception: %S\n", prettyprint(ball, 0, 0, 0));
				exits("exception");
				return 0;
			}else{
				goalstack = g->next;
				Goal *newgoal = gmalloc(sizeof(Goal));
				newgoal->goal = copyterm(g->goal, nil);
				newgoal->module = module;
				newgoal->catcher = nil;
				newgoal->next = goalstack;
				goalstack = newgoal;
				applybinding(newgoal->goal, *bindings);

				Choicepoint *cp = choicestack;
				while(cp != nil && cp->id >= goal->clausenr)
					cp = cp->next;
				choicestack = cp;
				return 1;
			}
		}
	}
	return 0;
}

int
builtincurrentprologflag(Term *goal, Binding **bindings, Module *module)
{
	USED(goal);
	USED(bindings);
	USED(module);
	return 0;
}

int
builtinsetprologflag(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);
	Term *key = goal->children;
	Term *value = key->next;

	if(key->tag == VariableTerm || value->tag == VariableTerm)
		Throw(instantiationerror());

	if(key->tag != AtomTerm)
		Throw(typeerror(L"atom", key));

	Term *error = setflag(key->text, value);
	if(error)
		Throw(error);
	return 1;
}

int
builtinopen(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);

	Term *sourcesink = goal->children;
	Term *mode = sourcesink->next;
	Term *stream = mode->next;
	Term *options = stream->next;

	if(sourcesink->tag == VariableTerm || mode->tag == VariableTerm || options->tag == VariableTerm)
		Throw(instantiationerror());

	if(stream->tag != VariableTerm)
		Throw(typeerror(L"variable", stream));

	if(options->tag != AtomTerm || runestrcmp(options->text, L"[]") != 0)
		Throw(typeerror(L"empty_list", options));

	if(mode->tag != AtomTerm)
		Throw(typeerror(L"atom", mode));

	if(sourcesink->tag != AtomTerm)
		Throw(domainerror(L"source_sink", sourcesink));

	Term *newstream;
	int error = openstream(sourcesink->text, mode->text, options, &newstream);
	if(error)
		Throw(newstream);
	else
		return unify(stream, newstream, bindings);

	return 0;
}

int
builtinclose(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);
	
	Term *stream = goal->children;
	Term *options = stream->next;

	if(stream->tag == VariableTerm || options->tag == VariableTerm)
		Throw(instantiationerror());

	if(options->tag != AtomTerm || runestrcmp(options->text, L"[]") != 0)
		Throw(typeerror(L"empty_list", options));

	if(stream->tag != IntegerTerm && stream->tag != AtomTerm)
		Throw(domainerror(L"stream_or_alias", stream));

	if(!isopenstream(stream))
		Throw(existenceerror(L"stream", stream));

	closestream(stream);

	return 1;
}

int
builtincurrentinput(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);

	Term *stream = goal->children;
	if(stream->tag != VariableTerm && stream->tag != IntegerTerm)
		Throw(domainerror(L"stream", stream));

	Term *current = currentinputstream();
	return unify(stream, current, bindings);
}

int
builtincurrentoutput(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);

	Term *stream = goal->children;
	if(stream->tag != VariableTerm && stream->tag != IntegerTerm)
		Throw(domainerror(L"stream", stream));

	Term *current = currentoutputstream();
	return unify(stream, current, bindings);
}

int
builtinsetinput(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);

	Term *stream = goal->children;
	if(stream->tag == VariableTerm)
		Throw(instantiationerror());

	if(stream->tag != IntegerTerm && stream->tag != AtomTerm)
		Throw(domainerror(L"stream_or_alias", stream));
	
	if(!isopenstream(stream))
		Throw(existenceerror(L"stream", stream));

	if(!isinputstream(stream))
		Throw(permissionerror(L"input", L"stream", stream));

	setcurrentinputstream(stream);
	return 1;
}

int
builtinsetoutput(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);

	Term *stream = goal->children;
	if(stream->tag == VariableTerm)
		Throw(instantiationerror());

	if(stream->tag != IntegerTerm && stream->tag != AtomTerm)
		Throw(domainerror(L"stream_or_alias", stream));
	
	if(!isopenstream(stream))
		Throw(existenceerror(L"stream", stream));

	if(!isoutputstream(stream))
		Throw(permissionerror(L"output", L"stream", stream));

	setcurrentoutputstream(stream);
	return 1;
}

int
builtinreadterm(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);

	Term *stream = goal->children;
	Term *term = stream->next;
	Term *options = term->next;

	if(stream->tag == VariableTerm)
		Throw(instantiationerror());
	if(options->tag != AtomTerm || runestrcmp(options->text, L"[]") != 0)
		Throw(typeerror(L"empty_list", options));
	if(stream->tag != IntegerTerm && stream->tag != AtomTerm)
		Throw(domainerror(L"stream_or_alias", stream));
	if(!isopenstream(stream))
		Throw(existenceerror(L"stream", stream));
	if(isoutputstream(stream))
		Throw(permissionerror(L"input", L"stream", stream));
	if(isbinarystream(stream))
		Throw(permissionerror(L"input", L"binary_stream", stream));

	Term *realterm;
	int error = readterm(stream, options, &realterm);
	if(error)
		Throw(realterm);

	return unify(term, realterm, bindings);
}

int
builtinwriteterm(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);

	Term *stream = goal->children;
	Term *term = stream->next;
	Term *options = term->next;

	if(stream->tag == VariableTerm)
		Throw(instantiationerror());
	if(options->tag != AtomTerm || runestrcmp(options->text, L"[]") != 0)
		Throw(typeerror(L"empty_list", options));
	if(stream->tag != IntegerTerm && stream->tag != AtomTerm)
		Throw(domainerror(L"stream_or_alias", stream));
	if(!isopenstream(stream))
		Throw(existenceerror(L"stream", stream));
	if(isinputstream(stream))
		Throw(permissionerror(L"output", L"stream", stream));
	if(isbinarystream(stream))
		Throw(permissionerror(L"output", L"binary_stream", stream));
	writeterm(stream, options, term);
	return 1;
}

int
builtingeq(Term *goal, Binding **bindings, Module *module)
{
	USED(bindings);
	USED(module);
	Term *a = goal->children;
	Term *b = a->next;

	int waserror;
	Term *aval = aritheval(a, &waserror);
	if(waserror)
		Throw(aval);
	
	Term *bval = aritheval(b, &waserror);
	if(waserror)
		Throw(bval);

	if(aval->tag == IntegerTerm && bval->tag == IntegerTerm)
		return aval->ival >= bval->ival;
	else if(aval->tag == FloatTerm && bval->tag == FloatTerm)
		return aval->dval >= bval->dval;
	else if(aval->tag == IntegerTerm && bval->tag == FloatTerm)
		return aval->ival >= bval->dval;
	else if(aval->tag == FloatTerm && bval->tag == IntegerTerm)
		return aval->dval >= bval->ival;
	else
		return 0;
}

int
builtinclause(Term *goal, Binding **bindings, Module *module)
{
	Term *head = goal->children;
	Term *body = head->next;
	Term *clauselist = body->next;
	
	if(head->tag == VariableTerm)
		Throw(instantiationerror());
	if(head->tag != AtomTerm && head->tag != CompoundTerm)
		Throw(typeerror(L"callable", head));
	if(body->tag != VariableTerm && body->tag != AtomTerm && body->tag != CompoundTerm)
		Throw(typeerror(L"callable", body));
	if(clauselist->tag != VariableTerm)
		Throw(typeerror(L"variable", clauselist));

	print("Attempting to find clauses in module %S where head unifies with %S\n", module->name, prettyprint(head, 0, 0, 0));

	Predicate *pred = findpredicate(module->predicates, head);
	if(pred == nil)
		return 0;

	Term *functor = mkatom(pred->name);
	functor->next = mkinteger(pred->arity);
	Term *pi = mkcompound(L"/", 2, functor);
	if(!pred->public)
		Throw(permissionerror(L"access", L"private_procedure", pi));

	Term *realclauses = nil;
	Clause *c = pred->clauses;
	while(c != nil){
		Binding *bs = nil;
		c = findclause(c, head, &bs);
		if(c != nil){
			/* Append the clause to the realclauselist */
			Term *cl = c->head;
			if(c->body)
				cl->next = c->body;
			else
				cl->next = mkatom(L"true");
			
			realclauses = appendterm(realclauses, mkcompound(L"clause", 2, cl));
			c = c->next;
		}
	}
	Term *realclauselist = mklist(realclauses);
	return unify(clauselist, realclauselist, bindings);
}