/* * 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.parser; import com.igormaznitsa.prol.containers.KnowledgeBase; import com.igormaznitsa.prol.data.Term; import com.igormaznitsa.prol.data.Operator; import com.igormaznitsa.prol.data.TermStruct; import com.igormaznitsa.prol.data.Var; import com.igormaznitsa.prol.exceptions.ParserException; import com.igormaznitsa.prol.exceptions.ProlHaltExecutionException; import com.igormaznitsa.prol.exceptions.ProlKnowledgeBaseException; import com.igormaznitsa.prol.logic.Goal; import com.igormaznitsa.prol.logic.ProlContext; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.Writer; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; /** * The class allows to input and parse a source string or resource to be placed * into a context. It could be called as interface of a context with an outside * world. Because you can enter data into context knowledge base only with the * consult object * * @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com) * @see com.igormaznitsa.prol.logic.ProlContext */ public class ProlConsult { /** * Inside logger for the library, as the logger name the canonical class name * is used (ProlConsult.class.getCanonicalName()) */ private static final Logger LOG = Logger.getLogger(ProlConsult.class.getCanonicalName()); /** * The variable contains reader to read data from a resource */ private final ProlReader reader; /** * The context bounding the consult object */ private final ProlContext context; /** * The knowledge base from the context will be saved here for speed */ private final KnowledgeBase base; /** * A constructor allows to create consult object without read operations * * @param context the context for the consult object, must not be null */ public ProlConsult(final ProlContext context) { this((ProlReader) null, context); } /** * A constructor allows to make consulting from a String object * * @param string the string object to be consulted, must not be null * @param context the context for the consult object, must not be null */ public ProlConsult(final String string, final ProlContext context) { this(new ProlReader(string), context); } /** * A constructor allows to make consulting from an input stream * * @param in the input stream to be the source for the consulting, must not be * null * @param context the context for the consult object, must not be null */ public ProlConsult(final InputStream in, final ProlContext context) { this(new ProlReader(in), context); } /** * A constructor allows to make consulting from a reader * * @param reader the reader to be the source for the consulting, must not be * null * @param context the context for the consult object, must not be null */ public ProlConsult(final Reader reader, final ProlContext context) { this(new ProlReader(reader), context); } /** * A constructor allows to make consulting from a prol reader * * @param reader the prol reader to be the source for the consulting, must not * be null * @param context the context for the consult object, must not be null */ public ProlConsult(final ProlReader reader, final ProlContext context) { this.reader = reader; this.context = context; this.base = context.getKnowledgeBase(); } /** * To consult for defined input source (if it presented) * * @throws IOException will be thrown if there will be any erro during a * transport operation */ public void consult() throws IOException { if (this.reader == null) { return; } final ProlTreeBuilder treeBuilder = new ProlTreeBuilder(context); final ProlTokenizer tokenizer = new ProlTokenizer(); final Thread thisthread = Thread.currentThread(); while (!thisthread.isInterrupted()) { final Term nextItem = treeBuilder.readPhraseAndMakeTree(tokenizer, reader); if (nextItem == null) { break; } final int line = tokenizer.getLastTokenLineNum(); final int strpos = tokenizer.getLastTokenStrPos(); try { switch (nextItem.getTermType()) { case Term.TYPE_ATOM: { base.assertZ(new TermStruct(nextItem)); } break; case Term.TYPE_STRUCT: { final TermStruct struct = (TermStruct) nextItem; final Term functor = struct.getFunctor(); switch (functor.getTermType()) { case Term.TYPE_OPERATOR: { final Operator op = (Operator) functor; final String text = op.getText(); final int type = op.getOperatorType(); if (struct.isFunctorLikeRuleDefinition()) { switch (type) { case Operator.OPTYPE_XFX: { // new rule base.assertZ(struct); } break; case Operator.OPTYPE_FX: { // directive if (!processDirective(struct.getElement(0))) { throw new ProlHaltExecutionException(2); } } break; } } else if ("?-".equals(text)) { // goal final Reader userreader = context.getStreamManager().getReaderForResource("user"); final Writer userwriter = context.getStreamManager().getWriterForResource("user", true); final Term termGoal = struct.getElement(0); if (userwriter != null) { userwriter.write("Goal: "); userwriter.write(termGoal.forWrite()); userwriter.write("\r\n"); } final Map<String, Var> varmap = new HashMap<String, Var>(); int solutioncounter = 0; final Goal thisGoal = new Goal(termGoal, context, null); while (true) { varmap.clear(); if (solveGoal(thisGoal, varmap)) { solutioncounter++; if (userwriter != null) { userwriter.write("\r\nYES\r\n"); if (!varmap.isEmpty()) { for (Entry<String, Var> avar : varmap.entrySet()) { final String name = avar.getKey(); final Var value = avar.getValue(); userwriter.write(name); userwriter.write('='); if (value.isUndefined()) { userwriter.write("???\r\n"); } else { userwriter.write(value.forWrite()); userwriter.write("\r\n"); } userwriter.flush(); } } } } else { if (userwriter != null) { userwriter.write(solutioncounter + (solutioncounter > 1 ? " Solutions\r\n" : " Solution\r\n")); userwriter.write("\r\nNO\r\n"); } break; } if (userwriter != null && userreader != null) { userwriter.append("Next solution? "); final int chr = userreader.read(); if (chr < 0) { if (userwriter != null) { userwriter.append(" Can't get the key value, it is possible that execution was canceled."); } break; } else { if (chr == ';') { if (userwriter != null) { userwriter.append("\r\n"); } } else { if (userwriter != null) { userwriter.write("\r\n" + solutioncounter + (solutioncounter > 1 ? " Solutions\r\n" : " Solution\r\n")); userwriter.append("Stopped by user"); } break; } } } } if (userwriter != null) { userwriter.flush(); } throw new ProlHaltExecutionException("Halted because goal failed.", 1); } else { base.assertZ(struct); } } break; default: { base.assertZ(struct); } break; } } break; default: { throw new ProlKnowledgeBaseException("Such element can't be saved at knowledge base [" + nextItem + ']'); } } } catch (Throwable ex) { LOG.log(Level.SEVERE, "consult()", ex); if (ex instanceof ThreadDeath) { throw (ThreadDeath) ex; } //context.halt(); throw new ParserException(ex.getMessage(), line, strpos, ex); } } } /** * Solve a goal for the context and fill the table by variables of the goal, * it finds the first solution for the goal if the var table is null * * @param goal the goal to be solved must not be null * @param varTable the table to be filled by variables, can be null if you * don't need it * @return true if the goal was solved, else false because the goal was not * solved * @throws IOException it will be thrown if there will be any problem to parse * goal * @throws InterruptedException it will be thrown if the thread has been * interrupted */ public boolean processGoal(final String goal, final Map<String, Var> varTable) throws IOException, InterruptedException { final ProlTreeBuilder treebuilder = new ProlTreeBuilder(context); final Term term = treebuilder.readPhraseAndMakeTree(goal); return processGoal(term, varTable); } /** * Solve a goal (presented as term) for the context and fill the table by * variables of the goal, it finds the first solution for the goal if the var * table is null * * @param goalterm the goal to be solved must not be null * @param varTable the table to be filled by variables, can be null if you * don't need it * @return true if the goal was solved, else false because the goal was not * solved * @throws InterruptedException it will be thrown if the thread has been * interrupted */ public boolean processGoal(final Term goalterm, final Map<String, Var> varTable) throws InterruptedException { final Goal goal = new Goal(goalterm, context, null); Term result = goal.solve(); if (result != null && varTable != null) { result.fillVarables(varTable); } return result != null; } /** * Inside function to get next solution of a goal and fill a hash map by its * variables * * @param goal the goal to get next solution, must not be null * @param varTable a hash map which should be filled by variables from the * goal, can be null * @return true if there is a solution, else false * @throws InterruptedException it will be thrown if the thread has been * interruped */ private boolean solveGoal(final Goal goal, final Map<String, Var> varTable) throws InterruptedException { final Term result = goal.solve(); if (result != null && varTable != null) { result.fillVarables(varTable); } return result != null; } /** * process directive from consulted stream (started from ':-'), it will be * solved single time. * * @param directive the directive term, must not be null * @return true true if the goal solved successfully, false if the goal failed * @throws IOException it will be thrown if there is any problem with IO * operations * @throws InterruptedException it will be thrown if the goal is interrupted */ private boolean processDirective(final Term directive) throws IOException, InterruptedException { final Goal goal = new Goal(directive, context, null); return goal.solve() != null; } }