/* * 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.TermList; import com.igormaznitsa.prol.data.Operator; import com.igormaznitsa.prol.containers.OperatorContainer; import com.igormaznitsa.prol.data.NumericTerm; import com.igormaznitsa.prol.data.TermStruct; import com.igormaznitsa.prol.data.Var; import com.igormaznitsa.prol.exceptions.ProlCriticalError; import com.igormaznitsa.prol.exceptions.ParserException; import com.igormaznitsa.prol.logic.ProlContext; import com.igormaznitsa.prol.parser.ProlTokenizer.ProlTokenizerResult; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; /** * The class implements the prol tree builder. It's very important class because * it allows to make term tree from read terms. * * @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com) * @see com.igormaznitsa.prol.parser.ProlTokenizer */ public final class ProlTreeBuilder { /** * Inside array of operators which will be used to read a prolog phrase */ private final OperatorContainer[] OPERATORS_PHRASE; /** * Inside array of operators which will be used to read an inside list */ private final OperatorContainer[] OPERATORS_INSIDE_LIST; /** * Inside array of operators which will be used to find the end of a list */ private final OperatorContainer[] OPERATORS_END_LIST; /** * Inside array of operators which will be used to read a structure */ private final OperatorContainer[] OPERATORS_INSIDE_STRUCT; /** * Inside array of operators which will be used to read a sub-block inside of * a block */ private final OperatorContainer[] OPERATORS_SUBBLOCK; /** * Inside auxulary class represents a tree item * * @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com) */ private static final class TreeItem { /** * The tree builder owner of the item */ private final ProlTreeBuilder builder; /** * The term saved by the item */ private final Term savedterm; /** * The left branch of the tree item */ private TreeItem leftBranch; /** * The right branch of the item */ private TreeItem rightBranch; /** * The link to the owner of the item */ private TreeItem owner; /** * The string position of the item at the read stream */ private final int strPos; /** * The line position of the item at the read stream */ private final int lineNum; /** * The term has been placed into brakes */ private final boolean atBrakes; /** * The constructor * * @param builder the builder which has the item, must not be null * @param term the term read from the input stream, must not be null * @param atBrakes the flag shows that the term was in the brakes so it has * the max priority * @param lineNum the line number of the line where the term has been found * @param strPos the string position of the read stream at the input stream */ private TreeItem(final ProlTreeBuilder builder, final Term term, final boolean atBrakes, final int lineNum, final int strPos) { savedterm = term; this.builder = builder; this.strPos = strPos; this.lineNum = lineNum; this.atBrakes = atBrakes; } /** * Get the priority oa the term. * * @return the priority of the term */ private int getPriority() { if (atBrakes) { return 0; } return savedterm.getPriority(); } /** * Set the right branch * * @param item the right branch for the term */ private void setRightBranch(final TreeItem item) { rightBranch = item; if (item != null) { item.owner = this; } } /** * Make an other ietm as the right branch * * @param item the item which will be used as the right branch * @return the item which will be used as the root, it can be this item or * setted item (it depends on priorities) */ private TreeItem makeAsRightBranch(final TreeItem item) { TreeItem currentSubbranch = rightBranch; setRightBranch(item); item.setLeftBranch(currentSubbranch); if (item.getItemType() == Term.TYPE_OPERATOR) { return item.getPriority() == 0 ? this : item; } return this; } /** * Make an other ietm as the left branch * * @param item the item which will be used as the left branch * @return the item which will be used as the root, it can be this item or * setted item (it depends on priorities) */ private TreeItem makeAsOwnerWithLeftBranch(final TreeItem item) { this.replaceForOwner(item); item.setLeftBranch(this); return item; } /** * Get the right branch of the item, can be null * * @return the right branch or null */ private TreeItem getRightBranch() { return rightBranch; } /** * Set the left branch of the item * * @param item the left branch for the item */ private void setLeftBranch(final TreeItem item) { leftBranch = item; if (item != null) { item.owner = this; } } /** * Get the left branch of the item, can be null * * @return the left branch of the item or null */ private TreeItem getLeftBranch() { return leftBranch; } /** * Get the type of the saved term by the item * * @return */ private int getItemType() { return savedterm.getTermType(); } /** * Find the root of the tree where the item has been placed * * @return the root item for the tree where the item has been placed */ private TreeItem findRoot() { if (owner == null) { return this; } return owner.findRoot(); } /** * Find the first node from owners of the item which has the same or lower * priority * * @param priority the priority to find an item * @return found root itemn which has the equal or less priority than the * value. */ private TreeItem findFirstNodeWithSuchOrLowerPriority(final int priority) { final TreeItem result; if (getPriority() >= priority || owner == null) { result = this; } else { result = owner.findFirstNodeWithSuchOrLowerPriority(priority); } return result; } /** * Replace the owner by other item * * @param newItem the item to replace current owner */ private void replaceForOwner(final TreeItem newItem) { if (owner == null) { newItem.owner = null; return; } if (this.equals(owner.getLeftBranch())) { owner.setLeftBranch(newItem); } else { owner.setRightBranch(newItem); } } /** * Get the operator type, the saved term must be an operator * * @return the operator type as integer */ private int getOperatorType() { return ((Operator) savedterm).getOperatorType(); } /** * Validate the tree item for its saved term * * @return true if the tree item is valid and false if it's not */ private boolean validate() { if (savedterm.getTermType() == Term.TYPE_OPERATOR) { final int priority = getPriority(); switch (((Operator) savedterm).getOperatorType()) { case Operator.OPTYPE_FX: { return leftBranch == null && (rightBranch != null && rightBranch.getPriority() < priority); } case Operator.OPTYPE_FY: { return leftBranch == null && (rightBranch != null && rightBranch.getPriority() <= priority); } case Operator.OPTYPE_YF: { return (leftBranch != null && leftBranch.getPriority() <= priority) && rightBranch == null; } case Operator.OPTYPE_XF: { return (leftBranch != null && leftBranch.getPriority() < priority) && rightBranch == null; } case Operator.OPTYPE_XFX: { return (leftBranch != null && leftBranch.getPriority() < priority) && (rightBranch != null && rightBranch.getPriority() < priority); } case Operator.OPTYPE_XFY: { return (leftBranch != null && leftBranch.getPriority() < priority) && (rightBranch != null && rightBranch.getPriority() <= priority); } case Operator.OPTYPE_YFX: { return (leftBranch != null && leftBranch.getPriority() <= priority) && (rightBranch != null && rightBranch.getPriority() < priority); } default: throw new ProlCriticalError("Unknown operator type"); } } else { return leftBranch == null && rightBranch == null; } } @Override public String toString() { return savedterm.toString(); } /** * Make the tree item into a term * * @return the tree item converted into a term */ private Term convertTreeItemIntoTerm() { Term result = null; switch (savedterm.getTermType()) { case Term.TYPE_OPERATOR: { final TermStruct operatorStruct; if (!validate()) { throw new ParserException("Wrong operator", lineNum, strPos); } final Term left = leftBranch != null ? leftBranch.convertTreeItemIntoTerm() : null; final Term right = rightBranch != null ? rightBranch.convertTreeItemIntoTerm() : null; if (left == null && right == null) { throw new ProlCriticalError("Operator without operands"); } // this code replaces '-'(number) to '-number' if ("-".equals(savedterm.getText()) && left == null) { if (right.getTermType() == Term.TYPE_ATOM && right instanceof NumericTerm) { result = (Term) ((NumericTerm) right).neg(); break; } } if (left != null) { if (right == null) { operatorStruct = new TermStruct((Operator) savedterm, new Term[]{left}); } else { operatorStruct = new TermStruct((Operator) savedterm, new Term[]{left, right}); } } else { operatorStruct = new TermStruct((Operator) savedterm, new Term[]{right}); } operatorStruct.setPredicateProcessor(builder.context.findProcessor(operatorStruct)); result = operatorStruct; } break; case Term.TYPE_STRUCT: { final TermStruct struct = (TermStruct) savedterm; struct.setPredicateProcessor(builder.context.findProcessor(struct)); result = savedterm; } break; default: { result = savedterm; } break; } return result; } } /** * Check that an operator has been presented into an operator array * * @param operator the checked operator, it can be null so the result will be * true * @param endOperators the array to be checked that it contains such operator, * it can be null so the result will be false * @return true if the array contains the operator else false */ private static boolean isEndOperator(final Term operator, final OperatorContainer[] endOperators) { if (operator == null) { return true; } if (endOperators == null) { return false; } if (operator.getTermType() == Term.TYPE_OPERATORS) { final String operatorName = operator.getText(); for (int li = 0; li < endOperators.length; li++) { if (endOperators[li].getText().equals(operatorName)) { return true; } } } return false; } /** * The map contains variable map which is being used to map variables inside a * tree */ private final Map<String, Var> variableSet; /** * The tokenizer which will be used to get tokens to build the tree */ private ProlTokenizer tokenizer; /** * The engine context which owns the tree builder */ private final ProlContext context; /** * The knowledge base of the context */ private final KnowledgeBase knowledgeBase; /** * The constructor * * @param context the context owns the tree builder, must not be null */ public ProlTreeBuilder(final ProlContext context) { variableSet = new HashMap<String, Var>(); this.context = context; knowledgeBase = context.getKnowledgeBase(); OPERATORS_PHRASE = new OperatorContainer[]{context.getSystemOperatorForName(".")}; OPERATORS_INSIDE_LIST = new OperatorContainer[]{context.getSystemOperatorForName(","), context.getSystemOperatorForName("]"), context.getSystemOperatorForName("|")}; OPERATORS_END_LIST = new OperatorContainer[]{context.getSystemOperatorForName("]")}; OPERATORS_INSIDE_STRUCT = new OperatorContainer[]{context.getSystemOperatorForName(","), context.getSystemOperatorForName(")")}; OPERATORS_SUBBLOCK = new OperatorContainer[]{context.getSystemOperatorForName(")")}; } /** * Parse a string and make a term tree from it. * * @param str the string to be parsed, must not be null * @return the term tree * @throws IOException it will be thrown it there will be any transport error * @throws InterruptedException it will be thrown if the thread has been * interrupted */ public synchronized final Term readPhraseAndMakeTree(final String str) throws IOException, InterruptedException { return this.readPhraseAndMakeTree(new ProlReader(str)); } /** * Read and make tree with predefined both a prol reader and a prol tokenizer * * @param tokenizer the tokenizer to be used for parsing, must not be null * @param reader the reader to be used for reading, must not be null * @return the term tree or null if the stream end has been reached * @throws IOException it will be thrown it there will be any transport error */ public synchronized final Term readPhraseAndMakeTree(final ProlTokenizer tokenizer, final ProlReader reader) throws IOException { variableSet.clear(); try { this.tokenizer = tokenizer; final Term result = readBlock(reader, OPERATORS_PHRASE); if (result == null) { return null; // end_of_file } final ProlTokenizerResult endAtom = tokenizer.nextToken(reader, knowledgeBase); if (endAtom == null || !endAtom.getText().equals(".")) { throw new ParserException("End operator is not found", reader.getLineNumber(), reader.getStrPos()); } return result; } finally { variableSet.clear(); } } /** * Read and make tree with a predefined prol reader * * @param reader the prol reader which will be used to read next token, must * not be null * @return the term tree * @throws IOException it will be thrown it there will be any transport error * @throws InterruptedException it will be thrown if the thread has been * interrupted */ public synchronized final Term readPhraseAndMakeTree(final ProlReader reader) throws IOException, InterruptedException { return this.readPhraseAndMakeTree(new ProlTokenizer(), reader); } /** * Read a struct with a predefined functor from a reader * * @param functor the functor for the read structure, must not be null * @param reader the reader to read the structure, must not be null * @return read structure or null if the stream end has been reached * @throws IOException it will be thrown if there will be any transport error * @throws InterruptedException it will be thrown if the thread has been * interrupted */ private TermStruct readStruct(final Term functor, final ProlReader reader) throws IOException { final ArrayList<Term> listOfAtoms = new ArrayList<Term>(); while (true) { final Term block = readBlock(reader, OPERATORS_INSIDE_STRUCT); final ProlTokenizerResult nextAtom = tokenizer.nextToken(reader, knowledgeBase); final String nextText = nextAtom.getText(); if (",".equals(nextText)) { // next item if (block == null) { throw new ParserException("Empty structure element", tokenizer.getLastTokenLineNum(), tokenizer.getLastTokenStrPos()); } else { listOfAtoms.add(block); } } else if (")".equals(nextText)) { // end of the structure if (block != null) { listOfAtoms.add(block); } break; } } final TermStruct result = new TermStruct(functor, listOfAtoms.toArray(new Term[listOfAtoms.size()])); result.setPredicateProcessor(context.findProcessor(result)); return result; } /** * Read a list with a predefined reader * * @param reader the reader which will be used to read the input stream, must * not be null * @return the read list or null if the stream end reached * @throws IOException it will be thrown if there is any transport error */ private Term readList(final ProlReader reader) throws IOException { TermList leftPart = TermList.NULLLIST; TermList leftPartFirst = leftPart; Term rightPart = null; boolean hasSeparator = false; boolean doRead = true; while (doRead) { final Term block = readBlock(reader, OPERATORS_INSIDE_LIST); ProlTokenizerResult nextAtom = tokenizer.nextToken(reader, knowledgeBase); final String text = nextAtom.getText(); if ("]".equals(text)) { // end doRead = false; if (block == null) { continue; } } else if ("|".equals(text)) { // we have found the list tail, so we need read it as one block until the ']' atom if (block == null) { throw new ParserException("There is not any list element", tokenizer.getLastTokenLineNum(), tokenizer.getLastTokenStrPos()); } if (leftPartFirst.isNullList()) { leftPartFirst = TermList.appendItem(leftPart, block); leftPart = leftPartFirst; } else { leftPart = TermList.appendItem(leftPart, block); } hasSeparator = true; rightPart = readBlock(reader, OPERATORS_END_LIST); nextAtom = tokenizer.nextToken(reader, knowledgeBase); if (!nextAtom.getText().equals("]")) { throw new ParserException("Wrong end of the list tail", tokenizer.getLastTokenLineNum(), tokenizer.getLastTokenStrPos()); } break; } else if (",".equals(text)) { // all good and we read next block if (block == null) { throw new ParserException("List element not found", tokenizer.getLastTokenLineNum(), tokenizer.getLastTokenStrPos()); } } else { throw new ProlCriticalError("Nonprocessd state at list definition"); } if (leftPartFirst.isNullList()) { leftPartFirst = TermList.appendItem(leftPart, block); leftPart = leftPartFirst; } else { leftPart = TermList.appendItem(leftPart, block); } } if (hasSeparator) { // '|' separator was found at the list if (rightPart == null) { throw new ParserException("There is not any term as the tail at the list", tokenizer.getLastTokenLineNum(), tokenizer.getLastTokenStrPos()); } leftPartFirst.replaceLastElement(rightPart); } return leftPartFirst; } /** * Read a block from a predefined reader * * @param reader the predefined reader, must not be null * @param endOperators the array contains end operators which will be bounding * the block, it can be null but it will be no good indeed * @return a read block as Term or null if the end of the stream has been * reached * @throws IOException it will be thrown if there is any transport error */ private Term readBlock(final ProlReader reader, final OperatorContainer[] endOperators) throws IOException { // the variable will contain last processed tree item contains either atom or operator TreeItem currentTreeItem = null; while (true) { // read next atom from tokenizer ProlTokenizerResult readAtomContainer = tokenizer.nextToken(reader, knowledgeBase); boolean atBrakes = false; if (readAtomContainer == null) { if (currentTreeItem == null) { // end_of_file return null; } else { // non closed something throw new ParserException("Not-ended phrase", tokenizer.getLastTokenLineNum(), tokenizer.getLastTokenStrPos()); } } Term readAtom = readAtomContainer.getTerm(); // check the atom to be the end atom if (isEndOperator(readAtom, endOperators)) { // it's an end atom so we push it back and end the cycle tokenizer.pushTermBack(readAtomContainer); break; } // the variable contains calculated atem priority (it can be not the same as the nature priority) int readAtomPriority = 0; // we make it as zero (the highest priority) default // check read atom type if (readAtom.getTermType() == Term.TYPE_OPERATORS) { // it is operator list // try to get the single operator from the list if the linst contains only one final Operator readOperator = ((OperatorContainer) readAtom).getOperatorIfSingle(); // check that the operator is single if (readOperator == null) { //there are a few operators in the list so we need to select one final OperatorContainer readOperators = (OperatorContainer) readAtom; boolean leftPresented = false; if (currentTreeItem != null) { if (currentTreeItem.getItemType() != Term.TYPE_OPERATOR) { leftPresented = true; } else { if (currentTreeItem.getRightBranch() != null) { leftPresented = true; } } } final boolean rightPresented = !isEndOperator(tokenizer.peekToken(reader, knowledgeBase).getTerm(), endOperators); readAtom = readOperators.getCompatibleOperator(leftPresented, rightPresented); if (readAtom == null) { // we didn't get any operator for our criteria, so throw an exception throw new ParserException("Incompatible operator type", tokenizer.getLastTokenLineNum(), tokenizer.getLastTokenStrPos()); } // we have found needed operator so get its priority readAtomPriority = readAtom.getPriority(); } else { readAtom = readOperator; final String operatorText = readOperator.getText(); if (operatorText.length() == 1) { if ("[".equals(operatorText)) { // it's a list readAtom = readList(reader); readAtomPriority = 0; } else if ("(".equals(operatorText)) { // read subblock atBrakes = true; readAtom = readBlock(reader, OPERATORS_SUBBLOCK); readAtomPriority = 0; final Term closingAtom = tokenizer.nextToken(reader, knowledgeBase).getTerm(); if (closingAtom == null || !closingAtom.getText().equals(")")) { throw new ParserException("Non-closed brakes", reader.getLineNumber(), reader.getStrPos()); } } else { readAtomPriority = readOperator.getPriority(); } } else { readAtomPriority = readOperator.getPriority(); } } } else { final ProlTokenizerResult nextToken = tokenizer.nextToken(reader, knowledgeBase); if (nextToken != null && nextToken.getText().equals("(")) { // it is a structure if (readAtom.getTermType() == Term.TYPE_ATOM) { readAtom = readStruct(readAtom, reader); } else { tokenizer.pushTermBack(nextToken); throw new ParserException("You must have an atom as the structure functor", tokenizer.getLastTokenLineNum(), tokenizer.getLastTokenStrPos()); } } else { // push back the next atom tokenizer.pushTermBack(nextToken); // check read atom to be zero-struct if (readAtomContainer.getState() == ProlTokenizerResult.STATE_ATOM && context.hasZeroArityPredicateForName(readAtom.getText())) { readAtom = new TermStruct(readAtom); } } } // check for variable if (readAtom.getTermType() == Term.TYPE_VAR) { // it's a variable final Var var = (Var) readAtom; if (!var.isAnonymous()) { // it's not an anonymous variable so we need to process it and cache if it is not at the var table yet final Var cachedVar = variableSet.get(var.getText()); if (cachedVar == null) { // first meet variable // cache it variableSet.put(var.getText(), var); } else { // set cached variable instead of current value readAtom = cachedVar; } } } final TreeItem readAtomTreeItem = new TreeItem(this, readAtom, atBrakes, tokenizer.getLastTokenLineNum(), tokenizer.getLastTokenStrPos()); if (currentTreeItem == null) { // it's first currentTreeItem = readAtomTreeItem; } else { // not first if (currentTreeItem.getItemType() == Term.TYPE_OPERATOR) { // it's an operator if (currentTreeItem.getPriority() <= readAtomPriority) { // new has low priority // make its as an ascendent final TreeItem foundItem = currentTreeItem.findFirstNodeWithSuchOrLowerPriority(readAtomPriority); if (foundItem.getPriority() < readAtomPriority) { // make as parent currentTreeItem = foundItem.makeAsOwnerWithLeftBranch(readAtomTreeItem); } else if (foundItem.getPriority() > readAtomPriority) { // make new as right subbranch currentTreeItem = foundItem.makeAsRightBranch(readAtomTreeItem); } else { // equals priority switch (foundItem.getOperatorType()) { case Operator.OPTYPE_XF: case Operator.OPTYPE_YF: case Operator.OPTYPE_FX: case Operator.OPTYPE_XFX: case Operator.OPTYPE_YFX: { currentTreeItem = foundItem.makeAsOwnerWithLeftBranch(readAtomTreeItem); } break; case Operator.OPTYPE_FY: case Operator.OPTYPE_XFY: { currentTreeItem = foundItem.makeAsRightBranch(readAtomTreeItem); } break; default: throw new ProlCriticalError("Unknown operator type"); } } } else if (currentTreeItem.getPriority() > readAtomPriority) { // new has great priority if (readAtomTreeItem.getItemType() != Term.TYPE_OPERATOR) { // it's a ground atom // so check that the right branch is empty if (currentTreeItem.getRightBranch() != null) { throw new ParserException("There is not any operator before the atom", tokenizer.getLastTokenLineNum(), tokenizer.getLastTokenStrPos()); } } // make it as right currentTreeItem = currentTreeItem.makeAsRightBranch(readAtomTreeItem); } } else { // check that it is an operator if (currentTreeItem != null && currentTreeItem.getItemType() != Term.TYPE_OPERATOR && readAtomTreeItem.getItemType() != Term.TYPE_OPERATOR) { throw new ParserException("There must be an operator between atoms or structures", tokenizer.getLastTokenLineNum(), tokenizer.getLastTokenStrPos()); } // make it as left branch currentTreeItem = currentTreeItem.makeAsOwnerWithLeftBranch(readAtomTreeItem); } } } if (currentTreeItem == null) { return null; } else { return currentTreeItem.findRoot().convertTreeItemIntoTerm(); } } }