File modules/editor/codemirror/mode/erlang/erlang.js
Last commit: Sun Dec 17 01:14:09 2017 +0100 Jan Dankert Integration eines weiteren Code-Editors: Codemirror. Demnächst müssen wir hier mal aufräumen und andere Editoren rauswerfen.
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 if (!s.length) return s 437 var last = s.length-1; 438 439 if (s[last].type === "dot") { 440 return []; 441 } 442 if (last > 1 && s[last].type === "fun" && s[last-1].token === "fun") { 443 return s.slice(0,last-1); 444 } 445 switch (s[last].token) { 446 case "}": return d(s,{g:["{"]}); 447 case "]": return d(s,{i:["["]}); 448 case ")": return d(s,{i:["("]}); 449 case ">>": return d(s,{i:["<<"]}); 450 case "end": return d(s,{i:["begin","case","fun","if","receive","try"]}); 451 case ",": return d(s,{e:["begin","try","when","->", 452 ",","(","[","{","<<"]}); 453 case "->": return d(s,{r:["when"], 454 m:["try","if","case","receive"]}); 455 case ";": return d(s,{E:["case","fun","if","receive","try","when"]}); 456 case "catch":return d(s,{e:["try"]}); 457 case "of": return d(s,{e:["case"]}); 458 case "after":return d(s,{e:["receive","try"]}); 459 default: return s; 460 } 461 } 462 463 function d(stack,tt) { 464 // stack is a stack of Token objects. 465 // tt is an object; {type:tokens} 466 // type is a char, tokens is a list of token strings. 467 // The function returns (possibly truncated) stack. 468 // It will descend the stack, looking for a Token such that Token.token 469 // is a member of tokens. If it does not find that, it will normally (but 470 // see "E" below) return stack. If it does find a match, it will remove 471 // all the Tokens between the top and the matched Token. 472 // If type is "m", that is all it does. 473 // If type is "i", it will also remove the matched Token and the top Token. 474 // If type is "g", like "i", but add a fake "group" token at the top. 475 // If type is "r", it will remove the matched Token, but not the top Token. 476 // If type is "e", it will keep the matched Token but not the top Token. 477 // If type is "E", it behaves as for type "e", except if there is no match, 478 // in which case it will return an empty stack. 479 480 for (var type in tt) { 481 var len = stack.length-1; 482 var tokens = tt[type]; 483 for (var i = len-1; -1 < i ; i--) { 484 if (is_member(stack[i].token,tokens)) { 485 var ss = stack.slice(0,i); 486 switch (type) { 487 case "m": return ss.concat(stack[i]).concat(stack[len]); 488 case "r": return ss.concat(stack[len]); 489 case "i": return ss; 490 case "g": return ss.concat(fakeToken("group")); 491 case "E": return ss.concat(stack[i]); 492 case "e": return ss.concat(stack[i]); 493 } 494 } 495 } 496 } 497 return (type == "E" ? [] : stack); 498 } 499 500 ///////////////////////////////////////////////////////////////////////////// 501 // indenter 502 503 function indenter(state,textAfter) { 504 var t; 505 var unit = cmCfg.indentUnit; 506 var wordAfter = wordafter(textAfter); 507 var currT = peekToken(state,1); 508 var prevT = peekToken(state,2); 509 510 if (state.in_string || state.in_atom) { 511 return CodeMirror.Pass; 512 }else if (!prevT) { 513 return 0; 514 }else if (currT.token == "when") { 515 return currT.column+unit; 516 }else if (wordAfter === "when" && prevT.type === "function") { 517 return prevT.indent+unit; 518 }else if (wordAfter === "(" && currT.token === "fun") { 519 return currT.column+3; 520 }else if (wordAfter === "catch" && (t = getToken(state,["try"]))) { 521 return t.column; 522 }else if (is_member(wordAfter,["end","after","of"])) { 523 t = getToken(state,["begin","case","fun","if","receive","try"]); 524 return t ? t.column : CodeMirror.Pass; 525 }else if (is_member(wordAfter,closeParenWords)) { 526 t = getToken(state,openParenWords); 527 return t ? t.column : CodeMirror.Pass; 528 }else if (is_member(currT.token,[",","|","||"]) || 529 is_member(wordAfter,[",","|","||"])) { 530 t = postcommaToken(state); 531 return t ? t.column+t.token.length : unit; 532 }else if (currT.token == "->") { 533 if (is_member(prevT.token, ["receive","case","if","try"])) { 534 return prevT.column+unit+unit; 535 }else{ 536 return prevT.column+unit; 537 } 538 }else if (is_member(currT.token,openParenWords)) { 539 return currT.column+currT.token.length; 540 }else{ 541 t = defaultToken(state); 542 return truthy(t) ? t.column+unit : 0; 543 } 544 } 545 546 function wordafter(str) { 547 var m = str.match(/,|[a-z]+|\}|\]|\)|>>|\|+|\(/); 548 549 return truthy(m) && (m.index === 0) ? m[0] : ""; 550 } 551 552 function postcommaToken(state) { 553 var objs = state.tokenStack.slice(0,-1); 554 var i = getTokenIndex(objs,"type",["open_paren"]); 555 556 return truthy(objs[i]) ? objs[i] : false; 557 } 558 559 function defaultToken(state) { 560 var objs = state.tokenStack; 561 var stop = getTokenIndex(objs,"type",["open_paren","separator","keyword"]); 562 var oper = getTokenIndex(objs,"type",["operator"]); 563 564 if (truthy(stop) && truthy(oper) && stop < oper) { 565 return objs[stop+1]; 566 } else if (truthy(stop)) { 567 return objs[stop]; 568 } else { 569 return false; 570 } 571 } 572 573 function getToken(state,tokens) { 574 var objs = state.tokenStack; 575 var i = getTokenIndex(objs,"token",tokens); 576 577 return truthy(objs[i]) ? objs[i] : false; 578 } 579 580 function getTokenIndex(objs,propname,propvals) { 581 582 for (var i = objs.length-1; -1 < i ; i--) { 583 if (is_member(objs[i][propname],propvals)) { 584 return i; 585 } 586 } 587 return false; 588 } 589 590 function truthy(x) { 591 return (x !== false) && (x != null); 592 } 593 594 ///////////////////////////////////////////////////////////////////////////// 595 // this object defines the mode 596 597 return { 598 startState: 599 function() { 600 return {tokenStack: [], 601 in_string: false, 602 in_atom: false}; 603 }, 604 605 token: 606 function(stream, state) { 607 return tokenizer(stream, state); 608 }, 609 610 indent: 611 function(state, textAfter) { 612 return indenter(state,textAfter); 613 }, 614 615 lineComment: "%" 616 }; 617 }); 618 619 });
Downloadmodules/editor/codemirror/mode/erlang/erlang.js
History Sun, 17 Dec 2017 01:14:09 +0100 Jan Dankert Integration eines weiteren Code-Editors: Codemirror. Demnächst müssen wir hier mal aufräumen und andere Editoren rauswerfen.