ink-docstrap
Version:
[](https://nodei.co/npm/ink-docstrap/)
406 lines (339 loc) • 12.9 kB
JavaScript
(function(sunlight, undefined){
//this is remarkably similar to the C# language definition
//ergo, there is a lot of copypasting going on here...
if (sunlight === undefined || sunlight["registerLanguage"] === undefined) {
throw "Include sunlight.js before including language files";
}
function createNamedIdentFunction(func) {
var typeDefinitionRegex = /^T([A-Z0-9]\w*)?$/;
return function(context) {
return !typeDefinitionRegex.test(context.tokens[context.index].value) && func(context);
};
};
var primitives = ["boolean", "byte", "char", "double", "float", "int", "long", "short"],
acceptableKeywords = primitives.concat(["extends"]);
sunlight.registerLanguage("java", {
keywords: [
//http://download.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html
"abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const",
"continue", "default", "do", "double", "else", "enum", "extends", "final", "finally", "float",
"for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native",
"new", "package", "private", "protected", "public", "return", "short", "static", "strictfp" /* wtf? */, "super",
"switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile", "while",
//literals
"null", "false", "true"
],
scopes: {
string: [ ["\"", "\"", sunlight.util.escapeSequences.concat(["\\\""])], ["'", "'", ["\'", "\\\\"]] ],
comment: [ ["//", "\n", null, true], ["/*", "*/"] ],
annotation: [ ["@", { length: 1, regex: /[\s\(]/ }, null, true] ]
},
identFirstLetter: /[A-Za-z_]/,
identAfterFirstLetter: /\w/,
//these are mostly stolen from the C# lang file
namedIdentRules: {
custom: [
//generic definitions/params between "<" and ">"
createNamedIdentFunction(function(context) {
//between < and > and preceded by an ident and not preceded by "class"
var index = context.index,
token,
foundIdent = false,
bracketCountLeft = [0, 0];
//look for "<" preceded by an ident but not "class"
//if we run into ">" before "," or "<" then it's a big fail
while ((token = context.tokens[--index]) !== undefined) {
if (token.name === "keyword" && token.value === "class") {
//this must be a generic class type definition, e.g. Foo<T>, and we don't want to color the "T"
return false;
}
if (token.name === "operator") {
switch (token.value) {
case "<":
case "<<":
bracketCountLeft[0] += token.value.length;
break;
case ">":
case ">>":
if (bracketCountLeft[0] === 0) {
return false;
}
bracketCountLeft[1] += token.value.length;
break;
case ".":
case "?":
case "&":
//allows generic method invocations, like "Foo" in "foo.Resolve<Foo>()"
//and <? extends Bar & Baz>
break;
default:
return false;
}
continue;
}
if (
(token.name === "keyword" && sunlight.util.contains(acceptableKeywords, token.value))
|| token.name === "default"
|| (token.name === "punctuation" && token.value === ",")
) {
continue;
}
if (token.name === "ident") {
foundIdent = true;
continue;
}
//anything else means we're no longer in a generic definition
break;
}
if (!foundIdent || bracketCountLeft[0] === 0) {
//not inside a generic definition
return false;
}
//now look forward to make sure the generic definition is closed
//this avoids false positives like "foo < bar"
index = context.index;
while ((token = context.tokens[++index]) !== undefined) {
if (token.name === "operator" && (token.value === ">" || token.value === ">>")) {
return true;
}
if (
(token.name === "keyword" && sunlight.util.contains(acceptableKeywords, token.value))
|| (token.name === "operator" && sunlight.util.contains(["<", "<<", ">", ">>"], token.value))
|| (token.name === "punctuation" && token.value === ",")
|| token.name === "ident"
|| token.name === "default"
) {
continue;
}
return false;
}
return false;
}),
//generic declarations and return values (ident preceding a generic definition)
//this finds "Foo" in "Foo<Bar> foo"
createNamedIdentFunction(function(context) {
//if it's preceded by an ident or a primitive/alias keyword then it's no good (i.e. a generic method definition like "public void Foo<T>")
//also a big fail if it is preceded by a ., i.e. a generic method invocation like container.Resolve()
var token = sunlight.util.getPreviousNonWsToken(context.tokens, context.index),
index,
bracketCount;
if (token !== undefined) {
if (
token.name === "ident"
|| (token.name === "keyword" && sunlight.util.contains(primitives.concat(["void"]), token.value))
|| (token.name === "operator" && token.value === ".")
) {
return false;
}
}
//needs to be immediately followed by <, then by idents or ?, acceptable keywords and ",", and then closed by >, then immediately followed by an ident
token = sunlight.util.getNextNonWsToken(context.tokens, context.index);
if (!token || token.name !== "operator" || token.value !== "<") {
return false;
}
index = context.index;
bracketCount = [0, 0]; //open (<), close (>)
while ((token = context.tokens[++index]) !== undefined) {
if (token.name === "operator") {
switch (token.value) {
case "<":
bracketCount[0]++;
break;
case "<<":
bracketCount[0] += 2;
break;
case ">":
bracketCount[1]++;
break;
case ">>":
bracketCount[1] += 2;
break;
case "?":
case "&":
//e.g. Foo<? extends Bar & Baz>
break;
default:
return false;
}
//if bracket counts match, get the f out
if (bracketCount[0] > 0 && bracketCount[0] === bracketCount[1]) {
break;
}
continue;
}
if (
token.name === "default"
|| token.name === "ident"
|| (token.name === "keyword" && sunlight.util.contains(acceptableKeywords, token.value))
|| (token.name === "punctuation" && token.value === ",")
) {
continue;
}
return false;
}
//verify bracket count
if (bracketCount[0] !== bracketCount[1]) {
//mismatched generics, could be something scary
return false;
}
//next token should be optional whitespace followed by an ident
token = context.tokens[++index];
if (!token || (token.name !== "default" && token.name !== "ident")) {
return false;
}
if (token.name === "default") {
token = context.tokens[++index];
if (!token || token.name !== "ident") {
return false;
}
}
return true;
}),
//fully qualified type names
createNamedIdentFunction(function(context) {
//next token is not "."
var nextToken = sunlight.util.getNextNonWsToken(context.tokens, context.index),
token,
index,
previous;
if (nextToken && nextToken.name === "operator" && nextToken.value === ".") {
return false;
}
//go backward and make sure that there are only idents and dots before the new keyword
index = context.index;
previous = context.tokens[index];
while ((token = context.tokens[--index]) !== undefined) {
if (token.name === "keyword" && (token.value === "new" || token.value === "import" || token.value === "instanceof")) {
return true;
}
if (token.name === "default") {
continue;
}
if (token.name === "ident") {
if (previous && previous.name === "ident") {
return false;
}
previous = token;
continue;
}
if (token.name === "operator" && token.value === ".") {
if (previous && previous.name !== "ident") {
return false;
}
previous = token;
continue;
}
break;
}
return false;
}),
//casting
function() {
var precedes = [
[sunlight.util.whitespace, { token: "punctuation", values: [")"] }, sunlight.util.whitespace, { token: "ident" }],
[sunlight.util.whitespace, { token: "punctuation", values: [")"] }, sunlight.util.whitespace, { token: "keyword", values: ["this"] }]
];
return createNamedIdentFunction(function(context) {
var token,
index,
prevToken,
precedesIsSatisfied = function(tokens) {
for (var i = 0; i < precedes.length; i++) {
if (sunlight.util.createProceduralRule(context.index + 1, 1, precedes[i], false)(tokens)) {
return true;
}
}
return false;
}(context.tokens);
if (!precedesIsSatisfied) {
return false;
}
//make sure the previous tokens are "(" and then not a keyword
//this'll make sure that things like "if (foo) doSomething();" won't color "foo"
index = context.index;
while (token = context.tokens[--index]) {
if (token.name === "punctuation" && token.value === "(") {
prevToken = sunlight.util.getPreviousNonWsToken(context.tokens, index);
if (prevToken && prevToken.name === "keyword") {
return false;
}
return true;
}
}
return false;
});
}(),
//can't use the follows/precedes/between utilities since we need to verify that it doesn't match the type definition naming convention
createNamedIdentFunction(function(context) {
var i,
follows = [
[{ token: "ident" }, sunlight.util.whitespace, { token: "keyword", values: ["extends", "implements"] }, sunlight.util.whitespace],
//method/property return values
//class/interface names
[{ token: "keyword", values: ["class", "interface", "enum", "public", "private", "protected", "static", "final"] }, sunlight.util.whitespace],
//bounded generic interface constraints
//this matches "MyInterface" in "public <T extends Object & MyInterface>..."
[
{ token: "keyword", values: ["extends"] },
{ token: "default" },
{ token: "ident" },
{ token: "default" },
{ token: "operator", values: ["&"] },
{ token: "default" }
]
],
precedes = [
//arrays
[sunlight.util.whitespace, { token: "punctuation", values: ["["] }, sunlight.util.whitespace, { token: "punctuation", values: ["]"] }], //in method parameters
//assignment: Object o = new object();
//method parameters: public int Foo(Foo foo, Bar b, Object o) { }
[{ token: "default" }, { token: "ident" }]
],
between = [
{ opener: { token: "keyword", values: ["implements", "throws"] }, closer: { token: "punctuation", values: ["{"] } }
];
for (i = 0; i < follows.length; i++) {
if (sunlight.util.createProceduralRule(context.index - 1, -1, follows[i], false)(context.tokens)) {
return true;
}
}
for (i = 0; i < precedes.length; i++) {
if (sunlight.util.createProceduralRule(context.index + 1, 1, precedes[i], false)(context.tokens)) {
return true;
}
}
for (i = 0; i < between.length; i++) {
if (sunlight.util.createBetweenRule(context.index, between[i].opener, between[i].closer, false)(context.tokens)) {
return true;
}
}
return false;
})
]
},
operators: [
//arithmetic
"++", "+=", "+",
"--", "-=", "-",
"*=", "*",
"/=", "/",
"%=", "%",
//boolean
"&&", "||",
//bitwise
"|=", "|",
"&=", "&",
"^=", "^",
">>>=", ">>>", ">>=", ">>",
"<<=", "<<",
//inequality
"<=", "<",
">=", ">",
"==", "!=",
//unary
"!", "~",
//other
"?", ":", ".", "="
]
});
}(this["Sunlight"]));