// This file is part of AceWiki.
// Copyright 2008-2013, AceWiki developers.
//
// AceWiki is free software: you can redistribute it and/or modify it under the terms of the GNU
// Lesser General Public License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// AceWiki is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License along with AceWiki. If
// not, see http://www.gnu.org/licenses/.
package ch.uzh.ifi.attempto.chartparser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* This class represents an edge of a chart to be used by a chart parser. Edges are derived from grammar
* rules. An edge contains the head and body categories of the rule it is derived from. In addition, an
* edge has a start position and an end position that define the range of the text that is covered.
* Furthermore, every edge has a progress value that records how many of the body categories are parsed.
* Edges that have unparsed categories are called "active" whereas edges for which all body categories are
* parsed are called "passive". In order to capture accessibility constraints for anaphoric
* references, edges are either scope-closing or not, like rules. Additionally, edges contain two lists that
* store the feature maps of the accessible antecedents. The first list stores the antecedents from outside the
* range of the edge (the external antecedent list) and the second list stores the antecedents from inside the
* range of the edge (the internal antecedent list).
*
* @see GrammarRule
* @author Tobias Kuhn
*/
class Edge {
private int startPos;
private int endPos;
private Category head;
private Category[] externalAnteList;
private Category[] internalAnteList;
private boolean scopeclosing;
private Category[] body;
private int progress = 0;
private String identifier;
private Annotation annotation;
private List<Edge> links;
private Edge() {
}
/**
* Creates a new edge on the basis of a grammar rule.
*
* @param pos Start and end position.
* @param rule The grammar rule that is the basis for the creation of the edge.
*/
Edge(int pos, GrammarRule rule) {
this.startPos = pos;
this.endPos = pos;
this.head = rule.getHead();
this.body = rule.getBody();
this.externalAnteList = new Category[0];
this.internalAnteList = new Category[0];
this.scopeclosing = rule.isScopeClosing();
this.annotation = rule.getAnnotation();
this.links = new ArrayList<Edge>();
}
/**
* Creates a new edge on the basis of a grammar rule.
*
* @param pos Start and end position.
* @param rule The grammar rule that is the basis for the creation of the edge.
* @param externalAnteList The list of external antecedents.
*/
Edge(int pos, GrammarRule rule, Category[] externalAnteList) {
this.startPos = pos;
this.endPos = pos;
this.head = rule.getHead();
this.body = rule.getBody();
if (externalAnteList == null || rule.hasEmptyBody()) {
this.externalAnteList = new Category[0];
} else {
this.externalAnteList = externalAnteList;
}
this.internalAnteList = new Category[0];
this.scopeclosing = rule.isScopeClosing();
this.annotation = rule.getAnnotation();
this.links = new ArrayList<Edge>();
}
/**
* Creates a new edge on the basis of a lexical rule.
*
* @param pos Start and end position.
* @param lexRule The lexical rule that is the basis for the creation of the edge.
*/
Edge(int pos, LexicalRule lexRule) {
this.startPos = pos;
this.endPos = pos;
this.head = lexRule.getCategory();
this.body = new Category[] {lexRule.getWord()};
this.externalAnteList = new Category[0];
this.internalAnteList = new Category[0];
this.annotation = lexRule.getAnnotation();
this.scopeclosing = false;
this.links = new ArrayList<Edge>();
}
/**
* Creates a new edge on the basis of a head category. The end position is the start position
* plus one.
*
* @param startPos Start position.
* @param head The head category.
*/
Edge(int startPos, Terminal head) {
this.startPos = startPos;
this.endPos = startPos + 1;
this.head = head;
this.body = new Category[0];
this.externalAnteList = new Category[0];
this.internalAnteList = new Category[0];
this.annotation = new Annotation();
this.scopeclosing = false;
this.links = new ArrayList<Edge>();
}
/**
* Returns the start position of this edge.
*
* @return The start position.
*/
public int getStartPos() {
return startPos;
}
/**
* Returns the end position of this edge.
*
* @return The end position.
*/
public int getEndPos() {
return endPos;
}
/**
* Returns the head category of this edge.
*
* @return The head category.
*/
public Category getHead() {
return head;
}
/**
* Returns the body categories of this edge.
*
* @return The body categories.
*/
public Category[] getBody() {
return body;
}
/**
* Returns the progress value of this edge.
*
* @return The progress value.
*/
public int getProgress() {
return progress;
}
/**
* Returns the list of external antecedents.
*
* @return The external antecedents.
*/
public Category[] getExternalAnteList() {
return externalAnteList;
}
/**
* Returns the list of internal antecedents.
*
* @return The internal antecedents.
*/
public Category[] getInternalAnteList() {
return internalAnteList;
}
/**
* Returns a list that is a concatenation of the lists of external and internal antecedents.
*
* @return A combined antecedents list.
*/
public Category[] getCombinedAnteList() {
Category[] l = new Category[externalAnteList.length + internalAnteList.length];
for (int i = 0 ; i < externalAnteList.length ; i++) {
l[i] = externalAnteList[i];
}
for (int i = 0 ; i < internalAnteList.length ; i++) {
l[externalAnteList.length + i] = internalAnteList[i];
}
return l;
}
/**
* Returns the annotation object for this edge.
*
* @return The annotation object.
*/
public Annotation getAnnotation() {
return annotation;
}
/**
* Returns true if this edge is active, i.e. has unparsed body categories.
*
* @return true if this edge is active.
*/
public boolean isActive() {
return body.length > progress;
}
/**
* Returns true if this edge is scope-closing, i.e. is derived from an scope-closing rule.
*
* @return true if this edge is scope-closing.
*/
public boolean isScopeClosing() {
return scopeclosing;
}
/**
* Returns true if this edge carries antecedent information. Edge with an empty body and edges
* that originate from lexical rules do not carry antecedent information. All other edges do.
*
* @return true if this edge carries antecedent information.
*/
public boolean carriesAntecedentInformation() {
return body.length == 0 || (head instanceof Preterminal);
}
/**
* Returns the first active category, i.e. the first body category that has not yet been
* parsed. null is returned for inactive edges.
*
* @return The first active category.
*/
public Category getNextActive() {
if (!isActive()) return null;
return body[progress];
}
/**
* Returns the linked edges, i.e. the edges on the basis of which this edge has been completed.
* It is assumed that the grammar is unambiguous. For ambiguous grammars, the links might be
* incomplete or incorrect.
*
* @return The edges that are children of this edge.
*/
public List<Edge> getLinks() {
return links;
}
/**
* Moves the parsing progress forward to the given position. The given edge is stored as a link.
* No checking is done within this method whether this operation is actually possible according
* to the rules of chart parsing.
*
* @param pos The end position to move forward to.
* @param edge The edge that is used for moving forward.
*/
void step(int pos, Edge edge) {
if (!isActive()) {
throw new RuntimeException("Passive edge");
}
progress++;
endPos = pos;
boolean restr = false;
for (Category c : edge.getInternalAnteList()) {
if (!edge.isScopeClosing()) {
addAntecedents(c);
} else if (c.getName().equals("//")) {
restr = true;
} else if (!restr || c.getName().equals(">>")) {
addAntecedents(c);
}
}
links.add(edge);
}
/**
* Moves the parsing progress one step forward while keeping the same end position.
* No checking is done within this method whether this operation is actually possible according
* to the rules of chart parsing.
*/
void step() {
if (!isActive()) {
throw new RuntimeException("Passive edge");
}
progress++;
links.add(null);
}
/**
* Adds the feature maps to the list of internal antecedents.
*
* @param newAnteList The feature maps of internal antecedents.
*/
void addAntecedents(Category... newAnteList) {
Category[] oldInternalList = internalAnteList;
int l1 = oldInternalList.length;
int l2 = newAnteList.length;
internalAnteList = new Category[l1 + l2];
for (int i = 0 ; i < l1 ; i++) {
internalAnteList[i] = oldInternalList[i];
}
for (int i = 0 ; i < l2 ; i++) {
internalAnteList[l1 + i] = newAnteList[i];
}
}
/**
* Unifies this edge with the given edge. If the unification fails, a
* UnificationFailedException is thrown. In this case, the two edges remain partly unified,
* i.e. no backtracking is done. Thus, this operation should be perfomed only if it is certain that
* the unification succeeds, or if the operation is performed on copies of objects that are not
* used anymore afterwards.
*
* @param edge The edge to be unified with this edge.
* @throws UnificationFailedException If unification fails.
*/
void unify(Edge edge) throws UnificationFailedException {
if (scopeclosing != edge.scopeclosing) throw new UnificationFailedException();
if (body.length != edge.body.length) throw new UnificationFailedException();
if (progress != edge.progress) throw new UnificationFailedException();
if (startPos != edge.startPos) throw new UnificationFailedException();
if (endPos != edge.endPos) throw new UnificationFailedException();
if (externalAnteList.length != edge.externalAnteList.length) throw new UnificationFailedException();
if (internalAnteList.length != edge.internalAnteList.length) throw new UnificationFailedException();
head.unify(edge.head);
for (int i = 0 ; i < externalAnteList.length ; i++) {
externalAnteList[i].unify(edge.externalAnteList[i]);
}
for (int i = 0 ; i < internalAnteList.length ; i++) {
internalAnteList[i].unify(edge.internalAnteList[i]);
}
for (int i = 0 ; i < body.length ; i++) {
body[i].unify(edge.body[i]);
}
}
/**
* Tries to unify this edge with another edge. If unification is not possible, an exception
* is thrown. In the case unification would be possible, the unification is not performed completely.
* In any case the two edges remain in an unconsistent state afterwards. Thus, this operation should
* be performed only on copies of objects that are not used anymore afterwards.
*
* @param edge The edge to be unified with this edge.
* @throws UnificationFailedException If unification fails.
*/
void tryToUnify(Edge edge) throws UnificationFailedException {
if (scopeclosing != edge.scopeclosing) throw new UnificationFailedException();
if (body.length != edge.body.length) throw new UnificationFailedException();
if (progress != edge.progress) throw new UnificationFailedException();
if (startPos != edge.startPos) throw new UnificationFailedException();
if (endPos != edge.endPos) throw new UnificationFailedException();
if (externalAnteList.length != edge.externalAnteList.length) throw new UnificationFailedException();
if (internalAnteList.length != edge.internalAnteList.length) throw new UnificationFailedException();
if (externalAnteList.length != edge.externalAnteList.length) throw new UnificationFailedException();
if (internalAnteList.length != edge.internalAnteList.length) throw new UnificationFailedException();
head.tryToUnify(edge.head);
for (int i = 0 ; i < externalAnteList.length ; i++) {
externalAnteList[i].tryToUnify(edge.externalAnteList[i]);
}
for (int i = 0 ; i < internalAnteList.length ; i++) {
internalAnteList[i].tryToUnify(edge.internalAnteList[i]);
}
for (int i = 0 ; i < body.length ; i++) {
body[i].tryToUnify(edge.body[i]);
}
}
/**
* This methods checks whether two edges are similar. Two edges are similar if and only if
* the replacement of every variable occurence by a new singleton variable (in both edges)
* would make them equal (apart from variable names). Edges that are not similar can never
* unify. Similar edges may or may not unify.
*
* @param edge The edge for which similarity with this edge should be checked.
* @return true if the two edges are similar.
*/
boolean isSimilar(Edge edge) {
if (scopeclosing != edge.scopeclosing) return false;
if (startPos != edge.startPos) return false;
if (endPos != edge.endPos) return false;
if (!head.isSimilar(edge.head)) return false;
if (body.length != edge.body.length) return false;
if (progress != edge.progress) return false;
if (externalAnteList.length != edge.externalAnteList.length) return false;
if (internalAnteList.length != edge.internalAnteList.length) return false;
for (int i = 0 ; i < externalAnteList.length ; i++) {
if (!externalAnteList[i].isSimilar(edge.externalAnteList[i])) return false;
}
for (int i = 0 ; i < internalAnteList.length ; i++) {
if (!internalAnteList[i].isSimilar(edge.internalAnteList[i])) return false;
}
for (int i = 0 ; i < body.length ; i++) {
if (!body[i].isSimilar(edge.body[i])) return false;
}
return true;
}
/**
* This method returns true if this edge subsumes (i.e. is more general than) the given edge,
* or false otherwise.
*
* @param edge The edge for which it is checked whether this edge subsumes it.
* @return true if this edge subsumes the given edge.
*/
boolean subsumes(Edge edge) {
if (!isSimilar(edge)) return false;
// Both edges are copied to keep the existing edges untouched:
Edge edge1C = deepCopy();
Edge edge2C = edge.deepCopy();
// Edge 1 subsumes edge 2 iff 1 unifies with 2 after the skolemization of 2.
edge2C.skolemize();
try {
edge1C.tryToUnify(edge2C);
return true;
} catch (UnificationFailedException ex) {
return false;
}
}
/**
* Skolemizes the variables of this edge.
*/
private void skolemize() {
head.skolemize();
for (Category c : externalAnteList) c.skolemize();
for (Category c : internalAnteList) c.skolemize();
for (Category c : body) c.skolemize();
}
/**
* Creates a deep copy of this edge. This means that all categories are copied. The child edges
* can be linked or copied.
*
* @param copyChildren defines whether child edges should be copied or just linked.
* @return A deep copy.
*/
Edge deepCopy(boolean copyChildren) {
return deepCopy(new HashMap<Integer, StringObject>(), copyChildren);
}
/**
* Creates a deep copy of this edge. All categories of this edge are copied, but the child
* edges are linked without being copied.
*
* @return A deep copy.
*/
Edge deepCopy() {
return deepCopy(false);
}
/**
* Creates a deep copy of this edge using the given string objects. This method is usually
* called form another deepCopy-method. The child edges can be linked or copied.
*
* @param stringObjs The string objects to be used.
* @param copyChildren defines whether child edges should be copied or just linked.
* @return A deep copy.
*/
Edge deepCopy(HashMap<Integer, StringObject> stringObjs, boolean copyChildren) {
Edge edgeC = new Edge();
edgeC.startPos = startPos;
edgeC.endPos = endPos;
edgeC.progress = progress;
edgeC.scopeclosing = scopeclosing;
edgeC.head = head.deepCopy(stringObjs);
edgeC.body = new Category[body.length];
for (int i=0 ; i < body.length ; i++) {
edgeC.body[i] = body[i].deepCopy(stringObjs);
}
edgeC.externalAnteList = new Category[externalAnteList.length];
for (int i=0 ; i < externalAnteList.length ; i++) {
edgeC.externalAnteList[i] = externalAnteList[i].deepCopy(stringObjs);
}
edgeC.internalAnteList = new Category[internalAnteList.length];
for (int i=0 ; i < internalAnteList.length ; i++) {
edgeC.internalAnteList[i] = internalAnteList[i].deepCopy(stringObjs);
}
edgeC.annotation = annotation.deepCopy(stringObjs);
if (copyChildren) {
edgeC.links = new ArrayList<Edge>();
for (Edge l : links) {
if (l == null) {
edgeC.links.add(null);
} else {
edgeC.links.add(l.deepCopy(true));
}
}
} else {
edgeC.links = new ArrayList<Edge>(links);
}
return edgeC;
}
/**
* Creates a deep copy of this edge using the given string objects. This method is usually
* called form another deepCopy-method. All categories of this edge are copied, but the child
* edges are linked without being copied.
*
* @param stringObjs The string objects to be used.
* @return A deep copy.
*/
Edge deepCopy(HashMap<Integer, StringObject> stringObjs) {
return deepCopy(stringObjs, false);
}
void calculateIdentifier(String[] usedFeatureNames) {
List<Integer> vars = new ArrayList<Integer>();
List<Integer> mvars = new ArrayList<Integer>();
head.collectVars(vars, mvars);
for (Category c : externalAnteList) {
c.collectVars(vars, mvars);
}
for (Category c : internalAnteList) {
c.collectVars(vars, mvars);
}
for (Category c : body) {
c.collectVars(vars, mvars);
}
identifier = startPos + "," + endPos + " ";
identifier += head.getIdentifier(mvars, usedFeatureNames) + " ";
for (Category c : externalAnteList) {
identifier += c.getIdentifier(mvars, usedFeatureNames);
}
if (scopeclosing) {
identifier += "~";
} else {
identifier += "=";
}
for (Category c : internalAnteList) {
identifier += c.getIdentifier(mvars, usedFeatureNames);
}
identifier += " " + progress;
for (Category c : body) {
identifier += c.getIdentifier(mvars, usedFeatureNames) + " ";
}
}
/**
* Returns an identifier for this edge. Two edges have the same identifier if and only if they
* are equivalent.
*
* @return The identifier of this edge.
*/
public String getIdentifier() {
return identifier;
}
public boolean equals(Object obj) {
if (identifier == null) return this == obj;
if (!(obj instanceof Edge)) return false;
Edge other = (Edge) obj;
return identifier.equals(other.identifier);
}
public int hashCode() {
if (identifier == null) return super.hashCode();
return identifier.hashCode();
}
public String toString() {
String s = "<" + startPos + "," + endPos + "> " + head;
s += " ";
for (Category f : externalAnteList) {
s += f;
}
if (scopeclosing) {
s += "~";
} else {
s += "=";
}
for (Category f : internalAnteList) {
s += f;
}
s += ">";
for (int i=0 ; i < body.length ; i++) {
if (i == progress) s += " .";
s += " " + body[i];
}
if (!isActive()) s += " .";
return s;
}
}