| /* |
| * |
| * 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) |
| { |
| if ( ! ruleNumber.containsKey(rule) ) |
| ruleNumber.put(rule, String.format("getCostForRule%x", ruleNumber.size())); |
| return ruleNumber.get(rule); |
| } |
| |
| private Map<JBurgRule,String> ruleNumber = new HashMap<JBurgRule, String>(); |
| |
| /** |
| * 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(); |
| } |
| |
| } |
| } |