github.com/elliott5/community@v0.14.1-0.20160709191136-823126fb026a/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  });