/* * 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. *

* 'JLS7' refers to the Java Language Specification, Java SE 7 * Edition. */ @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; } /** *

     *   CompilationUnit := [ PackageDeclaration ]
     *                      { ImportDeclaration }
     *                      { TypeDeclaration }
     * 
*/ 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; } /** *
     *   PackageDeclaration := 'package' QualifiedIdentifier ';'
     * 
*/ 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); } /** *
     *   ImportDeclaration := 'import' ImportDeclarationBody ';'
     * 
*/ public CompilationUnit.ImportDeclaration parseImportDeclaration() throws CompileException, IOException { this.read("import"); CompilationUnit.ImportDeclaration importDeclaration = this.parseImportDeclarationBody(); this.read(";"); return importDeclaration; } /** *
     *   ImportDeclarationBody := [ 'static' ] Identifier { '.' Identifier } [ '.' '*' ]
     * 
*/ public CompilationUnit.ImportDeclaration parseImportDeclarationBody() throws CompileException, IOException { final Location loc = this.location(); boolean isStatic = this.peekRead("static"); List 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()); } } /** *
     *   QualifiedIdentifier := Identifier { '.' Identifier }
     * 
*/ public String[] parseQualifiedIdentifier() throws CompileException, IOException { List 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()]); } /** *
     *   PackageMemberTypeDeclaration :=
     *             ModifiersOpt 'class' ClassDeclarationRest |
     *             ModifiersOpt 'interface' InterfaceDeclarationRest
     * 
*/ 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(); } } /** *
     *   ModifiersAndAnnotations := { 'public' | 'protected' | 'private' | 'static' | 'abstract' | 'final' | 'native'
     *           | 'synchronized' | 'transient' | 'volatile' | 'strictfp' | Annotation }
     * 
*/ public Java.Modifiers parseModifiers() throws CompileException, IOException { short mod = 0; List 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, }; /** *
     *   Annotation :=
     *           MarkerAnnotation             // JLS7 9.7.2
     *           | SingleElementAnnotation    // JLS7 9.7.3
     *           | NormalAnnotation           // JLS7 9.7.1
     *
     *   MarkerAnnotation        := '@' Identifier
     *
     *   SingleElementAnnotation := '@' Identifier '(' ElementValue ')'
     *
     *   NormalAnnotation        := '@' TypeName '(' ElementValuePairsOpt ')'
     *
     *   ElementValuePairsOpt    := [ ElementValuePair { ',' ElementValuePair } ]
     * 
*/ private 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 evps = new ArrayList(); while (!this.peekRead(")")) evps.add(this.parseElementValuePair()); return new Java.NormalAnnotation( type, (ElementValuePair[]) evps.toArray(new Java.ElementValuePair[evps.size()]) ); } /** *
     *   ElementValuePair := Identifier '=' ElementValue
     * 
*/ private Java.ElementValuePair parseElementValuePair() throws CompileException, IOException { String identifier = this.readIdentifier(); this.read("="); return new Java.ElementValuePair(identifier, this.parseElementValue()); } /** *
     *   ElementValue :=
     *           ConditionalExpression
     *           | Annotation
     *           | ElementValueArrayInitializer
     * 
*/ private Java.ElementValue parseElementValue() throws CompileException, IOException { if (this.peek("@")) return this.parseAnnotation(); if (this.peek("{")) return this.parseElementValueArrayInitializer(); return this.parseConditionalAndExpression().toRvalueOrCompileException(); } /** *
     *   ElementValueArrayInitializer := '{' { ElementValue | ',' } '}'
     * 
*/ private ElementValue parseElementValueArrayInitializer() throws CompileException, IOException { this.read("{"); List 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()])); } /** *
     *   ClassDeclarationRest :=
     *        Identifier [ typeParameters ]
     *        [ 'extends' ReferenceType ]
     *        [ 'implements' ReferenceTypeList ]
     *        ClassBody
     * 
*/ 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); } } /** *
     *   ClassBody := '{' { ClassBodyDeclaration } '}'
     * 
*/ public void parseClassBody(ClassDeclaration classDeclaration) throws CompileException, IOException { this.read("{"); for (;;) { if (this.peekRead("}")) return; this.parseClassBodyDeclaration(classDeclaration); } } /** *
     *   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 ';'
     *       )
     *     )
     *
     * 
*/ 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); } /** *
     *   InterfaceDeclarationRest :=
     *     Identifier [ typeParameters ]
     *     [ 'extends' ReferenceTypeList ]
     *     InterfaceBody
     * 
*/ 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); } } /** *
     *   InterfaceBody := '{' {
     *     ';' |
     *     ModifiersOpt (
     *       'void' Identifier MethodDeclarationRest |
     *       'class' ClassDeclarationRest |
     *       'interface' InterfaceDeclarationRest |
     *       Type Identifier (
     *         MethodDeclarationRest |
     *         FieldDeclarationRest
     *       )
     *     )
     *   } '}'
     * 
*/ public void parseInterfaceBody(InterfaceDeclaration interfaceDeclaration) throws CompileException, IOException { this.read("{"); while (!this.peekRead("}")) { if (this.peekRead(";")) continue; String 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); } } } } /** *
     *   ConstructorDeclarator :=
     *     Identifier
     *     FormalParameters
     *     [ 'throws' ReferenceTypeList ]
     *     '{'
     *       [ 'this' Arguments ';' | 'super' Arguments ';' | Primary '.' 'super' Arguments ';' ]
     *       BlockStatements
     *     '}'
     * 
*/ 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 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 ); } /** *
     *   MethodDeclarationRest :=
     *     FormalParameters
     *     { '[' ']' }
     *     [ 'throws' ReferenceTypeList ]
     *     ( ';' | MethodBody )
     * 
*/ 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 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 ); } /** *
     *   VariableInitializer :=
     *     ArrayInitializer |
     *     Expression
     * 
*/ public ArrayInitializerOrRvalue parseVariableInitializer() throws CompileException, IOException { if (this.peek("{")) { return this.parseArrayInitializer(); } else { return this.parseExpression().toRvalueOrCompileException(); } } /** *
     *   ArrayInitializer :=
     *     '{' [ VariableInitializer { ',' VariableInitializer } [ ',' ] '}'
     * 
*/ public ArrayInitializer parseArrayInitializer() throws CompileException, IOException { final Location location = this.location(); this.read("{"); List l = new ArrayList(); while (!this.peekRead("}")) { l.add(this.parseVariableInitializer()); if (this.peekRead("}")) break; this.read(","); } return new ArrayInitializer( location, (ArrayInitializerOrRvalue[]) l.toArray(new ArrayInitializerOrRvalue[l.size()]) ); } /** *
     *   FormalParameters := '(' [ FormalParameter { ',' FormalParameter } ] ')'
     * 
*/ public FormalParameters parseFormalParameters() throws CompileException, IOException { this.read("("); if (this.peekRead(")")) return new FormalParameters(); List 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 ); } /** *
     *   FormalParameter := [ 'final' ] Type [ '.' '.' '.' ] Identifier BracketsOpt
     * 
*/ 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); } /** *
     *   BracketsOpt := { '[' ']' }
     * 
*/ int parseBracketsOpt() throws CompileException, IOException { int res = 0; while (this.peek("[") && this.peekNextButOne("]")) { this.read(); this.read(); ++res; } return res; } /** *
     *   MethodBody := Block
     * 
*/ public Block parseMethodBody() throws CompileException, IOException { return this.parseBlock(); } /** *
     *   '{' BlockStatements '}'
     * 
*/ public Block parseBlock() throws CompileException, IOException { Block block = new Block(this.location()); this.read("{"); block.addStatements(this.parseBlockStatements()); this.read("}"); return block; } /** *
     *   BlockStatements := { BlockStatement }
     * 
*/ public List parseBlockStatements() throws CompileException, IOException { List l = new ArrayList(); while (!this.peek("}") && !this.peek("case") && !this.peek("default")) l.add(this.parseBlockStatement()); return l; } /** *
     *   BlockStatement := { Identifier ':' } (
     *     ( Modifiers Type | ModifiersOpt BasicType ) VariableDeclarators ';' |
     *     'class' ... |
     *     Statement |
     *     'final' Type VariableDeclarators ';' |
     *     Expression ';' |
     *     Expression VariableDeclarators ';'   (1)
     *   )
     * 
* * (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; } /** *
     *   VariableDeclarators := VariableDeclarator { ',' VariableDeclarator }
     * 
*/ public VariableDeclarator[] parseVariableDeclarators() throws CompileException, IOException { List l = new ArrayList(); do { VariableDeclarator vd = this.parseVariableDeclarator(); this.verifyIdentifierIsConventionalLocalVariableOrParameterName(vd.name, vd.getLocation()); l.add(vd); } while (this.peekRead(",")); return (VariableDeclarator[]) l.toArray(new VariableDeclarator[l.size()]); } /** *
     *   FieldDeclarationRest :=
     *     VariableDeclaratorRest
     *     { ',' VariableDeclarator }
     * 
*/ public VariableDeclarator[] parseFieldDeclarationRest(String name) throws CompileException, IOException { List l = new ArrayList(); VariableDeclarator vd = this.parseVariableDeclaratorRest(name); this.verifyIdentifierIsConventionalFieldName(vd.name, vd.getLocation()); l.add(vd); while (this.peekRead(",")) { vd = this.parseVariableDeclarator(); this.verifyIdentifierIsConventionalFieldName(vd.name, vd.getLocation()); l.add(vd); } return (VariableDeclarator[]) l.toArray(new VariableDeclarator[l.size()]); } /** *
     *   VariableDeclarator := Identifier VariableDeclaratorRest
     * 
*/ public VariableDeclarator parseVariableDeclarator() throws CompileException, IOException { return this.parseVariableDeclaratorRest(this.readIdentifier()); } /** *
     *   VariableDeclaratorRest := { '[' ']' } [ '=' VariableInitializer ]
     * 
* Used by field declarations and local variable declarations. */ public VariableDeclarator parseVariableDeclaratorRest(String name) throws CompileException, IOException { Location loc = this.location(); int brackets = this.parseBracketsOpt(); ArrayInitializerOrRvalue initializer = null; if (this.peekRead("=")) initializer = this.parseVariableInitializer(); return new VariableDeclarator(loc, name, brackets, initializer); } /** *
     *   Statement :=
     *     LabeledStatement |
     *     Block |
     *     IfStatement |
     *     ForStatement |
     *     WhileStatement |
     *     DoStatement |
     *     TryStatement |
     *     'switch' ... |
     *     'synchronized' ... |
     *     ReturnStatement |
     *     ThrowStatement |
     *     BreakStatement |
     *     ContinueStatement |
     *     EmptyStatement |
     *     ExpressionStatement
     * 
*/ public Statement parseStatement() throws CompileException, IOException { if (this.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; } /** *
     *   LabeledStatement := Identifier ':' Statement
     * 
*/ public Statement parseLabeledStatement() throws CompileException, IOException { String label = this.readIdentifier(); this.read(":"); return new LabeledStatement( this.location(), // location label, // label this.parseStatement() // body ); } /** *
     *   IfStatement := 'if' '(' Expression ')' Statement [ 'else' Statement ]
     * 
*/ public Statement parseIfStatement() throws CompileException, IOException { 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 ); } /** *
     *   ForStatement :=
     *     'for' '(' [ ForInit ] ';' [ Expression ] ';' [ ExpressionList ] ')' Statement
     *     | 'for' '(' FormalParameter ':' Expression ')' Statement
     *
     *   ForInit :=
     *     Modifiers Type VariableDeclarators
     *     | ModifiersOpt BasicType VariableDeclarators
     *     | Expression VariableDeclarators              (1)
     *     | Expression { ',' Expression }
     * 
* (1) "Expression" must pose a type. */ public Statement parseForStatement() throws CompileException, IOException { this.read("for"); Location forLocation = this.location(); this.read("("); BlockStatement 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 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 ); } /** *
     *   WhileStatement := 'while' '(' Expression ')' Statement
     * 
*/ 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 ); } /** *
     *   DoStatement := 'do' Statement 'while' '(' Expression ')' ';'
     * 
*/ public Statement parseDoStatement() throws CompileException, IOException { final Location location = this.location(); this.read("do"); final Statement body = this.parseStatement(); this.read("while"); this.read("("); final Rvalue condition = this.parseExpression().toRvalueOrCompileException(); this.read(")"); this.read(";"); return new DoStatement( location, // location body, // body condition // condition ); } /** *
     *   TryStatement :=
     *     'try' Block Catches [ Finally ] |
     *     'try' Block Finally
     *
     *   Catches := CatchClause { CatchClause }
     *
     *   CatchClause := 'catch' '(' FormalParameter ')' Block
     *
     *   Finally := 'finally' Block
     * 
*/ public Statement parseTryStatement() throws CompileException, IOException { Location location = this.location(); this.read("try"); final Block body = this.parseBlock(); // { CatchClause } List 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 ); } /** *
     *   SwitchStatement :=
     *     'switch' '(' Expression ')' '{' { SwitchLabels BlockStatements } '}'
     *
     *   SwitchLabels := SwitchLabels { SwitchLabels }
     *
     *   SwitchLabel := 'case' Expression ':' | 'default' ':'
     * 
*/ public Statement parseSwitchStatement() throws CompileException, IOException { final Location location = this.location(); this.read("switch"); this.read("("); final Rvalue condition = this.parseExpression().toRvalueOrCompileException(); this.read(")"); this.read("{"); List sbsgs = new ArrayList(); while (!this.peekRead("}")) { Location location2 = this.location(); boolean hasDefaultLabel = false; List caseLabels = new ArrayList(); do { if (this.peekRead("case")) { caseLabels.add(this.parseExpression().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 ); } /** *
     *   SynchronizedStatement :=
     *     'synchronized' '(' expression ')' Block
     * 
*/ 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 ); } /** *
     *   ReturnStatement := 'return' [ Expression ] ';'
     * 
*/ 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); } /** *
     *   ThrowStatement := 'throw' Expression ';'
     * 
*/ 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); } /** *
     *   BreakStatement := 'break' [ Identifier ] ';'
     * 
*/ 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); } /** *
     *   ContinueStatement := 'continue' [ Identifier ] ';'
     * 
*/ 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); } /** *
     *   AssertStatement := 'assert' Expression [ ':' Expression ] ';'
     * 
*/ 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); } /** *
     *   EmptyStatement := ';'
     * 
*/ public Statement parseEmptyStatement() throws CompileException, IOException { Location location = this.location(); this.read(";"); return new EmptyStatement(location); } /** *
     *   ExpressionList := Expression { ',' Expression }
     * 
*/ public Rvalue[] parseExpressionList() throws CompileException, IOException { List l = new ArrayList(); do { l.add(this.parseExpression().toRvalueOrCompileException()); } while (this.peekRead(",")); return (Rvalue[]) l.toArray(new Rvalue[l.size()]); } /** *
     *   Type := (
     *     'byte' | 'short' | 'char' | 'int' | 'long' |
     *     'float' | 'double' | 'boolean' |
     *     ReferenceType
     *   ) { '[' ']' }
     * 
*/ public Type parseType() throws CompileException, IOException { 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 }; /** *
     *   ReferenceType := QualifiedIdentifier [ TypeArguments ]
     * 
*/ public ReferenceType parseReferenceType() throws CompileException, IOException { return new ReferenceType(this.location(), this.parseQualifiedIdentifier(), this.parseTypeArgumentsOpt()); } /** *
     *   TypeParameters := '<' TypeParameter { ',' TypeParameter } '>'
     * 
*/ private TypeParameter[] parseTypeParametersOpt() throws CompileException, IOException { if (!this.peekRead("<")) return null; List l = new ArrayList(); l.add(this.parseTypeParameter()); while (this.read(new String[] { ",", ">" }) == 0) { l.add(this.parseTypeParameter()); } return (TypeParameter[]) l.toArray(new TypeParameter[l.size()]); } /** *
     *   TypeParameter := identifier [ 'extends' ( identifier | ReferenceType { '&' ReferenceType }
     * 
*/ private TypeParameter parseTypeParameter() throws CompileException, IOException { String name = this.readIdentifier(); if (this.peekRead("extends")) { List bound = new ArrayList(); bound.add(this.parseReferenceType()); while (this.peekRead("&")) this.parseReferenceType(); return new TypeParameter(name, (ReferenceType[]) bound.toArray(new ReferenceType[bound.size()])); } return new TypeParameter(name, null); } /** *
     *   TypeArguments := '<' TypeArgument { ',' TypeArgument } '>'
     * 
*/ 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 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); } } /** *
     *   TypeArgument :=
     *     ReferenceType { '[' ']' }    <= The optional brackets are mising in JLS7, section 18!?
     *     | BasicType '[' ']' { '[' ']' }
     *     | '?' extends ReferenceType
     *     | '?' super ReferenceType
     * 
*/ private TypeArgument parseTypeArgument() throws CompileException, IOException { if (this.peekRead("?")) { return ( this.peekRead("extends") ? new Wildcard(Wildcard.BOUNDS_EXTENDS, this.parseReferenceType()) : this.peekRead("super") ? new Wildcard(Wildcard.BOUNDS_SUPER, this.parseReferenceType()) : new Wildcard() ); } Type t = this.parseType(); int i = this.parseBracketsOpt(); for (; i > 0; i--) t = new ArrayType(t); if (!(t instanceof TypeArgument)) throw this.compileException("'" + t + "' is not a valid type argument"); return (TypeArgument) t; } /** *
     *   ReferenceTypeList := ReferenceType { ',' ReferenceType }
     * 
*/ public ReferenceType[] parseReferenceTypeList() throws CompileException, IOException { List l = new ArrayList(); l.add(this.parseReferenceType()); while (this.peekRead(",")) { l.add(this.parseReferenceType()); } return (ReferenceType[]) l.toArray(new ReferenceType[l.size()]); } /** *
     *   Expression := AssignmentExpression
     * 
*/ public Atom parseExpression() throws CompileException, IOException { return this.parseAssignmentExpression(); } /** *
     *   AssignmentExpression :=
     *     ConditionalExpression [ AssignmentOperator AssignmentExpression ]
     *
     *   AssignmentOperator :=
     *     '=' | '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' |
     *     '>>=' | '>>>=' | '&=' | '^=' | '|='
     * 
*/ 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; } /** *
     *   ConditionalExpression :=
     *     ConditionalOrExpression [ '?' Expression ':' ConditionalExpression ]
     * 
*/ public Atom parseConditionalExpression() throws CompileException, IOException { Atom a = this.parseConditionalOrExpression(); if (!this.peekRead("?")) return a; Location location = this.location(); Rvalue lhs = a.toRvalueOrCompileException(); Rvalue mhs = this.parseExpression().toRvalueOrCompileException(); this.read(":"); Rvalue rhs = this.parseConditionalExpression().toRvalueOrCompileException(); return new ConditionalExpression(location, lhs, mhs, rhs); } /** *
     *   ConditionalOrExpression :=
     *     ConditionalAndExpression { '||' ConditionalAndExpression ]
     * 
*/ public Atom parseConditionalOrExpression() throws CompileException, IOException { Atom a = this.parseConditionalAndExpression(); while (this.peekRead("||")) { Location location = this.location(); a = new BinaryOperation( location, a.toRvalueOrCompileException(), "||", this.parseConditionalAndExpression().toRvalueOrCompileException() ); } return a; } /** *
     *   ConditionalAndExpression :=
     *     InclusiveOrExpression { '&&' InclusiveOrExpression }
     * 
*/ public Atom parseConditionalAndExpression() throws CompileException, IOException { Atom a = this.parseInclusiveOrExpression(); while (this.peekRead("&&")) { Location location = this.location(); a = new BinaryOperation( location, a.toRvalueOrCompileException(), "&&", this.parseInclusiveOrExpression().toRvalueOrCompileException() ); } return a; } /** *
     *   InclusiveOrExpression :=
     *     ExclusiveOrExpression { '|' ExclusiveOrExpression }
     * 
*/ public Atom parseInclusiveOrExpression() throws CompileException, IOException { Atom a = this.parseExclusiveOrExpression(); while (this.peekRead("|")) { Location location = this.location(); a = new BinaryOperation( location, a.toRvalueOrCompileException(), "|", this.parseExclusiveOrExpression().toRvalueOrCompileException() ); } return a; } /** *
     *   ExclusiveOrExpression :=
     *     AndExpression { '^' AndExpression }
     * 
*/ public Atom parseExclusiveOrExpression() throws CompileException, IOException { Atom a = this.parseAndExpression(); while (this.peekRead("^")) { Location location = this.location(); a = new BinaryOperation( location, a.toRvalueOrCompileException(), "^", this.parseAndExpression().toRvalueOrCompileException() ); } return a; } /** *
     *   AndExpression :=
     *     EqualityExpression { '&' EqualityExpression }
     * 
*/ public Atom parseAndExpression() throws CompileException, IOException { Atom a = this.parseEqualityExpression(); while (this.peekRead("&")) { Location location = this.location(); a = new BinaryOperation( location, a.toRvalueOrCompileException(), "&", this.parseEqualityExpression().toRvalueOrCompileException() ); } return a; } /** *
     *   EqualityExpression :=
     *     RelationalExpression { ( '==' | '!=' ) RelationalExpression }
     * 
*/ public Atom parseEqualityExpression() throws CompileException, IOException { Atom a = this.parseRelationalExpression(); while (this.peek(new String[] { "==", "!=" }) != -1) { a = new BinaryOperation( this.location(), // location a.toRvalueOrCompileException(), // lhs this.read().value, // op this.parseRelationalExpression().toRvalueOrCompileException() // rhs ); } return a; } /** *
     *   RelationalExpression :=
     *     ShiftExpression {
     *       'instanceof' ReferenceType
     *       | '<' ShiftExpression [ { ',' TypeArgument } '>' ]
     *       | '<' TypeArgument [ { ',' TypeArgument } '>' ]
     *       | ( '>' | '<=' | '>=' ) ShiftExpression
     *     }
     * 
*/ public Atom parseRelationalExpression() throws CompileException, IOException { Atom a = this.parseShiftExpression(); for (;;) { if (this.peekRead("instanceof")) { Location location = this.location(); a = new Instanceof( location, a.toRvalueOrCompileException(), this.parseType() ); } else if (this.peek(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 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 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; } } } /** *
     *   ShiftExpression :=
     *     AdditiveExpression { ( '<<' | '>>' | '>>>' ) AdditiveExpression }
     * 
*/ 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; } /** *
     *   AdditiveExpression :=
     *     MultiplicativeExpression { ( '+' | '-' ) MultiplicativeExpression }
     * 
*/ 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; } /** *
     *   MultiplicativeExpression :=
     *     UnaryExpression { ( '*' | '/' | '%' ) UnaryExpression }
     * 
*/ 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; } /** *
     *   UnaryExpression :=
     *     { PrefixOperator } Primary { Selector } { PostfixOperator }
     *
     *   PrefixOperator := '++' | '--' | '+' | '-' | '~' | '!'
     *
     *   PostfixOperator := '++' | '--'
     * 
*/ 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; } /** *
     *   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
     * 
*/ 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"); } /** *
     *   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
     * 
