| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  | // CodeMirror, copyright (c) by Marijn Haverbeke and others
 | 
					
						
							| 
									
										
										
										
											2018-10-07 12:02:07 +02:00
										 |  |  | // Distributed under an MIT license: https://codemirror.net/LICENSE
 | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | /*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: "%" | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | }); |