UNPKG

ink-docstrap

Version:

[![NPM](https://nodei.co/npm/ink-docstrap.png?downloads=true)](https://nodei.co/npm/ink-docstrap/)

784 lines (644 loc) 24.3 kB
(function(sunlight, undefined){ if (sunlight === undefined || sunlight["registerLanguage"] === undefined) { throw "Include sunlight.js before including language files"; } var primitives = ["int", "bool", "double", "float", "char", "byte", "sbyte", "uint", "long", "ulong", "char", "decimal", "short", "ushort"], //things that are allowed inside a generic type definition acceptableKeywords = primitives.concat(["in", "out", "string", "object"]); function createNamedIdentFunction(func) { var typeDefinitionRegex = /^T([A-Z0-9]\w*)?$/; return function(context) { return !typeDefinitionRegex.test(context.tokens[context.index].value) && func(context); }; } sunlight.registerLanguage("csharp", { keywords: primitives.concat([ //this is also contextual (must be first thing in the file or something), but it's never used so i don't really care "extern alias", //class qualifiers "public", "private", "protected", "internal", "static", "sealed", "abstract", "partial", //method qualifiers "virtual", "override", "new", "implicit", "explicit", "extern", "override", "operator", //member qualifiers "const", "readonly", "volatile", //types "class", "interface", "enum", "struct", "event", "delegate", //literals "null", "true", "false", //aliases "string", "object", "void", //looping "for", "foreach", "do", "while", //scoping "fixed", "unchecked", "using", "lock", "namespace", "checked", "unsafe", //flow control "if", "else", "try", "catch", "finally", "break", "continue", "goto", "case", "throw", "return", "switch", "yield return", "yield break", //parameter qualifiers "in", "out", "ref", "params", //type comparison "as", "is", "typeof", //other "this", "sizeof", "stackalloc", "var", "default", //contextual keywords //property stuff //get, set and value are handled by customParseRules below //linq "from", "select", "where", "groupby", "orderby" ]), customParseRules: [ //xml doc comments function(context) { var metaName = "xmlDocCommentMeta", //tags and the "///" starting token contentName = "xmlDocCommentContent", //actual comments (words and stuff) tokens, peek, current = { line: 0, column: 0, value: "", name: null }; if (context.reader.current() !== "/" || context.reader.peek(2) !== "//") { return null; } tokens = [context.createToken(metaName, "///", context.reader.getLine(), context.reader.getColumn())]; context.reader.read(2); while ((peek = context.reader.peek()) !== context.reader.EOF) { if (peek === "<" && current.name !== metaName) { //push the current token if (current.value !== "") { tokens.push(context.createToken(current.name, current.value, current.line, current.column)); } //amd create a token for the tag current.line = context.reader.getLine(); current.column = context.reader.getColumn(); current.name = metaName; current.value = context.reader.read(); continue; } if (peek === ">" && current.name === metaName) { //close the tag current.value += context.reader.read(); tokens.push(context.createToken(current.name, current.value, current.line, current.column)); current.name = null; current.value = ""; continue; } if (peek === "\n") { break; } if (current.name === null) { current.name = contentName; current.line = context.reader.getLine(); current.column = context.reader.getColumn(); } current.value += context.reader.read(); } if (current.name === contentName) { tokens.push(context.createToken(current.name, current.value, current.line, current.column)); } return tokens.length > 0 ? tokens : null; }, //get/set contextual keyword function(context) { var rule, count, peek, allGoodYo = false, line = context.reader.getLine(), column = context.reader.getColumn(), value; if (!/^(get|set)\b/.test(context.reader.current() + context.reader.peek(3))) { return null; } rule = sunlight.util.createProceduralRule(context.count() - 1, -1, [ { token: "punctuation", values: ["}", "{", ";"] }, sunlight.util.whitespace, { token: "keyword", values: ["public", "private", "protected", "internal"], optional: true } ]); if (!rule(context.getAllTokens())) { return null; } //now we need to look ahead and verify that the next non-sunlight.util.whitespace token is "{" or ";" count = "get".length; peek = context.reader.peek(count); while (peek.length === count) { if (!/\s$/.test(peek)) { if (!/[\{;]$/.test(peek)) { return null; } allGoodYo = true; break; } peek = context.reader.peek(++count); } if (!allGoodYo) { return null; } value = context.reader.current() + context.reader.read(2); //we already read the first letter return context.createToken("keyword", value, line, column); }, //value contextual keyword function(context) { var count, peek, allGoodYo, token, index, bracketCount, line = context.reader.getLine(), column = context.reader.getColumn(), peekPlus1; if (!/^value\b/.test(context.reader.current() + context.reader.peek(5))) { return null; } //comes after "set" but not after the closing "}" (we'll have to count them to make sure scoping is correct) //can't be on the left side of an assignment //first check equals because that's easy count = "value".length; peek = context.reader.peek(count); while (peek.length === count) { if (!/\s$/.test(peek)) { peekPlus1 = context.reader.peek(count + 1); if (peek.charAt(peek.length - 1) === "=" && peekPlus1.charAt(peekPlus1.length - 1) !== "=") { //"value" is on the left side of an assignment, so this is not the droid we're looking for return null; } allGoodYo = true; break; } peek = context.reader.peek(++count); } if (!allGoodYo) { //EOF FTL return null; } //now go backward until we run into a "set" keyword, keeping track of all brackets along the way index = context.count() -1; bracketCount = [0, 0]; //open, close tokenLoop: while ((token = context.token(index--)) !== undefined) { if (token.name === "punctuation") { if (token.value === "{") { bracketCount[0]++; } else if (token.value === "}") { bracketCount[1]++; } } else if (token.name === "keyword") { switch (token.value) { case "set": //yay! break tokenLoop; case "class": case "public": case "private": case "protected": case "internal": //easiest way to detect we're out of scope so we can stop looking return null; } } } if (token === undefined) { //EOF FTL return null; } //examine the bracket count and make sure we're in the correct scope if (bracketCount[1] >= bracketCount[0]) { //nope return null; } context.reader.read(4); //already read the "v" in "value" return context.createToken("keyword", "value", line, column); } ], scopes: { string: [ ["\"", "\"", sunlight.util.escapeSequences.concat(["\\\""])], ["@\"", "\"", ["\"\""]] ], "char": [ ["'", "'", ["\\'", "\\\\"]] ], comment: [ ["//", "\n", null, true], ["/*", "*/"] ], pragma: [ ["#", "\n", null, true] ] }, identFirstLetter: /[A-Za-z_@]/, identAfterFirstLetter: /\w/, namedIdentRules: { custom: [ //extends/implements/type constraints createNamedIdentFunction(function(context) { var index = context.index, token, foundColon = false, nextToken; //between ":" and "{" but not case statements //look backward for a ":" not preceded by a "case" while ((token = context.tokens[--index]) !== undefined) { if (token.name === "punctuation" && token.value === "{") { return false; } if (token.name === "keyword" && token.value === "case") { return false; } if (token.name === "keyword" && (token.value === "class" || token.value === "where")) { //the "class" keyword for classes //or the "where" keyword for generic methods/classes with type constraints //if "class" is used as a type constraint, then ignore it nextToken = context.tokens[index + 1].name === "default" ? context.tokens[index + 2] : context.tokens[index + 1]; if (nextToken.name === "punctuation" && nextToken.value === ",") { continue; } break; } if (token.name === "operator" && token.value === ":") { //make sure there isn't a case preceding it foundColon = true; } } if (!foundColon) { return false; } return true; }), //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; continue; case ">": case ">>": if (bracketCountLeft[0] === 0) { return false; } bracketCountLeft[1] += token.value.length; continue; } break; } 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) { var token = sunlight.util.getPreviousNonWsToken(context.tokens, context.index), index, bracketCount; //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() if (token !== undefined) { if ( token.name === "ident" || (token.name === "keyword" && sunlight.util.contains(primitives.concat(["string", "object", "void"]), token.value)) || (token.name === "operator" && token.value === ".") ) { return false; } } //needs to be immediately followed by <, then by idents, 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; default: return false; } //if bracket counts match, get the f out if (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; }), //using aliases, e.g. "Foo" in "using Foo = System.Linq.Enumerable;" function(context) { //previous non-ws token must be "using" and next non-ws token must be "=" var prevToken = sunlight.util.getPreviousNonWsToken(context.tokens, context.index), nextToken; if (!prevToken || prevToken.name !== "keyword" || prevToken.value !== "using") { return false; } nextToken = sunlight.util.getNextNonWsToken(context.tokens, context.index); if (!nextToken || nextToken.name !== "operator" || nextToken.value !== "=") { return false; } return true; }, //attributes (friggin' attributes...) createNamedIdentFunction(function(context) { var token = sunlight.util.getNextNonWsToken(context.tokens, context.index), index, bracketCount, foundComma = false, indexOfLastBracket; //if the next token is an equals sign, this is a named parameter (or something else not inside of an attribute) if (token && token.name === "operator" && (token.value === "=" || token.value === ".")) { return false; } //this is annoyingly complicated //we need to verify that we're between [], but not in something like "new string[foo]" for instance //first, verify that we're inside an opening bracket index = context.index; bracketCount = [0, 0]; foundComma = false; while ((token = context.tokens[--index]) !== undefined) { if (token.name === "punctuation") { if (token.value === "[") { bracketCount[0]++; continue; } if (token.value === "]") { bracketCount[1]++; continue; } if (token.value === ",") { foundComma = true; } //exit rules if (token.value === "{" || token.value === "}" || token.value === ";") { break; } } } if (bracketCount[0] === 0 || bracketCount[0] === bracketCount[1]) { //if no brackets were found OR... //all the found brackets are closed, so this ident is actually outside of the brackets //duh. return false; } //next, verify we're inside a closing bracket index = context.index; indexOfLastBracket = -1; while ((token = context.tokens[++index]) !== undefined) { if (token.name === "punctuation") { if (token.value === "[") { bracketCount[0]++; continue; } if (token.value === "]") { indexOfLastBracket = index; bracketCount[1]++; continue; } //some early exit rules if (token.value === "{" || token.value === "}" || token.value === ";") { break; } } } if (indexOfLastBracket < 0 || bracketCount[0] !== bracketCount[1]) { return false; } //next token after the last closing bracket should be either a keyword or an ident token = sunlight.util.getNextNonWsToken(context.tokens, indexOfLastBracket); if (token && (token.name === "keyword" || token.name === "ident")) { return true; } return false; }), //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 //"previous" is used to make sure that method declarations like "public new Object Value()..." are treated correctly index = context.index; previous = context.tokens[index]; while ((token = context.tokens[--index]) !== undefined) { if (token.name === "keyword" && (token.value === "new" || token.value === "is")) { 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; }); }(), //using alias type names, e.g. "Foo" in "using Bar = My.Namespace.Foo;" function(context) { var nextToken = sunlight.util.getNextNonWsToken(context.tokens, context.index), token, index; if (!nextToken || nextToken.name !== "punctuation" || nextToken.value !== ";") { return false; } //should be preceded by idents and dots, and then "using =" index = context.index; while (token = context.tokens[--index]) { if (token.name !== "ident" && token.name !== "default" && (token.name !== "operator" || token.value !== ".")) { //should be an equals sign, and then an ident and then "using" if (token.name !== "operator" || token.value !== "=") { return false; } return sunlight.util.createProceduralRule( index - 1, -1, [{ token: "keyword", values: ["using"] }, { token: "default" }, { token: "ident" }, sunlight.util.whitespace] )(context.tokens); } } return false; }, //can't use the follows/precedes utilities since we need to verify that it doesn't match the type definition naming convention createNamedIdentFunction(function(context) { var i, follows = [ //method/property return values //special method parameters //new: public new Foo Method() { } and new Foo(); //class/interface/event/struct/delegate names [{ token: "keyword", values: ["class", "interface", "event", "struct", "enum", "delegate", "public", "private", "protected", "internal", "static", "virtual", "sealed", "params"] }, sunlight.util.whitespace], //typeof/default [{ token: "keyword", values: ["typeof", "default"] }, sunlight.util.whitespace, { token: "punctuation", values: ["("] }, sunlight.util.whitespace ], //casting using "as" [{ token: "keyword", values: ["as"] }, sunlight.util.whitespace ] ], 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" }] ]; 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; } } return false; }) ] }, operators: [ //arithmetic "++", "+=", "+", "--", "-=", "-", "*=", "*", "/=", "/", "%=", "%", //boolean "&&", "||", //bitwise "|=", "|", "&=", "&", "^=", "^", ">>=", ">>", "<<=", "<<", //inequality "<=", "<", ">=", ">", "==", "!=", //unary "!", "~", //other "??", "?", "::", ":", ".", "=>", "=" ] }); }(this["Sunlight"]));