BACKGROUND: A synchronous grammar is a like two context-free grammars connected in parallel. It is used for translation. For example, here is a small synchronous grammar that can be used for translating between natural language text and semantic representation:
== {verb} ==
* I offer {noun}. / OFFER({noun})
* Will you accept {noun}? / QUERY({noun})
* I offer no company car. / OFFER(Leased Car=Without leased car)
== {noun} ==
* {number} % pension / Pension Fund={number}%
* A salary of {number} NIS / Salary={number}
* A company car / Leased Car=With leased car
The headings ({verb}, {noun}) are the nonterminals of the grammar. under each nonterminal is a list of translations enabled by this nonterminal.
Starting from the nontermnial {verb}, we can create, from the above grammar, the following 7 translations:
I offer no company car. => [OFFER(Leased Car=Without leased car)]
I offer A company car. => [OFFER(Leased Car=With leased car)]
Will you accept A salary of {number} NIS? => [QUERY(Salary={number})]
I offer {number} % pension. => [OFFER(Pension Fund={number}%)]
I offer A salary of {number} NIS. => [OFFER(Salary={number})]
Will you accept A company car? => [QUERY(Leased Car=With leased car)]
Will you accept {number} % pension? => [QUERY(Pension Fund={number}%)]
The translations are many-to-many, i.e., there can be more than one translation to each source string, and vice versa. So, each set of translations for a specific nontermnial is represented by a multimap (we use a class ValueSetMap for representing a many-to-many map). An entire grammar is represented by a map of such multimaps: Map>. It maps a nonterminal to its multimap of translations.
Here is some Java code I wrote, for expanding a grammar into a flat multimap of translations. It works for the above example and some more complicated examples, but I wonder if it really covers all cases. All comments are welcome:
public class GrammarExpander {
public GrammarExpander(Map<String, ValueSetMap<String,String>> grammarMap) {
this.grammarMap = grammarMap;
this.expandedGrammarMap = new HashMap<String,ValueSetMap<String,String>>();
}
public ValueSetMap<String, String> expand(String startNonterminal, int maxDepth) {
if (expandedGrammarMap.containsKey(startNonterminal))
return expandedGrammarMap.get(startNonterminal);
Set<String> nonterminals = grammarMap.keySet();
ValueSetMap<String, String> translationsFromStartNonterminal =
grammarMap.get(startNonterminal);
if (translationsFromStartNonterminal==null)
throw new NullPointerException("No translations from startNonterminal " +
startNonterminal);
// don't expand nonterminal anymore - prevent infinite recursion
if (maxDepth<=0)
return translationsFromStartNonterminal;
for (String nonterminal: nonterminals) { // expand each nonterminal in turn
ValueSetMap<String,String> newTranslations =
new SimpleValueSetMap<String,String>();
for (String source: translationsFromStartNonterminal.keySet()) {
for (String target: translationsFromStartNonterminal.get(source)) {
// source contains nonterminal - expand it recursively
if (source.contains(nonterminal) || target.contains(nonterminal)) {
ValueSetMap<String, String> expansions =
this.expand(nonterminal, maxDepth-1);
for (String expansionSource: expansions.keySet())
for (String expansionTarget: expansions.get(expansionSource))
newTranslations.put(
source.replace(nonterminal, expansionSource),
target.replace(nonterminal, expansionTarget));
} else {
newTranslations.put(source, target);
}
}
}
translationsFromStartNonterminal = newTranslations;
}
expandedGrammarMap.put(startNonterminal, translationsFromStartNonterminal);
return translationsFromStartNonterminal;
}
/*
* protected zone
*/
protected Map<String, ValueSetMap<String, String>> grammarMap;
protected Map<String, ValueSetMap<String, String>> expandedGrammarMap;
}