Resources/Public/JavaScript/editormd/lib/codemirror/mode/erlang/erlang.js   F
last analyzed

Complexity

Total Complexity 123
Complexity/F 4.1

Size

Lines of Code 605
Function Count 30

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 123
eloc 382
c 0
b 0
f 0
dl 0
loc 605
rs 2
mnd 93
bc 93
fnc 30
bpm 3.1
cpm 4.1
noi 12

24 Functions

Rating   Name   Duplication   Size   Complexity  
A erlang.js ➔ nongreedy 0 13 4
A erlang.js ➔ singleQuote 0 3 1
A erlang.js ➔ is_member 0 3 1
F erlang.js ➔ indenter 0 42 17
B erlang.js ➔ d 0 36 6
A erlang.js ➔ realToken 0 6 1
F erlang.js ➔ tokenizer 0 188 49
A erlang.js ➔ pushToken 0 7 2
A erlang.js ➔ aToken 0 6 1
A erlang.js ➔ getTokenIndex 0 9 3
A erlang.js ➔ wordafter 0 5 2
A erlang.js ➔ rval 0 33 2
A erlang.js ➔ quote 0 11 4
A erlang.js ➔ truthy 0 3 1
A erlang.js ➔ doubleQuote 0 3 1
A erlang.js ➔ lookahead 0 4 2
A erlang.js ➔ getToken 0 6 2
A erlang.js ➔ peekToken 0 10 3
A erlang.js ➔ fakeToken 0 3 1
A erlang.js ➔ defaultToken 0 13 3
A erlang.js ➔ maybe_drop_pre 0 13 3
A erlang.js ➔ postcommaToken 0 6 2
A erlang.js ➔ greedy 0 16 5
A erlang.js ➔ maybe_drop_post 0 26 4

How to fix   Complexity   

Complexity

Complex classes like Resources/Public/JavaScript/editormd/lib/codemirror/mode/erlang/erlang.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
// CodeMirror, copyright (c) by Marijn Haverbeke and others
2
// Distributed under an MIT license: http://codemirror.net/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
0 ignored issues
show
Bug introduced by
The variable define seems to be never declared. If this is a global, consider adding a /** global: define */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
22
    define(["../../lib/codemirror"], mod);
23
  else // Plain browser env
24
    mod(CodeMirror);
0 ignored issues
show
Bug introduced by
The variable CodeMirror seems to be never declared. If this is a global, consider adding a /** global: CodeMirror */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
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{
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
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 (is_member(w,operatorAtomWords)) {
224
        return rval(state,stream,"operator");
225
      }else if (lookahead(stream) == ":") {
226
        if (w == "erlang") {
227
          return rval(state,stream,"builtin");
228
        } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
229
          return rval(state,stream,"function");
230
        }
231
      }else if (is_member(w,["true","false"])) {
232
        return rval(state,stream,"boolean");
233
      }else if (is_member(w,["true","false"])) {
234
        return rval(state,stream,"boolean");
235
      }else{
236
        return rval(state,stream,"atom");
237
      }
238
    }
239
240
    // number
241
    var digitRE      = /[0-9]/;
242
    var radixRE      = /[0-9a-zA-Z]/;         // 36#zZ style int
243
    if (digitRE.test(ch)) {
244
      stream.eatWhile(digitRE);
245
      if (stream.eat('#')) {                // 36#aZ  style integer
246
        if (!stream.eatWhile(radixRE)) {
247
          stream.backUp(1);                 //"36#" - syntax error
248
        }
249
      } else if (stream.eat('.')) {       // float
250
        if (!stream.eatWhile(digitRE)) {
251
          stream.backUp(1);        // "3." - probably end of function
252
        } else {
253
          if (stream.eat(/[eE]/)) {        // float with exponent
254
            if (stream.eat(/[-+]/)) {
255
              if (!stream.eatWhile(digitRE)) {
256
                stream.backUp(2);            // "2e-" - syntax error
257
              }
258
            } else {
259
              if (!stream.eatWhile(digitRE)) {
260
                stream.backUp(1);            // "2e" - syntax error
261
              }
262
            }
263
          }
264
        }
265
      }
266
      return rval(state,stream,"number");   // normal integer
267
    }
