/* * This file is part of the OpenJML project. * Author: David R. Cok */ package com.sun.tools.javac.parser; import static com.sun.tools.javac.parser.Tokens.TokenKind.*; import static org.jmlspecs.openjml.JmlTokenKind.*; import java.io.PrintStream; import java.util.ArrayList; import java.util.Iterator; import java.util.Map; import org.eclipse.jdt.annotation.Nullable; import org.jmlspecs.openjml.*; import org.jmlspecs.openjml.JmlTree.IJmlLoop; import org.jmlspecs.openjml.JmlTree.JmlAbstractStatement; import org.jmlspecs.openjml.JmlTree.JmlAnnotation; import org.jmlspecs.openjml.JmlTree.JmlBlock; import org.jmlspecs.openjml.JmlTree.JmlChoose; import org.jmlspecs.openjml.JmlTree.JmlClassDecl; import org.jmlspecs.openjml.JmlTree.JmlCompilationUnit; import org.jmlspecs.openjml.JmlTree.JmlDoWhileLoop; import org.jmlspecs.openjml.JmlTree.JmlEnhancedForLoop; import org.jmlspecs.openjml.JmlTree.JmlForLoop; import org.jmlspecs.openjml.JmlTree.JmlGroupName; import org.jmlspecs.openjml.JmlTree.JmlImport; import org.jmlspecs.openjml.JmlTree.JmlMethodClause; import org.jmlspecs.openjml.JmlTree.JmlMethodClauseCallable; import org.jmlspecs.openjml.JmlTree.JmlMethodClauseConditional; import org.jmlspecs.openjml.JmlTree.JmlMethodClauseDecl; import org.jmlspecs.openjml.JmlTree.JmlMethodClauseExpr; import org.jmlspecs.openjml.JmlTree.JmlMethodClauseGroup; import org.jmlspecs.openjml.JmlTree.JmlMethodClauseSignals; import org.jmlspecs.openjml.JmlTree.JmlMethodClauseSignalsOnly; import org.jmlspecs.openjml.JmlTree.JmlMethodClauseStoreRef; import org.jmlspecs.openjml.JmlTree.JmlMethodDecl; import org.jmlspecs.openjml.JmlTree.JmlMethodInvocation; import org.jmlspecs.openjml.JmlTree.JmlMethodSig; import org.jmlspecs.openjml.JmlTree.JmlMethodSpecs; import org.jmlspecs.openjml.JmlTree.JmlQuantifiedExpr; import org.jmlspecs.openjml.JmlTree.JmlSingleton; import org.jmlspecs.openjml.JmlTree.JmlSpecificationCase; import org.jmlspecs.openjml.JmlTree.JmlStatement; import org.jmlspecs.openjml.JmlTree.JmlStatementLoop; import org.jmlspecs.openjml.JmlTree.JmlStoreRefArrayRange; import org.jmlspecs.openjml.JmlTree.JmlStoreRefKeyword; import org.jmlspecs.openjml.JmlTree.JmlTypeClause; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseConditional; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseConstraint; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseDecl; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseExpr; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseIn; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseInitializer; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseMaps; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseMonitorsFor; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseRepresents; import org.jmlspecs.openjml.JmlTree.JmlVariableDecl; import org.jmlspecs.openjml.JmlTree.JmlWhileLoop; import org.jmlspecs.openjml.esc.Label; import com.sun.source.tree.Tree.Kind; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.TypeTag; import com.sun.tools.javac.parser.JavacParser.ParensResult; import com.sun.tools.javac.parser.Tokens.Comment; import com.sun.tools.javac.parser.Tokens.Token; import com.sun.tools.javac.parser.Tokens.TokenKind; import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle; import com.sun.tools.javac.parser.Tokens.ITokenKind; import com.sun.tools.javac.tree.*; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCErroneous; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCModifiers; import com.sun.tools.javac.tree.JCTree.JCNewClass; import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; import com.sun.tools.javac.tree.JCTree.JCSkip; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.util.Assert; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Names; import com.sun.tools.javac.util.Options; import com.sun.tools.javac.util.Position; /** This class extends the javac parser to parse JML constructs as well. */ public class JmlParser extends JavacParser { /** The context this parser was created for */ // @ non_null public Context context; /** Cached value of the utilities object */ // @ non_null protected Utils utils; /** The scanner associated with the parser */ // @ non_null protected JmlScanner S; /** The node factory to use */ // @ non_null protected JmlTree.Maker jmlF; /** The table of identifiers */ // @ non_null protected Names names; /** True only when we are parsing within a model program */ protected boolean inModelProgram = false; /** * A constructor for a new parser, but you should get new instances of the * parser from the parser factory, that is, by * JmlParser.instance(context).newParser(...). * * @param fac * the node factory to use to create parse trees * @param S * the scanner to produce tokens for the parser to ask for * @param keepDocComments * whether to keep javadoc comments */ protected JmlParser(ParserFactory fac, Lexer S, boolean keepDocComments) { super(fac, S, keepDocComments, true, true); // true = keepLineMap, keepEndPositions if (!(S instanceof JmlScanner)) { log.error("jml.internal", "S expected to be a JmlScanner in JmlParser"); throw new JmlInternalError("Expected a JmlScanner for a JmlParser"); } if (!(F instanceof JmlTree.Maker)) { log.error("jml.internal", "F expected to be a JmlTree.Maker in JmlParser"); throw new JmlInternalError( "Expected a JmlTree.Maker for a JmlParser"); } this.S = (JmlScanner) S; this.jmlF = (JmlTree.Maker) F; } /** Beginning position of current token */ public int pos() { return token.pos; } /** End position of current token */ public int endPos() { return token.endPos; } public JmlTokenKind jmlTokenKind() { return token.ikind instanceof JmlTokenKind ? (JmlTokenKind)token.ikind : null; } /** Returns the scanner being used by the parser */ public JmlScanner getScanner() { return S; } /** Returns the node factory being used by the parser */ public JmlTree.Maker maker() { return jmlF; } public void rescan() { token = S.rescan(); } /** Returns true if the -deprecation option is set */ public boolean isDeprecationSet() { return Options.instance(context).isSet("-Xlint:deprecation"); } /** * Parses a compilation unit using tokens from the scanner - generally the * entry point used to parse a Java/JML file. */ @Override public JCTree.JCCompilationUnit parseCompilationUnit() { JCTree.JCCompilationUnit u = super.parseCompilationUnit(); if (!(u instanceof JmlCompilationUnit)) { log.error( "jml.internal", "JmlParser.compilationUnit expects to receive objects of type JmlCompilationUnit, but it found a " + u.getClass() + " instead, for source " + u.getSourceFile().toUri().getPath()); } else { JmlCompilationUnit jmlcu = (JmlCompilationUnit) u; setTopLevel(jmlcu,jmlcu.defs); } return u; } /** Recursively sets the toplevel field of class declarations */ private void setTopLevel(JmlCompilationUnit cu, List<JCTree> defs) { for (JCTree t : defs) { if (t instanceof JmlClassDecl) { JmlClassDecl jcd = (JmlClassDecl) t; jcd.toplevel = cu; setTopLevel(cu, jcd.defs); } } } /** * Overrides the super class importDeclaration in order to recognize model * import statements. * * @return null or a JCImport node */ // @ ensures \result == null || \result instanceof JCTree.JCImport; // @ nullable @Override protected JCTree importDeclaration(JCModifiers mods) { int p = pos(); boolean modelImport = false; for (JCAnnotation a: mods.annotations) { if (a.annotationType.toString().equals("org.jmlspecs.annotation.Model")) { modelImport = true; } else jmlerror(a.pos, "jml.no.mods.on.import"); } boolean importIsInJml = S.jml(); if (!modelImport && importIsInJml) { jmlerror(p, endPos(), "jml.import.no.model"); modelImport = true; } JCTree t = super.importDeclaration(mods); ((JmlImport) t).isModel = modelImport; if (modelImport && !importIsInJml) { jmlerror(p, t.getEndPosition(endPosTable), "jml.illformed.model.import"); } if (jmlTokenKind() == JmlTokenKind.ENDJMLCOMMENT) { nextToken(); } return t; } @Override JCAnnotation annotation(int pos, JCTree.Tag kind) { JCAnnotation a = super.annotation(pos, kind); ((JmlTree.JmlAnnotation)a).sourcefile = log.currentSourceFile(); return a; } /** * This parses a class, interface or enum declaration after the parser has * seen a group of modifiers and an optional javadoc comment. This can be a * declaration at the top-level in the compilation unit, within a class * body, or a local declaration in a method body. * * @param mods * the preceding modifiers and (java) annotations * @param dc * the preceding javadoc comment * @return a JCStatement that is a declaration */ @Override protected JCStatement classOrInterfaceOrEnumDeclaration(JCModifiers mods, Comment dc) { boolean prevInJmlDeclaration = inJmlDeclaration; JCStatement s; try { if (S.jml()) { if (mods == null) { mods = jmlF.at(Position.NOPOS).Modifiers(0); storeEnd(mods, Position.NOPOS); } if (!inJmlDeclaration) utils.setJML(mods); inJmlDeclaration = true; } if (token.kind == IMPORT) { // FIXME - I don't think this is used anymore JCAnnotation a = utils .findMod( mods, names.fromString(Strings.modelAnnotation)); if (a != null) { skipToSemi(); int p = mods.annotations.head.pos; jmlerror(p, endPos(), "jml.illformed.model.import"); s = toP(F.Exec(toP(F.at(p).Erroneous(List.<JCTree> nil())))); skipThroughSemi(); } else { int p = mods.annotations.head.pos; s = toP(F.Exec(toP(F.at(p).Erroneous(List.<JCTree> nil())))); jmlerror(mods.getStartPosition(), getEndPos(mods), "jml.no.mods.on.import"); } mods.flags = 0; mods.annotations = List.<JCAnnotation> nil(); } else if (!inJmlDeclaration || token.kind == CLASS || token.kind == INTERFACE || token.kind == ENUM) { // The guard above is used because if it is false, we want to produce // a better error message than we otherwise get, for misspelled // JML modifiers. However, the test above replicates tests in // the super method and may become obsolete. s = super.classOrInterfaceOrEnumDeclaration(mods, dc); } else { int p = pos(); jmlerror(pos(), endPos(), "jml.unexpected.or.misspelled.jml.token", token == null ? jmlTokenKind().internedName() : S.chars() ); setErrorEndPos(pos()); s = toP(F.Exec(toP(F.at(p).Erroneous(List.<JCTree> nil())))); //nextToken(); } if (s instanceof JCClassDecl && (((JCClassDecl)s).mods.flags & Flags.ENUM) != 0) { ListBuffer<JCExpression> args = new ListBuffer<JCExpression>(); JCClassDecl cd = (JCClassDecl)s; Name n = jmlF.Name(Strings.enumVar); JCExpression disj = null; for (JCTree d: cd.defs) { if (!(d instanceof JCVariableDecl)) continue; JCVariableDecl decl = (JCVariableDecl)d; long flags = decl.mods.flags; long expected = Flags.PUBLIC | Flags.STATIC | Flags.FINAL; if ((flags & expected) != expected || decl.init == null) continue; if (!(decl.vartype instanceof JCIdent && ((JCIdent)decl.vartype).name.equals(cd.name))) continue; JCExpression id = jmlF.at(d.pos).Ident(decl.getName()); args.add(id); id = jmlF.at(d.pos).Ident(decl.getName()); JCExpression ide = jmlF.at(d.pos).Ident(n); JCExpression ex = jmlF.at(id.pos).Binary(JCTree.Tag.EQ, ide, id); disj = disj == null ? ex : jmlF.at(ex.pos).Binary(JCTree.Tag.OR,disj,ex); } args.add(F.Literal(TypeTag.BOT,null)); JCExpression ex = jmlF.at(s.pos).JmlMethodInvocation(JmlTokenKind.BSDISTINCT,args.toList()); JmlTypeClauseExpr axiom = jmlF.at(s.pos).JmlTypeClauseExpr(jmlF.Modifiers(0),JmlTokenKind.AXIOM,ex); cd.defs = cd.defs.append(axiom); JCVariableDecl decl = jmlF.at(cd.pos).VarDef(jmlF.Modifiers(0),n,jmlF.Ident(jmlF.Name("Object")),null); ex = jmlF.JmlQuantifiedExpr(JmlTokenKind.BSFORALL,List.<JCVariableDecl>of(decl), null, jmlF.JmlBinary(JmlTokenKind.EQUIVALENCE, jmlF.TypeTest(jmlF.Ident(n), jmlF.Ident(cd.getSimpleName())),disj)); axiom = jmlF.at(s.pos).JmlTypeClauseExpr(jmlF.Modifiers(0),JmlTokenKind.AXIOM,ex); cd.defs = cd.defs.append(axiom); } // Can also be a JCErroneous // if (s instanceof JmlClassDecl) { // filterTypeBodyDeclarations((JmlClassDecl) s, context, jmlF); // } if (jmlTokenKind() == JmlTokenKind.ENDJMLCOMMENT) { nextToken(); } } finally { inJmlDeclaration = prevInJmlDeclaration; } return s; } /** * This override is needed in order to manage the value of S.jmlKeyword. In * parsing a class body, the value starts out true; however, the value might * be false on entering this method if we are parsing the class body of an * anonymous class expression. */ @Override List<JCTree> classOrInterfaceBody(Name className, boolean isInterface) { boolean savedJmlKeyword = S.jmlkeyword; S.setJmlKeyword(true); try { return super.classOrInterfaceBody(className, isInterface); } finally { S.setJmlKeyword(savedJmlKeyword); rescan(); } } // /** // * This parses a sequence of statements that can appear in a block. JML // * overrides it in order to include JML assert, assume, set, debug, ghost // * declarations, unreachable, loop invariants and any other JML specs that // * can appear in the body of a method. // * // * @return a list of JmlStatement nodes (despite the required type of the // * method) // */ // @Override // public List<JCStatement> blockStatements() { // ListBuffer<JCStatement> list = new ListBuffer<JCStatement>(); // int pos = -1; // JCModifiers mods = null; // while (token.kind != RBRACE && token.kind != EOF) { // // If the following equality is true, then the scanner has made no // // progress in the previous loop iteration. Presumably some error // // has occurred; for example no next statement is recognized. // // So we just abort the loop. Since the next token is not a right // // brace, an error will be issued when an attempt is made to read // // the right brace that closes the block. // if (S.currentPos() == pos) break; // pos = S.currentPos(); // // Only certain qualifiers can appear here - perhaps limit the // // parsing of modifiers to just that set. Java explicitly has // // final abstract strictfp; also synchronized but not as a modifier. // // FIXME - this needs more testing // if (token.kind != SYNCHRONIZED) { // mods = modifiersOpt(); // read any additional modifiers (e.g. // // JML ones) // } // JmlTokenKind jt = jmlTokenKind(); // if (jt != null) { // // if (isJmlTypeToken(jt)) { // JCExpression t = parseType(); // ListBuffer<JCStatement> vdefs = new ListBuffer<JCStatement>(); // variableDeclarators(mods, t, vdefs); // if (token.kind == SEMI) { // accept(SEMI); // } else { // accept(SEMI); // To get the error message // skipThroughSemi(); // } // for (JCStatement vdef : vdefs) { // JmlVariableDecl jmldef = (JmlVariableDecl) vdef; // utils.setJML(jmldef.mods); // list.append(vdef); // } // } else { // pushBackModifiers = mods; // mods = null; // JCStatement st = parseStatement(); // list.append(st); // } // } else { // pushBackModifiers = mods; // mods = null; // if (S.jml()) { // boolean prevInJmlDeclaration = inJmlDeclaration; // inJmlDeclaration = true; // List<JCTree.JCStatement> dlist = super.blockStatements(); // inJmlDeclaration = prevInJmlDeclaration; // if (inJmlDeclaration || inLocalOrAnonClass) { // // In this case we are in the body of a model method. // // Within the body we don't mark any local variables // // or classes as ghost or model (with setJML). // list.appendList(dlist); // } else { // for (JCTree.JCStatement t : dlist) { // if (t instanceof JmlVariableDecl) { // JmlVariableDecl d = (JmlVariableDecl) t; // utils.setJML(d.mods); // } else if (t instanceof JmlClassDecl) { // JmlClassDecl d = (JmlClassDecl) t; // utils.setJML(d.mods); // } else if (t instanceof JCTree.JCSkip) { // // An empty statement is not really allowed // // within // // a JML annotation, but it is common to find // // one // // after a local class declaration, so we won't // // complain. // } else { // jmlerror(t.pos, "jml.expected.decl.or.jml"); // } // list.append(t); // } // } // } else { // list.appendList(super.blockStatements()); // } // } // } // } @Override List<JCStatement> blockStatements() { List<JCStatement> stats = super.blockStatements(); ListBuffer<JCStatement> newstats = new ListBuffer<>(); ListBuffer<JmlStatementLoop> loopspecs = new ListBuffer<>(); for (JCStatement s: stats) { if (s instanceof JmlStatementLoop) { loopspecs.add((JmlStatementLoop)s); continue; } if (!loopspecs.isEmpty()) { if (s instanceof IJmlLoop) { ((IJmlLoop)s).setLoopSpecs(loopspecs.toList()); } else { jmlerror(loopspecs.first().pos, loopspecs.last().getEndPosition(endPosTable), "jml.loop.spec.misplaced"); } loopspecs = new ListBuffer<>(); } newstats.add(s); } if (!loopspecs.isEmpty()) { jmlerror(loopspecs.first().pos, loopspecs.last().getEndPosition(endPosTable), "jml.loop.spec.misplaced"); loopspecs = new ListBuffer<>(); } return newstats.toList(); } @Override protected List<JCStatement> blockStatement() { while (true) { if (!(token instanceof JmlToken)) { boolean inJml = S.jml(); List<JCStatement> stats = super.blockStatement(); if (inJml) { for (JCStatement s: stats) { if (s instanceof JCVariableDecl) { utils.setJML(((JCVariableDecl)s).mods); } else if (s instanceof JCClassDecl || s instanceof JmlAbstractStatement || s instanceof JCSkip) { // OK } else if (!inJmlDeclaration && !inModelProgram && !inLocalOrAnonClass) { // FIXME - unsure of this test jmlerror(s.pos, "jml.expected.decl.or.jml"); } } } return stats; } JmlTokenKind jkind = ((JmlToken)token).jmlkind; if (modifiers.contains(jkind)) { // MAINTENCE: Copied from JavacParser.blockStatement, FINAL case Comment dc = token.comment(CommentStyle.JAVADOC); JCModifiers mods = modifiersOpt(); if (S.jml()) utils.setJML(mods); // Added this to mark declarations in JML annotations if (token.kind == INTERFACE || token.kind == CLASS || allowEnums && token.kind == ENUM) { return List.of(classOrInterfaceOrEnumDeclaration(mods, dc)); } else { JCExpression t = parseType(); ListBuffer<JCStatement> stats = variableDeclarators(mods, t, new ListBuffer<JCStatement>()); // A "LocalVariableDeclarationStatement" subsumes the terminating semicolon storeEnd(stats.last(), token.endPos); accept(SEMI); return stats.toList(); } } else if (jkind == ENDJMLCOMMENT) { S.setJml(false); nextToken(); continue; } JCStatement s = parseStatement(); return List.<JCStatement>of(s); } } /** Overridden to parse JML statements */ @Override public JCStatement parseStatement() { JCStatement st; String reason = null; JmlTokenKind jtoken = jmlTokenKind(); if (token.kind == CUSTOM) { boolean needSemi = true; if (jtoken != JmlTokenKind.ENDJMLCOMMENT) { int pos = pos(); JmlSpecificationCase spc; if (jtoken != null) reason = jtoken.internedName() + " statement"; if (jtoken == JmlTokenKind.ASSUME || jtoken == JmlTokenKind.ASSERT) { S.setJmlKeyword(false); nextToken(); JCExpression t = null; t = parseExpression(); JmlTree.JmlStatementExpr ste = jmlF .at(pos) .JmlExpressionStatement( jtoken, jtoken == JmlTokenKind.ASSUME ? Label.EXPLICIT_ASSUME : Label.EXPLICIT_ASSERT, t); if (token.kind == COLON) { nextToken(); ste.optionalExpression = parseExpression(); } toP(ste); ste.source = log.currentSourceFile(); //ste.line = log.currentSource().getLineNumber(pos); st = ste; } else if (jtoken == UNREACHABLE || jtoken == REACHABLE) { S.setJmlKeyword(false); nextToken(); JCExpression t = null; if (token.ikind != ENDJMLCOMMENT && token.kind != SEMI) { t = parseExpression(); } JmlTree.JmlStatementExpr ste = to(jmlF.at(pos) .JmlExpressionStatement(jtoken, jtoken == REACHABLE ? Label.REACHABLE : Label.UNREACHABLE, t)); ste.source = log.currentSourceFile(); st = ste; if (JmlOption.isOption(context, JmlOption.STRICT)) { // FIXME - why the difference in the positions of these two warnings if (jtoken == UNREACHABLE && t != null) log.warning(pos(),"jml.not.strict","unreachable statement with expression"); if (jtoken == REACHABLE) log.warning(ste.pos,"jml.not.strict",jtoken.internedName()); } } else if (jtoken == HENCE_BY) { S.setJmlKeyword(false); nextToken(); JCExpression t = parseExpression(); JmlTree.JmlStatementExpr ste = to(jmlF.at(pos) .JmlExpressionStatement(jtoken, Label.EXPLICIT_ASSUME, t)); ste.source = log.currentSourceFile(); st = ste; } else if (jtoken == DECREASES || jtoken == LOOP_INVARIANT) { S.setJmlKeyword(false); nextToken(); JCExpression t = parseExpression(); JmlStatementLoop ste = to(jmlF.at(pos).JmlStatementLoop( jtoken, t)); // ste.source = log.currentSourceFile(); // ste.line = log.currentSource().getLineNumber(pos); st = ste; } else if (jtoken == JmlTokenKind.HAVOC ) { S.setJmlKeyword(false); nextToken(); ListBuffer<JCExpression> list = parseStoreRefList(false); if (token.kind == SEMI) { // OK, go on } else if (jmlTokenKind() == ENDJMLCOMMENT) { syntaxError(pos(), null, "jml.missing.semi"); } S.setJmlKeyword(true); if (token.kind != SEMI) { // error already reported skipThroughSemi(); } else { if (list.isEmpty()) { syntaxError(pos(), null, "jml.use.nothing.assignable"); } nextToken(); } st = toP(jmlF.at(pos).JmlHavocStatement(list.toList())); S.setJmlKeyword(true); // This comes a token too late. rescan(); needSemi = false; } else if (jtoken == JmlTokenKind.SET || jtoken == JmlTokenKind.DEBUG) { S.setJmlKeyword(false); nextToken(); // Only StatementExpressions are allowed - // assignment statements and stand-alone method calls - // but JML constructs are allowed. boolean prev = inJmlDeclaration; inJmlDeclaration = true; JCStatement t = super.parseStatement(); inJmlDeclaration = prev; if (!(t instanceof JCExpressionStatement)) { jmlerror(t.getStartPosition(), getEndPos(t), "jml.bad.statement.in.set.debug"); t = null; } st = toP(jmlF.at(pos).JmlStatement(jtoken, (JCExpressionStatement)t)); S.setJmlKeyword(true); // This comes a token too late. rescan(); needSemi = false; } else if (methodClauseTokens.contains(jtoken)) { if (JmlOption.isOption(context, JmlOption.STRICT)) { log.warning(pos(),"jml.refining.required"); } JCModifiers mods = jmlF.Modifiers(0); JmlMethodSpecs specs = parseMethodSpecs(mods); for (JmlSpecificationCase c : specs.cases) { if (!isNone(c.modifiers)) { jmlerror(c.modifiers.getStartPosition(), getEndPos(c.modifiers), "jml.no.mods.in.refining"); } } st = jmlF.at(pos).JmlStatementSpec(specs); storeEnd(st, getEndPos(specs)); needSemi = false; } else if (jtoken == JmlTokenKind.REFINING) { nextToken(); if (jmlTokenKind() == JmlTokenKind.ALSO) { jmlerror(pos(), endPos(), "jml.invalid.also"); nextToken(); } JCModifiers mods = modifiersOpt(); JmlMethodSpecs specs = parseMethodSpecs(mods); for (JmlSpecificationCase c : specs.cases) { if (!isNone(c.modifiers)) { jmlerror(c.modifiers.getStartPosition(), getEndPos(c.modifiers), "jml.no.mods.in.refining"); } } st = jmlF.at(pos).JmlStatementSpec(specs); storeEnd(st, getEndPos(specs)); needSemi = false; } else if (inModelProgram && jtoken == JmlTokenKind.CHOOSE) { st = parseChoose(); return toP(st); // FIXME - is this the correct end position? } else if (inModelProgram && jtoken == JmlTokenKind.CHOOSE_IF) { st = parseChooseIf(); return toP(st); // FIXME - is this the correct end position? } else if (inModelProgram && jtoken == JmlTokenKind.INVARIANT) { JCTree t = parseInvariantInitiallyAxiom(null); st = jmlF.JmlModelProgramStatement(t); return st; } else if (inModelProgram && (spc = parseSpecificationCase(null, false)) != null) { st = jmlF.JmlModelProgramStatement(spc); return st; // A JML statement can also be a ghost declaration that // begins with a JML type. These are parsed in // blockStatements() because they produce more than one // statement at a time. // This (old) implementation only allowed one variable to be // declared. // } else if (isJmlTypeToken(jtoken)) { // JCExpression t = parseType(); // ListBuffer<JCStatement> vdefs = new // ListBuffer<JCStatement>(); // variableDeclarators(null, t, vdefs); // st = vdefs.first(); // accept(SEMI); // utils.setJML(((JmlVariableDecl) st).mods); // return st; } else if (JmlTokenKind.modifiers.contains(jtoken)) { List<JCStatement> stats = super.blockStatement(); if (stats.size() > 1) jmlerror(stats.get(0).pos, "internal.notsobad", "Multiple statements where one expected"); // FIXME st = stats.get(0); needSemi = false; } else { jmlerror(pos(), endPos(), "jml.unknown.statement", jtoken.internedName()); skipToSemi(); st = jmlF.at(pos()).Skip(); } } else { nextToken(); // swallows the ENDJMLCOMMENT JCStatement stt = super.parseStatement(); return stt; } S.setJmlKeyword(true); if (!needSemi) { // If we do not need a semicolon yet (e.g. because we already // parsed it or because the statement does not end with one, // then the scanner has already scanned the next symbol, // with setJmlKeyword having been (potentially) false. // So we need to do the following conversion. if (token.kind == IDENTIFIER && S.jml()) { JmlTokenKind tt = JmlTokenKind.allTokens.get(S.chars()); if (tt != null) { S.setToken(new JmlToken(tt, null, pos(), endPos())); // FIXME - but S.token is not set } } } else if (token.ikind == ENDJMLCOMMENT) { log.warning(pos()-2, "jml.missing.semi", jtoken); } else if (token.kind != SEMI) { jmlerror(pos(), endPos(), "jml.bad.construct", reason); skipThroughSemi(); } else { nextToken(); // skip the semi } if (jmlTokenKind() == JmlTokenKind.ENDJMLCOMMENT) nextToken(); return st; } // if (S.jml() && !inModelProgram && !inJmlDeclaration && !inLocalOrAnonClass) { // FIXME - unsure of this test // jmlerror(pos(),"jml.expected.decl.or.jml"); // } JCStatement stt = super.parseStatement(); return stt; } /* Replicated and slightly altered from JavacParser in order to handle the case where the one statement * is a JML statement. */ JCStatement parseStatementAsBlock() { int pos = token.pos; List<JCStatement> stats = blockStatement(); JCStatement first = stats.head; while (first instanceof JmlAbstractStatement) { List<JCStatement> nextstats = blockStatement(); first = nextstats.head; stats = stats.appendList(nextstats); } if (first == null) { JCErroneous e = F.at(pos).Erroneous(); error(e, "illegal.start.of.stmt"); return F.at(pos).Exec(e); } else { String error = null; switch (first.getTag()) { case CLASSDEF: error = "class.not.allowed"; break; case VARDEF: error = "variable.not.allowed"; break; } if (error != null) { error(first, error); List<JCBlock> blist = List.of(F.at(first.pos).Block(0, stats)); return toP(F.at(pos).Exec(F.at(first.pos).Erroneous(blist))); } if (stats.size() > 1) return F.at(first.pos).Block(0, stats); return first; } } /** Returns true if the token is a JML type token */ public boolean isJmlTypeToken(JmlTokenKind t) { return t == JmlTokenKind.BSTYPEUC || t == JmlTokenKind.BSBIGINT || t == JmlTokenKind.BSREAL; } /** Parses a choose statement (the choose token is already read) */ public JmlChoose parseChoose() { JmlTokenKind jt = jmlTokenKind(); nextToken(); ListBuffer<JCBlock> orBlocks = new ListBuffer<JCBlock>(); orBlocks.append(block()); // FIXME - here and below - what if block() // returns null. while (jmlTokenKind() == JmlTokenKind.OR) { nextToken(); orBlocks.append(block()); } return jmlF.JmlChoose(jt, orBlocks.toList(), null); } /** * Parses a choose_if statement (the choose_if token is already read); * choose_if statements differ from choose statements in that the first * statement of a choose_if must be an assume statement. This is not checked * here. (FIXME - check for the assume?) */ public JmlChoose parseChooseIf() { JmlTokenKind jt = jmlTokenKind(); ListBuffer<JCBlock> orBlocks = new ListBuffer<JCBlock>(); JCBlock elseBlock = null; nextToken(); orBlocks.append(block()); while (jmlTokenKind() == JmlTokenKind.OR) { nextToken(); orBlocks.append(block()); } if (token.kind == ELSE) { nextToken(); elseBlock = block(); } return jmlF.JmlChoose(jt, orBlocks.toList(), elseBlock); } /** * Prints an AST using JmlDebugTreePrinter * * @param t * the tree to print * @param out * the PrintStream on which to write the output */ void printTree(JCTree t, PrintStream out) { new JmlDebugTreePrinter(out, endPosTable).scan(t); } /** * when true we are parsing declarations within a model method or class, so * the individual declarations are not themselves considered JML * declarations even though they may be within a JML comment. */ protected boolean inJmlDeclaration = false; protected @Nullable JmlMethodSpecs currentMethodSpecs = null; protected @Nullable JmlVariableDecl currentVariableDecl = null; /** * Overridden in order to parse JML declarations and clauses within the body * of a class or interface. */ @Override protected List<JCTree> classOrInterfaceBodyDeclaration(Name className, boolean isInterface) { ListBuffer<JCTree> list = new ListBuffer<JCTree>(); loop: while (true) { JmlVariableDecl mostRecentVarDecl = currentVariableDecl; currentVariableDecl = null; Comment dc = token.comment(CommentStyle.JAVADOC); if (jmlTokenKind() == ENDJMLCOMMENT) { nextToken(); // swallows the ENDJMLCOMMENT currentVariableDecl = mostRecentVarDecl; break loop; } if (S.jml()) S.setJmlKeyword(true); JCModifiers mods = modifiersOpt(); // Gets anything that is in // pushBackModifiers int pos = pos(); JmlTokenKind jt = jmlTokenKind(); // check: if (S.jml() && jt == null && !inJmlDeclaration && !inLocalOrAnonClass) { // String tokenString = S.chars(); // if (!tokenString.isEmpty()) { // if (mods.annotations != null) for (JCAnnotation a: mods.annotations) { // if (a.annotationType.toString().endsWith("Model")) break check; // if (a.annotationType.toString().endsWith("Ghost")) break check; // } // log.error(pos, "jml.bad.keyword", tokenString); // skipThroughSemi(); // break loop; // } // } if (jt == null || isJmlTypeToken(jt)) { pushBackModifiers = mods; // This is used to pass the modifiers // into super.classOrInterfaceBodyDeclaration mods = null; boolean startsInJml = S.jml(); List<JCTree> t; if (startsInJml && !inLocalOrAnonClass) { boolean prevInJmlDeclaration = inJmlDeclaration; inJmlDeclaration = true; t = super.classOrInterfaceBodyDeclaration( className, isInterface); inJmlDeclaration = prevInJmlDeclaration; } else { // no longer in JML // FIXME - attach doc comment? t = super.classOrInterfaceBodyDeclaration( className, isInterface); } if (!inJmlDeclaration) { for (JCTree tr : t) { JCTree ttr = tr; if (tr instanceof JmlClassDecl) { JmlClassDecl d = (JmlClassDecl) tr; if (startsInJml) utils.setJML(d.mods); //d.toplevel.sourcefile = log.currentSourceFile(); ttr = tr; // toP(jmlF.at(pos).JmlTypeClauseDecl(d)); attach(d, dc); } else if (tr instanceof JmlMethodDecl) { JmlMethodDecl d = (JmlMethodDecl) tr; if (startsInJml) utils.setJML(d.mods); d.sourcefile = log.currentSourceFile(); ttr = tr; // toP(jmlF.at(pos).JmlTypeClauseDecl(d)); attach(d, dc); d.cases = currentMethodSpecs; if (currentMethodSpecs != null) { currentMethodSpecs.decl = d; currentMethodSpecs = null; } } else if (tr instanceof JmlBlock) { JmlBlock d = (JmlBlock) tr; ttr = tr; // toP(jmlF.at(pos).JmlTypeClauseDecl(d)); attach(d, dc); d.cases = currentMethodSpecs; if (currentMethodSpecs != null) { currentMethodSpecs.decl = null; // FIXME - point to the block? currentMethodSpecs = null; } } else if (tr instanceof JmlVariableDecl) { JmlVariableDecl d = (JmlVariableDecl) tr; if (startsInJml) utils.setJML(d.mods); d.sourcefile = log.currentSourceFile(); ttr = tr; // toP(jmlF.at(pos).JmlTypeClauseDecl(d)); attach(d, dc); currentVariableDecl = d; } else { ttr = null; } // if (tr != null && utils.findMod(tmods, JmlTokenKind.MODEL) == null && utils.findMod(tmods, JmlTokenKind.GHOST) == null) { // jmlerror(tr.pos, "jml.missing.ghost.model"); // } dc = null; if (ttr != null) list.append(ttr); } } else if (t.head instanceof JmlMethodDecl) { JmlMethodDecl d = (JmlMethodDecl) t.head; if (startsInJml) utils.setJML(d.mods); d.sourcefile = log.currentSourceFile(); attach(d, dc); d.cases = currentMethodSpecs; if (currentMethodSpecs != null) { currentMethodSpecs.decl = d; currentMethodSpecs = null; } list.append(d); } else if (t.head instanceof JmlTypeClauseIn || t.head instanceof JmlTypeClauseMaps) { JCTree tree = t.head; if (tree instanceof JmlTypeClauseIn) { ((JmlTypeClauseIn) tree).parentVar = mostRecentVarDecl; } if (mostRecentVarDecl == null) { log.error(tree.pos(), "jml.misplaced.var.spec", ((JmlTypeClause) tree).token.internedName()); } else { if (mostRecentVarDecl.fieldSpecs == null) { mostRecentVarDecl.fieldSpecs = new JmlSpecs.FieldSpecs( mostRecentVarDecl); } mostRecentVarDecl.fieldSpecs.list .append((JmlTypeClause) tree); currentVariableDecl = mostRecentVarDecl; } } else if (t.head instanceof JmlMethodSpecs) { currentMethodSpecs = (JmlMethodSpecs)t.head; } else { list.appendList(t); } break; } else if (jt == INVARIANT || jt == INITIALLY || jt == AXIOM) { list.append(parseInvariantInitiallyAxiom(mods)); } else if (jt == CONSTRAINT) { list.append(parseConstraint(mods)); } else if (jt == REPRESENTS) { list.append(parseRepresents(mods)); } else if (methodClauseTokens.contains(jt) || specCaseTokens.contains(jt) || jt == SPEC_GROUP_START) { currentMethodSpecs = parseMethodSpecs(mods); //list.append(parseMethodSpecs(mods)); // FIXME - attach doc comment? // getMethodSpecs may have already parsed some modifiers. // They will be in pushBackModifiers } else if (jt == IN) { JmlTypeClauseIn inClause = parseIn(pos, mods); inClause.parentVar = mostRecentVarDecl; if (mostRecentVarDecl == null) { log.error(inClause.pos(), "jml.misplaced.var.spec", inClause.token.internedName()); } else { if (mostRecentVarDecl.fieldSpecs == null) { mostRecentVarDecl.fieldSpecs = new JmlSpecs.FieldSpecs( mostRecentVarDecl); } mostRecentVarDecl.fieldSpecs.list.append(inClause); currentVariableDecl = mostRecentVarDecl; } } else if (jt == MAPS) { JmlTypeClauseMaps mapsClause = parseMaps(pos, mods, list); if (mostRecentVarDecl == null) { log.error(mapsClause.pos(), "jml.misplaced.var.spec", mapsClause.token.internedName()); } else { if (mostRecentVarDecl.fieldSpecs == null) { mostRecentVarDecl.fieldSpecs = new JmlSpecs.FieldSpecs( mostRecentVarDecl); } mostRecentVarDecl.fieldSpecs.list.append(mapsClause); currentVariableDecl = mostRecentVarDecl; } } else if (jt == READABLE || jt == WRITABLE) { list.append(parseReadableWritable(mods, jt)); } else if (jt == MONITORS_FOR) { list.append(parseMonitorsFor(mods)); } else if (jt == INITIALIZER || jt == STATIC_INITIALIZER) { //@ FIXME - modifiers? JmlTypeClauseInitializer initializer = jmlF.at(pos()).JmlTypeClauseInitializer(jt,mods); initializer.specs = currentMethodSpecs; currentMethodSpecs = null; list.append(to(initializer)); nextToken(); } else { jmlerror(pos(), endPos(), "jml.illegal.token.for.declaration", jt.internedName()); skipThroughSemi(); break; } } return list.toList(); } /** * This method runs through a list of declarations in a class body, finding * the method and field annotations and associating them with the correct * declarations, issuing errors if they are in the wrong place. It also sorts * out the Java declarations and the specifications. * */ // // This is static because it is called outside the parser as well // static public void filterTypeBodyDeclarations(JmlClassDecl decl, // Context context, JmlTree.Maker jmlF) { // Log log = Log.instance(context); // // // decl.defs contains all of the declarations in the decl class, // // including method and field annotations as individual declarations. // // In the end we want two things: // // a) All method and field specs associated with the appropriate method // // or field declaration, and not in the decl.defs list // // b) The decl.defs list should just have Java declarations and the // // decl.typespecs list should have all the class, method, field and initializer // // declarations that were in decl.defs, in the same order. // List<JCTree> list = decl.defs; // JmlSpecs.TypeSpecs typeSpecs = new JmlSpecs.TypeSpecs(decl); // ListBuffer<JCTree> javalist = new ListBuffer<>(); // JmlVariableDecl currentVarDecl = null; // JmlVariableDecl mostRecentVarDecl = null; // JmlMethodSpecs currentMethodSpecs = null; // // Iterator<JCTree> iter = list.iterator(); // while (iter.hasNext()) { // JCTree tree = iter.next(); // mostRecentVarDecl = currentVarDecl; // currentVarDecl = null; // if (tree instanceof JmlVariableDecl) { // // A Java field declaration // currentVarDecl = (JmlVariableDecl) tree; // JCModifiers mods = currentVarDecl.mods; // if (currentVarDecl.fieldSpecs == null) { // currentVarDecl.fieldSpecs = new JmlSpecs.FieldSpecs(mods); // } else { // currentVarDecl.fieldSpecs.mods.annotations.appendList(mods.annotations); // } // javalist.append(tree); // // } else if (tree instanceof JmlTypeClauseIn // || tree instanceof JmlTypeClauseMaps) { // /** // * in and maps clauses get attached to the just preceding // * variable declaration // */ // if (tree instanceof JmlTypeClauseIn) { // ((JmlTypeClauseIn) tree).parentVar = mostRecentVarDecl; // } // if (mostRecentVarDecl == null) { // log.error(tree.pos(), "jml.misplaced.var.spec", // ((JmlTypeClause) tree).token.internedName()); // } else { // if (mostRecentVarDecl.fieldSpecs == null) { // mostRecentVarDecl.fieldSpecs = new JmlSpecs.FieldSpecs( // mostRecentVarDecl.mods); // } // mostRecentVarDecl.fieldSpecs.list // .append((JmlTypeClause) tree); // currentVarDecl = mostRecentVarDecl; // } // } else if (tree instanceof JmlMethodDecl) { // JmlMethodDecl mdecl = (JmlMethodDecl) tree; // mdecl.cases = currentMethodSpecs; // currentMethodSpecs.decl = mdecl; // javalist.append(mdecl); // currentMethodSpecs = null; // } else if (tree instanceof JmlMethodSpecs) { // // Method specifications come before the declaration they // // specify. // // They can apply to (a) a method declaration, (b) a ghost or // // model method declaration // // (c) a JML initializer clause, or (d) a Java initializer // // block. // // All consecutive method spec clauses are grouped together into // // one JmlMethodSpecs // currentMethodSpecs = (JmlMethodSpecs) tree; // continue; // // } else if (tree instanceof JmlTypeClauseDecl) { // // A ghost or model declaration within a class // JmlTypeClauseDecl tcd = (JmlTypeClauseDecl) tree; // tree = tcd.decl; // if (tree instanceof JmlVariableDecl) { // if (currentMethodSpecs != null) log.error(currentMethodSpecs.pos(), "jml.misplaced.method.spec"); // currentVarDecl = (JmlVariableDecl) tree; // } else if (tree instanceof JmlMethodDecl) { // if ( ((JmlMethodDecl)tree).name.toString().equals("initialCharSequence")) Utils.stop(); // JmlMethodDecl mdecl = (JmlMethodDecl) ((JmlTypeClauseDecl) tree).decl; // mdecl.cases = currentMethodSpecs; // } else if (tree instanceof JmlClassDecl) { // // OK // if (currentMethodSpecs != null) log.error(currentMethodSpecs.pos(), "jml.misplaced.method.spec"); // typeSpecs.modelTypes.add((JmlClassDecl)tree); // tree = null; // } else { // if (currentMethodSpecs != null) log.error(currentMethodSpecs.pos(), "jml.misplaced.method.spec"); // log.error(tree.pos(), "jml.internal.notsobad", // "An unknown kind of JmlTypeClauseDecl was encountered and not handled: " // + tree.getClass()); // tree = null; // } // currentMethodSpecs = null; // if (tree != null) typeSpecs.decls.append(tcd); // // // All the declarations need to be kept in the class declaration, // // in the order in which they are declared // if (tree != null) javalist.append(tree); // // } else if (tree instanceof JmlTypeClause) { // if (tree instanceof JmlTypeClauseInitializer) { // JmlTypeClauseInitializer tsp = (JmlTypeClauseInitializer) tree; // tsp.specs = currentMethodSpecs; // currentMethodSpecs = null; // checkInitializer(tsp, typeSpecs, context, jmlF); // } else { // // FIXME - what is this? // typeSpecs.clauses.append((JmlTypeClause) tree); // } // } else if (tree instanceof JCTree.JCBlock) { // typeSpecs.blocks // .put((JCTree.JCBlock) tree, // new JmlSpecs.MethodSpecs( // JmlTree.Maker.instance(context) // .Modifiers(0), currentMethodSpecs)); // currentMethodSpecs = null; // javalist.append(tree); // } else { // // presume that everything left is a valid Java declaration // javalist.append(tree); // } // if (currentMethodSpecs != null) { // log.error(currentMethodSpecs.pos(), "jml.misplaced.method.spec"); // currentMethodSpecs = null; // } // } // typeSpecs.modifiers = decl.mods; // typeSpecs.file = decl.source(); // typeSpecs.decl = decl; // decl.defs = javalist.toList(); // decl.typeSpecs = typeSpecs; // The type specs from just this compilation // // unit // } /** Parses a maps clause */ public JmlTypeClauseMaps parseMaps(int pos, JCModifiers mods, ListBuffer<JCTree> list) { if (!isNone(mods)) jmlerror(mods.getStartPosition(), mods.getPreferredPosition(), getEndPos(mods), "jml.no.mods.allowed", JmlTokenKind.MAPS.internedName()); S.setJmlKeyword(false); nextToken(); // skip over the maps token JCExpression e = parseMapsTarget(); ListBuffer<JmlGroupName> glist; if (jmlTokenKind() != JmlTokenKind.BSINTO) { jmlerror(pos(), endPos(), "jml.expected", "an \\into token here, or the maps target is ill-formed"); glist = new ListBuffer<JmlGroupName>(); S.setJmlKeyword(true); skipThroughSemi(); } else { nextToken(); glist = parseGroupNameList(); S.setJmlKeyword(true); if (token.kind != TokenKind.SEMI) { jmlerror(pos(), endPos(), "jml.bad.construct", "maps clause"); skipThroughSemi(); } else { nextToken(); } } return toP(jmlF.at(pos).JmlTypeClauseMaps(e, glist.toList())); } /** Parses the target portion (before the \\into) of a maps clause */ public JCExpression parseMapsTarget() { int p = pos(); if (token.kind != IDENTIFIER) { jmlerror(pos(), endPos(), "jml.expected", "an identifier"); skipThroughSemi(); return toP(jmlF.at(p).Erroneous()); } Name n = ident(); JCExpression result = to(jmlF.at(p).Ident(n)); if (token.kind == LBRACKET) { result = parseArrayRangeExpr(result, false); } if (token.kind == DOT) { nextToken(); if (token.kind == STAR) { nextToken(); n = null; } else if (token.kind == IDENTIFIER) { n = ident(); } else { jmlerror(pos(), endPos(), "jml.ident.or.star.after.dot"); skipThroughSemi(); return toP(jmlF.at(p).Erroneous()); } // Caution: Java will not expect n to be null // It is null to denote a wildcard selector result = to(jmlF.at(p).Select(result, n)); } else if (!(result instanceof JmlStoreRefArrayRange)) { jmlerror(pos(), endPos(), "jml.expected", "a . to select a field"); skipThroughSemi(); return to(jmlF.at(p).Erroneous()); } return result; } /** Parses an in clause */ public JmlTypeClauseIn parseIn(int pos, JCModifiers mods) { if (!isNone(mods)) jmlerror(pos, "jml.no.mods.allowed", JmlTokenKind.IN.internedName()); S.setJmlKeyword(false); nextToken(); // skip over the in token ListBuffer<JmlGroupName> list = parseGroupNameList(); S.setJmlKeyword(true); accept(SEMI); return toP(jmlF.at(pos).JmlTypeClauseIn(list.toList())); } /** Parses an invariant, initially, or axiom declaration */ public JmlTypeClauseExpr parseInvariantInitiallyAxiom(JCModifiers mods) { int pos = pos(); JmlTokenKind jt = jmlTokenKind(); S.setJmlKeyword(false); nextToken(); JCExpression e = parseExpression(); S.setJmlKeyword(true); if (token.kind != SEMI) { jmlerror(pos(), endPos(), "jml.bad.construct", jt.internedName() + " declaration"); skipThroughSemi(); } else { nextToken(); } if (mods == null) mods = jmlF.at(pos).Modifiers(0); JmlTypeClauseExpr tcl = to(jmlF.at(pos).JmlTypeClauseExpr(mods, jt, e)); tcl.source = log.currentSourceFile(); return tcl; } /** Parses a represents clause */ public JmlTypeClauseRepresents parseRepresents(JCModifiers mods) { S.setJmlKeyword(false); int pos = pos(); nextToken(); JCExpression id = parseStoreRef(true); boolean suchThat; JCExpression e; if (token.kind == EQ) { suchThat = false; nextToken(); e = parseExpression(); } else if (jmlTokenKind() == JmlTokenKind.LEFT_ARROW) { if (isDeprecationSet() || JmlOption.isOption(context, JmlOption.STRICT)) { log.warning(pos(), "jml.deprecated.left.arrow.in.represents"); } suchThat = false; nextToken(); e = parseExpression(); } else if (jmlTokenKind() == JmlTokenKind.BSSUCHTHAT) { suchThat = true; nextToken(); e = parseExpression(); } else { jmlerror(pos(), endPos(), "jml.bad.represents.token"); e = null; skipToSemi(); suchThat = false; } S.setJmlKeyword(true); if (e == null) { // skip e = jmlF.Erroneous(); } else if (token.kind != SEMI) { jmlerror(pos(), endPos(), "jml.invalid.expression.or.missing.semi"); skipThroughSemi(); } else { nextToken(); } if (mods == null) mods = jmlF.at(pos).Modifiers(0); JmlTypeClauseRepresents tcl = to(jmlF.at(pos).JmlTypeClauseRepresents( mods, id, suchThat, e)); tcl.source = log.currentSourceFile(); return tcl; } /** Parses a constraint clause */ public JmlTypeClauseConstraint parseConstraint(JCModifiers mods) { int pos = pos(); S.setJmlKeyword(false); nextToken(); JCExpression e = parseExpression(); S.setJmlKeyword(true); List<JmlMethodSig> sigs = null; boolean notlist = false; if (token.kind == FOR) { nextToken(); if (token.kind == BANG) { notlist = true; nextToken(); } if (jmlTokenKind() == JmlTokenKind.BSEVERYTHING || jmlTokenKind() == JmlTokenKind.BSNOTSPECIFIED) { nextToken(); // This is the default, so we just leave sigs null if (notlist) sigs = new ListBuffer<JmlMethodSig>().toList(); notlist = false; } else if (jmlTokenKind() == JmlTokenKind.BSNOTHING) { nextToken(); if (!notlist) sigs = new ListBuffer<JmlMethodSig>().toList(); notlist = false; // Here we just have an empty list } else { sigs = parseMethodNameList(); } } if (mods == null) mods = jmlF.at(pos).Modifiers(0); JmlTypeClauseConstraint tcl = to(jmlF.at(pos).JmlTypeClauseConstraint( mods, e, sigs)); tcl.notlist = notlist; tcl.source = log.currentSourceFile(); if (token.kind != SEMI) { jmlerror(pos(), endPos(), "jml.bad.construct", "constraint declaration"); skipThroughSemi(); } else { nextToken(); } return tcl; } /** * Parses a list of method-name; returns a possibly empty list; does not * parse the terminating semicolon */ public List<JmlMethodSig> parseMethodNameList() { S.setJmlKeyword(false); ListBuffer<JmlMethodSig> sigs = new ListBuffer<JmlMethodSig>(); while (true) { JmlMethodSig m = parseMethodName(); if (m == null) { skipToCommaOrParenOrSemi(); } else { sigs.append(m); } toP(m); if (token.kind != COMMA) break; nextToken(); } S.setJmlKeyword(true); return sigs.toList(); } /** Parses a method-name */ public JmlMethodSig parseMethodName() { int initpos = pos(); int p = initpos; Name n = null; JCTree newType = null; if (token.kind == NEW) { newType = parseType(); // FIXME - check that it is a reference type } else if (token.kind == IDENTIFIER) { n = ident(); } else if (token.kind == THIS) { n = names._this; nextToken(); } else if (token.kind == SUPER) { n = names._super; nextToken(); } else { jmlerror(pos(), endPos(), "jml.bad.construct", "constraint method"); return null; } JCExpression id = null; if (newType == null) { id = jmlF.at(p).Ident(n); boolean first = true; while (token.kind == DOT) { nextToken(); p = pos(); if (token.kind == IDENTIFIER) { n = ident(); } else if (token.kind == THIS) { n = names._this; nextToken(); } else if (token.kind == STAR) { // * may only be the only thing after any dot, if it is // present if (!first) { jmlerror(pos(), endPos(), "jml.expected", "identifier or this, since a * may only be used after the first dot"); } n = names.asterisk; nextToken(); if (token.kind == DOT) { jmlerror(pos(), endPos(), "jml.expected", "no dot, since a dot may not be used after a *"); } } else { jmlerror(pos(), endPos(), "jml.expected", "identifier or this"); break; } id = jmlF.at(p).Select(id, n); first = false; if (n == names.asterisk) { return jmlF.at(initpos).JmlConstraintMethodSig(id, null); } } } ListBuffer<JCExpression> args = null; if (token.kind == LPAREN) { args = new ListBuffer<JCExpression>(); nextToken(); if (token.kind != RPAREN) { JCExpression arg = parseType(); args.append(arg); while (token.kind == COMMA) { nextToken(); arg = parseType(); args.append(arg); } if (token.kind != RPAREN) { jmlerror(pos(), endPos(), "jml.expected", "comma or right parenthesis"); } else { nextToken(); } } else { nextToken(); // consume the RPAREN } } return jmlF.at(initpos).JmlConstraintMethodSig(id, args == null ? null : args.toList()); } /** Parse a readable or writable clause */ public JmlTypeClauseConditional parseReadableWritable(JCModifiers mods, JmlTokenKind jmlTokenKind) { int p = pos(); S.setJmlKeyword(false); nextToken(); Name n; JCExpression e; int identPos = pos(); if (token.kind != TokenKind.IDENTIFIER) { jmlerror(pos(), endPos(), "jml.expected", "an identifier"); n = names.asterisk; // place holder for an error situation e = jmlF.Erroneous(); } else { n = ident(); if (token.kind != IF) { jmlerror(pos(), endPos(), "jml.expected", "an if token"); e = jmlF.Erroneous(); } else { accept(TokenKind.IF); e = parseExpression(); } } JCTree.JCIdent id = to(jmlF.at(identPos).Ident(n)); S.setJmlKeyword(true); if (e.getTag() == JCTree.Tag.ERRONEOUS || token.kind != SEMI) { skipThroughSemi(); } else { nextToken(); } return toP(jmlF.at(p).JmlTypeClauseConditional(mods, jmlTokenKind, id, e)); } /** Parse a monitors_for clause */ public JmlTypeClauseMonitorsFor parseMonitorsFor(JCModifiers mods) { int p = pos(); S.setJmlKeyword(false); nextToken(); ListBuffer<JCExpression> elist = new ListBuffer<JCExpression>(); Name n; int identPos = pos(); ITokenKind tk = token.kind; if (tk != IDENTIFIER) { jmlerror(pos(), endPos(), "jml.expected", "an identifier"); n = names.asterisk; // place holder for an error situation } else { n = ident(); // Advances to next token if (token.kind != TokenKind.EQ && jmlTokenKind() != JmlTokenKind.LEFT_ARROW) { jmlerror(pos(), endPos(), "jml.expected", "an = or <- token"); } else { nextToken(); elist = expressionList(); } } JCTree.JCIdent id = to(jmlF.at(identPos).Ident(n)); S.setJmlKeyword(true); if (token.kind != SEMI) { skipThroughSemi(); } else { nextToken(); } return toP(jmlF.at(p).JmlTypeClauseMonitorsFor(mods, id, elist.toList())); } /** * This parses a comma-separated list of expressions; the last expression in * the list parses until it can parse no more - the caller needs to check * that the next token is an expected token in the context, such as a right * parenthesis. * * @return a ListBuffer of expressions, which may be empty or contain * JCErroneous expressions if errors occurred */ public ListBuffer<JCExpression> expressionList() { ListBuffer<JCExpression> args = new ListBuffer<>(); args.append(parseExpression()); while (token.kind == COMMA) { nextToken(); JCExpression e = parseExpression(); args.append(e); // e might be a JCErroneous } return args; } public JCExpression parseStoreRefListExpr() { int p = pos(); JmlTokenKind jt = jmlTokenKind(); nextToken(); accept(LPAREN); ListBuffer<JCExpression> list = parseStoreRefList(false); if (token.kind != RPAREN) { jmlerror(pos(), endPos(), "log.expected", "right parenthesis"); skipThroughRightParen(); } else { nextToken(); } return toP(jmlF.at(p).JmlStoreRefListExpression(jt, list.toList())); } public JmlMethodSpecs parseMethodSpecs(JCModifiers mods) { // Method specifications are a sequence of specification cases ListBuffer<JmlSpecificationCase> cases = new ListBuffer<JmlSpecificationCase>(); JmlSpecificationCase c; JmlTokenKind t; int pos = pos(); int lastPos = Position.NOPOS; while ((c = parseSpecificationCase(mods, false)) != null) { cases.append(c); lastPos = getEndPos(c); mods = modifiersOpt(); } JmlMethodSpecs sp = jmlF.at(pos).JmlMethodSpecs(cases.toList()); // end position set below if ((t = jmlTokenKind()) == JmlTokenKind.IMPLIES_THAT) { if (!isNone(mods)) jmlerror(pos(), endPos(), "jml.no.mods.allowed", t.internedName()); nextToken(); mods = modifiersOpt(); cases = new ListBuffer<JmlSpecificationCase>(); while ((c = parseSpecificationCase(mods, false)) != null) { cases.append(c); lastPos = getEndPos(c); mods = modifiersOpt(); } if (cases.size() > 0) cases.first().also = t; sp.impliesThatCases = cases.toList(); } if ((t = jmlTokenKind()) == JmlTokenKind.FOR_EXAMPLE) { if (!isNone(mods)) jmlerror(mods.getStartPosition(), getEndPos(mods), "jml.no.mods.allowed", t.internedName()); nextToken(); mods = modifiersOpt(); cases = new ListBuffer<JmlSpecificationCase>(); while ((c = parseSpecificationCase(mods, true)) != null) { cases.append(c); lastPos = getEndPos(c); mods = modifiersOpt(); } if (cases.size() > 0) cases.first().also = t; sp.forExampleCases = cases.toList(); } storeEnd(sp, lastPos); // We may have parsed some modifiers and then found out that we are not // at the beginning of a spec case (perhaps at the beginning of a method // declaration for example). So we have to preserve the modifiers that // have already been parsed. pushBackModifiers = mods; // It is possible that a problem in parsing results in // an empty set of specification cases. That would not be legal // JML, but I expect that a message has been logged about it already. return sp; } /** * Returns true if no modifiers or annotations (of any kind) have been set * * @param mods * the modifiers structure to check * @return true if any flags or annotations are set */ public boolean isNone(JCModifiers mods) { return mods == null || ((mods.flags & Flags.StandardFlags) == 0 && (mods.annotations == null || mods.annotations .isEmpty())); } // [ also ] [ modifiers ] [ | behavior | normal_behavior | // exceptional_behavior ] [ clause ]* public JmlSpecificationCase parseSpecificationCase(JCModifiers mods, boolean exampleSection) { JmlTokenKind also = null; JmlTokenKind ijt = jmlTokenKind(); if (ijt == ALSO) { if (!isNone(mods)) { jmlerror(mods.getStartPosition(), endPos(), "jml.no.mods.allowed", ijt.internedName()); mods = null; } nextToken(); also = ijt; // get any modifiers mods = modifiersOpt(); } boolean code = false; int codePos = 0; if (jmlTokenKind() == JmlTokenKind.CODE) { codePos = pos(); code = true; nextToken(); } JmlTokenKind jt = jmlTokenKind(); int pos = pos(); if (jt == JmlTokenKind.BEHAVIOR || jt == JmlTokenKind.NORMAL_BEHAVIOR || jt == JmlTokenKind.EXCEPTIONAL_BEHAVIOR || (jt == JmlTokenKind.ABRUPT_BEHAVIOR && inModelProgram)) { if (exampleSection) { log.warning(pos(), "jml.example.keyword", "must not", jt.internedName()); } nextToken(); } else if (jt == JmlTokenKind.EXAMPLE || jt == JmlTokenKind.NORMAL_EXAMPLE || jt == JmlTokenKind.EXCEPTIONAL_EXAMPLE) { if (!exampleSection) { log.warning(pos(), "jml.example.keyword", "must", jt.internedName()); } nextToken(); } else if (jt == MODEL_PROGRAM) { JmlSpecificationCase j = parseModelProgram(mods, code, also); j.sourcefile = log.currentSourceFile(); return j; } else if (jt == null && S.jml() && also != null) { jmlerror(pos(), endPos(), "jml.invalid.keyword.in.spec", S.chars()); skipThroughSemi(); // Call it lightweight } else { jt = null; if (code) log.warning(codePos, "jml.misplaced.code"); // lightweight } ListBuffer<JmlMethodClause> clauses = new ListBuffer<JmlMethodClause>(); JmlMethodClause e; while (token.kind == CUSTOM && (e = getClause()) != null) { clauses.append(e); } if (clauses.size() == 0) { if (jt != null) { jmlerror(pos, "jml.empty.specification.case"); } if (also == null && !code) return null; } if (jt == null && code) code = false; // Already warned about this JmlSpecificationCase j = jmlF.at(pos).JmlSpecificationCase(mods, code, jt, also, clauses.toList()); storeEnd(j, j.clauses.isEmpty() ? pos + 1 : getEndPos(j.clauses.last())); j.sourcefile = log.currentSourceFile(); return j; } /** Issues a warning that the named construct is parsed and ignored */ public void warnNotImplemented(int pos, String construct, String location) { if (JmlOption.isOption(context, JmlOption.SHOW_NOT_IMPLEMENTED)) log.warning(pos, "jml.unimplemented.construct", construct, location); } /** Parses a model program; presumes the current token is model_program */ public JmlSpecificationCase parseModelProgram(JCModifiers mods, boolean code, JmlTokenKind also) { int pos = pos(); nextToken(); // skip over the model_program token JCBlock stat; JmlSpecificationCase spc; try { inJmlDeclaration = true; inModelProgram = true; stat = block(); spc = toP(jmlF.at(pos).JmlSpecificationCase(mods, code, MODEL_PROGRAM, also, stat)); } finally { inJmlDeclaration = false; inModelProgram = false; } return spc; } /** * Parses an entire specification group; the current token must be the * SPEC_GROUP_START and upon return the SPEC_GROUP_END will have been * consumed. * * @return a JMLMethodClauseGroup AST node */ public JmlMethodClauseGroup getSpecificationGroup() { int p = pos(); ListBuffer<JmlSpecificationCase> list = new ListBuffer<JmlSpecificationCase>(); nextToken(); do { JmlSpecificationCase g = parseSpecificationCase(null, false); if (g == null) { // Empty specification group // Warning happens below as an invalid specification group end } else { list.append(g); } if (jmlTokenKind() == ENDJMLCOMMENT) nextToken(); } while (jmlTokenKind() == ALSO); if (jmlTokenKind() == ENDJMLCOMMENT) nextToken(); if (jmlTokenKind() != SPEC_GROUP_END) { jmlerror(pos(), endPos(), "jml.invalid.spec.group.end"); while (jmlTokenKind() != JmlTokenKind.ENDJMLCOMMENT && token.kind != EOF) nextToken(); if (token.kind != EOF) nextToken(); } else { nextToken(); } return toP(jmlF.at(p).JmlMethodClauseGroup(list.toList())); } /** * Parses a method specification clause; the current token must be the token * indication the kind of clause; upon return the terminating semicolon will * have been consumed. It is also legal for the current token to be * ENDJMLCOMMENT, in which case it is consumed. The * method returns null when no more clauses are recognized. * * @return a JmlMethodClause AST node, or null if there is no clause * recognized */ public JmlMethodClause getClause() { //String dc = S.docComment; // FIXME - do we need to do this? while (jmlTokenKind() == ENDJMLCOMMENT) { nextToken(); //S.docComment = dc; } JmlTokenKind jt = jmlTokenKind(); int pos = pos(); S.setJmlKeyword(false); JmlMethodClause res = null; if (jt != null) switch (jt) { // The cases have to include all clause types. case REQUIRES: case ENSURES: case DIVERGES: case WHEN: res = parseExprClause(); break; case SIGNALS: // signals (Exception e) parseExpression ; res = parseSignals(); break; case SIGNALS_ONLY: res = parseSignalsOnly(); break; case ASSIGNABLE: case ACCESSIBLE: case CAPTURES: res = parseStoreRefClause(); break; case FORALL: case OLD: res = parseForallOld(); break; case WORKING_SPACE: case MEASURED_BY: case DURATION: res = parseDurationEtc(); break; case CALLABLE: warnNotImplemented(pos(), jt.internedName(), "JmlParser.getClause()"); nextToken(); JmlStoreRefKeyword refkeyword = parseOptStoreRefKeyword(); List<JmlMethodSig> sigs = null; if (refkeyword == null) { sigs = parseMethodNameList(); } S.setJmlKeyword(true); int endpos = pos(); accept(SEMI); JmlMethodClauseCallable ec; if (refkeyword != null) { ec = toP(jmlF.at(pos).JmlMethodClauseCallable( refkeyword)); } else { ec = toP(jmlF.at(pos).JmlMethodClauseCallable(sigs)); } res = ec; break; case SPEC_GROUP_START: S.setJmlKeyword(true); res = getSpecificationGroup(); break; case CONTINUES: case BREAKS: case RETURNS: if (!inModelProgram) { jmlerror(pos(), endPos(), "jml.misplaced.modelprogram.statement", jt.toString()); } res = parseExprClause(); // FIXME _ continues, breaks may have an optional label break; default: // For any other token we just exit this loop, // WITHOUT HAVING CALLED nextToken()!!!! break; } if (res != null) res.sourcefile = log.currentSourceFile(); S.setJmlKeyword(true); // Just in case, but it is too late, since the // token after the semicolon is already read rescan(); return res; } /** Parses either a \\not_specified token or a JML expression */ public JCExpression parsePredicateOrNotSpecified() { if (jmlTokenKind() == BSNOTSPECIFIED) { int pos = pos(); nextToken(); return toP(jmlF.at(pos).JmlSingleton(BSNOTSPECIFIED)); } else { return parseExpression(); } } /** * Parses a method specification clause that takes a * predicate-or-not-specified argument * * @return the parsed JmlMethodClause */ public JmlMethodClauseExpr parseExprClause() { JmlTokenKind jt = jmlTokenKind(); int pos = pos(); nextToken(); JCExpression e = parsePredicateOrNotSpecified(); S.setJmlKeyword(true); if (token.kind != SEMI) { syntaxError(pos(), null, "jml.invalid.expression.or.missing.semi"); skipThroughSemi(); } else { nextToken(); // skip SEMI } JmlMethodClauseExpr cl = jmlF.at(pos).JmlMethodClauseExpr(jt, e); return toP(cl); } /** * Parses a signals method specification clause * * @return the parsed JmlMethodClause */ public JmlMethodClauseSignals parseSignals() { JmlTokenKind jt = jmlTokenKind(); int pos = pos(); JCExpression e; nextToken(); JCExpression t = null; Name ident = null; int rpos = pos; if (token.kind != LPAREN) { syntaxError(pos(), null, "jml.expected.lparen.signals"); t = to(jmlF.at(pos()).Ident(names.fromString("java"))); t = to(jmlF.at(pos()).Select(t, names.fromString("lang"))); t = to(jmlF.at(pos()).Select(t, names.fromString("Exception"))); e = parsePredicateOrNotSpecified(); } else { nextToken(); // Get type t = parseType(); // Get identifier (optional) if (token.kind == IDENTIFIER) { ident = ident(); } rpos = pos(); if (token.kind != RPAREN) { syntaxError(pos(), null, "jml.expected.rparen.signals"); skipToSemi(); e = toP(jmlF.at(pos()).Erroneous()); } else { nextToken(); if (token.kind == SEMI) { e = toP(jmlF.at(pos()).Literal(TypeTag.BOOLEAN, 1)); // Boolean.TRUE)); } else { e = parsePredicateOrNotSpecified(); } } } S.setJmlKeyword(true); JCTree.JCVariableDecl var = jmlF.at(t.pos).VarDef( jmlF.at(t.pos).Modifiers(0), ident, t, null); storeEnd(var, rpos); if (token.kind != SEMI) { if (e.getKind() != Kind.ERRONEOUS) syntaxError(pos(), null, "jml.missing.semi"); skipThroughSemi(); } else { nextToken(); } return toP(jmlF.at(pos).JmlMethodClauseSignals(jt, var, e)); } /** * Parses a signals_only clause. The current token must be the signals_only * token; upon return the terminating semicolon will have been parsed. * * @return a JmlMethodClauseSignalsOnly AST node */ public JmlMethodClauseSignalsOnly parseSignalsOnly() { JmlTokenKind clauseType = jmlTokenKind(); int pos = pos(); nextToken(); JmlTokenKind jt = jmlTokenKind(); ITokenKind tk = token.kind; ListBuffer<JCExpression> list = new ListBuffer<JCExpression>(); if (jt == BSNOTHING) { S.setJmlKeyword(true); nextToken(); if (token.kind != SEMI) { syntaxError(pos(), null, "jml.expected.semi.after.nothing"); skipThroughSemi(); } else { nextToken(); } } else if (tk == SEMI) { S.setJmlKeyword(true); syntaxError(pos(), null, "jml.use.nothing", clauseType.internedName()); nextToken(); } else { while (true) { JCExpression typ = parseType(); // if this fails, a JCErroneous // is returned list.append(typ); tk = token.kind; if (tk == SEMI) { S.setJmlKeyword(true); nextToken(); break; } else if (tk == COMMA) { nextToken(); continue; } else if (typ instanceof JCErroneous) { S.setJmlKeyword(true); skipThroughSemi(); break; } else if (jmlTokenKind() == ENDJMLCOMMENT) { syntaxError(pos(), null, "jml.missing.semi"); } else { syntaxError(pos(), null, "jml.missing.comma"); continue; } // error S.setJmlKeyword(true); skipThroughSemi(); break; } } return toP(jmlF.at(pos).JmlMethodClauseSignalsOnly(clauseType, list.toList())); } public JmlMethodClauseDecl parseForallOld() { int pos = pos(); JmlTokenKind jt = jmlTokenKind(); nextToken(); // non_null and nullable and perhaps other type modifiers in the // future are allowed JCModifiers mods = modifiersOpt(); JCExpression t = parseType(); boolean prev = inJmlDeclaration; inJmlDeclaration = true; // allows non-ghost declarations ListBuffer<JCTree.JCVariableDecl> decls = variableDeclarators(mods, t, new ListBuffer<JCVariableDecl>()); inJmlDeclaration = prev; JmlMethodClauseDecl res = to(jmlF.at(pos) .JmlMethodClauseDecl(jt, decls.toList())); S.setJmlKeyword(true); if (token.kind == SEMI) { nextToken(); } else { jmlerror(pos(), endPos(), "jml.bad.construct", jt.internedName() + " specification"); skipThroughSemi(); } return res; } /** * Parses (duration|working_space|?) (<expression>|"\\not_specified") [ "if" * <expression> ] ";" */ public JmlMethodClauseConditional parseDurationEtc() { int pos = pos(); JmlTokenKind jt = jmlTokenKind(); JCExpression p = null; nextToken(); JCExpression e = parsePredicateOrNotSpecified(); if (token.kind == IF) { // The if is not allowed if the // expression is not_specified, but we test that // during type checking nextToken(); p = parseExpression(); } JmlMethodClauseConditional res = to(jmlF.at(pos) .JmlMethodClauseConditional(jt, e, p)); S.setJmlKeyword(true); if (token.kind == SEMI) { nextToken(); } else { jmlerror(pos(), endPos(), "jml.bad.construct", jt.internedName() + " specification"); skipThroughSemi(); } return res; } /** Parses "assignable" <store-ref-list> ";" */ public JmlMethodClauseStoreRef parseStoreRefClause() { JmlTokenKind jt = jmlTokenKind(); int pos = pos(); ListBuffer<JCExpression> list = new ListBuffer<JCExpression>(); nextToken(); // skip over the assignable token if (token.kind == SEMI) { syntaxError(pos(), null, "jml.use.nothing.assignable"); S.setJmlKeyword(true); nextToken(); // skip over the SEMI } else { list = parseStoreRefList(false); if (token.kind == SEMI) { // OK, go on } else if (jmlTokenKind() == ENDJMLCOMMENT) { syntaxError(pos(), null, "jml.missing.semi"); } S.setJmlKeyword(true); if (token.kind != SEMI) { // error already reported skipThroughSemi(); } else { if (list.isEmpty()) { syntaxError(pos(), null, "jml.use.nothing.assignable"); } nextToken(); } } return toP(jmlF.at(pos).JmlMethodClauseStoreRef(jt, list.toList())); } /** * Parses ("\|nothing"|"\\everything"|"\\not_specified"| <store-ref> [ "," * <store-ref> ]* ) ";" <BR> * a store-ref is a JCIdent, a JCSelect (potentially with a null field), or * a JmlStoreRefArrayRange; there may be more than one use of a * JmlStoreRefArrayRange, e.g. a[2..3][4..5] or a.f[4..5].g[6..7] * * @param strictId * if true, keywords and wildcards are not allowed * @return list of zero or more store-refs or a list of one * store-ref-keyword; */ public ListBuffer<JCExpression> parseStoreRefList(boolean strictId) { ListBuffer<JCExpression> list = new ListBuffer<JCExpression>(); if (!strictId) { JCExpression s = parseOptStoreRefKeyword(); if (s != null) { list.append(s); return list; } } while (true) { JCExpression r = parseStoreRef(false); if (r != null) list.append(r); ITokenKind tk = token.kind; if (tk == COMMA) { nextToken(); continue; } else if (tk == SEMI || tk == RPAREN) { return list; } else if (jmlTokenKind() == ENDJMLCOMMENT) { // The missing semi-colon is reported by the caller return list; } else { syntaxError(pos(), null, "jml.missing.comma"); if (r == null) return list; } } } /** Parses a storeRefKeyword or returns null (with no error message) */ public JmlStoreRefKeyword parseOptStoreRefKeyword() { JmlTokenKind jt = jmlTokenKind(); int p = pos(); if (jt == BSNOTHING || jt == BSEVERYTHING || jt == BSNOTSPECIFIED) { JmlStoreRefKeyword s = to(jmlF.at(p).JmlStoreRefKeyword(jt)); nextToken(); return s; } return null; } /** * Parses (<informal-comment>| (<identifier>|"this"|"super") [ "." "*" | "." * <identifier> | <array-range-expr> ]* ) except that "this" or "super" must * have something following them. * * @param strictId * if true, no informal comments or wildcards are allowed * @return a JCExpression or JmlStoreRefKeyword */ public/* @ nullable */JCExpression parseStoreRef(boolean strictId) { JCExpression ss = parseStoreRefInit(strictId); if (ss instanceof JmlStoreRefKeyword) return ss; else { JCExpression e = ss; while (true) { if (token.kind == DOT) { int dotpos = pos(); nextToken(); if (!strictId && token.kind == STAR) { nextToken(); // Caution: Java will not expect the field selector to // be null e = toP(jmlF.at(dotpos).Select(e, (Name) null)); if (token.kind != COMMA && token.kind != SEMI && token.kind != RPAREN) { jmlerror(pos(), endPos(), "jml.not.after.star"); skipToCommaOrSemi(); } break; } else if (token.kind == IDENTIFIER) { Name n = ident(); e = to(jmlF.at(dotpos).Select(e, n)); continue; } else { if (strictId) jmlerror(pos(), endPos(), "jml.expected.id"); else jmlerror(pos(), endPos(), "jml.ident.or.star.after.dot"); skipToCommaOrSemi(); break; } } else if (token.kind == LBRACKET) { e = parseArrayRangeExpr(e, strictId); } else { break; } } if (e instanceof JCIdent) { if (((JCIdent) e).name == names._this || ((JCIdent) e).name == names._super) { log.error(e.pos(), "jml.naked.this.super"); // A standalone this or super is not allowed. We state the // error but the parse tree is this.* or super.* e = to(jmlF.at(e.pos).Select(e, (Name) null)); } } ss = e; } return ss; } /** * Parses the initial part of a store-ref: * (<informal-comment>|<identifier>|"this"|"super") * * @param strictId * when true, only store-refs that start with identifiers, this, * or super are allowed * @return an AST for the parsed code */ // @ ensures \result == null || \result instanceof JmlStoreRefKeyword || // \result instanceof JCIdent; protected JCExpression parseStoreRefInit(boolean strictId) { JmlTokenKind jt = jmlTokenKind(); int p = pos(); if (!strictId && (jt == INFORMAL_COMMENT)) { JCExpression s = to(jmlF.at(p).JmlStoreRefKeyword(jt)); nextToken(); return s; } else if (token.kind == IDENTIFIER) { Name n = ident(); JCTree.JCIdent id = to(jmlF.at(p).Ident(n)); return id; } else if (token.kind == SUPER) { nextToken(); // skip over the this or super JCTree.JCIdent id = toP(jmlF.at(p).Ident(names._super)); return id; } else if (token.kind == THIS) { nextToken(); // skip over the this or super JCTree.JCIdent id = toP(jmlF.at(p).Ident(names._this)); return id; } jmlerror(p, endPos(), "jml.bad.store.ref"); skipToCommaOrSemi(); return null; } /** * Parses [ "[" ( "*" | <expression> | <expression> ".." "*" | <expression> * ".." | <expression> ".." <expression> ) "]" ]* * * @param t * the leading expression for which the array index or range is a * suffix * @param strictId * if true, no wildcards or ranges are allowed * @return an AST for the parsed code */ protected JCExpression parseArrayRangeExpr(JCExpression t, boolean strictId) { while (token.kind == LBRACKET) { nextToken(); // move past the LBRACKET if (token.kind == STAR) { if (strictId) { jmlerror(pos(), endPos(), "jml.no.star.in.strict.mode"); } nextToken(); if (token.kind == RBRACKET) { nextToken(); t = toP(jmlF.at(t.pos).JmlStoreRefArrayRange(t, null, null)); continue; } else { jmlerror(pos(), endPos(), "jml.expected.rbracket.star"); skipToCommaOrSemi(); break; } } else { JCExpression lo = parseExpression(); if (token.kind == RBRACKET) { t = to(jmlF.at(t.pos).JmlStoreRefArrayRange(t, lo, lo)); nextToken(); continue; } else if (!strictId && jmlTokenKind() == DOT_DOT) { nextToken(); JCExpression hi = null; int rbracketPos = pos(); if (token.kind == STAR) { nextToken(); } else if (token.kind == RBRACKET) { if (JmlOption.isOption(context, JmlOption.STRICT)) { log.warning(rbracketPos,"jml.not.strict","storeref with implied end-of-range (a[i..])"); } // OK - missing hi end implies end of array } else { hi = parseExpression(); } if (token.kind == RBRACKET) { t = to(jmlF.at(t.pos).JmlStoreRefArrayRange(t, lo, hi)); nextToken(); } else { jmlerror(pos(), endPos(), "jml.expected.rbracket"); skipToCommaOrSemi(); break; } continue; } else { jmlerror(pos(), endPos(), "jml.invalid.expression.succeeding.token"); skipToCommaOrSemi(); break; } } } return t; } protected JCModifiers pushBackModifiers = null; /** * Overridden so that anything in 'pushBackModifiers' is incorporated into * the result of the call * * @return combination of 'pushBackModifiers' and any modifiers that are * next in the token string */ @Override protected JCModifiers modifiersOpt() { JCModifiers partial = pushBackModifiers; pushBackModifiers = null; return modifiersOpt(partial); } /** * Combines 'pushBackModifiers', the argument and any modifiers that are * next in the token string (including JML modifiers) * * @return combination of 'pushBackModifiers' and any modifiers that are * next in the token string */ @Override protected JCModifiers modifiersOpt(JCModifiers partial) { if (partial == null) { partial = pushBackModifiers; pushBackModifiers = null; } else if (pushBackModifiers != null) { jmlerror( pos(), endPos(), "jml.internal.notsobad", "This code branch in modifiersOpt() is not expected to be executed and is not fully implemented - please report with code samples"); // I don't think this is ever executed. If it is we need to check // that // there is no duplication of modifiers. long flags = partial.flags | pushBackModifiers.flags; // long same = (partial.flags & pushBackModifiers.flags); ListBuffer<JCAnnotation> annotations = new ListBuffer<JCAnnotation>(); annotations.appendList(pushBackModifiers.annotations); annotations.appendList(partial.annotations); partial = jmlF.at(pushBackModifiers.pos()).Modifiers(flags, annotations.toList()); pushBackModifiers = null; } partial = super.modifiersOpt(partial); while (token.kind == CUSTOM) { partial = jmlModifiersOpt(partial); if (token.kind == CUSTOM) break; partial = super.modifiersOpt(partial); } return partial; } /** * Converts a token to an annotation expression, or to null if there is no * corresponding annotation. * * @param jt * the input token * @param position * the character position * @return the annotation expression */ protected/* @ nullable */JCAnnotation tokenToAnnotationAST(JmlTokenKind jt, int position, int endpos) { Class<?> c = jt.annotationType; if (c == null) return null; JCExpression t = to(F.at(position).Ident(names.fromString("org"))); t = to(F.at(position).Select(t, names.fromString("jmlspecs"))); t = to(F.at(position).Select(t, names.fromString("annotation"))); t = to(F.at(position).Select(t, names.fromString(c.getSimpleName()))); JCAnnotation ann = to(F.at(position).Annotation(t, List.<JCExpression> nil())); ((JmlTree.JmlAnnotation)ann).sourcefile = log.currentSourceFile(); storeEnd(ann, endpos); return ann; } /** * Reads any JML modifiers, combining them with the input to produce a new * JCModifiers object * * @param partial * input modifiers and annotations * @return combined modifiers and annotations */ protected JCModifiers jmlModifiersOpt(JCModifiers partial) { ListBuffer<JCAnnotation> annotations = new ListBuffer<JCAnnotation>(); if (partial != null) annotations.appendList(partial.annotations); int pos = Position.NOPOS; int last = Position.NOPOS; if (partial != null) { pos = partial.pos; } JmlTokenKind j; while ((j = jmlTokenKind()) != null) { if (JmlTokenKind.modifiers.contains(j)) { last = endPos(); JCAnnotation a = tokenToAnnotationAST(j, pos(), last); // FIXME -is position correct? if (a != null) { annotations.append(a); if (pos == Position.NOPOS) pos = a.getStartPosition(); } // a is null if no annotation is defined for the modifier; // we just silently ignore that situation // (this is true at the moment for math annotations, but could // also be true for a modifier someone forgot) if (JmlTokenKind.extensions.contains(j) && JmlOption.isOption(context, JmlOption.STRICT)) { log.warning(pos(),"jml.not.strict",j.internedName()); // FIXME - probably wrong position } } else if (j == ENDJMLCOMMENT) { // skip over } else if (j == CONSTRUCTOR || j == FIELD || j == METHOD) { // FIXME - do we want to save this anywhere; check that it // matches the declaration; check that it is not used on // something other than a declaration? // Also setJmlKeyword back to true S.setJmlKeyword(false); } else { // Not a modifier break; } nextToken(); } JCModifiers mods = F.at(pos).Modifiers( partial == null ? 0 : partial.flags, annotations.toList()); if (last != Position.NOPOS) storeEnd(mods, last); return mods; } @Override public JCPrimitiveTypeTree basicType() { JmlTokenKind jt = jmlTokenKind(); if (jt == null) { return super.basicType(); } else if (jt == JmlTokenKind.BSTYPEUC || jt == JmlTokenKind.BSBIGINT || jt == JmlTokenKind.BSREAL) { JCPrimitiveTypeTree t = to(jmlF.at(pos()) .JmlPrimitiveTypeTree(jt)); nextToken(); return t; } else { jmlerror(pos(), endPos(), "jml.expected", "JML type token"); JCPrimitiveTypeTree t = to(F.at(pos()).TypeIdent( typetag(TokenKind.VOID))); nextToken(); return t; } } @Override public Name ident() { if (token.kind == CUSTOM) { jmlerror(pos(),endPos(),"jml.keyword.instead.of.ident",jmlTokenKind().internedName()); nextToken(); return names.error; } else { return super.ident(); } } // Have to replicate this method because we cannot just add the JML // operators into the token set for the Java operators. @Override protected JCExpression term1() { JCExpression t = term2Equiv(); if ((mode & EXPR) != 0 && token.kind == QUES) { mode = EXPR; return term1Rest(t); } else { return t; } } protected JCExpression term1Rest(JCExpression t) { return super.term1Rest(term2EquivRest(term2ImpRest(t))); } protected JCExpression term2Equiv() { JCExpression t = term2Imp(); if ((mode & EXPR) != 0 && (jmlTokenKind() == JmlTokenKind.EQUIVALENCE || jmlTokenKind() == JmlTokenKind.INEQUIVALENCE)) { mode = EXPR; return term2EquivRest(t); } else { return t; } } protected JCExpression term2EquivRest(JCExpression t) { JmlTokenKind jt = jmlTokenKind(); while (jt == JmlTokenKind.EQUIVALENCE || jt == JmlTokenKind.INEQUIVALENCE) { int ppos = pos(); // position of the operator nextToken(); JCExpression tt = term2Imp(); t = toP(jmlF.at(ppos).JmlBinary(jt, t, tt)); jt = jmlTokenKind(); } return t; } protected JCExpression term2Imp() { JCExpression t = term2(); if ((mode & EXPR) != 0 && (jmlTokenKind() == JmlTokenKind.IMPLIES || jmlTokenKind() == JmlTokenKind.REVERSE_IMPLIES)) { mode = EXPR; return term2ImpRest(t); } else { return t; } } protected JCExpression term2ImpRest(JCExpression t) { JmlTokenKind jt = jmlTokenKind(); if (jt == JmlTokenKind.IMPLIES) { // For IMPLIES we need to associate to the right int ppos = pos(); // position of the operator nextToken(); JCExpression tt = term2ImpRestX(); t = toP(jmlF.at(ppos).JmlBinary(jt, t, tt)); if (jmlTokenKind() == JmlTokenKind.REVERSE_IMPLIES) { syntaxError(pos(), null, "jml.mixed.implies"); skipToSemi(); } } else if (jt == JmlTokenKind.REVERSE_IMPLIES) { // For REVERSE_IMPLIES we do the conventional association to the // left do { int ppos = pos(); // position of the operator nextToken(); JCExpression tt = term2(); t = toP(jmlF.at(ppos).JmlBinary(jt, t, tt)); jt = jmlTokenKind(); } while (jt == JmlTokenKind.REVERSE_IMPLIES); if (jt == JmlTokenKind.IMPLIES) { syntaxError(pos(), null, "jml.mixed.implies"); skipToSemi(); } } return t; } /** A local call so we can use recursion to do the association to the right */ protected JCExpression term2ImpRestX() { JCExpression t = term2(); JmlTokenKind jt = jmlTokenKind(); if (jt != JmlTokenKind.IMPLIES) return t; int ppos = pos(); nextToken(); JCExpression tt = term2ImpRestX(); return toP(jmlF.at(ppos).JmlBinary(jt, t, tt)); } protected ParensResult analyzeParensHelper(Token t) { if (!(t instanceof JmlToken)) return ParensResult.PARENS; JmlTokenKind jtk = ((JmlToken)t).jmlkind; switch (jtk) { case IMPLIES: case REVERSE_IMPLIES: case EQUIVALENCE: case INEQUIVALENCE: case SUBTYPE_OF: case JSUBTYPE_OF: case DOT_DOT: case LEFT_ARROW: case LOCK_LE: case LOCK_LT: case RIGHT_ARROW: return ParensResult.PARENS; default: return ParensResult.CAST; } } protected ParensResult analyzeParensHelper2(int lookahead, Token t) { if (!(t instanceof JmlToken)) return ParensResult.PARENS; JmlTokenKind jtk = ((JmlToken)t).jmlkind; switch (jtk) { case BSTYPEUC: case BSREAL: case BSBIGINT: if (peekToken(lookahead, RPAREN)) { //Type, ')' -> cast return ParensResult.CAST; } else if (peekToken(lookahead, LAX_IDENTIFIER)) { //Type, Identifier/'_'/'assert'/'enum' -> explicit lambda return ParensResult.EXPLICIT_LAMBDA; } return ParensResult.PARENS; default: return ParensResult.PARENS; } } @Override protected JCExpression term3() { List<JCExpression> typeArgs = null; // No JML function expects type arguments. If they did we would parse // them here (before seeing the JML token). But we can't do that just // to check, because super.term3() down below expects to parse them // itself. So if someone does write type arguments for a JML function // the code will fall into the super.term3() call and the token will not // be recognized - no chance for a nice error message. if (token.kind == CUSTOM) { JCExpression t; JmlTokenKind jt = jmlTokenKind(); int p = pos(); // Position of the keyword if (isJmlTypeToken(jt)) { t = to(jmlF.at(p).JmlPrimitiveTypeTree(jt)); nextToken(); t = bracketsSuffix(bracketsOpt(t)); return t; } switch (jt) { case BSEXCEPTION:// FIXME - what can follow this? case BSINDEX: case BSVALUES:// FIXME - what can follow this? if (JmlOption.isOption(context,JmlOption.STRICT)) { log.warning(p,"jml.not.strict",jt.internedName()); } case BSRESULT:// FIXME - what can follow this? case BSLOCKSET: // FIXME - what can follow this? t = to(jmlF.at(p).JmlSingleton(jt)); nextToken(); if (token.kind == LPAREN) { JCExpression res = syntaxError(pos(), null, "jml.no.args.allowed", jt.internedName()); primaryTrailers(t, typeArgs); // Parse arguments and // ignore, both to do as much // type checking as possible and to skip valid // constructs to avoid extra errors return res; } else { return primaryTrailers(t, typeArgs); } case BSSAME: t = to(jmlF.at(p).JmlSingleton(jt)); nextToken(); return t; case INFORMAL_COMMENT: t = to(jmlF.at(p).JmlSingleton(jt)); ((JmlSingleton) t).info = S.chars(); nextToken(); return t; case BSTYPELC: int start = pos(); nextToken(); p = pos(); if (token.kind != LPAREN) { return syntaxError(p, List.<JCTree> nil(), "jml.args.required", jt); } else { accept(TokenKind.LPAREN); JCExpression e; if (token.kind == VOID) { e = to(F.at(pos()).TypeIdent(TypeTag.VOID)); nextToken(); } else { e = parseType(); } if (token.kind != RPAREN) { if (!(e instanceof JCErroneous)) jmlerror(pos(), endPos(), "jml.bad.bstype.expr"); skipThroughRightParen(); } else nextToken(); // FIXME - this should be a type literal e = toP(jmlF.at(p).JmlMethodInvocation(jt, List.of(e))); ((JmlMethodInvocation)e).startpos = start; return primaryTrailers(e, null); } case BSNONNULLELEMENTS: case BSMAX: case BSFRESH: case BSREACH: case BSSPACE: case BSWORKINGSPACE: case BSDISTINCT: case BSDURATION: case BSISINITIALIZED: case BSINVARIANTFOR: int startx = pos(); nextToken(); if (token.kind != LPAREN) { if (jt == BSMAX) { return parseQuantifiedExpr(p, jt); } return syntaxError(p, null, "jml.args.required", jt.internedName()); } else { int preferred = pos(); List<JCExpression> args = arguments(); JCExpression te = jmlF.at(preferred).JmlMethodInvocation( jt, args); ((JmlMethodInvocation)te).startpos = startx; te = toP(te); if (jt == BSREACH || jt == BSMAX) { te = primaryTrailers(te, null); } return te; } // case BSESC: // case BSRAC: // case BSELEMTYPE: // case BSERASURE: // case BSTYPEOF: // case BSPAST: // case BSOLD: // case BSPRE: // case BSNOWARN: // case BSNOWARNOP: // case BSWARN: // case BSWARNOP: // case BSBIGINT_MATH: // case BSSAFEMATH: // case BSJAVAMATH: // ExpressionExtension ne = Extensions.instance(context).find(pos(), // jt); // if (ne == null) { // jmlerror(p, endPos(), "jml.no.such.extension", // jt.internedName()); // return jmlF.at(p).Erroneous(); // } else { // return ne.parse(this, typeArgs); // } case BSNOTMODIFIED: case BSNOTASSIGNED: case BSONLYACCESSED: case BSONLYCAPTURED: case BSONLYASSIGNED: return parseStoreRefListExpr(); case BSFORALL: case BSEXISTS: case BSPRODUCT: case BSSUM: case BSNUMOF: case BSMIN: nextToken(); return parseQuantifiedExpr(p, jt); case NONNULL: case NULLABLE: case BSPEER: case BSREADONLY: case BSREP: case READONLY: nextToken(); warnNotImplemented(pos(), jt.internedName(), "JmlParser.term3(), as type modifiers"); // FIXME - ignoring these type modifiers for now return term3(); case BSLBLANY: case BSLBLNEG: case BSLBLPOS: nextToken(); return parseLblExpr(p, jt); case BSLET: nextToken(); return parseLet(p); case BSONLYCALLED: warnNotImplemented(pos(), jt.internedName(), "\\only_called"); nextToken(); if (token.kind != LPAREN) { accept(LPAREN); // fails skipThroughRightParen(); } else { accept(LPAREN); parseMethodNameList(); if (token.kind != RPAREN) { accept(RPAREN); // fails skipThroughRightParen(); } else { accept(RPAREN); } } // FIXME - needs implementation return toP(jmlF.at(p).Erroneous()); default: { ExpressionExtension ne = Extensions.instance(context).find(pos(),jt,false); if (ne == null) { jmlerror(p, endPos(), "jml.bad.type.expression", "( token " + jt.internedName() + " in JmlParser.term3())"); // jmlerror(p, endPos(), "jml.no.such.extension", // jt.internedName()); return jmlF.at(p).Erroneous(); } else { return ne.parse(this, typeArgs); } // jmlerror(p, endPos(), "jml.bad.type.expression", // "( token " + jt.internedName() // + " in JmlParser.term3())"); // return toP(jmlF.at(p).Erroneous()); } } } return toP(super.term3()); } @Override protected JCExpression potentialCast() { JmlTokenKind t = jmlTokenKind(); if (JmlTokenKind.jmloperators.contains(t)) return null; return term3(); } protected boolean inCreator = false; public JCExpression parseQuantifiedExpr(int pos, JmlTokenKind jt) { JCModifiers mods = modifiersOpt(); JCExpression t = parseType(); if (t.getTag() == JCTree.Tag.ERRONEOUS) return t; if (mods.pos == -1) { mods.pos = t.pos; // set the beginning of the modifiers storeEnd(mods,t.pos); } // modifiers // to the beginning of the type, if there // are no modifiers ListBuffer<JCVariableDecl> decls = new ListBuffer<JCVariableDecl>(); int idpos = pos(); Name id = ident(); // FIXME JML allows dimensions after the ident decls.append(toP(jmlF.at(idpos).VarDef(mods, id, t, null))); while (token.kind == COMMA) { nextToken(); idpos = pos(); id = ident(); // FIXME JML allows dimensions after the ident decls.append(toP(jmlF.at(idpos).VarDef(mods, id, t, null))); } if (token.kind != SEMI) { jmlerror(pos(), endPos(), "jml.expected.semicolon.quantified"); int p = pos(); skipThroughRightParen(); return toP(jmlF.at(p).Erroneous()); } nextToken(); JCExpression range = null; JCExpression pred = null; if (token.kind == SEMI) { // type id ; ; predicate // two consecutive semicolons is allowed, and means the // range is null - continue nextToken(); pred = parseExpression(); } else { range = parseExpression(); if (token.kind == SEMI) { // type id ; range ; predicate nextToken(); pred = parseExpression(); } else if (token.kind == RPAREN) { // type id ; predicate pred = range; range = null; } else { jmlerror(pos(), endPos(), "jml.expected.semicolon.quantified"); int p = pos(); skipThroughRightParen(); return toP(jmlF.at(p).Erroneous()); } } JmlQuantifiedExpr q = toP(jmlF.at(pos).JmlQuantifiedExpr(jt, decls.toList(), range, pred)); return q; } // MAINTENANCE ISSUE: // This is a copy from JavacParser, so we can add in parseSetComprehension JCExpression creator(int newpos, List<JCExpression> typeArgs) { List<JCAnnotation> newAnnotations = typeAnnotationsOpt(); switch ((TokenKind)token.kind) { case BYTE: case SHORT: case CHAR: case INT: case LONG: case FLOAT: case DOUBLE: case BOOLEAN: if (typeArgs == null) { if (newAnnotations.isEmpty()) { return arrayCreatorRest(newpos, basicType()); } else { return arrayCreatorRest(newpos, toP(F.at(newAnnotations.head.pos).AnnotatedType(newAnnotations, basicType()))); } } break; default: } JCExpression t = qualident(true); int oldmode = mode; mode = TYPE; boolean diamondFound = false; int lastTypeargsPos = -1; if (token.kind == LT) { checkGenerics(); lastTypeargsPos = token.pos; t = typeArguments(t,true); diamondFound = (mode & DIAMOND) != 0; } while (token.kind == DOT) { if (diamondFound) { // cannot select after diamond illegal(); } int pos = token.pos; nextToken(); List<JCAnnotation> tyannos = typeAnnotationsOpt(); t = toP(F.at(pos).Select(t, ident())); if (tyannos != null && tyannos.nonEmpty()) { t = toP(F.at(tyannos.head.pos).AnnotatedType(tyannos, t)); } if (token.kind == LT) { checkGenerics(); lastTypeargsPos = token.pos; t = typeArguments(t,true); diamondFound = (mode & DIAMOND) != 0; } } mode = oldmode; if (token.kind == LBRACKET || token.kind == MONKEYS_AT) { // handle type annotations for non primitive arrays if (newAnnotations.nonEmpty()) { t = insertAnnotationsToMostInner(t, newAnnotations, false); } JCExpression e = arrayCreatorRest(newpos, t); if (diamondFound) { reportSyntaxError(lastTypeargsPos, "cannot.create.array.with.diamond"); return toP(F.at(newpos).Erroneous(List.of(e))); } if (typeArgs != null) { int pos = newpos; if (!typeArgs.isEmpty() && typeArgs.head.pos != Position.NOPOS) { // note: this should always happen but we should // not rely on this as the parser is continuously // modified to improve error recovery. pos = typeArgs.head.pos; } setErrorEndPos(S.prevToken().endPos); JCErroneous err = F.at(newpos).Erroneous(typeArgs.prepend(e)); reportSyntaxError(err, "cannot.create.array.with.type.arguments"); return toP(err); } return e; } else if (token.kind == LPAREN) { boolean prev = inLocalOrAnonClass; inLocalOrAnonClass = true; try { JCNewClass newClass = classCreatorRest(newpos, null, typeArgs, t); if (newClass.def != null) { assert newClass.def.mods.annotations.isEmpty(); if (newAnnotations.nonEmpty()) { newClass.def.mods.pos = earlier(newClass.def.mods.pos, newAnnotations.head.pos); newClass.def.mods.annotations = newAnnotations; } } else { // handle type annotations for instantiations if (newAnnotations.nonEmpty()) { t = insertAnnotationsToMostInner(t, newAnnotations, false); newClass.clazz = t; } } return newClass; } finally { inLocalOrAnonClass = prev; } } else if (token.kind == LBRACE) { return parseSetComprehension(t); } else { syntaxError(pos(), null, "expected3", "\'(\'", "\'{\'", "\'[\'"); t = toP(F.at(newpos).NewClass(null, typeArgs, t, List.<JCExpression> nil(), null)); return toP(F.at(newpos).Erroneous(List.<JCTree> of(t))); } } // protected JCExpression moreCreator(TokenKind token, int newpos, JCExpression t, List<JCExpression> typeArgs) { // if (token.kind == LBRACE) { // return parseSetComprehension(t); // } // } protected boolean inLocalOrAnonClass = false; /** * Parses: <identifier> <expression> * * @param pos * character position of the lbl token * @param jmlToken * either the LBLPOS or LBLNEG token * @return a JmlLblExpression */ protected JCExpression parseLblExpr(int pos, JmlTokenKind jmlToken) { // The JML token is already scanned // pos is the position of the \lbl token int labelPos = pos(); Name n = ident(); JCExpression e = parseExpression(); if (jmlToken == JmlTokenKind.BSLBLANY && JmlOption.isOption(context,JmlOption.STRICT)) { log.warning(pos,"jml.not.strict","\\lbl"); } return toP(jmlF.at(pos).JmlLblExpression(labelPos,jmlToken, n, e)); } public JCExpression parseLet(int pos) { ListBuffer<JCVariableDecl> vdefs = new ListBuffer<JCVariableDecl>(); do { int pm = pos(); JCModifiers mods = jmlF.Modifiers(0); // FIXME - there are some modifiers allowed? if (mods.pos == -1) { mods.pos = pm; storeEnd(mods,pm); } JCExpression type = parseType(); int p = pos(); Name name = ident(); JCVariableDecl decl = variableDeclaratorRest(pos,mods,type,name,true,null); decl.pos = p; if (decl.init == null) toP(decl); vdefs.add(decl); if (token.kind != COMMA) break; accept(COMMA); } while (true); accept(SEMI); JCExpression expr = parseExpression(); return toP(jmlF.at(pos).LetExpr(vdefs.toList(),expr)); } /** Parses: "{" [ <modifiers> ] <type> <identifier> "|" <expression> "}" */ public JCExpression parseSetComprehension(JCExpression type) { JCExpression sc = null; int begin = pos(); if (token.kind != LBRACE) { accept(LBRACE); // fails } else { accept(LBRACE); JCModifiers mods = modifiersOpt(); int tpos = pos(); JCTree.JCExpression t = parseType(); if (t != null && !(t instanceof JCErroneous)) { Name n = ident(); if (n != names.error) { JCTree.JCVariableDecl v = toP(jmlF.at(tpos).VarDef(mods, n, t, null)); if (token.kind != BAR) { accept(BAR); // fails } else { accept(BAR); JCExpression predicate = parseExpression(); if (predicate != null && !(predicate instanceof JCErroneous)) { if (token.kind != RBRACE) { accept(RBRACE); // fails } else { accept(RBRACE); sc = toP(jmlF.at(begin).JmlSetComprehension( type, v, predicate)); } } } } } } if (sc == null) { skipThroughRightBrace(); sc = jmlF.Erroneous(); } return sc; } /** Parses: <groupName> [ "," <groupName> ]* */ protected ListBuffer<JmlGroupName> parseGroupNameList() { ListBuffer<JmlGroupName> list = new ListBuffer<JmlGroupName>(); JmlGroupName g = parseGroupName(); list.append(g); while (token.kind == COMMA) { nextToken(); g = parseGroupName(); list.append(g); } return list; } /** Parses: [ "this" "." | "super" "." ] <identifier> */ protected JmlGroupName parseGroupName() { JCExpression t = null; int p = pos(); if (token.kind == THIS) { t = to(jmlF.at(p).Ident(names._this)); nextToken(); accept(TokenKind.DOT); } else if (token.kind == SUPER) { t = to(jmlF.at(p).Ident(names._super)); nextToken(); accept(TokenKind.DOT); } Name n = ident(); if (t == null) t = toP(jmlF.at(p).Ident(n)); else t = toP(jmlF.at(p).Select(t, n)); return toP(jmlF.at(p).JmlGroupName(t)); } /** Overridden in order to absorb the pushBackModifiers */ @Override public <T extends ListBuffer<? super JCVariableDecl>> T variableDeclarators( JCModifiers mods, JCExpression type, T vdefs) { if (pushBackModifiers != null && isNone(mods)) { mods = pushBackModifiers; pushBackModifiers = null; } T t = variableDeclaratorsRest(pos(), mods, type, ident(), false, null, vdefs); return t; } @Override protected <T extends ListBuffer<? super JCVariableDecl>> T variableDeclaratorsRest( int pos, JCModifiers mods, JCExpression type, Name name, boolean reqInit, Comment dc, T vdefs) { if (S.jml()) reqInit = false; // In type checking we check this more // thoroughly // Here we just allow having no initializer return super.variableDeclaratorsRest(pos, mods, type, name, reqInit, dc, vdefs); } @Override public JCExpression variableInitializer() { S.setJmlKeyword(false); try { return super.variableInitializer(); } finally { // We should be looking at a comma or a semicolon or a right brace, // so we are not too late to set this S.setJmlKeyword(true); } } /** * This is overridden to try to get <:, <# and <=# with the right precedence */ // FIXME - not sure this is really robust protected int prec(ITokenKind token) { if (token instanceof JmlTokenKind) { switch ((JmlTokenKind)token) { // FIXME - check all these precedences case EQUIVALENCE: case INEQUIVALENCE: case IMPLIES: case REVERSE_IMPLIES: return -1; case SUBTYPE_OF: case JSUBTYPE_OF: case LOCK_LT: case LOCK_LE: return TreeInfo.ordPrec; case DOT_DOT: case ENDJMLCOMMENT: return -1000; default: return 1000; } // if (token != JmlTokenKind.SUBTYPE_OF // && token != JmlTokenKind.JSUBTYPE_OF // && token != JmlTokenKind.LOCK_LT // && token != JmlTokenKind.LOCK_LE) return -1; // For // // inequivalence // // and // // reverse/implies // return TreeInfo.ordPrec; // All the JML operators are comparisons } return super.prec(token); } // MAINTENANCE ISSUE - (Almost) Duplicated from JavacParser.java in order to track // Jml tokens protected JCExpression term2Rest(JCExpression tt, int minprec) { boolean bad = tt instanceof JCErroneous; JCExpression t = tt; JCExpression[] odStack = newOdStack(); Token[] opStack = newOpStack(); // optimization, was odStack = new Tree[...]; opStack = new Tree[...]; int top = 0; odStack[0] = t; int startPos = token.pos; Token topOp = Tokens.DUMMY; while (prec(S.token().ikind) >= minprec) { // FIXME - lookahead token - presumes scanner is just one token ahead opStack[top] = topOp; top++; topOp = S.token(); JmlTokenKind topOpJmlToken = jmlTokenKind(); nextToken(); // S.jmlToken() changes odStack[top] = (topOp.kind == INSTANCEOF) ? parseType() : term3(); while (top > 0 && prec(topOp.ikind) >= prec(token.ikind)) { if (topOp.kind == CUSTOM) { // <: JCExpression e = jmlF.at(topOp.pos).JmlBinary(topOpJmlToken, odStack[top - 1], odStack[top]); storeEnd(e, getEndPos(odStack[top])); odStack[top - 1] = e; } else { odStack[top - 1] = makeOp(topOp.pos, topOp.kind, odStack[top - 1], odStack[top]); } top--; topOp = opStack[top]; } } Assert.check(top == 0); t = odStack[0]; if (t.hasTag(JCTree.Tag.PLUS)) { t = foldStrings(t); } odStackSupply.add(odStack); opStackSupply.add(opStack); if (bad) return tt; return t; } /** * Skips up to and including a semicolon, though not including any EOF or * ENDJMLCOMMENT */ protected void skipThroughSemi() { while (token.kind != TokenKind.SEMI && token.kind != TokenKind.EOF && jmlTokenKind() != JmlTokenKind.ENDJMLCOMMENT) nextToken(); if (token.kind == TokenKind.SEMI) nextToken(); } /** Skips up to but not including a semicolon or EOF or ENDJMLCOMMENT */ protected void skipToSemi() { while (token.kind != SEMI && token.kind != EOF && jmlTokenKind() != JmlTokenKind.ENDJMLCOMMENT) nextToken(); } /** * Skips up to but not including a semicolon or comma or EOF or * ENDJMLCOMMENT */ protected void skipToCommaOrSemi() { while (token.kind != SEMI && token.kind != COMMA && token.kind != EOF && jmlTokenKind() != JmlTokenKind.ENDJMLCOMMENT) nextToken(); } /** * Skips up to but not including a right-parenthesis or comma or EOF or * ENDJMLCOMMENT */ protected void skipToCommaOrParenOrSemi() { while (token.kind != RPAREN && token.kind != COMMA && token.kind != SEMI && token.kind != EOF && jmlTokenKind() != JmlTokenKind.ENDJMLCOMMENT) nextToken(); } /** * Skips up to a EOF or ENDJMLCOMMENT or up to and including a right brace */ public void skipThroughRightBrace() { while (token.kind != RBRACE && token.kind != EOF && jmlTokenKind() != JmlTokenKind.ENDJMLCOMMENT) nextToken(); if (token.kind != EOF) nextToken(); } /** * Skips up to a EOF or ENDJMLCOMMENT or up to and including a right * parenthesis */ public void skipThroughRightParen() { while (token.kind != RPAREN && token.kind != EOF && jmlTokenKind() != JmlTokenKind.ENDJMLCOMMENT) nextToken(); if (token.kind != EOF) nextToken(); } public JCErroneous syntaxError(int pos, List<JCTree> errs, String key, Object... args) { setErrorEndPos(pos); reportSyntaxError(pos, key, args); return toP(F.at(pos).Erroneous(errs)); } // FIXME - do we need to set errorEndPos in the following? /** Creates an error message for which the source is a single character */ public void jmlerror(int pos, String key, Object... args) { log.error(new JmlTokenizer.DiagnosticPositionSE(pos, pos), key, args); } /** Creates a warning message for which the source is a single character */ public void jmlwarning(int pos, String key, Object... args) { log.warning(new JmlTokenizer.DiagnosticPositionSE(pos, pos), key, args); } /** * Creates an error message for which the source is a range of characters, * from begin up to and not including end; the identified line is that of * the begin position. */ public void jmlerror(int begin, int end, String key, Object... args) { log.error(new JmlTokenizer.DiagnosticPositionSE(begin, end - 1), key, args); // TODO - not unicode friendly } /** * Creates a warning message for which the source is a range of characters, * from begin up to and not including end; the identified line is that of * the begin position. */ public void jmlwarning(int begin, int end, String key, Object... args) { log.warning(new JmlTokenizer.DiagnosticPositionSE(begin, end - 1), key, args); // TODO - not unicode friendly } /** * Creates an error message for which the source is a range of characters, * from begin up to and not including end; it also specifies a preferred * location to highlight if the output format can only identify a single * location; the preferred location is also the line that is identified. */ public void jmlerror(int begin, int preferred, int end, String key, Object... args) { log.error( new JmlTokenizer.DiagnosticPositionSE(begin, preferred, end - 1), key, args);// TODO - not unicode friendly } /** * Creates a warning message for which the source is a range of characters, * from begin up to and not including end; it also specifies a preferred * location to highlight if the output format can only identify a single * location; the preferred location is also the line that is identified. */ public void jmlwarning(int begin, int preferred, int end, String key, Object... args) { log.warning( new JmlTokenizer.DiagnosticPositionSE(begin, preferred, end - 1), key, args);// TODO - not unicode friendly } // FIXME - check the use of Token.CUSTOM vs. null // FIXME - review the remaining uses of log.error vs. jmlerror // FIXME - refactor to better match the grammar as a top-down parser // FIXME - refactor for extension // FIXME - need to sort out the various modes - isJml isJmlDeclaration isLocalOrAnonClass... }