/* * Copyright 2014 Igor Maznitsa (http://www.igormaznitsa.com). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.prol.logic; import com.igormaznitsa.prol.containers.ClauseIterator; import com.igormaznitsa.prol.data.NumericTerm; import com.igormaznitsa.prol.data.Term; import com.igormaznitsa.prol.data.TermStruct; import com.igormaznitsa.prol.data.Var; import com.igormaznitsa.prol.exceptions.ProlCriticalError; import com.igormaznitsa.prol.exceptions.ProlHaltExecutionException; import com.igormaznitsa.prol.exceptions.ProlInstantiationErrorException; import com.igormaznitsa.prol.exceptions.ProlTypeErrorException; import com.igormaznitsa.prol.libraries.PredicateProcessor; import com.igormaznitsa.prol.parser.ProlTreeBuilder; import com.igormaznitsa.prol.trace.TraceListener; import com.igormaznitsa.prol.utils.Utils; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * The class implements the main prolog logic mechanism to solve goals. You must * remember that the goal works and return direct values of terms (for speed) so * you have to be accurate in work with the values. To avoid risks you can use a * IsolatedGoal wrapper. * * @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com) * @see IsolatedGoal * @see PreparedGoal */ public class Goal { /** * The inside constant shows that a goal is solved */ private static final int GOALRESULT_SOLVED = 0; /** * The inside constant shows that a goal is failed */ private static final int GOALRESULT_FAIL = 1; /** * The inside constant shows that the goal chain was changed */ private static final int GOALRESULT_STACK_CHANGED = 2; /** * The variable contains the link to the root goal for the goal */ private final Goal rootGoal; /** * The variable contains a map to get access to goal variables, the variable * has a value only for the root goal, for others it has null */ private final Map<String, Var> variables; /** * The variable contains the variable state snapshot for the goal, it can be * null if the goal doesn't change variables */ private final VariableStateSnapshot varSnapshot; /** * The variable contains the prol context for the goal */ private final ProlContext context; /** * The variable saves an auxiliary object for the goal */ private Object auxObject; /** * The flag shows that there ary not variants for the goal anymore */ private boolean noMoreVariantsFlag; /** * The variable contains the goal term of the goal */ private Term goalTerm; /** * The link field has the previous goal at the goal chain */ private Goal prevGoalAtChain; /** * The variable is used only by a root goal. It has the last goal at the goal * chain */ private Goal rootLastGoalAtChain; /** * The variable contains current subgoal of the goal */ private Goal subGoal; /** * The variable contains the connector term which will be used to send the * data from a subgoal to the goal */ private Term subGoalConnector; /** * The variable contains the connector term of the goal to get data from the * subgoal */ private Term thisConnector; /** * The variable has the next term which should be added to the chain */ private Term nextAndTerm; /** * The variable has the nextAndTerm field value for added goal */ private Term nextAndTermForNextGoal; /** * The variable contains current clause iterator for the goal */ private ClauseIterator clauseIterator; /** * This flag allows to cancel a clause iterator of the root goal from a child * goal */ private boolean cutMeet; /** * This variable contains a listener for goal events, it can be null */ private final TraceListener tracer; /** * The flag is used by tracer mechanism to see when the solve is being called * the first time */ private boolean notFirstProve; @Override public String toString() { return (isCompleted() ? "Completed " : "Active ") + "Goal(" + goalTerm.toString() + ')'; } /** * Get the current goal chain as a List * * @return a List object contains all goals at the goal chain of the root goal */ public List<Goal> getChainAsList() { final List<Goal> result = new ArrayList<Goal>(); Goal curgoal = rootGoal.rootLastGoalAtChain; while (curgoal != null) { result.add(0, curgoal); curgoal = curgoal.prevGoalAtChain; } return result; } /** * Get the value of a goal variable as a Number object. * * @param varName the name of the variable, must not be null; * @return null if the variable is not-instantiated one or its value as a * Number object * @throws IllegalArgumentException if the variable name is wrong * @throws NumberFormatException if the value is not-numeric one */ public Number getVarAsNumber(final String varName) { final Var var = getVarForName(varName); if (var == null) { throw new IllegalArgumentException("Unknown variable for name \'" + varName + '\''); } final Term value = var.getValue(); if (value == null) { return null; } else { if (value instanceof NumericTerm) { return ((NumericTerm) value).getNumericValue(); } else { throw new NumberFormatException("The variable contains a non-numeric value"); } } } /** * Get the value of a goal variable as a String object. * * @param varName the name of the variable, must not be null; * @return null if the variable is not-instantiated one or its text * representation (like it will be written by the write/1) * @throws IllegalArgumentException if the variable name is wrong */ public String getVarAsText(final String varName) { final Var var = getVarForName(varName); if (var == null) { throw new IllegalArgumentException("Unknown variable for name \'" + varName + '\''); } final Term value = var.getValue(); if (value == null) { return null; } else { return value.toString(); } } /** * Get a variable goal object for its name * * @param name the name of the needed variable, must not be null; * @return the found variable as a Var object or null if the variable is not * found */ public Var getVarForName(final String name) { if (name == null) { throw new NullPointerException("Variable name is null"); } return variables == null ? null : variables.get(name); } /** * To replace the last chain goal by the new goal for the term * * @param goal the new goal term which will be processed by the new last chain * goal * @return the generated goal object */ public Goal replaceLastGoalAtChain(final Term goal) { if (this.tracer != null) { this.tracer.onProlGoalExit(this.rootGoal.rootLastGoalAtChain); } final Goal newGoal = new Goal(this.rootGoal, goal, context, this.rootGoal.tracer); final Goal prevGoal = newGoal.prevGoalAtChain; if (prevGoal != null) { newGoal.prevGoalAtChain = prevGoal.prevGoalAtChain; newGoal.nextAndTerm = prevGoal.nextAndTerm; newGoal.nextAndTermForNextGoal = prevGoal.nextAndTermForNextGoal; } return newGoal; } /** * Inside special constructor * * @param rootGoal the root goal for the new goal, if it is null then the new * goal will be the root goal for itself * @param goal the term which will be goal for the new Goal, must not be null * @param context the context for the new goal * @param tracer the listener to listen events for the goal and its children * (it will replace the default listener of the context if it is presented) */ private Goal(final Goal rootGoal, Term goal, final ProlContext context, final TraceListener tracer) { this.rootGoal = rootGoal == null ? this : rootGoal; this.goalTerm = goal.getTermType() == Term.TYPE_ATOM ? new TermStruct(goal) : goal; this.context = context; this.tracer = tracer == null ? context.getDefaultTraceListener() : tracer; if (goal.getTermType() == Term.TYPE_VAR) { goal = Utils.getTermFromElement(goal); if (goal.getTermType() == Term.TYPE_VAR) { throw new ProlInstantiationErrorException("callable", goal); } } switch (goal.getTermType()) { case Term.TYPE_ATOM: { if (goal instanceof NumericTerm) { throw new ProlTypeErrorException("callable", goal); } } break; case Term.TYPE_VAR: { if (((Var) goal).isUndefined()) { throw new ProlInstantiationErrorException(goal); } } break; case Term.TYPE_LIST: { throw new ProlTypeErrorException("callable", goal); } } if (rootGoal == null) { if (goal.getTermType() == Term.TYPE_ATOM) { varSnapshot = null; variables = null; } else { variables = Utils.fillTableWithVars(goal); varSnapshot = new VariableStateSnapshot(goal); } rootLastGoalAtChain = this; prevGoalAtChain = null; } else { variables = null; if (goal.getTermType() == Term.TYPE_ATOM) { varSnapshot = null; } else { varSnapshot = new VariableStateSnapshot(rootGoal.varSnapshot);// new VariableStateSnapshot(rootGoal.goalTerm); } this.prevGoalAtChain = rootGoal.rootLastGoalAtChain; rootGoal.rootLastGoalAtChain = this; } } /** * Get the saved auxiliary object. It can be any successor of Object or null * * @return the saved auxiliary object or null */ public Object getAuxObject() { return auxObject; } /** * To get the tracer for the goal * * @return the tracer for the goal or null if the tracer is undefined */ public TraceListener getTracer() { return tracer; } /** * Save an auxiliary object for the goal. It can be any successor of Object or * null * * @param obj the object to be saved as an auxiliary one or null */ public void setAuxObject(final Object obj) { auxObject = obj; } /** * Get the goal term * * @return the goal term */ public Term getGoalTerm() { return goalTerm; } /** * Get the context for the goal * * @return the prol context for the goal */ public ProlContext getContext() { return context; } /** * Inside special constructor for special functions */ protected Goal() { rootGoal = null; variables = null; varSnapshot = null; context = null; tracer = null; } /** * A Constructor * * @param goal the goal as a String, must not be null * @param context the context for the goal, must not be null * @param tracer the tracer to listen goal events, can be null * @throws IOException it will be thrown if the goal string can't be parsed * well * @throws InterruptedException it will be thrown if the process is * interrupted */ public Goal(final String goal, final ProlContext context, final TraceListener tracer) throws IOException, InterruptedException { this(new ProlTreeBuilder(context).readPhraseAndMakeTree(goal), context, tracer); } /** * A Constructor * * @param goal the goal as a String, must not be null * @param context the context for the goal, must not be null * @throws IOException it will be thrown if the goal string can't be parsed * well * @throws InterruptedException it will be thrown if the process is * interrupted */ public Goal(final String goal, final ProlContext context) throws IOException, InterruptedException { this(new ProlTreeBuilder(context).readPhraseAndMakeTree(goal), context, null); } /** * A Constructor * * @param goal the goal term, must not be null * @param context the context for the goal, must not be null * @param tracer the tracer to listen goal events, it can be null */ public Goal(final Term goal, final ProlContext context, final TraceListener tracer) { this(null, goal, context, tracer); } /** * A Constructor * * @param goal the goal term, must not be null * @param context the context for the goal, must not be null */ public Goal(final Term goal, final ProlContext context) { this(null, goal, context, null); } /** * Get next solution of the goal * * @return the solution as a term or null if there is not a solution anymore * @throws InterruptedException it will be thrown if the process is * interrupted * @throws ProlHaltExecutionException it will be thrown if the context is * halted */ public Term solve() throws InterruptedException { Term result = null; boolean loop = true; final ProlContext localcontext = this.context; final boolean tracingOn = this.tracer != null; while (loop) { // check that the context is halted and throw an execption if it is if (localcontext.isHalted()) { throw new ProlHaltExecutionException(); } Goal goalToProcess = rootGoal.rootLastGoalAtChain; if (goalToProcess == null) { break; } else { if (!goalToProcess.noMoreVariantsFlag) { switch (goalToProcess._solve()) { case GOALRESULT_FAIL: { if (tracingOn) { this.tracer.onProlGoalFail(goalToProcess); this.tracer.onProlGoalExit(goalToProcess); } this.rootGoal.rootLastGoalAtChain = goalToProcess.prevGoalAtChain; } break; case GOALRESULT_SOLVED: { // we have to renew data about last chain goal because it can be changed during the operation goalToProcess = this.rootGoal.rootLastGoalAtChain; if (goalToProcess.nextAndTerm != null) { final Goal nextGoal = new Goal(this.rootGoal, goalToProcess.nextAndTerm, localcontext, this.rootGoal.tracer); nextGoal.nextAndTerm = goalToProcess.nextAndTermForNextGoal; } else { result = this.rootGoal.goalTerm; loop = false; } } break; case GOALRESULT_STACK_CHANGED: { } break; } } else { if (tracingOn) { this.tracer.onProlGoalExit(goalToProcess); } this.rootGoal.rootLastGoalAtChain = goalToProcess.prevGoalAtChain; } } } return result; } /** * Inside solve function allows to work with the goal chain * * @return GOALRESULT_FAIL if the goal failed, GOALRESULT_SOLVED if the goal * solved and GOALRESULT_STACK_CHANGED if the goal stack is changed and need * to be resolved * @throws InterruptedException it will be thrown if the process is * interrupted */ private int _solve() throws InterruptedException { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(); } if (tracer != null) { if (notFirstProve) { if (!tracer.onProlGoalRedo(this)) { return GOALRESULT_FAIL; } } else { notFirstProve = true; if (!tracer.onProlGoalCall(this)) { return GOALRESULT_FAIL; } } } int result = GOALRESULT_FAIL; boolean loop = true; while (loop) { // reset the variables to their initing state if (varSnapshot != null) { varSnapshot.resetToState(); } if (subGoal != null) { // solve subgoal final Term solvedTerm = subGoal.solve(); if (subGoal.cutMeet) { clauseIterator = null; } if (solvedTerm == null) { subGoal = null; if (clauseIterator == null) { result = GOALRESULT_FAIL; break; } } else { if (!thisConnector.Equ(subGoalConnector)) { throw new ProlCriticalError("Critical error #980234"); } result = GOALRESULT_SOLVED; break; } } if (clauseIterator != null) { // next clause if (clauseIterator.hasNext()) { final TermStruct structFromBase = clauseIterator.next(); final Term goalTermForEqu; if (((TermStruct) goalTerm).isFunctorLikeRuleDefinition()) { goalTermForEqu = ((TermStruct) goalTerm).getElement(0).makeClone(); } else { goalTermForEqu = goalTerm.makeClone(); } if (!goalTermForEqu.Equ(structFromBase.isFunctorLikeRuleDefinition() ? structFromBase.getElement(0) : structFromBase)) { throw new ProlCriticalError("impossible situation #2123123"); } if (structFromBase.isFunctorLikeRuleDefinition()) { thisConnector = goalTerm; subGoalConnector = structFromBase.getElement(0); subGoal = new Goal(structFromBase.getElement(1), context, tracer); continue; } else { if (!goalTerm.Equ(structFromBase)) { throw new ProlCriticalError("Impossible situation #0009824"); } result = GOALRESULT_SOLVED; break; } } else { clauseIterator = null; noMoreVariants(); break; } } switch (goalTerm.getTermType()) { case Term.TYPE_ATOM: { final String text = goalTerm.getText(); result = context.hasZeroArityPredicateForName(text) ? GOALRESULT_SOLVED : GOALRESULT_FAIL; noMoreVariants(); loop = false; } break; case Term.TYPE_STRUCT: { final TermStruct struct = (TermStruct) goalTerm; final int arity = struct.getArity(); if (struct.isFunctorLikeRuleDefinition()) { final TermStruct structClone = (TermStruct) struct.makeClone(); this.thisConnector = struct.getElement(0); this.subGoalConnector = structClone.getElement(0); if (arity == 1) { this.subGoal = new Goal(structClone.getElement(0), this.context, this.tracer); } else { this.subGoal = new Goal(structClone.getElement(1), this.context, this.tracer); } } else { final Term functor = struct.getFunctor(); final String functorText = functor.getText(); boolean process = true; switch (arity) { case 0: { final int len = functorText.length(); if (len == 1 && functorText.charAt(0) == '!') { // cut cut(); process = false; loop = false; result = GOALRESULT_SOLVED; this.noMoreVariantsFlag = true; } else if (len == 2 && "!!".equals(functorText)) { // cut local cutLocal(); process = false; loop = false; this.noMoreVariantsFlag = true; result = GOALRESULT_SOLVED; } } break; case 2: { final int textLen = functorText.length(); if (textLen == 1) { switch (functorText.charAt(0)) { case ',': { // and final Goal leftSubgoal = replaceLastGoalAtChain(struct.getElement(0)); leftSubgoal.nextAndTerm = struct.getElement(1); leftSubgoal.nextAndTermForNextGoal = this.nextAndTerm; result = GOALRESULT_STACK_CHANGED; loop = false; process = false; } break; case ';': { // or if (getAuxObject() == null) { // left subbranch final Goal leftSubbranch = new Goal(rootGoal, struct.getElement(0), context, tracer); leftSubbranch.nextAndTerm = this.nextAndTerm; setAuxObject(leftSubbranch); } else { // right subbranch replaceLastGoalAtChain(struct.getElement(1)); } result = GOALRESULT_STACK_CHANGED; process = false; loop = false; } break; } } } break; } if (process) { final PredicateProcessor processor = checkProcessor(struct); if (processor == PredicateProcessor.NULL_PROCESSOR) { // just a struct // find it at knowledge base clauseIterator = context.getKnowledgeBase().getClauseIterator(struct); if (clauseIterator == null || !clauseIterator.hasNext()) { loop = false; noMoreVariants(); result = GOALRESULT_FAIL; } } else { // it is a processor if (processor.isEvaluable() || processor.isDetermined()) { noMoreVariants(); } if (processor.execute(this, struct)) { result = GOALRESULT_SOLVED; } else { result = GOALRESULT_FAIL; } if (result == GOALRESULT_SOLVED && processor.doesChangeGoalChain()) { result = GOALRESULT_STACK_CHANGED; } loop = false; } } } } break; default: { result = GOALRESULT_FAIL; loop = false; noMoreVariants(); } break; } } return result; } /** * Set the flag of the goal shows that the goal hasn't more variants */ public void noMoreVariants() { noMoreVariantsFlag = true; } /** * Cut the goal chain with reaction of the root goal */ public void cut() { rootGoal.cutMeet = true; rootGoal.clauseIterator = null; prevGoalAtChain = null; } /** * Cut the goal chain without reaction of the root chain */ public void cutLocal() { prevGoalAtChain = null; } /** * Inside function to check processor for a term structure and to set the * processor if it is not presented * * @param structure the structure to be checked, must not be null * @return the predicate processor for the structure if it is found or null */ private PredicateProcessor checkProcessor(final TermStruct structure) { PredicateProcessor processor = structure.getPredicateProcessor(); if (processor == null) { processor = context.findProcessor(structure); if (processor == null) { processor = PredicateProcessor.NULL_PROCESSOR; } structure.setPredicateProcessor(processor); } return processor; } /** * Check that the goal is completed and doesn't have anymore variants * * @return true if the goal is completed else false */ public boolean isCompleted() { return rootGoal.rootLastGoalAtChain == null || noMoreVariantsFlag; } }