all repos — NoPaste @ 3e3c433f026c49e04d0efa0f0f39fc688ed32bb1

Resurrected - The PussTheCat.org fork of NoPaste

scripts/CodeMirror/mode/erlang/erlang.js (view raw)

  1// CodeMirror, copyright (c) by Marijn Haverbeke and others
  2// Distributed under an MIT license: https://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
 28CodeMirror.defineMIME("text/x-erlang", "erlang");
 29
 30CodeMirror.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});