/*
 * [The "BSD license"]
 *  Copyright (c) 2010 Terence Parr
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 *  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.antlr.tool;

import org.antlr.analysis.DFA;
import org.antlr.grammar.v3.ANTLRParser;
import org.antlr.misc.Utils;
import org.antlr.runtime.misc.Stats;

import java.lang.reflect.Field;
import java.util.*;

public class GrammarReport {
	
Because I may change the stats, I need to track version for later computations to be consistent.
/** Because I may change the stats, I need to track version for later * computations to be consistent. */
public static final String Version = "5"; public static final String GRAMMAR_STATS_FILENAME = "grammar.stats"; public static class ReportData { String version; String gname; String gtype; String language; int numRules; int numOuterProductions; int numberOfDecisionsInRealRules; int numberOfDecisions; int numberOfCyclicDecisions; int numberOfFixedKDecisions; int numLL1; int mink; int maxk; double avgk; int numTokens; long DFACreationWallClockTimeInMS; int numberOfSemanticPredicates; int numberOfManualLookaheadOptions; // TODO: verify int numNonLLStarDecisions; int numNondeterministicDecisions; int numNondeterministicDecisionNumbersResolvedWithPredicates; int errors; int warnings; int infos; //int num_synpreds; int blocksWithSynPreds; int decisionsWhoseDFAsUsesSynPreds; int blocksWithSemPreds; int decisionsWhoseDFAsUsesSemPreds; String output; String grammarLevelk; String grammarLevelBacktrack; } public static final String newline = System.getProperty("line.separator"); public Grammar grammar; public GrammarReport(Grammar grammar) { this.grammar = grammar; } public static ReportData getReportData(Grammar g) { ReportData data = new ReportData(); data.version = Version; data.gname = g.name; data.gtype = g.getGrammarTypeString(); data.language = (String) g.getOption("language"); data.output = (String) g.getOption("output"); if ( data.output==null ) { data.output = "none"; } String k = (String) g.getOption("k"); if ( k==null ) { k = "none"; } data.grammarLevelk = k; String backtrack = (String) g.getOption("backtrack"); if ( backtrack==null ) { backtrack = "false"; } data.grammarLevelBacktrack = backtrack; int totalNonSynPredProductions = 0; int totalNonSynPredRules = 0; Collection<Rule> rules = g.getRules(); for (Rule r : rules) { if ( !r.name.toUpperCase() .startsWith(Grammar.SYNPRED_RULE_PREFIX.toUpperCase()) ) { totalNonSynPredProductions += r.numberOfAlts; totalNonSynPredRules++; } } data.numRules = totalNonSynPredRules; data.numOuterProductions = totalNonSynPredProductions; int numACyclicDecisions = g.getNumberOfDecisions()- g.getNumberOfCyclicDecisions(); List<Integer> depths = new ArrayList<Integer>(); int[] acyclicDFAStates = new int[numACyclicDecisions]; int[] cyclicDFAStates = new int[g.getNumberOfCyclicDecisions()]; int acyclicIndex = 0; int cyclicIndex = 0; int numLL1 = 0; int blocksWithSynPreds = 0; int dfaWithSynPred = 0; int numDecisions = 0; int numCyclicDecisions = 0; for (int i=1; i<= g.getNumberOfDecisions(); i++) { Grammar.Decision d = g.getDecision(i); if( d.dfa==null ) { //System.out.println("dec "+d.decision+" has no AST"); continue; } Rule r = d.dfa.decisionNFAStartState.enclosingRule; if ( r.name.toUpperCase() .startsWith(Grammar.SYNPRED_RULE_PREFIX.toUpperCase()) ) { //System.out.println("dec "+d.decision+" is a synpred"); continue; } numDecisions++; if ( blockHasSynPred(d.blockAST) ) blocksWithSynPreds++; //if ( g.decisionsWhoseDFAsUsesSynPreds.contains(d.dfa) ) dfaWithSynPred++; if ( d.dfa.hasSynPred() ) dfaWithSynPred++; // NFAState decisionStartState = grammar.getDecisionNFAStartState(d.decision); // int nalts = grammar.getNumberOfAltsForDecisionNFA(decisionStartState); // for (int alt = 1; alt <= nalts; alt++) { // int walkAlt = // decisionStartState.translateDisplayAltToWalkAlt(alt); // NFAState altLeftEdge = grammar.getNFAStateForAltOfDecision(decisionStartState, walkAlt); // } // int nalts = grammar.getNumberOfAltsForDecisionNFA(d.dfa.decisionNFAStartState); // for (int a=1; a<nalts; a++) { // NFAState altStart = // grammar.getNFAStateForAltOfDecision(d.dfa.decisionNFAStartState, a); // } if ( !d.dfa.isCyclic() ) { if ( d.dfa.isClassicDFA() ) { int maxk = d.dfa.getMaxLookaheadDepth(); //System.out.println("decision "+d.dfa.decisionNumber+" k="+maxk); if ( maxk==1 ) numLL1++; depths.add( maxk ); } else { acyclicDFAStates[acyclicIndex] = d.dfa.getNumberOfStates(); acyclicIndex++; } } else { //System.out.println("CYCLIC decision "+d.dfa.decisionNumber); numCyclicDecisions++; cyclicDFAStates[cyclicIndex] = d.dfa.getNumberOfStates(); cyclicIndex++; } } data.numLL1 = numLL1; data.numberOfFixedKDecisions = depths.size(); data.mink = Stats.min(depths); data.maxk = Stats.max(depths); data.avgk = Stats.avg(depths); data.numberOfDecisionsInRealRules = numDecisions; data.numberOfDecisions = g.getNumberOfDecisions(); data.numberOfCyclicDecisions = numCyclicDecisions; // Map synpreds = grammar.getSyntacticPredicates(); // int num_synpreds = synpreds!=null ? synpreds.size() : 0; // data.num_synpreds = num_synpreds; data.blocksWithSynPreds = blocksWithSynPreds; data.decisionsWhoseDFAsUsesSynPreds = dfaWithSynPred; // // data. = Stats.stddev(depths); // // data. = Stats.min(acyclicDFAStates); // // data. = Stats.max(acyclicDFAStates); // // data. = Stats.avg(acyclicDFAStates); // // data. = Stats.stddev(acyclicDFAStates); // // data. = Stats.sum(acyclicDFAStates); // // data. = Stats.min(cyclicDFAStates); // // data. = Stats.max(cyclicDFAStates); // // data. = Stats.avg(cyclicDFAStates); // // data. = Stats.stddev(cyclicDFAStates); // // data. = Stats.sum(cyclicDFAStates); data.numTokens = g.getTokenTypes().size(); data.DFACreationWallClockTimeInMS = g.DFACreationWallClockTimeInMS; // includes true ones and preds in synpreds I think; strip out. data.numberOfSemanticPredicates = g.numberOfSemanticPredicates; data.numberOfManualLookaheadOptions = g.numberOfManualLookaheadOptions; data.numNonLLStarDecisions = g.numNonLLStar; data.numNondeterministicDecisions = g.setOfNondeterministicDecisionNumbers.size(); data.numNondeterministicDecisionNumbersResolvedWithPredicates = g.setOfNondeterministicDecisionNumbersResolvedWithPredicates.size(); data.errors = ErrorManager.getErrorState().errors; data.warnings = ErrorManager.getErrorState().warnings; data.infos = ErrorManager.getErrorState().infos; data.blocksWithSemPreds = g.blocksWithSemPreds.size(); data.decisionsWhoseDFAsUsesSemPreds = g.decisionsWhoseDFAsUsesSemPreds.size(); return data; }
Create a single-line stats report about this grammar suitable to send to the notify page at antlr.org
/** Create a single-line stats report about this grammar suitable to * send to the notify page at antlr.org */
public String toNotifyString() { StringBuilder buf = new StringBuilder(); ReportData data = getReportData(grammar); Field[] fields = ReportData.class.getDeclaredFields(); int i = 0; for (Field f : fields) { try { Object v = f.get(data); String s = v!=null ? v.toString() : "null"; if (i>0) buf.append('\t'); buf.append(s); } catch (Exception e) { ErrorManager.internalError("Can't get data", e); } i++; } return buf.toString(); } public String getBacktrackingReport() { StringBuilder buf = new StringBuilder(); buf.append("Backtracking report:"); buf.append(newline); buf.append("Number of decisions that backtrack: "); buf.append(grammar.decisionsWhoseDFAsUsesSynPreds.size()); buf.append(newline); buf.append(getDFALocations(grammar.decisionsWhoseDFAsUsesSynPreds)); return buf.toString(); } protected String getDFALocations(Set<DFA> dfas) { Set<Integer> decisions = new HashSet<Integer>(); StringBuilder buf = new StringBuilder(); for (DFA dfa : dfas) { // if we aborted a DFA and redid with k=1, the backtrackin if ( decisions.contains(Utils.integer(dfa.decisionNumber)) ) { continue; } decisions.add(Utils.integer(dfa.decisionNumber)); buf.append("Rule "); buf.append(dfa.decisionNFAStartState.enclosingRule.name); buf.append(" decision "); buf.append(dfa.decisionNumber); buf.append(" location "); GrammarAST decisionAST = dfa.decisionNFAStartState.associatedASTNode; buf.append(decisionAST.getLine()); buf.append(":"); buf.append(decisionAST.getCharPositionInLine()); buf.append(newline); } return buf.toString(); }
Given a stats line suitable for sending to the antlr.org site, return a human-readable version. Return null if there is a problem with the data.
/** Given a stats line suitable for sending to the antlr.org site, * return a human-readable version. Return null if there is a * problem with the data. */
@Override public String toString() { return toString(toNotifyString()); } protected static ReportData decodeReportData(String dataS) { ReportData data = new ReportData(); StringTokenizer st = new StringTokenizer(dataS, "\t"); Field[] fields = ReportData.class.getDeclaredFields(); for (Field f : fields) { String v = st.nextToken(); try { if ( f.getType() == String.class ) { f.set(data, v); } else if ( f.getType() == double.class ) { f.set(data, Double.valueOf(v)); } else { f.set(data, Integer.valueOf(v)); } } catch (Exception e) { ErrorManager.internalError("Can't get data", e); } } return data; } public static String toString(String notifyDataLine) { ReportData data = decodeReportData(notifyDataLine); if ( data ==null ) { return null; } StringBuilder buf = new StringBuilder(); buf.append("ANTLR Grammar Report; Stats Version "); buf.append(data.version); buf.append('\n'); buf.append("Grammar: "); buf.append(data.gname); buf.append('\n'); buf.append("Type: "); buf.append(data.gtype); buf.append('\n'); buf.append("Target language: "); buf.append(data.language); buf.append('\n'); buf.append("Output: "); buf.append(data.output); buf.append('\n'); buf.append("Grammar option k: "); buf.append(data.grammarLevelk); buf.append('\n'); buf.append("Grammar option backtrack: "); buf.append(data.grammarLevelBacktrack); buf.append('\n'); buf.append("Rules: "); buf.append(data.numRules); buf.append('\n'); buf.append("Outer productions: "); buf.append(data.numOuterProductions); buf.append('\n'); buf.append("Decisions: "); buf.append(data.numberOfDecisions); buf.append('\n'); buf.append("Decisions (ignoring decisions in synpreds): "); buf.append(data.numberOfDecisionsInRealRules); buf.append('\n'); buf.append("Fixed k DFA decisions: "); buf.append(data.numberOfFixedKDecisions); buf.append('\n'); buf.append("Cyclic DFA decisions: "); buf.append(data.numberOfCyclicDecisions); buf.append('\n'); buf.append("LL(1) decisions: "); buf.append(data.numLL1); buf.append('\n'); buf.append("Min fixed k: "); buf.append(data.mink); buf.append('\n'); buf.append("Max fixed k: "); buf.append(data.maxk); buf.append('\n'); buf.append("Average fixed k: "); buf.append(data.avgk); buf.append('\n'); // buf.append("Standard deviation of fixed k: "); buf.append(fields[12]); // buf.append('\n'); // buf.append("Min acyclic DFA states: "); buf.append(fields[13]); // buf.append('\n'); // buf.append("Max acyclic DFA states: "); buf.append(fields[14]); // buf.append('\n'); // buf.append("Average acyclic DFA states: "); buf.append(fields[15]); // buf.append('\n'); // buf.append("Standard deviation of acyclic DFA states: "); buf.append(fields[16]); // buf.append('\n'); // buf.append("Total acyclic DFA states: "); buf.append(fields[17]); // buf.append('\n'); // buf.append("Min cyclic DFA states: "); buf.append(fields[18]); // buf.append('\n'); // buf.append("Max cyclic DFA states: "); buf.append(fields[19]); // buf.append('\n'); // buf.append("Average cyclic DFA states: "); buf.append(fields[20]); // buf.append('\n'); // buf.append("Standard deviation of cyclic DFA states: "); buf.append(fields[21]); // buf.append('\n'); // buf.append("Total cyclic DFA states: "); buf.append(fields[22]); // buf.append('\n'); buf.append("DFA creation time in ms: "); buf.append(data.DFACreationWallClockTimeInMS); buf.append('\n'); // buf.append("Number of syntactic predicates available (including synpred rules): "); // buf.append(data.num_synpreds); // buf.append('\n'); buf.append("Decisions with available syntactic predicates (ignoring synpred rules): "); buf.append(data.blocksWithSynPreds); buf.append('\n'); buf.append("Decision DFAs using syntactic predicates (ignoring synpred rules): "); buf.append(data.decisionsWhoseDFAsUsesSynPreds); buf.append('\n'); buf.append("Number of semantic predicates found: "); buf.append(data.numberOfSemanticPredicates); buf.append('\n'); buf.append("Decisions with semantic predicates: "); buf.append(data.blocksWithSemPreds); buf.append('\n'); buf.append("Decision DFAs using semantic predicates: "); buf.append(data.decisionsWhoseDFAsUsesSemPreds); buf.append('\n'); buf.append("Number of (likely) non-LL(*) decisions: "); buf.append(data.numNonLLStarDecisions); buf.append('\n'); buf.append("Number of nondeterministic decisions: "); buf.append(data.numNondeterministicDecisions); buf.append('\n'); buf.append("Number of nondeterministic decisions resolved with predicates: "); buf.append(data.numNondeterministicDecisionNumbersResolvedWithPredicates); buf.append('\n'); buf.append("Number of manual or forced fixed lookahead k=value options: "); buf.append(data.numberOfManualLookaheadOptions); buf.append('\n'); buf.append("Vocabulary size: "); buf.append(data.numTokens); buf.append('\n'); buf.append("Number of errors: "); buf.append(data.errors); buf.append('\n'); buf.append("Number of warnings: "); buf.append(data.warnings); buf.append('\n'); buf.append("Number of infos: "); buf.append(data.infos); buf.append('\n'); return buf.toString(); } public static boolean blockHasSynPred(GrammarAST blockAST) { GrammarAST c1 = blockAST.findFirstType(ANTLRParser.SYN_SEMPRED); GrammarAST c2 = blockAST.findFirstType(ANTLRParser.BACKTRACK_SEMPRED); if ( c1!=null || c2!=null ) return true; // System.out.println(blockAST.enclosingRuleName+ // " "+blockAST.getLine()+":"+blockAST.getColumn()+" no preds AST="+blockAST.toStringTree()); return false; } }