diff --git a/tool/test/org/antlr/v4/test/Java-LR.g b/tool/test/org/antlr/v4/test/Java-LR.g index 97e5facbf..06aa32128 100644 --- a/tool/test/org/antlr/v4/test/Java-LR.g +++ b/tool/test/org/antlr/v4/test/Java-LR.g @@ -169,7 +169,6 @@ * Character.isJavaIdentifierPart(int) returns true." */ grammar Java; -options {backtrack=true; memoize=true;} @lexer::members { protected boolean enumIsKeyword = true; @@ -184,7 +183,9 @@ compilationUnit ( packageDeclaration importDeclaration* typeDeclaration* | classOrInterfaceDeclaration typeDeclaration* ) + EOF | packageDeclaration? importDeclaration* typeDeclaration* + EOF ; packageDeclaration @@ -499,7 +500,7 @@ constructorBody explicitConstructorInvocation : nonWildcardTypeArguments? ('this' | 'super') arguments ';' - | expression '.' nonWildcardTypeArguments? 'super' arguments ';' + | primary '.' nonWildcardTypeArguments? 'super' arguments ';' ; @@ -711,17 +712,12 @@ constantExpression ; expression - : parExpression - | 'this' - | 'super' - | literal - | Identifier + : primary | expression '.' Identifier - | expression '.' 'class' // should be type.class but causes backtracking | expression '.' 'this' | expression '.' 'super' '(' expressionList? ')' - | expression '.' 'super' '.' Identifier arguments? | expression '.' 'new' Identifier '(' expressionList? ')' + | expression '.' 'super' '.' Identifier arguments? | expression '.' explicitGenericInvocation | 'new' creator | expression '[' expression ']' @@ -754,8 +750,20 @@ expression | '>' '>' '=' | '>' '>' '>' '=' | '<' '<' '=' - | '%=') expression - ; + | '%=' + ) + expression + ; + +primary + : '(' expression ')' + | 'this' + | 'super' + | literal + | Identifier + | type '.' 'class' + | 'void' '.' 'class' + ; creator : nonWildcardTypeArguments createdName classCreatorRest @@ -827,15 +835,16 @@ FloatingPointLiteral | ('0'..'9')+ Exponent FloatTypeSuffix? | ('0'..'9')+ FloatTypeSuffix | '0' ('x'|'X') - ( HexDigit+ '.' HexDigit* Exponent? FloatTypeSuffix? - | '.' HexDigit+ Exponent? FloatTypeSuffix? - | HexDigit+ Exponent FloatTypeSuffix? - | HexDigit+ FloatTypeSuffix + ( HexDigit+ ('.' HexDigit*)? HexExponent FloatTypeSuffix? + | '.' HexDigit+ HexExponent FloatTypeSuffix? ) ; fragment -Exponent : ('e'|'E'|'p'|'P') ('+'|'-')? ('0'..'9')+ ; +Exponent : ('e'|'E') ('+'|'-')? ('0'..'9')+ ; + +fragment +HexExponent : ('p'|'P') ('+'|'-')? ('0'..'9')+ ; fragment FloatTypeSuffix : ('f'|'F'|'d'|'D') ; @@ -917,13 +926,13 @@ JavaIDDigit '\u1040'..'\u1049' ; -WS : (' '|'\r'|'\t'|'\u000C'|'\n')+ {$channel=HIDDEN;} +WS : (' '|'\r'|'\t'|'\u000C'|'\n')+ -> channel(HIDDEN) ; COMMENT - : '/*' ( options {greedy=false;} : . )* '*/' {$channel=HIDDEN;} + : '/*' .* '*/' -> channel(HIDDEN) ; LINE_COMMENT - : '//' ~('\n'|'\r')* '\r'? '\n' {$channel=HIDDEN;} + : '//' ~('\n'|'\r')* '\r'? '\n' -> channel(HIDDEN) ; diff --git a/tool/test/org/antlr/v4/test/Java.g b/tool/test/org/antlr/v4/test/Java.g index e5b174fda..f5ac3657d 100644 --- a/tool/test/org/antlr/v4/test/Java.g +++ b/tool/test/org/antlr/v4/test/Java.g @@ -1021,7 +1021,7 @@ WS : (' '|'\r'|'\t'|'\u000C'|'\n')+ {$channel=HIDDEN;} ; COMMENT - : '/*' ( options {greedy=false;} : . )* '*/' {$channel=HIDDEN;} + : '/*' .* '*/' {$channel=HIDDEN;} ; LINE_COMMENT diff --git a/tool/test/org/antlr/v4/test/TestPerformance.java b/tool/test/org/antlr/v4/test/TestPerformance.java index 98622ed83..2740bc1b8 100644 --- a/tool/test/org/antlr/v4/test/TestPerformance.java +++ b/tool/test/org/antlr/v4/test/TestPerformance.java @@ -1,3 +1,31 @@ +/* + * [The "BSD license"] + * Copyright (c) 2012 Sam Harwell + * 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.v4.test; import org.antlr.v4.runtime.*; @@ -14,11 +42,19 @@ import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.antlr.v4.runtime.atn.ATNConfig; +import org.antlr.v4.runtime.atn.ParserATNSimulator; +import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.dfa.DFAState; public class TestPerformance extends BaseTest { /** Parse all java files under this package within the JDK_SOURCE_ROOT. */ - private static final String TOP_PACKAGE = "java"; + private static final String TOP_PACKAGE = "java.lang"; /** True to load java files from sub-packages of {@link #TOP_PACKAGE}. */ private static final boolean RECURSIVE = true; @@ -27,26 +63,41 @@ public class TestPerformance extends BaseTest { * the standard grammar (Java.g). In either case, the grammar is renamed in the temporary directory to Java.g * before compiling. */ - private static final boolean USE_LR_GRAMMAR = false; + private static final boolean USE_LR_GRAMMAR = true; /** - * True to specify the -Xforceatn option when generating the grammar, forcing all decisions in JavaParser to - * be handled by {@link v2ParserATNSimulator#adaptivePredict}. + * True to specify the -Xforce-atn option when generating the grammar, forcing all decisions in JavaParser to + * be handled by {@link ParserATNSimulator#adaptivePredict}. */ private static final boolean FORCE_ATN = false; + /** + * True to specify the -atn option when generating the grammar. This will cause ANTLR + * to export the ATN for each decision as a DOT (GraphViz) file. + */ + private static final boolean EXPORT_ATN_GRAPHS = true; + /** + * True to delete temporary (generated and compiled) files when the test completes. + */ + private static final boolean DELETE_TEMP_FILES = true; + + private static final boolean PAUSE_FOR_HEAP_DUMP = false; /** Parse each file with JavaParser.compilationUnit */ private static final boolean RUN_PARSER = true; /** True to use {@link BailErrorStrategy}, False to use {@link DefaultErrorStrategy} */ - private static final boolean BAIL_ON_ERROR = false; - /** This value is passed to {@link org.antlr.v4.runtime.Parser#setBuildParseTree}. */ + private static final boolean BAIL_ON_ERROR = true; + /** This value is passed to {@link Parser#setBuildParseTree}. */ private static final boolean BUILD_PARSE_TREES = false; /** * Use ParseTreeWalker.DEFAULT.walk with the BlankJavaParserListener to show parse tree walking overhead. * If {@link #BUILD_PARSE_TREES} is false, the listener will instead be called during the parsing process via - * {@link org.antlr.v4.runtime.Parser#addParseListener}. + * {@link Parser#addParseListener}. */ private static final boolean BLANK_LISTENER = false; + private static final boolean SHOW_DFA_STATE_STATS = true; + + private static final boolean SHOW_CONFIG_STATS = false; + /** * If true, a single JavaLexer will be used, and {@link Lexer#setInputStream} will be called to initialize it * for each source file. In this mode, the cached DFA will be persisted throughout the lexing process. @@ -67,25 +118,30 @@ public class TestPerformance extends BaseTest { /** Total number of passes to make over the source */ private static final int PASSES = 4; - private Lexer sharedLexer; - private Parser sharedParser; + private static Lexer sharedLexer; + private static Parser sharedParser; @SuppressWarnings({"FieldCanBeLocal"}) - private ParseTreeListener sharedListener; + private static ParseTreeListener sharedListener; private int tokenCount; + private int currentPass; @Test -// @Ignore + //@Ignore public void compileJdk() throws IOException { - compileParser(USE_LR_GRAMMAR); - JavaParserFactory factory = getParserFactory(); String jdkSourceRoot = System.getenv("JDK_SOURCE_ROOT"); + if (jdkSourceRoot == null) { + jdkSourceRoot = System.getProperty("JDK_SOURCE_ROOT"); + } if (jdkSourceRoot == null) { System.err.println("The JDK_SOURCE_ROOT environment variable must be set for performance testing."); return; } - if (!TOP_PACKAGE.isEmpty()) { + compileParser(USE_LR_GRAMMAR); + JavaParserFactory factory = getParserFactory(); + + if (!TOP_PACKAGE.isEmpty()) { jdkSourceRoot = jdkSourceRoot + '/' + TOP_PACKAGE.replace('.', '/'); } @@ -93,10 +149,13 @@ public class TestPerformance extends BaseTest { assertTrue(directory.isDirectory()); Collection sources = loadSources(directory, RECURSIVE); - System.out.format("Lex=true, Parse=%s, ForceAtn=%s, Bail=%s, BuildParseTree=%s, BlankListener=%s\n", - RUN_PARSER, FORCE_ATN, BAIL_ON_ERROR, BUILD_PARSE_TREES, BLANK_LISTENER); + + System.out.print(getOptionsDescription()); + + currentPass = 0; parse1(factory, sources); for (int i = 0; i < PASSES - 1; i++) { + currentPass = i + 1; if (CLEAR_DFA) { sharedLexer = null; sharedParser = null; @@ -104,6 +163,55 @@ public class TestPerformance extends BaseTest { parse2(factory, sources); } + + sources.clear(); + if (PAUSE_FOR_HEAP_DUMP) { + System.gc(); + System.out.println("Pausing before application exit."); + try { + Thread.sleep(4000); + } catch (InterruptedException ex) { + Logger.getLogger(TestPerformance.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + @Override + protected void eraseTempDir() { + if (DELETE_TEMP_FILES) { + super.eraseTempDir(); + } + } + + public static String getOptionsDescription() { + StringBuilder builder = new StringBuilder(); + builder.append("Input="); + if (TestPerformance.TOP_PACKAGE.isEmpty()) { + builder.append("*"); + } + else { + builder.append(TOP_PACKAGE).append(".*"); + } + + builder.append(", Grammar=").append(USE_LR_GRAMMAR ? "LR" : "Standard"); + builder.append(", ForceAtn=").append(FORCE_ATN); + + builder.append('\n'); + + builder.append("Op=Lex").append(RUN_PARSER ? "+Parse" : " only"); + builder.append(", Strategy=").append(BAIL_ON_ERROR ? BailErrorStrategy.class.getSimpleName() : DefaultErrorStrategy.class.getSimpleName()); + builder.append(", BuildParseTree=").append(BUILD_PARSE_TREES); + builder.append(", WalkBlankListener=").append(BLANK_LISTENER); + + builder.append('\n'); + + builder.append("Lexer=").append(REUSE_LEXER ? "setInputStream" : "newInstance"); + builder.append(", Parser=").append(REUSE_PARSER ? "setInputStream" : "newInstance"); + builder.append(", AfterPass=").append(CLEAR_DFA ? "newInstance" : "setInputStream"); + + builder.append('\n'); + + return builder.toString(); } /** @@ -158,6 +266,8 @@ public class TestPerformance extends BaseTest { } } + int configOutputSize = 0; + protected void parseSources(JavaParserFactory factory, Collection sources) { long startTime = System.currentTimeMillis(); tokenCount = 0; @@ -167,7 +277,7 @@ public class TestPerformance extends BaseTest { input.seek(0); inputSize += input.size(); // this incurred a great deal of overhead and was causing significant variations in performance results. - //System.out.format("Parsing file %s\n", file.getAbsolutePath()); + //System.out.format("Parsing file %s\n", input.getSourceName()); try { factory.parseFile(input); } catch (IllegalStateException ex) { @@ -180,6 +290,80 @@ public class TestPerformance extends BaseTest { inputSize / 1024, tokenCount, System.currentTimeMillis() - startTime); + + if (RUN_PARSER) { + // make sure the individual DFAState objects actually have unique ATNConfig arrays + final ParserATNSimulator interpreter = sharedParser.getInterpreter(); + final DFA[] decisionToDFA = interpreter.decisionToDFA; + + if (SHOW_DFA_STATE_STATS) { + int states = 0; + + for (int i = 0; i < decisionToDFA.length; i++) { + DFA dfa = decisionToDFA[i]; + if (dfa == null || dfa.states == null) { + continue; + } + + states += dfa.states.size(); + } + + System.out.format("There are %d DFAState instances.\n", states); + } + + int localDfaCount = 0; + int globalDfaCount = 0; + int localConfigCount = 0; + int globalConfigCount = 0; + int[] contextsInDFAState = new int[0]; + + for (int i = 0; i < decisionToDFA.length; i++) { + DFA dfa = decisionToDFA[i]; + if (dfa == null || dfa.states == null) { + continue; + } + + if (SHOW_CONFIG_STATS) { + for (DFAState state : dfa.states.keySet()) { + if (state.configset.size() >= contextsInDFAState.length) { + contextsInDFAState = Arrays.copyOf(contextsInDFAState, state.configset.size() + 1); + } + + if (state.isAcceptState) { + boolean hasGlobal = false; + for (ATNConfig config : state.configset) { + if (config.reachesIntoOuterContext > 0) { + globalConfigCount++; + hasGlobal = true; + } else { + localConfigCount++; + } + } + + if (hasGlobal) { + globalDfaCount++; + } else { + localDfaCount++; + } + } + + contextsInDFAState[state.configset.size()]++; + } + } + } + + if (SHOW_CONFIG_STATS && currentPass == 0) { + System.out.format(" DFA accept states: %d total, %d with only local context, %d with a global context\n", localDfaCount + globalDfaCount, localDfaCount, globalDfaCount); + System.out.format(" Config stats: %d total, %d local, %d global\n", localConfigCount + globalConfigCount, localConfigCount, globalConfigCount); + if (SHOW_DFA_STATE_STATS) { + for (int i = 0; i < contextsInDFAState.length; i++) { + if (contextsInDFAState[i] != 0) { + System.out.format(" %d configs = %d\n", i, contextsInDFAState[i]); + } + } + } + } + } } protected void compileParser(boolean leftRecursive) throws IOException { @@ -187,8 +371,15 @@ public class TestPerformance extends BaseTest { String sourceName = leftRecursive ? "Java-LR.g" : "Java.g"; String body = load(sourceName, null); @SuppressWarnings({"ConstantConditions"}) - String[] extraOptions = FORCE_ATN ? new String[] {"-Xforceatn"} : new String[0]; - boolean success = rawGenerateAndBuildRecognizer(grammarFileName, body, "JavaParser", "JavaLexer", extraOptions); + List extraOptions = new ArrayList(); + if (FORCE_ATN) { + extraOptions.add("-Xforce-atn"); + } + if (EXPORT_ATN_GRAPHS) { + extraOptions.add("-atn"); + } + String[] extraOptionsArray = extraOptions.toArray(new String[extraOptions.size()]); + boolean success = rawGenerateAndBuildRecognizer(grammarFileName, body, "JavaParser", "JavaLexer", extraOptionsArray); assertTrue(success); } @@ -228,7 +419,7 @@ public class TestPerformance extends BaseTest { final Class parserClass = (Class)loader.loadClass("JavaParser"); @SuppressWarnings({"unchecked"}) final Class> listenerClass = (Class>)loader.loadClass("BlankJavaListener"); - this.sharedListener = listenerClass.newInstance(); + TestPerformance.sharedListener = listenerClass.newInstance(); final Constructor lexerCtor = lexerClass.getConstructor(CharStream.class); final Constructor parserCtor = parserClass.getConstructor(TokenStream.class); @@ -271,7 +462,7 @@ public class TestPerformance extends BaseTest { Method parseMethod = parserClass.getMethod("compilationUnit"); Object parseResult = parseMethod.invoke(sharedParser); - assert parseResult instanceof ParseTree; + Assert.assertTrue(parseResult instanceof ParseTree); if (BUILD_PARSE_TREES && BLANK_LISTENER) { ParseTreeWalker.DEFAULT.walk(sharedListener, (ParseTree)parseResult);