/*
* This file is part of the OpenJML project.
* Author: David R. Cok
*/
package com.sun.tools.javac.parser;
import java.nio.CharBuffer;
import java.util.Map;
import java.util.Set;
import org.jmlspecs.openjml.JmlOption;
import org.jmlspecs.openjml.JmlTokenKind;
import org.jmlspecs.openjml.Nowarns;
import org.jmlspecs.openjml.Utils;
import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle;
import com.sun.tools.javac.parser.Tokens.Token;
import com.sun.tools.javac.parser.Tokens.TokenKind;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.DiagnosticSource;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.tools.javac.util.LayoutCharacters;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Log.WriterKind;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Options;
/* NOTE: - oddities in the Scanner class
It seems that if the first character of a token is unicode, then pos is
set to be the last character of that initial unicode sequence.
I don't think the endPos value is set correctly when a unicode is read;
endPos appears to be the end of the character after the token characters,
which is not correct if the succeeding token is a unicode character
unicodeConversionBp does not seem to be useful for anything; does it stop
two unicode conversions in a row? Actually, when an initial backslash is
in unicode, then that backslash is not the beginning of another unicode
sequence - the check on unicodeConversionBp stops this.
Recovery after an ERROR token that is a \ seems to consume extra characters
I'm not sure the behavior of unicode with multiple backslashes is correct
*/
// FIXME - turn off jml when an exception happens?
/**
* This class is an extension of the JDK scanner that scans JML constructs as
* well. Note that it relies on some fields and methods of Scanner being
* protected that are not declared protected in the current version of OpenJDK.
* No other changes to Scanner are made, which does require a number of complicating
* workarounds in this code. In retrospect, a much simpler JML scanner could have
* been produced by being more surgical with JavaScanner; but given the goal to
* be minimally invasive, this is what we have.
*
* @author David Cok
*/
public class JmlScanner extends Scanner {
/**
* This factory is used to generate instances of JmlScanner. There is
* one factory per compilation context and a new instance of JmlScanner
* for each new file parsed. The Context is used to
* have a consistent set of information across all files parsed within a
* compilation task.
*/
public static class JmlFactory extends ScannerFactory {
/** The unique compilation context with which this factory was created */
public Context context;
/**
* Creates a new factory that creates instances of JmlScanner; a new scanner
* is used for each file being parsed.
*
* @param context The common context used for this whole compilation
*/
protected JmlFactory(Context context) {
super(context);
this.context = context;
}
/**
* Call this to register this factory as the factory to be used to
* create scanners, which will be JML scanners instead of Java scanners,
* within this compilation context.
*
* @param context The common compilation context
*/
public static void preRegister(final Context context) {
context.put(scannerFactoryKey,
new Context.Factory<ScannerFactory>() {
public ScannerFactory make(Context context) {
return new JmlScanner.JmlFactory(context);
}
});
}
/** A convenience method to produce a new scanner on the given input,
* set to parse JML and javadoc comments as well.
* @param input the input to parse
* @return the new scanner, initialized at the beginning of the input
*/
public Scanner newScanner(CharSequence input) {
return newScanner(input,true);
}
/*
* (non-Javadoc)
*
* @see com.sun.tools.javac.parser.Scanner.Factory#newScanner(char[],
* int, boolean)
*/
/** The last character in the input array may be overwritten with an
* EOI character; if you control the size of the input array, make it at
* least one character larger than necessary,
* to avoid reallocating and copying the array.
*/
@Override
public JmlScanner newScanner(char[] input, int inputLength, boolean keepDocComments) {
JmlScanner j = new JmlScanner(this, input, inputLength);
init(j);
return j;
}
/** Creates a new scanner that scans the given input */
@Override
public JmlScanner newScanner(CharSequence input, boolean keepDocComments) {
JmlScanner j = new JmlScanner(this, CharBuffer.wrap(input));
init(j);
return j;
}
/** Does some initialization of just produced scanners - all in one method so that
* the code does not have to be replicated across various newScanner methods.
* @param j the JmlSCanner to initialize
*/
protected void init(JmlScanner j) {
j.noJML = !JmlOption.isOption(context, JmlOption.JML);
j.keys = Utils.instance(context).commentKeys;
j.jmltokenizer.nowarns = Nowarns.instance(context);
}
}
/** The compilation context */
protected Context context;
/** A reference to the tokenizer being used */
protected JmlTokenizer jmltokenizer;
/**
* A flag that, when true, causes all JML constructs to be ignored; it is
* set on construction according to a command-line option.
*/
protected boolean noJML = false;
// /** Set to true internally while the scanner is within a JML comment */
// protected boolean jml = false;
/** Set to true internally while the scanner is within a JML comment */
protected boolean jml() {
return jmltokenizer.jml;
}
/** The set of keys for identifying optional comments */
/*@NonNull*/ protected Set<String> keys;
/** A mode of the scanner that determines whether end of jml comment tokens are returned */
public boolean returnEndOfCommentTokens = true;
/**
* When jml is true, then (non-backslash) JML keywords are recognized if
* jmlkeyword is true and not if jmlkeyword is false; this is set by the
* parser according to whether non-backslash JML tokens should be recognized
* in the current parser state (e.g. such tokens are not recognized while
* within expressions). jmlkeyword is always set to true at the beginning of
* JML comments.
*/
protected boolean jmlkeyword = true;
/**
* Set by the scanner to the style of comment, either CommentStyle.LINE or
* CommentStyle.BLOCK, prior to the scanner calling processComment()
*/
protected CommentStyle jmlcommentstyle;
// /** Valid after nextToken() and contains the next token if it is a JML token
// * and null if the next token is a Java token */
// //@ nullable
// protected JmlToken jmlToken;
/**
* Creates a new scanner, but you should use JmlFactory.newScanner() to get
* one, not this constructor.<P>
* The last character in the input array may be overwritten with an EOI character; if you
* control the size of the input array, make it at least one character larger than necessary,
* to avoid the overwriting or reallocating and copying the array.
* @param fac The factory generating the scanner
* @param input The character buffer to scan
* @param inputLength The number of characters to scan from the buffer
*/
// @ requires fac != null && input != null;
// @ requires inputLength <= input.length;
protected JmlScanner(JmlFactory fac, char[] input, int inputLength) {
super(fac, new JmlTokenizer(fac, input, inputLength));
context = fac.context;
jmltokenizer = (JmlTokenizer)tokenizer;
}
/**
* Creates a new scanner, but you should use JmlFactory.newScanner() to get
* one, not this constructor.
*
* @param fac The factory generating the scanner
* @param buffer The character buffer to scan
*/
// @ requires fac != null && buffer != null;
protected JmlScanner(JmlFactory fac, CharBuffer buffer) {
super(fac, new JmlTokenizer(fac, buffer));
context = fac.context;
jmltokenizer = (JmlTokenizer)tokenizer;
}
/**
* Sets the jml mode - used for testing to be able to test constructs that
* are within a JML comment.
*
* @param j set the jml mode to this boolean value
*/
public void setJml(boolean j) {
jmltokenizer.setJml(j);
}
/**
* Sets the keyword mode
*
* @param j
* the new value of the keyword mode
*/
public void setJmlKeyword(boolean j) {
if (!savedTokens.isEmpty()) Log.instance(context).getWriter(WriterKind.NOTICE).println("JmlKeyword mode changed while token buffer has lookahead");
jmltokenizer.setJmlKeyword(j);
jmlkeyword = j;
}
/** The current set of conditional keys used by the scanner.
*/
public Set<String> keys() {
return jmltokenizer.keys();
}
/**
* Valid after nextToken()
*
* @return the JML token for the most recent scan, null if the token is
* purely a Java token
*/
//@ pure nullable
public JmlToken jmlToken() {
Token t = token();
return t instanceof JmlToken ? (JmlToken)t : DUMMY;
}
JmlToken DUMMY = new JmlToken(null,TokenKind.ERROR,0,0);
public Token rescan() {
if (!jml()) return token;
int i = token.pos;
jmltokenizer.setReaderState(i);
savedTokens.clear();
nextToken();
return token;
}
public void setToken(Token token) {
this.token = token;
}
public String chars() {
return jmltokenizer.reader.chars();
}
public int currentPos() {
return jmltokenizer.reader.bp; // FIXME - do we need to add an offset for where the annotation comment starts?
}
}