openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

erlang.js (18887B)


      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 });