package compiler;
import static compiler.util.MatchCreator.new_match;
import java.util.ArrayList;
import java.util.List;
import grammar.Expression.MacroRule;
import grammar.Expression.Rule;
import parser.Match;
/**
* See the sections of my thesis titled "How does caxap expand macros?",
* "Expansion Strategies" and "Macro Expansion Order & Raw Macros" to learn more
* about how macro are expanded.
*/
public class MacroExpander extends MatchTreeTransformer
{
/*****************************************************************************
* Macro-expand the given match. Returns the expanded match. There is no
* guarantee that the supplied match will not be modified. The current
* implementation does modify the supplied match, but you still need to assign
* the result to account for cases such as a change of the root match.
*/
@Override public Match transform(Match match)
{
if (match.expr instanceof Rule && !match.expr.atomic
&& match.child().expr instanceof MacroRule)
{
return expandMacro(match, match.child());
}
return transformChilds(match);
}
/****************************************************************************/
private Match expandMacro(Match parent, Match macroMatch)
{
Macro macro = ((MacroRule) macroMatch.expr).macro;
Match oldMatch = macroMatch;
if (!macro.raw) {
macroMatch = transformChilds(macroMatch);
}
Match expansion = macro.expand(macroMatch);
expansion = transform(expansion); // recursive expansion
if (!macro.isCalled()) {
checkParent((Rule) parent.expr, macro);
}
if (macro.isAs()) {
checkAsExpansion(oldMatch, macro, expansion);
return expansion;
}
else if (macro.isUnder()) {
List<Match> childs = new ArrayList<>(parent.children());
childs.set(0, expansion);
return new_match(parent.expr, childs);
}
else /* macro.isReplaces() || macro.isCalled() */ {
return expansion;
}
}
/****************************************************************************/
private void checkParent(Rule parent, Macro macro)
{
if (!parent.name.equals(macro.parentRule.name))
{
throw new Error("Macro \"" + macro
+ "\" appearing under a rule it does not extend: \"" + parent.name
+ "\". Expected rule \"" + macro.parentRule + "\" instead.");
}
}
/****************************************************************************/
private void checkAsExpansion(Match expanded, Macro macro, Match expansion)
{
if (!(expansion.expr instanceof Rule))
{
throw new Error("AS Macro \"" + macro
+ "\" did not expand to a rule. (" + expanded.where() + ")");
}
Rule rule = (Rule) expansion.expr;
if(!rule.name.equals(macro.parentRule.name))
{
throw new Error("Macro \"" + macro
+ "\" did not expand to an instance of the rule it expands."
+ "(" + expanded.where() + ")");
}
}
}