shithub: pprolog

ref: 67e8b85a9f9b42acc896b44eb2ea1310e749f03a
dir: /eval.c/

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

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

typedef struct Binding Binding;
typedef struct Goal Goal;
typedef struct Choicepoint Choicepoint;

struct Binding
{
	Rune *name;
	uvlong nr; /* Unique number for each clause. Every time a clause is used, it gets a new number. */
	Term *value;
	Binding *next;
};

struct Goal
{
	Term *goal;
	Goal *next;
};

struct Choicepoint
{
	Goal *goalstack;
	Term *retryclause;
	Choicepoint *next;
};

Goal *addgoals(Goal *, Term *);
Term *findclause(Term *, Term *, Binding **);
int unify(Term *, Term *, Binding **);
int equalterms(Term *, Term *);
void applybinding(Term *, Binding *);
Goal *copygoals(Goal *);

static uvlong clausenr;

void
evalquery(Term *database, Term *query)
{
	Goal *goals = addgoals(nil, query);
	Choicepoint *choicestack = nil;

	clausenr = 0;

	while(goals != nil){
		Term *dbstart;
		Term *goal;

		dbstart = database;
Retry:
		goal = goals->goal;

		if(goal == nil){
			goals = goals->next;
			continue;
		}

		print("Solving goal %S\n", prettyprint(goal));

		/* Find a clause where the head unifies with the goal */
		Binding *bindings = nil;
		Term *clause = findclause(dbstart, goal, &bindings);
		if(clause != nil){
			if(clause->next != nil){
				/* Add a choicepoint. Note we create a choicepoint every time, so there is room for improvement. */
				Choicepoint *cp = malloc(sizeof(Choicepoint));
				cp->goalstack = copygoals(goals);
				cp->next = choicestack;
				cp->retryclause = clause->next;
				choicestack = cp;
			}
			goals = goals->next;
			/* Apply bindings to all goals on the top of the stack, down to the "bodystart" goal */
			Goal *g;
			for(g = goals; g != nil && g->goal != nil; g = g->next)
				applybinding(g->goal, bindings);
			
			/* Add clause body as goals, with bindings applied */
			if(clause->tag == CompoundTerm && clause->arity == 2 && runestrcmp(clause->text, L":-") == 0){
				Goal *bodystart = malloc(sizeof(Goal));
				bodystart->goal = nil;
				bodystart->next = goals;
				goals = bodystart;

				Term *subgoal = copyterm(clause->children->next, nil);
				applybinding(subgoal, bindings);
				goals = addgoals(goals, subgoal);
			}
		}else{
			if(choicestack == nil){
				print("Fail\n");
				return;
			}
			print("Backtracking...\n");
			Choicepoint *cp = choicestack;
			choicestack = cp->next;
			/* freegoals(goals) */
			goals = cp->goalstack;
			dbstart = cp->retryclause;

			goto Retry;
		}
	}
	print("Success.\n");
}

Goal *
addgoals(Goal *goals, Term *t)
{
	if(t->tag == CompoundTerm && runestrcmp(t->text, L",") == 0 && t->arity == 2){
		goals = addgoals(goals, t->children->next);
		goals = addgoals(goals, t->children);
	}else{
		Goal *g = malloc(sizeof(Goal));
		g->goal = t;
		g->next = goals;
		goals = g;
	}
	return goals;
}

Term *
findclause(Term *database, Term *goal, Binding **bindings)
{
	Term *clause;
	Term *head;
	for(; database != nil; database = database->next){
		clausenr++;
		clause = copyterm(database, &clausenr);
		clause->next = database->next;
		if(clause->tag == CompoundTerm && runestrcmp(clause->text, L":-") == 0 && clause->arity == 2)
			head = clause->children;
		else
			head = clause;

		if(unify(head, goal, bindings))
			return clause;
	}
	return nil;
}

int
unify(Term *a, Term *b, Binding **bindings)
{
	Term *leftstack;
	Term *rightstack;
	Term *left;
	Term *right;

	*bindings = nil;
	leftstack = copyterm(a, nil);
	rightstack = copyterm(b, nil);

	while(leftstack != nil && rightstack != nil){
		left = leftstack;
		leftstack = left->next;
		right = rightstack;
		rightstack = right->next;

		if(equalterms(left, right))
			continue;
		else if(left->tag == VariableTerm || right->tag == VariableTerm){
			if(right->tag == VariableTerm){
				Term *tmp = left;
				left = right;
				right = tmp;
			}

			if(runestrcmp(left->text, L"_") == 0)
				continue; /* _ doesn't introduce a new binding */

			Binding *b = malloc(sizeof(Binding));
			b->name = left->text;
			b->nr = left->clausenr;
			b->value = right;
			b->next = *bindings;
			*bindings = b;
			Term *t;
			for(t = leftstack; t != nil; t = t->next)
				applybinding(t, b);
			for(t = rightstack; t != nil; t = t->next)
				applybinding(t, b);
			Binding *tmpb;
			for(tmpb = *bindings; tmpb != nil; tmpb = tmpb->next)
				applybinding(tmpb->value, b);
		}else if(left->tag == CompoundTerm && right->tag == CompoundTerm && left->arity == right->arity && runestrcmp(left->text, right->text) == 0){
			Term *leftchild = left->children;
			Term *rightchild = right->children;
			while(leftchild != nil && rightchild != nil){
				Term *t1 = copyterm(leftchild, nil);
				t1->next = leftstack;
				leftstack = t1;
				leftchild = leftchild->next;

				Term *t2 = copyterm(rightchild, nil);
				t2->next = rightstack;
				rightstack = t2;
				rightchild = rightchild->next;
			}
		}else
			return 0; /* failure */
	}
	return 1;
}

int
equalterms(Term *a, Term *b)
{
	/* Check that two non-compound terms are identical */
	if(a->tag != b->tag)
		return 0;

	switch(a->tag){
	case AtomTerm:
	case VariableTerm:
	case StringTerm:
		return !runestrcmp(a->text, b->text);
	case NumberTerm:
		if(a->numbertype != b->numbertype)
			return 0;
		if(a->numbertype == NumberInt && a->ival == b->ival)
			return 1;
		if(a->numbertype == NumberFloat && a->dval == b->dval)
			return 1;
		return 0;	
	default:
		return 0;
	}
}

void
applybinding(Term *t, Binding *bindings)
{
	if(t->tag == VariableTerm){
		Binding *b;
		for(b = bindings; b != nil; b = b->next){
			if(runestrcmp(t->text, b->name) == 0 && t->clausenr == b->nr){
				Term *next = t->next;
				memcpy(t, b->value, sizeof(Term));
				t->next = next;
				return;
			}
		}
	}else if(t->tag == CompoundTerm){
		Term *child;
		for(child = t->children; child != nil; child = child->next)
			applybinding(child, bindings);
	}
}

Goal *
copygoals(Goal *goals)
{
	if(goals != nil){
		Goal *g = malloc(sizeof(Goal));
		if(goals->goal)
			g->goal = copyterm(goals->goal, nil);
		else
			g->goal = nil;
		g->next = copygoals(goals->next);
		return g;
	}else
		return nil;
}