erlang.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  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"
  9. CodeMirror.defineMIME("text/x-erlang", "erlang");
  10. CodeMirror.defineMode("erlang", function(cmCfg) {
  11. function rval(state,stream,type) {
  12. // distinguish between "." as terminator and record field operator
  13. if (type == "record") {
  14. state.context = "record";
  15. }else{
  16. state.context = false;
  17. }
  18. // remember last significant bit on last line for indenting
  19. if (type != "whitespace" && type != "comment") {
  20. state.lastToken = stream.current();
  21. }
  22. // erlang -> CodeMirror tag
  23. switch (type) {
  24. case "atom": return "atom";
  25. case "attribute": return "attribute";
  26. case "builtin": return "builtin";
  27. case "comment": return "comment";
  28. case "fun": return "meta";
  29. case "function": return "tag";
  30. case "guard": return "property";
  31. case "keyword": return "keyword";
  32. case "macro": return "variable-2";
  33. case "number": return "number";
  34. case "operator": return "operator";
  35. case "record": return "bracket";
  36. case "string": return "string";
  37. case "type": return "def";
  38. case "variable": return "variable";
  39. case "error": return "error";
  40. case "separator": return null;
  41. case "open_paren": return null;
  42. case "close_paren": return null;
  43. default: return null;
  44. }
  45. }
  46. var typeWords = [
  47. "-type", "-spec", "-export_type", "-opaque"];
  48. var keywordWords = [
  49. "after","begin","catch","case","cond","end","fun","if",
  50. "let","of","query","receive","try","when"];
  51. var separatorWords = [
  52. "->",";",":",".",","];
  53. var operatorWords = [
  54. "and","andalso","band","bnot","bor","bsl","bsr","bxor",
  55. "div","not","or","orelse","rem","xor"];
  56. var symbolWords = [
  57. "+","-","*","/",">",">=","<","=<","=:=","==","=/=","/=","||","<-"];
  58. var openParenWords = [
  59. "<<","(","[","{"];
  60. var closeParenWords = [
  61. "}","]",")",">>"];
  62. var guardWords = [
  63. "is_atom","is_binary","is_bitstring","is_boolean","is_float",
  64. "is_function","is_integer","is_list","is_number","is_pid",
  65. "is_port","is_record","is_reference","is_tuple",
  66. "atom","binary","bitstring","boolean","function","integer","list",
  67. "number","pid","port","record","reference","tuple"];
  68. var bifWords = [
  69. "abs","adler32","adler32_combine","alive","apply","atom_to_binary",
  70. "atom_to_list","binary_to_atom","binary_to_existing_atom",
  71. "binary_to_list","binary_to_term","bit_size","bitstring_to_list",
  72. "byte_size","check_process_code","contact_binary","crc32",
  73. "crc32_combine","date","decode_packet","delete_module",
  74. "disconnect_node","element","erase","exit","float","float_to_list",
  75. "garbage_collect","get","get_keys","group_leader","halt","hd",
  76. "integer_to_list","internal_bif","iolist_size","iolist_to_binary",
  77. "is_alive","is_atom","is_binary","is_bitstring","is_boolean",
  78. "is_float","is_function","is_integer","is_list","is_number","is_pid",
  79. "is_port","is_process_alive","is_record","is_reference","is_tuple",
  80. "length","link","list_to_atom","list_to_binary","list_to_bitstring",
  81. "list_to_existing_atom","list_to_float","list_to_integer",
  82. "list_to_pid","list_to_tuple","load_module","make_ref","module_loaded",
  83. "monitor_node","node","node_link","node_unlink","nodes","notalive",
  84. "now","open_port","pid_to_list","port_close","port_command",
  85. "port_connect","port_control","pre_loaded","process_flag",
  86. "process_info","processes","purge_module","put","register",
  87. "registered","round","self","setelement","size","spawn","spawn_link",
  88. "spawn_monitor","spawn_opt","split_binary","statistics",
  89. "term_to_binary","time","throw","tl","trunc","tuple_size",
  90. "tuple_to_list","unlink","unregister","whereis"];
  91. // ignored for indenting purposes
  92. var ignoreWords = [
  93. ",", ":", "catch", "after", "of", "cond", "let", "query"];
  94. var smallRE = /[a-z_]/;
  95. var largeRE = /[A-Z_]/;
  96. var digitRE = /[0-9]/;
  97. var octitRE = /[0-7]/;
  98. var anumRE = /[a-z_A-Z0-9]/;
  99. var symbolRE = /[\+\-\*\/<>=\|:]/;
  100. var openParenRE = /[<\(\[\{]/;
  101. var closeParenRE = /[>\)\]\}]/;
  102. var sepRE = /[\->\.,:;]/;
  103. function isMember(element,list) {
  104. return (-1 < list.indexOf(element));
  105. }
  106. function isPrev(stream,string) {
  107. var start = stream.start;
  108. var len = string.length;
  109. if (len <= start) {
  110. var word = stream.string.slice(start-len,start);
  111. return word == string;
  112. }else{
  113. return false;
  114. }
  115. }
  116. function tokenize(stream, state) {
  117. if (stream.eatSpace()) {
  118. return rval(state,stream,"whitespace");
  119. }
  120. // attributes and type specs
  121. if ((peekToken(state).token == "" || peekToken(state).token == ".") &&
  122. stream.peek() == '-') {
  123. stream.next();
  124. if (stream.eat(smallRE) && stream.eatWhile(anumRE)) {
  125. if (isMember(stream.current(),typeWords)) {
  126. return rval(state,stream,"type");
  127. }else{
  128. return rval(state,stream,"attribute");
  129. }
  130. }
  131. stream.backUp(1);
  132. }
  133. var ch = stream.next();
  134. // comment
  135. if (ch == '%') {
  136. stream.skipToEnd();
  137. return rval(state,stream,"comment");
  138. }
  139. // macro
  140. if (ch == '?') {
  141. stream.eatWhile(anumRE);
  142. return rval(state,stream,"macro");
  143. }
  144. // record
  145. if ( ch == "#") {
  146. stream.eatWhile(anumRE);
  147. return rval(state,stream,"record");
  148. }
  149. // char
  150. if ( ch == "$") {
  151. if (stream.next() == "\\") {
  152. if (!stream.eatWhile(octitRE)) {
  153. stream.next();
  154. }
  155. }
  156. return rval(state,stream,"string");
  157. }
  158. // quoted atom
  159. if (ch == '\'') {
  160. if (singleQuote(stream)) {
  161. return rval(state,stream,"atom");
  162. }else{
  163. return rval(state,stream,"error");
  164. }
  165. }
  166. // string
  167. if (ch == '"') {
  168. if (doubleQuote(stream)) {
  169. return rval(state,stream,"string");
  170. }else{
  171. return rval(state,stream,"error");
  172. }
  173. }
  174. // variable
  175. if (largeRE.test(ch)) {
  176. stream.eatWhile(anumRE);
  177. return rval(state,stream,"variable");
  178. }
  179. // atom/keyword/BIF/function
  180. if (smallRE.test(ch)) {
  181. stream.eatWhile(anumRE);
  182. if (stream.peek() == "/") {
  183. stream.next();
  184. if (stream.eatWhile(digitRE)) {
  185. return rval(state,stream,"fun"); // f/0 style fun
  186. }else{
  187. stream.backUp(1);
  188. return rval(state,stream,"atom");
  189. }
  190. }
  191. var w = stream.current();
  192. if (isMember(w,keywordWords)) {
  193. pushToken(state,stream);
  194. return rval(state,stream,"keyword");
  195. }
  196. if (stream.peek() == "(") {
  197. // 'put' and 'erlang:put' are bifs, 'foo:put' is not
  198. if (isMember(w,bifWords) &&
  199. (!isPrev(stream,":") || isPrev(stream,"erlang:"))) {
  200. return rval(state,stream,"builtin");
  201. }else{
  202. return rval(state,stream,"function");
  203. }
  204. }
  205. if (isMember(w,guardWords)) {
  206. return rval(state,stream,"guard");
  207. }
  208. if (isMember(w,operatorWords)) {
  209. return rval(state,stream,"operator");
  210. }
  211. if (stream.peek() == ":") {
  212. if (w == "erlang") {
  213. return rval(state,stream,"builtin");
  214. } else {
  215. return rval(state,stream,"function");
  216. }
  217. }
  218. return rval(state,stream,"atom");
  219. }
  220. // number
  221. if (digitRE.test(ch)) {
  222. stream.eatWhile(digitRE);
  223. if (stream.eat('#')) {
  224. stream.eatWhile(digitRE); // 16#10 style integer
  225. } else {
  226. if (stream.eat('.')) { // float
  227. stream.eatWhile(digitRE);
  228. }
  229. if (stream.eat(/[eE]/)) {
  230. stream.eat(/[-+]/); // float with exponent
  231. stream.eatWhile(digitRE);
  232. }
  233. }
  234. return rval(state,stream,"number"); // normal integer
  235. }
  236. // open parens
  237. if (nongreedy(stream,openParenRE,openParenWords)) {
  238. pushToken(state,stream);
  239. return rval(state,stream,"open_paren");
  240. }
  241. // close parens
  242. if (nongreedy(stream,closeParenRE,closeParenWords)) {
  243. pushToken(state,stream);
  244. return rval(state,stream,"close_paren");
  245. }
  246. // separators
  247. if (greedy(stream,sepRE,separatorWords)) {
  248. // distinguish between "." as terminator and record field operator
  249. if (state.context == false) {
  250. pushToken(state,stream);
  251. }
  252. return rval(state,stream,"separator");
  253. }
  254. // operators
  255. if (greedy(stream,symbolRE,symbolWords)) {
  256. return rval(state,stream,"operator");
  257. }
  258. return rval(state,stream,null);
  259. }
  260. function nongreedy(stream,re,words) {
  261. if (stream.current().length == 1 && re.test(stream.current())) {
  262. stream.backUp(1);
  263. while (re.test(stream.peek())) {
  264. stream.next();
  265. if (isMember(stream.current(),words)) {
  266. return true;
  267. }
  268. }
  269. stream.backUp(stream.current().length-1);
  270. }
  271. return false;
  272. }
  273. function greedy(stream,re,words) {
  274. if (stream.current().length == 1 && re.test(stream.current())) {
  275. while (re.test(stream.peek())) {
  276. stream.next();
  277. }
  278. while (0 < stream.current().length) {
  279. if (isMember(stream.current(),words)) {
  280. return true;
  281. }else{
  282. stream.backUp(1);
  283. }
  284. }
  285. stream.next();
  286. }
  287. return false;
  288. }
  289. function doubleQuote(stream) {
  290. return quote(stream, '"', '\\');
  291. }
  292. function singleQuote(stream) {
  293. return quote(stream,'\'','\\');
  294. }
  295. function quote(stream,quoteChar,escapeChar) {
  296. while (!stream.eol()) {
  297. var ch = stream.next();
  298. if (ch == quoteChar) {
  299. return true;
  300. }else if (ch == escapeChar) {
  301. stream.next();
  302. }
  303. }
  304. return false;
  305. }
  306. function Token(stream) {
  307. this.token = stream ? stream.current() : "";
  308. this.column = stream ? stream.column() : 0;
  309. this.indent = stream ? stream.indentation() : 0;
  310. }
  311. function myIndent(state,textAfter) {
  312. var indent = cmCfg.indentUnit;
  313. var outdentWords = ["after","catch"];
  314. var token = (peekToken(state)).token;
  315. var wordAfter = takewhile(textAfter,/[^a-z]/);
  316. if (isMember(token,openParenWords)) {
  317. return (peekToken(state)).column+token.length;
  318. }else if (token == "." || token == ""){
  319. return 0;
  320. }else if (token == "->") {
  321. if (wordAfter == "end") {
  322. return peekToken(state,2).column;
  323. }else if (peekToken(state,2).token == "fun") {
  324. return peekToken(state,2).column+indent;
  325. }else{
  326. return (peekToken(state)).indent+indent;
  327. }
  328. }else if (isMember(wordAfter,outdentWords)) {
  329. return (peekToken(state)).indent;
  330. }else{
  331. return (peekToken(state)).column+indent;
  332. }
  333. }
  334. function takewhile(str,re) {
  335. var m = str.match(re);
  336. return m ? str.slice(0,m.index) : str;
  337. }
  338. function popToken(state) {
  339. return state.tokenStack.pop();
  340. }
  341. function peekToken(state,depth) {
  342. var len = state.tokenStack.length;
  343. var dep = (depth ? depth : 1);
  344. if (len < dep) {
  345. return new Token;
  346. }else{
  347. return state.tokenStack[len-dep];
  348. }
  349. }
  350. function pushToken(state,stream) {
  351. var token = stream.current();
  352. var prev_token = peekToken(state).token;
  353. if (isMember(token,ignoreWords)) {
  354. return false;
  355. }else if (drop_both(prev_token,token)) {
  356. popToken(state);
  357. return false;
  358. }else if (drop_first(prev_token,token)) {
  359. popToken(state);
  360. return pushToken(state,stream);
  361. }else{
  362. state.tokenStack.push(new Token(stream));
  363. return true;
  364. }
  365. }
  366. function drop_first(open, close) {
  367. switch (open+" "+close) {
  368. case "when ->": return true;
  369. case "-> end": return true;
  370. case "-> .": return true;
  371. case ". .": return true;
  372. default: return false;
  373. }
  374. }
  375. function drop_both(open, close) {
  376. switch (open+" "+close) {
  377. case "( )": return true;
  378. case "[ ]": return true;
  379. case "{ }": return true;
  380. case "<< >>": return true;
  381. case "begin end": return true;
  382. case "case end": return true;
  383. case "fun end": return true;
  384. case "if end": return true;
  385. case "receive end": return true;
  386. case "try end": return true;
  387. case "-> ;": return true;
  388. default: return false;
  389. }
  390. }
  391. return {
  392. startState:
  393. function() {
  394. return {tokenStack: [],
  395. context: false,
  396. lastToken: null};
  397. },
  398. token:
  399. function(stream, state) {
  400. return tokenize(stream, state);
  401. },
  402. indent:
  403. function(state, textAfter) {
  404. return myIndent(state,textAfter);
  405. },
  406. lineComment: "%"
  407. };
  408. });