|
- // CodeMirror, copyright (c) by Marijn Haverbeke and others
- // Distributed under an MIT license: https://codemirror.net/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(/([\n\s]+|%[^\n]*\n)*(.)/,false);
- return m ? m.pop() : "";
- }
-
- 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: "%"
- };
- });
-
- });
|