package edu.stanford.nlp.semgraph.semgrex; import edu.stanford.nlp.util.logging.Redwood; import java.util.*; import edu.stanford.nlp.ling.IndexedWord; import edu.stanford.nlp.semgraph.SemanticGraph; /** @author Chloe Kiddon */ public class CoordinationPattern extends SemgrexPattern { /** A logger for this class */ private static Redwood.RedwoodChannels log = Redwood.channels(CoordinationPattern.class); /** * */ private static final long serialVersionUID = -3122330899634961002L; private boolean isConj; private boolean isNodeCoord; private List<SemgrexPattern> children; /* if isConj is true, then it is an "AND" ; if it is false, it is an "OR".*/ /* if isNodeCoord is true, then it is a node coordination conj; if it is false, then * it is a relation coordination conj. */ public CoordinationPattern(boolean isNodeCoord, List<SemgrexPattern> children, boolean isConj) { if (children.size() < 2) { throw new RuntimeException("Coordination node must have at least 2 children."); } this.children = children; this.isConj = isConj; this.isNodeCoord = isNodeCoord; } public boolean isNodeCoord() { return isNodeCoord; } @Override public void setChild(SemgrexPattern child) { if (isNodeCoord) { for (Object c : children) { if (c instanceof NodePattern) ((NodePattern)c).setChild(child); } } else { } } public void addRelnToNodeCoord(SemgrexPattern child) { if (isNodeCoord) { for (SemgrexPattern c : children) { List<SemgrexPattern> newChildren = new ArrayList<>(); newChildren.addAll(c.getChildren()); newChildren.add(child); c.setChild(new CoordinationPattern(false, newChildren, true)); } } } @Override public List<SemgrexPattern> getChildren() { return children; } @Override public String localString() { StringBuilder sb = new StringBuilder(); if (isNegated()) { sb.append('!'); } if (isOptional()) { sb.append('?'); } sb.append((isConj ? "and" : "or")); sb.append(" "); sb.append((isNodeCoord ? "node coordination" : "reln coordination")); return sb.toString(); } @Override public String toString() { return toString(true); } @Override public String toString(boolean hasPrecedence) { StringBuilder sb = new StringBuilder(); if (isConj) { for (SemgrexPattern node : children) { sb.append(node.toString()); } } else { sb.append('['); for (Iterator<SemgrexPattern> iter = children.iterator(); iter.hasNext();) { SemgrexPattern node = iter.next(); sb.append(node.toString()); if (iter.hasNext()) { sb.append(" |"); } } sb.append(']'); } return sb.toString(); } @Override public SemgrexMatcher matcher(SemanticGraph sg, IndexedWord node, Map<String, IndexedWord> namesToNodes, Map<String, String> namesToRelations, VariableStrings variableStrings, boolean ignoreCase) { return new CoordinationMatcher(this, sg, null, null, true, node, namesToNodes, namesToRelations, variableStrings, ignoreCase); } @Override public SemgrexMatcher matcher(SemanticGraph sg, Alignment alignment, SemanticGraph sg_align, boolean hypToText, IndexedWord node, Map<String, IndexedWord> namesToNodes, Map<String, String> namesToRelations, VariableStrings variableStrings, boolean ignoreCase) { return new CoordinationMatcher(this, sg, alignment, sg_align, hypToText, node, namesToNodes, namesToRelations, variableStrings, ignoreCase); } private static class CoordinationMatcher extends SemgrexMatcher { private SemgrexMatcher[] children; private final CoordinationPattern myNode; private int currChild; private final boolean considerAll; private IndexedWord nextNodeMatch = null; // 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 c, SemanticGraph sg, Alignment alignment, SemanticGraph sg_align, boolean hypToText, IndexedWord n, Map<String, IndexedWord> namesToNodes, Map<String, String> namesToRelations, VariableStrings variableStrings, boolean ignoreCase) { super(sg, alignment, sg_align, hypToText, n, namesToNodes, namesToRelations, variableStrings); myNode = c; children = new SemgrexMatcher[myNode.children.size()]; for (int i = 0; i < children.length; i++) { SemgrexPattern node = myNode.children.get(i); children[i] = node.matcher(sg, alignment, sg_align, hypToText, n, namesToNodes, namesToRelations, variableStrings, ignoreCase); } currChild = 0; considerAll = myNode.isConj ^ myNode.isNegated(); } @Override void resetChildIter() { currChild = 0; for (SemgrexMatcher aChildren : children) { aChildren.resetChildIter(); } nextNodeMatch = null; } @Override void resetChildIter(IndexedWord node) { // this.tree = node; currChild = 0; for (SemgrexMatcher aChildren : children) { aChildren.resetChildIter(node); } } // find the next local match @Override public boolean matches() { // also known as "FUN WITH LOGIC" //log.info(myNode.toString()); //log.info("consider all: " + considerAll); 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 (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; } else if (myNode.isNodeCoord) { nextNodeMatch = children[0].getMatch(); } return true; } } else { // oops, this didn't work. children[currChild].resetChildIter(); // go backwards to see if we can continue matching from an // earlier location. // TODO: perhaps there should be a version where we only // care about new assignments to the root, or new // assigments to the root and variables, in which case we // could make use of getChangesVariables() to optimize how // many nodes we can skip past --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++) { // namesToNodes.putAll(namesToNodesOld); // namesToRelations.putAll(namesToRelationsOld); if (myNode.isNegated() != children[currChild].matches()) { // a negated node should only match once (before being reset) if (myNode.isNegated()) { currChild = children.length; } if (myNode.isNodeCoord) nextNodeMatch = children[currChild].getMatch(); // this.namesToNodes.putAll(children[currChild].namesToNodes); // this.namesToRelations.putAll(children[currChild].namesToRelations); return true; } children[currChild].resetChildIter(); } if (myNode.isNegated()) { currChild = children.length; } return myNode.isOptional(); } } @Override public IndexedWord getMatch() { if (myNode.isNodeCoord && !myNode.isNegated()) { return nextNodeMatch; } else { throw new UnsupportedOperationException(); } } @Override public String toString() { String ret = "coordinate matcher for: "; for (SemgrexMatcher child : children) ret += child.toString() + " "; return ret; } } }