http://git-wip-us.apache.org/repos/asf/groovy/blob/91c04014/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/GroovyLangLexer.java ---------------------------------------------------------------------- diff --git a/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/GroovyLangLexer.java b/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/GroovyLangLexer.java new file mode 100644 index 0000000..8d20ad6 --- /dev/null +++ b/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/GroovyLangLexer.java @@ -0,0 +1,45 @@ +package org.apache.groovy.parser.antlr4; + +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.LexerNoViableAltException; +import org.antlr.v4.runtime.atn.ATN; +import org.antlr.v4.runtime.atn.LexerATNSimulator; +import org.apache.groovy.parser.antlr4.internal.AtnManager; + +/** + * The lexer for Groovy programming language, which is based on the lexer generated by Antlr4 + * + * @author <a href="mailto:realblue...@hotmail.com">Daniel.Sun</a> + * Created on 2016/08/14 + */ +public class GroovyLangLexer extends GroovyLexer { + public GroovyLangLexer(CharStream input) { + super(input); + + this.setInterpreter(new PositionAdjustingLexerATNSimulator(this, new AtnManager(this).getATN())); + } + + @Override + public void recover(LexerNoViableAltException e) { + throw e; // if some lexical error occurred, stop parsing! + } + + @Override + protected void rollbackOneChar() { + ((PositionAdjustingLexerATNSimulator) getInterpreter()).resetAcceptPosition(getInputStream(), _tokenStartCharIndex - 1, _tokenStartLine, _tokenStartCharPositionInLine - 1); + } + + private static class PositionAdjustingLexerATNSimulator extends LexerATNSimulator { + public PositionAdjustingLexerATNSimulator(Lexer recog, ATN atn) { + super(recog, atn); + } + + protected void resetAcceptPosition(CharStream input, int index, int line, int charPositionInLine) { + input.seek(index); + this.line = line; + this.charPositionInLine = charPositionInLine; + this.consume(input); + } + } +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/91c04014/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/GroovyLangParser.java ---------------------------------------------------------------------- diff --git a/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/GroovyLangParser.java b/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/GroovyLangParser.java new file mode 100644 index 0000000..fa33c6e --- /dev/null +++ b/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/GroovyLangParser.java @@ -0,0 +1,38 @@ +/* + * 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 org.apache.groovy.parser.antlr4; + +import org.antlr.v4.runtime.TokenStream; +import org.antlr.v4.runtime.atn.ParserATNSimulator; +import org.apache.groovy.parser.antlr4.internal.AtnManager; + +/** + * The parser for Groovy programming language, which is based on the parser generated by Antlr4 + * + * @author <a href="mailto:realblue...@hotmail.com">Daniel.Sun</a> + * Created on 2016/08/14 + */ +public class GroovyLangParser extends GroovyParser { + public GroovyLangParser(TokenStream input) { + super(input); + + this.setInterpreter(new ParserATNSimulator(this, new AtnManager(this).getATN())); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/91c04014/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/SemanticPredicates.java ---------------------------------------------------------------------- diff --git a/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/SemanticPredicates.java b/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/SemanticPredicates.java new file mode 100644 index 0000000..cf620dd --- /dev/null +++ b/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/SemanticPredicates.java @@ -0,0 +1,94 @@ +/* + * 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 org.apache.groovy.parser.antlr4; + +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.TokenStream; + +import static org.apache.groovy.parser.antlr4.GroovyParser.*; + +/** + * Some semantic predicates for altering the behaviour of the lexer and parser + * + * @author <a href="mailto:realblue...@hotmail.com">Daniel.Sun</a> + * Created on 2016/08/20 + */ +public class SemanticPredicates { + public static boolean isFollowedByWhiteSpaces(CharStream cs) { + for (int index = 1, c = cs.LA(index); !('\r' == c || '\n' == c || CharStream.EOF == c); index++, c = cs.LA(index)) { + if (String.valueOf((char) c).matches("\\S+?")) { + return false; + } + } + + return true; + } + + public static boolean isFollowedByJavaLetterInGString(CharStream cs) { + int c1 = cs.LA(1); + + if ('$' == c1) { // single $ is not a valid identifier + return false; + } + + String str1 = String.valueOf((char) c1); + + if (str1.matches("[a-zA-Z_{]")) { + return true; + } + + if (str1.matches("[^\u0000-\u007F\uD800-\uDBFF]") + && Character.isJavaIdentifierPart(c1)) { + return true; + } + + int c2 = cs.LA(2); + String str2 = String.valueOf((char) c2); + + if (str1.matches("[\uD800-\uDBFF]") + && str2.matches("[\uDC00-\uDFFF]") + && Character.isJavaIdentifierPart(Character.toCodePoint((char) c1, (char) c2))) { + + return true; + } + + return false; + } + + /** + * Check whether following a method name of command expression. + * Method name should not end with "2: arguments" and "3: closure" + * + * @param t the type of pathExpression + * @return + */ + public static boolean isFollowingMethodName(int t) { + return !(2 == t || 3 == t); + } + + /** + * Distinguish between method declaration and method call/constructor declaration + */ + public static boolean isInvalidMethodDeclaration(TokenStream ts) { + int tokenType = ts.LT(1).getType(); + + return (Identifier == tokenType || CapitalizedIdentifier == tokenType || StringLiteral == tokenType) + && GroovyLangParser.LPAREN == (ts.LT(2).getType()); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/91c04014/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/TryWithResourcesASTTransformation.java ---------------------------------------------------------------------- diff --git a/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/TryWithResourcesASTTransformation.java b/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/TryWithResourcesASTTransformation.java new file mode 100644 index 0000000..b249d5f --- /dev/null +++ b/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/TryWithResourcesASTTransformation.java @@ -0,0 +1,339 @@ +package org.apache.groovy.parser.antlr4; + +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.expr.*; +import org.codehaus.groovy.ast.stmt.*; +import org.codehaus.groovy.syntax.Types; +import org.objectweb.asm.Opcodes; + +import java.util.Collections; +import java.util.List; + +import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asBoolean; + +/** + * Transform try-with-resources to try-catch-finally + * Reference JLS "14.20.3. try-with-resources"(https://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html) + * + * @author <a href="mailto:realblue...@hotmail.com">Daniel.Sun</a> + * Created on 2016/11/04 + */ +public class TryWithResourcesASTTransformation { + private AstBuilder astBuilder; + + public TryWithResourcesASTTransformation(AstBuilder astBuilder) { + this.astBuilder = astBuilder; + } + + /** + * Reference JLS "14.20.3. try-with-resources"(https://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html) + * + * @param tryCatchStatement the try-with-resources statement to transform + * @return try-catch-finally statement, which contains no resources clause + */ + public Statement transform(TryCatchStatement tryCatchStatement) { + if (!asBoolean(tryCatchStatement.getResourceStatements())) { + return tryCatchStatement; + } + + if (this.isBasicTryWithResourcesStatement(tryCatchStatement)) { + return this.transformBasicTryWithResourcesStatement(tryCatchStatement); + } else { + return this.transformExtendedTryWithResourcesStatement(tryCatchStatement); + } + } + + private boolean isBasicTryWithResourcesStatement(TryCatchStatement tryCatchStatement) { + if (EmptyStatement.INSTANCE.equals(tryCatchStatement.getFinallyStatement()) + && !asBoolean(tryCatchStatement.getCatchStatements())) { + return true; + } + + return false; + } + + private ExpressionStatement makeVariableDeclarationFinal(ExpressionStatement variableDeclaration) { + if (!asBoolean(variableDeclaration)) { + return variableDeclaration; + } + + if (!(variableDeclaration.getExpression() instanceof DeclarationExpression)) { + throw new IllegalArgumentException("variableDeclaration is not a declaration statement"); + } + + DeclarationExpression declarationExpression = (DeclarationExpression) variableDeclaration.getExpression(); + if (!(declarationExpression.getLeftExpression() instanceof VariableExpression)) { + throw astBuilder.createParsingFailedException("The expression statement is not a variable delcaration statement", variableDeclaration); + } + + VariableExpression variableExpression = (VariableExpression) declarationExpression.getLeftExpression(); + variableExpression.setModifiers(variableExpression.getModifiers() | Opcodes.ACC_FINAL); + + return variableDeclaration; + } + + private long primaryExcCnt = 0; + private String genPrimaryExcName() { + return "__$$primaryExc" + primaryExcCnt++; + } + + /* + * try ResourceSpecification + * Block + * Catchesopt + * Finallyopt + * + * **The above AST should be transformed to the following AST** + * + * try { + * try ResourceSpecification + * Block + * } + * Catchesopt + * Finallyopt + */ + private Statement transformExtendedTryWithResourcesStatement(TryCatchStatement tryCatchStatement) { + /* + * try ResourceSpecification + * Block + */ + TryCatchStatement newTryWithResourcesStatement = + new TryCatchStatement( + tryCatchStatement.getTryStatement(), + EmptyStatement.INSTANCE); + tryCatchStatement.getResourceStatements().forEach(newTryWithResourcesStatement::addResource); + + + /* + * try { + * << the following try-with-resources has been transformed >> + * try ResourceSpecification + * Block + * } + * Catchesopt + * Finallyopt + */ + TryCatchStatement newTryCatchStatement = + new TryCatchStatement( + astBuilder.createBlockStatement(this.transform(newTryWithResourcesStatement)), + tryCatchStatement.getFinallyStatement()); + + tryCatchStatement.getCatchStatements().forEach(newTryCatchStatement::addCatch); + + return newTryCatchStatement; + } + + /* + * try (VariableModifiersopt R Identifier = Expression ...) + * Block + * + * **The above AST should be transformed to the following AST** + * + * { + * final VariableModifiers_minus_final R Identifier = Expression; + * Throwable #primaryExc = null; + * + * try ResourceSpecification_tail + * Block + * catch (Throwable #t) { + * #primaryExc = #t; + * throw #t; + * } finally { + * if (Identifier != null) { + * if (#primaryExc != null) { + * try { + * Identifier.close(); + * } catch (Throwable #suppressedExc) { + * #primaryExc.addSuppressed(#suppressedExc); + * } + * } else { + * Identifier.close(); + * } + * } + * } + * } + * + */ + private Statement transformBasicTryWithResourcesStatement(TryCatchStatement tryCatchStatement) { + // { ... } + BlockStatement blockStatement = new BlockStatement(); + + // final VariableModifiers_minus_final R Identifier = Expression; + ExpressionStatement firstResourceStatement = + this.makeVariableDeclarationFinal( + tryCatchStatement.getResourceStatement(0)); + astBuilder.appendStatementsToBlockStatement(blockStatement, firstResourceStatement); + + // Throwable #primaryExc = null; + String primaryExcName = this.genPrimaryExcName(); + ExpressionStatement primaryExcDeclarationStatement = + new ExpressionStatement( + new DeclarationExpression( + new VariableExpression(primaryExcName, ClassHelper.make(Throwable.class)), + org.codehaus.groovy.syntax.Token.newSymbol(Types.ASSIGN, -1, -1), + new ConstantExpression(null) + ) + ); + astBuilder.appendStatementsToBlockStatement(blockStatement, primaryExcDeclarationStatement); + + + // The generated try-catch-finally statement + String firstResourceIdentifierName = + ((DeclarationExpression) tryCatchStatement.getResourceStatement(0).getExpression()).getLeftExpression().getText(); + + TryCatchStatement newTryCatchStatement = + new TryCatchStatement( + tryCatchStatement.getTryStatement(), + this.createFinallyBlockForNewTryCatchStatement(primaryExcName, firstResourceIdentifierName)); + + List<ExpressionStatement> resourceStatements = tryCatchStatement.getResourceStatements(); + // 2nd, 3rd, ..., n'th resources declared in resources + List<ExpressionStatement> tailResourceStatements = resourceStatements.subList(1, resourceStatements.size()); + tailResourceStatements.stream().forEach(newTryCatchStatement::addResource); + + newTryCatchStatement.addCatch(this.createCatchBlockForOuterNewTryCatchStatement(primaryExcName)); + astBuilder.appendStatementsToBlockStatement(blockStatement, this.transform(newTryCatchStatement)); + + return blockStatement; + } + + /* + * catch (Throwable #t) { + * #primaryExc = #t; + * throw #t; + * } + * + */ + private CatchStatement createCatchBlockForOuterNewTryCatchStatement(String primaryExcName) { + // { ... } + BlockStatement blockStatement = new BlockStatement(); + String tExcName = this.genTExcName(); + + // #primaryExc = #t; + ExpressionStatement primaryExcAssignStatement = + new ExpressionStatement( + new BinaryExpression( + new VariableExpression(primaryExcName), + org.codehaus.groovy.syntax.Token.newSymbol(Types.ASSIGN, -1, -1), + new VariableExpression(tExcName))); + astBuilder.appendStatementsToBlockStatement(blockStatement, primaryExcAssignStatement); + + // throw #t; + ThrowStatement throwTExcStatement = new ThrowStatement(new VariableExpression(tExcName)); + astBuilder.appendStatementsToBlockStatement(blockStatement, throwTExcStatement); + + // Throwable #t + Parameter tExcParameter = new Parameter(ClassHelper.make(Throwable.class), tExcName); + + return new CatchStatement(tExcParameter, blockStatement); + } + + private long tExcCnt = 0; + private String genTExcName() { + return "__$$t" + tExcCnt++; + } + + /* + * finally { + * if (Identifier != null) { + * if (#primaryExc != null) { + * try { + * Identifier.close(); + * } catch (Throwable #suppressedExc) { + * #primaryExc.addSuppressed(#suppressedExc); + * } + * } else { + * Identifier.close(); + * } + * } + * } + * + * We can simplify the above code to a Groovy version as follows: + * + * finally { + * if (#primaryExc != null) + * try { + * Identifier?.close(); + * } catch (Throwable #suppressedExc) { + * #primaryExc.addSuppressed(#suppressedExc); + * } + * else + * Identifier?.close(); + * + * } + * + */ + private BlockStatement createFinallyBlockForNewTryCatchStatement(String primaryExcName, String firstResourceIdentifierName) { + BlockStatement finallyBlock = new BlockStatement(); + + // primaryExc != null + BooleanExpression conditionExpression = + new BooleanExpression( + new BinaryExpression( + new VariableExpression(primaryExcName), + org.codehaus.groovy.syntax.Token.newSymbol(Types.COMPARE_NOT_EQUAL, -1, -1), + new ConstantExpression(null))); + + // try-catch statement + TryCatchStatement newTryCatchStatement = + new TryCatchStatement( + astBuilder.createBlockStatement(this.createCloseResourceStatement(firstResourceIdentifierName)), // { Identifier?.close(); } + EmptyStatement.INSTANCE); + + + String suppressedExcName = this.genSuppressedExcName(); + newTryCatchStatement.addCatch( + // catch (Throwable #suppressedExc) { .. } + new CatchStatement( + new Parameter(ClassHelper.make(Throwable.class), suppressedExcName), + astBuilder.createBlockStatement(this.createAddSuppressedStatement(primaryExcName, suppressedExcName)) // #primaryExc.addSuppressed(#suppressedExc); + ) + ); + + // if (#primaryExc != null) { ... } + IfStatement ifStatement = + new IfStatement( + conditionExpression, + newTryCatchStatement, + this.createCloseResourceStatement(firstResourceIdentifierName) // Identifier?.close(); + ); + astBuilder.appendStatementsToBlockStatement(finallyBlock, ifStatement); + + return astBuilder.createBlockStatement(finallyBlock); + } + + private long suppressedExcCnt = 0; + private String genSuppressedExcName() { + return "__$$suppressedExc" + suppressedExcCnt++; + } + + /* + * Identifier?.close(); + */ + private ExpressionStatement createCloseResourceStatement(String firstResourceIdentifierName) { + MethodCallExpression closeMethodCallExpression = + new MethodCallExpression(new VariableExpression(firstResourceIdentifierName), "close", new ArgumentListExpression()); + + closeMethodCallExpression.setImplicitThis(false); + closeMethodCallExpression.setSafe(true); + + return new ExpressionStatement(closeMethodCallExpression); + } + + /* + * #primaryExc.addSuppressed(#suppressedExc); + */ + private ExpressionStatement createAddSuppressedStatement(String primaryExcName, String suppressedExcName) { + MethodCallExpression addSuppressedMethodCallExpression = + new MethodCallExpression( + new VariableExpression(primaryExcName), + "addSuppressed", + new ArgumentListExpression(Collections.singletonList(new VariableExpression(suppressedExcName)))); + addSuppressedMethodCallExpression.setImplicitThis(false); + addSuppressedMethodCallExpression.setSafe(true); + + return new ExpressionStatement(addSuppressedMethodCallExpression); + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/91c04014/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/internal/AtnManager.java ---------------------------------------------------------------------- diff --git a/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/internal/AtnManager.java b/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/internal/AtnManager.java new file mode 100644 index 0000000..2e6e3a8 --- /dev/null +++ b/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/internal/AtnManager.java @@ -0,0 +1,107 @@ +/* + * 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 org.apache.groovy.parser.antlr4.internal; + +import org.antlr.v4.runtime.atn.ATN; +import org.apache.groovy.parser.antlr4.GroovyLangLexer; +import org.apache.groovy.parser.antlr4.GroovyLangParser; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Manage ATN for lexer and parser to avoid memory leak + * + * @author <a href="mailto:realblue...@hotmail.com">Daniel.Sun</a> + * Created on 2016/08/14 + */ +public class AtnManager { + public static final ReentrantReadWriteLock RRWL = new ReentrantReadWriteLock(true); + private static final String CACHE_THRESHOLD_NAME = "groovy.antlr4.cache.threshold"; + private static final int CACHE_THRESHOLD; + private final Class ownerClass; + private final ATN atn; + private static final Map<Class, AtnWrapper> ATN_MAP = + Collections.unmodifiableMap(new HashMap<Class, AtnWrapper>() { + { + put(GroovyLangLexer.class, new AtnWrapper(GroovyLangLexer._ATN)); + put(GroovyLangParser.class, new AtnWrapper(GroovyLangParser._ATN)); + } + }); + + static { + int t = 50; + + try { + t = Integer.parseInt(System.getProperty(CACHE_THRESHOLD_NAME)); + + // cache threshold should be at least 50 for better performance + t = t < 50 ? 50 : t; + } catch (Exception e) { + // ignored + } + + CACHE_THRESHOLD = t; + } + + public AtnManager(GroovyLangLexer lexer) { + this.ownerClass = lexer.getClass(); + this.atn = getAtnWrapper(this.ownerClass).checkAndClear(); + } + + public AtnManager(GroovyLangParser parser) { + this.ownerClass = parser.getClass(); + this.atn = getAtnWrapper(this.ownerClass).checkAndClear(); + } + + public ATN getATN() { + return this.atn; + } + + private AtnWrapper getAtnWrapper(Class ownerClass) { + return ATN_MAP.get(ownerClass); + } + + private static class AtnWrapper { + private final ATN atn; + private final AtomicLong counter = new AtomicLong(0); + + public AtnWrapper(ATN atn) { + this.atn = atn; + } + + public ATN checkAndClear() { + if (0 != counter.incrementAndGet() % CACHE_THRESHOLD) { + return atn; + } + + RRWL.writeLock().lock(); + try { + atn.clearDFA(); + } finally { + RRWL.writeLock().unlock(); + } + + return atn; + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/91c04014/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/internal/DescriptiveErrorStrategy.java ---------------------------------------------------------------------- diff --git a/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/internal/DescriptiveErrorStrategy.java b/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/internal/DescriptiveErrorStrategy.java new file mode 100644 index 0000000..c2c5afe --- /dev/null +++ b/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/internal/DescriptiveErrorStrategy.java @@ -0,0 +1,102 @@ +/* + * 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 org.apache.groovy.parser.antlr4.internal; + +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.atn.PredictionMode; +import org.antlr.v4.runtime.misc.NotNull; +import org.antlr.v4.runtime.misc.ParseCancellationException; + +/** + * Provide friendly error messages when parsing errors occurred. + * + * @author <a href="mailto:realblue...@hotmail.com">Daniel.Sun</a> + * Created on 2016/10/19 + */ +public class DescriptiveErrorStrategy extends BailErrorStrategy { + @Override + public void recover(Parser recognizer, RecognitionException e) { + for (ParserRuleContext context = recognizer.getContext(); context != null; context = context.getParent()) { + context.exception = e; + } + + if (PredictionMode.LL.equals(recognizer.getInterpreter().getPredictionMode())) { + if (e instanceof NoViableAltException) { + this.reportNoViableAlternative(recognizer, (NoViableAltException) e); + } else if (e instanceof InputMismatchException) { + this.reportInputMismatch(recognizer, (InputMismatchException) e); + } else if (e instanceof FailedPredicateException) { + this.reportFailedPredicate(recognizer, (FailedPredicateException) e); + } + } + + throw new ParseCancellationException(e); + } + + @Override + public Token recoverInline(Parser recognizer) + throws RecognitionException { + + this.recover(recognizer, new InputMismatchException(recognizer)); // stop parsing + return null; + } + + protected String createNoViableAlternativeErrorMessage(Parser recognizer, NoViableAltException e) { + TokenStream tokens = recognizer.getInputStream(); + String input; + if (tokens != null) { + if (e.getStartToken().getType() == Token.EOF) input = "<EOF>"; + else input = tokens.getText(e.getStartToken(), e.getOffendingToken()); + } else { + input = "<unknown input>"; + } + + return "Unexpected input: " + escapeWSAndQuote(input); + } + + @Override + protected void reportNoViableAlternative(@NotNull Parser recognizer, + @NotNull NoViableAltException e) { + + notifyErrorListeners(recognizer, this.createNoViableAlternativeErrorMessage(recognizer, e), e); + } + + protected String createInputMismatchErrorMessage(@NotNull Parser recognizer, + @NotNull InputMismatchException e) { + return "Unexpected input: " + getTokenErrorDisplay(e.getOffendingToken(recognizer)) + + "; Expecting " + e.getExpectedTokens().toString(recognizer.getVocabulary()); + } + + protected void reportInputMismatch(@NotNull Parser recognizer, + @NotNull InputMismatchException e) { + + notifyErrorListeners(recognizer, this.createInputMismatchErrorMessage(recognizer, e), e); + } + + + protected String createFailedPredicateErrorMessage(@NotNull Parser recognizer, + @NotNull FailedPredicateException e) { + return e.getMessage(); + } + + protected void reportFailedPredicate(@NotNull Parser recognizer, + @NotNull FailedPredicateException e) { + notifyErrorListeners(recognizer, this.createFailedPredicateErrorMessage(recognizer, e), e); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/91c04014/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/util/StringUtils.java ---------------------------------------------------------------------- diff --git a/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/util/StringUtils.java b/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/util/StringUtils.java new file mode 100644 index 0000000..1c16679 --- /dev/null +++ b/subprojects/groovy-antlr4-grammar/src/main/java/org/apache/groovy/parser/antlr4/util/StringUtils.java @@ -0,0 +1,145 @@ +/* + * 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 org.apache.groovy.parser.antlr4.util; + +import groovy.lang.Closure; +import org.codehaus.groovy.runtime.StringGroovyMethods; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Utilities for handling strings + * + * @author <a href="mailto:realblue...@hotmail.com">Daniel.Sun</a> + * Created on 2016/08/20 + */ +public class StringUtils { + public static String replaceHexEscapes(String text) { + Pattern p = Pattern.compile("(\\\\*)\\\\u+([0-9abcdefABCDEF]{4})"); + return StringGroovyMethods.replaceAll((CharSequence) text, p, new Closure<Void>(null, null) { + Object doCall(String _0, String _1, String _2) { + if (null != _1 && _1.length() % 2 == 1) { + return _0; + } + + return _1 + new String(Character.toChars(Integer.parseInt(_2, 16))); + } + }); + } + + public static String replaceOctalEscapes(String text) { + Pattern p = Pattern.compile("(\\\\*)\\\\([0-3]?[0-7]?[0-7])"); + return StringGroovyMethods.replaceAll((CharSequence) text, p, new Closure<Void>(null, null) { + Object doCall(String _0, String _1, String _2) { + if (null != _1 && _1.length() % 2 == 1) { + return _0; + } + + return _1 + new String(Character.toChars(Integer.parseInt(_2, 8))); + } + }); + } + + private static Map<Character, Character> STANDARD_ESCAPES = new HashMap<Character, Character>() { + { + this.put('b', '\b'); + this.put('t', '\t'); + this.put('n', '\n'); + this.put('f', '\f'); + this.put('r', '\r'); + } + }; + + public static String replaceStandardEscapes(String text) { + Pattern p = Pattern.compile("(\\\\*)\\\\([btnfr\"'])"); + + String result = StringGroovyMethods.replaceAll((CharSequence) text, p, new Closure<Void>(null, null) { + Object doCall(String _0, String _1, String _2) { + if (null != _1 && _1.length() % 2 == 1) { + return _0; + } + + Character character = STANDARD_ESCAPES.get(_2.charAt(0)); + return _1 + (character != null ? character : _2); + } + }); + + return result.replace("\\\\", "\\"); + } + + public static final int NONE_SLASHY = 0; + public static final int SLASHY = 1; + public static final int DOLLAR_SLASHY = 2; + + public static String replaceEscapes(String text, int slashyType) { + if (slashyType == SLASHY || slashyType == DOLLAR_SLASHY) { + text = StringUtils.replaceHexEscapes(text); + text = StringUtils.replaceLineEscape(text); + + if (slashyType == SLASHY) { + text = text.replace("\\/", "/"); + } + + if (slashyType == DOLLAR_SLASHY) { + text = text.replace("$$", "$"); + text = text.replace("$/", "/"); + } + + } else if (slashyType == NONE_SLASHY) { + text = StringUtils.replaceEscapes(text); + } else { + throw new IllegalArgumentException("Invalid slashyType: " + slashyType); + } + + return text; + } + + private static String replaceEscapes(String text) { + text = text.replace("\\$", "$"); + + text = StringUtils.replaceLineEscape(text); + + return StringUtils.replaceStandardEscapes(replaceHexEscapes(replaceOctalEscapes(text))); + } + + private static String replaceLineEscape(String text) { + Pattern p = Pattern.compile("(\\\\*)\\\\\r?\n"); + text = StringGroovyMethods.replaceAll((CharSequence) text, p, new Closure<Void>(null, null) { + Object doCall(String _0, String _1) { + if (null != _1 && _1.length() % 2 == 1) { + return _0; + } + + return _1; + } + }); + + return text; + } + + public static String removeCR(String text) { + return text.replace("\r\n", "\n"); + } + + public static long countChar(String text, char c) { + return text.chars().filter(e -> c == e).count(); + } +} \ No newline at end of file