268
269
    // open parens
270
    if (nongreedy(stream,openParenRE,openParenWords)) {
271
      return rval(state,stream,"open_paren");
272
    }
273
274
    // close parens
275
    if (nongreedy(stream,closeParenRE,closeParenWords)) {
276
      return rval(state,stream,"close_paren");
277
    }
278
279
    // separators
280
    if (greedy(stream,separatorRE,separatorWords)) {
281
      return rval(state,stream,"separator");
282
    }
283
284
    // operators
285
    if (greedy(stream,operatorSymbolRE,operatorSymbolWords)) {
286
      return rval(state,stream,"operator");
287
    }
288
289
    return rval(state,stream,null);
290
  }
291
292
/////////////////////////////////////////////////////////////////////////////
293
// utilities
294
  function nongreedy(stream,re,words) {
295
    if (stream.current().length == 1 && re.test(stream.current())) {
0 ignored issues
show
Best Practice introduced by
Comparing stream.current().length to 1 using the == operator is not safe. Consider using === instead.
Loading history...
296
      stream.backUp(1);
297
      while (re.test(stream.peek())) {
298
        stream.next();
299
        if (is_member(stream.current(),words)) {
300
          return true;
301
        }
302
      }
303
      stream.backUp(stream.current().length-1);
304
    }
305
    return false;
306
  }
307
308
  function greedy(stream,re,words) {
309
    if (stream.current().length == 1 && re.test(stream.current())) {
0 ignored issues
show
Best Practice introduced by
Comparing stream.current().length to 1 using the == operator is not safe. Consider using === instead.
Loading history...
310
      while (re.test(stream.peek())) {
311
        stream.next();
312
      }
313
      while (0 < stream.current().length) {
314
        if (is_member(stream.current(),words)) {
315
          return true;
316
        }else{
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
317
          stream.backUp(1);
318
        }
319
      }
320
      stream.next();
321
    }
322
    return false;
323
  }
324
325
  function doubleQuote(stream) {
326
    return quote(stream, '"', '\\');
327
  }
328
329
  function singleQuote(stream) {
330
    return quote(stream,'\'','\\');
331
  }
332
333
  function quote(stream,quoteChar,escapeChar) {
334
    while (!stream.eol()) {
335
      var ch = stream.next();
336
      if (ch == quoteChar) {
337
        return true;
338
      }else if (ch == escapeChar) {
339
        stream.next();
340
      }
341
    }
342
    return false;
343
  }
344
345
  function lookahead(stream) {
346
    var m = stream.match(/([\n\s]+|%[^\n]*\n)*(.)/,false);
347
    return m ? m.pop() : "";
348
  }
349
350
  function is_member(element,list) {
351
    return (-1 < list.indexOf(element));
352
  }
353
354
  function rval(state,stream,type) {
355
356
    // parse stack
357
    pushToken(state,realToken(type,stream));
358
359
    // map erlang token type to CodeMirror style class
360
    //     erlang             -> CodeMirror tag
361
    switch (type) {
362
      case "atom":        return "atom";
363
      case "attribute":   return "attribute";
364
      case "boolean":     return "atom";
365
      case "builtin":     return "builtin";
366
      case "close_paren": return null;
367
      case "colon":       return null;
368
      case "comment":     return "comment";
369
      case "dot":         return null;
370
      case "error":       return "error";
371
      case "fun":         return "meta";
372
      case "function":    return "tag";
373
      case "guard":       return "property";
374
      case "keyword":     return "keyword";
375
      case "macro":       return "variable-2";
376
      case "number":      return "number";
377
      case "open_paren":  return null;
378
      case "operator":    return "operator";
379
      case "record":      return "bracket";
380
      case "separator":   return null;
381
      case "string":      return "string";
382
      case "type":        return "def";
383
      case "variable":    return "variable";
384
      default:            return null;
385
    }
386
  }
387
388
  function aToken(tok,col,ind,typ) {
389
    return {token:  tok,
390
            column: col,
391
            indent: ind,
392
            type:   typ};
393
  }
