diff .cms/lib/codemirror/mode/erlang/erlang.js @ 0:78edf6b517a0 draft

24.10
author Coffee CMS <info@coffee-cms.ru>
date Fri, 11 Oct 2024 22:40:23 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.cms/lib/codemirror/mode/erlang/erlang.js	Fri Oct 11 22:40:23 2024 +0000
@@ -0,0 +1,619 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/5/LICENSE
+
+/*jshint unused:true, eqnull:true, curly:true, bitwise:true */
+/*jshint undef:true, latedef:true, trailing:true */
+/*global CodeMirror:true */
+
+// erlang mode.
+// tokenizer -> token types -> CodeMirror styles
+// tokenizer maintains a parse stack
+// indenter uses the parse stack
+
+// TODO indenter:
+//   bit syntax
+//   old guard/bif/conversion clashes (e.g. "float/1")
+//   type/spec/opaque
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+"use strict";
+
+CodeMirror.defineMIME("text/x-erlang", "erlang");
+
+CodeMirror.defineMode("erlang", function(cmCfg) {
+  "use strict";
+
+/////////////////////////////////////////////////////////////////////////////
+// constants
+
+  var typeWords = [
+    "-type", "-spec", "-export_type", "-opaque"];
+
+  var keywordWords = [
+    "after","begin","catch","case","cond","end","fun","if",
+    "let","of","query","receive","try","when"];
+
+  var separatorRE    = /[\->,;]/;
+  var separatorWords = [
+    "->",";",","];
+
+  var operatorAtomWords = [
+    "and","andalso","band","bnot","bor","bsl","bsr","bxor",
+    "div","not","or","orelse","rem","xor"];
+
+  var operatorSymbolRE    = /[\+\-\*\/<>=\|:!]/;
+  var operatorSymbolWords = [
+    "=","+","-","*","/",">",">=","<","=<","=:=","==","=/=","/=","||","<-","!"];
+
+  var openParenRE    = /[<\(\[\{]/;
+  var openParenWords = [
+    "<<","(","[","{"];
+
+  var closeParenRE    = /[>\)\]\}]/;
+  var closeParenWords = [
+    "}","]",")",">>"];
+
+  var guardWords = [
+    "is_atom","is_binary","is_bitstring","is_boolean","is_float",
+    "is_function","is_integer","is_list","is_number","is_pid",
+    "is_port","is_record","is_reference","is_tuple",
+    "atom","binary","bitstring","boolean","function","integer","list",
+    "number","pid","port","record","reference","tuple"];
+
+  var bifWords = [
+    "abs","adler32","adler32_combine","alive","apply","atom_to_binary",
+    "atom_to_list","binary_to_atom","binary_to_existing_atom",
+    "binary_to_list","binary_to_term","bit_size","bitstring_to_list",
+    "byte_size","check_process_code","contact_binary","crc32",
+    "crc32_combine","date","decode_packet","delete_module",
+    "disconnect_node","element","erase","exit","float","float_to_list",
+    "garbage_collect","get","get_keys","group_leader","halt","hd",
+    "integer_to_list","internal_bif","iolist_size","iolist_to_binary",
+    "is_alive","is_atom","is_binary","is_bitstring","is_boolean",
+    "is_float","is_function","is_integer","is_list","is_number","is_pid",
+    "is_port","is_process_alive","is_record","is_reference","is_tuple",
+    "length","link","list_to_atom","list_to_binary","list_to_bitstring",
+    "list_to_existing_atom","list_to_float","list_to_integer",
+    "list_to_pid","list_to_tuple","load_module","make_ref","module_loaded",
+    "monitor_node","node","node_link","node_unlink","nodes","notalive",
+    "now","open_port","pid_to_list","port_close","port_command",
+    "port_connect","port_control","pre_loaded","process_flag",
+    "process_info","processes","purge_module","put","register",
+    "registered","round","self","setelement","size","spawn","spawn_link",
+    "spawn_monitor","spawn_opt","split_binary","statistics",
+    "term_to_binary","time","throw","tl","trunc","tuple_size",
+    "tuple_to_list","unlink","unregister","whereis"];
+
+// upper case: [A-Z] [Ø-Þ] [À-Ö]
+// lower case: [a-z] [ß-ö] [ø-ÿ]
+  var anumRE       = /[\w@Ø-ÞÀ-Öß-öø-ÿ]/;
+  var escapesRE    =
+    /[0-7]{1,3}|[bdefnrstv\\"']|\^[a-zA-Z]|x[0-9a-zA-Z]{2}|x{[0-9a-zA-Z]+}/;
+
+/////////////////////////////////////////////////////////////////////////////
+// tokenizer
+
+  function tokenizer(stream,state) {
+    // in multi-line string
+    if (state.in_string) {
+      state.in_string = (!doubleQuote(stream));
+      return rval(state,stream,"string");
+    }
+
+    // in multi-line atom
+    if (state.in_atom) {
+      state.in_atom = (!singleQuote(stream));
+      return rval(state,stream,"atom");
+    }
+
+    // whitespace
+    if (stream.eatSpace()) {
+      return rval(state,stream,"whitespace");
+    }
+
+    // attributes and type specs
+    if (!peekToken(state) &&
+        stream.match(/-\s*[a-zß-öø-ÿ][\wØ-ÞÀ-Öß-öø-ÿ]*/)) {
+      if (is_member(stream.current(),typeWords)) {
+        return rval(state,stream,"type");
+      }else{
+        return rval(state,stream,"attribute");
+      }
+    }
+
+    var ch = stream.next();
+
+    // comment
+    if (ch == '%') {
+      stream.skipToEnd();
+      return rval(state,stream,"comment");
+    }
+
+    // colon
+    if (ch == ":") {
+      return rval(state,stream,"colon");
+    }
+
+    // macro
+    if (ch == '?') {
+      stream.eatSpace();
+      stream.eatWhile(anumRE);
+      return rval(state,stream,"macro");
+    }
+
+    // record
+    if (ch == "#") {
+      stream.eatSpace();
+      stream.eatWhile(anumRE);
+      return rval(state,stream,"record");
+    }
+
+    // dollar escape
+    if (ch == "$") {
+      if (stream.next() == "\\" && !stream.match(escapesRE)) {
+        return rval(state,stream,"error");
+      }
+      return rval(state,stream,"number");
+    }
+
+    // dot
+    if (ch == ".") {
+      return rval(state,stream,"dot");
+    }
+
+    // quoted atom
+    if (ch == '\'') {
+      if (!(state.in_atom = (!singleQuote(stream)))) {
+        if (stream.match(/\s*\/\s*[0-9]/,false)) {
+          stream.match(/\s*\/\s*[0-9]/,true);
+          return rval(state,stream,"fun");      // 'f'/0 style fun
+        }
+        if (stream.match(/\s*\(/,false) || stream.match(/\s*:/,false)) {
+          return rval(state,stream,"function");
+        }
+      }
+      return rval(state,stream,"atom");
+    }
+
+    // string
+    if (ch == '"') {
+      state.in_string = (!doubleQuote(stream));
+      return rval(state,stream,"string");
+    }
+
+    // variable
+    if (/[A-Z_Ø-ÞÀ-Ö]/.test(ch)) {
+      stream.eatWhile(anumRE);
+      return rval(state,stream,"variable");
+    }
+
+    // atom/keyword/BIF/function
+    if (/[a-z_ß-öø-ÿ]/.test(ch)) {
+      stream.eatWhile(anumRE);
+
+      if (stream.match(/\s*\/\s*[0-9]/,false)) {
+        stream.match(/\s*\/\s*[0-9]/,true);
+        return rval(state,stream,"fun");      // f/0 style fun
+      }
+
+      var w = stream.current();
+
+      if (is_member(w,keywordWords)) {
+        return rval(state,stream,"keyword");
+      }else if (is_member(w,operatorAtomWords)) {
+        return rval(state,stream,"operator");
+      }else if (stream.match(/\s*\(/,false)) {
+        // 'put' and 'erlang:put' are bifs, 'foo:put' is not
+        if (is_member(w,bifWords) &&
+            ((peekToken(state).token != ":") ||
+             (peekToken(state,2).token == "erlang"))) {
+          return rval(state,stream,"builtin");
+        }else if (is_member(w,guardWords)) {
+          return rval(state,stream,"guard");
+        }else{
+          return rval(state,stream,"function");
+        }
+      }else if (lookahead(stream) == ":") {
+        if (w == "erlang") {
+          return rval(state,stream,"builtin");
+        } else {
+          return rval(state,stream,"function");
+        }
+      }else if (is_member(w,["true","false"])) {
+        return rval(state,stream,"boolean");
+      }else{
+        return rval(state,stream,"atom");
+      }
+    }
+
+    // number
+    var digitRE      = /[0-9]/;
+    var radixRE      = /[0-9a-zA-Z]/;         // 36#zZ style int
+    if (digitRE.test(ch)) {
+      stream.eatWhile(digitRE);
+      if (stream.eat('#')) {                // 36#aZ  style integer
+        if (!stream.eatWhile(radixRE)) {
+          stream.backUp(1);                 //"36#" - syntax error
+        }
+      } else if (stream.eat('.')) {       // float
+        if (!stream.eatWhile(digitRE)) {
+          stream.backUp(1);        // "3." - probably end of function
+        } else {
+          if (stream.eat(/[eE]/)) {        // float with exponent
+            if (stream.eat(/[-+]/)) {
+              if (!stream.eatWhile(digitRE)) {
+                stream.backUp(2);            // "2e-" - syntax error
+              }
+            } else {
+              if (!stream.eatWhile(digitRE)) {
+                stream.backUp(1);            // "2e" - syntax error
+              }
+            }
+          }
+        }
+      }
+      return rval(state,stream,"number");   // normal integer
+    }
+
+    // open parens
+    if (nongreedy(stream,openParenRE,openParenWords)) {
+      return rval(state,stream,"open_paren");
+    }
+
+    // close parens
+    if (nongreedy(stream,closeParenRE,closeParenWords)) {
+      return rval(state,stream,"close_paren");
+    }
+
+    // separators
+    if (greedy(stream,separatorRE,separatorWords)) {
+      return rval(state,stream,"separator");
+    }
+
+    // operators
+    if (greedy(stream,operatorSymbolRE,operatorSymbolWords)) {
+      return rval(state,stream,"operator");
+    }
+
+    return rval(state,stream,null);
+  }
+
+/////////////////////////////////////////////////////////////////////////////
+// utilities
+  function nongreedy(stream,re,words) {
+    if (stream.current().length == 1 && re.test(stream.current())) {
+      stream.backUp(1);
+      while (re.test(stream.peek())) {
+        stream.next();
+        if (is_member(stream.current(),words)) {
+          return true;
+        }
+      }
+      stream.backUp(stream.current().length-1);
+    }
+    return false;
+  }
+
+  function greedy(stream,re,words) {
+    if (stream.current().length == 1 && re.test(stream.current())) {
+      while (re.test(stream.peek())) {
+        stream.next();
+      }
+      while (0 < stream.current().length) {
+        if (is_member(stream.current(),words)) {
+          return true;
+        }else{
+          stream.backUp(1);
+        }
+      }
+      stream.next();
+    }
+    return false;
+  }
+
+  function doubleQuote(stream) {
+    return quote(stream, '"', '\\');
+  }
+
+  function singleQuote(stream) {
+    return quote(stream,'\'','\\');
+  }
+
+  function quote(stream,quoteChar,escapeChar) {
+    while (!stream.eol()) {
+      var ch = stream.next();
+      if (ch == quoteChar) {
+        return true;
+      }else if (ch == escapeChar) {
+        stream.next();
+      }
+    }
+    return false;
+  }
+
+  function lookahead(stream) {
+    var m = stream.match(/^\s*([^\s%])/, false)
+    return m ? m[1] : "";
+  }
+
+  function is_member(element,list) {
+    return (-1 < list.indexOf(element));
+  }
+
+  function rval(state,stream,type) {
+
+    // parse stack
+    pushToken(state,realToken(type,stream));
+
+    // map erlang token type to CodeMirror style class
+    //     erlang             -> CodeMirror tag
+    switch (type) {
+      case "atom":        return "atom";
+      case "attribute":   return "attribute";
+      case "boolean":     return "atom";
+      case "builtin":     return "builtin";
+      case "close_paren": return null;
+      case "colon":       return null;
+      case "comment":     return "comment";
+      case "dot":         return null;
+      case "error":       return "error";
+      case "fun":         return "meta";
+      case "function":    return "tag";
+      case "guard":       return "property";
+      case "keyword":     return "keyword";
+      case "macro":       return "variable-2";
+      case "number":      return "number";
+      case "open_paren":  return null;
+      case "operator":    return "operator";
+      case "record":      return "bracket";
+      case "separator":   return null;
+      case "string":      return "string";
+      case "type":        return "def";
+      case "variable":    return "variable";
+      default:            return null;
+    }
+  }
+
+  function aToken(tok,col,ind,typ) {
+    return {token:  tok,
+            column: col,
+            indent: ind,
+            type:   typ};
+  }
+
+  function realToken(type,stream) {
+    return aToken(stream.current(),
+                 stream.column(),
+                 stream.indentation(),
+                 type);
+  }
+
+  function fakeToken(type) {
+    return aToken(type,0,0,type);
+  }
+
+  function peekToken(state,depth) {
+    var len = state.tokenStack.length;
+    var dep = (depth ? depth : 1);
+
+    if (len < dep) {
+      return false;
+    }else{
+      return state.tokenStack[len-dep];
+    }
+  }
+
+  function pushToken(state,token) {
+
+    if (!(token.type == "comment" || token.type == "whitespace")) {
+      state.tokenStack = maybe_drop_pre(state.tokenStack,token);
+      state.tokenStack = maybe_drop_post(state.tokenStack);
+    }
+  }
+
+  function maybe_drop_pre(s,token) {
+    var last = s.length-1;
+
+    if (0 < last && s[last].type === "record" && token.type === "dot") {
+      s.pop();
+    }else if (0 < last && s[last].type === "group") {
+      s.pop();
+      s.push(token);
+    }else{
+      s.push(token);
+    }
+    return s;
+  }
+
+  function maybe_drop_post(s) {
+    if (!s.length) return s
+    var last = s.length-1;
+
+    if (s[last].type === "dot") {
+      return [];
+    }
+    if (last > 1 && s[last].type === "fun" && s[last-1].token === "fun") {
+      return s.slice(0,last-1);
+    }
+    switch (s[last].token) {
+      case "}":    return d(s,{g:["{"]});
+      case "]":    return d(s,{i:["["]});
+      case ")":    return d(s,{i:["("]});
+      case ">>":   return d(s,{i:["<<"]});
+      case "end":  return d(s,{i:["begin","case","fun","if","receive","try"]});
+      case ",":    return d(s,{e:["begin","try","when","->",
+                                  ",","(","[","{","<<"]});
+      case "->":   return d(s,{r:["when"],
+                               m:["try","if","case","receive"]});
+      case ";":    return d(s,{E:["case","fun","if","receive","try","when"]});
+      case "catch":return d(s,{e:["try"]});
+      case "of":   return d(s,{e:["case"]});
+      case "after":return d(s,{e:["receive","try"]});
+      default:     return s;
+    }
+  }
+
+  function d(stack,tt) {
+    // stack is a stack of Token objects.
+    // tt is an object; {type:tokens}
+    // type is a char, tokens is a list of token strings.
+    // The function returns (possibly truncated) stack.
+    // It will descend the stack, looking for a Token such that Token.token
+    //  is a member of tokens. If it does not find that, it will normally (but
+    //  see "E" below) return stack. If it does find a match, it will remove
+    //  all the Tokens between the top and the matched Token.
+    // If type is "m", that is all it does.
+    // If type is "i", it will also remove the matched Token and the top Token.
+    // If type is "g", like "i", but add a fake "group" token at the top.
+    // If type is "r", it will remove the matched Token, but not the top Token.
+    // If type is "e", it will keep the matched Token but not the top Token.
+    // If type is "E", it behaves as for type "e", except if there is no match,
+    //  in which case it will return an empty stack.
+
+    for (var type in tt) {
+      var len = stack.length-1;
+      var tokens = tt[type];
+      for (var i = len-1; -1 < i ; i--) {
+        if (is_member(stack[i].token,tokens)) {
+          var ss = stack.slice(0,i);
+          switch (type) {
+              case "m": return ss.concat(stack[i]).concat(stack[len]);
+              case "r": return ss.concat(stack[len]);
+              case "i": return ss;
+              case "g": return ss.concat(fakeToken("group"));
+              case "E": return ss.concat(stack[i]);
+              case "e": return ss.concat(stack[i]);
+          }
+        }
+      }
+    }
+    return (type == "E" ? [] : stack);
+  }
+
+/////////////////////////////////////////////////////////////////////////////
+// indenter
+
+  function indenter(state,textAfter) {
+    var t;
+    var unit = cmCfg.indentUnit;
+    var wordAfter = wordafter(textAfter);
+    var currT = peekToken(state,1);
+    var prevT = peekToken(state,2);
+
+    if (state.in_string || state.in_atom) {
+      return CodeMirror.Pass;
+    }else if (!prevT) {
+      return 0;
+    }else if (currT.token == "when") {
+      return currT.column+unit;
+    }else if (wordAfter === "when" && prevT.type === "function") {
+      return prevT.indent+unit;
+    }else if (wordAfter === "(" && currT.token === "fun") {
+      return  currT.column+3;
+    }else if (wordAfter === "catch" && (t = getToken(state,["try"]))) {
+      return t.column;
+    }else if (is_member(wordAfter,["end","after","of"])) {
+      t = getToken(state,["begin","case","fun","if","receive","try"]);
+      return t ? t.column : CodeMirror.Pass;
+    }else if (is_member(wordAfter,closeParenWords)) {
+      t = getToken(state,openParenWords);
+      return t ? t.column : CodeMirror.Pass;
+    }else if (is_member(currT.token,[",","|","||"]) ||
+              is_member(wordAfter,[",","|","||"])) {
+      t = postcommaToken(state);
+      return t ? t.column+t.token.length : unit;
+    }else if (currT.token == "->") {
+      if (is_member(prevT.token, ["receive","case","if","try"])) {
+        return prevT.column+unit+unit;
+      }else{
+        return prevT.column+unit;
+      }
+    }else if (is_member(currT.token,openParenWords)) {
+      return currT.column+currT.token.length;
+    }else{
+      t = defaultToken(state);
+      return truthy(t) ? t.column+unit : 0;
+    }
+  }
+
+  function wordafter(str) {
+    var m = str.match(/,|[a-z]+|\}|\]|\)|>>|\|+|\(/);
+
+    return truthy(m) && (m.index === 0) ? m[0] : "";
+  }
+
+  function postcommaToken(state) {
+    var objs = state.tokenStack.slice(0,-1);
+    var i = getTokenIndex(objs,"type",["open_paren"]);
+
+    return truthy(objs[i]) ? objs[i] : false;
+  }
+
+  function defaultToken(state) {
+    var objs = state.tokenStack;
+    var stop = getTokenIndex(objs,"type",["open_paren","separator","keyword"]);
+    var oper = getTokenIndex(objs,"type",["operator"]);
+
+    if (truthy(stop) && truthy(oper) && stop < oper) {
+      return objs[stop+1];
+    } else if (truthy(stop)) {
+      return objs[stop];
+    } else {
+      return false;
+    }
+  }
+
+  function getToken(state,tokens) {
+    var objs = state.tokenStack;
+    var i = getTokenIndex(objs,"token",tokens);
+
+    return truthy(objs[i]) ? objs[i] : false;
+  }
+
+  function getTokenIndex(objs,propname,propvals) {
+
+    for (var i = objs.length-1; -1 < i ; i--) {
+      if (is_member(objs[i][propname],propvals)) {
+        return i;
+      }
+    }
+    return false;
+  }
+
+  function truthy(x) {
+    return (x !== false) && (x != null);
+  }
+
+/////////////////////////////////////////////////////////////////////////////
+// this object defines the mode
+
+  return {
+    startState:
+      function() {
+        return {tokenStack: [],
+                in_string:  false,
+                in_atom:    false};
+      },
+
+    token:
+      function(stream, state) {
+        return tokenizer(stream, state);
+      },
+
+    indent:
+      function(state, textAfter) {
+        return indenter(state,textAfter);
+      },
+
+    lineComment: "%"
+  };
+});
+
+});