mirror of https://github.com/contiki-ng/mspsim
highlighting c source viewer
git-svn-id: https://mspsim.svn.sourceforge.net/svnroot/mspsim/mspsim@39 23d1a52b-0c3c-0410-b72d-8f29ab48fe35
This commit is contained in:
parent
56ef447eb2
commit
d132430db9
|
|
@ -0,0 +1,944 @@
|
|||
package se.sics.mspsim.extutil.highlight;
|
||||
// Public domain, no restrictions, Ian Holyer, University of Bristol.
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Provide a hand-written scanner for the Java language.
|
||||
*/
|
||||
|
||||
public class CScanner extends Scanner {
|
||||
|
||||
private boolean debug = false;
|
||||
|
||||
/** Create a Java scanner, for Java version 1.5 by default. */
|
||||
public CScanner() {
|
||||
super();
|
||||
initKind();
|
||||
initUniKind();
|
||||
}
|
||||
|
||||
/** Create a Java scanner, for a given version between "1.1" and "1.5". */
|
||||
public CScanner(String version) {
|
||||
super();
|
||||
initKind();
|
||||
initUniKind();
|
||||
}
|
||||
|
||||
/** Override the read method from the Scanner class. */
|
||||
protected int read() {
|
||||
int type, saveStart = 0;
|
||||
if (debug)
|
||||
saveStart = start;
|
||||
|
||||
if (start >= end)
|
||||
return WHITESPACE;
|
||||
|
||||
switch (state) {
|
||||
case MID_COMMENT:
|
||||
case END_COMMENT:
|
||||
type = readComment(MID_COMMENT);
|
||||
if (type == END_COMMENT)
|
||||
state = WHITESPACE;
|
||||
else
|
||||
state = MID_COMMENT;
|
||||
return type;
|
||||
default:
|
||||
char c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
if (c < 128)
|
||||
type = kind[c];
|
||||
else
|
||||
type = unikind[Character.getType(c)];
|
||||
switch (type) {
|
||||
case WHITESPACE:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
while (start < end) {
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
int k;
|
||||
if (c < 128)
|
||||
k = kind[c];
|
||||
else
|
||||
k = unikind[Character.getType(c)];
|
||||
if (k != WHITESPACE)
|
||||
break;
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
}
|
||||
break;
|
||||
case UNRECOGNIZED:
|
||||
case BRACKET:
|
||||
case SEPARATOR:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
break;
|
||||
case OPERATOR:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
type = readOperator(c);
|
||||
break;
|
||||
case CHARACTER:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
type = readCharLiteral();
|
||||
break;
|
||||
case STRING:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
type = readStringLiteral();
|
||||
break;
|
||||
case IDENTIFIER:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
while (start < end) {
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
int k;
|
||||
if (c < 128)
|
||||
k = kind[c];
|
||||
else
|
||||
k = unikind[Character.getType(c)];
|
||||
if (k != IDENTIFIER && k != NUMBER)
|
||||
break;
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
}
|
||||
break;
|
||||
case NUMBER:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
type = readNumber(c);
|
||||
break;
|
||||
case PUNCTUATION:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
type = readDot();
|
||||
break;
|
||||
case COMMENT:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
type = readSlash();
|
||||
if (type == START_COMMENT)
|
||||
state = MID_COMMENT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (debug) {
|
||||
System.out.print(TokenTypes.typeNames[type]);
|
||||
System.out.println(" " + saveStart + "," + end + "("
|
||||
+ (start - saveStart) + ")");
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private int readOperator(char c) {
|
||||
if (start >= end)
|
||||
return OPERATOR;
|
||||
char c2;
|
||||
|
||||
switch (c) {
|
||||
case '~':
|
||||
case '?':
|
||||
case ':':
|
||||
break;
|
||||
case '+':
|
||||
case '-':
|
||||
case '&':
|
||||
case '|':
|
||||
c2 = buffer[start];
|
||||
if (c2 == '\\')
|
||||
c2 = next();
|
||||
if (c2 != c && c2 != '=')
|
||||
break;
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
break;
|
||||
case '=':
|
||||
case '*':
|
||||
case '!':
|
||||
case '^':
|
||||
case '%':
|
||||
case '/':
|
||||
c2 = buffer[start];
|
||||
if (c2 == '\\')
|
||||
c2 = next();
|
||||
if (c2 != '=')
|
||||
break;
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
break;
|
||||
case '<':
|
||||
case '>':
|
||||
c2 = buffer[start];
|
||||
if (c2 == '\\')
|
||||
c2 = next();
|
||||
if (c2 == '=') {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
} else if (c2 == c) {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
break;
|
||||
char c3 = buffer[start];
|
||||
if (c3 == '\\')
|
||||
c3 = next();
|
||||
if (c3 == '=') {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
} else if (c == '>' && c3 == '>') // >>>
|
||||
{
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
break;
|
||||
char c4 = buffer[start];
|
||||
if (c4 == '\\')
|
||||
c4 = next();
|
||||
if (c4 != '=')
|
||||
break;
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return OPERATOR;
|
||||
}
|
||||
|
||||
private int readCharLiteral() {
|
||||
if (start >= end)
|
||||
return bad(CHARACTER);
|
||||
char c2 = buffer[start];
|
||||
if (c2 == '\\')
|
||||
c2 = next();
|
||||
|
||||
switch (c2) {
|
||||
case '\\':
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
boolean ok = readEscapeSequence();
|
||||
if (!ok)
|
||||
return bad(CHARACTER);
|
||||
break;
|
||||
case '\'':
|
||||
case '\n':
|
||||
return bad(CHARACTER);
|
||||
default:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
break;
|
||||
}
|
||||
if (start >= end)
|
||||
return bad(CHARACTER);
|
||||
char c3 = buffer[start];
|
||||
if (c3 == '\\')
|
||||
c3 = next();
|
||||
if (c3 != '\'')
|
||||
return bad(CHARACTER);
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
return CHARACTER;
|
||||
}
|
||||
|
||||
private int readStringLiteral() {
|
||||
if (start >= end)
|
||||
return bad(STRING);
|
||||
char c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
|
||||
while (c != '"') {
|
||||
switch (c) {
|
||||
case '\\':
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
boolean ok = readEscapeSequence();
|
||||
if (!ok)
|
||||
return bad(STRING);
|
||||
break;
|
||||
case '\n':
|
||||
return bad(STRING);
|
||||
default:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
return bad(STRING);
|
||||
break;
|
||||
}
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
}
|
||||
if (c != '"')
|
||||
return bad(STRING);
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
return STRING;
|
||||
}
|
||||
|
||||
private int readSlash() {
|
||||
if (start >= end)
|
||||
return OPERATOR;
|
||||
char c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
if (c == '/') {
|
||||
while (c != '\n') {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
return COMMENT;
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
}
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
return COMMENT;
|
||||
} else if (c == '*') {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
return readComment(START_COMMENT);
|
||||
}
|
||||
return readOperator('/');
|
||||
}
|
||||
|
||||
// Read one line of a /*...*/ comment, given the expected type
|
||||
int readComment(int type) {
|
||||
if (start >= end)
|
||||
return type;
|
||||
char c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
|
||||
while (true) {
|
||||
while (c != '*' && c != '\n') {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
return type;
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
}
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (c == '\n')
|
||||
return type;
|
||||
if (start >= end)
|
||||
return type;
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
if (c == '/') {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (type == START_COMMENT) {
|
||||
return COMMENT;
|
||||
}
|
||||
return END_COMMENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read a number, without checking whether it is out of range
|
||||
// Doesn't deal with e.g. 0777.9 or 07779f
|
||||
private int readNumber(char c) {
|
||||
if (c == '0') {
|
||||
int saveStart = start, saveLength = charlength;
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
return NUMBER;
|
||||
char c2 = buffer[start];
|
||||
if (c2 == '\\')
|
||||
c2 = next();
|
||||
switch (c2) {
|
||||
case 'x':
|
||||
case 'X':
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
boolean ok = readDigits(16);
|
||||
if (!ok)
|
||||
return bad(NUMBER);
|
||||
readSuffix();
|
||||
return NUMBER;
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
readDigits(8);
|
||||
readSuffix();
|
||||
return NUMBER;
|
||||
case '.':
|
||||
case 'e':
|
||||
case 'E':
|
||||
start = saveStart;
|
||||
charlength = saveLength;
|
||||
break;
|
||||
case 'f':
|
||||
case 'F':
|
||||
case 'd':
|
||||
case 'D':
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
return NUMBER;
|
||||
case 'l':
|
||||
case 'L':
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
return NUMBER;
|
||||
}
|
||||
}
|
||||
boolean hasDigits = false;
|
||||
if ('0' <= c && c <= '9') {
|
||||
hasDigits = true;
|
||||
readDigits(10);
|
||||
if (start >= end)
|
||||
return NUMBER;
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
if (c == 'l' || c == 'L') {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
return NUMBER;
|
||||
}
|
||||
}
|
||||
if (c == '.') {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
return NUMBER;
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
if ('0' <= c && c <= '9') {
|
||||
hasDigits = true;
|
||||
readDigits(10);
|
||||
if (start >= end)
|
||||
return NUMBER;
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
}
|
||||
}
|
||||
if (!hasDigits)
|
||||
return bad(NUMBER);
|
||||
switch (c) {
|
||||
case 'e':
|
||||
case 'E':
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
return bad(NUMBER);
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
if (c == '+' || c == '-') {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
return bad(NUMBER);
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
}
|
||||
readDigits(10);
|
||||
break;
|
||||
case 'f':
|
||||
case 'F':
|
||||
case 'd':
|
||||
case 'D':
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
return NUMBER;
|
||||
}
|
||||
return NUMBER;
|
||||
}
|
||||
|
||||
boolean readDigits(int radix) {
|
||||
if (start >= end)
|
||||
return false;
|
||||
char c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
if (Character.digit(c, radix) == -1)
|
||||
return false;
|
||||
while (Character.digit(c, radix) != -1) {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
return true;
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void readSuffix() {
|
||||
if (start >= end)
|
||||
return;
|
||||
char c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
switch (c) {
|
||||
case 'f':
|
||||
case 'F':
|
||||
case 'd':
|
||||
case 'D':
|
||||
case 'l':
|
||||
case 'L':
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private int readDot() {
|
||||
if (start >= end)
|
||||
return SEPARATOR;
|
||||
char c2 = buffer[start];
|
||||
if (c2 == '\\')
|
||||
c2 = next();
|
||||
if (Character.isDigit(c2)) {
|
||||
return readNumber('.');
|
||||
}
|
||||
if (start + 1 >= end) // || version < 15)
|
||||
return SEPARATOR;
|
||||
if (c2 != '.' || buffer[start + 1] != '.')
|
||||
return SEPARATOR;
|
||||
start = start + 2;
|
||||
return SEPARATOR;
|
||||
}
|
||||
|
||||
private boolean readEscapeSequence() {
|
||||
if (start >= end)
|
||||
return false;
|
||||
char c2 = buffer[start];
|
||||
if (c2 == '\\')
|
||||
c2 = next();
|
||||
|
||||
switch (c2) {
|
||||
case 'b':
|
||||
case 't':
|
||||
case 'n':
|
||||
case 'f':
|
||||
case 'r':
|
||||
case '\"':
|
||||
case '\'':
|
||||
case '\\':
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
return true;
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
return readOctal(3);
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
return readOctal(2);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean readOctal(int maxlength) {
|
||||
if (start >= end)
|
||||
return false;
|
||||
char c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
|
||||
int i, val = 0;
|
||||
for (i = 0; i < maxlength; i++) {
|
||||
if (Character.digit(c, 8) != -1) {
|
||||
val = 8 * val + Character.digit(c, 8);
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
break;
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
} else
|
||||
break;
|
||||
}
|
||||
if ((i == 0) || (val > 0xFF))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// A malformed or incomplete token has a negative type
|
||||
private int bad(int type) {
|
||||
return -type;
|
||||
}
|
||||
|
||||
// Look ahead at the next character or unicode escape.
|
||||
// For efficiency, replace c = next(); with
|
||||
// c = buffer[start]; if (c == '\\') c = next();
|
||||
// To accept the character after looking at it, use:
|
||||
// start = start + charlength; charlength = 1;
|
||||
|
||||
// Record the number of source code characters used up. To deal with an odd
|
||||
// or even number of backslashes preceding a unicode escape, whenever a
|
||||
// second backslash is coming up, mark its position as a pair.
|
||||
|
||||
private int charlength = 1;
|
||||
|
||||
private int pair = 0;
|
||||
|
||||
private char next() {
|
||||
if (start >= end)
|
||||
return 26; // EOF
|
||||
char c = buffer[start];
|
||||
if (c != '\\')
|
||||
return c;
|
||||
if (start == pair) {
|
||||
pair = 0;
|
||||
return '\\';
|
||||
}
|
||||
if (start + 1 >= end)
|
||||
return '\\';
|
||||
|
||||
c = buffer[start + 1];
|
||||
if (c == '\\')
|
||||
pair = start + 1;
|
||||
if (c != 'u')
|
||||
return '\\';
|
||||
|
||||
int pos = start + 2;
|
||||
while (pos < end && buffer[pos] == 'u')
|
||||
pos++;
|
||||
if (pos + 4 > end) {
|
||||
charlength = end - start;
|
||||
return '\0';
|
||||
}
|
||||
|
||||
c = 0;
|
||||
for (int j = 0; j < 4; j++) {
|
||||
int d = Character.digit(buffer[pos + j], 16);
|
||||
if (d < 0) {
|
||||
charlength = pos + j - start;
|
||||
return '\0';
|
||||
}
|
||||
c = (char) (c * 16 + d);
|
||||
}
|
||||
charlength = pos + 4 - start;
|
||||
return c;
|
||||
}
|
||||
|
||||
// Override initSymbolTable
|
||||
|
||||
protected void initSymbolTable() {
|
||||
lookup(KEYWORD, "abstract");
|
||||
lookup(KEYWORD, "assert");
|
||||
lookup(KEYWORD, "boolean");
|
||||
lookup(KEYWORD, "break");
|
||||
lookup(KEYWORD, "byte");
|
||||
lookup(KEYWORD, "case");
|
||||
lookup(KEYWORD, "catch");
|
||||
lookup(KEYWORD, "char");
|
||||
lookup(KEYWORD, "class");
|
||||
lookup(KEYWORD, "const");
|
||||
lookup(KEYWORD, "continue");
|
||||
lookup(KEYWORD, "default");
|
||||
lookup(KEYWORD, "do");
|
||||
lookup(KEYWORD, "double");
|
||||
lookup(KEYWORD, "else");
|
||||
lookup(KEYWORD, "enum");
|
||||
lookup(KEYWORD, "extends");
|
||||
lookup(KEYWORD, "float");
|
||||
lookup(KEYWORD, "for");
|
||||
lookup(KEYWORD, "goto");
|
||||
lookup(KEYWORD, "if");
|
||||
lookup(KEYWORD, "int");
|
||||
lookup(KEYWORD, "long");
|
||||
lookup(KEYWORD, "new");
|
||||
lookup(KEYWORD, "private");
|
||||
lookup(KEYWORD, "protected");
|
||||
lookup(KEYWORD, "public");
|
||||
lookup(KEYWORD, "return");
|
||||
lookup(KEYWORD, "short");
|
||||
lookup(KEYWORD, "static");
|
||||
lookup(KEYWORD, "super");
|
||||
lookup(KEYWORD, "switch");
|
||||
lookup(KEYWORD, "synchronized");
|
||||
lookup(KEYWORD, "this");
|
||||
lookup(KEYWORD, "throw");
|
||||
lookup(KEYWORD, "throws");
|
||||
lookup(KEYWORD, "transient");
|
||||
lookup(KEYWORD, "try");
|
||||
lookup(KEYWORD, "void");
|
||||
lookup(KEYWORD, "volatile");
|
||||
lookup(KEYWORD, "while");
|
||||
|
||||
lookup(LITERAL, "TRUE");
|
||||
lookup(LITERAL, "FALSE");
|
||||
lookup(LITERAL, "NULL");
|
||||
lookup(LITERAL, "int8_t");
|
||||
lookup(LITERAL, "int16_t");
|
||||
lookup(LITERAL, "int32_t");
|
||||
lookup(LITERAL, "uint8_t");
|
||||
lookup(LITERAL, "uint16_t");
|
||||
lookup(LITERAL, "uint32_t");
|
||||
lookup(LITERAL, "u8_t");
|
||||
lookup(LITERAL, "u16_t");
|
||||
lookup(LITERAL, "u32_t");
|
||||
}
|
||||
|
||||
// *** Override lookup, but what about unicode escape translation?
|
||||
|
||||
private Symbol temp = new Symbol(0, null);
|
||||
|
||||
protected Symbol lookup(int type, String name) {
|
||||
if (type != IDENTIFIER)
|
||||
return super.lookup(type, name);
|
||||
temp.type = KEYWORD;
|
||||
temp.name = name;
|
||||
Symbol sym = symbolTable.get(temp);
|
||||
if (sym != null)
|
||||
return sym;
|
||||
temp.type = LITERAL;
|
||||
sym = symbolTable.get(temp);
|
||||
if (sym != null)
|
||||
return sym;
|
||||
return super.lookup(type, name);
|
||||
}
|
||||
|
||||
// Classify the ascii characters using an array of kinds, and classify all
|
||||
// other unicode characters using an array indexed by unicode category.
|
||||
// See the source file java/lang/Character.java for the categories.
|
||||
// To find the classification of a character, use:
|
||||
// if (c < 128) k = kind[c]; else k = unikind[Character.getType(c)];
|
||||
|
||||
private static final byte[] kind = new byte[128];
|
||||
|
||||
private static final byte[] unikind = new byte[31];
|
||||
|
||||
// Initialise the two classification arrays using static initializer code.
|
||||
// Token types from the TokenTypes class are used to classify characters.
|
||||
|
||||
private void initKind() {
|
||||
for (char c = 0; c < 128; c++)
|
||||
kind[c] = -1;
|
||||
for (char c = 0; c < 128; c++)
|
||||
switch (c) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
case 11:
|
||||
case 13:
|
||||
case 14:
|
||||
case 15:
|
||||
case 16:
|
||||
case 17:
|
||||
case 18:
|
||||
case 19:
|
||||
case 20:
|
||||
case 21:
|
||||
case 22:
|
||||
case 23:
|
||||
case 24:
|
||||
case 25:
|
||||
case 27:
|
||||
case 28:
|
||||
case 29:
|
||||
case 30:
|
||||
case 31:
|
||||
case 127:
|
||||
case '#':
|
||||
case '@':
|
||||
case '`':
|
||||
case '\\':
|
||||
kind[c] = UNRECOGNIZED;
|
||||
break;
|
||||
case '\t':
|
||||
case '\n':
|
||||
case ' ':
|
||||
case '\f':
|
||||
case 26:
|
||||
kind[c] = WHITESPACE;
|
||||
break;
|
||||
case '!':
|
||||
case '%':
|
||||
case '&':
|
||||
case '*':
|
||||
case '+':
|
||||
case '-':
|
||||
case ':':
|
||||
case '<':
|
||||
case '=':
|
||||
case '>':
|
||||
case '?':
|
||||
case '^':
|
||||
case '|':
|
||||
case '~':
|
||||
kind[c] = OPERATOR;
|
||||
break;
|
||||
case '"':
|
||||
kind[c] = STRING;
|
||||
break;
|
||||
case '\'':
|
||||
kind[c] = CHARACTER;
|
||||
break;
|
||||
case '.':
|
||||
kind[c] = PUNCTUATION;
|
||||
break;
|
||||
case '/':
|
||||
kind[c] = COMMENT;
|
||||
break;
|
||||
case '$':
|
||||
case 'A':
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'D':
|
||||
case 'E':
|
||||
case 'F':
|
||||
case 'G':
|
||||
case 'H':
|
||||
case 'I':
|
||||
case 'J':
|
||||
case 'K':
|
||||
case 'L':
|
||||
case 'M':
|
||||
case 'N':
|
||||
case 'O':
|
||||
case 'P':
|
||||
case 'Q':
|
||||
case 'R':
|
||||
case 'S':
|
||||
case 'T':
|
||||
case 'U':
|
||||
case 'V':
|
||||
case 'W':
|
||||
case 'X':
|
||||
case 'Y':
|
||||
case 'Z':
|
||||
case '_':
|
||||
case 'a':
|
||||
case 'b':
|
||||
case 'c':
|
||||
case 'd':
|
||||
case 'e':
|
||||
case 'f':
|
||||
case 'g':
|
||||
case 'h':
|
||||
case 'i':
|
||||
case 'j':
|
||||
case 'k':
|
||||
case 'l':
|
||||
case 'm':
|
||||
case 'n':
|
||||
case 'o':
|
||||
case 'p':
|
||||
case 'q':
|
||||
case 'r':
|
||||
case 's':
|
||||
case 't':
|
||||
case 'u':
|
||||
case 'v':
|
||||
case 'w':
|
||||
case 'x':
|
||||
case 'y':
|
||||
case 'z':
|
||||
kind[c] = IDENTIFIER;
|
||||
break;
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
kind[c] = NUMBER;
|
||||
break;
|
||||
case '(':
|
||||
case ')':
|
||||
case '[':
|
||||
case ']':
|
||||
case '{':
|
||||
case '}':
|
||||
kind[c] = BRACKET;
|
||||
break;
|
||||
case ',':
|
||||
case ';':
|
||||
kind[c] = SEPARATOR;
|
||||
break;
|
||||
}
|
||||
for (char c = 0; c < 128; c++)
|
||||
if (kind[c] == -1)
|
||||
System.out.println("Char " + ((int) c) + " hasn't been classified");
|
||||
}
|
||||
|
||||
private void initUniKind() {
|
||||
for (byte b = 0; b < 31; b++)
|
||||
unikind[b] = -1;
|
||||
for (byte b = 0; b < 31; b++)
|
||||
switch (b) {
|
||||
case Character.UNASSIGNED:
|
||||
case Character.ENCLOSING_MARK:
|
||||
case Character.OTHER_NUMBER:
|
||||
case Character.SPACE_SEPARATOR:
|
||||
case Character.LINE_SEPARATOR:
|
||||
case Character.PARAGRAPH_SEPARATOR:
|
||||
case Character.CONTROL:
|
||||
case 17: // category 17 is unused
|
||||
case Character.PRIVATE_USE:
|
||||
case Character.SURROGATE:
|
||||
case Character.DASH_PUNCTUATION:
|
||||
case Character.START_PUNCTUATION:
|
||||
case Character.END_PUNCTUATION:
|
||||
case Character.OTHER_PUNCTUATION:
|
||||
case Character.MATH_SYMBOL:
|
||||
case Character.MODIFIER_SYMBOL:
|
||||
case Character.OTHER_SYMBOL:
|
||||
case Character.INITIAL_QUOTE_PUNCTUATION:
|
||||
case Character.FINAL_QUOTE_PUNCTUATION:
|
||||
unikind[b] = UNRECOGNIZED;
|
||||
break;
|
||||
case Character.UPPERCASE_LETTER:
|
||||
case Character.LOWERCASE_LETTER:
|
||||
case Character.TITLECASE_LETTER:
|
||||
case Character.MODIFIER_LETTER:
|
||||
case Character.OTHER_LETTER:
|
||||
case Character.LETTER_NUMBER:
|
||||
case Character.CONNECTOR_PUNCTUATION: // maybe NUMBER
|
||||
case Character.CURRENCY_SYMBOL:
|
||||
// Characters where Other_ID_Start is true
|
||||
unikind[b] = IDENTIFIER;
|
||||
break;
|
||||
case Character.NON_SPACING_MARK:
|
||||
case Character.COMBINING_SPACING_MARK:
|
||||
case Character.DECIMAL_DIGIT_NUMBER:
|
||||
case Character.FORMAT:
|
||||
unikind[b] = NUMBER;
|
||||
break;
|
||||
}
|
||||
for (byte b = 0; b < 31; b++)
|
||||
if (unikind[b] == -1)
|
||||
System.out.println("Unicode cat " + b + " hasn't been classified");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright (c) 2007, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $Id: $
|
||||
*
|
||||
* -----------------------------------------------------------------
|
||||
*
|
||||
* HighlightSourceViewer
|
||||
*
|
||||
* Authors : Adam Dunkels, Joakim Eriksson, Niclas Finne
|
||||
* Created : 6 dec 2007
|
||||
* Updated : $Date: 6 dec 2007 $
|
||||
* $Revision: 1.0 $
|
||||
*/
|
||||
|
||||
package se.sics.mspsim.extutil.highlight;
|
||||
|
||||
import java.awt.Container;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import se.sics.mspsim.util.SourceViewer;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class HighlightSourceViewer implements SourceViewer {
|
||||
|
||||
private JFrame window;
|
||||
private SyntaxHighlighter highlighter;
|
||||
|
||||
public HighlightSourceViewer() {
|
||||
//
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
if (window == null) {
|
||||
window = new JFrame("Source Viewer");
|
||||
window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||
|
||||
Scanner scanner = new CScanner();
|
||||
highlighter = new SyntaxHighlighter(24, 80, scanner);
|
||||
highlighter.setBorder(new LineNumberedBorder(LineNumberedBorder.LEFT_SIDE, LineNumberedBorder.RIGHT_JUSTIFY));
|
||||
JScrollPane scroller = new JScrollPane(highlighter);
|
||||
scroller.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
Container pane = window.getContentPane();
|
||||
pane.add(scroller);
|
||||
window.pack();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return window != null && window.isVisible();
|
||||
}
|
||||
|
||||
public void setVisible(boolean isVisible) {
|
||||
setup();
|
||||
window.setVisible(isVisible);
|
||||
}
|
||||
|
||||
public void viewFile(final String file) {
|
||||
setup();
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
FileReader reader = new FileReader(file);
|
||||
try {
|
||||
highlighter.read(reader, null);
|
||||
// Workaround for bug 4782232 in Java 1.4
|
||||
highlighter.setCaretPosition(1);
|
||||
highlighter.setCaretPosition(0);
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
} catch (IOException err) {
|
||||
err.printStackTrace();
|
||||
JOptionPane.showMessageDialog(window, "Failed to read the file '" + file + '\'', "Could not read file", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void viewLine(final int line) {
|
||||
if (highlighter != null) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
if (line >= 0 && line < highlighter.getLineCount()) {
|
||||
highlighter.setCaretPosition(highlighter.getLineStartOffset(line));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
HighlightSourceViewer sv = new HighlightSourceViewer();
|
||||
sv.setVisible(true);
|
||||
sv.viewFile(args[0]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,962 @@
|
|||
package se.sics.mspsim.extutil.highlight;
|
||||
// Public domain, no restrictions, Ian Holyer, University of Bristol.
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Provide a hand-written scanner for the Java language.
|
||||
*/
|
||||
|
||||
public class JavaScanner extends Scanner {
|
||||
|
||||
// The version of Java supported.
|
||||
private int version = 15;
|
||||
|
||||
private boolean debug = false;
|
||||
|
||||
/** Create a Java scanner, for Java version 1.5 by default. */
|
||||
public JavaScanner() {
|
||||
super();
|
||||
initKind();
|
||||
initUniKind();
|
||||
}
|
||||
|
||||
/** Create a Java scanner, for a given version between "1.1" and "1.5". */
|
||||
public JavaScanner(String version) {
|
||||
super();
|
||||
initKind();
|
||||
initUniKind();
|
||||
if (version.equals("1.1"))
|
||||
this.version = 11;
|
||||
else if (version.equals("1.2"))
|
||||
this.version = 12;
|
||||
else if (version.equals("1.3"))
|
||||
this.version = 13;
|
||||
else if (version.equals("1.4"))
|
||||
this.version = 14;
|
||||
else if (version.equals("1.5"))
|
||||
this.version = 15;
|
||||
else
|
||||
throw new Error("Unknown version of Java: " + version);
|
||||
}
|
||||
|
||||
/** Override the read method from the Scanner class. */
|
||||
protected int read() {
|
||||
int type, saveStart = 0;
|
||||
if (debug)
|
||||
saveStart = start;
|
||||
|
||||
if (start >= end)
|
||||
return WHITESPACE;
|
||||
|
||||
switch (state) {
|
||||
case MID_COMMENT:
|
||||
case END_COMMENT:
|
||||
type = readComment(MID_COMMENT);
|
||||
if (type == END_COMMENT)
|
||||
state = WHITESPACE;
|
||||
else
|
||||
state = MID_COMMENT;
|
||||
return type;
|
||||
default:
|
||||
char c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
if (c < 128)
|
||||
type = kind[c];
|
||||
else
|
||||
type = unikind[Character.getType(c)];
|
||||
switch (type) {
|
||||
case WHITESPACE:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
while (start < end) {
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
int k;
|
||||
if (c < 128)
|
||||
k = kind[c];
|
||||
else
|
||||
k = unikind[Character.getType(c)];
|
||||
if (k != WHITESPACE)
|
||||
break;
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
}
|
||||
break;
|
||||
case UNRECOGNIZED:
|
||||
case BRACKET:
|
||||
case SEPARATOR:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
break;
|
||||
case OPERATOR:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
type = readOperator(c);
|
||||
break;
|
||||
case CHARACTER:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
type = readCharLiteral();
|
||||
break;
|
||||
case STRING:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
type = readStringLiteral();
|
||||
break;
|
||||
case IDENTIFIER:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
while (start < end) {
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
int k;
|
||||
if (c < 128)
|
||||
k = kind[c];
|
||||
else
|
||||
k = unikind[Character.getType(c)];
|
||||
if (k != IDENTIFIER && k != NUMBER)
|
||||
break;
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
}
|
||||
break;
|
||||
case NUMBER:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
type = readNumber(c);
|
||||
break;
|
||||
case PUNCTUATION:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
type = readDot();
|
||||
break;
|
||||
case COMMENT:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
type = readSlash();
|
||||
if (type == START_COMMENT)
|
||||
state = MID_COMMENT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (debug) {
|
||||
System.out.print(TokenTypes.typeNames[type]);
|
||||
System.out.println(" " + saveStart + "," + end + "("
|
||||
+ (start - saveStart) + ")");
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private int readOperator(char c) {
|
||||
if (start >= end)
|
||||
return OPERATOR;
|
||||
char c2;
|
||||
|
||||
switch (c) {
|
||||
case '~':
|
||||
case '?':
|
||||
case ':':
|
||||
break;
|
||||
case '+':
|
||||
case '-':
|
||||
case '&':
|
||||
case '|':
|
||||
c2 = buffer[start];
|
||||
if (c2 == '\\')
|
||||
c2 = next();
|
||||
if (c2 != c && c2 != '=')
|
||||
break;
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
break;
|
||||
case '=':
|
||||
case '*':
|
||||
case '!':
|
||||
case '^':
|
||||
case '%':
|
||||
case '/':
|
||||
c2 = buffer[start];
|
||||
if (c2 == '\\')
|
||||
c2 = next();
|
||||
if (c2 != '=')
|
||||
break;
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
break;
|
||||
case '<':
|
||||
case '>':
|
||||
c2 = buffer[start];
|
||||
if (c2 == '\\')
|
||||
c2 = next();
|
||||
if (c2 == '=') {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
} else if (c2 == c) {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
break;
|
||||
char c3 = buffer[start];
|
||||
if (c3 == '\\')
|
||||
c3 = next();
|
||||
if (c3 == '=') {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
} else if (c == '>' && c3 == '>') // >>>
|
||||
{
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
break;
|
||||
char c4 = buffer[start];
|
||||
if (c4 == '\\')
|
||||
c4 = next();
|
||||
if (c4 != '=')
|
||||
break;
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return OPERATOR;
|
||||
}
|
||||
|
||||
private int readCharLiteral() {
|
||||
if (start >= end)
|
||||
return bad(CHARACTER);
|
||||
char c2 = buffer[start];
|
||||
if (c2 == '\\')
|
||||
c2 = next();
|
||||
|
||||
switch (c2) {
|
||||
case '\\':
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
boolean ok = readEscapeSequence();
|
||||
if (!ok)
|
||||
return bad(CHARACTER);
|
||||
break;
|
||||
case '\'':
|
||||
case '\n':
|
||||
return bad(CHARACTER);
|
||||
default:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
break;
|
||||
}
|
||||
if (start >= end)
|
||||
return bad(CHARACTER);
|
||||
char c3 = buffer[start];
|
||||
if (c3 == '\\')
|
||||
c3 = next();
|
||||
if (c3 != '\'')
|
||||
return bad(CHARACTER);
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
return CHARACTER;
|
||||
}
|
||||
|
||||
private int readStringLiteral() {
|
||||
if (start >= end)
|
||||
return bad(STRING);
|
||||
char c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
|
||||
while (c != '"') {
|
||||
switch (c) {
|
||||
case '\\':
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
boolean ok = readEscapeSequence();
|
||||
if (!ok)
|
||||
return bad(STRING);
|
||||
break;
|
||||
case '\n':
|
||||
return bad(STRING);
|
||||
default:
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
return bad(STRING);
|
||||
break;
|
||||
}
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
}
|
||||
if (c != '"')
|
||||
return bad(STRING);
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
return STRING;
|
||||
}
|
||||
|
||||
private int readSlash() {
|
||||
if (start >= end)
|
||||
return OPERATOR;
|
||||
char c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
if (c == '/') {
|
||||
while (c != '\n') {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
return COMMENT;
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
}
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
return COMMENT;
|
||||
} else if (c == '*') {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
return readComment(START_COMMENT);
|
||||
}
|
||||
return readOperator('/');
|
||||
}
|
||||
|
||||
// Read one line of a /*...*/ comment, given the expected type
|
||||
int readComment(int type) {
|
||||
if (start >= end)
|
||||
return type;
|
||||
char c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
|
||||
while (true) {
|
||||
while (c != '*' && c != '\n') {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
return type;
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
}
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (c == '\n')
|
||||
return type;
|
||||
if (start >= end)
|
||||
return type;
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
if (c == '/') {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (type == START_COMMENT) {
|
||||
return COMMENT;
|
||||
}
|
||||
return END_COMMENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read a number, without checking whether it is out of range
|
||||
// Doesn't deal with e.g. 0777.9 or 07779f
|
||||
private int readNumber(char c) {
|
||||
if (c == '0') {
|
||||
int saveStart = start, saveLength = charlength;
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
return NUMBER;
|
||||
char c2 = buffer[start];
|
||||
if (c2 == '\\')
|
||||
c2 = next();
|
||||
switch (c2) {
|
||||
case 'x':
|
||||
case 'X':
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
boolean ok = readDigits(16);
|
||||
if (!ok)
|
||||
return bad(NUMBER);
|
||||
readSuffix();
|
||||
return NUMBER;
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
readDigits(8);
|
||||
readSuffix();
|
||||
return NUMBER;
|
||||
case '.':
|
||||
case 'e':
|
||||
case 'E':
|
||||
start = saveStart;
|
||||
charlength = saveLength;
|
||||
break;
|
||||
case 'f':
|
||||
case 'F':
|
||||
case 'd':
|
||||
case 'D':
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
return NUMBER;
|
||||
case 'l':
|
||||
case 'L':
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
return NUMBER;
|
||||
}
|
||||
}
|
||||
boolean hasDigits = false;
|
||||
if ('0' <= c && c <= '9') {
|
||||
hasDigits = true;
|
||||
readDigits(10);
|
||||
if (start >= end)
|
||||
return NUMBER;
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
if (c == 'l' || c == 'L') {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
return NUMBER;
|
||||
}
|
||||
}
|
||||
if (c == '.') {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
return NUMBER;
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
if ('0' <= c && c <= '9') {
|
||||
hasDigits = true;
|
||||
readDigits(10);
|
||||
if (start >= end)
|
||||
return NUMBER;
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
}
|
||||
}
|
||||
if (!hasDigits)
|
||||
return bad(NUMBER);
|
||||
switch (c) {
|
||||
case 'e':
|
||||
case 'E':
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
return bad(NUMBER);
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
if (c == '+' || c == '-') {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
return bad(NUMBER);
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
}
|
||||
readDigits(10);
|
||||
break;
|
||||
case 'f':
|
||||
case 'F':
|
||||
case 'd':
|
||||
case 'D':
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
return NUMBER;
|
||||
}
|
||||
return NUMBER;
|
||||
}
|
||||
|
||||
boolean readDigits(int radix) {
|
||||
if (start >= end)
|
||||
return false;
|
||||
char c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
if (Character.digit(c, radix) == -1)
|
||||
return false;
|
||||
while (Character.digit(c, radix) != -1) {
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
return true;
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void readSuffix() {
|
||||
if (start >= end)
|
||||
return;
|
||||
char c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
switch (c) {
|
||||
case 'f':
|
||||
case 'F':
|
||||
case 'd':
|
||||
case 'D':
|
||||
case 'l':
|
||||
case 'L':
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private int readDot() {
|
||||
if (start >= end)
|
||||
return SEPARATOR;
|
||||
char c2 = buffer[start];
|
||||
if (c2 == '\\')
|
||||
c2 = next();
|
||||
if (Character.isDigit(c2)) {
|
||||
return readNumber('.');
|
||||
}
|
||||
if (start + 1 >= end || version < 15)
|
||||
return SEPARATOR;
|
||||
if (c2 != '.' || buffer[start + 1] != '.')
|
||||
return SEPARATOR;
|
||||
start = start + 2;
|
||||
return SEPARATOR;
|
||||
}
|
||||
|
||||
private boolean readEscapeSequence() {
|
||||
if (start >= end)
|
||||
return false;
|
||||
char c2 = buffer[start];
|
||||
if (c2 == '\\')
|
||||
c2 = next();
|
||||
|
||||
switch (c2) {
|
||||
case 'b':
|
||||
case 't':
|
||||
case 'n':
|
||||
case 'f':
|
||||
case 'r':
|
||||
case '\"':
|
||||
case '\'':
|
||||
case '\\':
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
return true;
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
return readOctal(3);
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
return readOctal(2);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean readOctal(int maxlength) {
|
||||
if (start >= end)
|
||||
return false;
|
||||
char c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
|
||||
int i, val = 0;
|
||||
for (i = 0; i < maxlength; i++) {
|
||||
if (Character.digit(c, 8) != -1) {
|
||||
val = 8 * val + Character.digit(c, 8);
|
||||
start = start + charlength;
|
||||
charlength = 1;
|
||||
if (start >= end)
|
||||
break;
|
||||
c = buffer[start];
|
||||
if (c == '\\')
|
||||
c = next();
|
||||
} else
|
||||
break;
|
||||
}
|
||||
if ((i == 0) || (val > 0xFF))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// A malformed or incomplete token has a negative type
|
||||
private int bad(int type) {
|
||||
return -type;
|
||||
}
|
||||
|
||||
// Look ahead at the next character or unicode escape.
|
||||
// For efficiency, replace c = next(); with
|
||||
// c = buffer[start]; if (c == '\\') c = next();
|
||||
// To accept the character after looking at it, use:
|
||||
// start = start + charlength; charlength = 1;
|
||||
|
||||
// Record the number of source code characters used up. To deal with an odd
|
||||
// or even number of backslashes preceding a unicode escape, whenever a
|
||||
// second backslash is coming up, mark its position as a pair.
|
||||
|
||||
private int charlength = 1;
|
||||
|
||||
private int pair = 0;
|
||||
|
||||
private char next() {
|
||||
if (start >= end)
|
||||
return 26; // EOF
|
||||
char c = buffer[start];
|
||||
if (c != '\\')
|
||||
return c;
|
||||
if (start == pair) {
|
||||
pair = 0;
|
||||
return '\\';
|
||||
}
|
||||
if (start + 1 >= end)
|
||||
return '\\';
|
||||
|
||||
c = buffer[start + 1];
|
||||
if (c == '\\')
|
||||
pair = start + 1;
|
||||
if (c != 'u')
|
||||
return '\\';
|
||||
|
||||
int pos = start + 2;
|
||||
while (pos < end && buffer[pos] == 'u')
|
||||
pos++;
|
||||
if (pos + 4 > end) {
|
||||
charlength = end - start;
|
||||
return '\0';
|
||||
}
|
||||
|
||||
c = 0;
|
||||
for (int j = 0; j < 4; j++) {
|
||||
int d = Character.digit(buffer[pos + j], 16);
|
||||
if (d < 0) {
|
||||
charlength = pos + j - start;
|
||||
return '\0';
|
||||
}
|
||||
c = (char) (c * 16 + d);
|
||||
}
|
||||
charlength = pos + 4 - start;
|
||||
return c;
|
||||
}
|
||||
|
||||
// Override initSymbolTable
|
||||
|
||||
protected void initSymbolTable() {
|
||||
lookup(KEYWORD, "abstract");
|
||||
if (version >= 14)
|
||||
lookup(KEYWORD, "assert");
|
||||
lookup(KEYWORD, "boolean");
|
||||
lookup(KEYWORD, "break");
|
||||
lookup(KEYWORD, "byte");
|
||||
lookup(KEYWORD, "case");
|
||||
lookup(KEYWORD, "catch");
|
||||
lookup(KEYWORD, "char");
|
||||
lookup(KEYWORD, "class");
|
||||
lookup(KEYWORD, "const");
|
||||
lookup(KEYWORD, "continue");
|
||||
lookup(KEYWORD, "default");
|
||||
lookup(KEYWORD, "do");
|
||||
lookup(KEYWORD, "double");
|
||||
lookup(KEYWORD, "else");
|
||||
if (version >= 15)
|
||||
lookup(KEYWORD, "enum");
|
||||
lookup(KEYWORD, "extends");
|
||||
lookup(KEYWORD, "final");
|
||||
lookup(KEYWORD, "finally");
|
||||
lookup(KEYWORD, "float");
|
||||
lookup(KEYWORD, "for");
|
||||
lookup(KEYWORD, "goto");
|
||||
lookup(KEYWORD, "if");
|
||||
lookup(KEYWORD, "implements");
|
||||
lookup(KEYWORD, "import");
|
||||
lookup(KEYWORD, "instanceof");
|
||||
lookup(KEYWORD, "int");
|
||||
lookup(KEYWORD, "interface");
|
||||
lookup(KEYWORD, "long");
|
||||
lookup(KEYWORD, "native");
|
||||
lookup(KEYWORD, "new");
|
||||
lookup(KEYWORD, "package");
|
||||
lookup(KEYWORD, "private");
|
||||
lookup(KEYWORD, "protected");
|
||||
lookup(KEYWORD, "public");
|
||||
lookup(KEYWORD, "return");
|
||||
lookup(KEYWORD, "short");
|
||||
lookup(KEYWORD, "static");
|
||||
if (version >= 12)
|
||||
lookup(KEYWORD, "strictfp");
|
||||
lookup(KEYWORD, "super");
|
||||
lookup(KEYWORD, "switch");
|
||||
lookup(KEYWORD, "synchronized");
|
||||
lookup(KEYWORD, "this");
|
||||
lookup(KEYWORD, "throw");
|
||||
lookup(KEYWORD, "throws");
|
||||
lookup(KEYWORD, "transient");
|
||||
lookup(KEYWORD, "try");
|
||||
lookup(KEYWORD, "void");
|
||||
lookup(KEYWORD, "volatile");
|
||||
lookup(KEYWORD, "while");
|
||||
|
||||
lookup(LITERAL, "true");
|
||||
lookup(LITERAL, "false");
|
||||
lookup(LITERAL, "null");
|
||||
}
|
||||
|
||||
// *** Override lookup, but what about unicode escape translation?
|
||||
|
||||
private Symbol temp = new Symbol(0, null);
|
||||
|
||||
protected Symbol lookup(int type, String name) {
|
||||
if (type != IDENTIFIER)
|
||||
return super.lookup(type, name);
|
||||
temp.type = KEYWORD;
|
||||
temp.name = name;
|
||||
Symbol sym = symbolTable.get(temp);
|
||||
if (sym != null)
|
||||
return sym;
|
||||
temp.type = LITERAL;
|
||||
sym = symbolTable.get(temp);
|
||||
if (sym != null)
|
||||
return sym;
|
||||
return super.lookup(type, name);
|
||||
}
|
||||
|
||||
// Classify the ascii characters using an array of kinds, and classify all
|
||||
// other unicode characters using an array indexed by unicode category.
|
||||
// See the source file java/lang/Character.java for the categories.
|
||||
// To find the classification of a character, use:
|
||||
// if (c < 128) k = kind[c]; else k = unikind[Character.getType(c)];
|
||||
|
||||
private static final byte[] kind = new byte[128];
|
||||
|
||||
private static final byte[] unikind = new byte[31];
|
||||
|
||||
// Initialise the two classification arrays using static initializer code.
|
||||
// Token types from the TokenTypes class are used to classify characters.
|
||||
|
||||
private void initKind() {
|
||||
for (char c = 0; c < 128; c++)
|
||||
kind[c] = -1;
|
||||
for (char c = 0; c < 128; c++)
|
||||
switch (c) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
case 11:
|
||||
case 13:
|
||||
case 14:
|
||||
case 15:
|
||||
case 16:
|
||||
case 17:
|
||||
case 18:
|
||||
case 19:
|
||||
case 20:
|
||||
case 21:
|
||||
case 22:
|
||||
case 23:
|
||||
case 24:
|
||||
case 25:
|
||||
case 27:
|
||||
case 28:
|
||||
case 29:
|
||||
case 30:
|
||||
case 31:
|
||||
case 127:
|
||||
case '#':
|
||||
case '@':
|
||||
case '`':
|
||||
case '\\':
|
||||
kind[c] = UNRECOGNIZED;
|
||||
break;
|
||||
case '\t':
|
||||
case '\n':
|
||||
case ' ':
|
||||
case '\f':
|
||||
case 26:
|
||||
kind[c] = WHITESPACE;
|
||||
break;
|
||||
case '!':
|
||||
case '%':
|
||||
case '&':
|
||||
case '*':
|
||||
case '+':
|
||||
case '-':
|
||||
case ':':
|
||||
case '<':
|
||||
case '=':
|
||||
case '>':
|
||||
case '?':
|
||||
case '^':
|
||||
case '|':
|
||||
case '~':
|
||||
kind[c] = OPERATOR;
|
||||
break;
|
||||
case '"':
|
||||
kind[c] = STRING;
|
||||
break;
|
||||
case '\'':
|
||||
kind[c] = CHARACTER;
|
||||
break;
|
||||
case '.':
|
||||
kind[c] = PUNCTUATION;
|
||||
break;
|
||||
case '/':
|
||||
kind[c] = COMMENT;
|
||||
break;
|
||||
case '$':
|
||||
case 'A':
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'D':
|
||||
case 'E':
|
||||
case 'F':
|
||||
case 'G':
|
||||
case 'H':
|
||||
case 'I':
|
||||
case 'J':
|
||||
case 'K':
|
||||
case 'L':
|
||||
case 'M':
|
||||
case 'N':
|
||||
case 'O':
|
||||
case 'P':
|
||||
case 'Q':
|
||||
case 'R':
|
||||
case 'S':
|
||||
case 'T':
|
||||
case 'U':
|
||||
case 'V':
|
||||
case 'W':
|
||||
case 'X':
|
||||
case 'Y':
|
||||
case 'Z':
|
||||
case '_':
|
||||
case 'a':
|
||||
case 'b':
|
||||
case 'c':
|
||||
case 'd':
|
||||
case 'e':
|
||||
case 'f':
|
||||
case 'g':
|
||||
case 'h':
|
||||
case 'i':
|
||||
case 'j':
|
||||
case 'k':
|
||||
case 'l':
|
||||
case 'm':
|
||||
case 'n':
|
||||
case 'o':
|
||||
case 'p':
|
||||
case 'q':
|
||||
case 'r':
|
||||
case 's':
|
||||
case 't':
|
||||
case 'u':
|
||||
case 'v':
|
||||
case 'w':
|
||||
case 'x':
|
||||
case 'y':
|
||||
case 'z':
|
||||
kind[c] = IDENTIFIER;
|
||||
break;
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
kind[c] = NUMBER;
|
||||
break;
|
||||
case '(':
|
||||
case ')':
|
||||
case '[':
|
||||
case ']':
|
||||
case '{':
|
||||
case '}':
|
||||
kind[c] = BRACKET;
|
||||
break;
|
||||
case ',':
|
||||
case ';':
|
||||
kind[c] = SEPARATOR;
|
||||
break;
|
||||
}
|
||||
for (char c = 0; c < 128; c++)
|
||||
if (kind[c] == -1)
|
||||
System.out.println("Char " + ((int) c) + " hasn't been classified");
|
||||
}
|
||||
|
||||
private void initUniKind() {
|
||||
for (byte b = 0; b < 31; b++)
|
||||
unikind[b] = -1;
|
||||
for (byte b = 0; b < 31; b++)
|
||||
switch (b) {
|
||||
case Character.UNASSIGNED:
|
||||
case Character.ENCLOSING_MARK:
|
||||
case Character.OTHER_NUMBER:
|
||||
case Character.SPACE_SEPARATOR:
|
||||
case Character.LINE_SEPARATOR:
|
||||
case Character.PARAGRAPH_SEPARATOR:
|
||||
case Character.CONTROL:
|
||||
case 17: // category 17 is unused
|
||||
case Character.PRIVATE_USE:
|
||||
case Character.SURROGATE:
|
||||
case Character.DASH_PUNCTUATION:
|
||||
case Character.START_PUNCTUATION:
|
||||
case Character.END_PUNCTUATION:
|
||||
case Character.OTHER_PUNCTUATION:
|
||||
case Character.MATH_SYMBOL:
|
||||
case Character.MODIFIER_SYMBOL:
|
||||
case Character.OTHER_SYMBOL:
|
||||
case Character.INITIAL_QUOTE_PUNCTUATION:
|
||||
case Character.FINAL_QUOTE_PUNCTUATION:
|
||||
unikind[b] = UNRECOGNIZED;
|
||||
break;
|
||||
case Character.UPPERCASE_LETTER:
|
||||
case Character.LOWERCASE_LETTER:
|
||||
case Character.TITLECASE_LETTER:
|
||||
case Character.MODIFIER_LETTER:
|
||||
case Character.OTHER_LETTER:
|
||||
case Character.LETTER_NUMBER:
|
||||
case Character.CONNECTOR_PUNCTUATION: // maybe NUMBER
|
||||
case Character.CURRENCY_SYMBOL:
|
||||
// Characters where Other_ID_Start is true
|
||||
unikind[b] = IDENTIFIER;
|
||||
break;
|
||||
case Character.NON_SPACING_MARK:
|
||||
case Character.COMBINING_SPACING_MARK:
|
||||
case Character.DECIMAL_DIGIT_NUMBER:
|
||||
case Character.FORMAT:
|
||||
unikind[b] = NUMBER;
|
||||
break;
|
||||
}
|
||||
for (byte b = 0; b < 31; b++)
|
||||
if (unikind[b] == -1)
|
||||
System.out.println("Unicode cat " + b + " hasn't been classified");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
package se.sics.mspsim.extutil.highlight;
|
||||
import java.awt.Component;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Insets;
|
||||
|
||||
import javax.swing.border.AbstractBorder;
|
||||
|
||||
/**
|
||||
* Draws line numbers next to each line, in the same font as the text.
|
||||
* Currently, this can only be used with a <tt>SyntaxHighlighter</tt> , since it
|
||||
* relies on the <tt>getRows()</tt> and <tt>getLineCount()</tt> methods. A
|
||||
* possible extension, create an interface to return this rows/linecount.
|
||||
*
|
||||
* @author Paul Durbin (McDurby@yahoo.com)
|
||||
* @created January 29, 2002
|
||||
*/
|
||||
public class LineNumberedBorder extends AbstractBorder {
|
||||
|
||||
private static final long serialVersionUID = -3812536735962506061L;
|
||||
|
||||
/**
|
||||
* The line numbers should be drawn on the left side of the component.
|
||||
*/
|
||||
public static int LEFT_SIDE = -2;
|
||||
|
||||
/**
|
||||
* The line numbers should be drawn on the right side of the component.
|
||||
*/
|
||||
public static int RIGHT_SIDE = -1;
|
||||
|
||||
/**
|
||||
* The line number should be right justified.
|
||||
*/
|
||||
public static int RIGHT_JUSTIFY = 0;
|
||||
|
||||
/**
|
||||
* The line number should be left justified.
|
||||
*/
|
||||
public static int LEFT_JUSTIFY = 1;
|
||||
|
||||
/**
|
||||
* Indicates the justification of the text of the line number.
|
||||
*/
|
||||
private int lineNumberJustification = RIGHT_JUSTIFY;
|
||||
|
||||
/**
|
||||
* Indicates the location of the line numbers, w.r.t. the component.
|
||||
*/
|
||||
private int location = LEFT_SIDE;
|
||||
|
||||
public LineNumberedBorder(int location, int justify) {
|
||||
setLocation(location);
|
||||
setLineNumberJustification(justify);
|
||||
}
|
||||
|
||||
public Insets getBorderInsets(Component c) {
|
||||
return getBorderInsets(c, new Insets(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* This modifies the insets, by adding space for the line number on the left.
|
||||
* Should be modified to add space on the right, depending upon Locale.
|
||||
*
|
||||
* @param c
|
||||
* Description of the Parameter
|
||||
* @param insets
|
||||
* Description of the Parameter
|
||||
* @return The borderInsets value
|
||||
*/
|
||||
public Insets getBorderInsets(Component c, Insets insets) {
|
||||
// if c is not a SyntaxHighlighter...nothing is done...
|
||||
if (c instanceof SyntaxHighlighter) {
|
||||
int width = lineNumberWidth((SyntaxHighlighter) c);
|
||||
if (location == LEFT_SIDE) {
|
||||
insets.left = width;
|
||||
} else {
|
||||
insets.right = width;
|
||||
}
|
||||
}
|
||||
return insets;
|
||||
}
|
||||
|
||||
public int getLineNumberJustification() {
|
||||
return lineNumberJustification;
|
||||
}
|
||||
|
||||
public void setLineNumberJustification(int justify) {
|
||||
if (justify == RIGHT_JUSTIFY || justify == LEFT_JUSTIFY) {
|
||||
lineNumberJustification = justify;
|
||||
}
|
||||
}
|
||||
|
||||
public int getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public void setLocation(int loc) {
|
||||
if (loc == RIGHT_SIDE || loc == LEFT_SIDE) {
|
||||
location = loc;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the width, in pixels, of the maximum line number, plus a trailing
|
||||
* space.
|
||||
*
|
||||
* @param textArea
|
||||
* Description of the Parameter
|
||||
* @return Description of the Return Value
|
||||
*/
|
||||
private int lineNumberWidth(SyntaxHighlighter textArea) {
|
||||
//
|
||||
// note: should this be changed to use all nines for the lineCount?
|
||||
// for example, if the number of rows is 111...999 could be wider
|
||||
// (in pixels) in a proportionally spaced font...
|
||||
//
|
||||
int lineCount = Math.max(textArea.getRows(), textArea.getLineCount() + 1);
|
||||
return textArea.getFontMetrics(textArea.getFont()).stringWidth(
|
||||
lineCount + " ");
|
||||
}
|
||||
|
||||
//
|
||||
// NOTE: This method is called every time the cursor blinks...
|
||||
// so...optimize (later and if possible) for speed...
|
||||
//
|
||||
public void paintBorder(Component c, Graphics g, int x, int y, int width,
|
||||
int height) {
|
||||
|
||||
java.awt.Rectangle clip = g.getClipBounds();
|
||||
|
||||
FontMetrics fm = g.getFontMetrics();
|
||||
int fontHeight = fm.getHeight();
|
||||
|
||||
// starting location at the "top" of the page...
|
||||
// y is the starting baseline for the font...
|
||||
// should "font leading" be applied?
|
||||
int ybaseline = y + fm.getAscent();
|
||||
|
||||
//
|
||||
// now determine if it is the "top" of the page...or somewhere else
|
||||
//
|
||||
int startingLineNumber = (clip.y / fontHeight) + 1;
|
||||
|
||||
//
|
||||
// use any one of the following if's:
|
||||
//
|
||||
// if (startingLineNumber != 1)
|
||||
if (ybaseline < clip.y) {
|
||||
//
|
||||
// not within the clip rectangle...move it...
|
||||
// determine how many fontHeight's there are between
|
||||
// y and clip.y...then add that many fontHeights
|
||||
//
|
||||
ybaseline =
|
||||
y + startingLineNumber * fontHeight - (fontHeight - fm.getAscent());
|
||||
}
|
||||
|
||||
//
|
||||
// options:
|
||||
// . write the number rows in the document (current)
|
||||
// . write the number of existing lines in the document (to do)
|
||||
// see getLineCount()
|
||||
//
|
||||
|
||||
// determine which the "drawing" should end...
|
||||
// add fontHeight: make sure...part of the line number is drawn
|
||||
//
|
||||
// could also do this by determining what the last line
|
||||
// number to draw.
|
||||
// then the "while" loop whould change accordingly.
|
||||
//
|
||||
// int yend = y + clip.height + fontHeight;
|
||||
// int yend = ybaseline + height + fontHeight; // original
|
||||
int yend = ybaseline + height;
|
||||
if (yend > (y + height)) {
|
||||
yend = y + height;
|
||||
}
|
||||
|
||||
SyntaxHighlighter jta = (SyntaxHighlighter) c;
|
||||
int lineWidth = lineNumberWidth(jta);
|
||||
|
||||
// base x position of the line number
|
||||
int lnxstart = x;
|
||||
if (location == LEFT_SIDE) {
|
||||
// x (LEFT) or (x + lineWidth) (RIGHT)
|
||||
// (depends upon justification)
|
||||
if (lineNumberJustification == LEFT_JUSTIFY) {
|
||||
lnxstart = x;
|
||||
} else {
|
||||
// RIGHT JUSTIFY
|
||||
lnxstart = x + lineWidth;
|
||||
}
|
||||
} else {
|
||||
// RIGHT SIDE
|
||||
// (y + width) - lineWidth (LEFT) or (y + width) (RIGHT)
|
||||
// (depends upon justification)
|
||||
if (lineNumberJustification == LEFT_JUSTIFY) {
|
||||
lnxstart = (y + width) - lineWidth;
|
||||
} else {
|
||||
// RIGHT JUSTIFY
|
||||
lnxstart = (y + width);
|
||||
}
|
||||
}
|
||||
|
||||
g.setColor(c.getForeground());
|
||||
//
|
||||
// loop until out of the "visible" region...
|
||||
//
|
||||
int length =
|
||||
("" + Math.max(jta.getRows(), jta.getLineCount() + 1)).length();
|
||||
while (ybaseline < yend) {
|
||||
//
|
||||
// options:
|
||||
// . left justify the line numbers
|
||||
// . right justify the line numbers
|
||||
//
|
||||
|
||||
if (lineNumberJustification == LEFT_JUSTIFY) {
|
||||
g.drawString(startingLineNumber + " ", lnxstart, ybaseline);
|
||||
} else {
|
||||
// right justify
|
||||
String label = padLabel(startingLineNumber, length, true);
|
||||
g.drawString(label, lnxstart - fm.stringWidth(label), ybaseline);
|
||||
}
|
||||
|
||||
ybaseline += fontHeight;
|
||||
startingLineNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
// paintComponent
|
||||
|
||||
/**
|
||||
* Create the string for the line number. NOTE: The <tt>length</tt> param
|
||||
* does not include the <em>optional</em> space added after the line number.
|
||||
*
|
||||
* @param lineNumber
|
||||
* to stringize
|
||||
* @param length
|
||||
* the length desired of the string
|
||||
* @param addSpace
|
||||
* Description of the Parameter
|
||||
* @return the line number for drawing
|
||||
*/
|
||||
private static String padLabel(int lineNumber, int length, boolean addSpace) {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
buffer.append(lineNumber);
|
||||
for (int count = (length - buffer.length()); count > 0; count--) {
|
||||
buffer.insert(0, ' ');
|
||||
}
|
||||
if (addSpace) {
|
||||
buffer.append(' ');
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
// LineNumberedBorder
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package se.sics.mspsim.extutil.highlight;
|
||||
// Illustrate the use of the scanner by reading in a file and displaying its
|
||||
// tokens. Public domain, no restrictions, Ian Holyer, University of Bristol.
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class Scan {
|
||||
// Get the filename from the command line
|
||||
public static void main(String[] args) throws IOException {
|
||||
Scan program = new Scan();
|
||||
if (args.length != 1) {
|
||||
System.out.println("Usage: java Scan filename");
|
||||
} else {
|
||||
program.scan(args[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Scan each line in turn
|
||||
public void scan(String filename) throws IOException {
|
||||
File file = new File(filename);
|
||||
int len = (int) file.length();
|
||||
char[] buffer = new char[len];
|
||||
Reader in = new FileReader(file);
|
||||
in.read(buffer);
|
||||
in.close();
|
||||
|
||||
Scanner scanner = new Scanner();
|
||||
scanner.change(0, 0, len);
|
||||
scanner.scan(buffer, 0, len);
|
||||
|
||||
for (int i = 0; i < scanner.size(); i++) {
|
||||
Token t = scanner.getToken(i);
|
||||
System.out.print("" + t.position);
|
||||
System.out.print(": " + t.symbol.name);
|
||||
System.out.println(" " + TokenTypes.typeNames[t.symbol.type]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,450 @@
|
|||
package se.sics.mspsim.extutil.highlight;
|
||||
import java.util.HashMap;
|
||||
|
||||
// Public domain, no restrictions, Ian Holyer, University of Bristol.
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A Scanner object provides a lexical analyser and a resulting token array.
|
||||
* Incremental rescanning is supported, e.g. for use in a token colouring
|
||||
* editor. This is a base class dealing with plain text, which can be extended
|
||||
* to support other languages.
|
||||
*
|
||||
* <p>
|
||||
* The actual text is assumed to be held elsewhere, e.g. in a document. The
|
||||
* <code>change()</code> method is called to report the position and length of
|
||||
* a change in the text, and the <code>scan()</code> method is called to
|
||||
* perform scanning or rescanning. For example, to scan an entire document held
|
||||
* in a character array <code>text</code> in one go:
|
||||
*
|
||||
* <blockquote>
|
||||
*
|
||||
* <pre>
|
||||
* scanner.change(0, 0, text.length);
|
||||
* scanner.scan(text, 0, text.length);
|
||||
* </pre>
|
||||
*
|
||||
* </blockquote>
|
||||
*
|
||||
* <p>
|
||||
* For incremental scanning, the <code>position()</code> method is used to
|
||||
* find the text position at which rescanning should start. For example, a
|
||||
* syntax highlighter might contain this code:
|
||||
*
|
||||
* <blockquote>
|
||||
*
|
||||
* <pre>
|
||||
* // Where to start rehighlighting, and a segment object
|
||||
* int firstRehighlightToken;
|
||||
* Segment segment;
|
||||
* ...
|
||||
* // Whenever the text changes, e.g. on an insert or remove or read.
|
||||
* firstRehighlightToken = scanner.change(offset, oldLength, newLength);
|
||||
* repaint();
|
||||
* ...
|
||||
* // in repaintComponent
|
||||
* int offset = scanner.position();
|
||||
* if (offset < 0) return;
|
||||
* int tokensToRedo = 0;
|
||||
* int amount = 100;
|
||||
* while (tokensToRedo == 0 && offset >= 0)
|
||||
* {
|
||||
* int length = doc.getLength() - offset;
|
||||
* if (length > amount) length = amount;
|
||||
* try { doc.getText(offset, length, text); }
|
||||
* catch (BadLocationException e) { return; }
|
||||
* tokensToRedo = scanner.scan(text.array, text.offset, text.count);
|
||||
* offset = scanner.position();
|
||||
* amount = 2*amount;
|
||||
* }
|
||||
* for (int i = 0; i < tokensToRedo; i++)
|
||||
* {
|
||||
* Token t = scanner.getToken(firstRehighlightToken + i);
|
||||
* int length = t.symbol.name.length();
|
||||
* int type = t.symbol.type;
|
||||
* doc.setCharacterAttributes (t.position, length, styles[type], false);
|
||||
* }
|
||||
* firstRehighlightToken += tokensToRedo;
|
||||
* if (offset >= 0) repaint(2);
|
||||
* </pre>
|
||||
*
|
||||
* </blockquote>
|
||||
*
|
||||
* <p>
|
||||
* Note that <code>change</code> can be called at any time, even between calls
|
||||
* to <code>scan</code>. Only small number of characters are passed to
|
||||
* <code>scan</code> so that only a small burst of scanning is done, to
|
||||
* prevent the program's user interface from freezing.
|
||||
*/
|
||||
public class Scanner implements TokenTypes {
|
||||
/**
|
||||
* <p>
|
||||
* Read one token from the start of the current text buffer, given the start
|
||||
* offset, end offset, and current scanner state. The method moves the start
|
||||
* offset past the token, updates the scanner state, and returns the type of
|
||||
* the token just scanned.
|
||||
*
|
||||
* <p>
|
||||
* The scanner state is a representative token type. It is either the state
|
||||
* left after the last call to read, or the type of the old token at the same
|
||||
* position if rescanning, or WHITESPACE if at the start of a document. The
|
||||
* method succeeds in all cases, returning whitespace or comment or error
|
||||
* tokens where necessary. Each line of a multi-line comment is treated as a
|
||||
* separate token, to improve incremental rescanning. If the buffer does not
|
||||
* extend to the end of the document, the last token returned for the buffer
|
||||
* may be incomplete and the caller must rescan it. The read method can be
|
||||
* overridden to implement different languages. The default version splits
|
||||
* plain text into words, numbers and punctuation.
|
||||
*/
|
||||
protected int read() {
|
||||
char c = buffer[start];
|
||||
int type;
|
||||
// Ignore the state, since there is only one.
|
||||
if (Character.isWhitespace(c)) {
|
||||
type = WHITESPACE;
|
||||
while (++start < end) {
|
||||
if (!Character.isWhitespace(buffer[start]))
|
||||
break;
|
||||
}
|
||||
} else if (Character.isLetter(c)) {
|
||||
type = WORD;
|
||||
while (++start < end) {
|
||||
c = buffer[start];
|
||||
if (Character.isLetter(c) || Character.isDigit(c))
|
||||
continue;
|
||||
if (c == '-' || c == '\'' || c == '_')
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
} else if (Character.isDigit(c)) {
|
||||
type = NUMBER;
|
||||
while (++start < end) {
|
||||
c = buffer[start];
|
||||
if (!Character.isDigit(c) && c != '.')
|
||||
break;
|
||||
}
|
||||
} else if (c >= '!' || c <= '~') {
|
||||
type = PUNCTUATION;
|
||||
start++;
|
||||
} else {
|
||||
type = UNRECOGNIZED;
|
||||
start++;
|
||||
}
|
||||
|
||||
// state = WHITESPACE;
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current buffer of text being scanned.
|
||||
*/
|
||||
protected char[] buffer;
|
||||
|
||||
/**
|
||||
* The current offset within the buffer, at which to scan the next token.
|
||||
*/
|
||||
protected int start;
|
||||
|
||||
/**
|
||||
* The end offset in the buffer.
|
||||
*/
|
||||
protected int end;
|
||||
|
||||
/**
|
||||
* The current scanner state, as a representative token type.
|
||||
*/
|
||||
protected int state = WHITESPACE;
|
||||
|
||||
// The array of tokens forms a gap buffer. The total length of the text is
|
||||
// tracked, and tokens after the gap have (negative) positions relative to
|
||||
// the end of the text. While scanning, the gap represents the area to be
|
||||
// scanned, no tokens after the gap can be taken as valid, and in particular
|
||||
// the end-of-text sentinel token is after the gap.
|
||||
|
||||
private Token[] tokens;
|
||||
|
||||
private int gap, endgap, textLength;
|
||||
|
||||
private boolean scanning;
|
||||
|
||||
private int position;
|
||||
|
||||
/**
|
||||
* The symbol table can be accessed by <code>initSymbolTable</code> or
|
||||
* <code>lookup</code>, if they are overridden. Symbols are inserted with
|
||||
* <code>symbolTable.put(sym,sym)</code> and extracted with
|
||||
* <code>symbolTable.get(sym)</code>.
|
||||
*/
|
||||
protected HashMap<Symbol,Symbol> symbolTable;
|
||||
|
||||
/**
|
||||
* Create a new Scanner representing an empty text document. For
|
||||
* non-incremental scanning, use change() to report the document size, then
|
||||
* pass the entire text to the scan() method in one go, or if coming from an
|
||||
* input stream, a bufferful at a time.
|
||||
*/
|
||||
public Scanner() {
|
||||
tokens = new Token[1];
|
||||
gap = 0;
|
||||
endgap = 0;
|
||||
textLength = 0;
|
||||
symbolTable = new HashMap<Symbol,Symbol>();
|
||||
initSymbolTable();
|
||||
Symbol endOfText = new Symbol(WHITESPACE, "");
|
||||
tokens[0] = new Token(endOfText, 0);
|
||||
scanning = false;
|
||||
position = 0;
|
||||
}
|
||||
|
||||
// Move the gap to a new index within the tokens array. When preparing to
|
||||
// pass a token back to a caller, this is used to ensure that the token's
|
||||
// position is relative to the start of the text and not the end.
|
||||
|
||||
private void moveGap(int newgap) {
|
||||
if (scanning)
|
||||
throw new Error("moveGap called while scanning");
|
||||
if (newgap < 0 || newgap > gap + tokens.length - endgap) {
|
||||
throw new Error("bad argument to moveGap");
|
||||
}
|
||||
if (gap < newgap) {
|
||||
while (gap < newgap) {
|
||||
tokens[endgap].position += textLength;
|
||||
tokens[gap++] = tokens[endgap++];
|
||||
}
|
||||
} else if (gap > newgap) {
|
||||
while (gap > newgap) {
|
||||
tokens[--endgap] = tokens[--gap];
|
||||
tokens[endgap].position -= textLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the number of available valid tokens, not counting tokens in or after
|
||||
* any area yet to be rescanned.
|
||||
*/
|
||||
public int size() {
|
||||
if (scanning) {
|
||||
return gap;
|
||||
}
|
||||
return gap + tokens.length - endgap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the n'th token, or null if it is not currently valid.
|
||||
*/
|
||||
public Token getToken(int n) {
|
||||
if (n < 0 || n >= gap && scanning)
|
||||
return null;
|
||||
if (n >= gap)
|
||||
moveGap(n + 1);
|
||||
return tokens[n];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index of the valid token starting before, but nearest to, text
|
||||
* position p. This uses an O(log(n)) binary chop search.
|
||||
*/
|
||||
public int find(int p) {
|
||||
int start = 0, end, mid, midpos;
|
||||
if (!scanning)
|
||||
moveGap(gap + tokens.length - endgap);
|
||||
end = gap - 1;
|
||||
if (p > tokens[end].position)
|
||||
return end;
|
||||
while (end > start + 1) {
|
||||
mid = (start + end) / 2;
|
||||
midpos = tokens[mid].position;
|
||||
if (p > midpos) {
|
||||
start = mid;
|
||||
} else {
|
||||
end = mid;
|
||||
}
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the position of an edit, the length of the text being replaced, and
|
||||
* the length of the replacement text, to prepare for rescanning. The call
|
||||
* returns the index of the token at which rescanning will start.
|
||||
*/
|
||||
public int change(int start, int len, int newLen) {
|
||||
if (start < 0 || len < 0 || newLen < 0 || start + len > textLength) {
|
||||
throw new Error("change(" + start + "," + len + "," + newLen + ")");
|
||||
}
|
||||
textLength += newLen - len;
|
||||
int end = start + newLen;
|
||||
if (scanning) {
|
||||
while (gap > 0 && tokens[gap - 1].position > start)
|
||||
gap--;
|
||||
if (gap > 0)
|
||||
gap--;
|
||||
if (gap > 0) {
|
||||
gap--;
|
||||
position = tokens[gap].position;
|
||||
state = tokens[gap].symbol.type;
|
||||
} else {
|
||||
position = 0;
|
||||
state = WHITESPACE;
|
||||
}
|
||||
while (tokens[endgap].position + textLength < end)
|
||||
endgap++;
|
||||
return gap;
|
||||
}
|
||||
if (endgap == tokens.length)
|
||||
moveGap(gap - 1);
|
||||
scanning = true;
|
||||
while (tokens[endgap].position + textLength < start) {
|
||||
tokens[endgap].position += textLength;
|
||||
tokens[gap++] = tokens[endgap++];
|
||||
}
|
||||
while (gap > 0 && tokens[gap - 1].position > start) {
|
||||
tokens[--endgap] = tokens[--gap];
|
||||
tokens[endgap].position -= textLength;
|
||||
}
|
||||
if (gap > 0)
|
||||
gap--;
|
||||
if (gap > 0) {
|
||||
gap--;
|
||||
position = tokens[gap].position;
|
||||
state = tokens[gap].symbol.type;
|
||||
} else {
|
||||
position = 0;
|
||||
state = WHITESPACE;
|
||||
}
|
||||
while (tokens[endgap].position + textLength < end)
|
||||
endgap++;
|
||||
return gap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find out at what text position any remaining scanning work should start, or
|
||||
* -1 if scanning is complete.
|
||||
*/
|
||||
public int position() {
|
||||
if (!scanning) {
|
||||
return -1;
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the initial symbol table. This can be overridden to enter keywords,
|
||||
* for example. The default implementation does nothing.
|
||||
*/
|
||||
protected void initSymbolTable() {
|
||||
// Nothing as default
|
||||
}
|
||||
|
||||
// Reuse this symbol object to create each new symbol, then look it up in
|
||||
// the symbol table, to replace it by a shared version to minimize space.
|
||||
|
||||
private Symbol symbol = new Symbol(0, null);
|
||||
|
||||
/**
|
||||
* Lookup a symbol in the symbol table. This can be overridden to implement
|
||||
* keyword detection, for example. The default implementation just uses the
|
||||
* table to ensure that there is only one shared occurrence of each symbol.
|
||||
*/
|
||||
protected Symbol lookup(int type, String name) {
|
||||
symbol.type = type;
|
||||
symbol.name = name;
|
||||
Symbol sym = symbolTable.get(symbol);
|
||||
if (sym != null) {
|
||||
return sym;
|
||||
}
|
||||
sym = new Symbol(type, name);
|
||||
symbolTable.put(sym, sym);
|
||||
return sym;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan or rescan a given read-only segment of text. The segment is assumed to
|
||||
* represent a portion of the document starting at <code>position()</code>.
|
||||
* Return the number of tokens successfully scanned, excluding any partial
|
||||
* token at the end of the text segment but not at the end of the document. If
|
||||
* the result is 0, the call should be retried with a longer segment.
|
||||
*/
|
||||
public int scan(char[] array, int offset, int length) {
|
||||
if (!scanning)
|
||||
throw new Error("scan called when not scanning");
|
||||
if (position + length > textLength)
|
||||
throw new Error("scan too much");
|
||||
boolean all = position + length == textLength;
|
||||
end = start + length;
|
||||
int startGap = gap;
|
||||
|
||||
buffer = array;
|
||||
start = offset;
|
||||
end = start + length;
|
||||
while (start < end) {
|
||||
int tokenStart = start;
|
||||
int type = read();
|
||||
if (start == end && !all)
|
||||
break;
|
||||
|
||||
if (type != WHITESPACE) {
|
||||
String name = new String(buffer, tokenStart, start - tokenStart);
|
||||
Symbol sym = lookup(type, name);
|
||||
Token t = new Token(sym, position);
|
||||
if (gap >= endgap)
|
||||
checkCapacity(gap + tokens.length - endgap + 1);
|
||||
tokens[gap++] = t;
|
||||
}
|
||||
|
||||
// Try to synchronise
|
||||
|
||||
while (tokens[endgap].position + textLength < position)
|
||||
endgap++;
|
||||
if (position + start - tokenStart == textLength)
|
||||
scanning = false;
|
||||
else if (gap > 0 && tokens[endgap].position + textLength == position
|
||||
&& tokens[endgap].symbol.type == type) {
|
||||
endgap++;
|
||||
scanning = false;
|
||||
break;
|
||||
}
|
||||
position += start - tokenStart;
|
||||
}
|
||||
checkCapacity(gap + tokens.length - endgap);
|
||||
return gap - startGap;
|
||||
}
|
||||
|
||||
// Change the size of the gap buffer, doubling it if it fills up, and
|
||||
// halving if it becomes less than a quarter full.
|
||||
|
||||
private void checkCapacity(int capacity) {
|
||||
int oldCapacity = tokens.length;
|
||||
if (capacity <= oldCapacity && 4 * capacity >= oldCapacity)
|
||||
return;
|
||||
Token[] oldTokens = tokens;
|
||||
int newCapacity;
|
||||
if (capacity > oldCapacity) {
|
||||
newCapacity = oldCapacity * 2;
|
||||
if (newCapacity < capacity)
|
||||
newCapacity = capacity;
|
||||
} else
|
||||
newCapacity = capacity * 2;
|
||||
|
||||
tokens = new Token[newCapacity];
|
||||
System.arraycopy(oldTokens, 0, tokens, 0, gap);
|
||||
int n = oldCapacity - endgap;
|
||||
System.arraycopy(oldTokens, endgap, tokens, newCapacity - n, n);
|
||||
endgap = newCapacity - n;
|
||||
}
|
||||
|
||||
void print() {
|
||||
for (int i = 0; i < tokens.length; i++) {
|
||||
if (i >= gap && i < endgap)
|
||||
continue;
|
||||
if (i == endgap)
|
||||
System.out.print("... ");
|
||||
System.out.print("" + i + ":" + tokens[i].position);
|
||||
System.out.print("-"
|
||||
+ (tokens[i].position + tokens[i].symbol.name.length()));
|
||||
System.out.print(" ");
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package se.sics.mspsim.extutil.highlight;
|
||||
// Public domain, no restrictions, Ian Holyer, University of Bristol.
|
||||
|
||||
/**
|
||||
* A Symbol represents the information shared between similar tokens, i.e. their
|
||||
* type and spelling.
|
||||
*/
|
||||
public class Symbol {
|
||||
/**
|
||||
* The type is a small integer used to classify symbols. It also distinguishes
|
||||
* different symbols with the same spelling, where necessary.
|
||||
*/
|
||||
public int type;
|
||||
|
||||
/**
|
||||
* The spelling.
|
||||
*/
|
||||
public String name;
|
||||
|
||||
/**
|
||||
* Construct a symbol from its type and name.
|
||||
*/
|
||||
public Symbol(int type, String name) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the symbol.
|
||||
*/
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form a hash value from the type and name.
|
||||
*/
|
||||
public int hashCode() {
|
||||
return name.hashCode() + type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the type and name with some other symbol.
|
||||
*/
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof Symbol))
|
||||
return false;
|
||||
Symbol that = (Symbol) obj;
|
||||
return name.equals(that.name) && type == that.type;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
package se.sics.mspsim.extutil.highlight;
|
||||
import java.awt.*;
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.*;
|
||||
import javax.swing.event.*;
|
||||
import java.io.*;
|
||||
|
||||
// Public domain, no restrictions, Ian Holyer, University of Bristol.
|
||||
|
||||
/**
|
||||
* Display text with syntax highlighting. Highlighting is done with full
|
||||
* accuracy, using a given language scanner. Large amounts of re-highlighting
|
||||
* are done in small bursts to make sure the user interface doesn't freeze.
|
||||
*/
|
||||
public class SyntaxHighlighter extends JTextPane implements DocumentListener, TokenTypes {
|
||||
|
||||
|
||||
private static final long serialVersionUID = -1801145479677890566L;
|
||||
|
||||
private StyledDocument doc;
|
||||
private Scanner scanner;
|
||||
private int rows, columns;
|
||||
|
||||
/**
|
||||
* Create a graphics component which displays text with syntax highlighting.
|
||||
* Provide a width and height, in characters, and a language scanner.
|
||||
*/
|
||||
public SyntaxHighlighter(int rows, int columns, Scanner scanner) {
|
||||
super(new DefaultStyledDocument());
|
||||
doc = (StyledDocument) getDocument();
|
||||
this.rows = rows;
|
||||
this.columns = columns;
|
||||
this.scanner = scanner;
|
||||
doc.addDocumentListener(this);
|
||||
Font font = new Font("Monospaced", Font.PLAIN, getFont().getSize());
|
||||
changeFont(font);
|
||||
initStyles();
|
||||
}
|
||||
|
||||
public int getColumns() {
|
||||
return columns;
|
||||
}
|
||||
|
||||
public int getRows() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the component's font, and change the size of the component to match.
|
||||
*/
|
||||
public void changeFont(Font font) {
|
||||
int borderOfJTextPane = 3;
|
||||
setFont(font);
|
||||
FontMetrics metrics = getFontMetrics(font);
|
||||
int paneWidth = columns * metrics.charWidth('m') + 2 * borderOfJTextPane;
|
||||
int paneHeight = rows * metrics.getHeight() + 2 * borderOfJTextPane;
|
||||
Dimension size = new Dimension(paneWidth, paneHeight);
|
||||
setMinimumSize(size);
|
||||
setPreferredSize(size);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read new text into the component from a <code>Reader</code>. Overrides
|
||||
* <code>read</code> in <code>JTextComponent</code> in order to highlight
|
||||
* the new text.
|
||||
*/
|
||||
public void read(Reader in, Object desc) throws IOException {
|
||||
int oldLength = getDocument().getLength();
|
||||
doc.removeDocumentListener(this);
|
||||
super.read(in, desc);
|
||||
doc = (StyledDocument) getDocument();
|
||||
doc.addDocumentListener(this);
|
||||
int newLength = getDocument().getLength();
|
||||
firstRehighlightToken = scanner.change(0, oldLength, newLength);
|
||||
repaint();
|
||||
}
|
||||
|
||||
// An array of styles, indexed by token type. Default styles are set up,
|
||||
// which can be used for any languages.
|
||||
|
||||
private Style[] styles;
|
||||
|
||||
private void initStyles() {
|
||||
styles = new Style[typeNames.length];
|
||||
changeStyle(UNRECOGNIZED, Color.red);
|
||||
changeStyle(WHITESPACE, Color.black);
|
||||
changeStyle(WORD, Color.black);
|
||||
changeStyle(NUMBER, Color.orange.darker());
|
||||
changeStyle(PUNCTUATION, Color.orange.darker());
|
||||
changeStyle(COMMENT, Color.green.darker());
|
||||
changeStyle(START_COMMENT, Color.green.darker());
|
||||
changeStyle(MID_COMMENT, Color.green.darker());
|
||||
changeStyle(END_COMMENT, Color.green.darker());
|
||||
changeStyle(TAG, Color.blue, Font.BOLD);
|
||||
changeStyle(END_TAG, Color.blue, Font.BOLD);
|
||||
changeStyle(KEYWORD, Color.blue);
|
||||
changeStyle(KEYWORD2, Color.blue);
|
||||
changeStyle(IDENTIFIER, Color.black);
|
||||
changeStyle(LITERAL, Color.magenta);
|
||||
changeStyle(STRING, Color.magenta);
|
||||
changeStyle(CHARACTER, Color.magenta);
|
||||
changeStyle(OPERATOR, Color.black, Font.BOLD);
|
||||
changeStyle(BRACKET, Color.orange.darker());
|
||||
changeStyle(SEPARATOR, Color.orange.darker());
|
||||
changeStyle(URL, Color.blue.darker());
|
||||
|
||||
for (int i = 0; i < styles.length; i++) {
|
||||
if (styles[i] == null) {
|
||||
styles[i] = styles[WHITESPACE];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the style of a particular type of token.
|
||||
*/
|
||||
public void changeStyle(int type, Color color) {
|
||||
Style style = addStyle(typeNames[type], null);
|
||||
StyleConstants.setForeground(style, color);
|
||||
styles[type] = style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the style of a particular type of token, including adding bold or
|
||||
* italic using a third argument of <code>Font.BOLD</code> or
|
||||
* <code>Font.ITALIC</code> or the bitwise union
|
||||
* <code>Font.BOLD|Font.ITALIC</code>.
|
||||
*/
|
||||
public void changeStyle(int type, Color color, int fontStyle) {
|
||||
Style style = addStyle(typeNames[type], null);
|
||||
StyleConstants.setForeground(style, color);
|
||||
if ((fontStyle & Font.BOLD) != 0)
|
||||
StyleConstants.setBold(style, true);
|
||||
if ((fontStyle & Font.ITALIC) != 0)
|
||||
StyleConstants.setItalic(style, true);
|
||||
styles[type] = style;
|
||||
}
|
||||
|
||||
public int getLineCount() {
|
||||
return getDocument().getDefaultRootElement().getElementCount();
|
||||
}
|
||||
|
||||
public int getLineStartOffset(int line) {
|
||||
Element root = getDocument().getDefaultRootElement();
|
||||
if (line < 0 || line >= root.getElementCount()) {
|
||||
throw new IndexOutOfBoundsException("illegal line");
|
||||
}
|
||||
return root.getElement(line).getStartOffset();
|
||||
}
|
||||
|
||||
public int getLineEndOffset(int line) {
|
||||
Element root = getDocument().getDefaultRootElement();
|
||||
if (line < 0 || line >= root.getElementCount()) {
|
||||
throw new IndexOutOfBoundsException("illegal line");
|
||||
}
|
||||
return root.getElement(line).getEndOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* <font style='color:gray;'>Ignore this method. Responds to the underlying
|
||||
* document changes by re-highlighting.</font>
|
||||
*/
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
int offset = e.getOffset();
|
||||
int length = e.getLength();
|
||||
firstRehighlightToken = scanner.change(offset, 0, length);
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* <font style='color:gray;'>Ignore this method. Responds to the underlying
|
||||
* document changes by re-highlighting.</font>
|
||||
*/
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
int offset = e.getOffset();
|
||||
int length = e.getLength();
|
||||
firstRehighlightToken = scanner.change(offset, length, 0);
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* <font style='color:gray;'>Ignore this method. Responds to the underlying
|
||||
* document changes by re-highlighting.</font>
|
||||
*/
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
// Scan a small portion of the document. If more is needed, call repaint()
|
||||
// so the GUI gets a go and doesn't freeze, but calls this again later.
|
||||
|
||||
private Segment text = new Segment();
|
||||
|
||||
private int firstRehighlightToken;
|
||||
|
||||
private int smallAmount = 100;
|
||||
|
||||
/**
|
||||
* <font style='color:gray;'>Ignore this method. Carries out a small amount of
|
||||
* re-highlighting for each call to <code>repaint</code>.</font>
|
||||
*/
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
int offset = scanner.position();
|
||||
if (offset < 0)
|
||||
return;
|
||||
|
||||
int tokensToRedo = 0;
|
||||
int amount = smallAmount;
|
||||
while (tokensToRedo == 0 && offset >= 0) {
|
||||
int length = doc.getLength() - offset;
|
||||
if (length > amount) {
|
||||
length = amount;
|
||||
}
|
||||
try {
|
||||
doc.getText(offset, length, text);
|
||||
} catch (BadLocationException e) {
|
||||
return;
|
||||
}
|
||||
tokensToRedo = scanner.scan(text.array, text.offset, text.count);
|
||||
offset = scanner.position();
|
||||
amount = 2 * amount;
|
||||
}
|
||||
for (int i = 0; i < tokensToRedo; i++) {
|
||||
Token t = scanner.getToken(firstRehighlightToken + i);
|
||||
int length = t.symbol.name.length();
|
||||
int type = t.symbol.type;
|
||||
if (type < 0) {
|
||||
type = UNRECOGNIZED;
|
||||
}
|
||||
doc.setCharacterAttributes(t.position, length, styles[type], false);
|
||||
}
|
||||
firstRehighlightToken += tokensToRedo;
|
||||
if (offset >= 0) {
|
||||
repaint(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package se.sics.mspsim.extutil.highlight;
|
||||
// This does exactly the same job as the Scanner class, but uses a table driven
|
||||
// scanner. This illustrates how to extend the Scanner class, and how a
|
||||
// generator might produce a table-driven scanner (though this one was written
|
||||
// by hand). Public domain, no restrictions, Ian Holyer, University of Bristol.
|
||||
|
||||
public class TextScanner extends Scanner {
|
||||
// Classify the 128 ASCII characters. Define a compact 'readable' form,
|
||||
// then expand into an array, using static initializer code. The 128 are:
|
||||
// .........tn..................... !"#$%&'()*+,-./0123456789:;<=>?
|
||||
// @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~.
|
||||
|
||||
static final String cs =
|
||||
"1111111112211111111111111111111127777774777774675555555555777777"
|
||||
+ "7333333333333333333333333337777473333333333333333333333333377771";
|
||||
|
||||
static final byte[] kinds = new byte[cs.length()];
|
||||
{
|
||||
for (int i = 0; i < cs.length(); i++) {
|
||||
kinds[i] = (byte) (cs.charAt(i) - '0');
|
||||
}
|
||||
}
|
||||
|
||||
// This state table has a row per scanner state, and a column per class of
|
||||
// character. Each entry is '-s' to mean accept the current character and
|
||||
// goto state s, or '+t' to mean end the token giving it type t. There is
|
||||
// no stack of states, or extra table to determine the next state after a
|
||||
// token is recognized, as there could be with more sophisticated scanners.
|
||||
// The raw numbers +t must use or match the constants in TokenTypes.
|
||||
|
||||
int[][] table = {
|
||||
// $ \0 \n z '-_ 9 . ( // $ = end of text, must be column 0
|
||||
{ +1, -1, -2, -3, -4, -5, -4, -4 }, // s=0: start token
|
||||
{ +1, +1, +1, +1, +1, +1, +1, +1 }, // s=1: illegal character (t=1)
|
||||
{ +0, +0, -2, +0, +0, +0, +0, +0 }, // s=2: whitespace (t=0)
|
||||
{ +2, +2, +2, -3, -3, -3, +2, +2 }, // s=3: word (t=2)
|
||||
{ +4, +4, +4, +4, +4, +4, +4, +4 }, // s=4: punctuation (t=4)
|
||||
{ +3, +3, +3, +3, +3, -5, -5, +3 } // s=5: number (t=3)
|
||||
};
|
||||
|
||||
// Now all we have to do is to override read(), using the parent variables
|
||||
// buffer, start, end, state.
|
||||
|
||||
protected int read() {
|
||||
state = 0;
|
||||
int kind = kinds[buffer[start]];
|
||||
int type;
|
||||
while (true) {
|
||||
type = table[state][kind];
|
||||
if (type >= 0)
|
||||
break;
|
||||
state = -type;
|
||||
start++;
|
||||
if (start >= end)
|
||||
kind = 0;
|
||||
else
|
||||
kind = kinds[buffer[start]];
|
||||
}
|
||||
state = WHITESPACE;
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package se.sics.mspsim.extutil.highlight;
|
||||
// Public domain, no restrictions, Ian Holyer, University of Bristol.
|
||||
|
||||
/**
|
||||
* A token represents a smallest meaningful fragment of text, such as a word,
|
||||
* recognised by a scanner.
|
||||
*/
|
||||
public class Token {
|
||||
/**
|
||||
* The symbol contains all the properties shared with similar tokens.
|
||||
*/
|
||||
public Symbol symbol;
|
||||
|
||||
/**
|
||||
* The token's position is given by an index into the document text.
|
||||
*/
|
||||
public int position;
|
||||
|
||||
/**
|
||||
* Create a token with a given symbol and position.
|
||||
*/
|
||||
public Token(Symbol symbol, int position) {
|
||||
this.symbol = symbol;
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package se.sics.mspsim.extutil.highlight;
|
||||
// Public domain, no restrictions, Ian Holyer, University of Bristol.
|
||||
|
||||
/**
|
||||
* The TokenTypes interface defines the integer constants representing different
|
||||
* types of tokens, for use with any languages. The constants are used in
|
||||
* symbols to represent the types of similar tokens, and in scanners as scanner
|
||||
* states, and in highlighters to determine the colour or style of tokens. There
|
||||
* is also an array typeNames of textual names, indexed by type, for descriptive
|
||||
* purposes.
|
||||
*
|
||||
* <p>
|
||||
* The UNRECOGNIZED constant (zero) is for tokens which are completely
|
||||
* unrecognized, usually consisting of a single illegal character. Other error
|
||||
* tokens are represented by negative types, where -t represents an incomplete
|
||||
* or malformed token of type t. An error token usually consists of the maximal
|
||||
* legal substring of the source text.
|
||||
*
|
||||
* <p>
|
||||
* The WHITESPACE constant is used to classify tokens which are to be discarded,
|
||||
* it acts as a suitable scanner state at the beginning of a document, and it is
|
||||
* used for the usual end-of-text sentinel token which marks the end of the
|
||||
* document. Comments can optionally be classified as WHITESPACE and discarded,
|
||||
* if they are not needed for highlighting. No other types besides UNRECOGNIZED
|
||||
* and WHITESPACE are treated specially.
|
||||
*
|
||||
* <p>
|
||||
* The constants are presented as an interface so that any class can implement
|
||||
* the interface and use the names of the constants directly, without prefixing
|
||||
* them with a class name.
|
||||
*
|
||||
*/
|
||||
public interface TokenTypes {
|
||||
public static final int UNRECOGNIZED = 0, WHITESPACE = 1, WORD = 2,
|
||||
NUMBER = 3, PUNCTUATION = 4, COMMENT = 5, START_COMMENT = 6,
|
||||
MID_COMMENT = 7, END_COMMENT = 8, TAG = 9, END_TAG = 10, KEYWORD = 11,
|
||||
KEYWORD2 = 12, IDENTIFIER = 13, LITERAL = 14, STRING = 15,
|
||||
CHARACTER = 16, OPERATOR = 17, BRACKET = 18, SEPARATOR = 19, URL = 20;
|
||||
|
||||
/**
|
||||
* The names of the token types, indexed by type, are provided for descriptive
|
||||
* purposes.
|
||||
*/
|
||||
public static final String[] typeNames = {
|
||||
"bad token", "whitespace", "word", "number", "punctuation", "comment",
|
||||
"start of comment", "middle of comment", "end of comment", "tag",
|
||||
"end tag", "keyword", "keyword 2", "identifier", "literal", "string",
|
||||
"character", "operator", "bracket", "separator", "url"
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue