1 // block; "begin", "case", "fun", "if", "receive", "try": closed by "end"
2 // block internal; "after", "catch", "of"
3 // guard; "when", closed by "->"
4 // "->" opens a clause, closed by ";" or "."
5 // "<<" opens a binary, closed by ">>"
6 // "," appears in arglists, lists, tuples and terminates lines of code
7 // "." resets indentation to 0
8 // obsolete; "cond", "let", "query"
10 CodeMirror.defineMIME("text/x-erlang", "erlang");
12 CodeMirror.defineMode("erlang", function(cmCfg) {
14 function rval(state,stream,type) {
15 // distinguish between "." as terminator and record field operator
16 if (type == "record") {
17 state.context = "record";
19 state.context = false;
22 // remember last significant bit on last line for indenting
23 if (type != "whitespace" && type != "comment") {
24 state.lastToken = stream.current();
26 // erlang -> CodeMirror tag
28 case "atom": return "atom";
29 case "attribute": return "attribute";
30 case "builtin": return "builtin";
31 case "comment": return "comment";
32 case "fun": return "meta";
33 case "function": return "tag";
34 case "guard": return "property";
35 case "keyword": return "keyword";
36 case "macro": return "variable-2";
37 case "number": return "number";
38 case "operator": return "operator";
39 case "record": return "bracket";
40 case "string": return "string";
41 case "type": return "def";
42 case "variable": return "variable";
43 case "error": return "error";
44 case "separator": return null;
45 case "open_paren": return null;
46 case "close_paren": return null;
52 "-type", "-spec", "-export_type", "-opaque"];
55 "after","begin","catch","case","cond","end","fun","if",
56 "let","of","query","receive","try","when"];
58 var separatorWords = [
59 "->",";",":",".",","];
62 "and","andalso","band","bnot","bor","bsl","bsr","bxor",
63 "div","not","or","orelse","rem","xor"];
66 "+","-","*","/",">",">=","<","=<","=:=","==","=/=","/=","||","<-"];
68 var openParenWords = [
71 var closeParenWords = [
75 "is_atom","is_binary","is_bitstring","is_boolean","is_float",
76 "is_function","is_integer","is_list","is_number","is_pid",
77 "is_port","is_record","is_reference","is_tuple",
78 "atom","binary","bitstring","boolean","function","integer","list",
79 "number","pid","port","record","reference","tuple"];
82 "abs","adler32","adler32_combine","alive","apply","atom_to_binary",
83 "atom_to_list","binary_to_atom","binary_to_existing_atom",
84 "binary_to_list","binary_to_term","bit_size","bitstring_to_list",
85 "byte_size","check_process_code","contact_binary","crc32",
86 "crc32_combine","date","decode_packet","delete_module",
87 "disconnect_node","element","erase","exit","float","float_to_list",
88 "garbage_collect","get","get_keys","group_leader","halt","hd",
89 "integer_to_list","internal_bif","iolist_size","iolist_to_binary",
90 "is_alive","is_atom","is_binary","is_bitstring","is_boolean",
91 "is_float","is_function","is_integer","is_list","is_number","is_pid",
92 "is_port","is_process_alive","is_record","is_reference","is_tuple",
93 "length","link","list_to_atom","list_to_binary","list_to_bitstring",
94 "list_to_existing_atom","list_to_float","list_to_integer",
95 "list_to_pid","list_to_tuple","load_module","make_ref","module_loaded",
96 "monitor_node","node","node_link","node_unlink","nodes","notalive",
97 "now","open_port","pid_to_list","port_close","port_command",
98 "port_connect","port_control","pre_loaded","process_flag",
99 "process_info","processes","purge_module","put","register",
100 "registered","round","self","setelement","size","spawn","spawn_link",
101 "spawn_monitor","spawn_opt","split_binary","statistics",
102 "term_to_binary","time","throw","tl","trunc","tuple_size",
103 "tuple_to_list","unlink","unregister","whereis"];
105 // ignored for indenting purposes
107 ",", ":", "catch", "after", "of", "cond", "let", "query"];
110 var smallRE = /[a-z_]/;
111 var largeRE = /[A-Z_]/;
112 var digitRE = /[0-9]/;
113 var octitRE = /[0-7]/;
114 var anumRE = /[a-z_A-Z0-9]/;
115 var symbolRE = /[\+\-\*\/<>=\|:]/;
116 var openParenRE = /[<\(\[\{]/;
117 var closeParenRE = /[>\)\]\}]/;
118 var sepRE = /[\->\.,:;]/;
120 function isMember(element,list) {
121 return (-1 < list.indexOf(element));
124 function isPrev(stream,string) {
125 var start = stream.start;
126 var len = string.length;
128 var word = stream.string.slice(start-len,start);
129 return word == string;
135 function tokenize(stream, state) {
136 if (stream.eatSpace()) {
137 return rval(state,stream,"whitespace");
140 // attributes and type specs
141 if ((peekToken(state).token == "" || peekToken(state).token == ".") &&
142 stream.peek() == '-') {
144 if (stream.eat(smallRE) && stream.eatWhile(anumRE)) {
145 if (isMember(stream.current(),typeWords)) {
146 return rval(state,stream,"type");
148 return rval(state,stream,"attribute");
154 var ch = stream.next();
159 return rval(state,stream,"comment");
164 stream.eatWhile(anumRE);
165 return rval(state,stream,"macro");
170 stream.eatWhile(anumRE);
171 return rval(state,stream,"record");
176 if (stream.next() == "\\") {
177 if (!stream.eatWhile(octitRE)) {
181 return rval(state,stream,"string");
186 if (singleQuote(stream)) {
187 return rval(state,stream,"atom");
189 return rval(state,stream,"error");
195 if (doubleQuote(stream)) {
196 return rval(state,stream,"string");
198 return rval(state,stream,"error");
203 if (largeRE.test(ch)) {
204 stream.eatWhile(anumRE);
205 return rval(state,stream,"variable");
208 // atom/keyword/BIF/function
209 if (smallRE.test(ch)) {
210 stream.eatWhile(anumRE);
212 if (stream.peek() == "/") {
214 if (stream.eatWhile(digitRE)) {
215 return rval(state,stream,"fun"); // f/0 style fun
218 return rval(state,stream,"atom");
222 var w = stream.current();
224 if (isMember(w,keywordWords)) {
225 pushToken(state,stream);
226 return rval(state,stream,"keyword");
228 if (stream.peek() == "(") {
229 // 'put' and 'erlang:put' are bifs, 'foo:put' is not
230 if (isMember(w,bifWords) &&
231 (!isPrev(stream,":") || isPrev(stream,"erlang:"))) {
232 return rval(state,stream,"builtin");
234 return rval(state,stream,"function");
237 if (isMember(w,guardWords)) {
238 return rval(state,stream,"guard");
240 if (isMember(w,operatorWords)) {
241 return rval(state,stream,"operator");
243 if (stream.peek() == ":") {
245 return rval(state,stream,"builtin");
247 return rval(state,stream,"function");
250 return rval(state,stream,"atom");
254 if (digitRE.test(ch)) {
255 stream.eatWhile(digitRE);
256 if (stream.eat('#')) {
257 stream.eatWhile(digitRE); // 16#10 style integer
259 if (stream.eat('.')) { // float
260 stream.eatWhile(digitRE);
262 if (stream.eat(/[eE]/)) {
263 stream.eat(/[-+]/); // float with exponent
264 stream.eatWhile(digitRE);
267 return rval(state,stream,"number"); // normal integer
271 if (nongreedy(stream,openParenRE,openParenWords)) {
272 pushToken(state,stream);
273 return rval(state,stream,"open_paren");
277 if (nongreedy(stream,closeParenRE,closeParenWords)) {
278 pushToken(state,stream);
279 return rval(state,stream,"close_paren");
283 if (greedy(stream,sepRE,separatorWords)) {
284 // distinguish between "." as terminator and record field operator
285 if (state.context == false) {
286 pushToken(state,stream);
288 return rval(state,stream,"separator");
292 if (greedy(stream,symbolRE,symbolWords)) {
293 return rval(state,stream,"operator");
296 return rval(state,stream,null);
299 function nongreedy(stream,re,words) {
300 if (stream.current().length == 1 && re.test(stream.current())) {
302 while (re.test(stream.peek())) {
304 if (isMember(stream.current(),words)) {
308 stream.backUp(stream.current().length-1);
313 function greedy(stream,re,words) {
314 if (stream.current().length == 1 && re.test(stream.current())) {
315 while (re.test(stream.peek())) {
318 while (0 < stream.current().length) {
319 if (isMember(stream.current(),words)) {
330 function doubleQuote(stream) {
331 return quote(stream, '"', '\\');
334 function singleQuote(stream) {
335 return quote(stream,'\'','\\');
338 function quote(stream,quoteChar,escapeChar) {
339 while (!stream.eol()) {
340 var ch = stream.next();
341 if (ch == quoteChar) {
343 }else if (ch == escapeChar) {
350 function Token(stream) {
351 this.token = stream ? stream.current() : "";
352 this.column = stream ? stream.column() : 0;
353 this.indent = stream ? stream.indentation() : 0;
356 function myIndent(state,textAfter) {
357 var indent = cmCfg.indentUnit;
358 var outdentWords = ["after","catch"];
359 var token = (peekToken(state)).token;
360 var wordAfter = takewhile(textAfter,/[^a-z]/);
362 if (isMember(token,openParenWords)) {
363 return (peekToken(state)).column+token.length;
364 }else if (token == "." || token == ""){
366 }else if (token == "->") {
367 if (wordAfter == "end") {
368 return peekToken(state,2).column;
369 }else if (peekToken(state,2).token == "fun") {
370 return peekToken(state,2).column+indent;
372 return (peekToken(state)).indent+indent;
374 }else if (isMember(wordAfter,outdentWords)) {
375 return (peekToken(state)).indent;
377 return (peekToken(state)).column+indent;
381 function takewhile(str,re) {
382 var m = str.match(re);
383 return m ? str.slice(0,m.index) : str;
386 function popToken(state) {
387 return state.tokenStack.pop();
390 function peekToken(state,depth) {
391 var len = state.tokenStack.length;
392 var dep = (depth ? depth : 1);
396 return state.tokenStack[len-dep];
400 function pushToken(state,stream) {
401 var token = stream.current();
402 var prev_token = peekToken(state).token;
403 if (isMember(token,ignoreWords)) {
405 }else if (drop_both(prev_token,token)) {
408 }else if (drop_first(prev_token,token)) {
410 return pushToken(state,stream);
412 state.tokenStack.push(new Token(stream));
417 function drop_first(open, close) {
418 switch (open+" "+close) {
419 case "when ->": return true;
420 case "-> end": return true;
421 case "-> .": return true;
422 case ". .": return true;
423 default: return false;
427 function drop_both(open, close) {
428 switch (open+" "+close) {
429 case "( )": return true;
430 case "[ ]": return true;
431 case "{ }": return true;
432 case "<< >>": return true;
433 case "begin end": return true;
434 case "case end": return true;
435 case "fun end": return true;
436 case "if end": return true;
437 case "receive end": return true;
438 case "try end": return true;
439 case "-> ;": return true;
440 default: return false;
447 return {tokenStack: [],
453 function(stream, state) {
454 return tokenize(stream, state);
458 function(state, textAfter) {
459 return myIndent(state,textAfter);