394
395
  function realToken(type,stream) {
396
    return aToken(stream.current(),
397
                 stream.column(),
398
                 stream.indentation(),
399
                 type);
400
  }
401
402
  function fakeToken(type) {
403
    return aToken(type,0,0,type);
404
  }
405
406
  function peekToken(state,depth) {
407
    var len = state.tokenStack.length;
408
    var dep = (depth ? depth : 1);
409
410
    if (len < dep) {
411
      return false;
412
    }else{
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
413
      return state.tokenStack[len-dep];
414
    }
415
  }
416
417
  function pushToken(state,token) {
418
419
    if (!(token.type == "comment" || token.type == "whitespace")) {
420
      state.tokenStack = maybe_drop_pre(state.tokenStack,token);
421
      state.tokenStack = maybe_drop_post(state.tokenStack);
422
    }
423
  }
424
425
  function maybe_drop_pre(s,token) {
426
    var last = s.length-1;
427
428
    if (0 < last && s[last].type === "record" && token.type === "dot") {
429
      s.pop();
430
    }else if (0 < last && s[last].type === "group") {
431
      s.pop();
432
      s.push(token);
433
    }else{
434
      s.push(token);
435
    }
436
    return s;
437
  }
438
439
  function maybe_drop_post(s) {
440
    var last = s.length-1;
441
442
    if (s[last].type === "dot") {
443
      return [];
444
    }
445
    if (s[last].type === "fun" && s[last-1].token === "fun") {
446
      return s.slice(0,last-1);
447
    }
448
    switch (s[s.length-1].token) {
449
      case "}":    return d(s,{g:["{"]});
450
      case "]":    return d(s,{i:["["]});
451
      case ")":    return d(s,{i:["("]});
452
      case ">>":   return d(s,{i:["<<"]});
453
      case "end":  return d(s,{i:["begin","case","fun","if","receive","try"]});
454
      case ",":    return d(s,{e:["begin","try","when","->",
455
                                  ",","(","[","{","<<"]});
456
      case "->":   return d(s,{r:["when"],
457
                               m:["try","if","case","receive"]});
458
      case ";":    return d(s,{E:["case","fun","if","receive","try","when"]});
459
      case "catch":return d(s,{e:["try"]});
460
      case "of":   return d(s,{e:["case"]});
461
      case "after":return d(s,{e:["receive","try"]});
462
      default:     return s;
463
    }
464
  }
465
466
  function d(stack,tt) {
467
    // stack is a stack of Token objects.
468
    // tt is an object; {type:tokens}
469
    // type is a char, tokens is a list of token strings.
470
    // The function returns (possibly truncated) stack.
471
    // It will descend the stack, looking for a Token such that Token.token
472
    //  is a member of tokens. If it does not find that, it will normally (but
473
    //  see "E" below) return stack. If it does find a match, it will remove
474
    //  all the Tokens between the top and the matched Token.
475
    // If type is "m", that is all it does.
476
    // If type is "i", it will also remove the matched Token and the top Token.
477
    // If type is "g", like "i", but add a fake "group" token at the top.
478
    // If type is "r", it will remove the matched Token, but not the top Token.
479
    // If type is "e", it will keep the matched Token but not the top Token.
480
    // If type is "E", it behaves as for type "e", except if there is no match,
481
    //  in which case it will return an empty stack.
482
483
    for (var type in tt) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
484
      var len = stack.length-1;
485
      var tokens = tt[type];
486
      for (var i = len-1; -1 < i ; i--) {
487
        if (is_member(stack[i].token,tokens)) {
488
          var ss = stack.slice(0,i);
489
          switch (type) {
490
              case "m": return ss.concat(stack[i]).concat(stack[len]);
491
              case "r": return ss.concat(stack[len]);
492
              case "i": return ss;
493
              case "g": return ss.concat(fakeToken("group"));
494
              case "E": return ss.concat(stack[i]);
495
              case "e": return ss.concat(stack[i]);
496
          }
497
        }
498
      }
499
    }
500
    return (type == "E" ? [] : stack);
0 ignored issues
show
Bug introduced by
The variable type seems to not be initialized for all possible execution paths.
Loading history...
501
  }
