You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@royale.apache.org by ah...@apache.org on 2019/04/05 17:47:25 UTC

[royale-compiler] branch develop updated: JBurgGenerator.java version 1.10 contributed by Adobe Inc and Tom Harwood. Permission to relicense granted in https://lists.apache.org/thread.html/ea55377e78d3124add309044dfd00470a16a52be16b97804ea4fa10c@%3Cdev.royale.apache.org%3E

This is an automated email from the ASF dual-hosted git repository.

aharui pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/royale-compiler.git


The following commit(s) were added to refs/heads/develop by this push:
     new b0ddc75  JBurgGenerator.java version 1.10 contributed by Adobe Inc and Tom Harwood.  Permission to relicense granted in https://lists.apache.org/thread.html/ea55377e78d3124add309044dfd00470a16a52be16b97804ea4fa10c@%3Cdev.royale.apache.org%3E
b0ddc75 is described below

commit b0ddc75daef307d10d372b8ed36aff145a10b257
Author: Alex Harui <ah...@apache.org>
AuthorDate: Fri Apr 5 10:44:51 2019 -0700

    JBurgGenerator.java version 1.10 contributed by Adobe Inc and Tom Harwood.  Permission to relicense granted in https://lists.apache.org/thread.html/ea55377e78d3124add309044dfd00470a16a52be16b97804ea4fa10c@%3Cdev.royale.apache.org%3E
    
    -Significant amounts of the file was authored by Tom H. while an employee of Adobe.  Consequently Adobe holds the copyright to that material,
    
    -Per Tom Harwood, all other material was authored by him alone and is not subject to an obligation of assignment to or ownership interest of anyone else,
    
    -Tom Harwood. has granted Adobe permission to submit all material in which he holds the copyright to the project under ALv2/per the terms of the Adobe CCLA
---
 NOTICE                                             |    5 +-
 .../src/main/java/jburg/burg/JBurgGenerator.java   | 3759 ++++++++++++++++++++
 2 files changed, 3763 insertions(+), 1 deletion(-)

