BuiltIn.java

/*
 * #%L
 * prolobjectlink-jpi-jtrolog
 * %%
 * Copyright (C) 2012 - 2018 WorkLogic Project
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 2.1 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 Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
 * #L%
 */
/*
 * tuProlog - Copyright (C) 2001-2007 aliCE team at deis.unibo.it
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package jTrolog.lib;

import jTrolog.engine.BindingsTable;
import jTrolog.engine.Prolog;
import jTrolog.errors.InvalidLibraryException;
import jTrolog.errors.PrologException;
import jTrolog.parser.Parser;
import jTrolog.terms.Clause;
import jTrolog.terms.Flag;
import jTrolog.terms.Int;
import jTrolog.terms.Number;
import jTrolog.terms.Struct;
import jTrolog.terms.StructAtom;
import jTrolog.terms.Term;
import jTrolog.terms.Var;
import jTrolog.terms.WrapStruct;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * Library of built-in predicates
 * 
 * @author Alex Benini
 * @author janerist@stud.ntnu.no
 * @author ivar.orstavik@hist.no
 */
@SuppressWarnings({ "rawtypes", "unchecked","serial" })
public class BuiltIn extends Library {

	private Prolog engine;

	public BuiltIn(Prolog mediator) {
		engine = mediator;
	}

	/**
	 * Defines a map for synonyms for primitives. String primitive name =
	 * String[]{synonym name, another synonym name, ..}.
	 */
	public String[] getSynonym(String primitive) {
		if (primitive.equals("cut"))
			return new String[] { "!" };
		if (primitive.equals("unify"))
			return new String[] { "=" };
		if (primitive.equals("deunify"))
			return new String[] { "\\=" };
		if (primitive.equals("comma"))
			return new String[] { "," };
		if (primitive.equals("solve"))
			return new String[] { "initialization" };
		if (primitive.equals("assertz"))
			return new String[] { "assert" };
		if (primitive.equals("copy"))
			return new String[] { "copy_term" };
		return null;
	}

	/**
	 * Primitives
	 */
	public boolean comma_2(BindingsTable bt, Term arg0, Term arg1) {
		throw new RuntimeException("','/2 method is hardcoded in the Engine, do not use Buitlin.comma_2!");
	}

	public static boolean cut_0(BindingsTable bt) {
		throw new RuntimeException("!/0 method is hardcoded in engine, do not use Buitlin.cut_0!");
	}

	public static boolean fail_0(BindingsTable bt) {
		return false;
	}

	public static boolean true_0(BindingsTable bt) {
		return true;
	}

	public static boolean call_1(BindingsTable bt, Term arg1) {
		throw new RuntimeException("call/1 method is hardcoded in engine, do not use Buitlin.call_1!");
	}

	public static boolean halt_0(BindingsTable bt) throws PrologException {
		throw new PrologException("halt()");
	}

	public boolean halt_1(BindingsTable bt, jTrolog.terms.Number arg0) throws PrologException {
		throw new PrologException("halt(" + arg0.intValue() + ")");
	}

	public boolean $debug_1(BindingsTable bt, Term arg0) {
		System.out.println("debug=== " + bt.variant(arg0));
		return true;
	}

	public boolean $debug_3(BindingsTable bt, Term arg0, Term arg2, Term arg3) {
		System.out.println("debug0=== " + bt.variant(arg0));
		System.out.println("debug1=== " + bt.variant(arg2));
		System.out.println("debug2=== " + bt.variant(arg3));
		return true;
	}

	public boolean asserta_1(BindingsTable bt, Term arg0) throws PrologException {
		arg0 = bt.variant(arg0);
		engine.assertA(convertTermToClause(arg0));
		return true;
	}

	public boolean assertz_1(BindingsTable bt, Term arg0) throws PrologException {
		arg0 = bt.variant(arg0);
		engine.assertZ(convertTermToClause(arg0));
		return true;
	}

