blob: bf9c9421cd4cc54e128223a7b900efc63fbd7d92 [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library classify;
import 'package:analyzer_experimental/src/generated/java_core.dart';
import 'package:analyzer_experimental/src/generated/scanner.dart';
class Classification {
static const NONE = "";
static const ERROR = "e";
static const COMMENT = "c";
static const IDENTIFIER = "i";
static const KEYWORD = "k";
static const OPERATOR = "o";
static const STRING = "s";
static const NUMBER = "n";
static const PUNCTUATION = "p";
static const TYPE_IDENTIFIER = "t";
static const SPECIAL_IDENTIFIER = "r";
static const ARROW_OPERATOR = "a";
static const STRING_INTERPOLATION = 'si';
}
String classifyDart(String src) {
var scanner = new StringScanner(null, src, null);
var token = scanner.tokenize();
var out = new StringBuffer();
var pos = 0;
while (token.type != TokenType.EOF) {
// If not a token and not whitespace assume comment.
var comment = src.slice(pos, token.offset);
if (comment.trim().length > 0) {
out.add('<span class="${Classification.COMMENT}">$comment</span>');
}
else out.add(comment);
pos = token.end;
var inString = (token.type == TokenType.STRING
|| token.type == TokenType.STRING_INTERPOLATION_EXPRESSION
|| token.type == TokenType.STRING_INTERPOLATION_IDENTIFIER);
var stringClass = inString ? ' ${Classification.STRING_INTERPOLATION}' : '';
var kind = classify(token);
out.add('<span class="$kind$stringClass">$token</span>');
token = token.next;
}
return out.toString();
}
Map createTokenMap() {
var map = new Map();
[ TokenType.OPEN_PAREN,
TokenType.CLOSE_PAREN,
TokenType.OPEN_CURLY_BRACKET,
TokenType.CLOSE_CURLY_BRACKET,
TokenType.OPEN_SQUARE_BRACKET,
TokenType.OPEN_SQUARE_BRACKET,
TokenType.COLON,
TokenType.SEMICOLON,
TokenType.COMMA,
TokenType.PERIOD,
TokenType.PERIOD_PERIOD
].forEach((t) => map[t] = Classification.PUNCTUATION);
[ TokenType.INT,
TokenType.HEXADECIMAL,
TokenType.DOUBLE
].forEach((t) => map[t] = Classification.NUMBER);
[ TokenType.STRING,
TokenType.STRING_INTERPOLATION_IDENTIFIER,
TokenType.STRING_INTERPOLATION_EXPRESSION,
TokenType.DOUBLE
].forEach((t) => map[t] = Classification.STRING);
[ TokenType.PLUS_PLUS,
TokenType.MINUS_MINUS,
TokenType.TILDE,
TokenType.BANG,
TokenType.EQ,
TokenType.BAR_EQ,
TokenType.CARET_EQ,
TokenType.AMPERSAND_EQ,
TokenType.LT_LT_EQ,
TokenType.GT_GT_EQ,
TokenType.PLUS_EQ,
TokenType.MINUS_EQ,
TokenType.STAR_EQ,
TokenType.SLASH_EQ,
TokenType.TILDE_SLASH_EQ,
TokenType.PERCENT_EQ,
TokenType.QUESTION,
TokenType.BAR_BAR,
TokenType.AMPERSAND_AMPERSAND,
TokenType.BAR,
TokenType.CARET,
TokenType.AMPERSAND,
TokenType.LT_LT,
TokenType.GT_GT,
TokenType.PLUS,
TokenType.MINUS,
TokenType.STAR,
TokenType.SLASH,
TokenType.TILDE_SLASH,
TokenType.PERCENT,
TokenType.EQ_EQ,
TokenType.BANG_EQ,
TokenType.LT,
TokenType.GT,
TokenType.LT_EQ,
TokenType.GT_EQ,
TokenType.INDEX,
TokenType.INDEX_EQ,
].forEach((t) => map[t] = Classification.OPERATOR);
// => is so awesome it is in a class of its own.
map[TokenType.FUNCTION] = Classification.ARROW_OPERATOR;
map[TokenType.IDENTIFIER] = Classification.IDENTIFIER;
map[TokenType.KEYWORD] = Classification.KEYWORD;
map[TokenType.HASH] = Classification.KEYWORD;
return map;
}
var _tokenMap = createTokenMap();
String classify(Token token) {
if (!_tokenMap.containsKey(token.type)) {
return Classification.NONE;
}
var classification = _tokenMap[token.type];
// Special case for names that look like types.
if (classification == Classification.IDENTIFIER) {
final text = token.lexeme;
if (_looksLikeType(text)
|| text == 'num'
|| text == 'bool'
|| text == 'int'
|| text == 'double') {
return Classification.TYPE_IDENTIFIER;
}
}
// Color keyword token. Most are colored as keywords.
if (classification == Classification.KEYWORD) {
if (token.lexeme == 'void') {
// Color "void" as a type.
return Classification.TYPE_IDENTIFIER;
}
if (token.lexeme == 'this' || token.lexeme == 'super') {
// Color "this" and "super" as identifiers.
return Classification.SPECIAL_IDENTIFIER;
}
}
return classification;
}
bool _looksLikeType(String name) {
// If the name looks like an UppercaseName, assume it's a type.
return _looksLikePublicType(name) || _looksLikePrivateType(name);
}
bool _looksLikePublicType(String name) {
// If the name looks like an UppercaseName, assume it's a type.
return name.length >= 2 && isUpper(name[0]) && isLower(name[1]);
}
bool _looksLikePrivateType(String name) {
// If the name looks like an _UppercaseName, assume it's a type.
return (name.length >= 3 && name[0] == '_' && isUpper(name[1])
&& isLower(name[2]));
}
// These ensure that they don't return "true" if the string only has symbols.
bool isUpper(String s) => s.toLowerCase() != s;
bool isLower(String s) => s.toUpperCase() != s;