*/ 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"); } /** *
     *   DimExprs := DimExpr { DimExpr }
     * 
*/ public Rvalue[] parseDimExprs() throws CompileException, IOException { List l = new ArrayList(); l.add(this.parseDimExpr()); while (this.peek("[") && !this.peekNextButOne("]")) { l.add(this.parseDimExpr()); } return (Rvalue[]) l.toArray(new Rvalue[l.size()]); } /** *
     *   DimExpr := '[' Expression ']'
     * 
*/ public Rvalue parseDimExpr() throws CompileException, IOException { this.read("["); Rvalue res = this.parseExpression().toRvalueOrCompileException(); this.read("]"); return res; } /** *
     *   Arguments := '(' [ ArgumentList ] ')'
     * 
*/ public Rvalue[] parseArguments() throws CompileException, IOException { this.read("("); if (this.peekRead(")")) return new Rvalue[0]; Rvalue[] arguments = this.parseArgumentList(); this.read(")"); return arguments; } /** *
     *   ArgumentList := Expression { ',' Expression }
     * 
*/ public Rvalue[] parseArgumentList() throws CompileException, IOException { List l = new ArrayList(); do { l.add(this.parseExpression().toRvalueOrCompileException()); } while (this.peekRead(",")); return (Rvalue[]) l.toArray(new Rvalue[l.size()]); } /** *
     *   Literal :=
     *     IntegerLiteral
     *     | FloatingPointLiteral
     *     | BooleanLiteral
     *     | CharacterLiteral
     *     | StringLiteral
     *     | NullLiteral
     * 
*/ public Rvalue parseLiteral() throws CompileException, IOException { Token t = this.read(); switch (t.type) { case 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"); } } /** *
     *   ExpressionStatement := Expression ';'
     * 
*/ 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 values */ 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}. *

* Notice that there is no Parser.setErrorHandler() method, but parse errors always throw a {@link * CompileException}. The reason being is that there is no reasonable way to recover from parse errors and continue * parsing, so there is no need to install a custom parse error handler. * * @param optionalWarningHandler null 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)}. *

* The handle 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(); } }