	public boolean $retract_1(BindingsTable bt, Struct arg0) throws PrologException {
		arg0 = (Struct) bt.variant(arg0);
		arg0 = convertTermToClause(arg0).original;
		Struct sarg0 = (Struct) bt.wrapWithID(arg0);
		Struct retractedClause = engine.retract(sarg0);
		// if clause to retract found -> retract + true
		if (retractedClause != null) {
			bt.unify(retractedClause, arg0);
			return true;
		}
		return false;
	}

	/**
	 * see 8.9.4 p.81 in ISO standard
	 * 
	 * @param predicateIndicator
	 * @return true
	 * @throws PrologException
	 *             in case of errors
	 */
	public boolean abolish_1(BindingsTable bt, Struct predicateIndicator) throws PrologException {
		engine.abolish(isPredicateIndicator(predicateIndicator, bt));
		return true;
	}

	/**
	 * Registers the existence predicate indicator in the dynamic data base. If
	 * such a predicate is later requested, but none exists, the search will not
	 * throw an error, but return false. see ISO spec on abolish for details on
	 * errors.
	 * 
	 * @param predicateIndicator
	 * @return true
	 * @throws PrologException
	 *             in case of bad input
	 */
	public boolean dynamic_1(BindingsTable bt, Struct predicateIndicator) throws PrologException {
		engine.setDynamicPredicateIndicator(isPredicateIndicator(predicateIndicator, bt));
		return true;
	}

	/*
	 * loads a engine library, given its java class name
	 */
	public boolean load_library_1(BindingsTable bt, Struct arg0) throws InvalidLibraryException {
		if (!BasicLibrary.atom_1(bt, arg0))
			return false;
		engine.loadLibrary(arg0.name);
		return true;
	}

	/**
	 * unloads a engine library, given its java class name
	 */
	public boolean unload_library_1(BindingsTable bt, Struct arg0) throws InvalidLibraryException {
		if (!BasicLibrary.atom_1(bt, arg0))
			return false;
		engine.unloadLibrary(arg0.name);
		return true;
	}

	/*
	 * get flag list: flag_list(-List)
	 */
	public boolean flag_list_1(BindingsTable bt, Term arg0) {
		return bt.unify(arg0, bt.wrapWithID(engine.getPrologFlagList()));
	}

	public boolean unify_2(BindingsTable bt, Term arg0, Term arg1) {
		return bt.unify(arg0, arg1);
	}

	// \=
	public boolean deunify_2(BindingsTable bt, Term arg0, Term arg1) {
		return !bt.unify(arg0, arg1);
	}

	// unifies a new Struct list of the given length with Any vars as elements
	// with the Var out
	public boolean newlist_2(BindingsTable bt, Int length, Var out) throws PrologException {
		int lengthInt = length.intValue();
		if (lengthInt < 0)
			throw new PrologException("domain_error(not_less_than_zero, " + length + ")");
		if (lengthInt == 0)
			return bt.unify(out, Term.emptyList);
		return bt.unify(out, (WrapStruct) bt.wrapWithID(Parser.createListContainingAnyVars(lengthInt)));
	}

	// $copy
	public boolean copy_2(BindingsTable bt, Term arg0, Term arg1) {
		return bt.unify(bt.wrapWithID(bt.variant(arg0)), arg1);
	}

	// $find
	// look for clauses whose head unifies whith arg0 and unify the list of them
	// with arg1
	public boolean $find_2(BindingsTable bt, Struct arg0, Term arg1) throws PrologException, CloneNotSupportedException {
		String key = arg0.predicateIndicator;
		if (engine.hasPrimitive(key))
			throw new PrologException("permission_error(access, static_procedure, " + key + ")");
		LinkedList res = new LinkedList();
		List clauses = engine.find(key);
		if (clauses.isEmpty())
			return bt.unify(arg1, Term.emptyList);
		for (Iterator it = clauses.iterator(); it.hasNext();)
			res.add(((Clause) it.next()).original);
		res.add(Term.emptyList);
		return bt.unify(arg1, bt.wrapWithID(Parser.createStructList(res)));
	}

