0
|
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
2 // Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
|
3
|
|
4 /*jshint unused:true, eqnull:true, curly:true, bitwise:true */
|
|
5 /*jshint undef:true, latedef:true, trailing:true */
|
|
6 /*global CodeMirror:true */
|
|
7
|
|
8 // erlang mode.
|
|
9 // tokenizer -> token types -> CodeMirror styles
|
|
10 // tokenizer maintains a parse stack
|
|
11 // indenter uses the parse stack
|
|
12
|
|
13 // TODO indenter:
|
|
14 // bit syntax
|
|
15 // old guard/bif/conversion clashes (e.g. "float/1")
|
|
16 // type/spec/opaque
|
|
17
|
|
18 (function(mod) {
|
|
19 if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
20 mod(require("../../lib/codemirror"));
|
|
21 else if (typeof define == "function" && define.amd) // AMD
|
|
22 define(["../../lib/codemirror"], mod);
|
|
23 else // Plain browser env
|
|
24 mod(CodeMirror);
|
|
25 })(function(CodeMirror) {
|
|
26 "use strict";
|
|
27
|
|
28 CodeMirror.defineMIME("text/x-erlang", "erlang");
|
|
29
|
|
30 CodeMirror.defineMode("erlang", function(cmCfg) {
|
|
31 "use strict";
|
|
32
|
|
33 /////////////////////////////////////////////////////////////////////////////
|
|
34 // constants
|
|
35
|
|
36 var typeWords = [
|
|
37 "-type", "-spec", "-export_type", "-opaque"];
|
|
38
|
|
39 var keywordWords = [
|
|
40 "after","begin","catch","case","cond","end","fun","if",
|
|
41 "let","of","query","receive","try","when"];
|
|
42
|
|
43 var separatorRE = /[\->,;]/;
|
|
44 var separatorWords = [
|
|
45 "->",";",","];
|
|
46
|
|
47 var operatorAtomWords = [
|
|
48 "and","andalso","band","bnot","bor","bsl","bsr","bxor",
|
|
49 "div","not","or","orelse","rem","xor"];
|
|
50
|
|
51 var operatorSymbolRE = /[\+\-\*\/<>=\|:!]/;
|
|
52 var operatorSymbolWords = [
|
|
53 "=","+","-","*","/",">",">=","<","=<","=:=","==","=/=","/=","||","<-","!"];
|
|
54
|
|
55 var openParenRE = /[<\(\[\{]/;
|
|
56 var openParenWords = [
|
|
57 "<<","(","[","{"];
|
|
58
|
|
59 var closeParenRE = /[>\)\]\}]/;
|
|
60 var closeParenWords = [
|
|
61 "}","]",")",">>"];
|
|
62
|
|
63 var guardWords = [
|
|
64 "is_atom","is_binary","is_bitstring","is_boolean","is_float",
|
|
65 "is_function","is_integer","is_list","is_number","is_pid",
|
|
66 "is_port","is_record","is_reference","is_tuple",
|
|
67 "atom","binary","bitstring","boolean","function","integer","list",
|
|
68 "number","pid","port","record","reference","tuple"];
|
|
69
|
|
70 var bifWords = [
|
|
71 "abs","adler32","adler32_combine","alive","apply","atom_to_binary",
|
|
72 "atom_to_list","binary_to_atom","binary_to_existing_atom",
|
|
73 "binary_to_list","binary_to_term","bit_size","bitstring_to_list",
|
|
74 "byte_size","check_process_code","contact_binary","crc32",
|
|
75 "crc32_combine","date","decode_packet","delete_module",
|
|
76 "disconnect_node","element","erase","exit","float","float_to_list",
|
|
77 "garbage_collect","get","get_keys","group_leader","halt","hd",
|
|
78 "integer_to_list","internal_bif","iolist_size","iolist_to_binary",
|
|
79 "is_alive","is_atom","is_binary","is_bitstring","is_boolean",
|
|
80 "is_float","is_function","is_integer","is_list","is_number","is_pid",
|
|
81 "is_port","is_process_alive","is_record","is_reference","is_tuple",
|
|
82 "length","link","list_to_atom","list_to_binary","list_to_bitstring",
|
|
83 "list_to_existing_atom","list_to_float","list_to_integer",
|
|
84 "list_to_pid","list_to_tuple","load_module","make_ref","module_loaded",
|
|
85 "monitor_node","node","node_link","node_unlink","nodes","notalive",
|
|
86 "now","open_port","pid_to_list","port_close","port_command",
|
|
87 "port_connect","port_control","pre_loaded","process_flag",
|
|
88 "process_info","processes","purge_module","put","register",
|
|
89 "registered","round","self","setelement","size","spawn","spawn_link",
|
|
90 "spawn_monitor","spawn_opt","split_binary","statistics",
|
|
91 "term_to_binary","time","throw","tl","trunc","tuple_size",
|
|
92 "tuple_to_list","unlink","unregister","whereis"];
|
|
93
|
|
94 // upper case: [A-Z] [Ø-Þ] [À-Ö]
|
|
95 // lower case: [a-z] [ß-ö] [ø-ÿ]
|
|
96 var anumRE = /[\w@Ø-ÞÀ-Öß-öø-ÿ]/;
|
|
97 var escapesRE =
|
|
98 /[0-7]{1,3}|[bdefnrstv\\"']|\^[a-zA-Z]|x[0-9a-zA-Z]{2}|x{[0-9a-zA-Z]+}/;
|
|
99
|
|
100 /////////////////////////////////////////////////////////////////////////////
|
|
101 // tokenizer
|
|
102
|
|
103 function tokenizer(stream,state) {
|
|
104 // in multi-line string
|
|
105 if (state.in_string) {
|
|
106 state.in_string = (!doubleQuote(stream));
|
|
107 return rval(state,stream,"string");
|
|
108 }
|
|
109
|
|
110 // in multi-line atom
|
|
111 if (state.in_atom) {
|
|
112 state.in_atom = (!singleQuote(stream));
|
|
113 return rval(state,stream,"atom");
|
|
114 }
|
|
115
|
|
116 // whitespace
|
|
117 if (stream.eatSpace()) {
|
|
118 return rval(state,stream,"whitespace");
|
|
119 }
|
|
120
|
|
121 // attributes and type specs
|
|
122 if (!peekToken(state) &&
|
|
123 stream.match(/-\s*[a-zß-öø-ÿ][\wØ-ÞÀ-Öß-öø-ÿ]*/)) {
|
|
124 if (is_member(stream.current(),typeWords)) {
|
|
125 return rval(state,stream,"type");
|
|
126 }else{
|
|
127 return rval(state,stream,"attribute");
|
|
128 }
|
|
129 }
|
|
130
|
|
131 var ch = stream.next();
|
|
132
|
|
133 // comment
|
|
134 if (ch == '%') {
|
|
135 stream.skipToEnd();
|
|
136 return rval(state,stream,"comment");
|
|
137 }
|
|
138
|
|
139 // colon
|
|
140 if (ch == ":") {
|
|
141 return rval(state,stream,"colon");
|
|
142 }
|
|
143
|
|
144 // macro
|
|
145 if (ch == '?') {
|
|
146 stream.eatSpace();
|
|
147 stream.eatWhile(anumRE);
|
|
148 return rval(state,stream,"macro");
|
|
149 }
|
|
150
|
|
151 // record
|
|
152 if (ch == "#") {
|
|
153 stream.eatSpace();
|
|
154 stream.eatWhile(anumRE);
|
|
155 return rval(state,stream,"record");
|
|
156 }
|
|
157
|
|
158 // dollar escape
|
|
159 if (ch == "$") {
|
|
160 if (stream.next() == "\\" && !stream.match(escapesRE)) {
|
|
161 return rval(state,stream,"error");
|
|
162 }
|
|
163 return rval(state,stream,"number");
|
|
164 }
|
|
165
|
|
166 // dot
|
|
167 if (ch == ".") {
|
|
168 return rval(state,stream,"dot");
|
|
169 }
|
|
170
|
|
171 // quoted atom
|
|
172 if (ch == '\'') {
|
|
173 if (!(state.in_atom = (!singleQuote(stream)))) {
|
|
174 if (stream.match(/\s*\/\s*[0-9]/,false)) {
|
|
175 stream.match(/\s*\/\s*[0-9]/,true);
|
|
176 return rval(state,stream,"fun"); // 'f'/0 style fun
|
|
177 }
|
|
178 if (stream.match(/\s*\(/,false) || stream.match(/\s*:/,false)) {
|
|
179 return rval(state,stream,"function");
|
|
180 }
|
|
181 }
|
|
182 return rval(state,stream,"atom");
|
|
183 }
|
|
184
|
|
185 // string
|
|
186 if (ch == '"') {
|
|
187 state.in_string = (!doubleQuote(stream));
|
|
188 return rval(state,stream,"string");
|
|
189 }
|
|
190
|
|
191 // variable
|
|
192 if (/[A-Z_Ø-ÞÀ-Ö]/.test(ch)) {
|
|
193 stream.eatWhile(anumRE);
|
|
194 return rval(state,stream,"variable");
|
|
195 }
|
|
196
|
|
197 // atom/keyword/BIF/function
|
|
198 if (/[a-z_ß-öø-ÿ]/.test(ch)) {
|
|
199 stream.eatWhile(anumRE);
|
|
200
|
|
201 if (stream.match(/\s*\/\s*[0-9]/,false)) {
|
|
202 stream.match(/\s*\/\s*[0-9]/,true);
|
|
203 return rval(state,stream,"fun"); // f/0 style fun
|
|
204 }
|
|
205
|
|
206 var w = stream.current();
|
|
207
|
|
208 if (is_member(w,keywordWords)) {
|
|
209 return rval(state,stream,"keyword");
|
|
210 }else if (is_member(w,operatorAtomWords)) {
|
|
211 return rval(state,stream,"operator");
|
|
212 }else if (stream.match(/\s*\(/,false)) {
|
|
213 // 'put' and 'erlang:put' are bifs, 'foo:put' is not
|
|
214 if (is_member(w,bifWords) &&
|
|
215 ((peekToken(state).token != ":") ||
|
|
216 (peekToken(state,2).token == "erlang"))) {
|
|
217 return rval(state,stream,"builtin");
|
|
218 }else if (is_member(w,guardWords)) {
|
|
219 return rval(state,stream,"guard");
|
|
220 }else{
|
|
221 return rval(state,stream,"function");
|
|
222 }
|
|
223 }else if (lookahead(stream) == ":") {
|
|
224 if (w == "erlang") {
|
|
225 return rval(state,stream,"builtin");
|
|
226 } else {
|
|
227 return rval(state,stream,"function");
|
|
228 }
|
|
229 }else if (is_member(w,["true","false"])) {
|
|
230 return rval(state,stream,"boolean");
|
|
231 }else{
|
|
232 return rval(state,stream,"atom");
|
|
233 }
|
|
234 }
|
|
235
|
|
236 // number
|
|
237 var digitRE = /[0-9]/;
|
|
238 var radixRE = /[0-9a-zA-Z]/; // 36#zZ style int
|
|
239 if (digitRE.test(ch)) {
|
|
240 stream.eatWhile(digitRE);
|
|
241 if (stream.eat('#')) { // 36#aZ style integer
|
|
242 if (!stream.eatWhile(radixRE)) {
|
|
243 stream.backUp(1); //"36#" - syntax error
|
|
244 }
|
|
245 } else if (stream.eat('.')) { // float
|
|
246 if (!stream.eatWhile(digitRE)) {
|
|
247 stream.backUp(1); // "3." - probably end of function
|
|
248 } else {
|
|
249 if (stream.eat(/[eE]/)) { // float with exponent
|
|
250 if (stream.eat(/[-+]/)) {
|
|
251 if (!stream.eatWhile(digitRE)) {
|
|
252 stream.backUp(2); // "2e-" - syntax error
|
|
253 }
|
|
254 } else {
|
|
255 if (!stream.eatWhile(digitRE)) {
|
|
256 stream.backUp(1); // "2e" - syntax error
|
|
257 }
|
|
258 }
|
|
259 }
|
|
260 }
|
|
261 }
|
|
262 return rval(state,stream,"number"); // normal integer
|
|
263 }
|
|
264
|
|
265 // open parens
|
|
266 if (nongreedy(stream,openParenRE,openParenWords)) {
|
|
267 return rval(state,stream,"open_paren");
|
|
268 }
|
|
269
|
|
270 // close parens
|
|
271 if (nongreedy(stream,closeParenRE,closeParenWords)) {
|
|
272 return rval(state,stream,"close_paren");
|
|
273 }
|
|
274
|
|
275 // separators
|
|
276 if (greedy(stream,separatorRE,separatorWords)) {
|
|
277 return rval(state,stream,"separator");
|
|
278 }
|
|
279
|
|
280 // operators
|
|
281 if (greedy(stream,operatorSymbolRE,operatorSymbolWords)) {
|
|
282 return rval(state,stream,"operator");
|
|
283 }
|
|
284
|
|
285 return rval(state,stream,null);
|
|
286 }
|
|
287
|
|
288 /////////////////////////////////////////////////////////////////////////////
|
|
289 // utilities
|
|
290 function nongreedy(stream,re,words) {
|
|
291 if (stream.current().length == 1 && re.test(stream.current())) {
|
|
292 stream.backUp(1);
|
|
293 while (re.test(stream.peek())) {
|
|
294 stream.next();
|
|
295 if (is_member(stream.current(),words)) {
|
|
296 return true;
|
|
297 }
|
|
298 }
|
|
299 stream.backUp(stream.current().length-1);
|
|
300 }
|
|
301 return false;
|
|
302 }
|
|
303
|
|
304 function greedy(stream,re,words) {
|
|
305 if (stream.current().length == 1 && re.test(stream.current())) {
|
|
306 while (re.test(stream.peek())) {
|
|
307 stream.next();
|
|
308 }
|
|
309 while (0 < stream.current().length) {
|
|
310 if (is_member(stream.current(),words)) {
|
|
311 return true;
|
|
312 }else{
|
|
313 stream.backUp(1);
|
|
314 }
|
|
315 }
|
|
316 stream.next();
|
|
317 }
|
|
318 return false;
|
|
319 }
|
|
320
|
|
321 function doubleQuote(stream) {
|
|
322 return quote(stream, '"', '\\');
|
|
323 }
|
|
324
|
|
325 function singleQuote(stream) {
|
|
326 return quote(stream,'\'','\\');
|
|
327 }
|
|
328
|
|
329 function quote(stream,quoteChar,escapeChar) {
|
|
330 while (!stream.eol()) {
|
|
331 var ch = stream.next();
|
|
332 if (ch == quoteChar) {
|
|
333 return true;
|
|
334 }else if (ch == escapeChar) {
|
|
335 stream.next();
|
|
336 }
|
|
337 }
|
|
338 return false;
|
|
339 }
|
|
340
|
|
341 function lookahead(stream) {
|
|
342 var m = stream.match(/^\s*([^\s%])/, false)
|
|
343 return m ? m[1] : "";
|
|
344 }
|
|
345
|
|
346 function is_member(element,list) {
|
|
347 return (-1 < list.indexOf(element));
|
|
348 }
|
|
349
|
|
350 function rval(state,stream,type) {
|
|
351
|
|
352 // parse stack
|
|
353 pushToken(state,realToken(type,stream));
|
|
354
|
|
355 // map erlang token type to CodeMirror style class
|
|
356 // erlang -> CodeMirror tag
|
|
357 switch (type) {
|
|
358 case "atom": return "atom";
|
|
359 case "attribute": return "attribute";
|
|
360 case "boolean": return "atom";
|
|
361 case "builtin": return "builtin";
|
|
362 case "close_paren": return null;
|
|
363 case "colon": return null;
|
|
364 case "comment": return "comment";
|
|
365 case "dot": return null;
|
|
366 case "error": return "error";
|
|
367 case "fun": return "meta";
|
|
368 case "function": return "tag";
|
|
369 case "guard": return "property";
|
|
370 case "keyword": return "keyword";
|
|
371 case "macro": return "variable-2";
|
|
372 case "number": return "number";
|
|
373 case "open_paren": return null;
|
|
374 case "operator": return "operator";
|
|
375 case "record": return "bracket";
|
|
376 case "separator": return null;
|
|
377 case "string": return "string";
|
|
378 case "type": return "def";
|
|
379 case "variable": return "variable";
|
|
380 default: return null;
|
|
381 }
|
|
382 }
|
|
383
|
|
384 function aToken(tok,col,ind,typ) {
|
|
385 return {token: tok,
|
|
386 column: col,
|
|
387 indent: ind,
|
|
388 type: typ};
|
|
389 }
|
|
390
|
|
391 function realToken(type,stream) {
|
|
392 return aToken(stream.current(),
|
|
393 stream.column(),
|
|
394 stream.indentation(),
|
|
395 type);
|
|
396 }
|
|
397
|
|
398 function fakeToken(type) {
|
|
399 return aToken(type,0,0,type);
|
|
400 }
|
|
401
|
|
402 function peekToken(state,depth) {
|
|
403 var len = state.tokenStack.length;
|
|
404 var dep = (depth ? depth : 1);
|
|
405
|
|
406 if (len < dep) {
|
|
407 return false;
|
|
408 }else{
|
|
409 return state.tokenStack[len-dep];
|
|
410 }
|
|
411 }
|
|
412
|
|
413 function pushToken(state,token) {
|
|
414
|
|
415 if (!(token.type == "comment" || token.type == "whitespace")) {
|
|
416 state.tokenStack = maybe_drop_pre(state.tokenStack,token);
|
|
417 state.tokenStack = maybe_drop_post(state.tokenStack);
|
|
418 }
|
|
419 }
|
|
420
|
|
421 function maybe_drop_pre(s,token) {
|
|
422 var last = s.length-1;
|
|
423
|
|
424 if (0 < last && s[last].type === "record" && token.type === "dot") {
|
|
425 s.pop();
|
|
426 }else if (0 < last && s[last].type === "group") {
|
|
427 s.pop();
|
|
428 s.push(token);
|
|
429 }else{
|
|
430 s.push(token);
|
|
431 }
|
|
432 return s;
|
|
433 }
|
|
434
|
|
435 function maybe_drop_post(s) {
|
|
436 if (!s.length) return s
|
|
437 var last = s.length-1;
|
|
438
|
|
439 if (s[last].type === "dot") {
|
|
440 return [];
|
|
441 }
|
|
442 if (last > 1 && s[last].type === "fun" && s[last-1].token === "fun") {
|
|
443 return s.slice(0,last-1);
|
|
444 }
|
|
445 switch (s[last].token) {
|
|
446 case "}": return d(s,{g:["{"]});
|
|
447 case "]": return d(s,{i:["["]});
|
|
448 case ")": return d(s,{i:["("]});
|
|
449 case ">>": return d(s,{i:["<<"]});
|
|
450 case "end": return d(s,{i:["begin","case","fun","if","receive","try"]});
|
|
451 case ",": return d(s,{e:["begin","try","when","->",
|
|
452 ",","(","[","{","<<"]});
|
|
453 case "->": return d(s,{r:["when"],
|
|
454 m:["try","if","case","receive"]});
|
|
455 case ";": return d(s,{E:["case","fun","if","receive","try","when"]});
|
|
456 case "catch":return d(s,{e:["try"]});
|
|
457 case "of": return d(s,{e:["case"]});
|
|
458 case "after":return d(s,{e:["receive","try"]});
|
|
459 default: return s;
|
|
460 }
|
|
461 }
|
|
462
|
|
463 function d(stack,tt) {
|
|
464 // stack is a stack of Token objects.
|
|
465 // tt is an object; {type:tokens}
|
|
466 // type is a char, tokens is a list of token strings.
|
|
467 // The function returns (possibly truncated) stack.
|
|
468 // It will descend the stack, looking for a Token such that Token.token
|
|
469 // is a member of tokens. If it does not find that, it will normally (but
|
|
470 // see "E" below) return stack. If it does find a match, it will remove
|
|
471 // all the Tokens between the top and the matched Token.
|
|
472 // If type is "m", that is all it does.
|
|
473 // If type is "i", it will also remove the matched Token and the top Token.
|
|
474 // If type is "g", like "i", but add a fake "group" token at the top.
|
|
475 // If type is "r", it will remove the matched Token, but not the top Token.
|
|
476 // If type is "e", it will keep the matched Token but not the top Token.
|
|
477 // If type is "E", it behaves as for type "e", except if there is no match,
|
|
478 // in which case it will return an empty stack.
|
|
479
|
|
480 for (var type in tt) {
|
|
481 var len = stack.length-1;
|
|
482 var tokens = tt[type];
|
|
483 for (var i = len-1; -1 < i ; i--) {
|
|
484 if (is_member(stack[i].token,tokens)) {
|
|
485 var ss = stack.slice(0,i);
|
|
486 switch (type) {
|
|
487 case "m": return ss.concat(stack[i]).concat(stack[len]);
|
|
488 case "r": return ss.concat(stack[len]);
|
|
489 case "i": return ss;
|
|
490 case "g": return ss.concat(fakeToken("group"));
|
|
491 case "E": return ss.concat(stack[i]);
|
|
492 case "e": return ss.concat(stack[i]);
|
|
493 }
|
|
494 }
|
|
495 }
|
|
496 }
|
|
497 return (type == "E" ? [] : stack);
|
|
498 }
|
|
499
|
|
500 /////////////////////////////////////////////////////////////////////////////
|
|
501 // indenter
|
|
502
|
|
503 function indenter(state,textAfter) {
|
|
504 var t;
|
|
505 var unit = cmCfg.indentUnit;
|
|
506 var wordAfter = wordafter(textAfter);
|
|
507 var currT = peekToken(state,1);
|
|
508 var prevT = peekToken(state,2);
|
|
509
|
|
510 if (state.in_string || state.in_atom) {
|
|
511 return CodeMirror.Pass;
|
|
512 }else if (!prevT) {
|
|
513 return 0;
|
|
514 }else if (currT.token == "when") {
|
|
515 return currT.column+unit;
|
|
516 }else if (wordAfter === "when" && prevT.type === "function") {
|
|
517 return prevT.indent+unit;
|
|
518 }else if (wordAfter === "(" && currT.token === "fun") {
|
|
519 return currT.column+3;
|
|
520 }else if (wordAfter === "catch" && (t = getToken(state,["try"]))) {
|
|
521 return t.column;
|
|
522 }else if (is_member(wordAfter,["end","after","of"])) {
|
|
523 t = getToken(state,["begin","case","fun","if","receive","try"]);
|
|
524 return t ? t.column : CodeMirror.Pass;
|
|
525 }else if (is_member(wordAfter,closeParenWords)) {
|
|
526 t = getToken(state,openParenWords);
|
|
527 return t ? t.column : CodeMirror.Pass;
|
|
528 }else if (is_member(currT.token,[",","|","||"]) ||
|
|
529 is_member(wordAfter,[",","|","||"])) {
|
|
530 t = postcommaToken(state);
|
|
531 return t ? t.column+t.token.length : unit;
|
|
532 }else if (currT.token == "->") {
|
|
533 if (is_member(prevT.token, ["receive","case","if","try"])) {
|
|
534 return prevT.column+unit+unit;
|
|
535 }else{
|
|
536 return prevT.column+unit;
|
|
537 }
|
|
538 }else if (is_member(currT.token,openParenWords)) {
|
|
539 return currT.column+currT.token.length;
|
|
540 }else{
|
|
541 t = defaultToken(state);
|
|
542 return truthy(t) ? t.column+unit : 0;
|
|
543 }
|
|
544 }
|
|
545
|
|
546 function wordafter(str) {
|
|
547 var m = str.match(/,|[a-z]+|\}|\]|\)|>>|\|+|\(/);
|
|
548
|
|
549 return truthy(m) && (m.index === 0) ? m[0] : "";
|
|
550 }
|
|
551
|
|
552 function postcommaToken(state) {
|
|
553 var objs = state.tokenStack.slice(0,-1);
|
|
554 var i = getTokenIndex(objs,"type",["open_paren"]);
|
|
555
|
|
556 return truthy(objs[i]) ? objs[i] : false;
|
|
557 }
|
|
558
|
|
559 function defaultToken(state) {
|
|
560 var objs = state.tokenStack;
|
|
561 var stop = getTokenIndex(objs,"type",["open_paren","separator","keyword"]);
|
|
562 var oper = getTokenIndex(objs,"type",["operator"]);
|
|
563
|
|
564 if (truthy(stop) && truthy(oper) && stop < oper) {
|
|
565 return objs[stop+1];
|
|
566 } else if (truthy(stop)) {
|
|
567 return objs[stop];
|
|
568 } else {
|
|
569 return false;
|
|
570 }
|
|
571 }
|
|
572
|
|
573 function getToken(state,tokens) {
|
|
574 var objs = state.tokenStack;
|
|
575 var i = getTokenIndex(objs,"token",tokens);
|
|
576
|
|
577 return truthy(objs[i]) ? objs[i] : false;
|
|
578 }
|
|
579
|
|
580 function getTokenIndex(objs,propname,propvals) {
|
|
581
|
|
582 for (var i = objs.length-1; -1 < i ; i--) {
|
|
583 if (is_member(objs[i][propname],propvals)) {
|
|
584 return i;
|
|
585 }
|
|
586 }
|
|
587 return false;
|
|
588 }
|
|
589
|
|
590 function truthy(x) {
|
|
591 return (x !== false) && (x != null);
|
|
592 }
|
|
593
|
|
594 /////////////////////////////////////////////////////////////////////////////
|
|
595 // this object defines the mode
|
|
596
|
|
597 return {
|
|
598 startState:
|
|
599 function() {
|
|
600 return {tokenStack: [],
|
|
601 in_string: false,
|
|
602 in_atom: false};
|
|
603 },
|
|
604
|
|
605 token:
|
|
606 function(stream, state) {
|
|
607 return tokenizer(stream, state);
|
|
608 },
|
|
609
|
|
610 indent:
|
|
611 function(state, textAfter) {
|
|
612 return indenter(state,textAfter);
|
|
613 },
|
|
614
|
|
615 lineComment: "%"
|
|
616 };
|
|
617 });
|
|
618
|
|
619 });
|