diff --git a/NOTICE b/NOTICE
index ee01330..a7a2fe9 100644
--- a/NOTICE
+++ b/NOTICE
@@ -5,9 +5,12 @@ This product includes software developed at
 The Apache Software Foundation (http://www.apache.org/).
 
 The Initial Developer of the Original Code, known as Adobe Flex
-and Adobe ASC 2.0, is Adobe Systems Incorporated (http://www.adobe.com/).
+and Adobe ASC 2.0 and JBurgGenerator.java, is Adobe Systems Incorporated 
+(http://www.adobe.com/).
     Copyright 2003 - 2012 Adobe Systems Incorporated. All Rights Reserved.
 
 The flex-compiler-oem compiler contains code written by Jeff Dyer at: 
     Copyright Mountain View Compiler Company (1998-2003).
 
+Portions of JBurgGenerator.java contains code written by Tom Harwood
+    Copyright Tom Harwood (2003-2008).
diff --git a/compiler-jburg-types/src/main/java/jburg/burg/JBurgGenerator.java b/compiler-jburg-types/src/main/java/jburg/burg/JBurgGenerator.java
new file mode 100644
index 0000000..ac5dfd9
--- /dev/null
+++ b/compiler-jburg-types/src/main/java/jburg/burg/JBurgGenerator.java
@@ -0,0 +1,3759 @@
+/*
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package jburg.burg;
+
+//  BURM specification is parsed into ANTLR2 AST objects.
+import antlr.collections.AST;
+
+//  The code emitter interface definition.
+import jburg.burg.emitlangs.EmitLang;
+
+//  The factory that selects and creates an emitter.
+import jburg.burg.emitlangs.JBurgEmitterFactory;
+
+//  The i-node adapter interface definition.
+import jburg.burg.inode.InodeAdapter;
+import jburg.burg.inode.InodeAdapter2;
+//  Interface for i-node adapters that need
+//  to emit support routines.
+import jburg.burg.inode.InodeAuxiliarySupport;
+
+//  The factory that selects and creates an I-node adapter.
+import jburg.burg.inode.InodeAdapterFactory;
+
+//  Token types from ANTLR.
+import jburg.parser.JBurgTokenTypes;
+
+//  Version file, generated by build.
+import jburg.generator.version.JBurgVersion;
+
+
+import java.io.PrintStream;
+
+import java.lang.reflect.Modifier;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.Vector;
+
+
+@SuppressWarnings("nls")
+public class JBurgGenerator implements JBurgTokenTypes
+{
+    /**
+     *  The JBurg specification's rules are categorized as:
+     *  <ul>
+     *  <li> Pattern  (e.g., integer = PLUS(integer, integer) )
+     *  <li> Simple Transformational   (e.g., registerOperand  = integer)
+     *  <li> Complex Transformational  (e.g., string = integer { code to convert integer to string} )
+     *  </ul>
+     *
+     *  Simple transformational rules allow a subgoal to satisfy
+     *  other subgoals without additional processing; complex
+     *  transformational rules allow one subgoal to feed additional
+     *  processing that can satisfy other subgoals (at added cost).
+     *
+     *  Syntactically, a simple transformational rule is distinguished 
+	 *  by its lack of a block of code.
+	 *  Simple transformational rules are also known as "chain rules"
+	 *  in simpler BURGs that don't support complex transformational
+	 *  rules.  This longer name was adopted to highlight that difference,
+	 *  but a simple transformational rule is, in fact, a chain rule
+	 *  by a different name.
+     */
+
+	/**
+     *   The table of all transformation rules; keyed
+     *   by the rules' necessary state, e.g., integer
+	 *   in the rule number = integer).
+     */
+    Map<String, Vector<JBurgRule>> transformationRules = new HashMap<String, Vector<JBurgRule>>();
+
+	/**
+	 *  The table of all pattern rules, keyed
+	 *  by the top-level operator of the pattern,
+	 *  e.g., PLUS in the pattern integer = PLUS(integer, integer).
+	 */
+    Map<String, Vector<JBurgRule>> patternRules = new HashMap<String, Vector<JBurgRule>>();
+
+    /**
+     *  The goal states' names become symbolic constants
+     *  in the generated reducer.
+     */
+    Set<String> allGoalStates = new HashSet<String>();
+
+    /**
+     *  Closure sets are organized by their goal state; each state's
+     *  closure set contains an entry for every closure
+     *  path from any other non-terminal state to the target state.
+     */
+    Map<String, Vector<ClosureRecord>> closureSets = new HashMap<String, Vector<ClosureRecord>>();
+
+    /**  Action code fragments.  */
+    ArrayList<JBurgReduceAction> reduceActions = new ArrayList<JBurgReduceAction>();
+
+    /**
+     *  Cost function fragments.
+     *  Note that cost functions may also be embedded
+     *  in the inline code blocks in a specification.
+     */
+    ArrayList<AST> costFunctions = new ArrayList<AST>();
+
+    /**  The names of any interfaces that the generated BURM implements.  */
+    Vector<String> interfaceNames = new Vector<String>();
+    
+    /** blocks of code to be added into the class verbatim */
+    Vector<String> inclassCode = new Vector<String>();
+
+    /**
+     *  Rules that can generate compressed annotations.
+     */
+    RulesByOperatorAndArity compressedAnnotations = new RulesByOperatorAndArity();
+
+    /** The package name of the generated reducer. */
+    String packageName = null;
+
+    /**  Header code, copied as-is into the reducer. */
+    String headerBlock = null;
+
+    /**
+     *  The name of the i-node class that's being labeled and reduced.
+     */
+    String iNodeClass = null;
+
+	/** 
+	 *  I-node adapter class name.  The adapter is instantiated by name.
+	 *  @note: specified as an alternative to the I-node class' name,
+	 *  which selects a builtin adapter.
+	 */
+	String iNodeAdapterClass;
+
+    /**
+     *  The return types for specific states.
+     */
+    Map<String, String> returnTypeTable = new HashMap<String, String>();
+
+    /**
+     *  The default return type of the reducer functions.
+     *  If this is defaulted to null, the generated reducer will return nodes of the iNodeClass.
+     */
+    String defaultReturnType = null;
+
+    /**
+     *  Table of properties to be added to the BURM.
+     *  Each one is a name/type pair; the BURM gets
+     *  a private member and get/set methods.
+     */
+    Map<String, String> burmProperties = new HashMap<String, String>();
+
+    /**
+     *  Simple transformations simply delegate to their antecedent reduction,
+     *  so all transformations to a given nonterminal state can share the
+     *  same rule.
+     */
+    Map<String, JBurgRule> simpleTransformationRules = new HashMap<String,JBurgRule>();
+
+    /**  Include debugging code in the generated reducer?  */
+    boolean debugMode;
+    
+    /** 
+     * If the pattern-matcher generator fails, dump its annotation tree here.
+     * Note: this is only enabled (or useful) when debugging JBurg's own BURM. 
+     */
+    String patternMatcherDumpFile = null;
+
+    /**  
+     *  Caller's logging interface.  If defaulted to null, informational and
+     *  error messages go to System.out and System.err respectively.
+     */
+    Logger logger = null;
+    
+    /**
+     * Name for the language to emit code in (default is assumed to be java (=emitlang.EmitJava)
+     */
+    String emitLanguageName = null;
+    
+    /** Code emitter to use (selected using the language name) */
+	EmitLang codeEmitter = null;
+
+	/** I-node adapter to use */
+	InodeAdapter iNodeAdapter;
+	
+	/** 
+	 *  If the InodeAdapter is also an InodeAdapter2,
+	 *  keep a reference to it. 
+	 */
+	InodeAdapter2 adapter2;
+
+	/**  Cumulative error count. */
+	int errCount = 0;
+
+	/**  Name of the initial parameter to the label routine. */
+	private static String initalParamName = "to_be_labelled";
+	
+	/** Name of the node in the reducer */
+	private String reducerNodeName = "__p";
+	
+	/** Name of the stack of reduced values */
+	private String reducedValuesName = "__reducedValues";
+
+    /**  Default error handler.  null means use hard-coded default, i.e., throw an exception. */
+    String defaultErrorHandler = null;
+
+    /**  Prologue snippets mapped to the corresponding rule number. */
+    Map<Integer,String> prologueBlocks = new TreeMap<Integer,String>();
+
+    /** 
+     *  All operators known to any pattern accumulate in this set
+     *  so that the BURG can generate operator-specific getNthChild()
+     *  routines.
+     */
+    Set<String> allOperators = new TreeSet<String>();
+    
+    /**
+     * operator mapped to node type. This map is populated by JBurg.NodeType
+     * directives.
+     * <p>
+     * For example, an operator named IdentifierID would be associated with
+     * {@code IIdentifierNode*} in this map if the input contained
+     * {@code JBurg.NodeType IdentifierID = IIdentifierNode*;
+     */
+    final Map<String, String> opcodeNodeTypes = new HashMap<String, String>();
+    
+    /**
+     *  The type of the INodes' opcode.  Defaults to int but
+     *  can be an enum for maintainability.
+     */
+    String opcodeType = "int";
+
+    /**
+     *  Manifest constants in the JBurg specification.
+     */
+    Map<String,Integer> manifestConstants = new HashMap<String,Integer>();
+    
+    /**
+     *  Patterns that have been optimized out are represented
+     *  by this not-likely-to-compile sequence.
+     */
+    final private static String nilPattern = "-null-";
+
+    /**
+     *  Manifest constant for method declarations.
+     */
+    final private static Class<?>[] throwsNothing = null;
+
+    /**
+     *  Manifest constant for method declarations.
+     */
+    final private static Class<?>[] throwsException = new Class<?>[] { Exception.class };
+
+
+    /**
+     *  Manifest constant for method declarations.
+     */
+    final private static String[][] noFormalParameters = new String[][] { };
+
+    /**
+     *  Manifest constant for method calls.
+     */
+    final private static String[] noActualParameters = { };
+
+    /**
+     *  Manifest constant for an unfeasible rule.
+     */
+    final private static String NO_FEASIBLE_RULE = "-1";
+
+    /**
+     *  Manifest constant for an uninitialized cost.
+     */
+    final private static String UNINITIALIZED = "-1";
+
+    /**
+     *  NamedPattern holds a named pattern, and keeps a table of the reductions that refer to it.
+     *  (See defect 3063143 for problems with multiple reductions sharing a pattern.)
+     */
+    class NamedPattern
+    {
+        String patternName;
+        AST pattern = null;
+        Vector<AST> reductions = new Vector<AST>();
+
+        NamedPattern(String name)
+        {
+            this.patternName = name;
+        }
+    }
+
+    /**
+     *  A PatternMap keeps the map of pattern names to reductions.
+     */
+    @SuppressWarnings("serial")
+    class PatternMap extends  TreeMap<String,NamedPattern>
+    {
+        NamedPattern getPattern(String key)
+        {
+            if ( !super.containsKey(key) )
+                put(key, new NamedPattern(key));
+            return super.get(key);
+        }
+
+        void addPattern(String key, AST pattern)
+        {
+            NamedPattern p = getPattern(key);
+            p.pattern = pattern;
+        }
+
+        void addReduction(String key, AST reduction)
+        {
+            NamedPattern p = getPattern(key);
+            p.reductions.add(reduction);
+        }
+    }
+
+    PatternMap namedPatterns = new  PatternMap();
+
+    /**
+     *  Aggregate path computations if more than this threshold
+     *  number of pattern elements share a path.
+     */
+    private int aggregatePathThreshold = Integer.MAX_VALUE;
+
+    public void setAggregatePathThreshold(int threshold)
+    {
+        this.aggregatePathThreshold = threshold;
+    }
+
+    /**
+      *  @param root - the root of the AST generated by parsing the .jburg specification.
+      */
+    public JBurgGenerator(AST root, Logger log) throws Exception
+    {
+        this.logger = log;
+
+        //  Walk over the children of the root, each of which 
+		//  is a self-contained syntactical construct, and
+        //  find an appropriate action for each.
+        for (AST currentNode = root; currentNode != null;
+                currentNode = currentNode.getNextSibling()) 
+		{
+            switch (currentNode.getType()) 
+			{
+            case COST_FUNCTION:
+                this.costFunctions.add(currentNode);
+                break;
+
+            case HEADER_DECLARATION:
+
+				if ( null == this.headerBlock )
+				{
+					this.headerBlock = getCodeBlock(currentNode);
+				}
+				else
+				{
+                    throw new IllegalArgumentException("The class header may only be specified once.");
+				}
+
+                break;
+                
+            case INCLASS_DECLARATION:
+				{
+            		this.inclassCode.add( stripBrackets(getCodeBlock(currentNode)) );
+            	}
+				
+				break;
+
+            case INODE_ADAPTER_DECLARATION:
+
+                if (this.iNodeAdapterClass == null)
+				{
+					this.iNodeAdapterClass = getIdentifierText(currentNode.getFirstChild());
+				}
+				else
+				{
+                    throw new IllegalArgumentException("INodeAdapter may only be specified once.");
+				}
+
+				break;
+
+            case INODE_TYPE_DECLARATION:
+
+                if (this.iNodeClass == null)
+				{
+                    this.iNodeClass = getIdentifierText(currentNode.getFirstChild());
+				}
+                else
+				{
+                    throw new IllegalArgumentException("INodeType may only be specified once.");
+				}
+
+                break;
+                
+            case LANGUAGE_DECLARATION:
+
+            	if ( null == this.emitLanguageName )
+				{
+            		this.emitLanguageName = currentNode.getFirstChild().getText();
+				}
+            	else
+				{
+            		throw new IllegalArgumentException("Language may only be specified once.");
+				}
+
+            	break;
+
+            case IMPLEMENTS_INTERFACE_SPECIFICATION:
+                this.interfaceNames.addElement(getIdentifierText( currentNode.getFirstChild()) );
+
+                break;
+
+            case PACKAGE_SPECIFICATION:
+                if ( null == this.packageName )
+				{
+					this.packageName = getIdentifierText(currentNode.getFirstChild());
+				}
+				else
+				{
+            		throw new IllegalArgumentException("package may only be specified once.");
+				}
+
+                break;
+
+            case PROPERTY_SPECIFICATION: 
+				{
+					String propertyType = getIdentifierText(currentNode.getFirstChild());
+					String propertyName = currentNode.getFirstChild().getNextSibling().getText();
+					this.burmProperties.put(propertyName, propertyType);
+				}
+
+                break;
+
+            case RETURN_DECLARATION:
+
+                if (this.defaultReturnType == null)
+                    this.defaultReturnType = getIdentifierText(currentNode.getFirstChild());
+                else
+                    throw new IllegalArgumentException( "ReturnType may only be specified once.");
+
+                break;
+
+            case PATTERN_RULE:
+                addPatternRule(currentNode);
+
+                break;
+
+            case SIMPLE_TRANSFORMATION_RULE:
+                addSimpleTransformationRule(currentNode);
+
+                break;
+
+            case TRANSFORMATION_RULE:
+                addComplexTransformationRule(currentNode);
+
+                break;
+
+            case TYPED_RETURN_DECLARATION:
+
+                {
+                    String stateName = currentNode.getFirstChild().getText();
+                    String returnType = getIdentifierText(currentNode.getFirstChild()
+                            .getNextSibling());
+
+                    //  Put the return declaration in the table, but only once per state.
+                    Object typeCollision = this.returnTypeTable.put(stateName, returnType);
+                    if ( null != typeCollision )
+                    {
+                        throw new IllegalArgumentException(
+                            "A state may only specify one ReturnType."
+                        );
+                    }
+                }
+
+                break;
+
+            case PATTERN_DECLARATION:
+                {
+                    String pattern_name = currentNode.getFirstChild().getText();
+                    namedPatterns.addPattern(pattern_name, currentNode);
+                }
+                break;
+
+            case REDUCTION_DECLARATION:
+                {
+                    String pattern_name = currentNode.getFirstChild().getNextSibling().getText();
+                    namedPatterns.addReduction(pattern_name, currentNode);
+                }
+                break;
+
+            case DEFAULT_ERROR_HANDLER:
+                this.defaultErrorHandler = getCodeBlock(currentNode);
+                break;
+            case NODE_TYPE:
+            {
+                final String operatorID = currentNode.getFirstChild().getText();
+                assert !operatorID.isEmpty() : "Parser should never create empty operator!";
+                final String nodeType = currentNode.getFirstChild().getNextSibling().getText();               
+                assert !nodeType.isEmpty() : "Parser should never create empty node type!";
+                
+                if (this.opcodeNodeTypes.put(operatorID, nodeType) != null)
+                {
+                    final String message = "Duplicate node type declaration for '"
+                        + operatorID
+                        + "'.";
+                    throw new IllegalArgumentException(message);
+                }
+                break;
+            }
+            case OPCODE_TYPE:
+                this.opcodeType = currentNode.getFirstChild().getText();
+                break;
+
+            case MANIFEST_CONSTANT:
+                manifestConstants.put(currentNode.getFirstChild().getText(), Integer.parseInt(currentNode.getFirstChild().getNextSibling().getText()));
+                break;
+
+            default:
+                throw new IllegalArgumentException("Unknown specification AST type " +
+            											String.valueOf(currentNode.getType()));
+            }
+        }
+
+        //  Set the language emitter.
+        codeEmitter = JBurgEmitterFactory.getEmitter(emitLanguageName, getLogger());
+
+        if ( codeEmitter == null )
+		{
+			throw new IllegalStateException("Unknown language specified: \""+ emitLanguageName +"\"");
+        }
+
+		if ( iNodeClass != null )
+		{
+			if ( iNodeAdapterClass != null )
+			{
+				try
+				{
+					this.iNodeAdapter = (InodeAdapter)Class.forName(iNodeAdapterClass).newInstance();
+				}
+				catch ( Exception ex )
+				{
+					ex.printStackTrace();
+					throw new IllegalArgumentException ("Unable to instantiate i-node adapter " + iNodeAdapterClass );
+				}
+			}
+			else
+			{
+				this.iNodeAdapter = InodeAdapterFactory.getAdapter(iNodeClass);
+			}
+			
+			codeEmitter.setINodeType(this.iNodeClass);
+
+			if ( this.iNodeAdapter != null )
+			{
+				logger.info("Using i-node adapter " + this.iNodeAdapter.getClass().getName() );
+			}
+			else
+			{
+				getLogger().warning("using default i-node adapter, no adapter matches " + iNodeClass );
+				this.iNodeAdapter = new jburg.burg.inode.DefaultAdapter();
+			}
+			
+			//  See if the adapter is also an InodeAdapter2 implementation.
+			this.adapter2 = (this.iNodeAdapter instanceof InodeAdapter2)? (InodeAdapter2)this.iNodeAdapter : null;
+		}
+		else
+		{
+			throw new IllegalStateException("You must specify the i-node type.");
+		}
+
+		codeEmitter.setInodeAdapter(this.iNodeAdapter);
+
+        //  Default return type is the same as the INode class.
+        if (this.defaultReturnType == null)
+		{
+            this.defaultReturnType = this.iNodeClass;
+		}
+
+        //  Mutate pattern/reduction decl pairs into pattern rules.
+        for ( NamedPattern np: namedPatterns.values() )
+        {
+            if ( np.pattern != null && np.reductions.size() > 0 )
+            {
+                AST named_pattern = np.pattern.getFirstChild().getNextSibling().getFirstChild();
+
+                for ( AST reduction: np.reductions )
+                {    
+                    //  Splice the pattern into the reduction AST.
+                
+                    AST nt_state = reduction.getFirstChild();
+                    AST pattern_name = nt_state.getNextSibling();
+                    AST cost_decl = pattern_name.getNextSibling();
+                    
+                    //  Create a new holder for the pattern
+                    //  so the original AST isn't mutated.
+                    AST pattern_holder = new antlr.CommonAST();
+                    pattern_holder.setFirstChild(named_pattern);
+                    nt_state.setNextSibling(pattern_holder);
+                    pattern_holder.setNextSibling(cost_decl);
+    
+                    //  Give the composite AST the appropriate type
+                    //  for its pattern.
+                    reduction.setType(PATTERN_RULE);
+    
+                    addPatternRule(reduction);
+                }
+            }
+            else if ( np.pattern != null )
+            {
+                getLogger().warning("pattern " + np.patternName + " has no reduction - ignored.");
+            }
+            else if ( np.reductions.size() > 0 )
+            {
+                throw new IllegalStateException("Reduction " + np.patternName + " has no associated pattern.");
+            }
+        }
+
+        //  Add target-specific logic to the simple transformations' rules.
+        for ( JBurgRule rule: this.simpleTransformationRules.values() )
+        {
+            String actionCode = String.format(
+                "%s%s",
+                codeEmitter.genReturnValue(
+                    codeEmitter.genPopFromStack(reducedValuesName, getReturnType(rule.getGoalState()))
+                ),
+                codeEmitter.genEndStmt()
+            );
+
+            JBurgReduceAction action = addAction(actionCode, rule.getGoalState());
+            action.setAntecedentState(rule.getAntecedentState());
+            rule.setReduceAction(action);
+        }
+
+        //  Compute the closure sets.
+        computeClosureSets();
+    }
+    
+    /**
+     *  Add a reduce action to the action list.
+	 *  Actions are keyed by number in the generated BURM.
+     */
+    private JBurgReduceAction addAction(String strAction, String strState)
+    {
+        JBurgReduceAction newAction = new JBurgReduceAction(strAction);
+        this.reduceActions.add(newAction);
+
+        //  The first index is used as the default action for
+        //  simple transformation rules.
+        newAction.setIndex(this.reduceActions.size());
+        newAction.setState(strState);
+
+        return newAction;
+    }
+
+    /**
+     *  Sort through a rule's action routine specifications and assemble
+     *  a JBurgReduceAction from them.
+     *  @param parent - the rule's AST.
+     *  @param goal_state - the state the rule produces.
+     *  
+     */
+    private JBurgReduceAction decodeReduction(AST parent, String goal_state)
+    {
+        JBurgReduceAction action = null;
+        AST reduction = null;
+
+        if ( hasASTOfType(parent, REDUCTION_ACTION) )
+        {
+            reduction = getASTByType(parent, REDUCTION_ACTION);
+            action = addAction(getCodeBlock(reduction), goal_state);
+        }
+        else if ( hasASTOfType(parent, EXPLICIT_REDUCTION ) )
+        {
+            reduction = getASTByType(parent, EXPLICIT_REDUCTION);
+            action = addAction( "return " + decodeProcedureCall(reduction) + ";", goal_state );
+        }
+        else
+        {
+            throw new IllegalStateException("A reduction must specify an implementation block or callback function");
+        }
+
+        if ( hasASTOfType(reduction, PROLOGUE) )
+        {
+            AST prologue = getASTByType(reduction, PROLOGUE);
+            String prologue_action = null;
+
+            if ( hasASTOfType(prologue, BLOCK) )
+            {
+                prologue_action = getCodeBlock(prologue);
+            }
+            else if ( hasASTOfType(prologue, PROCEDURE_CALL) )
+            {
+                prologue_action = "\t" + decodeProcedureCall(prologue) + ";";
+            }
+            else
+            {
+                throw new IllegalStateException("Prologue block with no inline code or callback");
+            }
+
+            this.prologueBlocks.put(action.index, prologue_action);
+        }
+
+        return action;
+    }
+
+    private String decodeProcedureCall(AST parent)
+    {
+        AST procall = getASTByType(parent, PROCEDURE_CALL);
+
+        StringBuilder buffer = new StringBuilder();
+
+        AST id = procall.getFirstChild();
+        buffer.append(id.getText());
+        buffer.append("(");
+
+        AST param = id.getNextSibling();
+        if ( param != null )
+        {
+            buffer.append(param.getText());
+
+            for ( param = param.getNextSibling(); param != null; param = param.getNextSibling() )
+            {
+                buffer.append(",");
+                buffer.append(param.getText());
+            }
+        }
+        buffer.append(")");
+
+        return buffer.toString();
+    }
+
+    /**
+     *  Add a complex transformation rule to its table,
+     *  and record the rule's reduction action.
+     */
+    private void addComplexTransformationRule(AST transform)
+        throws Exception
+    {
+        JBurgRule n = addNamedRule(this.transformationRules, transform);
+
+        //  Prepare the rule's reduce action.
+        JBurgReduceAction action = decodeReduction(transform, n.getGoalState());
+        action.setAntecedentState(n.getAntecedentState());
+
+        //  Add the antecedent state by name as an alias action routine "parameter."
+        //  The "parameter" is popped off the reduced values stack into a local variable.
+        action.addParameter(
+            n.getAntecedentState(), 
+            n.getAntecedentState(), 
+            ParameterDescriptor.ArityType.Fixed
+        );
+
+        n.setReduceAction(action);
+    }
+
+    /**
+     *  If this simple transformation is not yet known, add it to its rule table.
+     */
+    private void addSimpleTransformationRule(AST transform)
+    {
+        JBurgRule newRule = new JBurgRule(transform);
+
+        String key = String.format("%s=%s", newRule.getGoalState(), newRule.getAntecedentState());
+
+        if ( ! this.simpleTransformationRules.containsKey(key) )
+        {
+            //  Ensure this rule's nonterminal is known.
+            this.allGoalStates.add(newRule.getGoalState());
+            addNamedRule(this.transformationRules, newRule);
+
+            //  Target-specific action logic will be
+            //  added once the code emitter is up.
+            this.simpleTransformationRules.put(key, newRule);
+        }
+    }
+
+    /**
+     *  Wrap a pattern rule in an I-node, and add it to pattern rule table.
+     */
+    private void addPatternRule(AST newRule) throws Exception
+    {
+        JBurgRule n = addNamedRule(this.patternRules, newRule);
+
+        JBurgReduceAction newAction = decodeReduction(newRule, n.getGoalState());
+
+        n.setReduceAction(newAction);
+    }
+
+    /**
+     *  Wrap a rule in an I-node, and add it to the list of rules
+     *  that produce a particular subgoal.
+     */
+    private JBurgRule addNamedRule(Map<String, Vector<JBurgRule>> ruleTable, AST newAST)
+    {
+        JBurgRule newRule = new JBurgRule(newAST);
+
+        //  Ensure this rule's nonterminal is known.
+        this.allGoalStates.add( newRule.getGoalState() );
+        return addNamedRule(ruleTable, newRule);
+    }
+
+    /**
+     *  Add a rule to its rule table.
+     *  @param ruleTable - the appropriate table for this type of rule.  Keyed by operator.
+     *  @param newRule - the rule to add.
+     */
+    private JBurgRule addNamedRule(Map<String, Vector<JBurgRule>> ruleTable, JBurgRule newRule)
+    {
+        //  Store the rule by operator.
+        String operator = newRule.getOperator();
+
+        Vector<JBurgRule> vRules = ruleTable.get(operator);
+
+        if (vRules == null)
+        {
+            vRules = new Vector<JBurgRule>();
+            ruleTable.put(operator, vRules);
+        }
+
+        vRules.add(newRule);
+
+        return newRule;
+    }
+
+    /**
+     *  Compute the closure set of a rule:  the set of rules that
+     *  that can transform this rule's goal to satsify another goal.
+	 *  @note computeClosure is public so that it can be applied
+	 *    by JBurgUtilities.
+     */
+    public void computeClosure(JBurgRule n) throws Exception
+    {
+		//  Get the list of transformation rules that can use this rule's goal state.
+        Vector<JBurgRule> vClosureCandidate = this.transformationRules.get(n.getGoalState());
+
+        if (vClosureCandidate != null) 
+		{
+            //  Found some closures:
+            //  create or fetch this target state's closure set.
+            Vector<ClosureRecord> vClosureSet = this.closureSets.get(n.getGoalState());
+
+            if (vClosureSet == null) 
+			{
+                vClosureSet = new Vector<ClosureRecord>();
+                this.closureSets.put(n.getGoalState(), vClosureSet);
+            }
+
+            for (JBurgRule nPrime:vClosureCandidate ) 
+			{
+
+                //  Add this closure to the closure set.
+                ClosureRecord newClosure = new ClosureRecord(nPrime);
+
+                if (!vClosureSet.contains(newClosure)) 
+				{
+                    vClosureSet.add(newClosure);
+                }
+            }
+        }
+    }
+
+    /**
+     *  Scan all the rules and compute their closure sets.
+     */
+    private void computeClosureSets() throws Exception
+    {
+        for (Vector<JBurgRule> patterns : this.patternRules.values() ) 
+		{
+            try 
+			{
+                JBurgUtilities.applyToAll( this, patterns, "computeClosure", JBurgRule.class);
+            }
+			catch (Exception e)
+			{
+                e.printStackTrace();
+            }
+        }
+
+        for (Vector<JBurgRule> transformations : this.transformationRules.values() )
+		{
+            try
+			{
+                JBurgUtilities.applyToAll( this, transformations, "computeClosure", JBurgRule.class);
+            }
+			catch (Exception e)
+			{
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     *  Emit the BURG specification's resultant BURM to an output stream.
+     */
+    public int generate(String className, PrintStream output)
+    {
+        try
+		{
+            codeEmitter.setOpcodeType(this.opcodeType);
+        	emitHeader(className, output);
+        	codeEmitter.emitInclass(iNodeClass, this.inclassCode, output);
+
+        	emitNTConstants(this.allGoalStates, output);
+            emitStatics(output);
+
+			if ( this.iNodeAdapter instanceof InodeAuxiliarySupport )
+			{
+				((InodeAuxiliarySupport)this.iNodeAdapter).emitAuxiliarySupport(codeEmitter, output);
+			}
+
+            emitLabelFunction(this.iNodeClass, output);
+            ruleSemanticAnalysis();
+
+            if  ( codeEmitter.supportsSpecializedAnnotations() )
+            {
+                for (Vector<JBurgRule> vPatternRules: this.patternRules.values())
+                    this.compressedAnnotations.addAll(vPatternRules);
+            }
+            else
+            {
+                emitComputeCostMatrixFunction(this.iNodeClass, output);
+                emitClosures(this.closureSets, this.iNodeClass, output);
+            }
+            emitActions(this.reduceActions, this.iNodeClass, output);
+            emitCostFunctions(this.costFunctions, this.iNodeClass, output);
+
+            if ( codeEmitter.supportsSpecializedAnnotations() )
+                emitCompressedAnnotations(output);
+            
+            //  If there's an InodeAdapter2 present,
+            //  emit the getNthChild routine that
+            //  implements the adapter's node-handling logic.
+            if ( this.adapter2 != null)
+                emitGetNthChild(output);
+
+            codeEmitter.emitTrailer(
+				className,
+				this.iNodeClass,
+				this.allGoalStates,
+				this.burmProperties,
+				this.debugMode,
+                this.defaultErrorHandler,
+                this.prologueBlocks,
+				output
+			);
+        }
+		catch (Exception e)
+		{
+            e.printStackTrace();
+			this.errCount++;
+        }
+
+		return this.errCount;
+    }
+
+    //  Emit static lookup tables.
+    private void emitStatics(PrintStream output)
+    {
+        //  Emit the static table of subgoal records.
+        Map<Integer, Vector<JBurgPatternMatcher>> rules_by_action = new HashMap<Integer,Vector<JBurgPatternMatcher>>();
+        int max_action = 0;
+        for (Vector<JBurgRule> vPatternRules: patternRules.values())
+        {
+            for (JBurgRule p: vPatternRules)
+            {
+                int action_number = p.getReduceAction().getIndex();
+                max_action = Math.max(max_action, action_number);
+                
+                if ( p.patternMatcher.isNary() )
+                {
+                    rules_by_action.put(action_number,new Vector<JBurgPatternMatcher>());
+                    rules_by_action.get(action_number).add(p.patternMatcher);
+                }
+                else if (!p.patternMatcher.getParameterizedSubtrees().isEmpty() )
+
+                {
+                    rules_by_action.put(action_number,p.patternMatcher.getParameterizedSubtrees());
+                }
+            }
+        }
+
+        //  Ensure the transformation rules' actions have subgoal records.
+        for ( Vector<JBurgRule> transformations:  this.transformationRules.values() )
+            for ( JBurgRule t: transformations )
+                max_action = Math.max(max_action, t.getReduceAction().getIndex());
+        
+        codeEmitter.emitStatics(max_action, rules_by_action, output);
+
+        //  Generate manifest constant declarations.
+        for ( Map.Entry<String,Integer> constants : this.manifestConstants.entrySet() )
+            genInstanceField(
+                output,
+                Modifier.PUBLIC + Modifier.STATIC + Modifier.FINAL,
+                "int",
+                constants.getKey(),
+                constants.getValue().toString()
+            );
+    }
+
+    /**
+     *  Get the payload of an identifier, with error checking.
+     *  @return the identifier's text.
+     */
+    private String getIdentifierText(AST p)
+    {
+		if ( p.getType() != IDENTIFIER )
+		{
+			throw new IllegalStateException ( "Expected IDENTIFIER, found " + p.toStringTree() );
+		}
+
+        return p.getText();
+    }
+
+    /**
+     *  Does this production have a closure set?
+     *  @return true if n has a closure set.
+     *  @param n - the production of interest.
+     */
+    public boolean hasClosure(JBurgProduction n)
+    {
+        return this.closureSets.get(n.getGoalState()) != null;
+    }
+
+    /**
+      *  Emit the file/class header information.
+      */
+    private void emitHeader(String className, PrintStream output)
+    {
+        genComment(output, " Generated " + new java.util.Date().toString() + " by JBurg version " + JBurgVersion.version  );
+        output.println();
+        
+        codeEmitter.emitHeader(className, this.packageName, this.headerBlock, this.interfaceNames, this.debugMode, output);
+    }
+
+	/**
+	 *  @return the return type for a specific state.
+	 */
+	public String getReturnType(String stateName)
+	{
+		Object result = returnTypeTable.get(stateName);
+
+		if (result == null)
+			result = defaultReturnType;
+
+		return result.toString();
+	}
+	
+    /**
+     *  setDebugMode controls the level of debugging code that will be generated in the reducer.
+     */
+    public void setDebugMode(boolean bDebugMode)
+    {
+        debugMode = bDebugMode;
+    }
+    
+	/**
+	 *  @return the text of the parent AST's code block, which is represented as a BLOCK token.
+	 */
+	private String getCodeBlock( AST parent )
+	{
+		return getASTByType(parent, BLOCK).getText();
+	}
+
+    /**
+     *  @return the first child of the parent AST with the specified node type.
+     */
+    private AST getASTByType(AST parent, int node_type)
+    {
+        for ( AST current = parent.getFirstChild(); current != null; current = current.getNextSibling() )
+        {
+            if ( current.getType() == node_type )
+                return current;
+        }
+
+        throw new IllegalStateException ( "AST " + parent.toString() + " has no child of node type " + node_type + "." );
+    }
+
+    private boolean hasASTOfType(AST parent, int node_type)
+    {
+        for ( AST current = parent.getFirstChild(); current != null; current = current.getNextSibling() )
+        {
+            if ( current.getType() == node_type )
+                return true;
+        }
+
+        return false;
+    }
+
+    /**
+     *  A JBurgProduction is a view of a pattern-matching rule (JBurgRule) 
+     *  or a nonterminal transformation rule (ClosureRecord) that exposes
+     *  characteristics important in their static analysis.
+     */
+    public interface JBurgProduction
+    {
+        /**
+         *  @return the nonterminal this production produces.
+         */
+        public String getGoalState();
+
+        /**
+         *  @return the code snippet that computes
+         *    or retrieves this production's 
+         *    (potentially) cached code.
+         */
+        public String getCachedCost();
+
+        /**
+         *  @return this production's reduce action.
+         */
+        public JBurgReduceAction getReduceAction();
+
+        /**
+         *  Is this production's cost constant in the
+         *  context where it's to be invoked?
+         *  @param productions - the set of productions at the
+         *    point where this productions's cost is required.
+         */
+        public boolean computesConstantCost(Multimap<String, JBurgProduction> productions);
+
+        /**
+         *  Get the constant cost of a production.
+         *  @param productions - the set of productions at the
+         *    point where this production's cost is required.
+         *  @return the cost, with overflow-safe addition.
+         *  @pre computesConstantCost(productions) must be true.
+         */
+        public int getConstantCost(Multimap<String, JBurgProduction> productions);
+    }
+
+    /**
+     *  JBurgRule contains an AST that represents a rule, 
+     *  its associated JBurgReduceAction,
+     *  and accessors to the AST's components.
+     */
+    public class JBurgRule implements JBurgProduction
+    {
+        /**
+         *  The parsed rule.
+         */
+        AST m_AST;
+        
+        /**
+         *  If the rule is a pattern rule, its pattern matcher.
+         */
+        JBurgPatternMatcher patternMatcher = null;
+        
+        /**
+         * The rule's reduction action.
+         */
+        JBurgReduceAction reduceAction;
+
+        /**
+         *   The pattern as a String; precomputed so it can be used as a key
+         *   to group labeling choices by common patterns.
+         */
+        private String encodedPattern;
+
+        public JBurgRule(AST n)
+        {
+            m_AST = n;
+            
+            if ( m_AST.getType() == PATTERN_RULE )
+            {
+                AST pattern_root = m_AST.getFirstChild().getNextSibling().getFirstChild();
+                try
+                {
+                    this.patternMatcher = generateMatcher(pattern_root); 
+                }
+                catch ( Exception ex )
+                {
+                    logger.exception("Building pattern recognizer for " + m_AST.toStringTree(), ex);
+                }
+            }
+        }
+
+        String getEncodedPattern()
+        {
+            if ( this.encodedPattern == null )
+            {
+                this.encodedPattern = 
+                    patternMatcher.generatePatternRecognizer(
+                        codeEmitter,
+                        "node",
+                        JBurgGenerator.this.adapter2
+                    );
+            }
+            
+            if ( this.encodedPattern == null )
+                this.encodedPattern = nilPattern;
+
+            return this.encodedPattern;
+        }
+
+        /**
+         * @param baseNode - the path to the base of the subtree.
+         * @return the subtree's cost specification.
+         */
+        public String getCost(String baseNode)
+        {
+            if ( m_AST.getType() != SIMPLE_TRANSFORMATION_RULE )
+            {
+                AST cost_spec = m_AST.getFirstChild().getNextSibling()
+                        .getNextSibling();
+
+                String costText = cost_spec.getFirstChild().getText();
+
+                if ( hasCostFunction() )
+                    return genCallMethod(null, costText, baseNode);
+                else
+                    return costText;
+            }
+            else
+            {
+                // Simple transformation rules don't cost anything.
+                return "0";
+            }
+        }
+        
+        /** 
+         *  @return true if the rule has a constant cost.
+         */
+        public boolean hasConstantCost()
+        {
+            if ( m_AST.getType() != SIMPLE_TRANSFORMATION_RULE )
+            {
+                AST cost_spec = m_AST.getFirstChild().getNextSibling()
+                        .getNextSibling();
+
+                String costText = cost_spec.getFirstChild().getText();
+
+                boolean ownCostConstant =
+                    cost_spec.getType() == LITERAL_COST_SPEC ||
+                    JBurgGenerator.this.manifestConstants.containsKey(costText);
+                
+                return ownCostConstant &&
+                     (this.isTerminalPattern() || this.patternMatcher == null);
+            }
+            else
+            {
+                // Simple transformation rules all cost 0.
+                return true;
+            }
+        }
+        
+        /**
+         *  @return the rule's cost.
+         *  @throws IllegalStateException if the cost isn't constant.
+         */
+        public Integer getConstantCost()
+        {
+            if ( m_AST.getType() != SIMPLE_TRANSFORMATION_RULE )
+            {
+                AST cost_spec = m_AST.getFirstChild().getNextSibling()
+                        .getNextSibling();
+
+                String costText = cost_spec.getFirstChild().getText();
+
+                if ( cost_spec.getType() == LITERAL_COST_SPEC )
+                {
+                    return new Integer(costText);
+                }
+                else if ( JBurgGenerator.this.manifestConstants.containsKey(costText) )
+                {
+                    return JBurgGenerator.this.manifestConstants.get(costText);
+                }
+                else
+                {
+                    throw new IllegalStateException("non constant cost: " + costText);
+                }
+            }
+            else
+            {
+                // Simple transformation rules don't cost anything.
+                return 0;
+            }
+        }
+
+        /**
+         *  @return true if the cost specification is a function call.
+         */
+        public boolean hasCostFunction()
+        {
+            boolean result = false;
+
+            if ( m_AST.getType() != SIMPLE_TRANSFORMATION_RULE )
+                result = m_AST.getFirstChild().getNextSibling().getNextSibling().getType() == FUNCTION_CALL;
+
+            return result;
+        }
+
+        /**
+         *  @return the rule's cached cost.
+         */
+        @Override
+        public String getCachedCost()
+        {
+            if ( this.hasCostFunction() )
+                return String.format("cachedCost_%h()", getCost(reducerNodeName));
+            else
+                return getCost(reducerNodeName);
+
+        }
+
+        /**
+         *  @return the rule's reduction action.
+         */
+        @Override
+        public JBurgReduceAction getReduceAction()
+        {
+			if ( null == this.reduceAction )
+			{
+				throw new IllegalStateException( "getReduceAction() has no action to return." );
+			}
+
+            return this.reduceAction;
+        }
+
+        /**
+         *  @return the antecedent state of a nonterminal-to-nonterminal
+         *    reduction, i.e., the state that the subtree must be reduced
+         *    to before this reduction can run.
+         */
+        public String getAntecedentState()
+        {
+             if ( m_AST.getType() == SIMPLE_TRANSFORMATION_RULE )
+             {
+                 return m_AST.getFirstChild().getNextSibling().getText();
+             }
+             else if ( m_AST.getType() == TRANSFORMATION_RULE )
+             {
+                 return m_AST.getFirstChild().getNextSibling().getText();
+             }
+             else
+                 throw new IllegalStateException(String.format("no antecedent for %s", m_AST.getType()));
+        }
+
+        /**
+         *  @return the node ID of the node at the root of the subtree.
+         */
+        public String getOperator()
+        {
+			int type = m_AST.getType();
+
+			if ( PATTERN_RULE == type )
+			{
+				return m_AST.getFirstChild().getNextSibling().getFirstChild().getFirstChild().getText();
+			}
+			else
+			{
+			    //  A transformation rule.
+				return m_AST.getFirstChild().getNextSibling().getText();
+			}
+        }
+
+        /**
+         *  @return the nonterminal produced by this reduction.
+         */
+        @Override
+        public String getGoalState()
+        {
+            return m_AST.getFirstChild().getText();
+        }
+
+        /**
+         *  Set this rule's associated action code.
+         */
+        public void setReduceAction(JBurgReduceAction reduceAction)
+        {
+            this.reduceAction = reduceAction;
+        }
+
+        /**
+         *  @return true if this node has no children (a "leaf" node).
+         */
+        public boolean isTerminalPattern()
+        {
+            return this.isFixedArity() && this.patternMatcher.getNominalArity() == 0;
+        }
+
+        /**
+         *  @return true if this rule's pattern has a fixed number of "operand" subtrees.
+         */
+        public boolean isFixedArity()
+        {
+            return this.patternMatcher != null && !this.patternMatcher.hasNaryTail();
+        }
+
+        /**
+         *  @return true if this rule needs an out-of-line method to check its cost.
+         */
+        public boolean needsCostFunction()
+        {
+            if ( this.isTerminalPattern() )
+                return false;
+            else if ( this.patternMatcher.hasNaryTail() )
+                return  this.patternMatcher.getNominalArity() != this.patternMatcher.getMinimumNaryChildCount();
+            else
+                return true;
+        }
+
+        /**
+         *  Is this production's cost constant in the
+         *  context where it's to be invoked?
+         *  @param productions - the set of productions at the
+         *    point where this productions's cost is required.
+         */
+        @Override
+        public boolean computesConstantCost(Multimap<String, JBurgProduction> productions)
+        {
+            return hasConstantCost();
+        }
+
+        /**
+         *  Get the constant cost of a production.
+         *  @param productions - the set of productions at the
+         *    point where this production's cost is required.
+         *  @return the cost, with overflow-safe addition.
+         *  @pre computesConstantCost(productions) must be true.
+         */
+        @Override
+        public int getConstantCost(Multimap<String, JBurgProduction> productions)
+        {
+            return getConstantCost();
+        }
+
+        @Override
+        public String toString()
+        {
+            return m_AST.toStringTree();
+        }
+    }
+
+    /**
+     *  JBurgReduceAction holds action code fragments and their associated "parameters."
+     *  A rule specifies "parameters" by naming individual subgoal states within a pattern
+     *  rule specifiction, for example,
+     *  <xmp>integer = PLUS ( integer i1, integer i2 )</xmp>
+     *  In this example, i1 and i2 are the action routine's "parameters."  Since the action
+     *  routines all share a common signature, the parameters are passed via the reducer's
+     *  reduced values stack.
+     */
+    private class JBurgReduceAction
+    {
+        /** The routine's source, from the input specification. */
+        private String actionCode;
+        
+        /**
+         * The non-terminal state produced by this action, e.g., expression from
+         * <xmp>expression = ADD(expression l, expression r)</xmp>
+         */
+        private String m_state;
+
+        /**
+         *  The antecedent reduction of a nonterminal-to-nonterminal rule.
+         */
+        private String antecedentState;
+        
+        /**
+         *  The operator ID of a pattern rule; used to find content-handling
+         *  code from the InodeAdapter2.
+         */
+        private String m_operator;
+        
+        /**
+         * Names and types of the routine's parameters.
+         * Types are given as their non-terminal state names; toString()
+         * translates these BURM-centric types into the corresponding
+         * types in the target language using the mapping set up by
+         * the ReturnType directives in the input specification.
+         */
+        private Vector<ParameterDescriptor> m_parameterList = new Vector<ParameterDescriptor>();
+
+        /**
+         *  This action routine's index, assigned in entry order.
+         *  The action routines are enumerated as action_1(JBurgNode p), action_2(JBurgNode p),
+         *  etc. in the generated reducer.
+         */
+        int index;
+        
+        /**
+         * Track name-to-subtree mappings to be emitted.
+         */
+        class NamedSubtree
+        {
+            String path;
+            String name;
+            
+            NamedSubtree(String path, String name)
+            {
+                this.path = path;
+                this.name = name;
+            }
+        }
+        /**
+         *  Saved name-to-subtree mappings.
+         */
+        Vector<NamedSubtree> namedChildNodes = new Vector<NamedSubtree>();
+        
+        /**
+         * Construct a reduce action.
+         * @param strActionCode - the action's implementation
+         * code, in a target-specific language 
+         */
+        public JBurgReduceAction(String strActionCode)
+        {
+            this.actionCode = strActionCode;
+        }
+
+        /**
+         * Add a parameter to the action's parameter list.
+         */
+        public void addParameter(String parmName, String parmState, ParameterDescriptor.ArityType arityType)
+            throws Exception
+        {
+            m_parameterList.add(new ParameterDescriptor(parmName, parmState, arityType));
+        }
+
+        /**
+         *  Return this action's index (the N constituent of its generated name, action_N).
+         */
+        public int getIndex()
+        {
+            return this.index;
+        }
+
+        /**
+         *  Set this action's index (the N constituent of its generated name, action_N).
+         */
+        public void setIndex(int index)
+        {
+            this.index = index;
+        }
+
+        /**
+         * Set the non-terminal state this reduction derives.
+         * @param state - the non-terminal state, e.g., expression from
+         * <xmp>expression = ADD(expression l, expression r)</xmp>
+         */
+        public void setState(String state)
+        {
+            this.m_state = state;
+        }
+
+        /**
+         * @return the non-terminal state this reduction derives.
+         */
+        public String getState()
+        {
+            return this.m_state;
+        }
+
+        /**
+         *  @param operator the operator at the root of 
+         *    the matched subtree.
+         */
+        public void setOperator(String operator)
+        {
+            this.m_operator = operator;
+        }
+
+        /**
+         * @return the operator at the root of 
+         *    the matched subtree.
+         */
+        public String getOperator()
+        {
+            return m_operator;
+        }
+
+        /**
+         *  Return the reduction action's code, 
+         *  with prepended logic to pop its parameters off the stack.
+         */
+        @Override
+        public String toString()
+        {
+            String result = "";
+
+            for ( ParameterDescriptor next_param: m_parameterList )
+            {
+                String paramName = next_param.paramName;
+                String paramType = getReturnType(next_param.paramState);
+
+				if ( next_param.arityType == ParameterDescriptor.ArityType.Variable )
+				{
+					paramType = codeEmitter.genNaryContainerType(paramType);
+				}
+                
+                result += codeEmitter.genActionRoutineParameter(reducedValuesName, paramType, paramName);
+            }
+            
+            for ( NamedSubtree named_child: this.namedChildNodes )
+            {
+                result += "\n\t" + iNodeClass + " " + named_child.name + " = " + named_child.path + ";";
+            }
+
+            // insert 2 tabs into each line, and convert \r\n to \n so the generated
+			// files have consistent line endings.
+			String[] actionlines = this.actionCode.toString().split("\n");
+			for( int l = 0; l < actionlines.length; ++l )
+			{
+				if(actionlines[l].endsWith("\r"))
+				{
+					actionlines[l] = actionlines[l].substring(0,actionlines[l].length()-1);
+				}
+				result += "\n\t\t" + actionlines[l];
+			}
+
+			return result;
+        }
+
+        /**
+         *  Track a name-to-subtree mapping.  These are unreduced I-nodes,
+         *  typically a terminal identified by pattern match.
+         *  @param path - the path from the root node to the subtree.
+         *  @param name - the name to give to the subtree.
+         */
+        public void addNamedSubtree(String path, String name)
+        {
+            namedChildNodes.add(new NamedSubtree(path, name));
+        }
+
+        /**
+         *  Set this reduction's antecedent state, which
+         *  determines what reduction needs to run before
+         *  this one to transform one nonterminal to another.
+         */
+        public void setAntecedentState(String antecedentState)
+        {
+            this.antecedentState = antecedentState;
+        }
+
+        /**
+         *  @return true if this rule has an antecedent state,
+         *    which also means it's a nonterminal-to-nonterminal rule.
+         */
+        public boolean hasAntecedentState()
+        {
+            return getAntecedentState() != null;
+        }
+
+        /**
+         *  @return this rule's antecedent state,
+         *    or null if no antecedent is present.
+         */
+        public String getAntecedentState()
+        {
+            return this.antecedentState;
+        }
+    }
+
+    /**
+     *  ClosureRecord tracks the target state, cost, and action code associated
+     *  with a reduction from an antecedent nonterminal to a target nonterminal state.
+     */
+    private class ClosureRecord implements JBurgProduction
+    {
+        private JBurgRule rule;
+
+        public ClosureRecord(JBurgRule rule) throws Exception
+        {
+            this.rule = rule;
+        }
+
+        /**
+         *  Get the closure's uncached cost.
+         *  @deprecated Will be removed when rules start caching costs.
+         */
+        public String getCost(String baseNT)
+        {
+            return this.rule.getCost(baseNT);
+        }
+
+        /**
+         *  Get the closure's (potentially) cached cost.
+         */
+        @Override
+        public String getCachedCost()
+        {
+            String cost = this.rule.getCost(reducerNodeName);
+
+            //  The cost function result will be cached;
+            //  return a unique accessor method name.
+            if ( hasCostFunction() )
+                return genCallMethod(null, String.format("getCostFunctionResult_%h", cost));
+            else
+                return cost;
+        }
+
+        /**
+         *  @return the rule's hasCostFunction() result.
+         */
+        public boolean hasCostFunction()
+        {
+            return rule.hasCostFunction();
+        }
+
+        /**
+         *  @return the rule's getConstantCost() result.
+         */
+        public int getConstantCost()
+        {
+            return this.rule.getConstantCost();
+        }
+
+        /**
+         *  @return the rule's hasConstantCost() result.
+         */
+        public boolean hasConstantCost()
+        {
+            return this.rule.hasConstantCost();
+        }
+
+        /**
+         *  @return the rule's getAntecedentState() result.
+         */
+        public String getAntecedentState()
+        {
+            return this.rule.getAntecedentState();
+        }
+
+        /**
+         *  @return the rule's getGoalState() result.
+         */
+        @Override
+        public String getGoalState()
+        {
+            return this.rule.getGoalState();
+        }
+
+        /**
+         *  @return this closure's reduce action.
+         */
+        @Override
+        public JBurgReduceAction getReduceAction()
+        {
+            return rule.getReduceAction();
+        }
+
+        /**
+         *  Closure records are equal if they have the same goal state, action, and cost.
+         *  @see java.lang.Object#equals
+         *
+         * @param o -- the object to test for equality.
+         * @return true if o is a ClosureRecord and it equals this one.
+         *
+         */
+        @Override
+        public boolean equals(Object o)
+        {
+            boolean result = false;
+
+            if (o instanceof ClosureRecord)
+            {
+                ClosureRecord cuz = (ClosureRecord) o;
+
+                result = getGoalState().equals(cuz.getGoalState());
+                result &= getReduceAction().equals(cuz.getReduceAction());
+                result &= this.rule.getCost(reducerNodeName).equals(cuz.rule.getCost(reducerNodeName));
+            }
+
+            return result;
+        }
+
+        /**
+         *  @return true if the cost of this closure is known to be zero.
+         */
+        public boolean costIsZero()
+        {
+            return hasConstantCost() && getConstantCost() == 0;
+        }
+
+        /**
+         *  @return the computation of this closure's cost,
+         *    which is somewhat error-prone when open coded.
+         */
+        public String getCostComputation()
+        {
+            String antecedentCost = 
+                genCallMethod(
+                    null,
+                    "getCost",
+                    getNonterminal(this.getAntecedentState())
+                );
+
+            if ( this.costIsZero() )
+            {
+                return antecedentCost;
+            }
+            else
+                return codeEmitter.genOverflowSafeAdd(this.getCachedCost(), antecedentCost);
+        }
+
+        /**
+         *  Precompute the cost of this closure if possible.
+         *  @param productions - the set of productions at the
+         *    point where the closure's cost is required.  If
+         *    the closure derives a single pattern-match 
+         *    rule that has a constant cost, and the closure
+         *    itself has a constant cost, then the cost
+         *    can be precomputed.
+         *  @return the precomputed cost, or the result of
+         *    calling {@link getCostComputation()} to get
+         *    a non-constant cost.
+         */
+        public String getCostComputation(Multimap<String, JBurgProduction> productions)
+        {
+            //  A closure back to a pattern-match with constant cost can be precomputed.
+            if ( this.computesConstantCost(productions) )
+            {
+                return Integer.toString(getConstantCost(productions));
+            }
+            else
+            {
+                //  Return the naive cost computation.
+                return getCostComputation();
+            }
+        }
+
+        /**
+         *  Get the constant cost of a closure.
+         *  @param productions - the set of productions at the
+         *    point where the closure's cost is required.
+         *  @return the cost, with overflow-safe addition.
+         *  @pre computesConstantCost(productions) must be true.
+         */
+        @Override
+        public int getConstantCost(Multimap<String, JBurgProduction> productions)
+        {
+            long constantAntecedent;
+            JBurgProduction production = productions.get(this.getAntecedentState()).get(0);
+
+            if ( production instanceof JBurgRule )
+                constantAntecedent = ((JBurgRule)production).getConstantCost();
+            else
+                constantAntecedent = ((ClosureRecord)production).getConstantCost(productions);
+
+            //  Integer-overflow safe addition.
+            @SuppressWarnings("cast")
+            long accum = (long)this.getConstantCost() + constantAntecedent;
+
+            if ( accum < Integer.MAX_VALUE )
+                return (int) accum;
+            else
+                return Integer.MAX_VALUE;
+        }
+
+        /**
+         *  Does this closure compute a constant cost
+         *  in the context of the given productions?
+         *  @param productions - the set of productions at the
+         *    point where the closure's cost is required.  If
+         *    the closure derives a single pattern-match 
+         *    rule that has a constant cost, and the closure
+         *    itself has a constant cost, then the cost
+         *    can be precomputed.
+         *  @return true if the computed cost is constant.
+         */
+        @Override
+        public boolean computesConstantCost(Multimap<String, JBurgProduction> productions)
+        {
+            boolean result = false;
+            if ( this.hasConstantCost() )
+            {
+                ArrayList<JBurgProduction> antecedents = productions.get(this.getAntecedentState());
+
+                if ( antecedents.size() == 1 )
+                {
+                    JBurgProduction production = antecedents.get(0);
+
+                    if ( production instanceof JBurgRule )
+                        result = ((JBurgRule)production).hasConstantCost();
+                    else
+                        result = ((ClosureRecord)production).computesConstantCost(productions);
+                }
+            }
+
+            return result;
+        }
+
+    }
+
+	/**
+	 *  A ParameterDescriptor is a pair (name, subgoalState) which parallels
+	 *  the FOO(name, subgoalState) found in the specification's syntax.
+	 */
+    private static class ParameterDescriptor
+    {
+        String paramName;
+        String paramState;
+        ArityType arityType;
+        
+        enum ArityType {Fixed, Variable}
+
+        ParameterDescriptor(String paramName, String paramState, ArityType arityType)
+        {
+            this.paramName = paramName;
+            this.paramState = paramState;
+            this.arityType = arityType;
+        }
+    }
+
+    /**
+     *  Finish semantic analysis of the pattern rules.
+     */
+	private void ruleSemanticAnalysis() throws Exception
+    {
+        for (Vector<JBurgRule> vPatternRules: this.patternRules.values())
+        {
+            for (JBurgRule rule: vPatternRules)
+            {
+                for (JBurgPatternMatcher subgoal: rule.patternMatcher.getParameterizedSubtrees())
+                {
+                    rule.getReduceAction().addParameter(
+                            subgoal.getParameterName(), 
+                            subgoal.getSubgoal(), 
+                            subgoal.isNary()? ParameterDescriptor.ArityType.Variable: ParameterDescriptor.ArityType.Fixed
+                            );
+                }
+
+                //  Add the named subtrees as a convenience.
+                for ( JBurgPatternMatcher named_terminal: rule.patternMatcher.getNamedSubtrees())
+                {
+                    rule.getReduceAction().addNamedSubtree(
+                            named_terminal.generateReduceTimePath(codeEmitter, reducerNodeName, this.iNodeAdapter, this.adapter2 != null), 
+                            named_terminal.getParameterName()
+                            );
+                }
+
+                //  If the pattern matches an operator, send that operator
+                //  to the rule so it can be matched in content-access snippets.
+                if ( rule.patternMatcher.matchesOperator() )
+                    rule.getReduceAction().setOperator(rule.patternMatcher.getOperator());
+            }
+        }
+    }
+
+	private void emitComputeCostMatrixFunction(String iNodeClass, PrintStream output) throws Exception 
+	{
+		//  Emit the method declaration and local variables.
+		genDeclareMethod( output, Modifier.PRIVATE, "void", "computeCostMatrix", "JBurgAnnotation", "node");
+		genBeginBlock(output);
+		genLocalVar(output,  "long", "iCost", null );
+
+		//  The calculations are based on finding patterns
+		//  that can label the node's type.
+		genSwitch(output, genCallMethod( "node", "getOperator" ));
+
+		//  Try each pattern for a match.
+		//  Any pattern that matches then checks its 
+		//  cost to see if it's less than the current
+		//  best cost for the node, and replaces the 
+		//  node's current rule and cost with its own
+		//  if it's the new best cost.
+
+		for (Vector<JBurgRule> vPatternRules: this.patternRules.values())
+		{
+            genCase( output, vPatternRules.firstElement().getOperator() );
+            
+            //  Group rules by the patterns they match.
+            Multimap<String, JBurgRule> rules_by_pattern = new Multimap<String, JBurgRule>();
+
+            for (JBurgRule rule: vPatternRules)
+            {
+                String encoded_pattern = rule.getEncodedPattern();
+                rules_by_pattern.addToSet(encoded_pattern, rule);
+            }
+
+            
+            for ( String encoded_pattern: rules_by_pattern.keySet() )
+            {
+                ArrayList<JBurgRule> current_rules = rules_by_pattern.get(encoded_pattern);
+
+                //  Emit the structural pattern match if it was not optimized out.
+                if ( !encoded_pattern.equals(nilPattern))
+                    genIf(output, encoded_pattern);
+                
+                //  Begin a block whether there was a test or not, to scope variables.
+                //  TODO: This will not work for a target that doesn't block-scope locals.
+                genBeginBlock(output);
+                
+                //  Find common path and cost subexpressions.
+                Multimap<JBurgPatternMatcher, JBurgPatternMatcher> aggregated_matchers = new Multimap<JBurgPatternMatcher, JBurgPatternMatcher>();
+                
+                Multimap<String, JBurgPatternMatcher> costs = new Multimap<String, JBurgPatternMatcher>();
+                
+                for ( JBurgRule rule: rules_by_pattern.get(encoded_pattern))
+                {
+                    for (JBurgPatternMatcher subgoal: rule.patternMatcher.getParameterizedSubtrees() )
+                    {
+                        subgoal.aggregatePaths(aggregated_matchers);
+                        costs.addToSet(subgoal.generateCost(codeEmitter, "node"), subgoal);
+                    }
+                }
+                
+                //  Factor out common paths.
+                int factored_path_count = 0;
+                for ( JBurgPatternMatcher key: aggregated_matchers.keySet())
+                {
+                    ArrayList<JBurgPatternMatcher> matchers = aggregated_matchers.get(key);
+                    
+                    if ( matchers.size() > aggregatePathThreshold  )
+                    {
+                        String factored_path_var = "factored_path_" + Integer.toString(++factored_path_count);
+                        genLocalVar(output, "JBurgAnnotation ", factored_path_var, key.generatePathToRoot(codeEmitter, "node"));
+                        
+                        for ( JBurgPatternMatcher matcher: matchers)
+                            matcher.factoredPath = factored_path_var;
+                        
+                    }
+                }
+                
+                //  Now that the paths have been factored, factor out common costs.
+                int factored_cost_count = 0;
+                
+                for (String key: costs.keySet() )
+                {
+                    if ( costs.get(key).size() > 1 )
+                    {
+                        String factored_cost_var = "factored_cost" + Integer.toString(++factored_cost_count);
+                        //  Regenerate the cost because it may now use factored path expressions.
+                        ArrayList<JBurgPatternMatcher> matchers = costs.get(key);
+                        genLocalVar(output, "int", factored_cost_var, matchers.get(0).generateCost(codeEmitter, "node"));
+                        for ( JBurgPatternMatcher subgoal: matchers)
+                        {
+                            subgoal.factoredCost = factored_cost_var;
+                        }
+                    }
+                }
+
+                //  Emit the cost checks and their labeling logic.
+                
+                int patternPosition = 0;
+
+                for ( JBurgRule rule: current_rules )
+                    emitPatternRule(rule, iNodeClass, output, patternPosition++ == 0);
+
+                genEndBlock(output);
+            }
+
+            genEndCase(output);
+		}
+
+		genEndSwitch(output);
+		genEndBlock(output);
+	}
+	
+	/**
+	 *  Emit a routine that inspects the operators found at compiler compile time,
+	 *  and emits code to get the corresponding node's Nth child.
+	 *  @param output - the current output stream.
+	 */
+	private void emitGetNthChild(PrintStream output)
+	{
+	    genDeclareMethod(
+            output,
+            Modifier.PRIVATE,
+            this.iNodeClass,
+            "getNthChild",
+	        genFormals(this.iNodeClass, "node", "int", "index"),
+            throwsNothing
+        );
+	    genBeginBlock(output);
+	    genLocalVar(output, this.iNodeClass, "result", null);
+	    
+	    genSwitch(output, this.iNodeAdapter.genGetOperator("node", this.codeEmitter));
+	    for ( String opcode: this.allOperators)
+	    {
+	        Integer choice_count = this.adapter2.getMaxNthChildChoice(opcode);
+	        if ( choice_count != null )
+	        {
+                genCase(output, opcode);
+	            genSwitch(output, "index");
+	            for ( int i = 0; i < choice_count; i++ )
+	            {
+	                genCase(output, Integer.toString(i));
+	                genAssignment(output, "result", this.adapter2.genGetNthChild(opcode, "node", i, codeEmitter));
+	                genEndCase(output);
+	                
+	            }
+                //  generate the default case for the index.
+                {
+                    genDefaultCase(output);
+                    genAssignment(output, "result", this.adapter2.genGetDefaultChild(opcode, "node", "index", codeEmitter));
+                    genEndCase(output);
+                }
+
+                genEndSwitch(output);
+	            genEndCase(output);
+	        }
+	        
+	    }
+        //  generate the default case for the opcode.
+        {
+            genDefaultCase(output);
+            genAssignment(output, "result", this.iNodeAdapter.genGetNthChild("node", "index", codeEmitter));
+            genEndCase(output);
+        }
+	    genEndSwitch(output);
+	    genReturnValue(output, "result");
+	    genEndBlock(output);
+	}
+
+	private void emitPatternRule(JBurgRule p, String iNodeClass, PrintStream output, boolean isFirstRule ) throws Exception
+	{
+	    genComment(output, "Try matching " + p.getOperator() + " ==> " + p.getGoalState());
+	    
+        /*
+		 *   Emit code to compute this rule's cost, which is the cost of the rule itself 
+		 *   and all the non-terminals associated with it; then emit a comparison with
+		 *   the current best cost.
+		 */
+		Vector<String> sub_costs = new Vector<String>();
+
+		//  Compute the basic cost of the pattern match.
+		sub_costs.add(
+		    p.getCost( 
+    			codeEmitter.genCast( 
+    				iNodeClass, 
+    				codeEmitter.genAccessMember("node", "m_node")
+    			)
+            )
+		);
+		
+	   //  Try and compute a constant cost.
+        Integer constant_cost = null;
+
+		if ( p.hasConstantCost() )
+		{
+		    constant_cost = p.getConstantCost();
+		}
+
+		
+		for ( JBurgPatternMatcher subgoal: p.patternMatcher.getParameterizedSubtrees() )
+		{
+		    String subgoal_cost = subgoal.generateCost(codeEmitter, "node");
+		    if ( subgoal_cost != null )
+		    {
+		        sub_costs.add(subgoal_cost);
+		        constant_cost = null;
+		    }
+		}
+		
+		String cost_factor;
+        boolean checkCostFactor = true;
+		
+		if ( constant_cost != null )
+		{
+		    cost_factor = constant_cost.toString();
+            checkCostFactor = constant_cost == Integer.MAX_VALUE;
+		}
+		else if ( sub_costs.size() == 1 )
+		{
+		    cost_factor = sub_costs.get(0);
+		}
+		else
+		{
+            //  Compute the cost using a long accumulator.
+		    cost_factor = "iCost";
+
+    		String match_cost = codeEmitter.genCast("long", sub_costs.get(0));
+        		
+            for ( int i = 1; i < sub_costs.size(); i++ )
+                match_cost = codeEmitter.genAddition(match_cost, codeEmitter.genCast("long", sub_costs.get(i)));
+    		
+    		genAssignment(output, "iCost", match_cost);
+		}
+
+        if ( !isFirstRule || checkCostFactor )
+        {
+            //  Does this pattern match cost less than the previously best match?
+            String getCostCall = genCallMethod ( "node", "getCost", codeEmitter.genGetGoalState(p) );
+            String costCheck = codeEmitter.genCmpLess( getCostCall, cost_factor );
+            genIf(output, costCheck );
+        }
+
+		output.print( codeEmitter.genBeginBlock() );
+
+		/*
+		 *   Emit code to handle a successful match.
+		 */
+
+		//  Reset the reduce-time rule to fire.
+        String strRule = String.valueOf(p.getReduceAction().getIndex());
+        
+        //  Now that the comparison has been done using long arithmetic,
+        //  iCost can be safely truncated to fit in an int.
+        if ( cost_factor.equals("iCost"))
+            cost_factor = codeEmitter.genCast("int", cost_factor);
+
+        genExpressionStmt(
+            output, 
+			codeEmitter.genCallMethod(
+				"node", 
+				"reset", 
+				new String[] { codeEmitter.genGetGoalState(p), cost_factor, strRule }
+			)
+		);
+
+        if (hasClosure(p))
+		{
+            genExpressionStmt(
+                output,
+				genCallMethod( 
+					null, 
+					"closure_" + p.getGoalState(),
+					"node", cost_factor
+				)
+			);
+        }
+
+        //  End the block begun by the cost comparison above.
+        genEndBlock(output);
+    }
+	
+	private JBurgPatternMatcher generateMatcher(AST pattern_root)
+	throws Exception
+	{   
+        JBurgPatternEncoder patternBURM = new JBurgPatternEncoder();
+
+        //  As we traverse the subtree, we may find parameterized subtrees,
+        //  for example, in ADD(expr lhs, expr rhs) lhs and rhs are paramterized
+        //  subtrees.  These subtrees play several parts in the computation of the
+        //  locally-optimal reduction:
+        //  -  They contribute to the rule's computed cost.
+        //  -  The reduction's action code may refer to these elements by name.
+        //  -  If the rule is of the form OP(nttype1 a [, nttype2 b]), then the rule
+        //     must enforce this reduce-time goal state on its subtrees' reductions.
+        patternBURM.setSubgoals(new Vector<JBurgPatternMatcher>());
+        
+        //  There may also be named terminals in the pattern;
+        //  as a convenience, record these so that they
+        //  can be used by name in the reduction.
+        patternBURM.setNamedterminals(new Vector<JBurgPatternMatcher>());
+        
+        patternBURM.setOperators(this.allOperators);
+
+        try
+        {
+            patternBURM.burm( pattern_root );
+        }
+        catch (IllegalStateException burm_error )
+        {
+            if ( this.patternMatcherDumpFile != null )
+            {
+                //  Dump the BURM's debugging info.
+                java.io.PrintWriter dumper = new java.io.PrintWriter(new java.io.FileWriter(patternMatcherDumpFile));
+                dumper.println ( "<?xml version=\"1.0\"?>");
+                dumper.println("<BurmDump date=\"" + new java.util.Date().toString() + "\">");
+                patternBURM.dump(dumper);
+                dumper.println("<AST>");
+                dumper.println("<![CDATA[");
+                dumper.println(pattern_root.toStringTree());
+                dumper.println("]]>");
+                dumper.println("</AST>");
+                dumper.println("</BurmDump>");
+                dumper.flush();
+                dumper.close();
+            }
+            
+            throw burm_error;
+        }
+        
+        JBurgPatternMatcher recognizer = (JBurgPatternMatcher) patternBURM.getResult();
+        recognizer.setParameterizedSubtrees(patternBURM.getSubgoals());
+        recognizer.setNamedSubtrees(patternBURM.getNamedterminals());
+        
+        return recognizer;
+	}
+
+	private void emitActions(ArrayList<JBurgReduceAction> reduceActions, String iNodeClass, PrintStream output)
+	{
+		int i = 1;
+
+		//  Print out the individual action routines.
+		for (JBurgReduceAction nextAction: reduceActions )
+		{
+            output.println();
+			genComment(output, nextAction.getState());
+			
+            //  Compute the type of the i-node.
+            String typeOfOperatorINode;
+			final String actionOperator = nextAction.getOperator();
+
+			if ( opcodeNodeTypes.containsKey(actionOperator) )
+			    typeOfOperatorINode = opcodeNodeTypes.get(actionOperator);
+			else
+			    typeOfOperatorINode = iNodeClass;
+
+			genDeclareMethod(
+                output,
+				Modifier.PRIVATE,
+				getReturnType(nextAction.getState()),
+				"action_" + String.valueOf(i++),
+				genFormals(typeOfOperatorINode, reducerNodeName),
+				throwsException
+			);
+				
+			genBeginBlock(output);
+
+			String action_routine = nextAction.toString();
+			
+			//  Replace #OPERATOR# with a content-access snippet.
+			if ( this.adapter2 != null && nextAction.getOperator() != null )
+			{
+			    String content_access = this.adapter2.genGetContent(nextAction.getOperator(), this.reducerNodeName, nextAction.getState(), codeEmitter);
+			    
+			    if ( content_access != null )
+			    {
+			        String content_pattern = "#" + nextAction.getOperator() + "#";
+			        action_routine = action_routine.replaceAll( content_pattern, content_access );
+			    }
+			}
+			//  Replace #goalstate with the name of the action's input node.
+			
+			String goalPattern = "#" + nextAction.getState();
+			action_routine = action_routine.replaceAll( goalPattern, this.reducerNodeName );
+			
+			output.print( action_routine );
+
+			genEndBlock(output);
+		}
+
+		//  Emit their common dispatch routine.
+		genDeclareMethod(
+            output,
+			Modifier.PRIVATE,
+			"void",
+			"dispatchAction",
+			genFormals("JBurgAnnotation", "___node", "int", "iRule"),
+			throwsException
+		);
+
+		genBeginBlock(output);
+
+        genLocalVar(
+            output,
+            iNodeClass,
+            this.reducerNodeName,
+            genCallMethod("___node", "getNode")
+        );
+
+		genSwitch(output, "iRule" );
+
+		//  Emit the dispatch case statement.
+		for (i = 1; i <= reduceActions.size(); i++)
+		{
+            JBurgReduceAction action = reduceActions.get(i-1);
+
+			genCase(output, String.valueOf(i));
+            {
+                //  If this is a nonterminal-to-nonterminal
+                //  transformation, run the antecedent
+                //  reduction action.
+                if ( action.hasAntecedentState() )
+                {
+                    genExpressionStmt(
+                        output,
+                        genCallMethod(
+                            "this",
+                            "reduceAntecedent",
+                            "___node", codeEmitter.genGetGoalState(action.getAntecedentState())
+                        )
+                    );
+                }
+
+                final String operatorName = action.getOperator();
+                final String nodeTypeForOperator =
+                    operatorName != null ? opcodeNodeTypes.get(operatorName) : null;
+                String nodeParameterString = reducerNodeName;
+                if (nodeTypeForOperator != null)
+                    nodeParameterString = codeEmitter.genCast(nodeTypeForOperator, nodeParameterString);
+                
+                genExpressionStmt(
+                    output,
+                    codeEmitter.genPushToStack(
+                        reducedValuesName,
+                        genCallMethod(
+                            "this",
+                            "action_" + String.valueOf(i),
+                            nodeParameterString
+                        )
+                    )
+                );
+            }
+			genEndCase(output);
+		}
+
+		genDefaultCase(output);
+        {
+            genThrow(output, "\"Unmatched reduce action \" + iRule" );
+        }
+        genEndBlock(output); // genEndCase() without unreachable break
+	    
+		genEndSwitch(output);
+		genEndBlock(output);
+	}
+
+	private void emitNTConstants(Set<String> goal_states, PrintStream output)
+	{
+		Iterator<String> keys = goal_states.iterator();
+
+		int nthConstant = 0;
+
+		output.print(codeEmitter.genBeginLine());
+		while (keys.hasNext())
+		{
+			nthConstant++;
+
+			String strNTName = codeEmitter.genGetGoalState(keys.next().toString() );
+
+            genInstanceField(
+                output, 
+                Modifier.PUBLIC + Modifier.STATIC + Modifier.FINAL,
+                "int",
+                strNTName,
+                String.valueOf(nthConstant)
+			);
+		}
+	
+        genInstanceField(
+            output,
+            Modifier.PUBLIC + Modifier.STATIC + Modifier.FINAL,
+            "int",
+            "nStates",
+            String.valueOf(nthConstant)
+		);
+	}
+
+	public void emitCostFunctions(ArrayList<AST> costFunctions, String iNodeClass, PrintStream output) 
+	{
+		String[][] costParms = genFormals( iNodeClass, reducerNodeName );
+
+		for (int i = 0; i < costFunctions.size(); i++)
+		{
+			AST currentNode = costFunctions.get(i);
+
+			String functionName = currentNode.getFirstChild().getText();
+
+			genDeclareMethod(output, Modifier.PRIVATE, "int", functionName, costParms, throwsNothing );
+			output.print( codeEmitter.genBeginLine() );
+			output.print( getCodeBlock(currentNode) );
+		}
+	}
+
+	public void emitClosures(Map<String, Vector<ClosureRecord>> closureSets, String iNodeClass, PrintStream output)
+	{
+        //  TODO: This method can be removed once the C++ target
+        //  has been updated to 1.9.0 style semantics.
+		for (String strClosureNT: closureSets.keySet())
+		{
+			genDeclareMethod(
+				output,
+                Modifier.PRIVATE,
+                "void",
+                "closure_" + strClosureNT,
+                genFormals("JBurgAnnotation", reducerNodeName, "long", "c"),
+                throwsNothing
+			);
+
+			genBeginBlock(output);
+
+			genLocalVar(output, "long", "iCost", null );
+
+			for (ClosureRecord newClosure: closureSets.get(strClosureNT) )
+			{
+				String strRule;
+
+				if (newClosure.getReduceAction() != null)
+					strRule = String.valueOf(newClosure.getReduceAction()
+													   .getIndex());
+				else
+					strRule = "0";
+
+				String closureCost = newClosure.getCost( codeEmitter.genAccessMember(reducerNodeName, "m_node"));
+
+				if (closureCost.equals("0") )
+				{
+					genAssignment( output, "iCost", "c");
+				}
+				else
+				{
+					genAssignment( output, "iCost",  codeEmitter.genAddition( "c", closureCost ));
+				}
+
+                genIf(
+                    output,
+                    codeEmitter.genCmpLess(
+                        genCallMethod (
+                            reducerNodeName,
+                            "getCost", 
+                            getNonterminal(newClosure.getGoalState())
+                        ),
+                        "iCost"
+					)
+				);
+				output.print( codeEmitter.genBeginBlock() );
+
+                genExpressionStmt(
+                    output,
+					codeEmitter.genCallMethod(
+						reducerNodeName,
+						"reset",
+						new String[] { getNonterminal(newClosure.getGoalState()), codeEmitter.genCast("int","iCost"), strRule }
+					)
+				);
+
+                genExpressionStmt(
+                    output,
+					codeEmitter.genCallMethod(
+						reducerNodeName,
+						"recordAntecedent",
+						new String[] { getNonterminal(newClosure.getGoalState() ), getNonterminal(strClosureNT ) }
+					)
+				);
+
+				if (hasClosure(newClosure))
+				{
+                    genExpressionStmt(
+                        output,
+						genCallMethod(
+							"this",
+							"closure_" + newClosure.getGoalState(),
+							reducerNodeName, "iCost"
+						)
+					);
+				}
+
+				genEndBlock(output);
+			}
+
+			genEndBlock(output);
+		}
+	}
+
+
+	public void emitLabelFunction(String iNodeClass, PrintStream output)
+	{
+        genDeclareMethod(
+            output,
+            Modifier.PUBLIC,
+            "JBurgAnnotation",
+            "label",  
+            genFormals(iNodeClass, JBurgGenerator.initalParamName),
+            throwsNothing
+		);
+
+		genBeginBlock(output);
+		genLocalVar (output,  "JBurgAnnotation", "result", codeEmitter.genNullPointer() );
+		genLocalVar (output,  "int", "i", null);
+		genLocalVar (output,  "int", "arity", null);
+
+        String nodeAlloc = null;
+
+        if ( codeEmitter.supportsSpecializedAnnotations() )
+        {
+			nodeAlloc = genCallMethod( "this", "getJBurgAnnotation", JBurgGenerator.initalParamName);
+        }
+        else
+        {
+			nodeAlloc = codeEmitter.genNewObject( "JBurgAnnotation", new String[] { JBurgGenerator.initalParamName, "nStates + 1" } );
+        }
+		genAssignment( output, "result", nodeAlloc );
+
+		//  Generate a loop over children that labels them,
+		//  and adds their labelled JBurgAnnotation nodes to the current node.
+		genAssignment(output, "arity", this.iNodeAdapter.genGetArity(JBurgGenerator.initalParamName, codeEmitter));	
+		genAssignment(output, "i", "0");
+
+		genLine(output, codeEmitter.genWhileLoop( codeEmitter.genCmpLess("arity", "i")));
+		genBeginBlock(output);
+        {
+            genExpressionStmt(
+                output,
+                genCallMethod(
+                    "result",
+                    "addChild",
+                    genCallLabel("i")
+                )
+            );
+            
+            genAssignment(output, "i", codeEmitter.genAddition("i", "1") );
+        }
+		genEndBlock(output); // while
+
+		//  Call the costing function.
+        if ( ! this.codeEmitter.supportsSpecializedAnnotations() )
+            genExpressionStmt(output, genCallMethod( "this", "computeCostMatrix", "result"));
+
+		genReturnValue(output, "result" );
+		genEndBlock(output);
+	}
+
+    /**
+     *  Emit the subclasses of JBurgAnnotation that encode data for specific pattern matches.
+     *  @param output - the destination output stream.
+     */
+    void emitCompressedAnnotations(PrintStream output)
+    {
+        //  Emit an annotation object for each equivalence class of rules.
+        for ( String operator: this.compressedAnnotations.getOperators() )
+            for ( ArrayList<JBurgRule> currentRules : this.compressedAnnotations.getRulesFor(operator) )
+                emitAnnotation(output, currentRules);
+
+        //  Emit getJBurgAnnotation
+        genDeclareMethod(
+            output,
+            Modifier.PUBLIC,
+            "JBurgAnnotation",
+            "getJBurgAnnotation",  
+            genFormals(iNodeClass, "node"),
+            throwsNothing
+        );
+
+        genBeginBlock(output);
+
+	    genSwitch(output, this.iNodeAdapter.genGetOperator("node", this.codeEmitter));
+
+        for ( String operator: compressedAnnotations.getOperators() )
+        {
+            genCase(output, operator);
+
+            for ( ArrayList<JBurgRule> rules : this.compressedAnnotations.getRulesFor(operator) )
+            {
+                int arity = getMinumumArity(rules);
+
+                if ( hasNaryness(rules) )
+                    genIf(output, String.format("%s >= %d", this.iNodeAdapter.genGetArity("node", codeEmitter), arity));
+                else
+                    genIf(output, String.format("%s == %d", this.iNodeAdapter.genGetArity("node", codeEmitter), arity));
+                indentNextLine();
+                genReturnValue(output, String.format("new %s(node)", getSpecializedClassName(rules)));
+            }
+
+            genEndCase(output);
+        }
+
+        genEndSwitch(output);
+        
+        genLine(output, "return new JBurgAnnotationGeneral(node, nStates+1);");
+        genEndBlock(output); // getJBurgAnnotation
+        
+    }
+
+    /**
+     *  Emit an annotation subclass.
+     *  @param output - the output stream.
+     *  @param currentRules - the set of rules that are this annotation's
+     *    domain.  The rules all have the same operator, but their arity
+     *    may differ if some of them are n-ary.
+     */
+    void emitAnnotation(PrintStream output, ArrayList<JBurgRule> currentRules )
+    {
+        int nominalArity = getMinumumArity(currentRules);
+
+        // The coalesced graph of closures for all the rules.
+        ClosureGraph closureCosts = new ClosureGraph();
+
+        //  Factored common subtrees of each pattern matcher.
+        //  TODO: These can be further coalesced by using the right comparator.
+        Multimap<JBurgPatternMatcher,JBurgPatternMatcher> commonSubtrees = new Multimap<JBurgPatternMatcher,JBurgPatternMatcher>();
+
+        //  Rules and closures by nonterminal.
+        Multimap<String, JBurgProduction> productions = new Multimap<String, JBurgProduction>();
+
+        //  Cached subtree costs.
+        Map<String, String> cachedCosts = new HashMap<String, String>();
+
+        //  Are all these rules fixed-arity?
+        //  If so, the annotation knows its arity a priori.
+        boolean fixedArity = !hasNaryness(currentRules);
+
+        /*
+         *  **  Semantic analysis  **
+         */
+        
+        //  Populate the closure graph and factor the pattern matchers.
+        for ( JBurgRule rule: currentRules)
+        {
+            productions.addToSet(rule.getGoalState(), rule);
+
+            closureCosts.addClosures(rule.getGoalState());
+
+            if ( fixedArity )
+                rule.patternMatcher.setFixedArityContext(true);
+
+            for ( JBurgPatternMatcher subgoal: rule.patternMatcher.getParameterizedSubtrees() )
+                subgoal.findFactors(commonSubtrees);
+
+            if ( rule.patternMatcher.getNominalArity() > 0 )
+            {
+                cachedCosts.put(rule.getGoalState(), String.format("cachedCostFor_%s", rule.getGoalState()));
+            }
+        }
+
+        //  Add the closures from the coalesced closure graph
+        //  to the set of productions.
+        for ( Map.Entry<String, ArrayList<ClosureRecord>> closures: closureCosts.entrySet() )
+        {
+            for ( ClosureRecord closure: closures.getValue() )
+                productions.addToSet(closures.getKey(), closure);
+        }
+
+        //  Emitting these variable declarations mutates the 
+        //  pattern matchers, so they can only be emitted once.
+        //  Capture the results so we can write it more than once.
+        Map<JBurgPatternMatcher,String> factored_variables = emitFactoredPathVariables(commonSubtrees);
+
+        /*
+         * Begin code-gen of the class.
+         */
+        output.println();
+        String annotationClass = getSpecializedClassName(currentRules);
+        
+        genLine(output, String.format("class %s extends JBurgSpecializedAnnotation", annotationClass));
+        genBeginBlock(output);
+
+        //  Emit a field for each child.
+        for ( int fieldIdx = 0; fieldIdx < nominalArity; fieldIdx++ )
+        {
+            genInstanceField( output, Modifier.PRIVATE, "JBurgAnnotation", String.format("subtree%d", fieldIdx), null);
+        }
+
+        if ( !fixedArity )
+        {
+            genInstanceField( 
+                output,
+                Modifier.PRIVATE,
+                codeEmitter.genNaryContainerType("JBurgAnnotation"),
+                "narySubtrees",
+                String.format("new %s()", codeEmitter.genNaryContainerType("JBurgAnnotation"))
+            );
+        }
+
+        // Emit the constructor.
+        //  TODO: The emitter needs a genConstructor() API.
+        genLine(output, String.format("%s(%s node)", annotationClass, this.iNodeClass));
+        genBeginBlock(output);
+        //  TODO: The emitter also needs a callSuperclassConstructor() API.
+        genLine(output, "super(node);");
+        genEndBlock(output);
+
+        for ( String cachedCostVar: cachedCosts.values() )
+        {
+            genInstanceField( output, Modifier.PRIVATE, "int", cachedCostVar, UNINITIALIZED);
+        }
+
+        /*
+         *  ** Emit getCost()  **
+         */
+        genDeclareMethod( output, Modifier.PUBLIC, "int", "getCost", "int", "goalState" );
+        genBeginBlock(output);
+
+        genSwitch(output, "goalState");
+
+        for ( Map.Entry<String, ArrayList<JBurgProduction>> productionsByNonterminal: productions.entrySet() )
+        {
+            genCase(output, getNonterminal(productionsByNonterminal.getKey()));
+
+            ArrayList<JBurgProduction> currentProductions = productionsByNonterminal.getValue();
+
+            //  Try to find the optimal production at compiler-compile time.
+            JBurgProduction optimalProduction = findOptimalProduction(currentProductions, productions);
+
+            if ( optimalProduction != null )
+            {
+                genReturnValue(output, Integer.toString(optimalProduction.getConstantCost(productions)));
+            }
+            else
+            {
+                final boolean hasMultipleProductions = currentProductions.size() > 1;
+                final boolean cacheCost = cachedCosts.containsKey(productionsByNonterminal.getKey());
+
+                boolean currentCostDeclared = false;
+
+                String bestCostVar;
+
+                if ( cacheCost )
+                {
+                    bestCostVar = cachedCosts.get(productionsByNonterminal.getKey());
+                    genIf ( output, codeEmitter.genCmpEquality(bestCostVar, UNINITIALIZED, true) );
+                    genBeginBlock(output);
+                }
+                else if ( hasMultipleProductions )
+                {
+                    //  Store the best cost in a local.
+                    bestCostVar = "bestCost";
+                    genLocalVar(output, "int", "bestCost");
+                }
+                else
+                {
+                    bestCostVar = codeEmitter.genMaxIntValue();
+                }
+
+                for ( int i = 0; i < currentProductions.size(); i++ )
+                {
+                    JBurgProduction production = currentProductions.get(i);
+                    String productionCost = getCostForProduction(production, productions);
+
+                    if ( currentProductions.size() == 1 && !cacheCost )
+                    {
+                        //  Only one production, just return its cost.
+                        genReturnValue(output, productionCost);
+                    }
+                    else if ( i == 0 )
+                    {
+                        //  Emit an assignment with no if guards
+                        //  if this is the first (or only) assignment.
+                        genAssignment(output, bestCostVar, productionCost);
+                        output.println();
+                    }
+                    else
+                    {
+                        //  If the cost uses a computation, put it in a temp.
+                        if ( !production.computesConstantCost(productions) )
+                        {
+                            if ( ! currentCostDeclared )
+                            {
+                                genLocalVar(output, "int", "currentCost", productionCost);
+                                currentCostDeclared = true;
+                            }
+                            else
+                            {
+                                genAssignment(output, "currentCost", productionCost);
+                            }
+                            
+                            productionCost = "currentCost";
+                        }
+
+                        genIf(output, codeEmitter.genCmpLess(bestCostVar, productionCost));
+                        indentNextLine();
+                        genAssignment(output, bestCostVar, productionCost);
+                    }
+                }
+
+                if ( cacheCost )
+                    //  End the if statement.
+                    genEndBlock(output);
+
+                if ( cacheCost || hasMultipleProductions )
+                    genReturnValue(output, bestCostVar);
+                // else the cost has already been returned.
+            }
+
+            genEndBlock(output); // end case without unreachable break
+        }
+        genEndSwitch(output);
+        genReturnValue(output, codeEmitter.genMaxIntValue());
+        genEndBlock(output); // getCost
+
+        /*
+         *  ** Emit getRule()  **
+         */
+        genDeclareMethod(output, Modifier.PUBLIC, "int", "getRule", "int", "goalState" );
+        genBeginBlock(output);
+
+        genSwitch(output, "goalState");
+
+        for ( Map.Entry<String, ArrayList<JBurgProduction>> productionsByNonterminal: productions.entrySet() )
+        {
+            genCase(output, getNonterminal(productionsByNonterminal.getKey()));
+
+            ArrayList<JBurgProduction> currentProductions = productionsByNonterminal.getValue();
+
+            //  Try to resolve the optimal production at compiler-compile time.
+            JBurgProduction optimalProduction = findOptimalProduction(currentProductions, productions);
+
+            if ( optimalProduction != null )
+            {
+                genReturnValue(output, Integer.toString(optimalProduction.getReduceAction().getIndex()));
+            }
+            else
+            {
+                // Emit the analogous compile-time computation.
+                genLocalVar(output, "int", "rule", NO_FEASIBLE_RULE);
+                genLocalVar(output, "int", "bestCost", codeEmitter.genMaxIntValue());
+
+                //  Emit this declaration at its first use.
+                boolean currentCostDeclared = false;
+
+                for ( int i = 0; i < currentProductions.size(); i++ )
+                {
+                    boolean costIsConstant = false;
+                    String currentProductionCost;
+
+                    //  Extract required information from the production:
+                    //  what is its cost, and is it constant?
+                    JBurgProduction production = currentProductions.get(i);
+
+                    if ( production.computesConstantCost(productions) )
+                    {
+                        int constantCost = production.getConstantCost(productions);
+                        costIsConstant = constantCost < Integer.MAX_VALUE;
+                        currentProductionCost = Integer.toString(constantCost);
+                    }
+                    else
+                    {
+                        currentProductionCost = getCostForProduction(production, productions);
+                    }
+
+                    //  Generate the necessary tests and assignments.
+
+                    //  If the first production's cost is constant
+                    //  (and feasible, which has been checked and
+                    //  incorporated into costIsConstant), then 
+                    //  testing it against bestCost is a tautology.
+                    boolean needComparison = (!costIsConstant) || i > 0;
+
+                    if ( needComparison )
+                    {
+                        //  If the cost uses a computation, put it in a temp.
+                        if ( ! costIsConstant )
+                        {
+                            if ( ! currentCostDeclared )
+                            {
+                                genLocalVar(output, "int", "currentCost", currentProductionCost);
+                                currentCostDeclared = true;
+                            }
+                            else
+                            {
+                                genAssignment(output, "currentCost", currentProductionCost);
+                            }
+                            currentProductionCost = "currentCost";
+                        }
+
+                        genIf(output, codeEmitter.genCmpLess("bestCost", currentProductionCost));
+                        genBeginBlock(output);
+                    }
+
+                    //  Track the new best cost if there's another choice to be evaluated.
+                    if ( i + 1 < currentProductions.size() )
+                        genAssignment(output, "bestCost", currentProductionCost);
+
+                    genAssignment(output, "rule", Integer.toString(production.getReduceAction().getIndex()));
+
+                    if ( needComparison )
+                        genEndBlock(output);
+                }
+
+                genReturnValue(output,"rule");
+            }
+
+            genEndBlock(output);  // endCase() without unreachable break statement
+        }
+
+        genEndSwitch(output);
+        genReturnValue(output, NO_FEASIBLE_RULE);
+        genEndBlock(output); // getRule
+
+        /*
+         *  ** Emit getArity()  **
+         */
+        genDeclareMethod(output, Modifier.PUBLIC, "int", "getArity");
+        genBeginBlock(output);
+
+        if ( fixedArity )
+        {
+            genReturnValue(output, Integer.toString(nominalArity));
+        }
+        else if ( nominalArity != 0 )
+        {
+            //  TODO: Need an emitter method to get the correct size() call
+            genReturnValue(output, String.format("%d + %s", nominalArity, "narySubtrees.size()" ));
+        }
+        else
+        {
+            genReturnValue(output, "narySubtrees.size()" );
+        }
+
+        genEndBlock(output); // getArity
+
+        /*
+         *  **  Emit overrides for getNthChild() and addChild() if necessary  **
+         */
+        if ( nominalArity > 0 || ! fixedArity )
+        {
+            //  getNthChild
+            genDeclareMethod( output, Modifier.PUBLIC, "JBurgAnnotation", "getNthChild", "int", "index");
+
+            genBeginBlock(output); // getNthChild
+            genSwitch(output, "index");
+
+            for ( int i = 0; i < nominalArity; i++ )
+            {
+                genLine(output, String.format("case %d:", i));
+                genSingleLineBlock(output, String.format("return subtree%d;", i));
+            }
+
+            genDefaultCase(output);
+            if ( ! fixedArity )
+            {
+                if ( nominalArity == 0 )
+                    genLine(output, "return narySubtrees.get(index);");
+                else
+                    genLine(output, String.format("return narySubtrees.get(index - %d);", nominalArity));
+            }
+            else
+            {
+                genThrow(output, "\"Invalid index \" + index");
+            }
+            genEndBlock(output);  //  genEndCase() without unreachable break
+
+            genEndBlock(output); // switch
+            genEndBlock(output); // getNthChild
+
+            //  addChild
+            genDeclareMethod(output, Modifier.PUBLIC, "void", "addChild", "JBurgAnnotation", "child");
+
+            genBeginBlock(output); // addChild
+
+            for ( int i = 0; i < nominalArity; i++ )
+            {
+                if ( i == 0 )
+                    genLine(output, String.format("if ( subtree%d == null )", i));
+                else
+                    genLine(output, String.format("else if ( subtree%d == null )", i));
+                genSingleLineBlock(output, String.format("subtree%d = child;", i));
+            }
+
+            if ( nominalArity > 0 )
+                genLine(output, String.format("else"));
+
+            if ( ! fixedArity )
+                genSingleLineBlock(output, "narySubtrees.add(child);");
+            else
+                genSingleLineBlock(output, "throw new IllegalStateException(\"too many children\");");
+
+            genEndBlock(output); // addChild
+        }
+
+        //  Emit caches for any costs that require a function call;
+        //  the BURM's contract says these functions are only called once.
+        Set<String> emittedCosts = new HashSet<String>();
+
+        for ( ArrayList<ClosureRecord> closures: closureCosts.values() )
+            for ( ClosureRecord closure: closures )
+                if ( closure.hasCostFunction() && emittedCosts.add(closure.getCachedCost()) )
+                    emitCachedCost(output, closure.getCachedCost(), closure.getCost("m_node")); 
+
+        for ( JBurgRule rule: currentRules )
+        {
+            if ( rule.needsCostFunction() )
+                emitGetCostForRule(output, rule, factored_variables);
+
+            if ( rule.hasCostFunction() && emittedCosts.add(rule.getCachedCost()) )
+                emitCachedCost(output, rule.getCachedCost(), rule.getCost("m_node")); 
+        }
+
+        //  Finish the compressed annotation's class declaration.
+        genEndBlock(output);
+    }
+
+    /**
+     *  @param productions - a list of productions.
+     *  @return true if the list has one member and that member has a constant cost.
+     */
+    boolean hasConstantCost(ArrayList<JBurgProduction> productions)
+    {
+        return productions != null &&
+            productions.size() == 1 && 
+            productions.get(0) instanceof JBurgRule &&
+            ((JBurgRule)productions.get(0)).hasConstantCost();
+    }
+
+    /**
+     *  @return the expression to use for a production's cost;
+     *    this may be an arithmetic expression or a call
+     *    to an implementation method, depending on
+     *    the production's complexity.
+     */
+    String getCostForProduction(JBurgProduction production, Multimap<String,JBurgProduction> productions)
+    {
+        if ( production instanceof JBurgRule )
+        {
+            JBurgRule rule = (JBurgRule) production;
+            if ( rule.needsCostFunction() )
+                return genCallMethod(null, getCostingFunctionForRule(rule), "goalState");
+            else
+                return getCompositeCostOfRule(rule);
+        }
+        else
+        {
+            return ((ClosureRecord)production).getCostComputation(productions);
+        }
+    }
+
+    /**
+     *  Do compiler-compile time dynamic programming to choose
+     *  the best alternative from a list of possibilities.
+     *  @param currentProductions - the list of productions to choose from.
+     *  @param allProductions - the entire set of productions active at the site.
+     */
+    JBurgProduction findOptimalProduction(List<JBurgProduction> currentProductions, Multimap<String, JBurgProduction> allProductions)
+    {
+        int bestCost = Integer.MAX_VALUE;
+        JBurgProduction result = null;
+
+        for ( JBurgProduction production: currentProductions )
+        {
+            if ( production.computesConstantCost(allProductions) )
+            {
+                int cost = production.getConstantCost(allProductions);
+                if ( cost < bestCost )
+                {
+                    result = production;
+                    bestCost = cost;
+                }
+            }
+            else
+            {
+                //  Can't be determined at compiler-compile time.
+                result = null;
+                break;
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     *  Emit a rule's cost function.
+     *  @param output - the current output stream.
+     *  @param rule - the rule to emit.
+     *  @param factored_variables - the list of pattern matchers it's worth factoring.
+     */
+    void emitGetCostForRule(PrintStream output, JBurgRule rule, Map<JBurgPatternMatcher,String> factored_variables )
+    {
+        assert(rule.needsCostFunction());
+
+        genDeclareMethod(output, Modifier.PRIVATE, "int", getCostingFunctionForRule(rule), "int", "goalState");
+        genBeginBlock(output);
+        {
+            for ( String var_decl: factored_variables.values() )
+                if ( rule.patternMatcher.usesFactoredVariable(var_decl) )
+                genLine(output, var_decl);
+
+            output.println();
+
+            String patternMatch = rule.patternMatcher.generatePatternRecognizer(
+                    codeEmitter,
+                    "this",
+                    JBurgGenerator.this.adapter2
+                );
+
+            // The pattern match may be null if it's trival.
+            if ( patternMatch != null )
+            {
+                genIf(output, patternMatch);
+                indentNextLine();
+            }
+
+            genReturnValue(output, getCompositeCostOfRule(rule));
+
+            if ( patternMatch != null )
+            {
+                genLine(output, codeEmitter.genElse());
+                indentNextLine();
+                genReturnValue(output, codeEmitter.genMaxIntValue());
+            }
+
+        }
+        genEndBlock(output);
+    }
+
+    /**
+     *  @return a unique identifier for the method that computes
+     *    a rule's cost (note: this is not the rule's cost function).
+     */
+    String getCostingFunctionForRule(JBurgRule rule)
+    {
+        return String.format("getCostForRule_%h", rule);
+    }
+
+    /**
+     *  Emit the cache field and accessor function for a cached 
+     *    result of a cost function call.
+     */
+    void emitCachedCost(PrintStream output, String functionName, String payload)
+    {
+        //  Strip () characters.
+        functionName = functionName.substring(0, functionName.length() -2);
+
+        String varName = String.format("cachedCostFunctionResult_%h", functionName);
+
+        genInstanceField( output, Modifier.PRIVATE, "int", varName, UNINITIALIZED);
+
+        genDeclareMethod( output, Modifier.PRIVATE, "int", functionName );
+
+        genBeginBlock(output);
+        {
+            genIf ( output, codeEmitter.genCmpEquality(varName, UNINITIALIZED, true) );
+            indentNextLine();
+            genAssignment(output, varName, payload);
+            genReturnValue(output, varName);
+        }
+        genEndBlock(output);
+    }
+
+    /**
+     *  Add up all of a rule's cost factors.
+     *  @param rule - the rule of interest.
+     *  @return an overflow-safe addition of the
+     *    rule's cost and the costs of its subtrees.
+     */
+    String getCompositeCostOfRule(JBurgRule rule)
+    {
+        String subtreeCost = null;
+
+        for ( JBurgPatternMatcher subgoal: rule.patternMatcher.getParameterizedSubtrees() )
+        {
+            String subgoalCost = subgoal.generateCost(codeEmitter, "this");
+
+            if ( subgoalCost != null )
+                subtreeCost = codeEmitter.genAddition(subtreeCost, codeEmitter.genCast("long", subgoalCost));
+        }
+
+        if ( subtreeCost != null )
+            return codeEmitter.genOverflowSafeAdd(rule.getCachedCost(), subtreeCost);
+        else
+            return rule.getCachedCost();
+    }
+
+    /**
+     *  Emit the factored path variables into a map,
+     *  and mutate the affected pattern matchers
+     *  to refer to the factored variable.
+     *  @param commonSubtrees - the map of common subtrees to their pattern matchers.
+     */
+    Map<JBurgPatternMatcher,String> emitFactoredPathVariables(Multimap<JBurgPatternMatcher, JBurgPatternMatcher> commonSubtrees)
+    {
+        Map<JBurgPatternMatcher,String> result = new TreeMap<JBurgPatternMatcher, String>();
+
+        int varNum = 0;
+        for ( Map.Entry<JBurgPatternMatcher, ArrayList<JBurgPatternMatcher>> factored: commonSubtrees.entrySet() )
+        {
+            String varName = String.format("factoredPath_%d", varNum++);
+
+            result.put(factored.getKey(), codeEmitter.genLocalVar("JBurgAnnotation", varName, factored.getKey().generateFactoredReference(codeEmitter)));
+
+            for ( JBurgPatternMatcher matcher: factored.getValue() )
+                matcher.factoredPath = varName;
+        }
+    
+        return result;
+    }
+
+    /**
+     *  ClosuresByNonterminal is a convenience class that holds
+     *  closure sets grouped by nonterminal; the NT may
+     *  be the production or the antecedent, depending on usage.
+     */
+    class ClosuresByNonterminal extends Multimap<String, ClosureRecord>
+    {
+        /**
+         *  Add a closure record.
+         *  @param nt - the nonterminal that indexes this closure.
+         *    May be the production or the antecedent.
+         */
+        public void addClosure(String nt, ClosureRecord closure)
+        {
+            if ( ! this.getSet(nt).contains(closure) )
+                this.getSet(nt).add(closure);
+        }
+    }
+
+    /**
+     *  A ClosureGraph holds the graph of closures that
+     *  can be reached from a starting nonterminal.
+     */
+    class ClosureGraph extends ClosuresByNonterminal
+    {
+        /**
+         *  Sweep the set of nonterminal-to-nonterminal rules and
+         *  add any that can be produced by the most recently
+         *  added nonterminal.
+         *  @param currentNT - the most recently added nonterminal.
+         */
+        void addClosures(String currentNT)
+        {
+            addClosures(currentNT, currentNT);
+        }
+
+        /**
+         *  Sweep the set of nonterminal-to-nonterminal rules and
+         *  add any that can be produced by the most recently
+         *  added nonterminal.
+         *  @param currentNT - the most recently added nonterminal.
+         *  @param patternNT - the nonterminal produced by the pattern match.
+         */
+        void addClosures(String currentNT, String patternNT)
+        {
+            if ( JBurgGenerator.this.closureSets.containsKey(currentNT) )
+            {
+                for ( ClosureRecord closure: JBurgGenerator.this.closureSets.get(currentNT) )
+                {
+                    String newNT = closure.getGoalState();
+
+                    //  Can't replace the pattern with a closure.
+                    if ( !newNT.equals(patternNT) )
+                    {
+                        if ( ! this.containsKey ( newNT ) )
+                        {
+                            super.addClosure(newNT, closure);
+                            addClosures(newNT);
+                        }
+                        else
+                        {
+                            //  Add this closure, but its consequent
+                            //  closures are already in the set so
+                            //  it's not necessary to add them.
+                            super.addClosure(newNT, closure);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     *  @return the current Logger implementation;
+     *    constructs a Logger that emits messages
+     *    to stdout/stderr.
+     */
+    Logger getLogger()
+    {
+        if ( null == this.logger )
+            this.logger = new Logger(true, true, true);
+        return this.logger;
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genAssignment and prints the result.
+     */
+	void genAssignment(PrintStream output, String lvalue, String rvalue)
+	{
+		genLine(output, codeEmitter.genAssignment( lvalue, rvalue ) );
+	}
+
+    /**
+     *  Convenience method wraps codeEmitter.genComment and prints the result.
+     */
+    void genComment(PrintStream output, String comment)
+    {
+        genLine(output, codeEmitter.genComment(comment) );
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genLine and prints the result.
+     *  @param output - the current output stream
+     *  @param contents - the contents to write.  Contents will be trimmed
+     *    of whitespace before output.
+     */
+    void genLine(PrintStream output, String contents)
+    {
+        String formattedContents = contents.trim();
+
+        if ( doIndentNextLine )
+            formattedContents = "\t" + formattedContents;
+
+        doIndentNextLine = false;
+        output.print(codeEmitter.genLine(formattedContents) );
+    }
+
+    /**
+     *  When set, indent the next line of output to note that
+     *  the statement on that line is an if/then branch.
+     *  @see {@link #indentNextLine()}, which sets this field.
+     *  @see {@link #genLine(PrintStream,String)}, which consumes this field and resets it.
+     *
+     */
+    boolean doIndentNextLine = false;
+
+    /**
+     *  Signal that the next line should be indented.
+     *  @see {@link #doIndentNextLine}, which this method sets.
+     */
+    void indentNextLine()
+    {
+        doIndentNextLine = true;
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genLine and prints the result,
+     *  with an extra level of nesting added to improve readability.
+     */
+    void genSingleLineBlock (PrintStream output, String contents)
+    {
+        indentNextLine();
+        genLine(output, contents);
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genBeginBlock and prints the result.
+     */
+    void genBeginBlock(PrintStream output)
+    {
+        output.print(codeEmitter.genBeginBlock());
+    }
+    
+    /**
+     *  Convenience method wraps codeEmitter.genEndBlock and prints the result.
+     */
+    void genEndBlock(PrintStream output)
+    {
+        output.print(codeEmitter.genEndBlock());
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genSwitch and prints the result.
+     */
+    void genSwitch(PrintStream output, String expression)
+    {
+        genLine(output, codeEmitter.genSwitch(expression) );
+        genBeginBlock(output);
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genEndSwitch and prints the result.
+     */
+    void genEndSwitch(PrintStream output)
+    {
+        genLine(output, codeEmitter.genEndSwitch() );
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genCase and prints the result.
+     */
+    void genCase(PrintStream output, String selector )
+    {
+        output.print(codeEmitter.genCase(selector));
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genEndCase and prints the result.
+     */
+    void genEndCase(PrintStream output)
+    {
+        output.print(codeEmitter.genEndCase());
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genDefaultCase and prints the result.
+     */
+    void genDefaultCase(PrintStream output)
+    {
+        genLine(output, codeEmitter.genDefaultCase());
+        genBeginBlock(output);
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genIf and prints the result.
+     */
+    void genIf(PrintStream output, String condition )
+    {
+        genLine(output, codeEmitter.genIf(condition));
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genReturnValue and prints the result.
+     */
+    void genReturnValue(PrintStream output, String value )
+    {
+        genLine(output, codeEmitter.genReturnValue(value) + codeEmitter.genEndStmt());
+    }
+
+    /**
+     *  Convenience method prints an expression as a statement.
+     */
+    void genExpressionStmt(PrintStream output, String expr)
+    {
+        genLine(output, expr + codeEmitter.genEndStmt());
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genDeclareMethod and prints the result.
+     */
+    void genDeclareMethod(PrintStream output, int modifiers, String returnClass, String name, String[][] plist, Class<?>[] exceptions )
+    {
+        output.print(codeEmitter.declareMethod(modifiers, returnClass, name, plist, exceptions));
+    }
+
+    /**
+     *  Convenience method declares BURM methods that don't throw.
+     */
+    void genDeclareMethod(PrintStream output, int modifiers, String returnClass, String name, Object ... plist)
+    {
+        genDeclareMethod(output, modifiers, returnClass, name, genFormals(plist), throwsNothing);
+    }
+
+    /**
+     *  Convenience method declares BURM methods that don't throw or have parameters.
+     */
+    void genDeclareMethod(PrintStream output, int modifiers, String returnClass, String name)
+    {
+        genDeclareMethod(output, modifiers, returnClass, name, noFormalParameters, throwsNothing);
+    }
+
+    /**
+     *  Generate formal parameters from a list of type, name pairs.
+     *  @param raw_formals - a variadic list of type, name pairs.
+     *  @return the raw formals marshalled into a 2-dimensional array.
+     */
+    String[][] genFormals(Object ... raw_formals)
+    {
+        assert(raw_formals.length % 2 == 0): "n-ary formal parameters must be in (type, name) pairs";
+
+        String[][] plist = new String[raw_formals.length/2][2];
+        for ( int i = 0; i < raw_formals.length - 1; i += 2  )
+        {
+            plist[i/2][0] = raw_formals[i].toString();
+            plist[i/2][1] = raw_formals[i+1].toString();
+        }
+
+        return plist;
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genLocalVar and prints the result.
+     */
+    void genLocalVar(PrintStream output, String varType, String varName, String initializer)
+    {
+        genLine(output, codeEmitter.genLocalVar(varType, varName, initializer));
+        output.println();
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genLocalVar and prints the result.
+     */
+    void genLocalVar(PrintStream output, String varType, String varName)
+    {
+        genLocalVar(output, varType, varName, null);
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genThrow and prints the result.
+     */
+    void genThrow( PrintStream output, String diagnostic )
+    {
+        genLine(output, codeEmitter.genThrow(diagnostic) + codeEmitter.genEndStmt() );
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genInstanceField and prints the result.
+     */
+    void genInstanceField(PrintStream output, int modifiers, String type, String name, String initializer)
+    { 
+        genLine(output, codeEmitter.genInstanceField(modifiers, type, name, initializer));
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genGetGoalState.
+     */
+    String getNonterminal(String raw_nt)
+    {
+        return codeEmitter.genGetGoalState(raw_nt);
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genCallMethod for methods with no parameters.
+     */
+    String genCallMethod(String stem, String methodName)
+    {
+        return codeEmitter.genCallMethod(stem, methodName, noActualParameters );
+    }
+    
+    /**
+     *  Convenience method wraps codeEmitter.genCallMethod for methods with one parameter.
+     */
+    String genCallMethod(String stem, String methodName, Object param)
+    {
+        return codeEmitter.genCallMethod(stem, methodName, new String[] { param.toString() } );
+    }
+
+    /**
+     *  Convenience method wraps codeEmitter.genCallMethod for methods with two parameters.
+     */
+    String genCallMethod(String stem, String methodName, Object param1, Object param2)
+    {
+        return codeEmitter.genCallMethod(stem, methodName, new String[] { param1.toString(), param2.toString() } );
+    }
+
+
+    /**
+     *  @return the input string with its outer
+     *    layer of {} brackets stripped off.
+     */
+    public static String stripBrackets(String src)
+    {
+        int startchar = src.indexOf('{') + 1;
+        int lentoend = src.lastIndexOf('}');
+        return src.substring( startchar, lentoend-startchar );
+    
+    }
+
+    /**
+     *  Generate the correct calling sequence to get the nth child
+     *  of an input i-node.
+     *  @param indexTerm - the n in nth child.
+     */
+    String genGetNthChild(String indexTerm)
+    {
+		return this.adapter2 != null ?
+		    genCallMethod("this", "getNthChild", JBurgGenerator.initalParamName, indexTerm):
+	        this.iNodeAdapter.genGetNthChild( JBurgGenerator.initalParamName, indexTerm, codeEmitter );
+    }
+		
+    /**
+     *  Generate the calling sequence for the label() method.
+     */
+    String genCallLabel(String indexTerm)
+    {
+        return genCallMethod(
+            "this",
+            "label",
+            codeEmitter.genCast( 
+                iNodeClass, 
+                genGetNthChild(indexTerm)
+            )
+        );
+    }
+
+    /**
+     *  @return true if any rules in the list have n-ary operands.
+     */
+    private boolean hasNaryness(Collection<JBurgRule> rules)
+    {
+        boolean result = false;
+
+        for ( JBurgRule rule: rules )
+            result |= rule.patternMatcher.hasNaryness();
+
+        return result;
+    }
+
+    /**
+     *  @return the minumum arity of a list of rules.
+     */
+    private int getMinumumArity(Collection<JBurgRule> rules)
+    {
+        int result = Integer.MAX_VALUE;
+
+        for ( JBurgRule rule: rules )
+            result = Math.min(result, rule.patternMatcher.getNominalArity());
+
+        return result;
+    }
+
+    /**
+     *  @return the name of a JBurgAnnotation specialized subclass.
+     */
+    String getSpecializedClassName(ArrayList<JBurgRule> rules )
+    {
+        if ( hasNaryness(rules) )
+            return String.format("JBurgAnnotation_%s_%d_n", rules.get(0).getOperator(), getMinumumArity(rules));
+        else
+            return String.format("JBurgAnnotation_%s_%d", rules.get(0).getOperator(), getMinumumArity(rules));
+    }
+
+    /**
+     *  RulesByOperatorAndArity is a multidimensional associative store that
+     *  sorts JBurgRules into equivalence classes, where the equivalaence
+     *  relation is "these rules can share an annotation object."
+     */
+    class RulesByOperatorAndArity
+    {
+        /**
+         *  Unsorted rules.
+         */
+        Multimap<String, JBurgRule> unsortedRules = new Multimap<String, JBurgRule>();
+
+        Map<String, Iterable<ArrayList<JBurgRule>>> sortedRules = null;
+
+        public void addAll(List<JBurgRule> rules)
+        {
+            assert sortedRules == null;
+
+            String operator = rules.get(0).getOperator();
+            this.unsortedRules.addAllToSet(operator, rules);
+        }
+
+        public void addRule(JBurgRule rule)
+        {
+            assert sortedRules == null;
+            this.unsortedRules.addToSet(rule.getOperator(), rule);
+        }
+
+        public Set<String> getOperators()
+        {
+            return unsortedRules.keySet();
+        }
+
+        public Iterable<ArrayList<JBurgRule>> getRulesFor(String operator)
+        {
+            if ( this.sortedRules == null )
+            {
+                this.sortedRules = new TreeMap<String, Iterable<ArrayList<JBurgRule>>>();
+
+                for ( String op: getOperators() )
+                    this.sortedRules.put(op, sortRules(this.unsortedRules.get(op)));
+            }
+
+            return this.sortedRules.get(operator);
+        }
+
+        private Iterable<ArrayList<JBurgRule>> sortRules( ArrayList<JBurgRule> unsorted_rules)
+        {
+            //  Find the minumum arity of all n-ary patterns;
+            //  any rule with arity >= this limit gets sorted
+            //  into the "variable-arity" bucket.
+            int arity_requires_variable = Integer.MAX_VALUE;
+
+            for ( JBurgRule rule: unsorted_rules )
+                if ( rule.patternMatcher.hasNaryness() )
+                    arity_requires_variable = Math.min(arity_requires_variable, rule.patternMatcher.getNominalArity());
+
+            Multimap<Integer, JBurgRule> rules = new Multimap<Integer, JBurgRule>(); 
+
+            for ( JBurgRule rule: unsorted_rules )
+            {
+                //  All n-ary patterns and any fixed-arity patterns that
+                //  overlap with n-ary patterns go into a common annotation;
+                //  fixed-arity patterns of arity less than the smallest
+                //  n-ary arity can't be confused with an n-ary pattern
+                //  and can go in their own annotation.
+                Integer key = 
+                    rule.patternMatcher.getNominalArity() < arity_requires_variable?
+                    rule.patternMatcher.getNominalArity():
+                    arity_requires_variable;
+
+                rules.addToSet(key,rule);
+            }
+
+            return rules.values();
+        }
+
+    }
+}