	// set_prolog_flag(+Name,@Value)
	public boolean set_prolog_flag_2(BindingsTable bt, Term arg0, Term arg1) {
		if (!BasicLibrary.atom_1(bt, arg0) && !(arg0 instanceof Struct) || !BasicLibrary.ground_1(bt, arg1))
			return false;

		String name = arg0.toString();
		Flag flag = engine.getFlag(name);// Flag) it.next();
		if (flag != null) {
			try {
				for (Iterator it2 = bt.structListIterator(flag.getValueList(), true); it2.hasNext();) {
					Term t = (Term) it2.next();
					if (Prolog.match(arg1, t)) {
						flag.setValue(arg1);
						return true;
					}
				}
			} catch (PrologException e) {
				throw new RuntimeException("error in iterating a Struct list");
			}
		}
		return false;
	}

	// get_prolog_flag(@Name,?Value)
	public boolean get_prolog_flag_2(BindingsTable bt, Term arg0, Term arg1) {
		if (!BasicLibrary.atom_1(bt, arg0) && !(arg0 instanceof Struct))
			return false;

		String name = arg0.toString();
		Term value = engine.getFlagValue(name);
		if (value == null)
			return false;
		return bt.unify(value, arg1);
	}

	/*
	 * DIRECTIVES
	 */

	/**
	 * op(+Precedence, +Type, +Name) defines a new operator if precedence not in
	 * 0..1200 = delete currently present op
	 */
	public void op_3(BindingsTable bt, Number arg0, StructAtom arg1, StructAtom arg2) {
		String st = ((Struct) arg1).name;
		if (st.matches("[xy]?f[xy]?")) // a tad simplified
			engine.opNew(arg2.toString(), st, arg0.intValue());
	}

	public void flag_4(BindingsTable bt, Term flagName, Struct flagSet, Term flagDefault, Term flagModifiable) {
		if (flagModifiable.equals(Term.TRUE) || flagModifiable.equals(Term.FALSE))
			engine.defineFlag(flagName.toString(), flagSet, flagDefault, flagModifiable.equals(Term.TRUE));
	}

	// todo test that this works properly.
	// todo Can alternatively use a temporary list to be run after the rest of
	// the theories in consults have been added.
	public void solve_1(BindingsTable bt, Struct goal) throws Throwable {
		engine.solve(bt.wrapWithID(bt.variant(goal)).toString());
	}

	public void load_library_1(BindingsTable bt, Term lib) throws InvalidLibraryException {
		if (BasicLibrary.atom_1(bt, lib))
			engine.loadLibrary(((Struct) lib).name);
	}

	public void consult_1(BindingsTable bt, Term theory) throws FileNotFoundException, PrologException, IOException {
		FileInputStream is;
		try {
			is = new FileInputStream(Parser.removeApices(theory.toString()));
		} catch (FileNotFoundException e) {
			File dir = new File(System.getProperty("user.dir"));
			String filename = dir + File.separator + Parser.removeApices(theory.toString());
			is = new FileInputStream(filename);
		}
		engine.addTheory(IOLibrary.readStream(is));
	}

	public boolean $instantiation_error_0(BindingsTable bt) throws PrologException {
		throw new PrologException("instantiation_error");
	}

	public boolean $type_error_2(BindingsTable bt, Term typeName, Term term) throws PrologException {
		throw new PrologException("type_error(" + typeName + ", " + term + ")");
	}

	public boolean $representation_error_1(BindingsTable bt, Term maxArity) throws PrologException {
		throw new PrologException("representation_error(" + maxArity + ")");
	}

