package edu.stanford.nlp.trees.tregex; import edu.stanford.nlp.trees.HeadFinder; import edu.stanford.nlp.trees.Tree; import java.util.Iterator; import java.util.List; import java.util.IdentityHashMap; import java.util.Map; class CoordinationPattern extends TregexPattern { private final boolean isConj; private final List<TregexPattern> children; /* if isConj is true, then it is an "AND" ; if it is false, it is an "OR".*/ public CoordinationPattern(List<TregexPattern> children, boolean isConj) { if (children.size() < 2) { throw new RuntimeException("Coordination node must have at least 2 children."); } this.children = children; this.isConj = isConj; // System.out.println("Made " + (isConj ? "and " : "or ") + "node with " + children.size() + " children."); } @Override public List<TregexPattern> getChildren() { return children; } @Override public String localString() { return (isConj ? "and" : "or"); } @Override public String toString() { StringBuilder sb = new StringBuilder(); if (isConj) { if (isNegated()) { sb.append("!("); } for (TregexPattern node : children) { sb.append(node.toString()); } if (isNegated()) { sb.append(")"); } } else { if (isNegated()) { sb.append("!"); } sb.append('['); for (Iterator<TregexPattern> iter = children.iterator(); iter.hasNext();) { TregexPattern node = iter.next(); sb.append(node.toString()); if (iter.hasNext()) { sb.append(" |"); } } sb.append(']'); } return sb.toString(); } @Override public TregexMatcher matcher(Tree root, Tree tree, IdentityHashMap<Tree, Tree> nodesToParents, Map<String, Tree> namesToNodes, VariableStrings variableStrings, HeadFinder headFinder) { return new CoordinationMatcher(this, root, tree, nodesToParents, namesToNodes, variableStrings, headFinder); } private static class CoordinationMatcher extends TregexMatcher { private TregexMatcher[] children; private final CoordinationPattern myNode; private int currChild; private final boolean considerAll; // do all con/dis-juncts have to be considered to determine a match? // i.e. true if conj and not negated or disj and negated public CoordinationMatcher(CoordinationPattern n, Tree root, Tree tree, IdentityHashMap<Tree, Tree> nodesToParents, Map<String, Tree> namesToNodes, VariableStrings variableStrings, HeadFinder headFinder) { super(root, tree, nodesToParents, namesToNodes, variableStrings, headFinder); myNode = n; children = new TregexMatcher[myNode.children.size()]; // lazy initialize the children... don't set children[i] yet //for (int i = 0; i < children.length; i++) { // TregexPattern node = myNode.children.get(i); // children[i] = node.matcher(root, tree, nodesToParents, // namesToNodes, variableStrings); //} currChild = 0; considerAll = myNode.isConj ^ myNode.isNegated(); } @Override void resetChildIter() { currChild = 0; for (TregexMatcher child : children) { if (child != null) { child.resetChildIter(); } } } @Override void resetChildIter(Tree tree) { this.tree = tree; currChild = 0; for (TregexMatcher child : children) { if (child != null) { child.resetChildIter(tree); } } } // find the next local match @Override public boolean matches() { // also known as "FUN WITH LOGIC" if (considerAll) { // these are the cases where all children must be considered to match if (currChild < 0) { // a past call to this node either got that it failed // matching or that it was a negative match that succeeded, // which we only want to accept once return myNode.isOptional(); } // we must have happily reached the end of a match the last // time we were here if (currChild == children.length) { --currChild; } while (true) { if (children[currChild] == null) { children[currChild] = myNode.children.get(currChild).matcher(root, tree, nodesToParents, namesToNodes, variableStrings, headFinder); children[currChild].resetChildIter(tree); } if (myNode.isNegated() != children[currChild].matches()) { // This node is set correctly. Move on to the next node ++currChild; if (currChild == children.length) { // yay, all nodes matched. if (myNode.isNegated()) { // a negated node should only match once (before being reset) currChild = -1; } return true; } } else { // oops, this didn't work. children[currChild].resetChildIter(); // go backwards to see if we can continue matching from an // earlier location. --currChild; if (currChild < 0) { return myNode.isOptional(); } } } } else { // these are the cases where a single child node can make you match for (; currChild < children.length; currChild++) { if (children[currChild] == null) { children[currChild] = myNode.children.get(currChild).matcher(root, tree, nodesToParents, namesToNodes, variableStrings, headFinder); children[currChild].resetChildIter(tree); } if (myNode.isNegated() != children[currChild].matches()) { // a negated node should only match once (before being reset) // otherwise you get repeated matches for every node that // causes the negated match to pass, which would be silly if (myNode.isNegated()) { currChild = children.length; } return true; } } if (myNode.isNegated()) { currChild = children.length; } for (int resetChild = 0; resetChild < currChild; ++resetChild) { // clean up variables that may have been set in previously // accepted nodes if (children[resetChild] != null) { children[resetChild].resetChildIter(); } } return myNode.isOptional(); } } @Override public Tree getMatch() { // in general, only DescriptionNodes can match // exception: if we are a positive disjunction, we care about // exactly one of the children, so we return its match if (!myNode.isConj && !myNode.isNegated()) { if (currChild >= children.length || currChild < 0 || children[currChild] == null) { return null; } else { return children[currChild].getMatch(); } } else { throw new UnsupportedOperationException(); } } } // end private class CoordinationMatcher private static final long serialVersionUID = -7797084959452603087L; }