Spaces:
Running
Running
// block; "begin", "case", "fun", "if", "receive", "try": closed by "end" | |
// block internal; "after", "catch", "of" | |
// guard; "when", closed by "->" | |
// "->" opens a clause, closed by ";" or "." | |
// "<<" opens a binary, closed by ">>" | |
// "," appears in arglists, lists, tuples and terminates lines of code | |
// "." resets indentation to 0 | |
// obsolete; "cond", "let", "query" | |
CodeMirror.defineMIME("text/x-erlang", "erlang"); | |
CodeMirror.defineMode("erlang", function(cmCfg) { | |
function rval(state,stream,type) { | |
// distinguish between "." as terminator and record field operator | |
if (type == "record") { | |
state.context = "record"; | |
}else{ | |
state.context = false; | |
} | |
// remember last significant bit on last line for indenting | |
if (type != "whitespace" && type != "comment") { | |
state.lastToken = stream.current(); | |
} | |
// erlang -> CodeMirror tag | |
switch (type) { | |
case "atom": return "atom"; | |
case "attribute": return "attribute"; | |
case "builtin": return "builtin"; | |
case "comment": return "comment"; | |
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 "operator": return "operator"; | |
case "record": return "bracket"; | |
case "string": return "string"; | |
case "type": return "def"; | |
case "variable": return "variable"; | |
case "error": return "error"; | |
case "separator": return null; | |
case "open_paren": return null; | |
case "close_paren": return null; | |
default: return null; | |
} | |
} | |
var typeWords = [ | |
"-type", "-spec", "-export_type", "-opaque"]; | |
var keywordWords = [ | |
"after","begin","catch","case","cond","end","fun","if", | |
"let","of","query","receive","try","when"]; | |
var separatorWords = [ | |
"->",";",":",".",","]; | |
var operatorWords = [ | |
"and","andalso","band","bnot","bor","bsl","bsr","bxor", | |
"div","not","or","orelse","rem","xor"]; | |
var symbolWords = [ | |
"+","-","*","/",">",">=","<","=<","=:=","==","=/=","/=","||","<-"]; | |
var openParenWords = [ | |
"<<","(","[","{"]; | |
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"]; | |
// ignored for indenting purposes | |
var ignoreWords = [ | |
",", ":", "catch", "after", "of", "cond", "let", "query"]; | |
var smallRE = /[a-z_]/; | |
var largeRE = /[A-Z_]/; | |
var digitRE = /[0-9]/; | |
var octitRE = /[0-7]/; | |
var anumRE = /[a-z_A-Z0-9]/; | |
var symbolRE = /[\+\-\*\/<>=\|:]/; | |
var openParenRE = /[<\(\[\{]/; | |
var closeParenRE = /[>\)\]\}]/; | |
var sepRE = /[\->\.,:;]/; | |
function isMember(element,list) { | |
return (-1 < list.indexOf(element)); | |
} | |
function isPrev(stream,string) { | |
var start = stream.start; | |
var len = string.length; | |
if (len <= start) { | |
var word = stream.string.slice(start-len,start); | |
return word == string; | |
}else{ | |
return false; | |
} | |
} | |
function tokenize(stream, state) { | |
if (stream.eatSpace()) { | |
return rval(state,stream,"whitespace"); | |
} | |
// attributes and type specs | |
if ((peekToken(state).token == "" || peekToken(state).token == ".") && | |
stream.peek() == '-') { | |
stream.next(); | |
if (stream.eat(smallRE) && stream.eatWhile(anumRE)) { | |
if (isMember(stream.current(),typeWords)) { | |
return rval(state,stream,"type"); | |
}else{ | |
return rval(state,stream,"attribute"); | |
} | |
} | |
stream.backUp(1); | |
} | |
var ch = stream.next(); | |
// comment | |
if (ch == '%') { | |
stream.skipToEnd(); | |
return rval(state,stream,"comment"); | |
} | |
// macro | |
if (ch == '?') { | |
stream.eatWhile(anumRE); | |
return rval(state,stream,"macro"); | |
} | |
// record | |
if ( ch == "#") { | |
stream.eatWhile(anumRE); | |
return rval(state,stream,"record"); | |
} | |
// char | |
if ( ch == "$") { | |
if (stream.next() == "\\") { | |
if (!stream.eatWhile(octitRE)) { | |
stream.next(); | |
} | |
} | |
return rval(state,stream,"string"); | |
} | |
// quoted atom | |
if (ch == '\'') { | |
if (singleQuote(stream)) { | |
return rval(state,stream,"atom"); | |
}else{ | |
return rval(state,stream,"error"); | |
} | |
} | |
// string | |
if (ch == '"') { | |
if (doubleQuote(stream)) { | |
return rval(state,stream,"string"); | |
}else{ | |
return rval(state,stream,"error"); | |
} | |
} | |
// variable | |
if (largeRE.test(ch)) { | |
stream.eatWhile(anumRE); | |
return rval(state,stream,"variable"); | |
} | |
// atom/keyword/BIF/function | |
if (smallRE.test(ch)) { | |
stream.eatWhile(anumRE); | |
if (stream.peek() == "/") { | |
stream.next(); | |
if (stream.eatWhile(digitRE)) { | |
return rval(state,stream,"fun"); // f/0 style fun | |
}else{ | |
stream.backUp(1); | |
return rval(state,stream,"atom"); | |
} | |
} | |
var w = stream.current(); | |
if (isMember(w,keywordWords)) { | |
pushToken(state,stream); | |
return rval(state,stream,"keyword"); | |
} | |
if (stream.peek() == "(") { | |
// 'put' and 'erlang:put' are bifs, 'foo:put' is not | |
if (isMember(w,bifWords) && | |
(!isPrev(stream,":") || isPrev(stream,"erlang:"))) { | |
return rval(state,stream,"builtin"); | |
}else{ | |
return rval(state,stream,"function"); | |
} | |
} | |
if (isMember(w,guardWords)) { | |
return rval(state,stream,"guard"); | |
} | |
if (isMember(w,operatorWords)) { | |
return rval(state,stream,"operator"); | |
} | |
if (stream.peek() == ":") { | |
if (w == "erlang") { | |
return rval(state,stream,"builtin"); | |
} else { | |
return rval(state,stream,"function"); | |
} | |
} | |
return rval(state,stream,"atom"); | |
} | |
// number | |
if (digitRE.test(ch)) { | |
stream.eatWhile(digitRE); | |
if (stream.eat('#')) { | |
stream.eatWhile(digitRE); // 16#10 style integer | |
} else { | |
if (stream.eat('.')) { // float | |
stream.eatWhile(digitRE); | |
} | |
if (stream.eat(/[eE]/)) { | |
stream.eat(/[-+]/); // float with exponent | |
stream.eatWhile(digitRE); | |
} | |
} | |
return rval(state,stream,"number"); // normal integer | |
} | |
// open parens | |
if (nongreedy(stream,openParenRE,openParenWords)) { | |
pushToken(state,stream); | |
return rval(state,stream,"open_paren"); | |
} | |
// close parens | |
if (nongreedy(stream,closeParenRE,closeParenWords)) { | |
pushToken(state,stream); | |
return rval(state,stream,"close_paren"); | |
} | |
// separators | |
if (greedy(stream,sepRE,separatorWords)) { | |
// distinguish between "." as terminator and record field operator | |
if (state.context == false) { | |
pushToken(state,stream); | |
} | |
return rval(state,stream,"separator"); | |
} | |
// operators | |
if (greedy(stream,symbolRE,symbolWords)) { | |
return rval(state,stream,"operator"); | |
} | |
return rval(state,stream,null); | |
} | |
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 (isMember(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 (isMember(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 Token(stream) { | |
this.token = stream ? stream.current() : ""; | |
this.column = stream ? stream.column() : 0; | |
this.indent = stream ? stream.indentation() : 0; | |
} | |
function myIndent(state,textAfter) { | |
var indent = cmCfg.indentUnit; | |
var outdentWords = ["after","catch"]; | |
var token = (peekToken(state)).token; | |
var wordAfter = takewhile(textAfter,/[^a-z]/); | |
if (isMember(token,openParenWords)) { | |
return (peekToken(state)).column+token.length; | |
}else if (token == "." || token == ""){ | |
return 0; | |
}else if (token == "->") { | |
if (wordAfter == "end") { | |
return peekToken(state,2).column; | |
}else if (peekToken(state,2).token == "fun") { | |
return peekToken(state,2).column+indent; | |
}else{ | |
return (peekToken(state)).indent+indent; | |
} | |
}else if (isMember(wordAfter,outdentWords)) { | |
return (peekToken(state)).indent; | |
}else{ | |
return (peekToken(state)).column+indent; | |
} | |
} | |
function takewhile(str,re) { | |
var m = str.match(re); | |
return m ? str.slice(0,m.index) : str; | |
} | |
function popToken(state) { | |
return state.tokenStack.pop(); | |
} | |
function peekToken(state,depth) { | |
var len = state.tokenStack.length; | |
var dep = (depth ? depth : 1); | |
if (len < dep) { | |
return new Token; | |
}else{ | |
return state.tokenStack[len-dep]; | |
} | |
} | |
function pushToken(state,stream) { | |
var token = stream.current(); | |
var prev_token = peekToken(state).token; | |
if (isMember(token,ignoreWords)) { | |
return false; | |
}else if (drop_both(prev_token,token)) { | |
popToken(state); | |
return false; | |
}else if (drop_first(prev_token,token)) { | |
popToken(state); | |
return pushToken(state,stream); | |
}else{ | |
state.tokenStack.push(new Token(stream)); | |
return true; | |
} | |
} | |
function drop_first(open, close) { | |
switch (open+" "+close) { | |
case "when ->": return true; | |
case "-> end": return true; | |
case "-> .": return true; | |
case ". .": return true; | |
default: return false; | |
} | |
} | |
function drop_both(open, close) { | |
switch (open+" "+close) { | |
case "( )": return true; | |
case "[ ]": return true; | |
case "{ }": return true; | |
case "<< >>": return true; | |
case "begin end": return true; | |
case "case end": return true; | |
case "fun end": return true; | |
case "if end": return true; | |
case "receive end": return true; | |
case "try end": return true; | |
case "-> ;": return true; | |
default: return false; | |
} | |
} | |
return { | |
startState: | |
function() { | |
return {tokenStack: [], | |
context: false, | |
lastToken: null}; | |
}, | |
token: | |
function(stream, state) { | |
return tokenize(stream, state); | |
}, | |
indent: | |
function(state, textAfter) { | |
// console.log(state.tokenStack); | |
return myIndent(state,textAfter); | |
} | |
}; | |
}); | |