	public boolean $domain_error_zero_1(BindingsTable bt, Term term) throws Exception {
		throw new PrologException("domain_error(not_less_than_zero, " + term + ")");
	}

	/**
	 * see ISO 7.6.1 and 7.6.2
	 * 
	 * @param arg0
	 * @return
	 * @throws PrologException
	 */
	public static Clause convertTermToClause(Term arg0) throws PrologException {
		if (arg0 instanceof Var)
			throw new PrologException("instantiation_error");

		if (arg0 instanceof Number)
			throw new PrologException("type_error(callable, " + arg0 + ")");

		Term head, body;
		Struct cl = (Struct) arg0;
		if (cl.predicateIndicator != Parser.doubleClauseSignature) {
			head = arg0;
			body = Term.TRUE;
		} else {
			head = cl.getArg(0);
			body = cl.getArg(1);
		}
		if (head instanceof Number)
			throw new PrologException("type_error(callable, " + head + ")");
		if (head instanceof Var)
			throw new PrologException("instantiation_error");

		if (body instanceof Number)
			throw new PrologException("type_error(callable, " + body + ")");
		body = convertTermToClauseBody(body);
		Struct[] body2 = convertTermToClauseBody2(body);

		return new Clause(body2, (Struct) head, new Struct(":-", new Term[] { head, body }));
	}

	// todo see 7.6.2 and table 9 in ISO
	public static Struct[] convertTermToClauseBody2(Term body) throws PrologException {
		LinkedList tmp = new LinkedList();
		while (body instanceof Struct && ((Struct) body).predicateIndicator == Parser.commaSignature) {
			tmp.add(convertTermToClauseBody(((Struct) body).getArg(0))); // todo,
																			// this
																			// might
																			// turn
																			// out
																			// to
																			// be
																			// a
																			// problem
																			// if
																			// the
																			// left
																			// child
																			// is
																			// a
																			// comma...
			body = ((Struct) body).getArg(1);
		}
		tmp.add(convertTermToClauseBody(body));
		return (Struct[]) tmp.toArray(new Struct[tmp.size()]);
	}

	public static Struct convertTermToClauseBody(Term body) throws PrologException {
		if (body instanceof Var)
			return new Struct("call", new Term[] { body });
		if (body instanceof Number)
			throw new PrologException("type_error(callable, " + body + ")");
		Struct s = (Struct) body;
		String sPredIndic = s.predicateIndicator;
		if (sPredIndic == Parser.ifSignature || sPredIndic == Parser.commaSignature || sPredIndic == Parser.semiColonSignature || sPredIndic == Parser.throwSignature
				|| sPredIndic == Parser.catchSignature) {
			Term[] args = new Term[s.arity];
			for (int i = 0; i < s.arity; i++)
				args[i] = convertTermToClauseBody(s.getArg(i));
			return new Struct(s.name, args, s.getOperatorType());
		}
		return s;
	}

	public static String isPredicateIndicator(Struct predicateIndicator, BindingsTable bt) throws PrologException {
		if (predicateIndicator.predicateIndicator != "'/'/2")
			throw new PrologException("type_error(predicate_indicator, " + predicateIndicator + ")");
		Term name = bt.resolve(predicateIndicator.getArg(0));
		Term arity = bt.resolve(predicateIndicator.getArg(1));
		if (name instanceof Var || arity instanceof Var)
			throw new PrologException("instantiation_error");
		if (!(arity instanceof Int))
			throw new PrologException("type_error(integer, " + arity + ")");
		if (!(name instanceof StructAtom))
			throw new PrologException("type_error(atom, " + name + ")");
		Int arityInt = (Int) arity;
		if (arityInt.intValue() < 0)
			throw new PrologException("domain_error(not_less_than_zero, " + arityInt + ")");
		if (arityInt.longValue() > Integer.MAX_VALUE)
			throw new PrologException("representation_error(max_arity)");
		return Parser.wrapAtom(name.toString()) + "/" + arity;
	}
}