package edu.stanford.nlp.sempre.tables.grow;
import java.util.*;
import edu.stanford.nlp.sempre.*;
import edu.stanford.nlp.sempre.tables.DenotationTypeInference;
import edu.stanford.nlp.sempre.tables.ScopedFormula;
import edu.stanford.nlp.sempre.tables.ScopedValue;
import fig.basic.LispTree;
import fig.basic.MapUtils;
import fig.basic.Option;
/**
* Apply the function on the children from left to right.
*
* Example:
* (lambda x (lambda y ((var x) (count (var y)))))
* on x = population, y = (type cake)
* ==> (population (count (type cake)))
*
* If some of the arguments are ScopeFormulas, they must have either
* the same denotation (when exactScopeHead is false) or the same
* head formula (when exactScopeHead is true). The function is applied
* on the relation part of the ScopeFormula.
*
* In addition to the function, a type can be specified.
* A parser can use this type information to save running time
* (e.g., by not subtracting things that are not numbers or dates)
*
* @author ppasupat
*/
public class ApplyFn extends SemanticFn {
public static class Options {
@Option(gloss = "verbosity") public int verbose = 0;
}
public static Options opts = new Options();
public static boolean exactScopeHead = true;
Formula formula;
// Type information
boolean hasTypeInfo = false, sameType = false;
SemType arg1Type = null, arg2Type = null;
public void init(LispTree tree) {
super.init(tree);
formula = Formulas.fromLispTree(tree.child(1));
for (int i = 2; i < tree.children.size(); i++) {
hasTypeInfo = true;
LispTree typeInfo = tree.child(i);
if (typeInfo.isLeaf() && "same-type".equals(typeInfo.value)) {
sameType = true;
} else if (!typeInfo.isLeaf() && typeInfo.children.size() == 2 && "arg1-type".equals(typeInfo.child(0).value)) {
arg1Type = SemType.fromLispTree(typeInfo.child(1));
} else if (!typeInfo.isLeaf() && typeInfo.children.size() == 2 && "arg2-type".equals(typeInfo.child(0).value)) {
arg2Type = SemType.fromLispTree(typeInfo.child(1));
} else {
throw new RuntimeException("Cannot parse type information: " + typeInfo);
}
}
}
public Formula getFormula() {
return formula;
}
public boolean hasTypeInfo() { return hasTypeInfo; }
public boolean sameType() { return sameType; }
public SemType getArg1Type() { return arg1Type == null ? SemType.anyType : arg1Type; }
public SemType getArg2Type() { return arg2Type == null ? SemType.anyType : arg2Type; }
@Override
public DerivationStream call(Example ex, Callable c) {
return new SingleDerivationStream() {
@Override
public Derivation createDerivation() {
Formula result = formula, head = null;
Value headValue = null;
// Check the scopes
for (Derivation child : c.getChildren()) {
if (!(child.formula instanceof ScopedFormula)) continue;
ScopedFormula scoped = (ScopedFormula) child.formula;
ScopedValue scopedValue = (ScopedValue) child.value;
if (head == null) {
head = scoped.head;
if (scopedValue != null)
headValue = scopedValue.head;
} else {
if ((exactScopeHead && !head.equals(scoped.head)) ||
(!exactScopeHead && !headValue.equals(scopedValue.head)))
return null;
}
}
// Apply the function on the arguments
for (Derivation child : c.getChildren()) {
if (!(result instanceof LambdaFormula))
throw new RuntimeException("Too many arguments: " + c.getChildren() + " for " + formula);
Formula argument = child.formula;
if (argument instanceof ScopedFormula)
argument = ((ScopedFormula) argument).relation;
result = Formulas.lambdaApply((LambdaFormula) result, argument);
}
// SUPER HACK: Resolve ((reverse (lambda x (r1 (r2 (var x))))) ...) => ((reverse r2) ((reverse r1) ...))
if (result instanceof JoinFormula)
result = hackJoin(result);
else if (result instanceof LambdaFormula && ((LambdaFormula) result).body instanceof JoinFormula) {
LambdaFormula lambda = (LambdaFormula) result;
if (lambda.body instanceof JoinFormula)
result = new LambdaFormula(lambda.var, hackJoin(lambda.body));
}
// END SUPER HACK
return new Derivation.Builder().withCallable(c)
.formula(head == null ? result : new ScopedFormula(head, result))
.type(TypeInference.inferType(result)).createDerivation();
}
};
}
private Formula hackJoin(Formula formula) {
if (formula instanceof JoinFormula) {
JoinFormula join = (JoinFormula) formula;
if (join.relation instanceof ReverseFormula) {
ReverseFormula reverse = (ReverseFormula) join.relation;
if (reverse.child instanceof LambdaFormula) {
LambdaFormula lambda = (LambdaFormula) reverse.child;
if (lambda.body instanceof JoinFormula) {
JoinFormula join2 = (JoinFormula) lambda.body;
if (join2.child instanceof JoinFormula) {
JoinFormula join3 = (JoinFormula) join2.child;
// YAY!
return new JoinFormula(new ReverseFormula(join3.relation),
new JoinFormula(new ReverseFormula(join2.relation), join.child));
}
}
}
}
}
// AWW!
return formula;
}
@Override
public boolean supportFilteringOnTypeData() { return true; }
@Override
public Collection<ChildDerivationsGroup> getFilteredDerivations(List<Derivation> derivations1, List<Derivation> derivations2) {
if (!hasTypeInfo)
return Collections.singleton(new ChildDerivationsGroup(derivations1, derivations2));
// TODO: Currently this works only for lists of values, not mappings
Map<String, List<Derivation>> grouped1 = groupByType(derivations1, getArg1Type()),
grouped2 = (derivations2 == null) ? null : groupByType(derivations2, getArg2Type());
List<ChildDerivationsGroup> groups = new ArrayList<>();
if (sameType) { // Matching pairs
for (String valueType : grouped1.keySet()) {
if (!grouped2.containsKey(valueType)) continue;
groups.add(new ChildDerivationsGroup(grouped1.get(valueType), grouped2.get(valueType)));
}
} else if (derivations2 != null) { // All pairs
for (List<Derivation> filtered1 : grouped1.values()) {
for (List<Derivation> filtered2 : grouped2.values()) {
groups.add(new ChildDerivationsGroup(filtered1, filtered2));
}
}
} else {
for (List<Derivation> filtered : grouped1.values()) {
groups.add(new ChildDerivationsGroup(filtered));
}
}
return groups;
}
private Map<String, List<Derivation>> groupByType(List<Derivation> derivations, SemType parentType) {
Map<String, List<Derivation>> typeToDerivs = new HashMap<>();
for (Derivation deriv : derivations) {
String valueType = DenotationTypeInference.getValueType(deriv.value);
MapUtils.addToList(typeToDerivs, valueType, deriv);
}
for (Iterator<String> itr = typeToDerivs.keySet().iterator(); itr.hasNext();) {
String valueType = itr.next();
if (!parentType.meet(SemType.newAtomicSemType(valueType)).isValid())
itr.remove();
}
return typeToDerivs;
}
}