502
503
/////////////////////////////////////////////////////////////////////////////
504
// indenter
505
506
  function indenter(state,textAfter) {
507
    var t;
508
    var unit = cmCfg.indentUnit;
509
    var wordAfter = wordafter(textAfter);
510
    var currT = peekToken(state,1);
511
    var prevT = peekToken(state,2);
512
513
    if (state.in_string || state.in_atom) {
514
      return CodeMirror.Pass;
515
    }else if (!prevT) {
516
      return 0;
517
    }else if (currT.token == "when") {
518
      return currT.column+unit;
519
    }else if (wordAfter === "when" && prevT.type === "function") {
520
      return prevT.indent+unit;
521
    }else if (wordAfter === "(" && currT.token === "fun") {
522
      return  currT.column+3;
523
    }else if (wordAfter === "catch" && (t = getToken(state,["try"]))) {
524
      return t.column;
525
    }else if (is_member(wordAfter,["end","after","of"])) {
526
      t = getToken(state,["begin","case","fun","if","receive","try"]);
527
      return t ? t.column : CodeMirror.Pass;
528
    }else if (is_member(wordAfter,closeParenWords)) {
529
      t = getToken(state,openParenWords);
530
      return t ? t.column : CodeMirror.Pass;
531
    }else if (is_member(currT.token,[",","|","||"]) ||
532
              is_member(wordAfter,[",","|","||"])) {
533
      t = postcommaToken(state);
534
      return t ? t.column+t.token.length : unit;
535
    }else if (currT.token == "->") {
536
      if (is_member(prevT.token, ["receive","case","if","try"])) {
537
        return prevT.column+unit+unit;
538
      }else{
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
539
        return prevT.column+unit;
540
      }
541
    }else if (is_member(currT.token,openParenWords)) {
542
      return currT.column+currT.token.length;
543
    }else{
544
      t = defaultToken(state);
545
      return truthy(t) ? t.column+unit : 0;
546
    }
547
  }
548
549
  function wordafter(str) {
550
    var m = str.match(/,|[a-z]+|\}|\]|\)|>>|\|+|\(/);
551
552
    return truthy(m) && (m.index === 0) ? m[0] : "";
553
  }
554
555
  function postcommaToken(state) {
556
    var objs = state.tokenStack.slice(0,-1);
557
    var i = getTokenIndex(objs,"type",["open_paren"]);
558
559
    return truthy(objs[i]) ? objs[i] : false;
560
  }
561
562
  function defaultToken(state) {
563
    var objs = state.tokenStack;
564
    var stop = getTokenIndex(objs,"type",["open_paren","separator","keyword"]);
565
    var oper = getTokenIndex(objs,"type",["operator"]);
566
567
    if (truthy(stop) && truthy(oper) && stop < oper) {
568
      return objs[stop+1];
569
    } else if (truthy(stop)) {
570
      return objs[stop];
571
    } else {
572
      return false;
573
    }
574
  }
575
576
  function getToken(state,tokens) {
577
    var objs = state.tokenStack;
578
    var i = getTokenIndex(objs,"token",tokens);
579
580
    return truthy(objs[i]) ? objs[i] : false;
581
  }
582
583
  function getTokenIndex(objs,propname,propvals) {
584
585
    for (var i = objs.length-1; -1 < i ; i--) {
586
      if (is_member(objs[i][propname],propvals)) {
587
        return i;
588
      }
589
    }
590
    return false;
591
  }
592
593
  function truthy(x) {
594
    return (x !== false) && (x != null);
0 ignored issues
show
Best Practice introduced by
Comparing x to null using the != operator is not safe. Consider using !== instead.
Loading history...
595
  }
596
597
/////////////////////////////////////////////////////////////////////////////
598
// this object defines the mode
599
600
  return {
601
    startState:
602
      function() {
603
        return {tokenStack: [],
604
                in_string:  false,
605
                in_atom:    false};
606
      },
607
608
    token:
609
      function(stream, state) {
610
        return tokenizer(stream, state);
611
      },
612
613
    indent:
614
      function(state, textAfter) {
615
        return indenter(state,textAfter);
616
      },
617
618
    lineComment: "%"
619
  };
620
});
621
622
});
623