GPQAPP

Форк
0
619 строк · 18.4 Кб
1
// CodeMirror, copyright (c) by Marijn Haverbeke and others
2
// Distributed under an MIT license: https://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
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
});
620

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.