github.com/elliott5/community@v0.14.1-0.20160709191136-823126fb026a/app/public/codemirror/mode/crystal/crystal.js (about) 1 // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 // Distributed under an MIT license: http://codemirror.net/LICENSE 3 4 (function(mod) { 5 if (typeof exports == "object" && typeof module == "object") // CommonJS 6 mod(require("../../lib/codemirror")); 7 else if (typeof define == "function" && define.amd) // AMD 8 define(["../../lib/codemirror"], mod); 9 else // Plain browser env 10 mod(CodeMirror); 11 })(function(CodeMirror) { 12 "use strict"; 13 14 CodeMirror.defineMode("crystal", function(config) { 15 function wordRegExp(words, end) { 16 return new RegExp((end ? "" : "^") + "(?:" + words.join("|") + ")" + (end ? "$" : "\\b")); 17 } 18 19 function chain(tokenize, stream, state) { 20 state.tokenize.push(tokenize); 21 return tokenize(stream, state); 22 } 23 24 var operators = /^(?:[-+/%|&^]|\*\*?|[<>]{2})/; 25 var conditionalOperators = /^(?:[=!]~|===|<=>|[<>=!]=?|[|&]{2}|~)/; 26 var indexingOperators = /^(?:\[\][?=]?)/; 27 var anotherOperators = /^(?:\.(?:\.{2})?|->|[?:])/; 28 var idents = /^[a-z_\u009F-\uFFFF][a-zA-Z0-9_\u009F-\uFFFF]*/; 29 var types = /^[A-Z_\u009F-\uFFFF][a-zA-Z0-9_\u009F-\uFFFF]*/; 30 var keywords = wordRegExp([ 31 "abstract", "alias", "as", "asm", "begin", "break", "case", "class", "def", "do", 32 "else", "elsif", "end", "ensure", "enum", "extend", "for", "fun", "if", "ifdef", 33 "include", "instance_sizeof", "lib", "macro", "module", "next", "of", "out", "pointerof", 34 "private", "protected", "rescue", "return", "require", "sizeof", "struct", 35 "super", "then", "type", "typeof", "union", "unless", "until", "when", "while", "with", 36 "yield", "__DIR__", "__FILE__", "__LINE__" 37 ]); 38 var atomWords = wordRegExp(["true", "false", "nil", "self"]); 39 var indentKeywordsArray = [ 40 "def", "fun", "macro", 41 "class", "module", "struct", "lib", "enum", "union", 42 "if", "unless", "case", "while", "until", "begin", "then", 43 "do", 44 "for", "ifdef" 45 ]; 46 var indentKeywords = wordRegExp(indentKeywordsArray); 47 var dedentKeywordsArray = [ 48 "end", 49 "else", "elsif", 50 "rescue", "ensure" 51 ]; 52 var dedentKeywords = wordRegExp(dedentKeywordsArray); 53 var dedentPunctualsArray = ["\\)", "\\}", "\\]"]; 54 var dedentPunctuals = new RegExp("^(?:" + dedentPunctualsArray.join("|") + ")$"); 55 var nextTokenizer = { 56 "def": tokenFollowIdent, "fun": tokenFollowIdent, "macro": tokenMacroDef, 57 "class": tokenFollowType, "module": tokenFollowType, "struct": tokenFollowType, 58 "lib": tokenFollowType, "enum": tokenFollowType, "union": tokenFollowType 59 }; 60 var matching = {"[": "]", "{": "}", "(": ")", "<": ">"}; 61 62 function tokenBase(stream, state) { 63 if (stream.eatSpace()) { 64 return null; 65 } 66 67 // Macros 68 if (state.lastToken != "\\" && stream.match("{%", false)) { 69 return chain(tokenMacro("%", "%"), stream, state); 70 } 71 72 if (state.lastToken != "\\" && stream.match("{{", false)) { 73 return chain(tokenMacro("{", "}"), stream, state); 74 } 75 76 // Comments 77 if (stream.peek() == "#") { 78 stream.skipToEnd(); 79 return "comment"; 80 } 81 82 // Variables and keywords 83 var matched; 84 if (stream.match(idents)) { 85 stream.eat(/[?!]/); 86 87 matched = stream.current(); 88 if (stream.eat(":")) { 89 return "atom"; 90 } else if (state.lastToken == ".") { 91 return "property"; 92 } else if (keywords.test(matched)) { 93 if (state.lastToken != "abstract" && indentKeywords.test(matched)) { 94 if (!(matched == "fun" && state.blocks.indexOf("lib") >= 0)) { 95 state.blocks.push(matched); 96 state.currentIndent += 1; 97 } 98 } else if (dedentKeywords.test(matched)) { 99 state.blocks.pop(); 100 state.currentIndent -= 1; 101 } 102 103 if (nextTokenizer.hasOwnProperty(matched)) { 104 state.tokenize.push(nextTokenizer[matched]); 105 } 106 107 return "keyword"; 108 } else if (atomWords.test(matched)) { 109 return "atom"; 110 } 111 112 return "variable"; 113 } 114 115 // Class variables and instance variables 116 // or attributes 117 if (stream.eat("@")) { 118 if (stream.peek() == "[") { 119 return chain(tokenNest("[", "]", "meta"), stream, state); 120 } 121 122 stream.eat("@"); 123 stream.match(idents) || stream.match(types); 124 return "variable-2"; 125 } 126 127 // Global variables 128 if (stream.eat("$")) { 129 stream.eat(/[0-9]+|\?/) || stream.match(idents) || stream.match(types); 130 return "variable-3"; 131 } 132 133 // Constants and types 134 if (stream.match(types)) { 135 return "tag"; 136 } 137 138 // Symbols or ':' operator 139 if (stream.eat(":")) { 140 if (stream.eat("\"")) { 141 return chain(tokenQuote("\"", "atom", false), stream, state); 142 } else if (stream.match(idents) || stream.match(types) || 143 stream.match(operators) || stream.match(conditionalOperators) || stream.match(indexingOperators)) { 144 return "atom"; 145 } 146 stream.eat(":"); 147 return "operator"; 148 } 149 150 // Strings 151 if (stream.eat("\"")) { 152 return chain(tokenQuote("\"", "string", true), stream, state); 153 } 154 155 // Strings or regexps or macro variables or '%' operator 156 if (stream.peek() == "%") { 157 var style = "string"; 158 var embed = true; 159 var delim; 160 161 if (stream.match("%r")) { 162 // Regexps 163 style = "string-2"; 164 delim = stream.next(); 165 } else if (stream.match("%w")) { 166 embed = false; 167 delim = stream.next(); 168 } else { 169 if(delim = stream.match(/^%([^\w\s=])/)) { 170 delim = delim[1]; 171 } else if (stream.match(/^%[a-zA-Z0-9_\u009F-\uFFFF]*/)) { 172 // Macro variables 173 return "meta"; 174 } else { 175 // '%' operator 176 return "operator"; 177 } 178 } 179 180 if (matching.hasOwnProperty(delim)) { 181 delim = matching[delim]; 182 } 183 return chain(tokenQuote(delim, style, embed), stream, state); 184 } 185 186 // Characters 187 if (stream.eat("'")) { 188 stream.match(/^(?:[^']|\\(?:[befnrtv0'"]|[0-7]{3}|u(?:[0-9a-fA-F]{4}|\{[0-9a-fA-F]{1,6}\})))/); 189 stream.eat("'"); 190 return "atom"; 191 } 192 193 // Numbers 194 if (stream.eat("0")) { 195 if (stream.eat("x")) { 196 stream.match(/^[0-9a-fA-F]+/); 197 } else if (stream.eat("o")) { 198 stream.match(/^[0-7]+/); 199 } else if (stream.eat("b")) { 200 stream.match(/^[01]+/); 201 } 202 return "number"; 203 } 204 205 if (stream.eat(/\d/)) { 206 stream.match(/^\d*(?:\.\d+)?(?:[eE][+-]?\d+)?/); 207 return "number"; 208 } 209 210 // Operators 211 if (stream.match(operators)) { 212 stream.eat("="); // Operators can follow assigin symbol. 213 return "operator"; 214 } 215 216 if (stream.match(conditionalOperators) || stream.match(anotherOperators)) { 217 return "operator"; 218 } 219 220 // Parens and braces 221 if (matched = stream.match(/[({[]/, false)) { 222 matched = matched[0]; 223 return chain(tokenNest(matched, matching[matched], null), stream, state); 224 } 225 226 // Escapes 227 if (stream.eat("\\")) { 228 stream.next(); 229 return "meta"; 230 } 231 232 stream.next(); 233 return null; 234 } 235 236 function tokenNest(begin, end, style, started) { 237 return function (stream, state) { 238 if (!started && stream.match(begin)) { 239 state.tokenize[state.tokenize.length - 1] = tokenNest(begin, end, style, true); 240 state.currentIndent += 1; 241 return style; 242 } 243 244 var nextStyle = tokenBase(stream, state); 245 if (stream.current() === end) { 246 state.tokenize.pop(); 247 state.currentIndent -= 1; 248 nextStyle = style; 249 } 250 251 return nextStyle; 252 }; 253 } 254 255 function tokenMacro(begin, end, started) { 256 return function (stream, state) { 257 if (!started && stream.match("{" + begin)) { 258 state.currentIndent += 1; 259 state.tokenize[state.tokenize.length - 1] = tokenMacro(begin, end, true); 260 return "meta"; 261 } 262 263 if (stream.match(end + "}")) { 264 state.currentIndent -= 1; 265 state.tokenize.pop(); 266 return "meta"; 267 } 268 269 return tokenBase(stream, state); 270 }; 271 } 272 273 function tokenMacroDef(stream, state) { 274 if (stream.eatSpace()) { 275 return null; 276 } 277 278 var matched; 279 if (matched = stream.match(idents)) { 280 if (matched == "def") { 281 return "keyword"; 282 } 283 stream.eat(/[?!]/); 284 } 285 286 state.tokenize.pop(); 287 return "def"; 288 } 289 290 function tokenFollowIdent(stream, state) { 291 if (stream.eatSpace()) { 292 return null; 293 } 294 295 if (stream.match(idents)) { 296 stream.eat(/[!?]/); 297 } else { 298 stream.match(operators) || stream.match(conditionalOperators) || stream.match(indexingOperators); 299 } 300 state.tokenize.pop(); 301 return "def"; 302 } 303 304 function tokenFollowType(stream, state) { 305 if (stream.eatSpace()) { 306 return null; 307 } 308 309 stream.match(types); 310 state.tokenize.pop(); 311 return "def"; 312 } 313 314 function tokenQuote(end, style, embed) { 315 return function (stream, state) { 316 var escaped = false; 317 318 while (stream.peek()) { 319 if (!escaped) { 320 if (stream.match("{%", false)) { 321 state.tokenize.push(tokenMacro("%", "%")); 322 return style; 323 } 324 325 if (stream.match("{{", false)) { 326 state.tokenize.push(tokenMacro("{", "}")); 327 return style; 328 } 329 330 if (embed && stream.match("#{", false)) { 331 state.tokenize.push(tokenNest("#{", "}", "meta")); 332 return style; 333 } 334 335 var ch = stream.next(); 336 337 if (ch == end) { 338 state.tokenize.pop(); 339 return style; 340 } 341 342 escaped = ch == "\\"; 343 } else { 344 stream.next(); 345 escaped = false; 346 } 347 } 348 349 return style; 350 }; 351 } 352 353 return { 354 startState: function () { 355 return { 356 tokenize: [tokenBase], 357 currentIndent: 0, 358 lastToken: null, 359 blocks: [] 360 }; 361 }, 362 363 token: function (stream, state) { 364 var style = state.tokenize[state.tokenize.length - 1](stream, state); 365 var token = stream.current(); 366 367 if (style && style != "comment") { 368 state.lastToken = token; 369 } 370 371 return style; 372 }, 373 374 indent: function (state, textAfter) { 375 textAfter = textAfter.replace(/^\s*(?:\{%)?\s*|\s*(?:%\})?\s*$/g, ""); 376 377 if (dedentKeywords.test(textAfter) || dedentPunctuals.test(textAfter)) { 378 return config.indentUnit * (state.currentIndent - 1); 379 } 380 381 return config.indentUnit * state.currentIndent; 382 }, 383 384 fold: "indent", 385 electricInput: wordRegExp(dedentPunctualsArray.concat(dedentKeywordsArray), true), 386 lineComment: '#' 387 }; 388 }); 389 390 CodeMirror.defineMIME("text/x-crystal", "crystal"); 391 });