3139 lines
118 KiB
Java
3139 lines
118 KiB
Java
|
|
/*
|
|
* 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. The name of the author may not be used to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
* THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
package org.codehaus.janino;
|
|
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
import org.codehaus.commons.compiler.CompileException;
|
|
import org.codehaus.commons.compiler.Location;
|
|
import org.codehaus.commons.compiler.WarningHandler;
|
|
import org.codehaus.janino.Java.AlternateConstructorInvocation;
|
|
import org.codehaus.janino.Java.AmbiguousName;
|
|
import org.codehaus.janino.Java.Annotation;
|
|
import org.codehaus.janino.Java.AnonymousClassDeclaration;
|
|
import org.codehaus.janino.Java.ArrayAccessExpression;
|
|
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.BasicType;
|
|
import org.codehaus.janino.Java.BinaryOperation;
|
|
import org.codehaus.janino.Java.Block;
|
|
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.CharacterLiteral;
|
|
import org.codehaus.janino.Java.ClassDeclaration;
|
|
import org.codehaus.janino.Java.ClassLiteral;
|
|
import org.codehaus.janino.Java.CompilationUnit;
|
|
import org.codehaus.janino.Java.CompilationUnit.ImportDeclaration;
|
|
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.ElementValuePair;
|
|
import org.codehaus.janino.Java.EmptyStatement;
|
|
import org.codehaus.janino.Java.ExpressionStatement;
|
|
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.FunctionDeclarator.FormalParameter;
|
|
import org.codehaus.janino.Java.FunctionDeclarator.FormalParameters;
|
|
import org.codehaus.janino.Java.IfStatement;
|
|
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.LocalClassDeclaration;
|
|
import org.codehaus.janino.Java.LocalClassDeclarationStatement;
|
|
import org.codehaus.janino.Java.LocalVariableDeclarationStatement;
|
|
import org.codehaus.janino.Java.Lvalue;
|
|
import org.codehaus.janino.Java.MemberClassDeclaration;
|
|
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.Modifiers;
|
|
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.NullLiteral;
|
|
import org.codehaus.janino.Java.PackageDeclaration;
|
|
import org.codehaus.janino.Java.PackageMemberClassDeclaration;
|
|
import org.codehaus.janino.Java.PackageMemberInterfaceDeclaration;
|
|
import org.codehaus.janino.Java.PackageMemberTypeDeclaration;
|
|
import org.codehaus.janino.Java.ParenthesizedExpression;
|
|
import org.codehaus.janino.Java.QualifiedThisReference;
|
|
import org.codehaus.janino.Java.ReferenceType;
|
|
import org.codehaus.janino.Java.ReturnStatement;
|
|
import org.codehaus.janino.Java.Rvalue;
|
|
import org.codehaus.janino.Java.RvalueMemberType;
|
|
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.VariableDeclarator;
|
|
import org.codehaus.janino.Java.WhileStatement;
|
|
import org.codehaus.janino.Java.Wildcard;
|
|
import org.codehaus.janino.Scanner.Token;
|
|
import org.codehaus.janino.util.enumerator.Enumerator;
|
|
|
|
/**
|
|
* A parser for the Java™ programming language.
|
|
* <p>
|
|
* 'JLS7' refers to the <a href="http://docs.oracle.com/javase/specs/">Java Language Specification, Java SE 7
|
|
* Edition</a>.
|
|
*/
|
|
@SuppressWarnings({ "rawtypes", "unchecked" }) public
|
|
class Parser {
|
|
private final Scanner scanner;
|
|
|
|
public
|
|
Parser(Scanner scanner) { this.scanner = scanner; }
|
|
|
|
/** @return The scanner that produces the tokens for this parser. */
|
|
public Scanner
|
|
getScanner() { return this.scanner; }
|
|
|
|
/**
|
|
* <pre>
|
|
* CompilationUnit := [ PackageDeclaration ]
|
|
* { ImportDeclaration }
|
|
* { TypeDeclaration }
|
|
* </pre>
|
|
*/
|
|
public CompilationUnit
|
|
parseCompilationUnit() throws CompileException, IOException {
|
|
CompilationUnit compilationUnit = new CompilationUnit(this.location().getFileName());
|
|
|
|
if (this.peek("package")) {
|
|
compilationUnit.setPackageDeclaration(this.parsePackageDeclaration());
|
|
}
|
|
|
|
while (this.peek("import")) {
|
|
compilationUnit.addImportDeclaration(this.parseImportDeclaration());
|
|
}
|
|
|
|
while (!this.peekEof()) {
|
|
if (this.peekRead(";")) continue;
|
|
|
|
compilationUnit.addPackageMemberTypeDeclaration(this.parsePackageMemberTypeDeclaration());
|
|
}
|
|
|
|
return compilationUnit;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* PackageDeclaration := 'package' QualifiedIdentifier ';'
|
|
* </pre>
|
|
*/
|
|
public PackageDeclaration
|
|
parsePackageDeclaration() throws CompileException, IOException {
|
|
this.read("package");
|
|
Location loc = this.location();
|
|
String packageName = Parser.join(this.parseQualifiedIdentifier(), ".");
|
|
this.read(";");
|
|
this.verifyStringIsConventionalPackageName(packageName, loc);
|
|
return new PackageDeclaration(loc, packageName);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ImportDeclaration := 'import' ImportDeclarationBody ';'
|
|
* </pre>
|
|
*/
|
|
public CompilationUnit.ImportDeclaration
|
|
parseImportDeclaration() throws CompileException, IOException {
|
|
this.read("import");
|
|
CompilationUnit.ImportDeclaration importDeclaration = this.parseImportDeclarationBody();
|
|
this.read(";");
|
|
return importDeclaration;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ImportDeclarationBody := [ 'static' ] Identifier { '.' Identifier } [ '.' '*' ]
|
|
* </pre>
|
|
*/
|
|
public CompilationUnit.ImportDeclaration
|
|
parseImportDeclarationBody() throws CompileException, IOException {
|
|
final Location loc = this.location();
|
|
|
|
boolean isStatic = this.peekRead("static");
|
|
|
|
List<String> l = new ArrayList();
|
|
l.add(this.readIdentifier());
|
|
for (;;) {
|
|
if (!this.peek(".")) {
|
|
String[] identifiers = (String[]) l.toArray(new String[l.size()]);
|
|
return (
|
|
isStatic
|
|
? (ImportDeclaration) new CompilationUnit.SingleStaticImportDeclaration(loc, identifiers)
|
|
: (ImportDeclaration) new CompilationUnit.SingleTypeImportDeclaration(loc, identifiers)
|
|
);
|
|
}
|
|
this.read(".");
|
|
if (this.peekRead("*")) {
|
|
String[] identifiers = (String[]) l.toArray(new String[l.size()]);
|
|
return (
|
|
isStatic
|
|
? (ImportDeclaration) new CompilationUnit.StaticImportOnDemandDeclaration(loc, identifiers)
|
|
: (ImportDeclaration) new CompilationUnit.TypeImportOnDemandDeclaration(loc, identifiers)
|
|
);
|
|
}
|
|
l.add(this.readIdentifier());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* QualifiedIdentifier := Identifier { '.' Identifier }
|
|
* </pre>
|
|
*/
|
|
public String[]
|
|
parseQualifiedIdentifier() throws CompileException, IOException {
|
|
List<String> l = new ArrayList();
|
|
l.add(this.readIdentifier());
|
|
while (this.peek(".") && this.peekNextButOne().type == Token.IDENTIFIER) {
|
|
this.read();
|
|
l.add(this.readIdentifier());
|
|
}
|
|
return (String[]) l.toArray(new String[l.size()]);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* PackageMemberTypeDeclaration :=
|
|
* ModifiersOpt 'class' ClassDeclarationRest |
|
|
* ModifiersOpt 'interface' InterfaceDeclarationRest
|
|
* </pre>
|
|
*/
|
|
public PackageMemberTypeDeclaration
|
|
parsePackageMemberTypeDeclaration() throws CompileException, IOException {
|
|
String optionalDocComment = this.scanner.doc();
|
|
|
|
Modifiers modifiers = this.parseModifiers();
|
|
|
|
switch (this.read(new String[] { "class", "interface" })) {
|
|
case 0:
|
|
if (optionalDocComment == null) this.warning("CDCM", "Class doc comment missing", this.location());
|
|
return (PackageMemberClassDeclaration) this.parseClassDeclarationRest(
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers, // modifiers
|
|
ClassDeclarationContext.COMPILATION_UNIT // context
|
|
);
|
|
case 1:
|
|
if (optionalDocComment == null) this.warning("IDCM", "Interface doc comment missing", this.location());
|
|
return (PackageMemberInterfaceDeclaration) this.parseInterfaceDeclarationRest(
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers, // modifiers
|
|
InterfaceDeclarationContext.COMPILATION_UNIT // context
|
|
);
|
|
default:
|
|
throw new IllegalStateException();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ModifiersAndAnnotations := { 'public' | 'protected' | 'private' | 'static' | 'abstract' | 'final' | 'native'
|
|
* | 'synchronized' | 'transient' | 'volatile' | 'strictfp' | Annotation }
|
|
* </pre>
|
|
*/
|
|
public Java.Modifiers
|
|
parseModifiers() throws CompileException, IOException {
|
|
short mod = 0;
|
|
List<Java.Annotation> as = new ArrayList();
|
|
for (;;) {
|
|
if (this.peek("@")) {
|
|
as.add(this.parseAnnotation());
|
|
continue;
|
|
}
|
|
|
|
int idx = this.peekRead(Parser.MODIFIER_NAMES);
|
|
if (idx == -1) break;
|
|
String kw = Parser.MODIFIER_NAMES[idx];
|
|
short x = Parser.MODIFIER_CODES[idx];
|
|
|
|
if ((mod & x) != 0) throw this.compileException("Duplicate modifier \"" + kw + "\"");
|
|
for (short m : Parser.MUTUALLY_EXCLUSIVE_MODIFIER_CODES) {
|
|
if ((x & m) != 0 && (mod & m) != 0) {
|
|
throw this.compileException("Only one of '" + Mod.shortToString(m) + "' allowed");
|
|
}
|
|
}
|
|
mod |= x;
|
|
}
|
|
|
|
return new Modifiers(mod, (Annotation[]) as.toArray(new Java.Annotation[as.size()]));
|
|
}
|
|
private static final String[] MODIFIER_NAMES = {
|
|
"public", "protected", "private", "static", "abstract", "final", "native", "synchronized",
|
|
"transient", "volatile", "strictfp"
|
|
};
|
|
private static final short[] MODIFIER_CODES = {
|
|
Mod.PUBLIC, Mod.PROTECTED, Mod.PRIVATE, Mod.STATIC, Mod.ABSTRACT, Mod.FINAL, Mod.NATIVE, Mod.SYNCHRONIZED,
|
|
Mod.TRANSIENT, Mod.VOLATILE, Mod.STRICTFP
|
|
};
|
|
private static final short[] MUTUALLY_EXCLUSIVE_MODIFIER_CODES = {
|
|
Mod.PUBLIC | Mod.PROTECTED | Mod.PRIVATE,
|
|
Mod.ABSTRACT | Mod.FINAL,
|
|
};
|
|
|
|
/**
|
|
* <pre>
|
|
* 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 } ]
|
|
* </pre>
|
|
*/
|
|
private Java.Annotation
|
|
parseAnnotation() throws CompileException, IOException {
|
|
this.read("@");
|
|
ReferenceType type = this.parseReferenceType();
|
|
|
|
if (!this.peekRead("(")) return new Java.MarkerAnnotation(type);
|
|
|
|
if (this.peekIdentifier() == null || !this.peekNextButOne("=")) {
|
|
Java.ElementValue elementValue = this.parseElementValue();
|
|
this.read(")");
|
|
return new Java.SingleElementAnnotation(type, elementValue);
|
|
}
|
|
|
|
List<Java.ElementValuePair> evps = new ArrayList();
|
|
while (!this.peekRead(")")) evps.add(this.parseElementValuePair());
|
|
|
|
return new Java.NormalAnnotation(
|
|
type,
|
|
(ElementValuePair[]) evps.toArray(new Java.ElementValuePair[evps.size()])
|
|
);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ElementValuePair := Identifier '=' ElementValue
|
|
* </pre>
|
|
*/
|
|
private Java.ElementValuePair
|
|
parseElementValuePair() throws CompileException, IOException {
|
|
String identifier = this.readIdentifier();
|
|
this.read("=");
|
|
return new Java.ElementValuePair(identifier, this.parseElementValue());
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ElementValue :=
|
|
* ConditionalExpression
|
|
* | Annotation
|
|
* | ElementValueArrayInitializer
|
|
* </pre>
|
|
*/
|
|
private Java.ElementValue
|
|
parseElementValue() throws CompileException, IOException {
|
|
if (this.peek("@")) return this.parseAnnotation();
|
|
if (this.peek("{")) return this.parseElementValueArrayInitializer();
|
|
return this.parseConditionalAndExpression().toRvalueOrCompileException();
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ElementValueArrayInitializer := '{' { ElementValue | ',' } '}'
|
|
* </pre>
|
|
*/
|
|
private ElementValue
|
|
parseElementValueArrayInitializer() throws CompileException, IOException {
|
|
this.read("{");
|
|
List<Java.ElementValue> evs = new ArrayList();
|
|
while (!this.peekRead("}")) {
|
|
if (this.peekRead(",")) continue;
|
|
evs.add(this.parseElementValue());
|
|
}
|
|
return new Java.ElementValueArrayInitializer((ElementValue[]) evs.toArray(new Java.ElementValue[evs.size()]));
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ClassDeclarationRest :=
|
|
* Identifier [ typeParameters ]
|
|
* [ 'extends' ReferenceType ]
|
|
* [ 'implements' ReferenceTypeList ]
|
|
* ClassBody
|
|
* </pre>
|
|
*/
|
|
public NamedClassDeclaration
|
|
parseClassDeclarationRest(
|
|
String optionalDocComment,
|
|
Modifiers modifiers,
|
|
ClassDeclarationContext context
|
|
) throws CompileException, IOException {
|
|
Location location = this.location();
|
|
String className = this.readIdentifier();
|
|
this.verifyIdentifierIsConventionalClassOrInterfaceName(className, location);
|
|
|
|
TypeParameter[] optionalTypeParameters = this.parseTypeParametersOpt();
|
|
|
|
ReferenceType optionalExtendedType = null;
|
|
if (this.peekRead("extends")) {
|
|
optionalExtendedType = 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
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers, // modifiers
|
|
className, // name
|
|
optionalTypeParameters, // optionalTypeParameters
|
|
optionalExtendedType, // optinalExtendedType
|
|
implementedTypes // implementedTypes
|
|
);
|
|
} else
|
|
if (context == ClassDeclarationContext.TYPE_DECLARATION) {
|
|
namedClassDeclaration = new MemberClassDeclaration(
|
|
location, // location
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers, // modifiers
|
|
className, // name
|
|
optionalTypeParameters, // optionalTypeParameters
|
|
optionalExtendedType, // optionalExtendedType
|
|
implementedTypes // implementedTypes
|
|
);
|
|
} else
|
|
if (context == ClassDeclarationContext.BLOCK) {
|
|
namedClassDeclaration = new LocalClassDeclaration(
|
|
location, // location
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers, // modifiers
|
|
className, // name
|
|
optionalTypeParameters, // optionalTypeParameters
|
|
optionalExtendedType, // optionalExtendedType
|
|
implementedTypes // implementedTypes
|
|
);
|
|
} else
|
|
{
|
|
throw new JaninoRuntimeException("SNO: Class declaration in unexpected context " + context);
|
|
}
|
|
|
|
this.parseClassBody(namedClassDeclaration);
|
|
return namedClassDeclaration;
|
|
}
|
|
|
|
/** Enumerator for the kinds of context where a class declaration can occur. */
|
|
public static final
|
|
class ClassDeclarationContext extends Enumerator {
|
|
|
|
/** Enumerator for the kinds of context where a class declaration can occur. */
|
|
/** The class declaration appears inside a 'block'. */
|
|
/** The class declaration appears inside a 'block'. */
|
|
public static final ClassDeclarationContext BLOCK = new ClassDeclarationContext("block");
|
|
|
|
/** The class declaration appears (directly) inside a type declaration. */
|
|
public static final ClassDeclarationContext TYPE_DECLARATION = new ClassDeclarationContext("type_declaration");
|
|
|
|
/** The class declaration appears on the top level. */
|
|
public static final ClassDeclarationContext COMPILATION_UNIT = new ClassDeclarationContext("compilation_unit");
|
|
|
|
private ClassDeclarationContext(String name) { super(name); }
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ClassBody := '{' { ClassBodyDeclaration } '}'
|
|
* </pre>
|
|
*/
|
|
public void
|
|
parseClassBody(ClassDeclaration classDeclaration) throws CompileException, IOException {
|
|
this.read("{");
|
|
|
|
for (;;) {
|
|
if (this.peekRead("}")) return;
|
|
|
|
this.parseClassBodyDeclaration(classDeclaration);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ClassBodyDeclaration :=
|
|
* ';' |
|
|
* ModifiersOpt (
|
|
* Block | // Instance (JLS7 8.6) or static initializer (JLS7 8.7)
|
|
* 'void' Identifier MethodDeclarationRest |
|
|
* 'class' ClassDeclarationRest |
|
|
* 'interface' InterfaceDeclarationRest |
|
|
* ConstructorDeclarator |
|
|
* Type Identifier (
|
|
* MethodDeclarationRest |
|
|
* FieldDeclarationRest ';'
|
|
* )
|
|
* )
|
|
*
|
|
* </pre>
|
|
*/
|
|
public void
|
|
parseClassBodyDeclaration(ClassDeclaration classDeclaration) throws CompileException, IOException {
|
|
if (this.peekRead(";")) return;
|
|
|
|
String optionalDocComment = this.scanner.doc();
|
|
Modifiers modifiers = this.parseModifiers();
|
|
|
|
// Initializer?
|
|
if (this.peek("{")) {
|
|
if ((modifiers.flags & ~Mod.STATIC) != 0) {
|
|
throw this.compileException("Only modifier \"static\" allowed on initializer");
|
|
}
|
|
|
|
Initializer initializer = new Initializer(
|
|
this.location(), // location
|
|
Mod.isStatic(modifiers.flags), // statiC
|
|
this.parseBlock() // block
|
|
);
|
|
|
|
classDeclaration.addInitializer(initializer);
|
|
return;
|
|
}
|
|
|
|
// "void" method declaration.
|
|
if (this.peekRead("void")) {
|
|
Location location = this.location();
|
|
if (optionalDocComment == null) this.warning("MDCM", "Method doc comment missing", location);
|
|
String name = this.readIdentifier();
|
|
classDeclaration.addDeclaredMethod(this.parseMethodDeclarationRest(
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers, // modifiers
|
|
new BasicType(location, BasicType.VOID), // type
|
|
name // name
|
|
));
|
|
return;
|
|
}
|
|
|
|
// Member class.
|
|
if (this.peekRead("class")) {
|
|
if (optionalDocComment == null) this.warning("MCDCM", "Member class doc comment missing", this.location());
|
|
classDeclaration.addMemberTypeDeclaration((MemberTypeDeclaration) this.parseClassDeclarationRest(
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers, // modifiers
|
|
ClassDeclarationContext.TYPE_DECLARATION // context
|
|
));
|
|
return;
|
|
}
|
|
|
|
// Member interface.
|
|
if (this.peekRead("interface")) {
|
|
if (optionalDocComment == null) {
|
|
this.warning("MIDCM", "Member interface doc comment missing", this.location());
|
|
}
|
|
classDeclaration.addMemberTypeDeclaration((MemberTypeDeclaration) this.parseInterfaceDeclarationRest(
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers.add(Mod.STATIC), // modifiers
|
|
InterfaceDeclarationContext.NAMED_TYPE_DECLARATION // context
|
|
));
|
|
return;
|
|
}
|
|
|
|
// Constructor.
|
|
if (
|
|
classDeclaration instanceof NamedClassDeclaration
|
|
&& this.peek().value.equals(((NamedClassDeclaration) classDeclaration).getName())
|
|
&& this.peekNextButOne("(")
|
|
) {
|
|
if (optionalDocComment == null) this.warning("CDCM", "Constructor doc comment missing", this.location());
|
|
classDeclaration.addConstructor(this.parseConstructorDeclarator(
|
|
optionalDocComment, // declaringClass
|
|
modifiers // modifiers
|
|
));
|
|
return;
|
|
}
|
|
|
|
// Member method or field.
|
|
Type memberType = this.parseType();
|
|
Location location = this.location();
|
|
String memberName = this.readIdentifier();
|
|
|
|
// Method declarator.
|
|
if (this.peek("(")) {
|
|
if (optionalDocComment == null) this.warning("MDCM", "Method doc comment missing", this.location());
|
|
classDeclaration.addDeclaredMethod(this.parseMethodDeclarationRest(
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers, // modifiers
|
|
memberType, // type
|
|
memberName // name
|
|
));
|
|
return;
|
|
}
|
|
|
|
// Field declarator.
|
|
if (optionalDocComment == null) this.warning("FDCM", "Field doc comment missing", this.location());
|
|
FieldDeclaration fd = new FieldDeclaration(
|
|
location, // location
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers, // modifiers
|
|
memberType, // type
|
|
this.parseFieldDeclarationRest(memberName) // variableDeclarators
|
|
);
|
|
this.read(";");
|
|
classDeclaration.addFieldDeclaration(fd);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* InterfaceDeclarationRest :=
|
|
* Identifier [ typeParameters ]
|
|
* [ 'extends' ReferenceTypeList ]
|
|
* InterfaceBody
|
|
* </pre>
|
|
*/
|
|
public InterfaceDeclaration
|
|
parseInterfaceDeclarationRest(
|
|
String optionalDocComment,
|
|
Modifiers modifiers,
|
|
InterfaceDeclarationContext context
|
|
) throws CompileException, IOException {
|
|
Location location = this.location();
|
|
String interfaceName = this.readIdentifier();
|
|
this.verifyIdentifierIsConventionalClassOrInterfaceName(interfaceName, location);
|
|
|
|
TypeParameter[] optionalTypeParameters = this.parseTypeParametersOpt();
|
|
|
|
ReferenceType[] extendedTypes = new ReferenceType[0];
|
|
if (this.peekRead("extends")) {
|
|
extendedTypes = this.parseReferenceTypeList();
|
|
}
|
|
|
|
InterfaceDeclaration interfaceDeclaration;
|
|
if (context == InterfaceDeclarationContext.COMPILATION_UNIT) {
|
|
interfaceDeclaration = new PackageMemberInterfaceDeclaration(
|
|
location, // location
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers, // modifiers
|
|
interfaceName, // name
|
|
optionalTypeParameters, // optionalTypeParameters
|
|
extendedTypes // extendedTypes
|
|
);
|
|
} else
|
|
if (context == InterfaceDeclarationContext.NAMED_TYPE_DECLARATION) {
|
|
interfaceDeclaration = new MemberInterfaceDeclaration(
|
|
location, // location
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers, // modifiers
|
|
interfaceName, // name
|
|
optionalTypeParameters, // optionalTypeParameters
|
|
extendedTypes // extendedTypes
|
|
);
|
|
} else
|
|
{
|
|
throw new JaninoRuntimeException("SNO: Interface declaration in unexpected context " + context);
|
|
}
|
|
|
|
this.parseInterfaceBody(interfaceDeclaration);
|
|
return interfaceDeclaration;
|
|
}
|
|
|
|
/** Enumerator for the kinds of context where an interface declaration can occur. */
|
|
public static final
|
|
class InterfaceDeclarationContext extends Enumerator {
|
|
|
|
/** The interface declaration appears (directly) inside a 'named type declaration'. */
|
|
public static final InterfaceDeclarationContext
|
|
NAMED_TYPE_DECLARATION = new InterfaceDeclarationContext("named_type_declaration");
|
|
|
|
/** The interface declaration appears at the top level. */
|
|
public static final InterfaceDeclarationContext
|
|
COMPILATION_UNIT = new InterfaceDeclarationContext("compilation_unit");
|
|
|
|
private InterfaceDeclarationContext(String name) { super(name); }
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* InterfaceBody := '{' {
|
|
* ';' |
|
|
* ModifiersOpt (
|
|
* 'void' Identifier MethodDeclarationRest |
|
|
* 'class' ClassDeclarationRest |
|
|
* 'interface' InterfaceDeclarationRest |
|
|
* Type Identifier (
|
|
* MethodDeclarationRest |
|
|
* FieldDeclarationRest
|
|
* )
|
|
* )
|
|
* } '}'
|
|
* </pre>
|
|
*/
|
|
public void
|
|
parseInterfaceBody(InterfaceDeclaration interfaceDeclaration) throws CompileException, IOException {
|
|
this.read("{");
|
|
|
|
while (!this.peekRead("}")) {
|
|
|
|
if (this.peekRead(";")) continue;
|
|
|
|
String optionalDocComment = this.scanner.doc();
|
|
Modifiers modifiers = this.parseModifiers();
|
|
|
|
// "void" method declaration.
|
|
if (this.peekRead("void")) {
|
|
if (optionalDocComment == null) this.warning("MDCM", "Method doc comment missing", this.location());
|
|
Location location = this.location();
|
|
String name = this.readIdentifier();
|
|
interfaceDeclaration.addDeclaredMethod(this.parseMethodDeclarationRest(
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers.add(Mod.ABSTRACT | Mod.PUBLIC), // modifiers
|
|
new BasicType(location, BasicType.VOID), // type
|
|
name // name
|
|
));
|
|
} else
|
|
|
|
// Member class.
|
|
if (this.peekRead("class")) {
|
|
if (optionalDocComment == null) {
|
|
this.warning("MCDCM", "Member class doc comment missing", this.location());
|
|
}
|
|
interfaceDeclaration.addMemberTypeDeclaration(
|
|
(MemberTypeDeclaration) this.parseClassDeclarationRest(
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers.add(Mod.STATIC | Mod.PUBLIC), // ModifiersAndAnnotations
|
|
ClassDeclarationContext.TYPE_DECLARATION // context
|
|
)
|
|
);
|
|
} else
|
|
|
|
// Member interface.
|
|
if (this.peekRead("interface")) {
|
|
if (optionalDocComment == null) {
|
|
this.warning("MIDCM", "Member interface doc comment missing", this.location());
|
|
}
|
|
interfaceDeclaration.addMemberTypeDeclaration(
|
|
(MemberTypeDeclaration) this.parseInterfaceDeclarationRest(
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers.add(Mod.STATIC | Mod.PUBLIC), // ModifiersAndAnnotations
|
|
InterfaceDeclarationContext.NAMED_TYPE_DECLARATION // context
|
|
)
|
|
);
|
|
} else
|
|
|
|
// Member method or field.
|
|
{
|
|
this.parseTypeArgumentsOpt();
|
|
|
|
Type memberType = this.parseType();
|
|
String memberName = this.readIdentifier();
|
|
Location location = this.location();
|
|
|
|
// Method declarator.
|
|
if (this.peek("(")) {
|
|
if (optionalDocComment == null) this.warning("MDCM", "Method doc comment missing", this.location());
|
|
interfaceDeclaration.addDeclaredMethod(this.parseMethodDeclarationRest(
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers.add(Mod.ABSTRACT | Mod.PUBLIC), // modifiers
|
|
memberType, // type
|
|
memberName // name
|
|
));
|
|
} else
|
|
|
|
// Field declarator.
|
|
{
|
|
if (optionalDocComment == null) this.warning("FDCM", "Field doc comment missing", this.location());
|
|
FieldDeclaration fd = new FieldDeclaration(
|
|
location, // location
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers.add(Mod.PUBLIC | Mod.STATIC | Mod.FINAL), // modifiers
|
|
memberType, // type
|
|
this.parseFieldDeclarationRest(memberName) // variableDeclarators
|
|
);
|
|
interfaceDeclaration.addConstantDeclaration(fd);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ConstructorDeclarator :=
|
|
* Identifier
|
|
* FormalParameters
|
|
* [ 'throws' ReferenceTypeList ]
|
|
* '{'
|
|
* [ 'this' Arguments ';' | 'super' Arguments ';' | Primary '.' 'super' Arguments ';' ]
|
|
* BlockStatements
|
|
* '}'
|
|
* </pre>
|
|
*/
|
|
public ConstructorDeclarator
|
|
parseConstructorDeclarator(String optionalDocComment, Modifiers modifiers)
|
|
throws CompileException, IOException {
|
|
this.readIdentifier(); // 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 optionalConstructorInvocation = null;
|
|
List<BlockStatement> statements = new ArrayList();
|
|
if (
|
|
this.peek(new String[] {
|
|
"this", "super", "new", "void",
|
|
"byte", "char", "short", "int", "long", "float", "double", "boolean",
|
|
}) != -1
|
|
|| this.peekLiteral()
|
|
|| this.peekIdentifier() != null
|
|
) {
|
|
Atom a = this.parseExpression();
|
|
if (a instanceof ConstructorInvocation) {
|
|
this.read(";");
|
|
optionalConstructorInvocation = (ConstructorInvocation) a;
|
|
} else {
|
|
Statement s;
|
|
if (this.peekIdentifier() != null) {
|
|
Type variableType = a.toTypeOrCompileException();
|
|
s = new LocalVariableDeclarationStatement(
|
|
a.getLocation(), // location
|
|
new Java.Modifiers(Mod.NONE), // 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
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers, // modifiers
|
|
formalParameters, // formalParameters
|
|
thrownExceptions, // thrownExceptions
|
|
optionalConstructorInvocation, // optionalConstructorInvocationStatement
|
|
statements // statements
|
|
);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* MethodDeclarationRest :=
|
|
* FormalParameters
|
|
* { '[' ']' }
|
|
* [ 'throws' ReferenceTypeList ]
|
|
* ( ';' | MethodBody )
|
|
* </pre>
|
|
*/
|
|
public MethodDeclarator
|
|
parseMethodDeclarationRest(
|
|
String optionalDocComment,
|
|
Modifiers modifiers,
|
|
Type type,
|
|
String name
|
|
) throws CompileException, IOException {
|
|
Location location = this.location();
|
|
|
|
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];
|
|
}
|
|
|
|
List<BlockStatement> optionalStatements;
|
|
if (this.peekRead(";")) {
|
|
if (!Mod.isAbstract(modifiers.flags) && !Mod.isNative(modifiers.flags)) {
|
|
throw this.compileException("Non-abstract, non-native method must have a body");
|
|
}
|
|
optionalStatements = null;
|
|
} else {
|
|
if (Mod.isAbstract(modifiers.flags) || Mod.isNative(modifiers.flags)) {
|
|
throw this.compileException("Abstract or native method must not have a body");
|
|
}
|
|
this.read("{");
|
|
optionalStatements = this.parseBlockStatements();
|
|
this.read("}");
|
|
}
|
|
return new MethodDeclarator(
|
|
location, // location
|
|
optionalDocComment, // optionalDocComment
|
|
modifiers, // modifiers
|
|
type, // type
|
|
name, // name
|
|
formalParameters, // formalParameters
|
|
thrownExceptions, // thrownExceptions
|
|
optionalStatements // optionalStatements
|
|
);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* VariableInitializer :=
|
|
* ArrayInitializer |
|
|
* Expression
|
|
* </pre>
|
|
*/
|
|
public ArrayInitializerOrRvalue
|
|
parseVariableInitializer() throws CompileException, IOException {
|
|
if (this.peek("{")) {
|
|
return this.parseArrayInitializer();
|
|
} else
|
|
{
|
|
return this.parseExpression().toRvalueOrCompileException();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ArrayInitializer :=
|
|
* '{' [ VariableInitializer { ',' VariableInitializer } [ ',' ] '}'
|
|
* </pre>
|
|
*/
|
|
public ArrayInitializer
|
|
parseArrayInitializer() throws CompileException, IOException {
|
|
final Location location = this.location();
|
|
this.read("{");
|
|
List<ArrayInitializerOrRvalue> 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()])
|
|
);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* FormalParameters := '(' [ FormalParameter { ',' FormalParameter } ] ')'
|
|
* </pre>
|
|
*/
|
|
public FormalParameters
|
|
parseFormalParameters() throws CompileException, IOException {
|
|
this.read("(");
|
|
if (this.peekRead(")")) return new FormalParameters();
|
|
|
|
List<FormalParameter> l = new ArrayList();
|
|
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.read(new String[] { ",", ")" }) == 0);
|
|
return new FormalParameters(
|
|
this.location(), // location
|
|
(FormalParameter[]) l.toArray(new FormalParameter[l.size()]), // parameters
|
|
hasEllipsis[0] // variableArity
|
|
);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* FormalParameter := [ 'final' ] Type [ '.' '.' '.' ] Identifier BracketsOpt
|
|
* </pre>
|
|
*/
|
|
public FormalParameter
|
|
parseFormalParameter(boolean[] hasEllipsis) throws CompileException, IOException {
|
|
final boolean finaL = this.peekRead("final");
|
|
|
|
Type type = this.parseType(); // SUPPRESS CHECKSTYLE UsageDistance
|
|
|
|
if (this.peekRead(".")) {
|
|
this.read(".");
|
|
this.read(".");
|
|
hasEllipsis[0] = true;
|
|
}
|
|
Location location = this.location();
|
|
String name = this.readIdentifier();
|
|
this.verifyIdentifierIsConventionalLocalVariableOrParameterName(name, location);
|
|
|
|
for (int i = this.parseBracketsOpt(); i > 0; --i) type = new ArrayType(type);
|
|
return new FormalParameter(location, finaL, type, name);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* BracketsOpt := { '[' ']' }
|
|
* </pre>
|
|
*/
|
|
int
|
|
parseBracketsOpt() throws CompileException, IOException {
|
|
int res = 0;
|
|
while (this.peek("[") && this.peekNextButOne("]")) {
|
|
this.read();
|
|
this.read();
|
|
++res;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* MethodBody := Block
|
|
* </pre>
|
|
*/
|
|
public Block
|
|
parseMethodBody() throws CompileException, IOException { return this.parseBlock(); }
|
|
|
|
/**
|
|
* <pre>
|
|
* '{' BlockStatements '}'
|
|
* </pre>
|
|
*/
|
|
public Block
|
|
parseBlock() throws CompileException, IOException {
|
|
Block block = new Block(this.location());
|
|
this.read("{");
|
|
block.addStatements(this.parseBlockStatements());
|
|
this.read("}");
|
|
return block;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* BlockStatements := { BlockStatement }
|
|
* </pre>
|
|
*/
|
|
public List<BlockStatement>
|
|
parseBlockStatements() throws CompileException, IOException {
|
|
List<BlockStatement> l = new ArrayList();
|
|
while (!this.peek("}") && !this.peek("case") && !this.peek("default")) l.add(this.parseBlockStatement());
|
|
return l;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* BlockStatement := { Identifier ':' } (
|
|
* ( Modifiers Type | ModifiersOpt BasicType ) VariableDeclarators ';' |
|
|
* 'class' ... |
|
|
* Statement |
|
|
* 'final' Type VariableDeclarators ';' |
|
|
* Expression ';' |
|
|
* Expression VariableDeclarators ';' (1)
|
|
* )
|
|
* </pre>
|
|
*
|
|
* (1) "Expression" must pose a type, and has optional trailing brackets.
|
|
*/
|
|
public BlockStatement
|
|
parseBlockStatement() throws CompileException, IOException {
|
|
|
|
// Statement?
|
|
if (
|
|
(
|
|
this.peekIdentifier() != null
|
|
&& this.peekNextButOne(":")
|
|
)
|
|
|| this.peek(new String[] {
|
|
"if", "for", "while", "do", "try", "switch", "synchronized",
|
|
"return", "throw", "break", "continue", "assert"
|
|
}) != -1
|
|
|| this.peek(new String[] { "{", ";" }) != -1
|
|
) return this.parseStatement();
|
|
|
|
// Local class declaration?
|
|
if (this.peekRead("class")) {
|
|
// JAVADOC[TM] ignores doc comments for local classes, but we
|
|
// don't...
|
|
String optionalDocComment = this.scanner.doc();
|
|
if (optionalDocComment == null) this.warning("LCDCM", "Local class doc comment missing", this.location());
|
|
|
|
final LocalClassDeclaration lcd = (LocalClassDeclaration) this.parseClassDeclarationRest(
|
|
optionalDocComment, // optionalDocComment
|
|
new Modifiers(), // modifiers
|
|
ClassDeclarationContext.BLOCK // context
|
|
);
|
|
return new LocalClassDeclarationStatement(lcd);
|
|
}
|
|
|
|
// Modifiers Type VariableDeclarators ';'
|
|
if (this.peek(new String[] { "final", "@" }) != -1) {
|
|
LocalVariableDeclarationStatement lvds = new LocalVariableDeclarationStatement(
|
|
this.location(), // location
|
|
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.parseExpression();
|
|
|
|
// Expression ';'
|
|
if (this.peekRead(";")) {
|
|
return new ExpressionStatement(a.toRvalueOrCompileException());
|
|
}
|
|
|
|
// Expression VariableDeclarators ';'
|
|
Type variableType = a.toTypeOrCompileException();
|
|
LocalVariableDeclarationStatement lvds = new LocalVariableDeclarationStatement(
|
|
a.getLocation(), // location
|
|
new Java.Modifiers(Mod.NONE), // modifiers
|
|
variableType, // type
|
|
this.parseVariableDeclarators() // variableDeclarators
|
|
);
|
|
this.read(";");
|
|
return lvds;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* VariableDeclarators := VariableDeclarator { ',' VariableDeclarator }
|
|
* </pre>
|
|
*/
|
|
public VariableDeclarator[]
|
|
parseVariableDeclarators() throws CompileException, IOException {
|
|
List<VariableDeclarator> 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()]);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* FieldDeclarationRest :=
|
|
* VariableDeclaratorRest
|
|
* { ',' VariableDeclarator }
|
|
* </pre>
|
|
*/
|
|
public VariableDeclarator[]
|
|
parseFieldDeclarationRest(String name) throws CompileException, IOException {
|
|
List<VariableDeclarator> 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()]);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* VariableDeclarator := Identifier VariableDeclaratorRest
|
|
* </pre>
|
|
*/
|
|
public VariableDeclarator
|
|
parseVariableDeclarator() throws CompileException, IOException {
|
|
return this.parseVariableDeclaratorRest(this.readIdentifier());
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* VariableDeclaratorRest := { '[' ']' } [ '=' VariableInitializer ]
|
|
* </pre>
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* Statement :=
|
|
* LabeledStatement |
|
|
* Block |
|
|
* IfStatement |
|
|
* ForStatement |
|
|
* WhileStatement |
|
|
* DoStatement |
|
|
* TryStatement |
|
|
* 'switch' ... |
|
|
* 'synchronized' ... |
|
|
* ReturnStatement |
|
|
* ThrowStatement |
|
|
* BreakStatement |
|
|
* ContinueStatement |
|
|
* EmptyStatement |
|
|
* ExpressionStatement
|
|
* </pre>
|
|
*/
|
|
public Statement
|
|
parseStatement() throws CompileException, IOException {
|
|
if (this.peekIdentifier() != null && 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()
|
|
);
|
|
if (stmt == null) throw this.compileException("'" + this.peek().value + "' NYI");
|
|
|
|
return stmt;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* LabeledStatement := Identifier ':' Statement
|
|
* </pre>
|
|
*/
|
|
public Statement
|
|
parseLabeledStatement() throws CompileException, IOException {
|
|
String label = this.readIdentifier();
|
|
this.read(":");
|
|
return new LabeledStatement(
|
|
this.location(), // location
|
|
label, // label
|
|
this.parseStatement() // body
|
|
);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* IfStatement := 'if' '(' Expression ')' Statement [ 'else' Statement ]
|
|
* </pre>
|
|
*/
|
|
public Statement
|
|
parseIfStatement() throws CompileException, IOException {
|
|
final Location location = this.location();
|
|
this.read("if");
|
|
this.read("(");
|
|
final Rvalue condition = this.parseExpression().toRvalueOrCompileException();
|
|
this.read(")");
|
|
|
|
Statement thenStatement = this.parseStatement();
|
|
|
|
Statement optionalElseStatement = null;
|
|
if (this.peekRead("else")) {
|
|
optionalElseStatement = this.parseStatement();
|
|
}
|
|
|
|
return new IfStatement(
|
|
location, // location
|
|
condition, // condition
|
|
thenStatement, // thenStatement
|
|
optionalElseStatement // optionalElseStatement
|
|
);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ForStatement :=
|
|
* 'for' '(' [ ForInit ] ';' [ Expression ] ';' [ ExpressionList ] ')' Statement
|
|
* | 'for' '(' FormalParameter ':' Expression ')' Statement
|
|
*
|
|
* ForInit :=
|
|
* Modifiers Type VariableDeclarators
|
|
* | ModifiersOpt BasicType VariableDeclarators
|
|
* | Expression VariableDeclarators (1)
|
|
* | Expression { ',' Expression }
|
|
* </pre>
|
|
* (1) "Expression" must pose a type.
|
|
*/
|
|
public Statement
|
|
parseForStatement() throws CompileException, IOException {
|
|
this.read("for");
|
|
Location forLocation = this.location();
|
|
|
|
this.read("(");
|
|
|
|
BlockStatement optionalInit = null;
|
|
INIT:
|
|
if (!this.peek(";")) {
|
|
|
|
// 'for' '(' Modifiers Type VariableDeclarators
|
|
// 'for' '(' [ Modifiers ] BasicType VariableDeclarators
|
|
if (this.peek(new String[] {
|
|
"final", "@", "byte", "short", "char", "int", "long", "float", "double", "boolean"
|
|
}) != -1) {
|
|
Modifiers modifiers = this.parseModifiers();
|
|
Type type = this.parseType();
|
|
if (this.peekIdentifier() != null && this.peekNextButOne(":")) {
|
|
|
|
// 'for' '(' [ Modifiers ] Type identifier ':' Expression ')' Statement
|
|
final String name = this.readIdentifier();
|
|
final Location nameLocation = this.location();
|
|
this.read(":");
|
|
Rvalue expression = this.parseExpression().toRvalue();
|
|
this.read(")");
|
|
return new ForEachStatement(
|
|
forLocation, // location
|
|
new FormalParameter( // currentElement
|
|
nameLocation,
|
|
Mod.isFinal(modifiers.flags),
|
|
type,
|
|
name
|
|
),
|
|
expression, // expression
|
|
this.parseStatement() // body
|
|
);
|
|
}
|
|
|
|
// 'for' '(' [ Modifiers ] Type VariableDeclarators
|
|
optionalInit = new LocalVariableDeclarationStatement(
|
|
this.location(), // location
|
|
modifiers, // modifiers
|
|
type, // type
|
|
this.parseVariableDeclarators() // variableDeclarators
|
|
);
|
|
break INIT;
|
|
}
|
|
|
|
Atom a = this.parseExpression();
|
|
|
|
if (this.peekIdentifier() != null) {
|
|
if (this.peekNextButOne(":")) {
|
|
|
|
// 'for' '(' Expression identifier ':' Expression ')' Statement
|
|
final String name = this.readIdentifier();
|
|
final Location nameLocation = this.location();
|
|
this.read(":");
|
|
Rvalue expression = this.parseExpression().toRvalue();
|
|
this.read(")");
|
|
return new ForEachStatement(
|
|
forLocation, // location
|
|
new FormalParameter( // currentElement
|
|
nameLocation,
|
|
false,
|
|
a.toTypeOrCompileException(),
|
|
name
|
|
),
|
|
expression, // expression
|
|
this.parseStatement() // body
|
|
);
|
|
}
|
|
|
|
// 'for' '(' Expression VariableDeclarators
|
|
optionalInit = new LocalVariableDeclarationStatement(
|
|
this.location(), // location
|
|
new Java.Modifiers(Mod.NONE), // modifiers
|
|
a.toTypeOrCompileException(), // type
|
|
this.parseVariableDeclarators() // variableDeclarators
|
|
);
|
|
break INIT;
|
|
}
|
|
|
|
if (!this.peekRead(",")) {
|
|
|
|
// 'for' '(' Expression
|
|
optionalInit = new ExpressionStatement(a.toRvalueOrCompileException());
|
|
break INIT;
|
|
}
|
|
|
|
// 'for' '(' Expression { ',' Expression }
|
|
{
|
|
List<BlockStatement> l = new ArrayList();
|
|
l.add(new ExpressionStatement(a.toRvalueOrCompileException()));
|
|
do {
|
|
l.add(new ExpressionStatement(this.parseExpression().toRvalueOrCompileException()));
|
|
} while (this.peekRead(","));
|
|
|
|
Block b = new Block(a.getLocation());
|
|
b.addStatements(l);
|
|
optionalInit = b;
|
|
}
|
|
}
|
|
|
|
this.read(";");
|
|
|
|
Rvalue optionalCondition = null;
|
|
if (!this.peek(";")) optionalCondition = this.parseExpression().toRvalueOrCompileException();
|
|
|
|
this.read(";");
|
|
|
|
Rvalue[] optionalUpdate = null;
|
|
if (!this.peek(")")) optionalUpdate = this.parseExpressionList();
|
|
|
|
this.read(")");
|
|
|
|
return new ForStatement(
|
|
forLocation, // location
|
|
optionalInit, // optionalInit
|
|
optionalCondition, // optionalCondition
|
|
optionalUpdate, // optionalUpdate
|
|
this.parseStatement() // body
|
|
);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* WhileStatement := 'while' '(' Expression ')' Statement
|
|
* </pre>
|
|
*/
|
|
public Statement
|
|
parseWhileStatement() throws CompileException, IOException {
|
|
final Location location = this.location();
|
|
this.read("while");
|
|
|
|
this.read("(");
|
|
Rvalue condition = this.parseExpression().toRvalueOrCompileException();
|
|
this.read(")");
|
|
|
|
return new WhileStatement(
|
|
location, // location
|
|
condition, // condition
|
|
this.parseStatement() // body
|
|
);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* DoStatement := 'do' Statement 'while' '(' Expression ')' ';'
|
|
* </pre>
|
|
*/
|
|
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().toRvalueOrCompileException();
|
|
this.read(")");
|
|
this.read(";");
|
|
|
|
return new DoStatement(
|
|
location, // location
|
|
body, // body
|
|
condition // condition
|
|
);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* TryStatement :=
|
|
* 'try' Block Catches [ Finally ] |
|
|
* 'try' Block Finally
|
|
*
|
|
* Catches := CatchClause { CatchClause }
|
|
*
|
|
* CatchClause := 'catch' '(' FormalParameter ')' Block
|
|
*
|
|
* Finally := 'finally' Block
|
|
* </pre>
|
|
*/
|
|
public Statement
|
|
parseTryStatement() throws CompileException, IOException {
|
|
Location location = this.location();
|
|
this.read("try");
|
|
|
|
final Block body = this.parseBlock();
|
|
|
|
// { CatchClause }
|
|
List<CatchClause> ccs = new ArrayList();
|
|
while (this.peekRead("catch")) {
|
|
final Location loc = this.location();
|
|
this.read("(");
|
|
boolean[] hasEllipsis = new boolean[1];
|
|
final FormalParameter caughtException = this.parseFormalParameter(hasEllipsis);
|
|
if (hasEllipsis[0]) throw this.compileException("Catch clause parameter must not have an ellipsis");
|
|
this.read(")");
|
|
ccs.add(new CatchClause(
|
|
loc, // location
|
|
caughtException, // caughtException
|
|
this.parseBlock() // body
|
|
));
|
|
}
|
|
Block optionalFinally = null;
|
|
if (this.peekRead("finally")) {
|
|
optionalFinally = this.parseBlock();
|
|
}
|
|
if (ccs.size() == 0 && optionalFinally == null) {
|
|
throw this.compileException(
|
|
"\"try\" statement must have at least one \"catch\" clause or a \"finally\" clause"
|
|
);
|
|
}
|
|
|
|
return new TryStatement(
|
|
location, // location
|
|
body, // body
|
|
ccs, // catchClauses
|
|
optionalFinally // optionalFinally
|
|
);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* SwitchStatement :=
|
|
* 'switch' '(' Expression ')' '{' { SwitchLabels BlockStatements } '}'
|
|
*
|
|
* SwitchLabels := SwitchLabels { SwitchLabels }
|
|
*
|
|
* SwitchLabel := 'case' Expression ':' | 'default' ':'
|
|
* </pre>
|
|
*/
|
|
public Statement
|
|
parseSwitchStatement() throws CompileException, IOException {
|
|
final Location location = this.location();
|
|
this.read("switch");
|
|
|
|
this.read("(");
|
|
final Rvalue condition = this.parseExpression().toRvalueOrCompileException();
|
|
this.read(")");
|
|
|
|
this.read("{");
|
|
List<SwitchStatement.SwitchBlockStatementGroup> sbsgs = new ArrayList();
|
|
while (!this.peekRead("}")) {
|
|
Location location2 = this.location();
|
|
boolean hasDefaultLabel = false;
|
|
List<Rvalue> caseLabels = new ArrayList();
|
|
do {
|
|
if (this.peekRead("case")) {
|
|
caseLabels.add(this.parseExpression().toRvalueOrCompileException());
|
|
} 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(new String[] { "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
|
|
);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* SynchronizedStatement :=
|
|
* 'synchronized' '(' expression ')' Block
|
|
* </pre>
|
|
*/
|
|
public Statement
|
|
parseSynchronizedStatement() throws CompileException, IOException {
|
|
final Location location = this.location();
|
|
this.read("synchronized");
|
|
this.read("(");
|
|
Rvalue expression = this.parseExpression().toRvalueOrCompileException();
|
|
this.read(")");
|
|
return new SynchronizedStatement(
|
|
location, // location
|
|
expression, // expression
|
|
this.parseBlock() // body
|
|
);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ReturnStatement := 'return' [ Expression ] ';'
|
|
* </pre>
|
|
*/
|
|
public Statement
|
|
parseReturnStatement() throws CompileException, IOException {
|
|
final Location location = this.location();
|
|
this.read("return");
|
|
Rvalue returnValue = this.peek(";") ? null : this.parseExpression().toRvalueOrCompileException();
|
|
this.read(";");
|
|
return new ReturnStatement(location, returnValue);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ThrowStatement := 'throw' Expression ';'
|
|
* </pre>
|
|
*/
|
|
public Statement
|
|
parseThrowStatement() throws CompileException, IOException {
|
|
final Location location = this.location();
|
|
this.read("throw");
|
|
final Rvalue expression = this.parseExpression().toRvalueOrCompileException();
|
|
this.read(";");
|
|
|
|
return new ThrowStatement(location, expression);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* BreakStatement := 'break' [ Identifier ] ';'
|
|
* </pre>
|
|
*/
|
|
public Statement
|
|
parseBreakStatement() throws CompileException, IOException {
|
|
final Location location = this.location();
|
|
this.read("break");
|
|
String optionalLabel = null;
|
|
if (this.peekIdentifier() != null) optionalLabel = this.readIdentifier();
|
|
this.read(";");
|
|
return new BreakStatement(location, optionalLabel);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ContinueStatement := 'continue' [ Identifier ] ';'
|
|
* </pre>
|
|
*/
|
|
public Statement
|
|
parseContinueStatement() throws CompileException, IOException {
|
|
final Location location = this.location();
|
|
this.read("continue");
|
|
String optionalLabel = null;
|
|
if (this.peekIdentifier() != null) optionalLabel = this.readIdentifier();
|
|
this.read(";");
|
|
return new ContinueStatement(location, optionalLabel);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* AssertStatement := 'assert' Expression [ ':' Expression ] ';'
|
|
* </pre>
|
|
*/
|
|
public Statement
|
|
parseAssertStatement() throws CompileException, IOException {
|
|
this.read("assert");
|
|
Location loc = this.location();
|
|
|
|
Rvalue expression1 = this.parseExpression().toRvalueOrCompileException();
|
|
Rvalue optionalExpression2 = this.peekRead(":") ? this.parseExpression().toRvalueOrCompileException() : null;
|
|
this.read(";");
|
|
|
|
return new AssertStatement(loc, expression1, optionalExpression2);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* EmptyStatement := ';'
|
|
* </pre>
|
|
*/
|
|
public Statement
|
|
parseEmptyStatement() throws CompileException, IOException {
|
|
Location location = this.location();
|
|
this.read(";");
|
|
return new EmptyStatement(location);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ExpressionList := Expression { ',' Expression }
|
|
* </pre>
|
|
*/
|
|
public Rvalue[]
|
|
parseExpressionList() throws CompileException, IOException {
|
|
List<Rvalue> l = new ArrayList();
|
|
do {
|
|
l.add(this.parseExpression().toRvalueOrCompileException());
|
|
} while (this.peekRead(","));
|
|
return (Rvalue[]) l.toArray(new Rvalue[l.size()]);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* Type := (
|
|
* 'byte' | 'short' | 'char' | 'int' | 'long' |
|
|
* 'float' | 'double' | 'boolean' |
|
|
* ReferenceType
|
|
* ) { '[' ']' }
|
|
* </pre>
|
|
*/
|
|
public Type
|
|
parseType() throws CompileException, IOException {
|
|
int idx = this.peekRead(Parser.BASIC_TYPE_NAMES);
|
|
Type res = (
|
|
idx != -1
|
|
? (Type) new BasicType(this.location(), Parser.BASIC_TYPE_CODES[idx])
|
|
: this.parseReferenceType()
|
|
);
|
|
for (int i = this.parseBracketsOpt(); i > 0; --i) res = new ArrayType(res);
|
|
return res;
|
|
}
|
|
private static final String[] BASIC_TYPE_NAMES = {
|
|
"byte", "short", "char", "int", "long", "float",
|
|
"double", "boolean"
|
|
};
|
|
private static final int[] BASIC_TYPE_CODES = {
|
|
BasicType.BYTE, BasicType.SHORT, BasicType.CHAR, BasicType.INT, BasicType.LONG, BasicType.FLOAT,
|
|
BasicType.DOUBLE, BasicType.BOOLEAN
|
|
};
|
|
|
|
/**
|
|
* <pre>
|
|
* ReferenceType := QualifiedIdentifier [ TypeArguments ]
|
|
* </pre>
|
|
*/
|
|
public ReferenceType
|
|
parseReferenceType() throws CompileException, IOException {
|
|
return new ReferenceType(this.location(), this.parseQualifiedIdentifier(), this.parseTypeArgumentsOpt());
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* TypeParameters := '<' TypeParameter { ',' TypeParameter } '>'
|
|
* </pre>
|
|
*/
|
|
private TypeParameter[]
|
|
parseTypeParametersOpt() throws CompileException, IOException {
|
|
if (!this.peekRead("<")) return null;
|
|
List<TypeParameter> l = new ArrayList<TypeParameter>();
|
|
l.add(this.parseTypeParameter());
|
|
while (this.read(new String[] { ",", ">" }) == 0) {
|
|
l.add(this.parseTypeParameter());
|
|
}
|
|
return (TypeParameter[]) l.toArray(new TypeParameter[l.size()]);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* TypeParameter := identifier [ 'extends' ( identifier | ReferenceType { '&' ReferenceType }
|
|
* </pre>
|
|
*/
|
|
private TypeParameter
|
|
parseTypeParameter() throws CompileException, IOException {
|
|
String name = this.readIdentifier();
|
|
if (this.peekRead("extends")) {
|
|
List<ReferenceType> bound = new ArrayList<ReferenceType>();
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* TypeArguments := '<' TypeArgument { ',' TypeArgument } '>'
|
|
* </pre>
|
|
*/
|
|
private TypeArgument[]
|
|
parseTypeArgumentsOpt() throws CompileException, IOException {
|
|
if (!this.peekRead("<")) return null;
|
|
|
|
// Temporarily switch the scanner into 'expect greater' mode, where it doesn't recognize operators starting
|
|
// with '>>'.
|
|
boolean orig = this.scanner.setExpectGreater(true);
|
|
try {
|
|
|
|
List<TypeArgument> typeArguments = new ArrayList();
|
|
typeArguments.add(this.parseTypeArgument());
|
|
while (this.read(new String[] { ">", "," }) == 1) {
|
|
typeArguments.add(this.parseTypeArgument());
|
|
}
|
|
return (TypeArgument[]) typeArguments.toArray(new TypeArgument[typeArguments.size()]);
|
|
} finally {
|
|
this.scanner.setExpectGreater(orig);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* TypeArgument :=
|
|
* ReferenceType { '[' ']' } <= The optional brackets are mising in JLS7, section 18!?
|
|
* | BasicType '[' ']' { '[' ']' }
|
|
* | '?' extends ReferenceType
|
|
* | '?' super ReferenceType
|
|
* </pre>
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ReferenceTypeList := ReferenceType { ',' ReferenceType }
|
|
* </pre>
|
|
*/
|
|
public ReferenceType[]
|
|
parseReferenceTypeList() throws CompileException, IOException {
|
|
List<ReferenceType> l = new ArrayList();
|
|
l.add(this.parseReferenceType());
|
|
while (this.peekRead(",")) {
|
|
l.add(this.parseReferenceType());
|
|
}
|
|
return (ReferenceType[]) l.toArray(new ReferenceType[l.size()]);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* Expression := AssignmentExpression
|
|
* </pre>
|
|
*/
|
|
public Atom
|
|
parseExpression() throws CompileException, IOException {
|
|
return this.parseAssignmentExpression();
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* AssignmentExpression :=
|
|
* ConditionalExpression [ AssignmentOperator AssignmentExpression ]
|
|
*
|
|
* AssignmentOperator :=
|
|
* '=' | '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' |
|
|
* '>>=' | '>>>=' | '&=' | '^=' | '|='
|
|
* </pre>
|
|
*/
|
|
public Atom
|
|
parseAssignmentExpression() throws CompileException, IOException {
|
|
Atom a = this.parseConditionalExpression();
|
|
if (this.peek(
|
|
new String[] { "=", "+=", "-=", "*=", "/=", "&=", "|=", "^=", "%=", "<<=", ">>=", ">>>=" }
|
|
) != -1) {
|
|
final Lvalue lhs = a.toLvalueOrCompileException();
|
|
Location location = this.location();
|
|
String operator = this.readOperator();
|
|
final Rvalue rhs = this.parseAssignmentExpression().toRvalueOrCompileException();
|
|
return new Assignment(location, lhs, operator, rhs);
|
|
}
|
|
return a;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ConditionalExpression :=
|
|
* ConditionalOrExpression [ '?' Expression ':' ConditionalExpression ]
|
|
* </pre>
|
|
*/
|
|
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().toRvalueOrCompileException();
|
|
this.read(":");
|
|
Rvalue rhs = this.parseConditionalExpression().toRvalueOrCompileException();
|
|
return new ConditionalExpression(location, lhs, mhs, rhs);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ConditionalOrExpression :=
|
|
* ConditionalAndExpression { '||' ConditionalAndExpression ]
|
|
* </pre>
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ConditionalAndExpression :=
|
|
* InclusiveOrExpression { '&&' InclusiveOrExpression }
|
|
* </pre>
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* InclusiveOrExpression :=
|
|
* ExclusiveOrExpression { '|' ExclusiveOrExpression }
|
|
* </pre>
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ExclusiveOrExpression :=
|
|
* AndExpression { '^' AndExpression }
|
|
* </pre>
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* AndExpression :=
|
|
* EqualityExpression { '&' EqualityExpression }
|
|
* </pre>
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* EqualityExpression :=
|
|
* RelationalExpression { ( '==' | '!=' ) RelationalExpression }
|
|
* </pre>
|
|
*/
|
|
public Atom
|
|
parseEqualityExpression() throws CompileException, IOException {
|
|
Atom a = this.parseRelationalExpression();
|
|
|
|
while (this.peek(new String[] { "==", "!=" }) != -1) {
|
|
a = new BinaryOperation(
|
|
this.location(), // location
|
|
a.toRvalueOrCompileException(), // lhs
|
|
this.read().value, // op
|
|
this.parseRelationalExpression().toRvalueOrCompileException() // rhs
|
|
);
|
|
}
|
|
return a;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* RelationalExpression :=
|
|
* ShiftExpression {
|
|
* 'instanceof' ReferenceType
|
|
* | '<' ShiftExpression [ { ',' TypeArgument } '>' ]
|
|
* | '<' TypeArgument [ { ',' TypeArgument } '>' ]
|
|
* | ( '>' | '<=' | '>=' ) ShiftExpression
|
|
* }
|
|
* </pre>
|
|
*/
|
|
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(new String[] { "<", ">", "<=", ">=" }) != -1) {
|
|
String op = this.read().value;
|
|
|
|
if ("<".equals(op) && a instanceof Java.AmbiguousName && this.peek("?")) {
|
|
final String[] identifiers = ((Java.AmbiguousName) a).identifiers;
|
|
|
|
// Temporarily switch the scanner into 'expect greater' mode, where it doesn't recognize operators
|
|
// starting with '>>'.
|
|
boolean orig = this.scanner.setExpectGreater(true);
|
|
try {
|
|
|
|
// '<' TypeArgument [ { ',' TypeArgument } '>' ]
|
|
List<TypeArgument> typeArguments = new ArrayList();
|
|
typeArguments.add(this.parseTypeArgument());
|
|
while (this.read(new String[] { ">", "," }) == 1) typeArguments.add(this.parseTypeArgument());
|
|
|
|
return new ReferenceType(
|
|
this.location(),
|
|
identifiers,
|
|
(TypeArgument[]) typeArguments.toArray(new TypeArgument[typeArguments.size()])
|
|
);
|
|
} finally {
|
|
this.scanner.setExpectGreater(orig);
|
|
}
|
|
}
|
|
|
|
Atom rhs = this.parseShiftExpression();
|
|
|
|
if ("<".equals(op) && a instanceof Java.AmbiguousName) {
|
|
|
|
// Temporarily switch the scanner into 'expect greater' mode, where it doesn't recognize operators
|
|
// starting with '>>'.
|
|
boolean orig = this.scanner.setExpectGreater(true);
|
|
try {
|
|
|
|
if (this.peek(new String[] { "<", ">", "," }) != -1) {
|
|
final String[] identifiers = ((Java.AmbiguousName) a).identifiers;
|
|
|
|
// '<' ShiftExpression [ TypeArguments ] { ',' TypeArgument } '>'
|
|
this.parseTypeArgumentsOpt();
|
|
Type t = rhs.toTypeOrCompileException();
|
|
|
|
TypeArgument ta;
|
|
if (t instanceof ArrayType) { ta = (ArrayType) t; } else
|
|
if (t instanceof ReferenceType) { ta = (ReferenceType) t; } else
|
|
{
|
|
throw this.compileException("'" + t + "' is not a valid type argument");
|
|
}
|
|
|
|
List<TypeArgument> typeArguments = new ArrayList();
|
|
typeArguments.add(ta);
|
|
while (this.read(new String[] { ">", "," }) == 1) {
|
|
typeArguments.add(this.parseTypeArgument());
|
|
}
|
|
|
|
return new ReferenceType(
|
|
this.location(),
|
|
identifiers,
|
|
(TypeArgument[]) typeArguments.toArray(new TypeArgument[typeArguments.size()])
|
|
);
|
|
}
|
|
} finally {
|
|
this.scanner.setExpectGreater(orig);
|
|
}
|
|
}
|
|
|
|
a = new BinaryOperation(
|
|
this.location(), // location
|
|
a.toRvalueOrCompileException(), // lhs
|
|
op, // op
|
|
rhs.toRvalueOrCompileException() // rhs
|
|
);
|
|
} else {
|
|
return a;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ShiftExpression :=
|
|
* AdditiveExpression { ( '<<' | '>>' | '>>>' ) AdditiveExpression }
|
|
* </pre>
|
|
*/
|
|
public Atom
|
|
parseShiftExpression() throws CompileException, IOException {
|
|
Atom a = this.parseAdditiveExpression();
|
|
|
|
while (this.peek(new String[] { "<<", ">>", ">>>" }) != -1) {
|
|
a = new BinaryOperation(
|
|
this.location(), // location
|
|
a.toRvalueOrCompileException(), // lhs
|
|
this.read().value, // op
|
|
this.parseAdditiveExpression().toRvalueOrCompileException() // rhs
|
|
);
|
|
}
|
|
return a;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* AdditiveExpression :=
|
|
* MultiplicativeExpression { ( '+' | '-' ) MultiplicativeExpression }
|
|
* </pre>
|
|
*/
|
|
public Atom
|
|
parseAdditiveExpression() throws CompileException, IOException {
|
|
Atom a = this.parseMultiplicativeExpression();
|
|
|
|
while (this.peek(new String[] { "+", "-" }) != -1) {
|
|
a = new BinaryOperation(
|
|
this.location(), // location
|
|
a.toRvalueOrCompileException(), // lhs
|
|
this.read().value, // op
|
|
this.parseMultiplicativeExpression().toRvalueOrCompileException() // rhs
|
|
);
|
|
}
|
|
return a;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* MultiplicativeExpression :=
|
|
* UnaryExpression { ( '*' | '/' | '%' ) UnaryExpression }
|
|
* </pre>
|
|
*/
|
|
public Atom
|
|
parseMultiplicativeExpression() throws CompileException, IOException {
|
|
Atom a = this.parseUnaryExpression();
|
|
|
|
while (this.peek(new String[] { "*", "/", "%" }) != -1) {
|
|
a = new BinaryOperation(
|
|
this.location(), // location
|
|
a.toRvalueOrCompileException(), // lhs
|
|
this.read().value, // op
|
|
this.parseUnaryExpression().toRvalueOrCompileException() // rhs
|
|
);
|
|
}
|
|
return a;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* UnaryExpression :=
|
|
* { PrefixOperator } Primary { Selector } { PostfixOperator }
|
|
*
|
|
* PrefixOperator := '++' | '--' | '+' | '-' | '~' | '!'
|
|
*
|
|
* PostfixOperator := '++' | '--'
|
|
* </pre>
|
|
*/
|
|
public Atom
|
|
parseUnaryExpression() throws CompileException, IOException {
|
|
if (this.peek(new String[] { "++", "--" }) != -1) {
|
|
return new Crement(
|
|
this.location(), // location
|
|
this.read().value, // operator
|
|
this.parseUnaryExpression().toLvalueOrCompileException() // operand
|
|
);
|
|
}
|
|
|
|
if (this.peek(new String[] { "+", "-", "~", "!" }) != -1) {
|
|
return new UnaryOperation(
|
|
this.location(), // location
|
|
this.read().value, // operator
|
|
this.parseUnaryExpression().toRvalueOrCompileException() // operand
|
|
);
|
|
}
|
|
|
|
Atom a = this.parsePrimary();
|
|
|
|
while (this.peek(new String[] { ".", "[" }) != -1) {
|
|
a = this.parseSelector(a);
|
|
}
|
|
|
|
while (this.peek(new String[] { "++", "--" }) != -1) {
|
|
a = new Crement(
|
|
this.location(), // location
|
|
a.toLvalueOrCompileException(), // operand
|
|
this.read().value // operator
|
|
);
|
|
}
|
|
|
|
return a;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* 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
|
|
* BasicType { '[]' } | // Type
|
|
* BasicType { '[]' } '.' 'class' | // ClassLiteral 15.8.2
|
|
* 'void' '.' 'class' // ClassLiteral 15.8.2
|
|
*
|
|
* CastExpression :=
|
|
* '(' PrimitiveType { '[]' } ')' UnaryExpression |
|
|
* '(' Expression ')' UnaryExpression
|
|
*
|
|
* NewClassInstance := 'new' ReferenceType Arguments
|
|
*
|
|
* NewAnonymousClassInstance := 'new' ReferenceType Arguments [ ClassBody ]
|
|
*
|
|
* NewArray := 'new' Type DimExprs { '[]' }
|
|
*
|
|
* NewInitializedArray := 'new' ArrayType ArrayInitializer
|
|
* </pre>
|
|
*/
|
|
public Atom
|
|
parsePrimary() throws CompileException, IOException {
|
|
if (this.peekRead("(")) {
|
|
if (
|
|
this.peek(new String[] { "boolean", "char", "byte", "short", "int", "long", "float", "double", }) != -1
|
|
) {
|
|
// '(' 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
|
|
);
|
|
}
|
|
Atom a = this.parseExpression();
|
|
this.read(")");
|
|
|
|
if (
|
|
this.peekLiteral()
|
|
|| this.peekIdentifier() != null
|
|
|| this.peek(new String[] { "(", "~", "!", }) != -1
|
|
|| this.peek(new String[] { "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.peekIdentifier() != null) {
|
|
Location location = this.location();
|
|
String[] qi = this.parseQualifiedIdentifier();
|
|
if (this.peek("(")) {
|
|
// Name Arguments
|
|
return new MethodInvocation(
|
|
this.location(), // location
|
|
qi.length == 1 ? null : new AmbiguousName( // optionalTarget
|
|
location, // location
|
|
qi, // identifiers
|
|
qi.length - 1 // n
|
|
),
|
|
qi[qi.length - 1], // methodName
|
|
this.parseArguments() // arguments
|
|
);
|
|
}
|
|
if (this.peek("[") && this.peekNextButOne("]")) {
|
|
// Name '[]' { '[]' }
|
|
// Name '[]' { '[]' } '.' 'class'
|
|
Type res = new ReferenceType(
|
|
location, // location
|
|
qi, // identifiers
|
|
null // optionalTypeArguments
|
|
);
|
|
int brackets = this.parseBracketsOpt();
|
|
for (int i = 0; i < brackets; ++i) res = new ArrayType(res);
|
|
if (this.peek(".") && this.peekNextButOne("class")) {
|
|
this.read();
|
|
Location location2 = this.location();
|
|
this.read();
|
|
return new ClassLiteral(location2, res);
|
|
} else {
|
|
return res;
|
|
}
|
|
}
|
|
// Name
|
|
return new AmbiguousName(
|
|
location, // location
|
|
qi // identifiers
|
|
);
|
|
}
|
|
|
|
if (this.peekRead("this")) {
|
|
Location location = this.location();
|
|
if (this.peek("(")) {
|
|
|
|
// 'this' Arguments
|
|
// Alternate constructor invocation (JLS7 8.8.7.1).
|
|
return new AlternateConstructorInvocation(
|
|
location, // location
|
|
this.parseArguments() // arguments
|
|
);
|
|
} else
|
|
{
|
|
|
|
// 'this'
|
|
return new ThisReference(location);
|
|
}
|
|
}
|
|
|
|
if (this.peekRead("super")) {
|
|
if (this.peek("(")) {
|
|
|
|
// 'super' Arguments
|
|
// Unqualified superclass constructor invocation (JLS7 8.8.7.1).
|
|
return new SuperConstructorInvocation(
|
|
this.location(), // location
|
|
(Rvalue) null, // optionalQualification
|
|
this.parseArguments() // arguments
|
|
);
|
|
}
|
|
this.read(".");
|
|
String name = this.readIdentifier();
|
|
if (this.peek("(")) {
|
|
|
|
// 'super' '.' Identifier Arguments
|
|
return new SuperclassMethodInvocation(
|
|
this.location(), // location
|
|
name, // methodName
|
|
this.parseArguments() // arguments
|
|
);
|
|
} else {
|
|
|
|
// 'super' '.' Identifier
|
|
return new SuperclassFieldAccessExpression(
|
|
this.location(), // location
|
|
(Type) null, // optionalQualification
|
|
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, // optionalQualification
|
|
anonymousClassDeclaration, // anonymousClassDeclaration
|
|
arguments // arguments
|
|
);
|
|
} else {
|
|
// 'new' ReferenceType Arguments
|
|
return new NewClassInstance(
|
|
location, // location
|
|
(Rvalue) null, // optionalQualification
|
|
type, // type
|
|
arguments // arguments
|
|
);
|
|
}
|
|
}
|
|
// 'new' Type DimExprs { '[]' }
|
|
return new NewArray(
|
|
location, // location
|
|
type, // type
|
|
this.parseDimExprs(), // dimExprs
|
|
this.parseBracketsOpt() // dims
|
|
);
|
|
}
|
|
|
|
// BasicType
|
|
if (this.peek(new String[] { "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")) {
|
|
// BasicType { '[]' } '.' 'class'
|
|
this.read();
|
|
Location location = this.location();
|
|
this.read();
|
|
return new ClassLiteral(location, res);
|
|
}
|
|
// BasicType { '[]' }
|
|
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 BasicType(location, BasicType.VOID));
|
|
}
|
|
throw this.compileException("\"void\" encountered in wrong context");
|
|
}
|
|
|
|
throw this.compileException("Unexpected token \"" + this.peek().value + "\" in primary");
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* Selector :=
|
|
* '.' Identifier | // FieldAccess 15.11.1
|
|
* '.' Identifier Arguments | // MethodInvocation
|
|
* '.' '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
|
|
* </pre>
|
|
*/
|
|
public Atom
|
|
parseSelector(Atom atom) throws CompileException, IOException {
|
|
if (this.peekRead(".")) {
|
|
if (this.peek().type == Token.IDENTIFIER) {
|
|
String identifier = this.readIdentifier();
|
|
if (this.peek("(")) {
|
|
// '.' Identifier Arguments
|
|
return new MethodInvocation(
|
|
this.location(), // location
|
|
atom.toRvalueOrCompileException(), // optionalTarget
|
|
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(), // optionalQualification
|
|
this.parseArguments() // arguments
|
|
);
|
|
}
|
|
this.read(".");
|
|
String identifier = this.readIdentifier();
|
|
|
|
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(), // optionalQualification
|
|
identifier // fieldName
|
|
);
|
|
}
|
|
}
|
|
if (this.peekRead("new")) {
|
|
// '.' 'new' Identifier Arguments [ ClassBody ]
|
|
Rvalue lhs = atom.toRvalue();
|
|
Location location = this.location();
|
|
String identifier = this.readIdentifier();
|
|
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, // optionalQualification
|
|
anonymousClassDeclaration, // anonymousClassDeclaration
|
|
arguments // arguments
|
|
);
|
|
} else {
|
|
// '.' 'new' Identifier Arguments (LHS is an Rvalue)
|
|
return new NewClassInstance(
|
|
location, // location
|
|
lhs, // optionalQualification
|
|
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().toRvalueOrCompileException();
|
|
this.read("]");
|
|
return new ArrayAccessExpression(
|
|
location, // location
|
|
atom.toRvalueOrCompileException(), // lhs
|
|
index // index
|
|
);
|
|
}
|
|
throw this.compileException("Unexpected token '" + this.peek().value + "' in selector");
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* DimExprs := DimExpr { DimExpr }
|
|
* </pre>
|
|
*/
|
|
public Rvalue[]
|
|
parseDimExprs() throws CompileException, IOException {
|
|
List<Rvalue> l = new ArrayList();
|
|
l.add(this.parseDimExpr());
|
|
while (this.peek("[") && !this.peekNextButOne("]")) {
|
|
l.add(this.parseDimExpr());
|
|
}
|
|
return (Rvalue[]) l.toArray(new Rvalue[l.size()]);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* DimExpr := '[' Expression ']'
|
|
* </pre>
|
|
*/
|
|
public Rvalue
|
|
parseDimExpr() throws CompileException, IOException {
|
|
this.read("[");
|
|
Rvalue res = this.parseExpression().toRvalueOrCompileException();
|
|
this.read("]");
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* Arguments := '(' [ ArgumentList ] ')'
|
|
* </pre>
|
|
*/
|
|
public Rvalue[]
|
|
parseArguments() throws CompileException, IOException {
|
|
this.read("(");
|
|
if (this.peekRead(")")) return new Rvalue[0];
|
|
Rvalue[] arguments = this.parseArgumentList();
|
|
this.read(")");
|
|
return arguments;
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ArgumentList := Expression { ',' Expression }
|
|
* </pre>
|
|
*/
|
|
public Rvalue[]
|
|
parseArgumentList() throws CompileException, IOException {
|
|
List<Rvalue> l = new ArrayList();
|
|
do {
|
|
l.add(this.parseExpression().toRvalueOrCompileException());
|
|
} while (this.peekRead(","));
|
|
return (Rvalue[]) l.toArray(new Rvalue[l.size()]);
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* Literal :=
|
|
* IntegerLiteral
|
|
* | FloatingPointLiteral
|
|
* | BooleanLiteral
|
|
* | CharacterLiteral
|
|
* | StringLiteral
|
|
* | NullLiteral
|
|
* </pre>
|
|
*/
|
|
public Rvalue
|
|
parseLiteral() throws CompileException, IOException {
|
|
Token t = this.read();
|
|
switch (t.type) {
|
|
case Token.INTEGER_LITERAL: return new IntegerLiteral(t.getLocation(), t.value);
|
|
case Token.FLOATING_POINT_LITERAL: return new FloatingPointLiteral(t.getLocation(), t.value);
|
|
case Token.BOOLEAN_LITERAL: return new BooleanLiteral(t.getLocation(), t.value);
|
|
case Token.CHARACTER_LITERAL: return new CharacterLiteral(t.getLocation(), t.value);
|
|
case Token.STRING_LITERAL: return new StringLiteral(t.getLocation(), t.value);
|
|
case Token.NULL_LITERAL: return new NullLiteral(t.getLocation(), t.value);
|
|
default:
|
|
throw this.compileException("Literal expected");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <pre>
|
|
* ExpressionStatement := Expression ';'
|
|
* </pre>
|
|
*/
|
|
public Statement
|
|
parseExpressionStatement() throws CompileException, IOException {
|
|
Rvalue rv = this.parseExpression().toRvalueOrCompileException();
|
|
this.read(";");
|
|
|
|
return new ExpressionStatement(rv);
|
|
}
|
|
|
|
/** @return The location of the first character of the previously {@link #peek()}ed or {@link #read()} token */
|
|
public Location
|
|
location() { return this.scanner.location(); }
|
|
|
|
private Token nextToken, nextButOneToken;
|
|
|
|
// Token-level methods.
|
|
|
|
/** @return The next token, but does not consume it */
|
|
public Token
|
|
peek() throws CompileException, IOException {
|
|
if (this.nextToken == null) this.nextToken = this.scanner.produce();
|
|
return this.nextToken;
|
|
}
|
|
|
|
/** @return The next-but-one token, but consumes neither the next nor the next-but-one token */
|
|
public Token
|
|
peekNextButOne() throws CompileException, IOException {
|
|
if (this.nextToken == null) this.nextToken = this.scanner.produce();
|
|
if (this.nextButOneToken == null) this.nextButOneToken = this.scanner.produce();
|
|
return this.nextButOneToken;
|
|
}
|
|
|
|
/** @return The next and also consumes it, or {@code null} iff the scanner is at end-of-input */
|
|
public Token
|
|
read() throws CompileException, IOException {
|
|
if (this.nextToken == null) return this.scanner.produce();
|
|
final Token result = this.nextToken;
|
|
this.nextToken = this.nextButOneToken;
|
|
this.nextButOneToken = null;
|
|
return result;
|
|
}
|
|
|
|
// Peek/read/peekRead convenience methods.
|
|
|
|
/** @return Whether the value of the next token equals {@code suspected}; does not consume the next token */
|
|
public boolean
|
|
peek(String suspected) throws CompileException, IOException {
|
|
return this.peek().value.equals(suspected);
|
|
}
|
|
|
|
/**
|
|
* Checks whether the value of the next token equals any of the {@code suspected}; does not consume the next
|
|
* token.
|
|
*
|
|
* @return The index of the first of the {@code suspected} that equals the value of the next token, or -1 if the
|
|
* value of the next token equals none of the {@code suspected}
|
|
*/
|
|
public int
|
|
peek(String[] suspected) throws CompileException, IOException {
|
|
return Parser.indexOf(suspected, this.peek().value);
|
|
}
|
|
|
|
/**
|
|
* Checks whether the type of the next token is any of the {@code suspected}; does not consume the next token.
|
|
*
|
|
* @return The index of the first of the {@code suspected} types that is the next token's type, or -1 if the type
|
|
* of the next token is none of the {@code suspected} types
|
|
*/
|
|
public int
|
|
peek(int[] suspected) throws CompileException, IOException {
|
|
return Parser.indexOf(suspected, this.peek().type);
|
|
}
|
|
|
|
/**
|
|
* @return Whether the value of the next-but-one token equals the {@code suspected}; consumes neither the next
|
|
* nor the next-but-one token
|
|
*/
|
|
public boolean
|
|
peekNextButOne(String suspected) throws CompileException, IOException {
|
|
return this.peekNextButOne().value.equals(suspected);
|
|
}
|
|
|
|
/**
|
|
* Verifies that the value of the next token equals {@code expected}, and consumes the token.
|
|
*
|
|
* @throws CompileException The value of the next token does not equal {@code expected} (this includes the case
|
|
* that the scanner is at end-of-input)
|
|
*/
|
|
public void
|
|
read(String expected) throws CompileException, IOException {
|
|
String s = this.read().value;
|
|
if (!s.equals(expected)) throw this.compileException("'" + expected + "' expected instead of '" + s + "'");
|
|
}
|
|
|
|
/**
|
|
* Verifies that the value of the next token equals one of the {@code expected}, and consumes the token.
|
|
*
|
|
* @return The index of the consumed token within {@code expected}
|
|
* @throws CompileException The value of the next token does not equal any of the {@code expected} (this includes
|
|
* the case where the scanner is at end-of-input)
|
|
*/
|
|
public int
|
|
read(String[] expected) throws CompileException, IOException {
|
|
|
|
String s = this.read().value;
|
|
|
|
int idx = Parser.indexOf(expected, s);
|
|
if (idx == -1) {
|
|
throw this.compileException("One of '" + Parser.join(expected, " ") + "' expected instead of '" + s + "'");
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
/**
|
|
* @return Whether the value of the next token equals the {@code suspected}; if so, it consumes the next token
|
|
* @throws CompileException
|
|
* @throws IOException
|
|
*/
|
|
public boolean
|
|
peekRead(String suspected) throws CompileException, IOException {
|
|
if (this.nextToken == null) {
|
|
Token t = this.scanner.produce();
|
|
if (t.value.equals(suspected)) return true;
|
|
this.nextToken = t;
|
|
return false;
|
|
}
|
|
if (!this.nextToken.value.equals(suspected)) return false;
|
|
this.nextToken = this.nextButOneToken;
|
|
this.nextButOneToken = null;
|
|
return true;
|
|
}
|
|
|
|
/** @return -1 iff the next token is none of <code>values</code> */
|
|
public int
|
|
peekRead(String[] values) throws CompileException, IOException {
|
|
if (this.nextToken == null) {
|
|
Token t = this.scanner.produce();
|
|
int idx = Parser.indexOf(values, t.value);
|
|
if (idx != -1) return idx;
|
|
this.nextToken = t;
|
|
return -1;
|
|
}
|
|
int idx = Parser.indexOf(values, this.nextToken.value);
|
|
if (idx == -1) return -1;
|
|
this.nextToken = this.nextButOneToken;
|
|
this.nextButOneToken = null;
|
|
return idx;
|
|
}
|
|
|
|
/** @return Whether the scanner is at end-of-input */
|
|
public boolean
|
|
peekEof() throws CompileException, IOException {
|
|
return this.peek().type == Token.EOF;
|
|
}
|
|
|
|
/** @return {@code null} iff the next token is not an identifier, otherwise the value of the identifier token */
|
|
public String
|
|
peekIdentifier() throws CompileException, IOException {
|
|
Token t = this.peek();
|
|
return t.type == Token.IDENTIFIER ? t.value : null;
|
|
}
|
|
|
|
/** @return Whether the next token is a literal */
|
|
public boolean
|
|
peekLiteral() throws CompileException, IOException {
|
|
return this.peek(new int[] {
|
|
Token.INTEGER_LITERAL, Token.FLOATING_POINT_LITERAL, Token.BOOLEAN_LITERAL, Token.CHARACTER_LITERAL,
|
|
Token.STRING_LITERAL, Token.NULL_LITERAL,
|
|
}) != -1;
|
|
}
|
|
|
|
/**
|
|
* @return The value of the next token, which is an indentifier
|
|
* @throws CompileException The next token is not an identifier
|
|
*/
|
|
public String
|
|
readIdentifier() throws CompileException, IOException {
|
|
Token t = this.read();
|
|
if (t.type != Token.IDENTIFIER) throw this.compileException("Identifier expected instead of '" + t.value + "'");
|
|
return t.value;
|
|
}
|
|
|
|
/**
|
|
* @return The value of the next token, which is an operator
|
|
* @throws CompileException The next token is not an operator
|
|
*/
|
|
public String
|
|
readOperator() throws CompileException, IOException {
|
|
Token t = this.read();
|
|
if (t.type != Token.OPERATOR) throw this.compileException("Operator expected instead of '" + t.value + "'");
|
|
return t.value;
|
|
}
|
|
|
|
private static int
|
|
indexOf(String[] strings, String subject) {
|
|
for (int i = 0; i < strings.length; ++i) {
|
|
if (strings[i].equals(subject)) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
private static int
|
|
indexOf(int[] values, int subject) {
|
|
for (int i = 0; i < values.length; ++i) {
|
|
if (values[i] == subject) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/** Issue 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Issue 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Issue 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Issue 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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Issue 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* By default, warnings are discarded, but an application my install a {@link WarningHandler}.
|
|
* <p>
|
|
* Notice that there is no <code>Parser.setErrorHandler()</code> 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 optionalWarningHandler <code>null</code> to indicate that no warnings be issued
|
|
*/
|
|
public void
|
|
setWarningHandler(WarningHandler optionalWarningHandler) {
|
|
this.optionalWarningHandler = optionalWarningHandler;
|
|
}
|
|
|
|
// Used for elaborate warning handling.
|
|
private WarningHandler optionalWarningHandler;
|
|
|
|
/**
|
|
* 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)}.
|
|
* <p>
|
|
* The <code>handle</code> argument qulifies 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, Location optionalLocation) throws CompileException {
|
|
if (this.optionalWarningHandler != null) {
|
|
this.optionalWarningHandler.handleWarning(handle, message, optionalLocation);
|
|
}
|
|
}
|
|
|
|
/** Convenience method for throwing a {@link CompileException}. */
|
|
protected final CompileException
|
|
compileException(String message) {
|
|
return new CompileException(message, this.location());
|
|
}
|
|
|
|
private static String
|
|
join(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();
|
|
}
|
|
}
|