/*
* 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.HashSet;
import java.util.Set;
import org.eclipse.jdt.annotation.Nullable;
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.EndPosTable;
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.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
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 JmlTokenizer extends JavadocTokenizer { // FIXME - or should this be JavaTokenizer?
/** The compilation context with which this instance was created */
public Context context;
/** A temporary reference to the instance of the nowarn collector for the context. */
/*@ non_null */ public Nowarns nowarns;
/**
* 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;
/** The set of defined keys for identifying optional comments */
/*@NonNull*/ protected Set<String> keys = new HashSet<>();
/** 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 are considered identifiers 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;
/**
* The style of comment, either CommentStyle.LINE or
* CommentStyle.BLOCK, prior to the scanner calling processComment()
*/
@Nullable 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 JmlTokenKind jmlTokenKind;
private JmlToken jmlToken;
/**
* Creates a new tokenizer, for JML and Java tokens.<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 in the buffer
*/
//@ requires inputLength <= input.length;
protected JmlTokenizer(JmlScanner.JmlFactory fac, char[] input, int inputLength) {
super(fac, input, inputLength);
context = fac.context;
getKeys();
}
/**
* Creates a new tokenizer, for JML tokens.<P>
*
* @param fac The factory generating the scanner
* @param buffer The character buffer to scan
*/
// @ requires fac != null && buffer != null;
protected JmlTokenizer(JmlScanner.JmlFactory fac, CharBuffer buffer) {
super(fac, buffer);
context = fac.context;
getKeys();
}
protected void getKeys() {
keys.addAll(Utils.instance(context).commentKeys);
}
/**
* 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) {
jml = j;
}
/**
* Sets the keyword mode
*
* @param j
* the new value of the keyword mode
*/
public void setJmlKeyword(boolean j) {
jmlkeyword = j;
}
/** The current set of conditional keys used by the tokenizer.
*/
public Set<String> keys() {
return keys;
}
protected int saved_bp;
protected char saved_ch;
public void saveReaderState() {
saved_bp = reader.bp;
saved_ch = reader.ch;
}
public void restoreReaderState() {
reader.bp = saved_bp;
reader.ch = saved_ch;
}
public void setReaderState(int bp) {
reader.bp = bp;
reader.ch = reader.buf[bp]; // FIXME - this is not unicode correct
}
public void setReaderState(int bp, char ch) {
reader.bp = bp;
reader.ch = ch;
}
// This is called whenever the Java (superclass) scanner has scanned a whole
// comment. We override it in order to handle JML comments specially. The
// overridden
// method returns and proceeds immediately to scan for the next Java token.
// Here if the comment is indeed a JML comment, we reset the scanner to the
// beginning of the comment, set jml to true, and let the scanner proceed.
@Override
protected Tokens.Comment processComment(int pos, int endPos, CommentStyle style) {
// The range pos() to endPos() does include the opening and closing
// comment characters.
// It does not include line ending for line comments, so
// an empty line comment can be just two characters.
if (noJML || style == CommentStyle.JAVADOC) {
return super.processComment(pos,endPos,style);
}
// Save the buffer positions in the super class
// After a call of scanChar(), ch is the character at the bp
// position in character buffer; if the buffer held a unicode
// sequence, bp is at the end of the sequence and ch is the
// translated character
saveReaderState(); // This is the state after having read the comment that we are now processing
// Note on scanChar - it is valid to call scanChar if
// ch is not EOI; the last character of the input will be an EOI
// Set the scanner to the beginning of the comment being processed
// pos() points to the first / of the comment sequence (or to the
// last character of the unicode sequence for that /)
setReaderState(pos);
int commentStart = pos;
scanChar(); // the next / or *
int ch = scanChar(); // The @, or a + or - if there are keys, or something else if it is not a JML comment
int plusPosition = reader.bp;
// FIXME - do we need this line?
//if (bp >= end) return; // This can happen if there is a // right at the end of the line
boolean someplus = false; // true when at least one + key has been read
boolean foundplus = false; // true when a + key that is in the list of enabled keys has been read
while ((ch=reader.ch) != '@') {
if (ch != '+' && ch != '-') {
// Not a valid JML comment - just return
// Restart after the comment we are currently processing
restoreReaderState();
return super.processComment(pos, endPos, style);
}
boolean isplus = ch == '+';
ch = scanChar();
if (ch == '@') {
// Old -style //+@ or //-@ comments
// Too many warnings to enable this message
if (Options.instance(context).isSet("-Xlint:deprecation")) {
log.warning(new DiagnosticPositionSE(commentStart,plusPosition,reader.bp),"jml.deprecated.conditional.annotation");
}
// To be backward compatible at the moment,
// quit for //-@, process the comment if //+@
// TODO: Change this behavior once the library specs have no more //+@ or /*+@ comments
if (isplus) break;
restoreReaderState();
return super.processComment(pos, endPos, style);
}
if (isplus) someplus = true;
// We might call nextToken here and look for an identifier,
// but that could be a recursive call of nextToken, which might
// be dangerous, so we use scanIdent instead
if (!Character.isJavaIdentifierStart(ch)) {
// Not a valid JML comment - just return
// Restart after the comment
restoreReaderState();
return super.processComment(pos, endPos, style);
}
// Check for keys
scanIdent(); // Only valid if ch is isJavaIdentifierStart; sets 'name'
reader.sp = 0; // See the use of sp in UnicodeReader - if sp is not reset, then the
// next identifier is appended to the key
String key = name.toString(); // the identifier just scanned
if (keys.contains(key)) {
if (isplus) foundplus = true;
else {
// negative key found - return - ignore comment for JML
restoreReaderState();
return super.processComment(pos, endPos, style);
}
}
}
if (!foundplus && someplus) {
// There were pluses but not the plus we were looking for, so ignore JML comment
// Restart after the comment
restoreReaderState();
return super.processComment(pos, endPos, style);
}
while (reader.ch == '@') {
reader.scanChar();
} // Gobble up all leading @s
if (jml) {
// We are already in a JML comment - so we have an embedded comment.
// The action is to just ignore the embedded comment start
// characters.
// do nothing
} else {
// We initialize state and proceed to process the comment as JML text
jmlcommentstyle = style;
jml = true;
jmlkeyword = true;
}
return null; // Tell the caller to ignore the comment
}
/**
* Scans the next token in the character buffer; after this call, then
* token() and jmlToken() provide the values of the scanned token. Also
* pos() gives the beginning character position of the scanned token, and
* endPos() gives (one past) the end character position of the scanned
* token.
* <P>
* Both pos and endPos point to the end of a unicode sequence if the // FIXME - validate this paragraph
* relevant character is represented by a unicode sequence. The internal bp
* pointer should be at endPos and the internal ch variable should contain
* the following character.
* <P>
* Note that one of the following holds at return from this method:<BR>
* a) token() != CUSTOM && token() != null && jmlToken() == null (a Java token)<BR>
* b) token() == CUSTOM && jmlToken() != null (a JML token)<BR>
* c) token() == IMPORT && jmlToken() == MODEL (the beginning of a model import)
* <BR>
*/
// NOTE: We do quite a bit of hackery here to avoid changes in the Scanner.
// We did however have to change some permissions, so perhaps we should have
// bitten the bullet and just combined all of this into a revised scanner.
// It would have not made possible our goal of keeping changes in the
// javac code minimal,
// for easier maintenance, but it would have made for a simpler and more
// understandable and better designed JML scanner.
@Override
public Token readToken() {
// JmlScanner enters with this relevant global state:
// jml - true if we are currently within a JML comment
// jmlcommentstyle - (if jml is true) the kind of comment block (LINE or BLOCK) we are in
// jmlkeyword - (if jml is true) whether to translate non-backslash
// keywords as JML keywords or as Java identifiers
// Responds with updates to that state as well as to
// jmlToken, jmlTokenKind, bp, ch, endPos, tk, name
boolean initialJml = jml;
int pos, endPos;
while (true) { // The loop is just so we can easily restart with a 'continue'
jmlTokenKind = null;
Token t = super.readToken(); // Sets tk, May modify jmlTokenKind
pos = t.pos;
endPos = t.endPos;
// Note that the above may call processComment. If the comment is a JML comment, the
// reader and tokenizer will be repointed to tokenize within the JML comment. This
// may result in a CUSTOM Java token being produced with jmlTokenKind set, for example,
// by calls down to methods such as scanOperator and scanIdent in this class.
// So now we can produce a replacement token.
// FIXME - what about doc comments in general?
// Possible situations at this point
// a) jmlToken.kind != CUSTOM, jmlTokenKind == null (a Java token)
// b) jmlToken.kind == CUSTOM, jmlTokenKind != null (a JML token)
// c) jmlToken.kind == MONKEYS_AT, jmlTokenKind = ENDJMLCOMMENT, jml = false (leaving a JML comment)
// jml may have changed (and will for case c), meaning that we have entered or are leaving a JML comment
// No special token is issued for starting a JML comment.
// There is a mode that determines whether ENDJMLCOMMENT tokens are returned.
// The advantage of returning them is that then error recovery is better - bad JML can be caught by
// unexpected end of comment; the disadvantage is that ENDJMLCOMMENT tokens can appear lots of places -
// the parser has to expect and swallow them. In official JML, clauses and expressions must be contained
// in whole JML comments, so the locations where END tokens can occur are restricted. Nevertheless, lots
// of bugs have resulted from unexpected end of comment tokens.
// An end token is never returned if the comment is empty or has only nowarn material in it.
// nowarn information is scanned and stored, but not returned as tokens
// FIXME - review and fix use of ENDOFCOMMENT tokens in the parser; also unicode
if (!jml) {
if (jmlTokenKind == JmlTokenKind.ENDJMLCOMMENT) {
jmlToken = new JmlToken(jmlTokenKind, t);
if (!returnEndOfCommentTokens || !initialJml) continue; // Go get next token
return jmlToken;
} else {
return t; // A Java token
}
}
if (jmlTokenKind == JmlTokenKind.NOWARN) {
scanNowarn(reader.bp); // FIXME - I doubt this is working after the Java 8 merge
continue;
} else if (tk == TokenKind.STAR && reader.ch == '/'
&& jmlcommentstyle == CommentStyle.BLOCK) {
// We're in a BLOCK comment and we scanned a * and the next
// character is a / so we are at the end of the comment
scanChar(); // advance past the /
jml = false;
endPos = reader.bp;
jmlTokenKind = JmlTokenKind.ENDJMLCOMMENT;
if (!returnEndOfCommentTokens || !initialJml) continue;
} else if (tk == TokenKind.MONKEYS_AT) {
// This is just for the case that a terminating */ is preceded by one or more @s
// JML allows this, but it just complicates scanning - I wish JML would deprecate that syntax
int first = reader.bp;
if (jmlcommentstyle == CommentStyle.BLOCK
&& (reader.ch == '*' || reader.ch == '@')) {
// This may be the end of a BLOCK comment. We have seen a real @;
// there may be more @s and then the * and /
while (reader.ch == '@')
reader.scanChar(); // advances bp, ch is the character at bp; bp is end of unicode sequence
if (reader.ch != '*') {
// TODO - should really report this as a legal series
// of AT tokens
jmlError(first, reader.bp, "jml.unexpected.at.symbols");
return readToken();
} else {
reader.scanChar(); // consume *
if (reader.ch != '/') {
// TODO - should really report this as a legal
// series of AT tokens and a STAR
jmlError(first, reader.bp, "jml.at.and.star.but.no.slash");
return readToken();
} else {
reader.scanChar(); // consume /
}
}
tk = TokenKind.CUSTOM;
jmlTokenKind = JmlTokenKind.ENDJMLCOMMENT;
jml = false;
endPos = reader.bp;
if (!returnEndOfCommentTokens || !initialJml) continue;
}
} else if (tk == TokenKind.LPAREN && reader.ch == '*') {
int start = reader.bp;
reader.scanChar(); // skip the *
outer: do {
while (reader.ch != '*') {
if ((jmlcommentstyle == CommentStyle.LINE && (reader.ch == '\n' || reader.ch == '\r')) || reader.ch == LayoutCharacters.EOI ) {
// Unclosed informal expression
jmlError(start,reader.bp,"jml.unclosed.informal.expression");
break outer;
}
reader.putChar(reader.ch);
scanChar();
}
int star = reader.bp;
char ch = scanChar(); // skip the *
if ((jmlcommentstyle == CommentStyle.LINE && (ch == '\n' || ch == '\r')) || ch == LayoutCharacters.EOI ) {
// Unclosed informal expression
jmlError(start,reader.bp,"jml.unclosed.informal.expression");
break outer;
}
if (jmlcommentstyle == CommentStyle.BLOCK && ch == '/') {
// Unclosed informal expression
jmlError(start,reader.bp,"jml.unclosed.informal.expression");
setReaderState(star,'*');
break outer;
}
} while (reader.ch != ')');
if (reader.ch == ')') scanChar();
endPos = reader.bp;
tk = TokenKind.CUSTOM;
jmlTokenKind = JmlTokenKind.INFORMAL_COMMENT;
// } else if (jmlTokenKind == JmlTokenKind.MODEL) {
// int prevbp = reader.bp; // FIXME _ this is probably not working; check endPos
// Token tt = super.readToken();
// //docComment = dc;
// if (jml && tt.kind == TokenKind.IMPORT) {
// jmlTokenKind = JmlTokenKind.MODEL;
// } else {
// // Need to backtrack
// setReaderState(prevbp);
// tk = TokenKind.CUSTOM;
// jmlTokenKind = JmlTokenKind.MODEL;
// }
} else if (tk == TokenKind.LBRACE && reader.ch == '|') {
tk = TokenKind.CUSTOM;
jmlTokenKind = JmlTokenKind.SPEC_GROUP_START;
scanChar();
endPos = reader.bp;
} else if (tk == TokenKind.BAR && reader.ch == '}') {
tk = TokenKind.CUSTOM;
jmlTokenKind = JmlTokenKind.SPEC_GROUP_END;
reader.scanChar();
endPos = reader.bp;
} else if (tk == TokenKind.ERROR && reader.sp == 2 && reader.sbuf[0] == '.' && reader.sbuf[1] == '.') {
jmlTokenKind = JmlTokenKind.DOT_DOT;
endPos = reader.bp;
}
return jmlTokenKind == null ? t : new JmlToken(jmlTokenKind, TokenKind.CUSTOM, pos, endPos);
}
}
/** Overrides the Java scanner in order to catch situations in which the
* first part of a double literal is immediately followed by a dot,
* e.g. 123.. ; within JML, this should be an int followed by a DOT_DOT token
*/
@Override
public void scanFractionAndSuffix(int pos) {
if (jml && reader.ch == '.') {
reader.sp--;
reader.bp--; // FIXME - not unicode safe // FIXME - adjust pos?
tk = TokenKind.INTLITERAL;
} else {
super.scanFractionAndSuffix(pos);
}
}
/** This method presumes the NOWARN token has been read and handles the names
* within the nowarn, reading through the terminating semicolon or end of JML comment
*/
public void scanNowarn(int pos) {
boolean prev = jmlkeyword;
jmlkeyword = false;
try {
Token t = readToken();
if (t.kind == TokenKind.SEMI || t.ikind == JmlTokenKind.ENDJMLCOMMENT) {
// No labels - so this means suppress everything
// Indicate this with null
handleNowarn(log.currentSource(), pos, null);
} else {
while (t.kind != TokenKind.SEMI && t.ikind != JmlTokenKind.ENDJMLCOMMENT) {
if (t.kind != TokenKind.IDENTIFIER){
// Bad statement or missing terminating semicolon
jmlError(t.pos, reader.bp, "jml.bad.nowarn");
// expected identifier
skipThroughChar(';');
return;
}
String label = reader.chars();
handleNowarn(log.currentSource(), reader.bp, label);
t = readToken();
if (t.kind == TokenKind.COMMA){
t = readToken();
}
}
}
if (jmlTokenKind == JmlTokenKind.ENDJMLCOMMENT) {
log.warning(t.pos, "jml.nowarn.with.no.semicolon");
// Here we are swallowing the end of comment - we normally
// expect that token in the stream. However if there is just a
// nowarn, the Java scanner will not expect a standalone ENDJMLCOMMENT
// FIXME - check the case of //@ some JML stuff ; nowarn xx
// or with /*@ -- does this parse OK
}
} finally {
jmlkeyword = prev;
}
}
/**
* This method is called internally when a nowarn lexical pragma is scanned;
* it is called once for each label in the pragma.
*
* @param file The file being scanned
* @param pos The 0-based character position of the label
* @param label The label in the pragma
*/
protected void handleNowarn(DiagnosticSource file, int pos, String label) {
nowarns.addItem(file,pos,label);
}
/**
* Scans the next character, doing any unicode conversion. The buffer
* pointer is incremented BEFORE fetching the character. So except for
* unicode and backslash issues, after this call, buf[bp] == ch; This does
* not set pos() or endPos(). If the character scanned is unicode, then upon
* return, bp points to the last character of the unicode sequence within
* the character buffer (buf). Also, if the character just scanned is
* unicode, then unicodeConversionBp == bp. This enables a check (via the
* test unicodeConversionBp == bp) of whether the character just scanned is
* unicode and hence not allowed to be the backslash that starts a new
* unicode sequence.
*/
protected char scanChar() {
reader.scanChar();
return reader.ch;
}
/**
* Overrides the superclass method in order to denote a backslash as
* special. This lets scanOperator detect the JML backslash keywords.
* Otherwise the backslash is reported as an illegal character. This does
* not affect scanning literals or unicode conversion.
*/
@Override
protected boolean isSpecial(char ch) {
if (jml && ch == '\\') return true;
return super.isSpecial(ch);
}
/**
* Called when the superclass scanner scans a line termination. We override
* this method so that when jml is true, we can (a) if this is a LINE
* comment, signal the end of the comment (b) if this is a BLOCK comment,
* skip over leading @ symbols in the following line
*/
@Override
protected void processLineTerminator(int pos, int endPos) {
super.processLineTerminator(pos, endPos);
if (jml) {
if (jmlcommentstyle == CommentStyle.LINE) {
jml = false;
jmlTokenKind = JmlTokenKind.ENDJMLCOMMENT;
if (returnEndOfCommentTokens) {
reader.ch = '@'; // Signals the end of comment to nextToken()
reader.bp--; // FIXME - not right for unicode
}
} else {
// skip any whitespace followed by @ symbols
while (Character.isWhitespace(reader.ch))
reader.scanChar();
while (reader.ch == '@')
reader.scanChar();
}
}
}
/**
* Called to find identifiers and keywords when a character that can start a
* Java identifier has been scanned. We override so that when jml and
* jmlkeyword are true,
* we can convert identifiers to JML keywords, including converting assert
* to the JML assert keyword.
* Sets tk, name, jmlTokenKind
*/
@Override
protected void scanIdent() {
super.scanIdent(); // Sets tk and name
if (!jml || !jmlkeyword) // FIXME _ in this case jmlTokenKind should be set to null?
return;
if (tk == TokenKind.IDENTIFIER) {
String s = reader.chars();
// TODO - we are just ignoring the redundantly suffixes
if (s.endsWith("_redundantly")) {
s = s.substring(0, s.length() - "_redundantly".length());
}
JmlTokenKind tt = JmlTokenKind.allTokens.get(s);
if (tt != null) {
jmlTokenKind = tt;
return;
}
// FIXME - can we count on jmlTokenKind to be null?
} else if (tk == TokenKind.ASSERT) {
jmlTokenKind = JmlTokenKind.ASSERT;
}
}
/**
* Called to scan an operator. We override to detect JML operators as well.
* And also backslash tokens.
*/
@Override
protected void scanOperator() {
if (jml && reader.ch == '\\') {
// backslash identifiers get redirected here since a \ itself is an
// error in pure Java - isSpecial does the trick
int ep = reader.bp;
reader.putChar(reader.ch); // include the backslash
reader.scanChar();
if (Character.isLetter(reader.ch)) {
super.scanIdent(); // tk and name are set
// assuming that token() is Token.IDENTIFIER
String seq = reader.chars();
JmlTokenKind t = JmlTokenKind.backslashTokens.get(seq);
if (t != null) {
tk = TokenKind.CUSTOM;
jmlTokenKind = t;
} else {
jmlError(ep, reader.bp, "jml.bad.backslash.token", seq);
// token is set to ERROR
}
} else {
jmlError(ep, reader.bp, "jml.extraneous.backslash");
// token is set to ERROR
}
return;
}
super.scanOperator();
if (!jml) return; // not in JML - so we scanned a regular Java operator
int endPos = reader.bp;
TokenKind t = tk;
if (tk == TokenKind.EQEQ) {
if (reader.ch == '>') {
reader.scanChar();
endPos = reader.bp;
tk = TokenKind.CUSTOM;
jmlTokenKind = JmlTokenKind.IMPLIES; // ==>
}
} else if (t == TokenKind.LTEQ) {
if (reader.ch == '=') {
// At the last = of <==
reader.scanChar();
if (reader.ch == '>') {
tk = TokenKind.CUSTOM;
jmlTokenKind = JmlTokenKind.EQUIVALENCE; // <==>
reader.scanChar();
endPos = reader.bp;
} else {
tk = TokenKind.CUSTOM;
jmlTokenKind = JmlTokenKind.REVERSE_IMPLIES; // <==
endPos = reader.bp;
}
} else if (reader.ch == '!') {
int k = reader.bp; // Last character of the !
reader.scanChar();
if (reader.ch == '=') {
scanChar();
if (reader.ch == '>') {
tk = TokenKind.CUSTOM;
jmlTokenKind = JmlTokenKind.INEQUIVALENCE; // <=!=>
reader.scanChar();
endPos = reader.bp;
} else { // reset to the !
setReaderState(k,'!');
}
} else {
setReaderState(k,'!');
}
}
} else if (t == TokenKind.LT) {
if (reader.ch == ':') {
tk = TokenKind.CUSTOM;
jmlTokenKind = JmlTokenKind.SUBTYPE_OF; // <:
reader.scanChar();
endPos = reader.bp;
} else if (reader.ch == '-') {
tk = TokenKind.CUSTOM;
jmlTokenKind = JmlTokenKind.LEFT_ARROW; // <-
reader.scanChar();
endPos = reader.bp;
} else if (reader.ch == '#') {
tk = TokenKind.CUSTOM;
jmlTokenKind = JmlTokenKind.LOCK_LT; // <#
reader.scanChar();
if (reader.ch == '=') {
jmlTokenKind = JmlTokenKind.LOCK_LE; // <#=
reader.scanChar();
}
endPos = reader.bp;
}
} else if (tk == TokenKind.ARROW) {
// Java 8 has a token called ARROW - which was not present in Java 7
// FIXME - we will need to combine the two uses
jmlTokenKind = JmlTokenKind.RIGHT_ARROW;
// } else if (tk == TokenKind.SUB) {
// if (reader.ch == '>') {
// tk = TokenKind.CUSTOM;
// jmlTokenKind = JmlTokenKind.RIGHT_ARROW; // ->
// reader.scanChar();
// endPos = reader.bp;
// }
}
}
/**
* Reads characters up to an EOI or up to and through the specified
* character, which ever is first.
*
* @param c the character to look for
*/
public void skipThroughChar(char c) {
while (reader.ch != c && reader.ch != LayoutCharacters.EOI)
reader.scanChar();
if (reader.ch == c)
reader.scanChar();
}
/** Overrides in order to catch the error message that results from seeing just
* a .. instead of . or ...
*/
@Override
protected void lexError(int pos, String key, Object... args) {
if (reader.sp == 2 && "illegal.dot".equals(key) && reader.sbuf[0] == '.' && reader.sbuf[1] == '.') {
tk = TokenKind.CUSTOM;
jmlTokenKind = JmlTokenKind.DOT_DOT;
} else {
jmlError(pos,key,args);
}
}
/** Records a lexical error (no valid token) at a single character position,
* the resulting token is an ERROR token. */
protected void jmlError(int pos, String key, Object... args) {
log.error(new DiagnosticPositionSE(pos,pos),key,args);
tk = TokenKind.ERROR;
jmlToken = null;
jmlTokenKind = null;
errPos(pos);
}
/** Logs an error; the character range of the error is from pos up to
* BUT NOT including endpos.
*/
protected void jmlError(int pos, int endpos, String key, Object... args) {
// FIXME - the endPos -1 is not unicode friendly - and actually we'd like to adjust the
// positions of pos and endpos to correspond to appropriate unicode boundaries, here and in
// the other jmlError method
log.error(new DiagnosticPositionSE(pos,endpos-1),key,args);
tk = TokenKind.ERROR;
jmlToken = null;
errPos(pos);
}
/** A derived class of DiagnosticPosition that allows for straightforward setting of the
* various positions associated with an error message.
*/
// FIXME - comment on relationship to unicode characters
static public class DiagnosticPositionSE implements DiagnosticPosition {
protected int begin;
protected int preferred;
protected int end; // The end character, NOT ONE CHARACTER BEYOND
public DiagnosticPositionSE(int begin, int end) {
this.begin = begin;
this.preferred = begin;
this.end = end;
}
public DiagnosticPositionSE(int begin, int preferred, int end) {
this.begin = begin;
this.preferred = preferred;
this.end = end;
}
@Override
public JCTree getTree() {
return null;
}
@Override
public int getStartPosition() {
return begin;
}
@Override
public int getPreferredPosition() {
return preferred;
}
@Override
public int getEndPosition(EndPosTable endPosTable) {
return end;// FIXME
}
}
// public BasicComment<UnicodeReader> makeJavaComment(int pos, int endPos, CommentStyle style) {
// char[] buf = reader.getRawCharacters(pos, endPos);
// return new BasicComment<UnicodeReader>(new UnicodeReader(fac, buf, buf.length), style);
// }
}