github.com/jancarloviray/community@v0.41.1-0.20170124221257-33a66c87cf2f/app/public/codemirror/mode/erlang/erlang.js (about) 1 // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 // Distributed under an MIT license: http://codemirror.net/LICENSE 3 4 /*jshint unused:true, eqnull:true, curly:true, bitwise:true */ 5 /*jshint undef:true, latedef:true, trailing:true */ 6 /*global CodeMirror:true */ 7 8 // erlang mode. 9 // tokenizer -> token types -> CodeMirror styles 10 // tokenizer maintains a parse stack 11 // indenter uses the parse stack 12 13 // TODO indenter: 14 // bit syntax 15 // old guard/bif/conversion clashes (e.g. "float/1") 16 // type/spec/opaque 17 18 (function(mod) { 19 if (typeof exports == "object" && typeof module == "object") // CommonJS 20 mod(require("../../lib/codemirror")); 21 else if (typeof define == "function" && define.amd) // AMD 22 define(["../../lib/codemirror"], mod); 23 else // Plain browser env 24 mod(CodeMirror); 25 })(function(CodeMirror) { 26 "use strict"; 27 28 CodeMirror.defineMIME("text/x-erlang", "erlang"); 29 30 CodeMirror.defineMode("erlang", function(cmCfg) { 31 "use strict"; 32 33 ///////////////////////////////////////////////////////////////////////////// 34 // constants 35 36 var typeWords = [ 37 "-type", "-spec", "-export_type", "-opaque"]; 38 39 var keywordWords = [ 40 "after","begin","catch","case","cond","end","fun","if", 41 "let","of","query","receive","try","when"]; 42 43 var separatorRE = /[\->,;]/; 44 var separatorWords = [ 45 "->",";",","]; 46 47 var operatorAtomWords = [ 48 "and","andalso","band","bnot","bor","bsl","bsr","bxor", 49 "div","not","or","orelse","rem","xor"]; 50 51 var operatorSymbolRE = /[\+\-\*\/<>=\|:!]/; 52 var operatorSymbolWords = [ 53 "=","+","-","*","/",">",">=","<","=<","=:=","==","=/=","/=","||","<-","!"]; 54 55 var openParenRE = /[<\(\[\{]/; 56 var openParenWords = [ 57 "<<","(","[","{"]; 58 59 var closeParenRE = /[>\)\]\}]/; 60 var closeParenWords = [ 61 "}","]",")",">>"]; 62 63 var guardWords = [ 64 "is_atom","is_binary","is_bitstring","is_boolean","is_float", 65 "is_function","is_integer","is_list","is_number","is_pid", 66 "is_port","is_record","is_reference","is_tuple", 67 "atom","binary","bitstring","boolean","function","integer","list", 68 "number","pid","port","record","reference","tuple"]; 69 70 var bifWords = [ 71 "abs","adler32","adler32_combine","alive","apply","atom_to_binary", 72 "atom_to_list","binary_to_atom","binary_to_existing_atom", 73 "binary_to_list","binary_to_term","bit_size","bitstring_to_list", 74 "byte_size","check_process_code","contact_binary","crc32", 75 "crc32_combine","date","decode_packet","delete_module", 76 "disconnect_node","element","erase","exit","float","float_to_list", 77 "garbage_collect","get","get_keys","group_leader","halt","hd", 78 "integer_to_list","internal_bif","iolist_size","iolist_to_binary", 79 "is_alive","is_atom","is_binary","is_bitstring","is_boolean", 80 "is_float","is_function","is_integer","is_list","is_number","is_pid", 81 "is_port","is_process_alive","is_record","is_reference","is_tuple", 82 "length","link","list_to_atom","list_to_binary","list_to_bitstring", 83 "list_to_existing_atom","list_to_float","list_to_integer", 84 "list_to_pid","list_to_tuple","load_module","make_ref","module_loaded", 85 "monitor_node","node","node_link","node_unlink","nodes","notalive", 86 "now","open_port","pid_to_list","port_close","port_command", 87 "port_connect","port_control","pre_loaded","process_flag", 88 "process_info","processes","purge_module","put","register", 89 "registered","round","self","setelement","size","spawn","spawn_link", 90 "spawn_monitor","spawn_opt","split_binary","statistics", 91 "term_to_binary","time","throw","tl","trunc","tuple_size", 92 "tuple_to_list","unlink","unregister","whereis"]; 93 94 // upper case: [A-Z] [Ø-Þ] [À-Ö] 95 // lower case: [a-z] [ß-ö] [ø-ÿ] 96 var anumRE = /[\w@Ø-ÞÀ-Öß-öø-ÿ]/; 97 var escapesRE = 98 /[0-7]{1,3}|[bdefnrstv\\"']|\^[a-zA-Z]|x[0-9a-zA-Z]{2}|x{[0-9a-zA-Z]+}/; 99 100 ///////////////////////////////////////////////////////////////////////////// 101 // tokenizer 102 103 function tokenizer(stream,state) { 104 // in multi-line string 105 if (state.in_string) { 106 state.in_string = (!doubleQuote(stream)); 107 return rval(state,stream,"string"); 108 } 109 110 // in multi-line atom 111 if (state.in_atom) { 112 state.in_atom = (!singleQuote(stream)); 113 return rval(state,stream,"atom"); 114 } 115 116 // whitespace 117 if (stream.eatSpace()) { 118 return rval(state,stream,"whitespace"); 119 } 120 121 // attributes and type specs 122 if (!peekToken(state) && 123 stream.match(/-\s*[a-zß-öø-ÿ][\wØ-ÞÀ-Öß-öø-ÿ]*/)) { 124 if (is_member(stream.current(),typeWords)) { 125 return rval(state,stream,"type"); 126 }else{ 127 return rval(state,stream,"attribute"); 128 } 129 } 130 131 var ch = stream.next(); 132 133 // comment 134 if (ch == '%') { 135 stream.skipToEnd(); 136 return rval(state,stream,"comment"); 137 } 138 139 // colon 140 if (ch == ":") { 141 return rval(state,stream,"colon"); 142 } 143 144 // macro 145 if (ch == '?') { 146 stream.eatSpace(); 147 stream.eatWhile(anumRE); 148 return rval(state,stream,"macro"); 149 } 150 151 // record 152 if (ch == "#") { 153 stream.eatSpace(); 154 stream.eatWhile(anumRE); 155 return rval(state,stream,"record"); 156 } 157 158 // dollar escape 159 if (ch == "$") { 160 if (stream.next() == "\\" && !stream.match(escapesRE)) { 161 return rval(state,stream,"error"); 162 } 163 return rval(state,stream,"number"); 164 } 165 166 // dot 167 if (ch == ".") { 168 return rval(state,stream,"dot"); 169 } 170 171 // quoted atom 172 if (ch == '\'') { 173 if (!(state.in_atom = (!singleQuote(stream)))) { 174 if (stream.match(/\s*\/\s*[0-9]/,false)) { 175 stream.match(/\s*\/\s*[0-9]/,true); 176 return rval(state,stream,"fun"); // 'f'/0 style fun 177 } 178 if (stream.match(/\s*\(/,false) || stream.match(/\s*:/,false)) { 179 return rval(state,stream,"function"); 180 } 181 } 182 return rval(state,stream,"atom"); 183 } 184 185 // string 186 if (ch == '"') { 187 state.in_string = (!doubleQuote(stream)); 188 return rval(state,stream,"string"); 189 } 190 191 // variable 192 if (/[A-Z_Ø-ÞÀ-Ö]/.test(ch)) { 193 stream.eatWhile(anumRE); 194 return rval(state,stream,"variable"); 195 } 196 197 // atom/keyword/BIF/function 198 if (/[a-z_ß-öø-ÿ]/.test(ch)) { 199 stream.eatWhile(anumRE); 200 201 if (stream.match(/\s*\/\s*[0-9]/,false)) { 202 stream.match(/\s*\/\s*[0-9]/,true); 203 return rval(state,stream,"fun"); // f/0 style fun 204 } 205 206 var w = stream.current(); 207 208 if (is_member(w,keywordWords)) { 209 return rval(state,stream,"keyword"); 210 }else if (is_member(w,operatorAtomWords)) { 211 return rval(state,stream,"operator"); 212 }else if (stream.match(/\s*\(/,false)) { 213 // 'put' and 'erlang:put' are bifs, 'foo:put' is not 214 if (is_member(w,bifWords) && 215 ((peekToken(state).token != ":") || 216 (peekToken(state,2).token == "erlang"))) { 217 return rval(state,stream,"builtin"); 218 }else if (is_member(w,guardWords)) { 219 return rval(state,stream,"guard"); 220 }else{ 221 return rval(state,stream,"function"); 222 } 223 }else if (lookahead(stream) == ":") { 224 if (w == "erlang") { 225 return rval(state,stream,"builtin"); 226 } else { 227 return rval(state,stream,"function"); 228 } 229 }else if (is_member(w,["true","false"])) { 230 return rval(state,stream,"boolean"); 231 }else{ 232 return rval(state,stream,"atom"); 233 } 234 } 235 236 // number 237 var digitRE = /[0-9]/; 238 var radixRE = /[0-9a-zA-Z]/; // 36#zZ style int 239 if (digitRE.test(ch)) { 240 stream.eatWhile(digitRE); 241 if (stream.eat('#')) { // 36#aZ style integer 242 if (!stream.eatWhile(radixRE)) { 243 stream.backUp(1); //"36#" - syntax error 244 } 245 } else if (stream.eat('.')) { // float 246 if (!stream.eatWhile(digitRE)) { 247 stream.backUp(1); // "3." - probably end of function 248 } else { 249 if (stream.eat(/[eE]/)) { // float with exponent 250 if (stream.eat(/[-+]/)) { 251 if (!stream.eatWhile(digitRE)) { 252 stream.backUp(2); // "2e-" - syntax error 253 } 254 } else { 255 if (!stream.eatWhile(digitRE)) { 256 stream.backUp(1); // "2e" - syntax error 257 } 258 } 259 } 260 } 261 } 262 return rval(state,stream,"number"); // normal integer 263 } 264 265 // open parens 266 if (nongreedy(stream,openParenRE,openParenWords)) { 267 return rval(state,stream,"open_paren"); 268 } 269 270 // close parens 271 if (nongreedy(stream,closeParenRE,closeParenWords)) { 272 return rval(state,stream,"close_paren"); 273 } 274 275 // separators 276 if (greedy(stream,separatorRE,separatorWords)) { 277 return rval(state,stream,"separator"); 278 } 279 280 // operators 281 if (greedy(stream,operatorSymbolRE,operatorSymbolWords)) { 282 return rval(state,stream,"operator"); 283 } 284 285 return rval(state,stream,null); 286 } 287 288 ///////////////////////////////////////////////////////////////////////////// 289 // utilities 290 function nongreedy(stream,re,words) { 291 if (stream.current().length == 1 && re.test(stream.current())) { 292 stream.backUp(1); 293 while (re.test(stream.peek())) { 294 stream.next(); 295 if (is_member(stream.current(),words)) { 296 return true; 297 } 298 } 299 stream.backUp(stream.current().length-1); 300 } 301 return false; 302 } 303 304 function greedy(stream,re,words) { 305 if (stream.current().length == 1 && re.test(stream.current())) { 306 while (re.test(stream.peek())) { 307 stream.next(); 308 } 309 while (0 < stream.current().length) { 310 if (is_member(stream.current(),words)) { 311 return true; 312 }else{ 313 stream.backUp(1); 314 } 315 } 316 stream.next(); 317 } 318 return false; 319 } 320 321 function doubleQuote(stream) { 322 return quote(stream, '"', '\\'); 323 } 324 325 function singleQuote(stream) { 326 return quote(stream,'\'','\\'); 327 } 328 329 function quote(stream,quoteChar,escapeChar) { 330 while (!stream.eol()) { 331 var ch = stream.next(); 332 if (ch == quoteChar) { 333 return true; 334 }else if (ch == escapeChar) { 335 stream.next(); 336 } 337 } 338 return false; 339 } 340 341 function lookahead(stream) { 342 var m = stream.match(/([\n\s]+|%[^\n]*\n)*(.)/,false); 343 return m ? m.pop() : ""; 344 } 345 346 function is_member(element,list) { 347 return (-1 < list.indexOf(element)); 348 } 349 350 function rval(state,stream,type) { 351 352 // parse stack 353 pushToken(state,realToken(type,stream)); 354 355 // map erlang token type to CodeMirror style class 356 // erlang -> CodeMirror tag 357 switch (type) { 358 case "atom": return "atom"; 359 case "attribute": return "attribute"; 360 case "boolean": return "atom"; 361 case "builtin": return "builtin"; 362 case "close_paren": return null; 363 case "colon": return null; 364 case "comment": return "comment"; 365 case "dot": return null; 366 case "error": return "error"; 367 case "fun": return "meta"; 368 case "function": return "tag"; 369 case "guard": return "property"; 370 case "keyword": return "keyword"; 371 case "macro": return "variable-2"; 372 case "number": return "number"; 373 case "open_paren": return null; 374 case "operator": return "operator"; 375 case "record": return "bracket"; 376 case "separator": return null; 377 case "string": return "string"; 378 case "type": return "def"; 379 case "variable": return "variable"; 380 default: return null; 381 } 382 } 383 384 function aToken(tok,col,ind,typ) { 385 return {token: tok, 386 column: col, 387 indent: ind, 388 type: typ}; 389 } 390 391 function realToken(type,stream) { 392 return aToken(stream.current(), 393 stream.column(), 394 stream.indentation(), 395 type); 396 } 397 398 function fakeToken(type) { 399 return aToken(type,0,0,type); 400 } 401 402 function peekToken(state,depth) { 403 var len = state.tokenStack.length; 404 var dep = (depth ? depth : 1); 405 406 if (len < dep) { 407 return false; 408 }else{ 409 return state.tokenStack[len-dep]; 410 } 411 } 412 413 function pushToken(state,token) { 414 415 if (!(token.type == "comment" || token.type == "whitespace")) { 416 state.tokenStack = maybe_drop_pre(state.tokenStack,token); 417 state.tokenStack = maybe_drop_post(state.tokenStack); 418 } 419 } 420 421 function maybe_drop_pre(s,token) { 422 var last = s.length-1; 423 424 if (0 < last && s[last].type === "record" && token.type === "dot") { 425 s.pop(); 426 }else if (0 < last && s[last].type === "group") { 427 s.pop(); 428 s.push(token); 429 }else{ 430 s.push(token); 431 } 432 return s; 433 } 434 435 function maybe_drop_post(s) { 436 var last = s.length-1; 437 438 if (s[last].type === "dot") { 439 return []; 440 } 441 if (s[last].type === "fun" && s[last-1].token === "fun") { 442 return s.slice(0,last-1); 443 } 444 switch (s[s.length-1].token) { 445 case "}": return d(s,{g:["{"]}); 446 case "]": return d(s,{i:["["]}); 447 case ")": return d(s,{i:["("]}); 448 case ">>": return d(s,{i:["<<"]}); 449 case "end": return d(s,{i:["begin","case","fun","if","receive","try"]}); 450 case ",": return d(s,{e:["begin","try","when","->", 451 ",","(","[","{","<<"]}); 452 case "->": return d(s,{r:["when"], 453 m:["try","if","case","receive"]}); 454 case ";": return d(s,{E:["case","fun","if","receive","try","when"]}); 455 case "catch":return d(s,{e:["try"]}); 456 case "of": return d(s,{e:["case"]}); 457 case "after":return d(s,{e:["receive","try"]}); 458 default: return s; 459 } 460 } 461 462 function d(stack,tt) { 463 // stack is a stack of Token objects. 464 // tt is an object; {type:tokens} 465 // type is a char, tokens is a list of token strings. 466 // The function returns (possibly truncated) stack. 467 // It will descend the stack, looking for a Token such that Token.token 468 // is a member of tokens. If it does not find that, it will normally (but 469 // see "E" below) return stack. If it does find a match, it will remove 470 // all the Tokens between the top and the matched Token. 471 // If type is "m", that is all it does. 472 // If type is "i", it will also remove the matched Token and the top Token. 473 // If type is "g", like "i", but add a fake "group" token at the top. 474 // If type is "r", it will remove the matched Token, but not the top Token. 475 // If type is "e", it will keep the matched Token but not the top Token. 476 // If type is "E", it behaves as for type "e", except if there is no match, 477 // in which case it will return an empty stack. 478 479 for (var type in tt) { 480 var len = stack.length-1; 481 var tokens = tt[type]; 482 for (var i = len-1; -1 < i ; i--) { 483 if (is_member(stack[i].token,tokens)) { 484 var ss = stack.slice(0,i); 485 switch (type) { 486 case "m": return ss.concat(stack[i]).concat(stack[len]); 487 case "r": return ss.concat(stack[len]); 488 case "i": return ss; 489 case "g": return ss.concat(fakeToken("group")); 490 case "E": return ss.concat(stack[i]); 491 case "e": return ss.concat(stack[i]); 492 } 493 } 494 } 495 } 496 return (type == "E" ? [] : stack); 497 } 498 499 ///////////////////////////////////////////////////////////////////////////// 500 // indenter 501 502 function indenter(state,textAfter) { 503 var t; 504 var unit = cmCfg.indentUnit; 505 var wordAfter = wordafter(textAfter); 506 var currT = peekToken(state,1); 507 var prevT = peekToken(state,2); 508 509 if (state.in_string || state.in_atom) { 510 return CodeMirror.Pass; 511 }else if (!prevT) { 512 return 0; 513 }else if (currT.token == "when") { 514 return currT.column+unit; 515 }else if (wordAfter === "when" && prevT.type === "function") { 516 return prevT.indent+unit; 517 }else if (wordAfter === "(" && currT.token === "fun") { 518 return currT.column+3; 519 }else if (wordAfter === "catch" && (t = getToken(state,["try"]))) { 520 return t.column; 521 }else if (is_member(wordAfter,["end","after","of"])) { 522 t = getToken(state,["begin","case","fun","if","receive","try"]); 523 return t ? t.column : CodeMirror.Pass; 524 }else if (is_member(wordAfter,closeParenWords)) { 525 t = getToken(state,openParenWords); 526 return t ? t.column : CodeMirror.Pass; 527 }else if (is_member(currT.token,[",","|","||"]) || 528 is_member(wordAfter,[",","|","||"])) { 529 t = postcommaToken(state); 530 return t ? t.column+t.token.length : unit; 531 }else if (currT.token == "->") { 532 if (is_member(prevT.token, ["receive","case","if","try"])) { 533 return prevT.column+unit+unit; 534 }else{ 535 return prevT.column+unit; 536 } 537 }else if (is_member(currT.token,openParenWords)) { 538 return currT.column+currT.token.length; 539 }else{ 540 t = defaultToken(state); 541 return truthy(t) ? t.column+unit : 0; 542 } 543 } 544 545 function wordafter(str) { 546 var m = str.match(/,|[a-z]+|\}|\]|\)|>>|\|+|\(/); 547 548 return truthy(m) && (m.index === 0) ? m[0] : ""; 549 } 550 551 function postcommaToken(state) { 552 var objs = state.tokenStack.slice(0,-1); 553 var i = getTokenIndex(objs,"type",["open_paren"]); 554 555 return truthy(objs[i]) ? objs[i] : false; 556 } 557 558 function defaultToken(state) { 559 var objs = state.tokenStack; 560 var stop = getTokenIndex(objs,"type",["open_paren","separator","keyword"]); 561 var oper = getTokenIndex(objs,"type",["operator"]); 562 563 if (truthy(stop) && truthy(oper) && stop < oper) { 564 return objs[stop+1]; 565 } else if (truthy(stop)) { 566 return objs[stop]; 567 } else { 568 return false; 569 } 570 } 571 572 function getToken(state,tokens) { 573 var objs = state.tokenStack; 574 var i = getTokenIndex(objs,"token",tokens); 575 576 return truthy(objs[i]) ? objs[i] : false; 577 } 578 579 function getTokenIndex(objs,propname,propvals) { 580 581 for (var i = objs.length-1; -1 < i ; i--) { 582 if (is_member(objs[i][propname],propvals)) { 583 return i; 584 } 585 } 586 return false; 587 } 588 589 function truthy(x) { 590 return (x !== false) && (x != null); 591 } 592 593 ///////////////////////////////////////////////////////////////////////////// 594 // this object defines the mode 595 596 return { 597 startState: 598 function() { 599 return {tokenStack: [], 600 in_string: false, 601 in_atom: false}; 602 }, 603 604 token: 605 function(stream, state) { 606 return tokenizer(stream, state); 607 }, 608 609 indent: 610 function(state, textAfter) { 611 return indenter(state,textAfter); 612 }, 613 614 lineComment: "%" 615 }; 616 }); 617 618 });