org.codehaus.janino.Parser Maven / Gradle / Ivy
/*
* Janino - An embedded Java[TM] compiler
*
* Copyright (c) 2001-2010 Arno Unkrig. 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. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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.codehaus.janino;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.codehaus.commons.compiler.CompileException;
import org.codehaus.commons.compiler.InternalCompilerException;
import org.codehaus.commons.compiler.Location;
import org.codehaus.commons.compiler.WarningHandler;
import org.codehaus.commons.nullanalysis.Nullable;
import org.codehaus.janino.Java.AbstractClassDeclaration;
import org.codehaus.janino.Java.AbstractCompilationUnit;
import org.codehaus.janino.Java.AbstractCompilationUnit.ImportDeclaration;
import org.codehaus.janino.Java.AccessModifier;
import org.codehaus.janino.Java.AlternateConstructorInvocation;
import org.codehaus.janino.Java.AmbiguousName;
import org.codehaus.janino.Java.Annotation;
import org.codehaus.janino.Java.AnnotationTypeDeclaration;
import org.codehaus.janino.Java.AnonymousClassDeclaration;
import org.codehaus.janino.Java.ArrayAccessExpression;
import org.codehaus.janino.Java.ArrayCreationReference;
import org.codehaus.janino.Java.ArrayInitializer;
import org.codehaus.janino.Java.ArrayInitializerOrRvalue;
import org.codehaus.janino.Java.ArrayType;
import org.codehaus.janino.Java.AssertStatement;
import org.codehaus.janino.Java.Assignment;
import org.codehaus.janino.Java.Atom;
import org.codehaus.janino.Java.BinaryOperation;
import org.codehaus.janino.Java.Block;
import org.codehaus.janino.Java.BlockLambdaBody;
import org.codehaus.janino.Java.BlockStatement;
import org.codehaus.janino.Java.BooleanLiteral;
import org.codehaus.janino.Java.BreakStatement;
import org.codehaus.janino.Java.Cast;
import org.codehaus.janino.Java.CatchClause;
import org.codehaus.janino.Java.CatchParameter;
import org.codehaus.janino.Java.CharacterLiteral;
import org.codehaus.janino.Java.ClassInstanceCreationReference;
import org.codehaus.janino.Java.ClassLiteral;
import org.codehaus.janino.Java.CompilationUnit;
import org.codehaus.janino.Java.ConditionalExpression;
import org.codehaus.janino.Java.ConstructorDeclarator;
import org.codehaus.janino.Java.ConstructorInvocation;
import org.codehaus.janino.Java.ContinueStatement;
import org.codehaus.janino.Java.Crement;
import org.codehaus.janino.Java.DoStatement;
import org.codehaus.janino.Java.ElementValue;
import org.codehaus.janino.Java.ElementValueArrayInitializer;
import org.codehaus.janino.Java.ElementValuePair;
import org.codehaus.janino.Java.EmptyStatement;
import org.codehaus.janino.Java.EnumConstant;
import org.codehaus.janino.Java.EnumDeclaration;
import org.codehaus.janino.Java.ExportsModuleDirective;
import org.codehaus.janino.Java.ExpressionLambdaBody;
import org.codehaus.janino.Java.ExpressionStatement;
import org.codehaus.janino.Java.FieldAccess;
import org.codehaus.janino.Java.FieldAccessExpression;
import org.codehaus.janino.Java.FieldDeclaration;
import org.codehaus.janino.Java.FloatingPointLiteral;
import org.codehaus.janino.Java.ForEachStatement;
import org.codehaus.janino.Java.ForStatement;
import org.codehaus.janino.Java.FormalLambdaParameters;
import org.codehaus.janino.Java.FunctionDeclarator.FormalParameter;
import org.codehaus.janino.Java.FunctionDeclarator.FormalParameters;
import org.codehaus.janino.Java.IdentifierLambdaParameters;
import org.codehaus.janino.Java.IfStatement;
import org.codehaus.janino.Java.InferredLambdaParameters;
import org.codehaus.janino.Java.Initializer;
import org.codehaus.janino.Java.Instanceof;
import org.codehaus.janino.Java.IntegerLiteral;
import org.codehaus.janino.Java.InterfaceDeclaration;
import org.codehaus.janino.Java.LabeledStatement;
import org.codehaus.janino.Java.LambdaBody;
import org.codehaus.janino.Java.LambdaExpression;
import org.codehaus.janino.Java.LambdaParameters;
import org.codehaus.janino.Java.LocalClassDeclaration;
import org.codehaus.janino.Java.LocalClassDeclarationStatement;
import org.codehaus.janino.Java.LocalVariableDeclarationStatement;
import org.codehaus.janino.Java.Lvalue;
import org.codehaus.janino.Java.MarkerAnnotation;
import org.codehaus.janino.Java.MemberAnnotationTypeDeclaration;
import org.codehaus.janino.Java.MemberClassDeclaration;
import org.codehaus.janino.Java.MemberEnumDeclaration;
import org.codehaus.janino.Java.MemberInterfaceDeclaration;
import org.codehaus.janino.Java.MemberTypeDeclaration;
import org.codehaus.janino.Java.MethodDeclarator;
import org.codehaus.janino.Java.MethodInvocation;
import org.codehaus.janino.Java.MethodReference;
import org.codehaus.janino.Java.Modifier;
import org.codehaus.janino.Java.ModularCompilationUnit;
import org.codehaus.janino.Java.ModuleDeclaration;
import org.codehaus.janino.Java.ModuleDirective;
import org.codehaus.janino.Java.NamedClassDeclaration;
import org.codehaus.janino.Java.NewAnonymousClassInstance;
import org.codehaus.janino.Java.NewArray;
import org.codehaus.janino.Java.NewClassInstance;
import org.codehaus.janino.Java.NewInitializedArray;
import org.codehaus.janino.Java.NormalAnnotation;
import org.codehaus.janino.Java.NullLiteral;
import org.codehaus.janino.Java.OpensModuleDirective;
import org.codehaus.janino.Java.PackageDeclaration;
import org.codehaus.janino.Java.PackageMemberAnnotationTypeDeclaration;
import org.codehaus.janino.Java.PackageMemberClassDeclaration;
import org.codehaus.janino.Java.PackageMemberEnumDeclaration;
import org.codehaus.janino.Java.PackageMemberInterfaceDeclaration;
import org.codehaus.janino.Java.PackageMemberTypeDeclaration;
import org.codehaus.janino.Java.ParenthesizedExpression;
import org.codehaus.janino.Java.Primitive;
import org.codehaus.janino.Java.PrimitiveType;
import org.codehaus.janino.Java.ProvidesModuleDirective;
import org.codehaus.janino.Java.QualifiedThisReference;
import org.codehaus.janino.Java.ReferenceType;
import org.codehaus.janino.Java.RequiresModuleDirective;
import org.codehaus.janino.Java.ReturnStatement;
import org.codehaus.janino.Java.Rvalue;
import org.codehaus.janino.Java.RvalueMemberType;
import org.codehaus.janino.Java.SingleElementAnnotation;
import org.codehaus.janino.Java.Statement;
import org.codehaus.janino.Java.StringLiteral;
import org.codehaus.janino.Java.SuperConstructorInvocation;
import org.codehaus.janino.Java.SuperclassFieldAccessExpression;
import org.codehaus.janino.Java.SuperclassMethodInvocation;
import org.codehaus.janino.Java.SwitchStatement;
import org.codehaus.janino.Java.SynchronizedStatement;
import org.codehaus.janino.Java.ThisReference;
import org.codehaus.janino.Java.ThrowStatement;
import org.codehaus.janino.Java.TryStatement;
import org.codehaus.janino.Java.Type;
import org.codehaus.janino.Java.TypeArgument;
import org.codehaus.janino.Java.TypeParameter;
import org.codehaus.janino.Java.UnaryOperation;
import org.codehaus.janino.Java.UsesModuleDirective;
import org.codehaus.janino.Java.VariableDeclarator;
import org.codehaus.janino.Java.WhileStatement;
import org.codehaus.janino.Java.Wildcard;
/**
* A parser for the Java programming language.
*
* 'JLS7' refers to the Java Language Specification, Java SE 7
* Edition.
*
*/
public
class Parser {
private final Scanner scanner;
private final TokenStream tokenStream;
public
Parser(Scanner scanner) { this(scanner, new TokenStreamImpl(scanner)); }
public
Parser(Scanner scanner, TokenStream tokenStream) {
(this.scanner = scanner).setIgnoreWhiteSpace(true);
this.tokenStream = tokenStream;
}
/**
* The optional JAVADOC comment preceding the {@link #nextToken}.
*/
@Nullable private String docComment;
/**
* Gets the text of the doc comment (a.k.a. "JAVADOC comment") preceding the next token.
*
* @return {@code null} if the next token is not preceded by a doc comment
*/
@Nullable public String
doc() {
String s = this.docComment;
this.docComment = null;
return s;
}
/**
* @return The scanner that produces the tokens for this parser.
*/
public Scanner
getScanner() { return this.scanner; }
/**
*
* CompilationUnit := [ PackageDeclaration ]
* { ImportDeclaration }
* { TypeDeclaration }
*
*/
public AbstractCompilationUnit
parseAbstractCompilationUnit() throws CompileException, IOException {
String docComment = this.doc();
Modifier[] modifiers = this.parseModifiers();
PackageDeclaration packageDeclaration = null;
if (this.peek("package")) {
packageDeclaration = this.parsePackageDeclarationRest(docComment, modifiers);
docComment = this.doc();
modifiers = this.parseModifiers();
}
ImportDeclaration[] importDeclarations;
{
List l = new ArrayList();
while (this.peek("import")) {
if (modifiers.length > 0) {
this.warning("import.modifiers", "No modifiers allowed on import declarations");
}
if (docComment != null) {
this.warning("import.doc_comment", "Doc comment on import declaration");
}
l.add(this.parseImportDeclaration());
docComment = this.doc();
modifiers = this.parseModifiers();
}
importDeclarations = (ImportDeclaration[]) l.toArray(new ImportDeclaration[l.size()]);
}
if (this.peek("open", "module") != -1) {
return new ModularCompilationUnit(
this.location().getFileName(),
importDeclarations,
this.parseModuleDeclarationRest(modifiers)
);
}
CompilationUnit compilationUnit = new CompilationUnit(this.location().getFileName(), importDeclarations);
compilationUnit.setPackageDeclaration(packageDeclaration);
if (this.peek(TokenType.END_OF_INPUT)) return compilationUnit;
// Parse the first package-member type declaration.
compilationUnit.addPackageMemberTypeDeclaration(
this.parsePackageMemberTypeDeclarationRest(docComment, modifiers)
);
// Parse the second, third, ... package-member type declaration.
while (!this.peek(TokenType.END_OF_INPUT)) {
if (this.peekRead(";")) continue;
compilationUnit.addPackageMemberTypeDeclaration(this.parsePackageMemberTypeDeclaration());
}
return compilationUnit;
}
/**
*
* ModuleDeclarationRest := [ 'open' ] 'module' identifier { '.' identifier} '{' { ModuleDirective } '}'
*
*/
public ModuleDeclaration
parseModuleDeclarationRest(Modifier[] modifiers) throws CompileException, IOException {
final boolean isOpen = this.peekRead("open");
this.read("module");
final String[] moduleName = this.parseQualifiedIdentifier();
this.read("(");
List moduleDirectives = new ArrayList();
while (!this.peekRead(")")) {
ModuleDirective md;
switch (this.read("requires", "exports", "opens", "uses", "provides")) {
case 0: // requires
md = new RequiresModuleDirective(
this.location(),
this.parseModifiers(),
this.parseQualifiedIdentifier()
);
break;
case 1: // exports
{
String[] packageName = this.parseQualifiedIdentifier();
String[][] toModuleNames;
if (this.peekRead("to")) {
List l = new ArrayList();
l.add(this.parseQualifiedIdentifier());
while (this.peekRead(",")) l.add(this.parseQualifiedIdentifier());
toModuleNames = (String[][]) l.toArray(new String[l.size()][]);
} else {
toModuleNames = null;
}
md = new ExportsModuleDirective(this.location(), packageName, toModuleNames);
}
break;
case 2: // opens
{
String[] packageName = this.parseQualifiedIdentifier();
String[][] toModuleNames;
if (this.peekRead("to")) {
List l = new ArrayList();
l.add(this.parseQualifiedIdentifier());
while (this.peekRead(",")) l.add(this.parseQualifiedIdentifier());
toModuleNames = (String[][]) l.toArray(new String[l.size()][]);
} else {
toModuleNames = null;
}
md = new OpensModuleDirective(this.location(), packageName, toModuleNames);
}
break;
case 3: // uses
md = new UsesModuleDirective(this.location(), this.parseQualifiedIdentifier());
break;
case 4: // provides
final String[] typeName = this.parseQualifiedIdentifier();
this.read("with");
List withTypeNames = new ArrayList();
withTypeNames.add(this.parseQualifiedIdentifier());
while (this.peekRead(",")) withTypeNames.add(this.parseQualifiedIdentifier());
md = new ProvidesModuleDirective(
this.location(),
typeName,
(String[][]) withTypeNames.toArray(new String[withTypeNames.size()][])
);
break;
default:
throw new AssertionError();
}
this.read(";");
moduleDirectives.add(md);
}
return new ModuleDeclaration(
this.location(),
modifiers,
isOpen,
moduleName,
(ModuleDirective[]) moduleDirectives.toArray(new ModuleDirective[moduleDirectives.size()])
);
}
/**
*
* PackageDeclaration := 'package' QualifiedIdentifier ';'
*
*/
public PackageDeclaration
parsePackageDeclaration() throws CompileException, IOException {
return this.parsePackageDeclarationRest(this.doc(), this.parseModifiers());
}
/**
*
* PackageDeclaration := { PackageModifier } 'package' identifier { '.' identifier} ';'
*
*/
public PackageDeclaration
parsePackageDeclarationRest(@Nullable String docComment, Modifier[] modifiers)
throws CompileException, IOException {
this.packageModifiers(modifiers);
this.read("package");
Location loc = this.location();
String packageName = Parser.join(this.parseQualifiedIdentifier(), ".");
this.read(";");
this.verifyStringIsConventionalPackageName(packageName, loc);
return new PackageDeclaration(loc, packageName);
}
/**
*
* ImportDeclaration := 'import' ImportDeclarationBody ';'
*
*/
public AbstractCompilationUnit.ImportDeclaration
parseImportDeclaration() throws CompileException, IOException {
this.read("import");
AbstractCompilationUnit.ImportDeclaration importDeclaration = this.parseImportDeclarationBody();
this.read(";");
return importDeclaration;
}
/**
*
* ImportDeclarationBody := [ 'static' ] Identifier { '.' Identifier } [ '.' '*' ]
*
*/
public AbstractCompilationUnit.ImportDeclaration
parseImportDeclarationBody() throws CompileException, IOException {
final Location loc = this.location();
boolean isStatic = this.peekRead("static");
List l = new ArrayList();
l.add(this.read(TokenType.IDENTIFIER));
for (;;) {
if (!this.peek(".")) {
String[] identifiers = (String[]) l.toArray(new String[l.size()]);
return (
isStatic
? new AbstractCompilationUnit.SingleStaticImportDeclaration(loc, identifiers)
: new AbstractCompilationUnit.SingleTypeImportDeclaration(loc, identifiers)
);
}
this.read(".");
if (this.peekRead("*")) {
String[] identifiers = (String[]) l.toArray(new String[l.size()]);
return (
isStatic
? new AbstractCompilationUnit.StaticImportOnDemandDeclaration(loc, identifiers)
: new AbstractCompilationUnit.TypeImportOnDemandDeclaration(loc, identifiers)
);
}
l.add(this.read(TokenType.IDENTIFIER));
}
}
/**
*
* QualifiedIdentifier := Identifier { '.' Identifier }
*
*/
public String[]
parseQualifiedIdentifier() throws CompileException, IOException {
List l = new ArrayList();
l.add(this.read(TokenType.IDENTIFIER));
while (this.peek(".") && this.peekNextButOne().type == TokenType.IDENTIFIER) {
this.read();
l.add(this.read(TokenType.IDENTIFIER));
}
return (String[]) l.toArray(new String[l.size()]);
}
/**
*
* PackageMemberTypeDeclaration := ModifiersOpt PackageMemberTypeDeclarationRest
*
*/
public PackageMemberTypeDeclaration
parsePackageMemberTypeDeclaration() throws CompileException, IOException {
return this.parsePackageMemberTypeDeclarationRest(this.doc(), this.parseModifiers());
}
/**
*
* PackageMemberTypeDeclarationRest :=
* 'class' ClassDeclarationRest |
* 'enum' EnumDeclarationRest |
* 'interface' InterfaceDeclarationRest
* '@' 'interface' AnnotationTypeDeclarationRest
*
*/
private PackageMemberTypeDeclaration
parsePackageMemberTypeDeclarationRest(@Nullable String docComment, Modifier[] modifiers)
throws CompileException, IOException {
switch (this.read("class", "enum", "interface", "@")) {
case 0: // "class"
if (docComment == null) this.warning("CDCM", "Class doc comment missing");
return (PackageMemberClassDeclaration) this.parseClassDeclarationRest(
docComment, // docComment
modifiers, // modifiers
ClassDeclarationContext.COMPILATION_UNIT // context
);
case 1: // "enum"
if (docComment == null) this.warning("EDCM", "Enum doc comment missing");
return (PackageMemberEnumDeclaration) this.parseEnumDeclarationRest(
docComment, // docComment
modifiers, // modifiers
ClassDeclarationContext.COMPILATION_UNIT // context
);
case 2: // "interface"
if (docComment == null) this.warning("IDCM", "Interface doc comment missing");
return (PackageMemberInterfaceDeclaration) this.parseInterfaceDeclarationRest(
docComment, // docComment
modifiers, // modifiers
InterfaceDeclarationContext.COMPILATION_UNIT // context
);
case 3: // "@"
this.read("interface");
if (docComment == null) {
this.warning("ATDCM", "Annotation type doc comment missing");
}
return (PackageMemberAnnotationTypeDeclaration) this.parseAnnotationTypeDeclarationRest(
docComment, // docComment
modifiers, // modifiers
InterfaceDeclarationContext.COMPILATION_UNIT // context
);
default:
throw new IllegalStateException();
}
}
/**
*
* Modifiers := { Modifier }
*
*
* Includes the case "no modifiers".
*
*/
public Modifier[]
parseModifiers() throws CompileException, IOException {
List result = new ArrayList();
for (;;) {
Modifier m = this.parseOptionalModifier();
if (m == null) break;
result.add(m);
}
return (Modifier[]) result.toArray(new Modifier[result.size()]);
}
/**
*
* Modifier :=
* Annotation
* | 'public' | 'protected' | 'private'
* | 'static' | 'abstract' | 'final' | 'native' | 'synchronized' | 'transient' | 'volatile' | 'strictfp'
* | 'default'
*
*/
@Nullable public Modifier
parseOptionalModifier() throws CompileException, IOException {
if (this.peek("@")) {
// Annotation type declaration ahead?
if (this.peekNextButOne().value.equals("interface")) return null;
return this.parseAnnotation();
}
int idx = this.peekRead(Parser.ACCESS_MODIFIER_KEYWORDS);
if (idx == -1) return null;
return new AccessModifier(Parser.ACCESS_MODIFIER_KEYWORDS[idx], this.location());
}
private static final String[] ACCESS_MODIFIER_KEYWORDS = {
"public", "protected", "private",
"static", "abstract", "final", "native", "synchronized", "transient", "volatile", "strictfp",
"default",
"transitive",
};
/**
*
* Annotation :=
* MarkerAnnotation // JLS7 9.7.2
* | SingleElementAnnotation // JLS7 9.7.3
* | NormalAnnotation // JLS7 9.7.1
*
* MarkerAnnotation := '@' Identifier
*
* SingleElementAnnotation := '@' Identifier '(' ElementValue ')'
*
* NormalAnnotation := '@' TypeName '(' ElementValuePairsOpt ')'
*
* ElementValuePairsOpt := [ ElementValuePair { ',' ElementValuePair } ]
*
*/
private Annotation
parseAnnotation() throws CompileException, IOException {
this.read("@");
ReferenceType type = new ReferenceType(
this.location(),
new Annotation[0],
this.parseQualifiedIdentifier(),
null // typeArguments
);
// Marker annotation?
if (!this.peekRead("(")) return new MarkerAnnotation(type);
// Single-element annotation?
if (!this.peek(TokenType.IDENTIFIER) || !this.peekNextButOne("=")) {
ElementValue elementValue = this.parseElementValue();
this.read(")");
return new SingleElementAnnotation(type, elementValue);
}
// Normal annotation.
ElementValuePair[] elementValuePairs;
if (this.peekRead(")")) {
elementValuePairs = new ElementValuePair[0];
} else {
List evps = new ArrayList();
do {
evps.add(this.parseElementValuePair());
} while (this.read(",", ")") == 0);
elementValuePairs = (ElementValuePair[]) evps.toArray(new ElementValuePair[evps.size()]);
}
return new NormalAnnotation(type, elementValuePairs);
}
/**
*
* ElementValuePair := Identifier '=' ElementValue
*
*/
private ElementValuePair
parseElementValuePair() throws CompileException, IOException {
String identifier = this.read(TokenType.IDENTIFIER);
this.read("=");
return new ElementValuePair(identifier, this.parseElementValue());
}
/**
*
* ElementValue :=
* ConditionalExpression
* | Annotation
* | ElementValueArrayInitializer
*
*/
private ElementValue
parseElementValue() throws CompileException, IOException {
if (this.peek("@")) return this.parseAnnotation();
if (this.peek("{")) return this.parseElementValueArrayInitializer();
return this.parseConditionalAndExpression().toRvalueOrCompileException();
}
/**
*
* ElementValueArrayInitializer := '{' { ElementValue | ',' } '}'
*
*/
private ElementValue
parseElementValueArrayInitializer() throws CompileException, IOException {
this.read("{");
Location loc = this.location();
List evs = new ArrayList();
while (!this.peekRead("}")) {
if (this.peekRead(",")) continue;
evs.add(this.parseElementValue());
}
return new ElementValueArrayInitializer((ElementValue[]) evs.toArray(new ElementValue[evs.size()]), loc);
}
/**
*
* ClassDeclarationRest :=
* Identifier [ typeParameters ]
* [ 'extends' ReferenceType ]
* [ 'implements' ReferenceTypeList ]
* ClassBody
*
*/
public NamedClassDeclaration
parseClassDeclarationRest(
@Nullable String docComment,
Modifier[] modifiers,
ClassDeclarationContext context
) throws CompileException, IOException {
Location location = this.location();
String className = this.read(TokenType.IDENTIFIER);
this.verifyIdentifierIsConventionalClassOrInterfaceName(className, location);
TypeParameter[] typeParameters = this.parseTypeParametersOpt();
ReferenceType extendedType = null;
if (this.peekRead("extends")) {
extendedType = this.parseReferenceType();
}
ReferenceType[] implementedTypes = new ReferenceType[0];
if (this.peekRead("implements")) {
implementedTypes = this.parseReferenceTypeList();
}
NamedClassDeclaration namedClassDeclaration;
if (context == ClassDeclarationContext.COMPILATION_UNIT) {
namedClassDeclaration = new PackageMemberClassDeclaration(
location, // location
docComment, // docComment
this.packageMemberClassModifiers(modifiers), // modifiers
className, // name
typeParameters, // typeParameters
extendedType, // optinalExtendedType
implementedTypes // implementedTypes
);
} else
if (context == ClassDeclarationContext.TYPE_DECLARATION) {
namedClassDeclaration = new MemberClassDeclaration(
location, // location
docComment, // docComment
this.classModifiers(modifiers), // modifiers
className, // name
typeParameters, // typeParameters
extendedType, // extendedType
implementedTypes // implementedTypes
);
} else
if (context == ClassDeclarationContext.BLOCK) {
namedClassDeclaration = new LocalClassDeclaration(
location, // location
docComment, // docComment
this.classModifiers(modifiers), // modifiers
className, // name
typeParameters, // typeParameters
extendedType, // extendedType
implementedTypes // implementedTypes
);
} else
{
throw new InternalCompilerException("SNO: Class declaration in unexpected context " + context);
}
this.parseClassBody(namedClassDeclaration);
return namedClassDeclaration;
}
/**
*
* EnumDeclarationRest := Identifier [ 'implements' ReferenceTypeList ] EnumBody
*
*/
public EnumDeclaration
parseEnumDeclarationRest(
@Nullable String docComment,
Modifier[] modifiers,
ClassDeclarationContext context
) throws CompileException, IOException {
Location location = this.location();
String enumName = this.read(TokenType.IDENTIFIER);
this.verifyIdentifierIsConventionalClassOrInterfaceName(enumName, location);
if (this.peekRead("<")) {
throw this.compileException("Enum declaration must not have type parameters");
}
if (this.peekRead("extends")) {
throw this.compileException("Enum declaration must not have an EXTENDS clause");
}
ReferenceType[] implementedTypes = new ReferenceType[0];
if (this.peekRead("implements")) {
implementedTypes = this.parseReferenceTypeList();
}
EnumDeclaration enumDeclaration;
if (context == ClassDeclarationContext.COMPILATION_UNIT) {
enumDeclaration = new PackageMemberEnumDeclaration(
location, // location
docComment, // docComment
this.classModifiers(modifiers), // modifiers
enumName, // name
implementedTypes // implementedTypes
);
} else
if (context == ClassDeclarationContext.TYPE_DECLARATION) {
enumDeclaration = new MemberEnumDeclaration(
location, // location
docComment, // docComment
this.classModifiers(modifiers), // modifiers
enumName, // name
implementedTypes // implementedTypes
);
} else
{
throw new InternalCompilerException("SNO: Enum declaration in unexpected context " + context);
}
this.parseEnumBody(enumDeclaration);
return enumDeclaration;
}
/**
* The kinds of context where a class declaration can occur.
*/
public
enum ClassDeclarationContext {
/**
* The class declaration appears inside a 'block'.
*/
BLOCK,
/**
* The class declaration appears (directly) inside a type declaration.
*/
TYPE_DECLARATION,
/**
* The class declaration appears on the top level.
*/
COMPILATION_UNIT,
}
/**
* The kinds of context where a method declaration can occur.
*/
public
enum MethodDeclarationContext {
/** Class method declaration. */
CLASS_DECLARATION,
/** Interface method declaration. */
INTERFACE_DECLARATION,
/** Annotation type method declaration. */
ANNOTATION_TYPE_DECLARATION,
}
/**
*
* ClassBody := '{' { ClassBodyDeclaration } '}'
*
*/
public void
parseClassBody(AbstractClassDeclaration classDeclaration) throws CompileException, IOException {
this.read("{");
for (;;) {
if (this.peekRead("}")) return;
this.parseClassBodyDeclaration(classDeclaration);
}
}
/**
*
* EnumBody := '{' [ EnumConstant { ',' EnumConstant } [ ',' ] [ ';' ] { ClassBodyDeclaration } '}'
*
*/
public void
parseEnumBody(EnumDeclaration enumDeclaration) throws CompileException, IOException {
this.read("{");
while (this.peek(";", "}") == -1) {
enumDeclaration.addConstant(this.parseEnumConstant());
if (!this.peekRead(",")) break;
}
while (!this.peekRead("}")) {
this.parseClassBodyDeclaration((AbstractClassDeclaration) enumDeclaration);
}
}
/**
*
* EnumConstant := [ Annotations ] Identifier [ Arguments ] [ ClassBody ]
*
*/
public EnumConstant
parseEnumConstant() throws CompileException, IOException {
EnumConstant result = new EnumConstant(
this.location(), // location
this.doc(), // docComment
this.enumConstantModifiers(this.parseModifiers()), // modifiers
this.read(TokenType.IDENTIFIER), // name
this.peek("(") ? this.parseArguments() : null // arguments
);
if (this.peek("{")) {
this.parseClassBody(result);
}
return result;
}
/**
*
* ClassBodyDeclaration :=
* ';' |
* ModifiersOpt (
* Block | // Instance (JLS7 8.6) or static initializer (JLS7 8.7)
* 'void' Identifier MethodDeclarationRest |
* 'class' ClassDeclarationRest |
* 'interface' InterfaceDeclarationRest |
* ConstructorDeclarator |
* [ TypeArguments ] Type Identifier MethodDeclarationRest |
* Type Identifier FieldDeclarationRest ';'
* )
*
*/
public void
parseClassBodyDeclaration(AbstractClassDeclaration classDeclaration) throws CompileException, IOException {
if (this.peekRead(";")) return;
final String docComment = this.doc();
final Modifier[] modifiers = this.parseModifiers();
// Initializer?
if (this.peek("{")) {
if (Parser.hasAccessModifierOtherThan(modifiers, "static")) {
throw this.compileException("Only access flag \"static\" allowed on initializer");
}
Initializer initializer = new Initializer(this.location(), modifiers, this.parseBlock());
classDeclaration.addInitializer(initializer);
return;
}
// "void" method declaration (without type arguments).
if (this.peekRead("void")) {
Location location = this.location();
String name = this.read(TokenType.IDENTIFIER);
classDeclaration.addDeclaredMethod(this.parseMethodDeclarationRest(
docComment, // docComment
this.methodModifiers(modifiers), // modifiers
null, // typeParameters
new PrimitiveType(location, Primitive.VOID), // type
name, // name
false, // allowDefaultClause
MethodDeclarationContext.CLASS_DECLARATION // context
));
return;
}
// Member class.
if (this.peekRead("class")) {
if (docComment == null) this.warning("MCDCM", "Member class doc comment missing");
classDeclaration.addMemberTypeDeclaration((MemberTypeDeclaration) this.parseClassDeclarationRest(
docComment, // docComment
this.classModifiers(modifiers), // modifiers
ClassDeclarationContext.TYPE_DECLARATION // context
));
return;
}
// Member enum.
if (this.peekRead("enum")) {
if (docComment == null) this.warning("MEDCM", "Member enum doc comment missing");
classDeclaration.addMemberTypeDeclaration((MemberTypeDeclaration) this.parseEnumDeclarationRest(
docComment, // docComment
this.classModifiers(modifiers), // modifiers
ClassDeclarationContext.TYPE_DECLARATION // context
));
return;
}
// Member interface.
if (this.peekRead("interface")) {
if (docComment == null) {
this.warning("MIDCM", "Member interface doc comment missing");
}
classDeclaration.addMemberTypeDeclaration((MemberTypeDeclaration) this.parseInterfaceDeclarationRest(
docComment, // docComment
this.interfaceModifiers(modifiers), // modifiers
InterfaceDeclarationContext.NAMED_TYPE_DECLARATION // context
));
return;
}
// Member annotation type.
if (this.peek("@") && this.peekNextButOne("interface")) {
this.read();
this.read();
if (docComment == null) {
this.warning("MATDCM", "Member annotation type doc comment missing", this.location());
}
classDeclaration.addMemberTypeDeclaration((MemberTypeDeclaration) this.parseInterfaceDeclarationRest(
docComment, // docComment
this.interfaceModifiers(modifiers), // modifiers
InterfaceDeclarationContext.NAMED_TYPE_DECLARATION // context
));
return;
}
// Constructor.
if (
classDeclaration instanceof NamedClassDeclaration
&& this.peek().value.equals(((NamedClassDeclaration) classDeclaration).getName())
&& this.peekNextButOne("(")
) {
if (docComment == null) this.warning("CDCM", "Constructor doc comment missing", this.location());
classDeclaration.addConstructor(this.parseConstructorDeclarator(
docComment, // declaringClass
this.constructorModifiers(modifiers) // modifiers
));
return;
}
// Member method or field.
TypeParameter[] typeParameters = this.parseTypeParametersOpt();
// VOID method declaration?
if (this.peekRead("void")) {
String name = this.read(TokenType.IDENTIFIER);
classDeclaration.addDeclaredMethod(this.parseMethodDeclarationRest(
docComment, // docComment
this.methodModifiers(modifiers), // modifiers
typeParameters, // typeParameters
new PrimitiveType(this.location(), Primitive.VOID), // type
name, // name
false, // allowDefaultClause
MethodDeclarationContext.CLASS_DECLARATION // context
));
return;
}
Type memberType = this.parseType();
Location location = this.location();
String memberName = this.read(TokenType.IDENTIFIER);
// Method declarator.
if (this.peek("(")) {
classDeclaration.addDeclaredMethod(this.parseMethodDeclarationRest(
docComment, // docComment
this.methodModifiers(modifiers), // modifiers
typeParameters, // typeParameters
memberType, // type
memberName, // name
false, // allowDefaultClause
MethodDeclarationContext.CLASS_DECLARATION // context
));
return;
}
// Field declarator.
if (typeParameters != null) {
throw this.compileException("Type parameters not allowed on field declaration");
}
if (docComment == null) this.warning("FDCM", "Field doc comment missing", this.location());
FieldDeclaration fd = new FieldDeclaration(
location, // location
docComment, // docComment
this.fieldModifiers(modifiers), // modifiers
memberType, // type
this.parseFieldDeclarationRest(memberName) // variableDeclarators
);
this.read(";");
classDeclaration.addFieldDeclaration(fd);
}
/**
*
* InterfaceDeclarationRest :=
* Identifier [ typeParameters ]
* [ 'extends' ReferenceTypeList ]
* InterfaceBody
*
*/
public InterfaceDeclaration
parseInterfaceDeclarationRest(
@Nullable String docComment,
Modifier[] modifiers,
InterfaceDeclarationContext context
) throws CompileException, IOException {
Location location = this.location();
String interfaceName = this.read(TokenType.IDENTIFIER);
this.verifyIdentifierIsConventionalClassOrInterfaceName(interfaceName, location);
TypeParameter[] typeParameters = this.parseTypeParametersOpt();
ReferenceType[] extendedTypes = new ReferenceType[0];
if (this.peekRead("extends")) {
extendedTypes = this.parseReferenceTypeList();
}
InterfaceDeclaration id;
if (context == InterfaceDeclarationContext.COMPILATION_UNIT) {
id = new PackageMemberInterfaceDeclaration(
location, // location
docComment, // docComment
this.packageMemberInterfaceModifiers(modifiers), // modifiers
interfaceName, // name
typeParameters, // typeParameters
extendedTypes // extendedTypes
);
} else
if (context == InterfaceDeclarationContext.NAMED_TYPE_DECLARATION) {
id = new MemberInterfaceDeclaration(
location, // location
docComment, // docComment
this.interfaceModifiers(modifiers), // modifiers
interfaceName, // name
typeParameters, // typeParameters
extendedTypes // extendedTypes
);
} else
{
throw new InternalCompilerException("SNO: Interface declaration in unexpected context " + context);
}
this.parseInterfaceBody(id);
return id;
}
/**
*
* AnnotationTypeDeclarationRest := Identifier AnnotationTypeBody
*
*/
public AnnotationTypeDeclaration
parseAnnotationTypeDeclarationRest(
@Nullable String docComment,
Modifier[] modifiers,
InterfaceDeclarationContext context
) throws CompileException, IOException {
Location location = this.location();
String annotationTypeName = this.read(TokenType.IDENTIFIER);
this.verifyIdentifierIsConventionalClassOrInterfaceName(annotationTypeName, location);
AnnotationTypeDeclaration atd;
if (context == InterfaceDeclarationContext.COMPILATION_UNIT) {
atd = new PackageMemberAnnotationTypeDeclaration(
location, // location
docComment, // docComment
this.packageMemberInterfaceModifiers(modifiers), // modifiers
annotationTypeName // name
);
} else
if (context == InterfaceDeclarationContext.NAMED_TYPE_DECLARATION) {
atd = new MemberAnnotationTypeDeclaration(
location, // location
docComment, // docComment
this.interfaceModifiers(modifiers), // modifiers
annotationTypeName // name
);
} else
{
throw new InternalCompilerException("SNO: Annotation type declaration in unexpected context " + context);
}
this.parseInterfaceBody((InterfaceDeclaration) atd);
return atd;
}
/**
* The kinds of context where an interface declaration can occur.
*/
public
enum InterfaceDeclarationContext {
/**
* The interface declaration appears (directly) inside a 'named type declaration'.
*/
NAMED_TYPE_DECLARATION,
/**
* The interface declaration appears at the top level.
*/
COMPILATION_UNIT,
}
/**
*
* InterfaceBody := '{' {
* ';' |
* ModifiersOpt (
* 'void' Identifier MethodDeclarationRest |
* 'class' ClassDeclarationRest |
* 'interface' InterfaceDeclarationRest |
* Type Identifier (
* MethodDeclarationRest |
* FieldDeclarationRest
* )
* )
* } '}'
*
*/
public void
parseInterfaceBody(InterfaceDeclaration interfaceDeclaration) throws CompileException, IOException {
this.read("{");
while (!this.peekRead("}")) {
if (this.peekRead(";")) continue;
String docComment = this.doc();
Modifier[] modifiers = this.parseModifiers();
// "void" method declaration (without type parameters).
if (this.peekRead("void")) {
interfaceDeclaration.addDeclaredMethod(this.parseMethodDeclarationRest(
docComment, // docComment
modifiers, // modifiers
null, // typeParameters
new PrimitiveType(this.location(), Primitive.VOID), // type
this.read(TokenType.IDENTIFIER), // name
true, // allowDefaultClause
( // context
interfaceDeclaration instanceof AnnotationTypeDeclaration
? MethodDeclarationContext.ANNOTATION_TYPE_DECLARATION
: MethodDeclarationContext.INTERFACE_DECLARATION
)
));
continue;
}
// Member class.
if (this.peekRead("class")) {
if (docComment == null) {
this.warning("MCDCM", "Member class doc comment missing", this.location());
}
if (Parser.hasAccessModifier(modifiers, "default")) {
throw this.compileException("Modifier \"default\" not allowed on member class declaration");
}
interfaceDeclaration.addMemberTypeDeclaration(
(MemberTypeDeclaration) this.parseClassDeclarationRest(
docComment, // docComment
this.classModifiers(modifiers), // modifiers
ClassDeclarationContext.TYPE_DECLARATION // context
)
);
continue;
}
// Member enum.
if (this.peekRead("enum")) {
if (docComment == null) {
this.warning("MEDCM", "Member enum doc comment missing", this.location());
}
if (Parser.hasAccessModifier(modifiers, "default")) {
throw this.compileException("Modifier \"default\" not allowed on member enum declaration");
}
interfaceDeclaration.addMemberTypeDeclaration(
(MemberTypeDeclaration) this.parseClassDeclarationRest(
docComment, // docComment
this.classModifiers(modifiers), // modifiers
ClassDeclarationContext.TYPE_DECLARATION // context
)
);
continue;
}
// Member interface.
if (this.peekRead("interface")) {
if (docComment == null) {
this.warning("MIDCM", "Member interface doc comment missing", this.location());
}
if (Parser.hasAccessModifier(modifiers, "default")) {
throw this.compileException("Modifier \"default\" not allowed on member interface declaration");
}
interfaceDeclaration.addMemberTypeDeclaration(
(MemberTypeDeclaration) this.parseInterfaceDeclarationRest(
docComment, // docComment
this.interfaceModifiers(modifiers), // modifiers
InterfaceDeclarationContext.NAMED_TYPE_DECLARATION // context
)
);
continue;
}
// Member annotation type.
if (this.peek("@") && this.peekNextButOne("interface")) {
this.read();
this.read();
if (docComment == null) {
this.warning("MATDCM", "Member annotation type doc comment missing", this.location());
}
if (Parser.hasAccessModifier(modifiers, "default")) {
throw this.compileException(
"Modifier \"default\" not allowed on member annotation type declaration"
);
}
interfaceDeclaration.addMemberTypeDeclaration(
(MemberTypeDeclaration) this.parseInterfaceDeclarationRest(
docComment, // docComment
this.interfaceModifiers(modifiers), // modifiers
InterfaceDeclarationContext.NAMED_TYPE_DECLARATION // context
)
);
continue;
}
// Member method or field.
TypeParameter[] typeParameters = this.parseTypeParametersOpt();
// "void" method declaration?
if (this.peekRead("void")) {
Location location = this.location();
String name = this.read(TokenType.IDENTIFIER);
interfaceDeclaration.addDeclaredMethod(this.parseMethodDeclarationRest(
docComment, // docComment
modifiers, // modifiers
typeParameters, // typeParameters
new PrimitiveType(location, Primitive.VOID), // type
name, // name
true, // allowDefaultClause
( // context
interfaceDeclaration instanceof AnnotationTypeDeclaration
? MethodDeclarationContext.ANNOTATION_TYPE_DECLARATION
: MethodDeclarationContext.INTERFACE_DECLARATION
)
));
continue;
}
Type memberType = this.parseType();
String memberName = this.read(TokenType.IDENTIFIER);
Location location = this.location();
// Method declarator?
if (this.peek("(")) {
interfaceDeclaration.addDeclaredMethod(this.parseMethodDeclarationRest(
docComment, // docComment
modifiers, // modifiers
typeParameters, // typeParameters
memberType, // type
memberName, // name
true, // allowDefaultClause
( // context
interfaceDeclaration instanceof AnnotationTypeDeclaration
? MethodDeclarationContext.ANNOTATION_TYPE_DECLARATION
: MethodDeclarationContext.INTERFACE_DECLARATION
)
));
continue;
}
// Must be a constant declarator.
if (typeParameters != null) {
throw this.compileException("Type parameters not allowed with constant declaration");
}
if (docComment == null) this.warning("CDCM", "Constant doc comment missing", this.location());
if (Parser.hasAccessModifier(modifiers, "default")) {
throw this.compileException("Modifier \"default\" not allowed for constants");
}
FieldDeclaration cd = new FieldDeclaration(
location, // location
docComment, // docComment
this.constantModifiers(modifiers), // modifiers
memberType, // type
this.parseFieldDeclarationRest(memberName) // variableDeclarators
);
interfaceDeclaration.addConstantDeclaration(cd);
}
}
/**
*
* ConstructorDeclarator :=
* Identifier
* FormalParameters
* [ 'throws' ReferenceTypeList ]
* '{'
* [ 'this' Arguments ';' | 'super' Arguments ';' | Primary '.' 'super' Arguments ';' ]
* BlockStatements
* '}'
*
*/
public ConstructorDeclarator
parseConstructorDeclarator(@Nullable String docComment, Modifier[] modifiers)
throws CompileException, IOException {
this.read(TokenType.IDENTIFIER); // Class name
// Parse formal parameters.
final FormalParameters formalParameters = this.parseFormalParameters();
// Parse "throws" clause.
ReferenceType[] thrownExceptions;
if (this.peekRead("throws")) {
thrownExceptions = this.parseReferenceTypeList();
} else {
thrownExceptions = new ReferenceType[0];
}
// Parse constructor body.
final Location location = this.location();
this.read("{");
// Special treatment for the first statement of the constructor body: If this is surely an
// expression statement, and if it could be a "ConstructorInvocation", then parse the
// expression and check if it IS a ConstructorInvocation.
ConstructorInvocation constructorInvocation = null;
List statements = new ArrayList();
if (
this.peek(
"this", "super", "new", "void", // SUPPRESS CHECKSTYLE Wrap:1
"byte", "char", "short", "int", "long", "float", "double", "boolean"
) != -1
|| this.peekLiteral()
|| this.peek(TokenType.IDENTIFIER)
) {
Atom a = this.parseExpressionOrType();
if (a instanceof ConstructorInvocation) {
this.read(";");
constructorInvocation = (ConstructorInvocation) a;
} else {
Statement s;
if (this.peek(TokenType.IDENTIFIER)) {
Type variableType = a.toTypeOrCompileException();
s = new LocalVariableDeclarationStatement(
a.getLocation(), // location
new Modifier[0], // modifiers
variableType, // type
this.parseVariableDeclarators() // variableDeclarators
);
this.read(";");
} else {
s = new ExpressionStatement(a.toRvalueOrCompileException());
this.read(";");
}
statements.add(s);
}
}
statements.addAll(this.parseBlockStatements());
this.read("}");
return new ConstructorDeclarator(
location, // location
docComment, // docComment
this.constructorModifiers(modifiers), // modifiers
formalParameters, // formalParameters
thrownExceptions, // thrownExceptions
constructorInvocation, // constructorInvocationStatement
statements // statements
);
}
/**
* Equivalent with {@code parseMethodDeclaration(false, MethodDeclarationContext.CLASS_DECLARATION)}.
*
* @see #parseMethodDeclaration(boolean, MethodDeclarationContext)
*/
public MethodDeclarator
parseMethodDeclaration() throws CompileException, IOException {
return this.parseMethodDeclaration(false, MethodDeclarationContext.CLASS_DECLARATION);
}
/**
*
* MethodDeclaration :=
* [ DocComment ] Modifiers [ TypeParameters ] VoidOrType Identifier MethodDeclarationRest
*
*
* @param allowDefaultClause Whether a "default clause" for an "annotation type element" (JLS8 9.6.2) should be
* parsed
*/
public MethodDeclarator
parseMethodDeclaration(boolean allowDefaultClause, MethodDeclarationContext context)
throws CompileException, IOException {
return this.parseMethodDeclarationRest(
this.doc(), // docComment
this.parseModifiers(), // modifiers
this.parseTypeParametersOpt(), // typeParameters
this.parseVoidOrType(), // type
this.read(TokenType.IDENTIFIER), // name
allowDefaultClause, // allowDefaultClause
context // context
);
}
/**
*
* VoidOrType := 'void' | Type
*
*/
public Type
parseVoidOrType() throws CompileException, IOException {
return this.peekRead("void") ? new PrimitiveType(this.location(), Primitive.VOID) : this.parseType();
}
/**
*
* MethodDeclarationRest :=
* FormalParameters
* { '[' ']' }
* [ 'throws' ReferenceTypeList ]
* [ 'default' expression ]
* ( ';' | MethodBody )
*
*
* @param allowDefaultClause Whether a "default clause" for an "annotation type element" (JLS8 9.6.2) should be
* parsed
*/
public MethodDeclarator
parseMethodDeclarationRest(
@Nullable String docComment,
Modifier[] modifiers,
@Nullable TypeParameter[] typeParameters,
Type type,
String name,
boolean allowDefaultClause,
MethodDeclarationContext context
) throws CompileException, IOException {
Location location = this.location();
if (docComment == null) this.warning("MDCM", "Method doc comment missing", location);
if (this.getSourceVersion() < 8 && Parser.hasAccessModifier(modifiers, "default")) {
throw this.compileException("Default interface methods only available for source version 8+");
}
this.verifyIdentifierIsConventionalMethodName(name, location);
final FormalParameters formalParameters = this.parseFormalParameters();
for (int i = this.parseBracketsOpt(); i > 0; --i) type = new ArrayType(type);
ReferenceType[] thrownExceptions;
if (this.peekRead("throws")) {
thrownExceptions = this.parseReferenceTypeList();
} else {
thrownExceptions = new ReferenceType[0];
}
ElementValue defaultValue = (
allowDefaultClause && this.peekRead("default")
? this.parseElementValue()
: null
);
List statements;
if (this.peekRead(";")) {
statements = null;
} else {
if (Parser.hasAccessModifier(modifiers, "abstract", "native")) {
throw this.compileException("Abstract or native method must not have a body");
}
this.read("{");
statements = this.parseBlockStatements();
this.read("}");
}
return new MethodDeclarator(
location, // location
docComment, // docComment
( // modifiers
context == MethodDeclarationContext.ANNOTATION_TYPE_DECLARATION
? this.annotationTypeElementModifiers(modifiers)
: context == MethodDeclarationContext.CLASS_DECLARATION
? this.methodModifiers(modifiers)
: context == MethodDeclarationContext.INTERFACE_DECLARATION
? this.interfaceMethodModifiers(modifiers)
: new Modifier[1]
),
typeParameters, // typeParameters
type, // type
name, // name
formalParameters, // formalParameters
thrownExceptions, // thrownExceptions
defaultValue, // defaultValue
statements // statements
);
}
private int
getSourceVersion() {
if (this.sourceVersion == -1) {
// System property Description Example values
// ------------------------------------------------------------------------------------------------
// java.vm.specification.version Java Virtual Machine specification version "1.8", "11"
// java.specification.version Java Runtime Environment specification version "1.8", "11"
// java.version Java Runtime Environment version "1.8.0_45", "11-ea"
// java.class.version Java class format version number "52.0", "55.0"
// String jsv = System.getProperty("java.specification.version");
// jsv = jsv.substring(jsv.indexOf('.') + 1);
// this.sourceVersion = Integer.parseInt(jsv);
// This parser can parse all Java 11 constructs, so there is no reason to restrict it to an older language
// version.
this.sourceVersion = 11;
}
return this.sourceVersion;
}
/**
*
* VariableInitializer :=
* ArrayInitializer |
* Expression
*
*/
public ArrayInitializerOrRvalue
parseVariableInitializer() throws CompileException, IOException {
if (this.peek("{")) {
return this.parseArrayInitializer();
} else
{
return this.parseExpression();
}
}
/**
*
* ArrayInitializer :=
* '{' [ VariableInitializer { ',' VariableInitializer } [ ',' ] '}'
*
*/
public ArrayInitializer
parseArrayInitializer() throws CompileException, IOException {
final Location location = this.location();
this.read("{");
List l = new ArrayList();
while (!this.peekRead("}")) {
l.add(this.parseVariableInitializer());
if (this.peekRead("}")) break;
this.read(",");
}
return new ArrayInitializer(
location,
(ArrayInitializerOrRvalue[]) l.toArray(new ArrayInitializerOrRvalue[l.size()])
);
}
/**
*
* FormalParameters := '(' [ FormalParameterList ] ')'
*
*/
public FormalParameters
parseFormalParameters() throws CompileException, IOException {
this.read("(");
if (this.peekRead(")")) return new FormalParameters(this.location());
FormalParameters result = this.parseFormalParameterList();
this.read(")");
return result;
}
/**
*
* FormalParameterList := FormalParameter { ',' FormalParameter }
*
*/
public FormalParameters
parseFormalParameterList() throws CompileException, IOException {
List l = new ArrayList();
final boolean[] hasEllipsis = new boolean[1];
do {
if (hasEllipsis[0]) throw this.compileException("Only the last parameter may have an ellipsis");
l.add(this.parseFormalParameter(hasEllipsis));
} while (this.peekRead(","));
return new FormalParameters(
this.location(), // location
(FormalParameter[]) l.toArray(new FormalParameter[l.size()]), // parameters
hasEllipsis[0] // variableArity
);
}
/**
*
* FormalParameterListRest := Identifier { ',' FormalParameter }
*
*/
public FormalParameters
parseFormalParameterListRest(Type firstParameterType) throws CompileException, IOException {
List l = new ArrayList();
final boolean[] hasEllipsis = new boolean[1];
l.add(this.parseFormalParameterRest(new Modifier[0], firstParameterType, hasEllipsis));
while (this.peekRead(",")) {
if (hasEllipsis[0]) throw this.compileException("Only the last parameter may have an ellipsis");
l.add(this.parseFormalParameter(hasEllipsis));
}
return new FormalParameters(
this.location(), // location
(FormalParameter[]) l.toArray(new FormalParameter[l.size()]), // parameters
hasEllipsis[0] // variableArity
);
}
/**
*
* FormalParameter := [ 'final' ] Type FormalParameterRest
*
*/
public FormalParameter
parseFormalParameter(boolean[] hasEllipsis) throws CompileException, IOException {
Modifier[] modifiers = this.parseModifiers();
if (Parser.hasAccessModifier(modifiers, "default")) {
throw this.compileException("Modifier \"default\" not allowed on formal parameters");
}
// Ignore annotations.
return this.parseFormalParameterRest(modifiers, this.parseType(), hasEllipsis);
}
/**
*
* FormalParameterRest := [ '.' '.' '.' ] Identifier BracketsOpt
*
*
* @param hasEllipsis After return, the zeroth element indicates whether the formal parameter was declared with an
* ellipsis
*/
public FormalParameter
parseFormalParameterRest(
Modifier[] modifiers,
Type type,
boolean[] hasEllipsis
) throws CompileException, IOException {
if (this.peekRead(".")) {
this.read(".");
this.read(".");
hasEllipsis[0] = true;
}
Location location = this.location();
String name = this.read(TokenType.IDENTIFIER);
this.verifyIdentifierIsConventionalLocalVariableOrParameterName(name, location);
for (int i = this.parseBracketsOpt(); i > 0; --i) type = new ArrayType(type);
return new FormalParameter(location, modifiers, type, name);
}
/**
*
* CatchFormalParameter := { VariableModifier } CatchType VariableDeclaratorId
* CatchType := UnannClassType { '|' ClassType }
* VariableModifier := Annotation | 'final'
* VariableDeclaratorId := Identifier [ Dims ]
* Dims := { Annotation } '[' ']' { { Annotation } '[' ']' }
* UnannClassType :=
* Identifier [ TypeArguments ]
* | UnannClassOrInterfaceType '.' { Annotation } Identifier [ TypeArguments ]
* UnannInterfaceType := UnannClassType
* UnannClassOrInterfaceType := UnannClassType | UnannInterfaceType
* ClassType :=
* { Annotation } Identifier [ TypeArguments ]
* | ClassOrInterfaceType '.' { Annotation } Identifier [ TypeArguments ]
*
*/
public CatchParameter
parseCatchParameter() throws CompileException, IOException {
Modifier[] modifiers = this.parseModifiers();
this.variableModifiers(modifiers);
List catchTypes = new ArrayList();
catchTypes.add(this.parseReferenceType());
while (this.peekRead("|")) catchTypes.add(this.parseReferenceType());
Location location = this.location();
String name = this.read(TokenType.IDENTIFIER);
this.verifyIdentifierIsConventionalLocalVariableOrParameterName(name, location);
return new CatchParameter(
location,
Parser.hasAccessModifier(modifiers, "final"),
(ReferenceType[]) catchTypes.toArray(new ReferenceType[catchTypes.size()]),
name
);
}
/**
*
* BracketsOpt := { '[' ']' }
*
*/
int
parseBracketsOpt() throws CompileException, IOException {
int res = 0;
while (this.peek("[") && this.peekNextButOne("]")) {
this.read();
this.read();
++res;
}
return res;
}
/**
*
* MethodBody := Block
*
*/
public Block
parseMethodBody() throws CompileException, IOException { return this.parseBlock(); }
/**
*
* Block := '{' BlockStatements '}'
*
*/
public Block
parseBlock() throws CompileException, IOException {
this.read("{");
Block block = new Block(this.location());
block.addStatements(this.parseBlockStatements());
this.read("}");
return block;
}
/**
*
* BlockStatements := { BlockStatement }
*
*/
public List
parseBlockStatements() throws CompileException, IOException {
List l = new ArrayList();
while (
!this.peek("}")
&& !this.peek("case")
&& !this.peek("default")
&& !this.peek(TokenType.END_OF_INPUT)
) l.add(this.parseBlockStatement());
return l;
}
/**
*
* BlockStatement :=
* Statement | (1)
* 'class' ... | (2)
* Modifiers Type VariableDeclarators ';' |
* Expression ';' |
* Expression BracketsOpt VariableDeclarators ';' (3)
*
*
*
* (1) Includes the "labeled statement".
*
*
* (2) Local class declaration.
*
*
* (3) Local variable declaration statement; "Expression" must pose a type, and has optional trailing brackets.
*
*/
public BlockStatement
parseBlockStatement() throws CompileException, IOException {
// Statement?
if (
(this.peek(TokenType.IDENTIFIER) && this.peekNextButOne(":"))
|| this.peek(
"if", "for", "while", "do", "try", "switch", "synchronized", // SUPPRESS CHECKSTYLE Wrap|LineLength:1
"return", "throw", "break", "continue", "assert"
) != -1
|| this.peek("{", ";") != -1
) return this.parseStatement();
// Local class declaration?
if (this.peekRead("class")) {
// JAVADOC[TM] ignores doc comments for local classes, but we
// don't...
String docComment = this.doc();
if (docComment == null) this.warning("LCDCM", "Local class doc comment missing", this.location());
final LocalClassDeclaration lcd = (LocalClassDeclaration) this.parseClassDeclarationRest(
docComment, // docComment
new Modifier[0], // modifiers
ClassDeclarationContext.BLOCK // context
);
return new LocalClassDeclarationStatement(lcd);
}
// Modifiers Type VariableDeclarators ';'
if (this.peek("final", "@") != -1) {
LocalVariableDeclarationStatement lvds = new LocalVariableDeclarationStatement(
this.location(), // location
this.variableModifiers(this.parseModifiers()), // modifiers
this.parseType(), // type
this.parseVariableDeclarators() // variableDeclarators
);
this.read(";");
return lvds;
}
// It's either a non-final local variable declaration or an expression statement. We can
// only tell after parsing an expression.
Atom a = this.parseExpressionOrType();
// Expression ';'
if (this.peekRead(";")) {
return new ExpressionStatement(a.toRvalueOrCompileException());
}
// Expression BracketsOpt VariableDeclarators ';'
Type variableType = a.toTypeOrCompileException();
for (int i = this.parseBracketsOpt(); i > 0; --i) variableType = new ArrayType(variableType);
LocalVariableDeclarationStatement lvds = new LocalVariableDeclarationStatement(
a.getLocation(), // location
new Modifier[0], // modifiers
variableType, // type
this.parseVariableDeclarators() // variableDeclarators
);
this.read(";");
return lvds;
}
/**
*
* VariableDeclarators := VariableDeclarator { ',' VariableDeclarator }
*
*/
public VariableDeclarator[]
parseVariableDeclarators() throws CompileException, IOException {
List l = new ArrayList();
do {
VariableDeclarator vd = this.parseVariableDeclarator();
this.verifyIdentifierIsConventionalLocalVariableOrParameterName(vd.name, vd.getLocation());
l.add(vd);
} while (this.peekRead(","));
return (VariableDeclarator[]) l.toArray(new VariableDeclarator[l.size()]);
}
/**
*
* FieldDeclarationRest :=
* VariableDeclaratorRest
* { ',' VariableDeclarator }
*
*/
public VariableDeclarator[]
parseFieldDeclarationRest(String name) throws CompileException, IOException {
List l = new ArrayList();
VariableDeclarator vd = this.parseVariableDeclaratorRest(name);
this.verifyIdentifierIsConventionalFieldName(vd.name, vd.getLocation());
l.add(vd);
while (this.peekRead(",")) {
vd = this.parseVariableDeclarator();
this.verifyIdentifierIsConventionalFieldName(vd.name, vd.getLocation());
l.add(vd);
}
return (VariableDeclarator[]) l.toArray(new VariableDeclarator[l.size()]);
}
/**
*
* VariableDeclarator := Identifier VariableDeclaratorRest
*
*/
public VariableDeclarator
parseVariableDeclarator() throws CompileException, IOException {
return this.parseVariableDeclaratorRest(this.read(TokenType.IDENTIFIER));
}
/**
*
* VariableDeclaratorRest := { '[' ']' } [ '=' VariableInitializer ]
*
*
* Used by field declarations and local variable declarations.
*
*/
public VariableDeclarator
parseVariableDeclaratorRest(String name) throws CompileException, IOException {
Location loc = this.location();
int brackets = this.parseBracketsOpt();
ArrayInitializerOrRvalue initializer = null;
if (this.peekRead("=")) initializer = this.parseVariableInitializer();
return new VariableDeclarator(loc, name, brackets, initializer);
}
/**
*
* Statement :=
* LabeledStatement |
* Block |
* IfStatement |
* ForStatement |
* WhileStatement |
* DoStatement |
* TryStatement |
* 'switch' ... |
* 'synchronized' ... |
* ReturnStatement |
* ThrowStatement |
* BreakStatement |
* ContinueStatement |
* EmptyStatement |
* ExpressionStatement
*
*/
public Statement
parseStatement() throws CompileException, IOException {
if (this.peek(TokenType.IDENTIFIER) && this.peekNextButOne(":")) {
return this.parseLabeledStatement();
}
Statement stmt = (
this.peek("{") ? this.parseBlock() :
this.peek("if") ? this.parseIfStatement() :
this.peek("for") ? this.parseForStatement() :
this.peek("while") ? this.parseWhileStatement() :
this.peek("do") ? this.parseDoStatement() :
this.peek("try") ? this.parseTryStatement() :
this.peek("switch") ? this.parseSwitchStatement() :
this.peek("synchronized") ? this.parseSynchronizedStatement() :
this.peek("return") ? this.parseReturnStatement() :
this.peek("throw") ? this.parseThrowStatement() :
this.peek("break") ? this.parseBreakStatement() :
this.peek("continue") ? this.parseContinueStatement() :
this.peek("assert") ? this.parseAssertStatement() :
this.peek(";") ? this.parseEmptyStatement() :
this.parseExpressionStatement()
);
return stmt;
}
/**
*
* LabeledStatement := Identifier ':' Statement
*
*/
public Statement
parseLabeledStatement() throws CompileException, IOException {
String label = this.read(TokenType.IDENTIFIER);
this.read(":");
return new LabeledStatement(
this.location(), // location
label, // label
this.parseStatement() // body
);
}
/**
*
* IfStatement := 'if' '(' Expression ')' Statement [ 'else' Statement ]
*
*/
public Statement
parseIfStatement() throws CompileException, IOException {
this.read("if");
final Location location = this.location();
this.read("(");
final Rvalue condition = this.parseExpression();
this.read(")");
Statement thenStatement = this.parseStatement();
Statement elseStatement = this.peekRead("else") ? this.parseStatement() : null;
return new IfStatement(
location, // location
condition, // condition
thenStatement, // thenStatement
elseStatement // elseStatement
);
}
/**
*
* ForStatement :=
* 'for' '(' [ ForInit ] ';' [ Expression ] ';' [ ExpressionList ] ')' Statement
* | 'for' '(' FormalParameter ':' Expression ')' Statement
*
* ForInit :=
* Modifiers Type VariableDeclarators
* | ModifiersOpt PrimitiveType VariableDeclarators
* | Expression VariableDeclarators (1)
* | Expression { ',' Expression }
*
*
* (1) "Expression" must pose a type.
*
*/
public Statement
parseForStatement() throws CompileException, IOException {
this.read("for");
Location forLocation = this.location();
this.read("(");
BlockStatement init = null;
INIT:
if (!this.peek(";")) {
// 'for' '(' Modifiers Type VariableDeclarators
// 'for' '(' [ Modifiers ] PrimitiveType VariableDeclarators
if (this.peek("final", "@", "byte", "short", "char", "int", "long", "float", "double", "boolean") != -1) {
Modifier[] modifiers = this.parseModifiers();
Type type = this.parseType();
if (this.peek(TokenType.IDENTIFIER) && this.peekNextButOne(":")) {
// 'for' '(' [ Modifiers ] Type identifier ':' Expression ')' Statement
final String name = this.read(TokenType.IDENTIFIER);
final Location nameLocation = this.location();
this.read(":");
Rvalue expression = this.parseExpression();
this.read(")");
return new ForEachStatement(
forLocation, // location
new FormalParameter(nameLocation, modifiers, type, name), // currentElement
expression, // expression
this.parseStatement() // body
);
}
// 'for' '(' [ Modifiers ] Type VariableDeclarators
init = new LocalVariableDeclarationStatement(
this.location(), // location
this.variableModifiers(modifiers), // modifiers
type, // type
this.parseVariableDeclarators() // variableDeclarators
);
break INIT;
}
Atom a = this.parseExpressionOrType();
if (this.peek(TokenType.IDENTIFIER)) {
if (this.peekNextButOne(":")) {
// 'for' '(' Expression identifier ':' Expression ')' Statement
final String name = this.read(TokenType.IDENTIFIER);
final Location nameLocation = this.location();
this.read(":");
Rvalue expression = this.parseExpression();
this.read(")");
return new ForEachStatement(
forLocation, // location
new FormalParameter( // currentElement
nameLocation,
new Modifier[0],
a.toTypeOrCompileException(),
name
),
expression, // expression
this.parseStatement() // body
);
}
// 'for' '(' Expression VariableDeclarators
init = new LocalVariableDeclarationStatement(
this.location(), // location
new Modifier[0], // modifiers
a.toTypeOrCompileException(), // type
this.parseVariableDeclarators() // variableDeclarators
);
break INIT;
}
if (!this.peekRead(",")) {
// 'for' '(' Expression
init = new ExpressionStatement(a.toRvalueOrCompileException());
break INIT;
}
// 'for' '(' Expression { ',' Expression }
{
List l = new ArrayList();
l.add(new ExpressionStatement(a.toRvalueOrCompileException()));
do {
l.add(new ExpressionStatement(this.parseExpression()));
} while (this.peekRead(","));
Block b = new Block(a.getLocation());
b.addStatements(l);
init = b;
}
}
this.read(";");
Rvalue condition = this.peek(";") ? null : this.parseExpression();
this.read(";");
Rvalue[] update = null;
if (!this.peek(")")) update = this.parseExpressionList();
this.read(")");
return new ForStatement(
forLocation, // location
init, // init
condition, // condition
update, // update
this.parseStatement() // body
);
}
/**
*
* WhileStatement := 'while' '(' Expression ')' Statement
*
*/
public Statement
parseWhileStatement() throws CompileException, IOException {
final Location location = this.location();
this.read("while");
this.read("(");
Rvalue condition = this.parseExpression();
this.read(")");
return new WhileStatement(
location, // location
condition, // condition
this.parseStatement() // body
);
}
/**
*
* DoStatement := 'do' Statement 'while' '(' Expression ')' ';'
*
*/
public Statement
parseDoStatement() throws CompileException, IOException {
final Location location = this.location();
this.read("do");
final Statement body = this.parseStatement();
this.read("while");
this.read("(");
final Rvalue condition = this.parseExpression();
this.read(")");
this.read(";");
return new DoStatement(
location, // location
body, // body
condition // condition
);
}
/**
*
* TryStatement :=
* 'try' Block Catches [ Finally ] |
* 'try' Block Finally
*
* Catches := CatchClause { CatchClause }
*
* CatchClause := 'catch' '(' FormalParameter ')' Block
*
* Finally := 'finally' Block
*
*/
public Statement
parseTryStatement() throws CompileException, IOException {
this.read("try");
final Location location = this.location();
// '(' Resource { ';' Resource } [ ';' ] ')'
List resources = new ArrayList();
if (this.peekRead("(")) {
resources.add(this.parseResource());
RESOURCES: for (;;) {
switch (this.read(";", ")")) {
case 0:
if (this.peekRead(")")) break RESOURCES;
resources.add(this.parseResource());
break;
case 1:
break RESOURCES;
default:
throw new AssertionError();
}
}
}
final Block body = this.parseBlock();
// { CatchClause }
List ccs = new ArrayList();
while (this.peekRead("catch")) {
final Location loc = this.location();
this.read("(");
final CatchParameter catchParameter = this.parseCatchParameter();
this.read(")");
ccs.add(new CatchClause(
loc, // location
catchParameter, // catchParameter
this.parseBlock() // body
));
}
// [ 'finally' block ]
Block finallY = this.peekRead("finally") ? this.parseBlock() : null;
if (resources.isEmpty() && ccs.isEmpty() && finallY == null) {
throw this.compileException(
"\"try\" statement must have at least one resource, \"catch\" clause or \"finally\" clause"
);
}
return new TryStatement(
location, // location
resources, // resources
body, // body
ccs, // catchClauses
finallY // finallY
);
}
/**
*
* Resource :=
* Modifiers Type VariableDeclarator
* | VariableAccess
*
*/
private TryStatement.Resource
parseResource() throws CompileException, IOException {
Location loc = this.location();
Modifier[] modifiers = this.parseModifiers();
Atom a = this.parseExpressionOrType();
if (modifiers.length > 0 || this.peek(TokenType.IDENTIFIER)) {
if (Parser.hasAccessModifier(modifiers, "default")) {
throw this.compileException("Modifier \"default\" not allowed on resource");
}
// Modifiers Type VariableDeclarator
return new TryStatement.LocalVariableDeclaratorResource(
loc, // location
this.variableModifiers(modifiers), // modifiers
a.toTypeOrCompileException(), // type
this.parseVariableDeclarator() // variableDeclarator
);
}
// VariableAccess
Rvalue rv = a.toRvalueOrCompileException();
if (!(rv instanceof FieldAccess)) {
this.compileException("Rvalue " + rv.getClass().getSimpleName() + " disallowed as a resource");
}
return new TryStatement.VariableAccessResource(loc, rv);
}
/**
*
* SwitchStatement :=
* 'switch' '(' Expression ')' '{' { SwitchLabels BlockStatements } '}'
*
* SwitchLabels := SwitchLabels { SwitchLabels }
*
* SwitchLabel := 'case' Expression ':' | 'default' ':'
*
*/
public Statement
parseSwitchStatement() throws CompileException, IOException {
final Location location = this.location();
this.read("switch");
this.read("(");
final Rvalue condition = this.parseExpression();
this.read(")");
this.read("{");
List
sbsgs = new ArrayList();
while (!this.peekRead("}")) {
Location location2 = this.location();
boolean hasDefaultLabel = false;
List caseLabels = new ArrayList();
do {
if (this.peekRead("case")) {
caseLabels.add(this.parseExpression());
} else
if (this.peekRead("default")) {
if (hasDefaultLabel) throw this.compileException("Duplicate \"default\" label");
hasDefaultLabel = true;
} else {
throw this.compileException("\"case\" or \"default\" expected");
}
this.read(":");
} while (this.peek("case", "default") != -1);
SwitchStatement.SwitchBlockStatementGroup sbsg = new SwitchStatement.SwitchBlockStatementGroup(
location2, // location
caseLabels, // caseLabels
hasDefaultLabel, // hasDefaultLabel
this.parseBlockStatements() // blockStatements
);
sbsgs.add(sbsg);
}
return new SwitchStatement(
location, // location
condition, // condition
sbsgs // sbsgs
);
}
/**
*
* SynchronizedStatement :=
* 'synchronized' '(' expression ')' Block
*
*/
public Statement
parseSynchronizedStatement() throws CompileException, IOException {
final Location location = this.location();
this.read("synchronized");
this.read("(");
Rvalue expression = this.parseExpression();
this.read(")");
return new SynchronizedStatement(
location, // location
expression, // expression
this.parseBlock() // body
);
}
/**
*
* ReturnStatement := 'return' [ Expression ] ';'
*
*/
public Statement
parseReturnStatement() throws CompileException, IOException {
final Location location = this.location();
this.read("return");
Rvalue returnValue = this.peek(";") ? null : this.parseExpression();
this.read(";");
return new ReturnStatement(location, returnValue);
}
/**
*
* ThrowStatement := 'throw' Expression ';'
*
*/
public Statement
parseThrowStatement() throws CompileException, IOException {
final Location location = this.location();
this.read("throw");
final Rvalue expression = this.parseExpression();
this.read(";");
return new ThrowStatement(location, expression);
}
/**
*
* BreakStatement := 'break' [ Identifier ] ';'
*
*/
public Statement
parseBreakStatement() throws CompileException, IOException {
final Location location = this.location();
this.read("break");
String label = null;
if (this.peek(TokenType.IDENTIFIER)) label = this.read(TokenType.IDENTIFIER);
this.read(";");
return new BreakStatement(location, label);
}
/**
*
* ContinueStatement := 'continue' [ Identifier ] ';'
*
*/
public Statement
parseContinueStatement() throws CompileException, IOException {
final Location location = this.location();
this.read("continue");
String label = null;
if (this.peek(TokenType.IDENTIFIER)) label = this.read(TokenType.IDENTIFIER);
this.read(";");
return new ContinueStatement(location, label);
}
/**
*
* AssertStatement := 'assert' Expression [ ':' Expression ] ';'
*
*/
public Statement
parseAssertStatement() throws CompileException, IOException {
this.read("assert");
Location loc = this.location();
Rvalue expression1 = this.parseExpression();
Rvalue expression2 = this.peekRead(":") ? this.parseExpression() : null;
this.read(";");
return new AssertStatement(loc, expression1, expression2);
}
/**
*
* EmptyStatement := ';'
*
*/
public Statement
parseEmptyStatement() throws CompileException, IOException {
Location location = this.location();
this.read(";");
return new EmptyStatement(location);
}
/**
*
* ExpressionList := Expression { ',' Expression }
*
*/
public Rvalue[]
parseExpressionList() throws CompileException, IOException {
List l = new ArrayList();
do {
l.add(this.parseExpression());
} while (this.peekRead(","));
return (Rvalue[]) l.toArray(new Rvalue[l.size()]);
}
/**
*
* Type := (
* 'byte' | 'short' | 'char' | 'int' | 'long' |
* 'float' | 'double' | 'boolean' |
* ReferenceType
* ) { '[' ']' }
*
*/
public Type
parseType() throws CompileException, IOException {
Type res;
switch (this.peekRead(
"byte", "short", "char", "int", "long", "float", "double", "boolean" // SUPPRESS CHECKSTYLE Wrap|LineLength
)) {
case 0: res = new PrimitiveType(this.location(), Primitive.BYTE); break;
case 1: res = new PrimitiveType(this.location(), Primitive.SHORT); break;
case 2: res = new PrimitiveType(this.location(), Primitive.CHAR); break;
case 3: res = new PrimitiveType(this.location(), Primitive.INT); break;
case 4: res = new PrimitiveType(this.location(), Primitive.LONG); break;
case 5: res = new PrimitiveType(this.location(), Primitive.FLOAT); break;
case 6: res = new PrimitiveType(this.location(), Primitive.DOUBLE); break;
case 7: res = new PrimitiveType(this.location(), Primitive.BOOLEAN); break;
case -1: res = this.parseReferenceType(); break;
default: throw new AssertionError();
}
for (int i = this.parseBracketsOpt(); i > 0; --i) res = new ArrayType(res);
return res;
}
/**
*
* ReferenceType := { Annotation } QualifiedIdentifier [ TypeArguments ]
*
*/
public ReferenceType
parseReferenceType() throws CompileException, IOException {
List annotations = new ArrayList();
while (this.peek("@")) annotations.add(this.parseAnnotation());
return new ReferenceType(
this.location(),
(Annotation[]) annotations.toArray(new Annotation[annotations.size()]),
this.parseQualifiedIdentifier(),
this.parseTypeArgumentsOpt()
);
}
/**
*
* TypeParameters := '<' TypeParameter { ',' TypeParameter } '>'
*
*/
@Nullable private TypeParameter[]
parseTypeParametersOpt() throws CompileException, IOException {
if (!this.peekRead("<")) return null;
List l = new ArrayList();
l.add(this.parseTypeParameter());
while (this.read(",", ">") == 0) {
l.add(this.parseTypeParameter());
}
return (TypeParameter[]) l.toArray(new TypeParameter[l.size()]);
}
/**
*
* TypeParameter := identifier [ 'extends' ( identifier | ReferenceType { '&' ReferenceType }
*
*/
private TypeParameter
parseTypeParameter() throws CompileException, IOException {
String name = this.read(TokenType.IDENTIFIER);
if (this.peekRead("extends")) {
List bound = new ArrayList();
bound.add(this.parseReferenceType());
while (this.peekRead("&")) this.parseReferenceType();
return new TypeParameter(name, (ReferenceType[]) bound.toArray(new ReferenceType[bound.size()]));
}
return new TypeParameter(name, null);
}
/**
*
* TypeArguments := '<' [ TypeArgument { ',' TypeArgument } ] '>'
*
*
* @return {@code null} iff there are no type arguments
*/
@Nullable private TypeArgument[]
parseTypeArgumentsOpt() throws CompileException, IOException {
if (!this.peekRead("<")) return null;
if (this.peekRead(">")) return new TypeArgument[0];
List typeArguments = new ArrayList();
typeArguments.add(this.parseTypeArgument());
while (this.read(">", ",") == 1) {
typeArguments.add(this.parseTypeArgument());
}
return (TypeArgument[]) typeArguments.toArray(new TypeArgument[typeArguments.size()]);
}
/**
*
* TypeArgument :=
* ReferenceType { '[' ']' } <= The optional brackets are missing in JLS7, section 18!?
* | PrimitiveType '[' ']' { '[' ']' }
* | '?' extends ReferenceType
* | '?' super ReferenceType
*
*/
private TypeArgument
parseTypeArgument() throws CompileException, IOException {
if (this.peekRead("?")) {
return (
this.peekRead("extends") ? new Wildcard(Wildcard.BOUNDS_EXTENDS, this.parseReferenceType()) :
this.peekRead("super") ? new Wildcard(Wildcard.BOUNDS_SUPER, this.parseReferenceType()) :
new Wildcard()
);
}
Type t = this.parseType();
int i = this.parseBracketsOpt();
for (; i > 0; i--) t = new ArrayType(t);
if (!(t instanceof TypeArgument)) throw this.compileException("'" + t + "' is not a valid type argument");
return (TypeArgument) t;
}
/**
*
* ReferenceTypeList := ReferenceType { ',' ReferenceType }
*
*/
public ReferenceType[]
parseReferenceTypeList() throws CompileException, IOException {
List l = new ArrayList();
l.add(this.parseReferenceType());
while (this.peekRead(",")) {
l.add(this.parseReferenceType());
}
return (ReferenceType[]) l.toArray(new ReferenceType[l.size()]);
}
/**
*
* Expression := AssignmentExpression | LambdaExpression
*
*
* Notice that all other kinds of lambda expressions are parsed in {@link #parsePrimary()}.
*
*/
public Rvalue
parseExpression() throws CompileException, IOException {
if (this.peek(TokenType.IDENTIFIER) && this.peekNextButOne("->")) return this.parseLambdaExpression();
return this.parseAssignmentExpression().toRvalueOrCompileException();
}
/**
* Same as {@link #parseExpression()}, but types like {@code int} or {@code Map")) return this.parseLambdaExpression();
this.preferParametrizedTypes = true;
try {
return this.parseAssignmentExpression();
} finally {
this.preferParametrizedTypes = false;
}
}
private boolean preferParametrizedTypes;
/**
*
* AssignmentExpression :=
* ConditionalExpression [ AssignmentOperator AssignmentExpression ]
*
* AssignmentOperator :=
* '=' | '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' |
* '>>=' | '>>>=' | '&=' | '^=' | '|='
*
*/
public Atom
parseAssignmentExpression() throws CompileException, IOException {
Atom a = this.parseConditionalExpression();
if (this.peek("=", "+=", "-=", "*=", "/=", "&=", "|=", "^=", "%=", "<<=", ">>=", ">>>=") != -1) {
final Lvalue lhs = a.toLvalueOrCompileException();
Location location = this.location();
String operator = this.read(TokenType.OPERATOR); // An interned string!
final Rvalue rhs = this.parseAssignmentExpression().toRvalueOrCompileException();
return new Assignment(location, lhs, operator, rhs);
}
return a;
}
/**
*
* ConditionalExpression :=
* ConditionalOrExpression [ '?' Expression ':' ConditionalExpression ]
*
*/
public Atom
parseConditionalExpression() throws CompileException, IOException {
Atom a = this.parseConditionalOrExpression();
if (!this.peekRead("?")) return a;
Location location = this.location();
Rvalue lhs = a.toRvalueOrCompileException();
Rvalue mhs = this.parseExpression();
this.read(":");
Rvalue rhs = this.parseConditionalExpression().toRvalueOrCompileException();
return new ConditionalExpression(location, lhs, mhs, rhs);
}
/**
*
* ConditionalOrExpression :=
* ConditionalAndExpression { '||' ConditionalAndExpression ]
*
*/
public Atom
parseConditionalOrExpression() throws CompileException, IOException {
Atom a = this.parseConditionalAndExpression();
while (this.peekRead("||")) {
Location location = this.location();
a = new BinaryOperation(
location,
a.toRvalueOrCompileException(),
"||",
this.parseConditionalAndExpression().toRvalueOrCompileException()
);
}
return a;
}
/**
*
* ConditionalAndExpression :=
* InclusiveOrExpression { '&&' InclusiveOrExpression }
*
*/
public Atom
parseConditionalAndExpression() throws CompileException, IOException {
Atom a = this.parseInclusiveOrExpression();
while (this.peekRead("&&")) {
Location location = this.location();
a = new BinaryOperation(
location,
a.toRvalueOrCompileException(),
"&&",
this.parseInclusiveOrExpression().toRvalueOrCompileException()
);
}
return a;
}
/**
*
* InclusiveOrExpression :=
* ExclusiveOrExpression { '|' ExclusiveOrExpression }
*
*/
public Atom
parseInclusiveOrExpression() throws CompileException, IOException {
Atom a = this.parseExclusiveOrExpression();
while (this.peekRead("|")) {
Location location = this.location();
a = new BinaryOperation(
location,
a.toRvalueOrCompileException(),
"|",
this.parseExclusiveOrExpression().toRvalueOrCompileException()
);
}
return a;
}
/**
*
* ExclusiveOrExpression :=
* AndExpression { '^' AndExpression }
*
*/
public Atom
parseExclusiveOrExpression() throws CompileException, IOException {
Atom a = this.parseAndExpression();
while (this.peekRead("^")) {
Location location = this.location();
a = new BinaryOperation(
location,
a.toRvalueOrCompileException(),
"^",
this.parseAndExpression().toRvalueOrCompileException()
);
}
return a;
}
/**
*
* AndExpression :=
* EqualityExpression { '&' EqualityExpression }
*
*/
public Atom
parseAndExpression() throws CompileException, IOException {
Atom a = this.parseEqualityExpression();
while (this.peekRead("&")) {
Location location = this.location();
a = new BinaryOperation(
location,
a.toRvalueOrCompileException(),
"&",
this.parseEqualityExpression().toRvalueOrCompileException()
);
}
return a;
}
/**
*
* EqualityExpression :=
* RelationalExpression { ( '==' | '!=' ) RelationalExpression }
*
*/
public Atom
parseEqualityExpression() throws CompileException, IOException {
Atom a = this.parseRelationalExpression();
while (this.peek("==", "!=") != -1) {
a = new BinaryOperation(
this.location(), // location
a.toRvalueOrCompileException(), // lhs
this.read().value, // operator
this.parseRelationalExpression().toRvalueOrCompileException() // rhs
);
}
return a;
}
/**
*
* RelationalExpression :=
* ShiftExpression {
* 'instanceof' ReferenceType
* | '<' ShiftExpression [ { ',' TypeArgument } '>' ]
* | '<' TypeArgument [ { ',' TypeArgument } '>' ]
* | ( '>' | '<=' | '>=' ) ShiftExpression
* }
*
*/
public Atom
parseRelationalExpression() throws CompileException, IOException {
Atom a = this.parseShiftExpression();
for (;;) {
if (this.peekRead("instanceof")) {
Location location = this.location();
a = new Instanceof(
location,
a.toRvalueOrCompileException(),
this.parseType()
);
} else
if (this.peek("<", ">", "<=", ">=") != -1) {
if (this.preferParametrizedTypes && a instanceof AmbiguousName && this.peek("<") && this.peekNextButOne("?")) {
// ambiguous-name '<' '?' ...
return new ReferenceType(
this.location(),
new Annotation[0],
((AmbiguousName) a).identifiers,
this.parseTypeArgumentsOpt()
);
}
String operator = this.read().value;
Atom rhs = this.parseShiftExpression();
if (
this.preferParametrizedTypes
&& "<".equals(operator)
&& this.peek("<", ">", ",") != -1
&& a instanceof AmbiguousName
&& rhs.toType() != null
) {
final String[] identifiers = ((AmbiguousName) a).identifiers;
// '<' Type [ TypeArguments ] ( '>' | ',' )
this.parseTypeArgumentsOpt();
TypeArgument firstTypeArgument;
{
Type t = rhs.toTypeOrCompileException();
if (t instanceof ArrayType) { firstTypeArgument = (ArrayType) t; } else
if (t instanceof ReferenceType) { firstTypeArgument = (ReferenceType) t; } else
{
throw this.compileException("'" + t + "' is not a valid type argument");
}
}
List typeArguments = new ArrayList();
typeArguments.add(firstTypeArgument);
// < type-argument { ',' type-argument } '>'
while (this.read(">", ",") == 1) typeArguments.add(this.parseTypeArgument());
return new ReferenceType(
this.location(),
new Annotation[0],
identifiers,
(TypeArgument[]) typeArguments.toArray(new TypeArgument[typeArguments.size()])
);
}
a = new BinaryOperation(
this.location(), // location
a.toRvalueOrCompileException(), // lhs
operator, // operator
rhs.toRvalueOrCompileException() // rhs
);
} else {
return a;
}
}
}
/**
*
* ShiftExpression :=
* AdditiveExpression { ( '<<' | '>>' | '>>>' ) AdditiveExpression }
*
*/
public Atom
parseShiftExpression() throws CompileException, IOException {
Atom a = this.parseAdditiveExpression();
while (this.peek("<<", ">>", ">>>") != -1) {
a = new BinaryOperation(
this.location(), // location
a.toRvalueOrCompileException(), // lhs
this.read().value, // operator
this.parseAdditiveExpression().toRvalueOrCompileException() // rhs
);
}
return a;
}
/**
*
* AdditiveExpression :=
* MultiplicativeExpression { ( '+' | '-' ) MultiplicativeExpression }
*
*/
public Atom
parseAdditiveExpression() throws CompileException, IOException {
Atom a = this.parseMultiplicativeExpression();
while (this.peek("+", "-") != -1) {
a = new BinaryOperation(
this.location(), // location
a.toRvalueOrCompileException(), // lhs
this.read().value, // operator
this.parseMultiplicativeExpression().toRvalueOrCompileException() // rhs
);
}
return a;
}
/**
*
* MultiplicativeExpression :=
* UnaryExpression { ( '*' | '/' | '%' ) UnaryExpression }
*
*/
public Atom
parseMultiplicativeExpression() throws CompileException, IOException {
Atom a = this.parseUnaryExpression();
while (this.peek("*", "/", "%") != -1) {
a = new BinaryOperation(
this.location(), // location
a.toRvalueOrCompileException(), // lhs
this.read().value, // operator
this.parseUnaryExpression().toRvalueOrCompileException() // rhs
);
}
return a;
}
/**
*
* UnaryExpression :=
* { PrefixOperator } Primary { Selector } { PostfixOperator }
*
* PrefixOperator := '++' | '--' | '+' | '-' | '~' | '!'
*
* PostfixOperator := '++' | '--'
*
*/
public Atom
parseUnaryExpression() throws CompileException, IOException {
if (this.peek("++", "--") != -1) {
return new Crement(
this.location(), // location
this.read().value, // operator
this.parseUnaryExpression().toLvalueOrCompileException() // operand
);
}
if (this.peek("+", "-", "~", "!") != -1) {
return new UnaryOperation(
this.location(), // location
this.read().value, // operator
this.parseUnaryExpression().toRvalueOrCompileException() // operand
);
}
Atom a = this.parsePrimary();
while (this.peek(".", "[") != -1) {
a = this.parseSelector(a);
}
if (this.peekRead("::")) {
if (a instanceof ArrayType) {
this.read("new");
// ArrayType '::' 'new'
return new ArrayCreationReference(this.location(), (ArrayType) a);
}
TypeArgument[] typeArguments = this.parseTypeArgumentsOpt();
switch (this.peek(TokenType.KEYWORD /* 'new' */, TokenType.IDENTIFIER)) {
case 0: // keyword
// ClassType '::' [ TypeArguments ] 'new'
this.read("new");
return new ClassInstanceCreationReference(
this.location(),
a.toTypeOrCompileException(),
typeArguments
);
case 1: // identifier
// ( Rvalue | ReferenceType ) '::' [ TypeArguments ] identifier
return new MethodReference(this.location(), a, this.read(TokenType.IDENTIFIER));
default:
throw new AssertionError(this.peek());
}
}
while (this.peek("++", "--") != -1) {
a = new Crement(
this.location(), // location
a.toLvalueOrCompileException(), // operand
this.read().value // operator
);
}
return a;
}
/**
*
* Primary :=
* CastExpression | // CastExpression 15.16
* '(' Expression ')' | // ParenthesizedExpression 15.8.5
* Literal | // Literal 15.8.1
* Name | // AmbiguousName
* Name Arguments | // MethodInvocation
* Name '[]' { '[]' } | // ArrayType 10.1
* Name '[]' { '[]' } '.' 'class' | // ClassLiteral 15.8.2
* 'this' | // This 15.8.3
* 'this' Arguments | // Alternate constructor invocation 8.8.5.1
* 'super' Arguments | // Unqualified superclass constructor invocation 8.8.5.1
* 'super' '.' Identifier | // SuperclassFieldAccess 15.11.2
* 'super' '.' Identifier Arguments | // SuperclassMethodInvocation 15.12.4.9
* NewClassInstance |
* NewAnonymousClassInstance | // ClassInstanceCreationExpression 15.9
* NewArray | // ArrayCreationExpression 15.10
* NewInitializedArray | // ArrayInitializer 10.6
* PrimitiveType { '[]' } | // Type
* PrimitiveType { '[]' } '.' 'class' | // ClassLiteral 15.8.2
* 'void' '.' 'class' | // ClassLiteral 15.8.2
* MethodReference // MethodReference JLS9 15.13
*
* Name :=
* Identifier { '.' Identifier }
*
* CastExpression :=
* '(' PrimitiveType { '[]' } ')' UnaryExpression |
* '(' Expression ')' UnaryExpression
*
* NewClassInstance := 'new' ReferenceType Arguments
*
* NewAnonymousClassInstance := 'new' ReferenceType Arguments [ ClassBody ]
*
* NewArray := 'new' Type DimExprs { '[]' }
*
* NewInitializedArray := 'new' ArrayType ArrayInitializer
*
*/
public Atom
parsePrimary() throws CompileException, IOException {
if (this.peekRead("(")) {
if (
this.peek("boolean", "char", "byte", "short", "int", "long", "float", "double") != -1
&& !this.peekNextButOne(TokenType.IDENTIFIER)
) {
// '(' PrimitiveType { '[]' } ')' UnaryExpression
Type type = this.parseType();
int brackets = this.parseBracketsOpt();
this.read(")");
for (int i = 0; i < brackets; ++i) type = new ArrayType(type);
return new Cast(
this.location(), // location
type, // targetType
this.parseUnaryExpression().toRvalueOrCompileException() // value
);
}
// '(' ')' '->' LambdaBody
if (this.peekRead(")")) {
LambdaParameters parameters = new FormalLambdaParameters(new FormalParameters(this.location()));
Location loc = this.location();
this.read("->");
return new LambdaExpression(loc, parameters, this.parseLambdaBody());
}
Atom a;
if (this.peek(TokenType.IDENTIFIER) && (this.peekNextButOne(",") || this.peekNextButOne(")"))) {
// '(' Identifier { ',' Identifier } ')' -> LambdaBody
String[] names;
{
List l = new ArrayList();
l.add(this.read(TokenType.IDENTIFIER));
while (this.peekRead(",")) l.add(this.read(TokenType.IDENTIFIER));
names = (String[]) l.toArray(new String[l.size()]);
}
Location loc = this.location();
if (this.peek(")") && this.peekNextButOne("->")) {
this.read();
this.read();
return new LambdaExpression(
loc,
new InferredLambdaParameters(names),
this.parseLambdaBody()
);
}
if (names.length != 1) throw this.compileException("Lambda expected");
a = new AmbiguousName(loc, new String[] { names[0] });
} else {
a = this.parseExpressionOrType();
}
// '(' FormalParameterList ')'
if (this.peek(TokenType.IDENTIFIER)) {
FormalParameters fpl = this.parseFormalParameterListRest(a.toTypeOrCompileException());
this.read(")");
LambdaParameters parameters = new FormalLambdaParameters(fpl);
Location loc = this.location();
this.read("->");
return new LambdaExpression(loc, parameters, this.parseLambdaBody());
}
// '(' atom ')'
this.read(")");
if (
this.peekLiteral()
|| this.peek(TokenType.IDENTIFIER)
|| this.peek("(", "~", "!") != -1
|| this.peek("this", "super", "new") != -1
) {
// '(' Expression ')' UnaryExpression
return new Cast(
this.location(), // location
a.toTypeOrCompileException(), // targetType
this.parseUnaryExpression().toRvalueOrCompileException() // value
);
}
// '(' Expression ')'
return new ParenthesizedExpression(a.getLocation(), a.toRvalueOrCompileException());
}
if (this.peekLiteral()) {
// Literal
return this.parseLiteral();
}
if (this.peek(TokenType.IDENTIFIER)) {
String[] qi = this.parseQualifiedIdentifier();
if (this.peek("(")) {
// Name Arguments
return new MethodInvocation(
this.location(), // location
qi.length == 1 ? null : new AmbiguousName( // target
this.location(), // location
qi, // identifiers
qi.length - 1 // n
),
qi[qi.length - 1], // methodName
this.parseArguments() // arguments
);
}
if (this.peek("[") && this.peekNextButOne("]")) {
Type res = new ReferenceType(
this.location(), // location
new Annotation[0], // annotations
qi, // identifiers
null // typeArguments
);
int brackets = this.parseBracketsOpt();
for (int i = 0; i < brackets; ++i) res = new ArrayType(res);
if (this.peek(".") && this.peekNextButOne("class")) {
// Name '[]' { '[]' } '.' 'class'
this.read();
Location location2 = this.location();
this.read();
return new ClassLiteral(location2, res);
} else {
// Name '[]' { '[]' }
return res;
}
}
// Name
return new AmbiguousName(
this.location(), // location
qi // identifiers
);
}
if (this.peekRead("this")) {
Location location = this.location();
if (this.peek("(")) {
// 'this' Arguments (JLS7 8.8.7.1 Alternate constructor invocation)
return new AlternateConstructorInvocation(
location, // location
this.parseArguments() // arguments
);
} else
{
// 'this'
return new ThisReference(location);
}
}
if (this.peekRead("super")) {
if (this.peek("(")) {
// 'super' Arguments (JLS7 8.8.7.1 Unqualified superclass constructor invocation)
return new SuperConstructorInvocation(
this.location(), // location
(Rvalue) null, // qualification
this.parseArguments() // arguments
);
}
this.read(".");
String name = this.read(TokenType.IDENTIFIER);
if (this.peek("(")) {
// 'super' '.' Identifier Arguments Superclass method invocation
return new SuperclassMethodInvocation(
this.location(), // location
name, // methodName
this.parseArguments() // arguments
);
} else {
// 'super' '.' Identifier Superclass field access
return new SuperclassFieldAccessExpression(
this.location(), // location
(Type) null, // qualification
name // fieldName
);
}
}
// 'new'
if (this.peekRead("new")) {
Location location = this.location();
Type type = this.parseType();
if (type instanceof ArrayType) {
// 'new' ArrayType ArrayInitializer
return new NewInitializedArray(location, (ArrayType) type, this.parseArrayInitializer());
}
if (type instanceof ReferenceType && this.peek("(")) {
// 'new' ReferenceType Arguments [ ClassBody ]
Rvalue[] arguments = this.parseArguments();
if (this.peek("{")) {
// 'new' ReferenceType Arguments ClassBody
final AnonymousClassDeclaration anonymousClassDeclaration = new AnonymousClassDeclaration(
this.location(), // location
type // baseType
);
this.parseClassBody(anonymousClassDeclaration);
return new NewAnonymousClassInstance(
location, // location
(Rvalue) null, // qualification
anonymousClassDeclaration, // anonymousClassDeclaration
arguments // arguments
);
} else {
// 'new' ReferenceType Arguments
return new NewClassInstance(
location, // location
(Rvalue) null, // qualification
type, // type
arguments // arguments
);
}
}
// 'new' Type DimExprs { '[]' }
return new NewArray(
location, // location
type, // type
this.parseDimExprs(), // dimExprs
this.parseBracketsOpt() // dims
);
}
// PrimitiveType
if (this.peek("boolean", "char", "byte", "short", "int", "long", "float", "double") != -1) {
Type res = this.parseType();
int brackets = this.parseBracketsOpt();
for (int i = 0; i < brackets; ++i) res = new ArrayType(res);
if (this.peek(".") && this.peekNextButOne("class")) {
// PrimitiveType { '[]' } '.' 'class'
this.read();
Location location = this.location();
this.read();
return new ClassLiteral(location, res);
}
// PrimitiveType { '[]' }
return res;
}
// 'void'
if (this.peekRead("void")) {
if (this.peek(".") && this.peekNextButOne("class")) {
// 'void' '.' 'class'
this.read();
Location location = this.location();
this.read();
return new ClassLiteral(location, new PrimitiveType(location, Primitive.VOID));
}
throw this.compileException("\"void\" encountered in wrong context");
}
throw this.compileException("Unexpected token \"" + this.read().value + "\" in primary");
}
/**
*
* Selector :=
* '.' Identifier | // FieldAccess 15.11.1
* '.' Identifier Arguments | // MethodInvocation
* '.' '<' TypeList '>' 'super' Arguments // Superconstructor invocation (?)
* '.' '<' TypeList '>' 'super' '.' . Identifier // ???
* '.' '<' TypeList '>' 'super' '.' . Identifier Arguments // Supermethod invocation
* '.' '<' TypeList '>' Identifier Arguments // ExplicitGenericInvocation
* '.' 'this' // QualifiedThis 15.8.4
* '.' 'super' Arguments // Qualified superclass constructor invocation (JLS7 8.8.7.1)
* '.' 'super' '.' Identifier | // SuperclassFieldReference (JLS7 15.11.2)
* '.' 'super' '.' Identifier Arguments | // SuperclassMethodInvocation (JLS7 15.12.3)
* '.' 'new' Identifier Arguments [ ClassBody ] | // QualifiedClassInstanceCreationExpression 15.9
* '.' 'class'
* '[' Expression ']' // ArrayAccessExpression 15.13
*
* ExplicitGenericInvocationSuffix :=
* 'super' SuperSuffix
* | Identifier Arguments
*
*/
public Atom
parseSelector(Atom atom) throws CompileException, IOException {
if (this.peekRead(".")) {
// Parse and ignore type arguments.
this.parseTypeArgumentsOpt();
if (this.peek().type == TokenType.IDENTIFIER) {
String identifier = this.read(TokenType.IDENTIFIER);
if (this.peek("(")) {
// '.' Identifier Arguments
return new MethodInvocation(
this.location(), // location
atom.toRvalueOrCompileException(), // target
identifier, // methodName
this.parseArguments() // arguments
);
}
// '.' Identifier
return new FieldAccessExpression(
this.location(), // location
atom.toRvalueOrCompileException(), // lhs
identifier // fieldName
);
}
if (this.peekRead("this")) {
// '.' 'this'
Location location = this.location();
return new QualifiedThisReference(
location, // location
atom.toTypeOrCompileException() // qualification
);
}
if (this.peekRead("super")) {
Location location = this.location();
if (this.peek("(")) {
// '.' 'super' Arguments
// Qualified superclass constructor invocation (JLS7 8.7.1.2.2) (LHS is an Rvalue)
return new SuperConstructorInvocation(
location, // location
atom.toRvalueOrCompileException(), // qualification
this.parseArguments() // arguments
);
}
this.read(".");
String identifier = this.read(TokenType.IDENTIFIER);
if (this.peek("(")) {
// '.' 'super' '.' Identifier Arguments
// Qualified superclass method invocation (JLS7 15.12.1.1.4) (LHS is a ClassName).
// TODO: Qualified superclass method invocation
throw this.compileException("Qualified superclass method invocation NYI");
} else {
// '.' 'super' '.' Identifier
// Qualified superclass field access (JLS7 15.11.2) (LHS is an Rvalue).
return new SuperclassFieldAccessExpression(
location, // location
atom.toTypeOrCompileException(), // qualification
identifier // fieldName
);
}
}
if (this.peekRead("new")) {
// '.' 'new' Identifier Arguments [ ClassBody ]
Rvalue lhs = atom.toRvalueOrCompileException();
Location location = this.location();
String identifier = this.read(TokenType.IDENTIFIER);
Type type = new RvalueMemberType(
location, // location
lhs, // rValue
identifier // identifier
);
Rvalue[] arguments = this.parseArguments();
if (this.peek("{")) {
// '.' 'new' Identifier Arguments ClassBody (LHS is an Rvalue)
final AnonymousClassDeclaration anonymousClassDeclaration = new AnonymousClassDeclaration(
this.location(), // location
type // baseType
);
this.parseClassBody(anonymousClassDeclaration);
return new NewAnonymousClassInstance(
location, // location
lhs, // qualification
anonymousClassDeclaration, // anonymousClassDeclaration
arguments // arguments
);
} else {
// '.' 'new' Identifier Arguments (LHS is an Rvalue)
return new NewClassInstance(
location, // location
lhs, // qualification
type, // referenceType
arguments // arguments
);
}
}
if (this.peekRead("class")) {
// '.' 'class'
Location location = this.location();
return new ClassLiteral(location, atom.toTypeOrCompileException());
}
throw this.compileException("Unexpected selector '" + this.peek().value + "' after \".\"");
}
if (this.peekRead("[")) {
// '[' Expression ']'
Location location = this.location();
Rvalue index = this.parseExpression();
this.read("]");
return new ArrayAccessExpression(
location, // location
atom.toRvalueOrCompileException(), // lhs
index // index
);
}
throw this.compileException("Unexpected token '" + this.peek().value + "' in selector");
}
/**
*
* DimExprs := DimExpr { DimExpr }
*
*/
public Rvalue[]
parseDimExprs() throws CompileException, IOException {
List l = new ArrayList();
l.add(this.parseDimExpr());
while (this.peek("[") && !this.peekNextButOne("]")) {
l.add(this.parseDimExpr());
}
return (Rvalue[]) l.toArray(new Rvalue[l.size()]);
}
/**
*
* DimExpr := '[' Expression ']'
*
*/
public Rvalue
parseDimExpr() throws CompileException, IOException {
this.read("[");
Rvalue res = this.parseExpression();
this.read("]");
return res;
}
/**
*
* Arguments := '(' [ ArgumentList ] ')'
*
*/
public Rvalue[]
parseArguments() throws CompileException, IOException {
this.read("(");
if (this.peekRead(")")) return new Rvalue[0];
Rvalue[] arguments = this.parseArgumentList();
this.read(")");
return arguments;
}
/**
*
* ArgumentList := Expression { ',' Expression }
*
*/
public Rvalue[]
parseArgumentList() throws CompileException, IOException {
List l = new ArrayList();
do { l.add(this.parseExpression()); } while (this.peekRead(","));
return (Rvalue[]) l.toArray(new Rvalue[l.size()]);
}
/**
*
* Literal :=
* IntegerLiteral
* | FloatingPointLiteral
* | BooleanLiteral
* | CharacterLiteral
* | StringLiteral
* | NullLiteral
*
*/
public Rvalue
parseLiteral() throws CompileException, IOException {
Token t = this.read();
switch (t.type) {
case INTEGER_LITERAL: return new IntegerLiteral(t.getLocation(), t.value);
case FLOATING_POINT_LITERAL: return new FloatingPointLiteral(t.getLocation(), t.value);
case BOOLEAN_LITERAL: return new BooleanLiteral(t.getLocation(), t.value);
case CHARACTER_LITERAL: return new CharacterLiteral(t.getLocation(), t.value);
case STRING_LITERAL: return new StringLiteral(t.getLocation(), t.value);
case NULL_LITERAL: return new NullLiteral(t.getLocation());
default:
throw this.compileException("Literal expected");
}
}
/**
*
* LambdaExpression := LambdaParameters '->' LambdaBody
*
*/
private LambdaExpression
parseLambdaExpression() throws CompileException, IOException {
LambdaParameters parameters = this.parseLambdaParameters();
Location loc = this.location();
this.read("->");
LambdaBody body = this.parseLambdaBody();
return new LambdaExpression(loc, parameters, body);
}
/**
*
* LambdaParameters :=
* Identifier
* | '(' [ FormalParameterList ] ')'
* | '(' InferredFormalParameterList ')'
*
*/
private LambdaParameters
parseLambdaParameters() throws CompileException, IOException {
// Identifier
String identifier = this.peekRead(TokenType.IDENTIFIER);
if (identifier != null) return new IdentifierLambdaParameters(identifier);
this.read("(");
// '(' ')'
if (this.peekRead(")")) return new FormalLambdaParameters(new FormalParameters(this.location()));
// '(' Identifier { ',' Identifier } ')'
if (this.peek(TokenType.IDENTIFIER) && (this.peekNextButOne(",") || this.peekNextButOne(")"))) {
List names = new ArrayList();
names.add(this.read(TokenType.IDENTIFIER));
while (this.peekRead(",")) names.add(this.read(TokenType.IDENTIFIER));
this.read(")");
return new InferredLambdaParameters((String[]) names.toArray(new String[names.size()]));
}
// '(' FormalParameterList ')'
FormalParameters fpl = this.parseFormalParameterList();
this.read(")");
return new FormalLambdaParameters(fpl);
}
/**
*
* LambdaBody :=
*
*/
private LambdaBody
parseLambdaBody() throws CompileException, IOException {
return (
this.peek("{")
? new BlockLambdaBody(this.parseBlock())
: new ExpressionLambdaBody(this.parseExpression())
);
}
/**
*
* ExpressionStatement := Expression ';'
*
*/
public Statement
parseExpressionStatement() throws CompileException, IOException {
Rvalue rv = this.parseExpression();
this.read(";");
return new ExpressionStatement(rv);
}
/**
* @return The location of the first character of the previously read (not peeked!) token
*/
public Location
location() { return this.tokenStream.location(); }
// Token-level methods.
// Shorthand for the various "TokenStream" methods. SUPPRESS CHECKSTYLE LineLength|JavadocMethod:16
public Token peek() throws CompileException, IOException { return this.tokenStream.peek(); }
public Token read() throws CompileException, IOException { return this.tokenStream.read(); }
public boolean peek(String suspected) throws CompileException, IOException { return this.tokenStream.peek(suspected); }
public int peek(String... suspected) throws CompileException, IOException { return this.tokenStream.peek(suspected); }
public boolean peek(TokenType suspected) throws CompileException, IOException { return this.tokenStream.peek(suspected); }
public int peek(TokenType... suspected) throws CompileException, IOException { return this.tokenStream.peek(suspected); }
public Token peekNextButOne() throws CompileException, IOException { return this.tokenStream.peekNextButOne(); }
public boolean peekNextButOne(String suspected) throws CompileException, IOException { return this.tokenStream.peekNextButOne(suspected); }
public boolean peekNextButOne(TokenType suspected) throws CompileException, IOException { return this.tokenStream.peekNextButOne().type == suspected; }
public void read(String expected) throws CompileException, IOException { this.tokenStream.read(expected); }
public int read(String... expected) throws CompileException, IOException { return this.tokenStream.read(expected); }
public String read(TokenType expected) throws CompileException, IOException { return this.tokenStream.read(expected); }
public boolean peekRead(String suspected) throws CompileException, IOException { return this.tokenStream.peekRead(suspected); }
public int peekRead(String... suspected) throws CompileException, IOException { return this.tokenStream.peekRead(suspected); }
@Nullable public String peekRead(TokenType suspected) throws CompileException, IOException { return this.tokenStream.peekRead(suspected); }
private boolean
peekLiteral() throws CompileException, IOException {
return this.peek(
TokenType.INTEGER_LITERAL, TokenType.FLOATING_POINT_LITERAL, TokenType.BOOLEAN_LITERAL,
TokenType.CHARACTER_LITERAL, TokenType.STRING_LITERAL, TokenType.NULL_LITERAL
) != -1;
}
/**
* Issues a warning if the given string does not comply with the package naming conventions.
*/
private void
verifyStringIsConventionalPackageName(String s, Location loc) throws CompileException {
if (!Character.isLowerCase(s.charAt(0))) {
this.warning(
"UPN",
"Package name \"" + s + "\" does not begin with a lower-case letter (see JLS7 6.8.1)",
loc
);
return;
}
for (int i = 0; i < s.length(); ++i) {
char c = s.charAt(i);
if (!Character.isLowerCase(c) && c != '_' && c != '.') {
this.warning("PPN", "Poorly chosen package name \"" + s + "\" contains bad character '" + c + "'", loc);
return;
}
}
}
/**
* Issues a warning if the given identifier does not comply with the class and interface type naming conventions
* (JLS7 6.8.2).
*/
private void
verifyIdentifierIsConventionalClassOrInterfaceName(String id, Location loc) throws CompileException {
if (!Character.isUpperCase(id.charAt(0))) {
this.warning(
"UCOIN1",
"Class or interface name \"" + id + "\" does not begin with an upper-case letter (see JLS7 6.8.2)",
loc
);
return;
}
for (int i = 0; i < id.length(); ++i) {
char c = id.charAt(i);
if (!Character.isLetter(c) && !Character.isDigit(c)) {
this.warning("UCOIN", (
"Class or interface name \""
+ id
+ "\" contains unconventional character \""
+ c
+ "\" (see JLS7 6.8.2)"
), loc);
return;
}
}
}
/**
* Issues a warning if the given identifier does not comply with the method naming conventions (JLS7 6.8.3).
*/
private void
verifyIdentifierIsConventionalMethodName(String id, Location loc) throws CompileException {
if (!Character.isLowerCase(id.charAt(0))) {
this.warning(
"UMN1",
"Method name \"" + id + "\" does not begin with a lower-case letter (see JLS7 6.8.3)",
loc
);
return;
}
for (int i = 0; i < id.length(); ++i) {
char c = id.charAt(i);
if (!Character.isLetter(c) && !Character.isDigit(c)) {
this.warning(
"UMN",
"Method name \"" + id + "\" contains unconventional character \"" + c + "\" (see JLS7 6.8.3)",
loc
);
return;
}
}
}
/**
* Issues a warning if the given identifier does not comply with the field naming conventions (JLS7 6.8.4) and
* constant naming conventions (JLS7 6.8.5).
*/
private void
verifyIdentifierIsConventionalFieldName(String id, Location loc) throws CompileException {
// In practice, a field is not always a constant iff it is static-final. So let's
// always tolerate both field and constant names.
if (Character.isUpperCase(id.charAt(0))) {
for (int i = 0; i < id.length(); ++i) {
char c = id.charAt(i);
if (!Character.isUpperCase(c) && !Character.isDigit(c) && c != '_') {
this.warning(
"UCN",
"Constant name \"" + id + "\" contains unconventional character \"" + c + "\" (see JLS7 6.8.5)",
loc
);
return;
}
}
} else
if (Character.isLowerCase(id.charAt(0))) {
for (int i = 0; i < id.length(); ++i) {
char c = id.charAt(i);
if (!Character.isLetter(c) && !Character.isDigit(c)) {
this.warning(
"UFN",
"Field name \"" + id + "\" contains unconventional character \"" + c + "\" (see JLS7 6.8.4)",
loc
);
return;
}
}
} else {
this.warning("UFN1", (
"\""
+ id
+ "\" is neither a conventional field name (JLS7 6.8.4) nor a conventional constant name (JLS7 6.8.5)"
), loc);
}
}
/**
* Issues a warning if the given identifier does not comply with the local variable and parameter naming
* conventions (JLS7 6.8.6).
*/
private void
verifyIdentifierIsConventionalLocalVariableOrParameterName(String id, Location loc) throws CompileException {
if (!Character.isLowerCase(id.charAt(0))) {
this.warning(
"ULVN1",
"Local variable name \"" + id + "\" does not begin with a lower-case letter (see JLS7 6.8.6)",
loc
);
return;
}
for (int i = 0; i < id.length(); ++i) {
char c = id.charAt(i);
if (!Character.isLetter(c) && !Character.isDigit(c)) {
this.warning("ULVN", (
"Local variable name \""
+ id
+ "\" contains unconventional character \""
+ c
+ "\" (see JLS7 6.8.6)"
), loc);
return;
}
}
}
public void
setSourceVersion(int version) { this.sourceVersion = version; }
private int sourceVersion = -1;
/**
* By default, warnings are discarded, but an application my install a {@link WarningHandler}.
*
* Notice that there is no {@code Parser.setErrorHandler()} method, but parse errors always throw a {@link
* CompileException}. The reason being is that there is no reasonable way to recover from parse errors and
* continue parsing, so there is no need to install a custom parse error handler.
*
*
* @param warningHandler {@code null} to indicate that no warnings be issued
*/
public void
setWarningHandler(@Nullable WarningHandler warningHandler) {
this.warningHandler = warningHandler;
this.tokenStream.setWarningHandler(warningHandler);
}
// Used for elaborate warning handling.
@Nullable private WarningHandler warningHandler;
private void
warning(String handle, String message) throws CompileException {
this.warning(handle, message, this.location());
}
/**
* Issues a warning with the given message and location and returns. This is done through
* a {@link WarningHandler} that was installed through
* {@link #setWarningHandler(WarningHandler)}.
*
* The {@code handle} argument qualifies the warning and is typically used by
* the {@link WarningHandler} to suppress individual warnings.
*
* @throws CompileException The optionally installed {@link WarningHandler} decided to throw a {@link
* CompileException}
*/
private void
warning(String handle, String message, @Nullable Location location) throws CompileException {
if (this.warningHandler != null) {
this.warningHandler.handleWarning(handle, message, location);
}
}
/**
* Convenience method for throwing a {@link CompileException}.
*/
protected final CompileException
compileException(String message) { return Parser.compileException(message, this.location()); }
/**
* Convenience method for throwing a {@link CompileException}.
*/
protected static CompileException
compileException(String message, Location location) { return new CompileException(message, location); }
private static String
join(@Nullable String[] sa, String separator) {
if (sa == null) return ("(null)");
if (sa.length == 0) return ("(zero length array)");
StringBuilder sb = new StringBuilder(sa[0]);
for (int i = 1; i < sa.length; ++i) {
sb.append(separator).append(sa[i]);
}
return sb.toString();
}
private static boolean
hasAccessModifier(Modifier[] modifiers, String... keywords) {
for (String kw : keywords) {
for (Modifier m : modifiers) {
if (m instanceof AccessModifier && kw.equals(((AccessModifier) m).keyword)) return true;
}
}
return false;
}
private static boolean
hasAccessModifierOtherThan(Modifier[] modifiers, String... keywords) {
MODIFIER:
for (Modifier m : modifiers) {
if (m instanceof AccessModifier) {
for (String kw : keywords) {
if (kw.equals(((AccessModifier) m).keyword)) continue MODIFIER;
}
return true;
}
}
return false;
}
private Modifier[]
packageModifiers(Modifier[] modifiers) throws CompileException {
return this.checkModifiers(modifiers);
}
private Modifier[]
classModifiers(Modifier[] modifiers) throws CompileException {
return this.checkModifiers(
modifiers,
"public", "protected", "private", "abstract", "static", "final", "strictfp"
);
}
private Modifier[]
packageMemberClassModifiers(Modifier[] modifiers) throws CompileException {
// JLS 8 8.1.1 Class Modifiers
return this.checkModifiers(modifiers, "public", "abstract", "final", "strictfp");
}
private Modifier[]
fieldModifiers(Modifier[] modifiers) throws CompileException {
return this.checkModifiers(
modifiers,
"public", "protected", "private", "static", "final", "transient", "volatile"
);
}
private Modifier[]
methodModifiers(Modifier[] modifiers) throws CompileException {
return this.checkModifiers(
modifiers,
"public", "protected", "private", "abstract", "static", "final", "synchronized", "native", "strictfp"
);
}
private Modifier[]
variableModifiers(Modifier[] modifiers) throws CompileException {
return this.checkModifiers(modifiers, "final");
}
private Modifier[]
constructorModifiers(Modifier[] modifiers) throws CompileException {
return this.checkModifiers(modifiers, "public", "protected", "private");
}
private Modifier[]
enumConstantModifiers(Modifier[] modifiers) throws CompileException {
return this.checkModifiers(modifiers, "xxx");
}
private Modifier[]
interfaceModifiers(Modifier[] modifiers) throws CompileException {
return this.checkModifiers(modifiers, "public", "protected", "private", "abstract", "static", "strictfp");
}
private Modifier[]
packageMemberInterfaceModifiers(Modifier[] modifiers) throws CompileException {
// JLS8 9.1.1 Interface Modifiers
return this.checkModifiers(modifiers, "public", "abstract", "strictfp");
}
private Modifier[]
constantModifiers(Modifier[] modifiers) throws CompileException {
return this.checkModifiers(modifiers, "public", "static", "final");
}
private Modifier[]
interfaceMethodModifiers(Modifier[] modifiers) throws CompileException {
return this.checkModifiers(modifiers, "public", "private", "abstract", "default", "static", "strictfp");
}
private Modifier[]
annotationTypeElementModifiers(Modifier[] modifiers) throws CompileException {
return this.checkModifiers(modifiers, "public", "abstract");
}
/**
* Verifies that the modifiers are consistent.
*
* - No two annotations must have the same type.
* - No access modifier must appear more than once
* - Certain modifier combinations must not occur (e.g. {@code abstract final}).
*
-
*
*
* @return modifiers
*/
private Modifier[]
checkModifiers(Modifier[] modifiers, String... allowedKeywords) throws CompileException {
// Check for duplicate annotations is not possible at parse-time because the annotations' types must be
// *resolved* before we can check for duplicates. See "UnitCompiler.compileAnnotations()".
Set keywords = new HashSet();
for (Modifier m : modifiers) {
if (!(m instanceof Java.AccessModifier)) continue;
AccessModifier am = (Java.AccessModifier) m;
// Duplicate access modifier?
if (!keywords.add(am.keyword)) {
throw Parser.compileException("Duplication access modifier \"" + am.keyword + "\"", am.getLocation());
}
// Mutually exclusive access modifier keywords?
for (Set meams : Parser.MUTUALLY_EXCLUSIVE_ACCESS_MODIFIERS) {
Set tmp = new HashSet(keywords);
tmp.retainAll(meams);
if (tmp.size() > 1) {
String[] a = (String[]) tmp.toArray(new String[tmp.size()]);
Arrays.sort(a);
throw Parser.compileException(
"Only one of " + Parser.join(a, " ") + " is allowed",
am.getLocation()
);
}
}
}
// Disallowed access modifiers?
for (String kw : allowedKeywords) keywords.remove(kw);
if (!keywords.isEmpty()) {
String[] a = (String[]) keywords.toArray(new String[keywords.size()]);
Arrays.sort(a);
throw this.compileException(
"Access modifier(s) " + Parser.join(a, " ") + " not allowed in this context"
);
}
return modifiers;
}
@SuppressWarnings("unchecked") private static final List>
MUTUALLY_EXCLUSIVE_ACCESS_MODIFIERS = Arrays.>asList(
new HashSet(Arrays.asList("public", "protected", "private")),
new HashSet(Arrays.asList("abstract", "final"))
);
}