Subversion-Projekte lars-tiefland.cienc

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
5 lars 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
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(/([\n\s]+|%[^\n]*\n)*(.)/,false);
343
    return m ? m.pop() : "";
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
    var last = s.length-1;
437
 
438
    if (s[last].type === "dot") {
439
      return [];
440
    }
441
    if (s[last].type === "fun" && s[last-1].token === "fun") {
442
      return s.slice(0,last-1);
443
    }
444
    switch (s[s.length-1].token) {
445
      case "}":    return d(s,{g:["{"]});
446
      case "]":    return d(s,{i:["["]});
447
      case ")":    return d(s,{i:["("]});
448
      case ">>":   return d(s,{i:["<<"]});
449
      case "end":  return d(s,{i:["begin","case","fun","if","receive","try"]});
450
      case ",":    return d(s,{e:["begin","try","when","->",
451
                                  ",","(","[","{","<<"]});
452
      case "->":   return d(s,{r:["when"],
453
                               m:["try","if","case","receive"]});
454
      case ";":    return d(s,{E:["case","fun","if","receive","try","when"]});
455
      case "catch":return d(s,{e:["try"]});
456
      case "of":   return d(s,{e:["case"]});
457
      case "after":return d(s,{e:["receive","try"]});
458
      default:     return s;
459
    }
460
  }
461
 
462
  function d(stack,tt) {
463
    // stack is a stack of Token objects.
464
    // tt is an object; {type:tokens}
465
    // type is a char, tokens is a list of token strings.
466
    // The function returns (possibly truncated) stack.
467
    // It will descend the stack, looking for a Token such that Token.token
468
    //  is a member of tokens. If it does not find that, it will normally (but
469
    //  see "E" below) return stack. If it does find a match, it will remove
470
    //  all the Tokens between the top and the matched Token.
471
    // If type is "m", that is all it does.
472
    // If type is "i", it will also remove the matched Token and the top Token.
473
    // If type is "g", like "i", but add a fake "group" token at the top.
474
    // If type is "r", it will remove the matched Token, but not the top Token.
475
    // If type is "e", it will keep the matched Token but not the top Token.
476
    // If type is "E", it behaves as for type "e", except if there is no match,
477
    //  in which case it will return an empty stack.
478
 
479
    for (var type in tt) {
480
      var len = stack.length-1;
481
      var tokens = tt[type];
482
      for (var i = len-1; -1 < i ; i--) {
483
        if (is_member(stack[i].token,tokens)) {
484
          var ss = stack.slice(0,i);
485
          switch (type) {
486
              case "m": return ss.concat(stack[i]).concat(stack[len]);
487
              case "r": return ss.concat(stack[len]);
488
              case "i": return ss;
489
              case "g": return ss.concat(fakeToken("group"));
490
              case "E": return ss.concat(stack[i]);
491
              case "e": return ss.concat(stack[i]);
492
          }
493
        }
494
      }
495
    }
496
    return (type == "E" ? [] : stack);
497
  }
498
 
499
/////////////////////////////////////////////////////////////////////////////
500
// indenter
501
 
502
  function indenter(state,textAfter) {
503
    var t;
504
    var unit = cmCfg.indentUnit;
505
    var wordAfter = wordafter(textAfter);
506
    var currT = peekToken(state,1);
507
    var prevT = peekToken(state,2);
508
 
509
    if (state.in_string || state.in_atom) {
510
      return CodeMirror.Pass;
511
    }else if (!prevT) {
512
      return 0;
513
    }else if (currT.token == "when") {
514
      return currT.column+unit;
515
    }else if (wordAfter === "when" && prevT.type === "function") {
516
      return prevT.indent+unit;
517
    }else if (wordAfter === "(" && currT.token === "fun") {
518
      return  currT.column+3;
519
    }else if (wordAfter === "catch" && (t = getToken(state,["try"]))) {
520
      return t.column;
521
    }else if (is_member(wordAfter,["end","after","of"])) {
522
      t = getToken(state,["begin","case","fun","if","receive","try"]);
523
      return t ? t.column : CodeMirror.Pass;
524
    }else if (is_member(wordAfter,closeParenWords)) {
525
      t = getToken(state,openParenWords);
526
      return t ? t.column : CodeMirror.Pass;
527
    }else if (is_member(currT.token,[",","|","||"]) ||
528
              is_member(wordAfter,[",","|","||"])) {
529
      t = postcommaToken(state);
530
      return t ? t.column+t.token.length : unit;
531
    }else if (currT.token == "->") {
532
      if (is_member(prevT.token, ["receive","case","if","try"])) {
533
        return prevT.column+unit+unit;
534
      }else{
535
        return prevT.column+unit;
536
      }
537
    }else if (is_member(currT.token,openParenWords)) {
538
      return currT.column+currT.token.length;
539
    }else{
540
      t = defaultToken(state);
541
      return truthy(t) ? t.column+unit : 0;
542
    }
543
  }
544
 
545
  function wordafter(str) {
546
    var m = str.match(/,|[a-z]+|\}|\]|\)|>>|\|+|\(/);
547
 
548
    return truthy(m) && (m.index === 0) ? m[0] : "";
549
  }
550
 
551
  function postcommaToken(state) {
552
    var objs = state.tokenStack.slice(0,-1);
553
    var i = getTokenIndex(objs,"type",["open_paren"]);
554
 
555
    return truthy(objs[i]) ? objs[i] : false;
556
  }
557
 
558
  function defaultToken(state) {
559
    var objs = state.tokenStack;
560
    var stop = getTokenIndex(objs,"type",["open_paren","separator","keyword"]);
561
    var oper = getTokenIndex(objs,"type",["operator"]);
562
 
563
    if (truthy(stop) && truthy(oper) && stop < oper) {
564
      return objs[stop+1];
565
    } else if (truthy(stop)) {
566
      return objs[stop];
567
    } else {
568
      return false;
569
    }
570
  }
571
 
572
  function getToken(state,tokens) {
573
    var objs = state.tokenStack;
574
    var i = getTokenIndex(objs,"token",tokens);
575
 
576
    return truthy(objs[i]) ? objs[i] : false;
577
  }
578
 
579
  function getTokenIndex(objs,propname,propvals) {
580
 
581
    for (var i = objs.length-1; -1 < i ; i--) {
582
      if (is_member(objs[i][propname],propvals)) {
583
        return i;
584
      }
585
    }
586
    return false;
587
  }
588
 
589
  function truthy(x) {
590
    return (x !== false) && (x != null);
591
  }
592
 
593
/////////////////////////////////////////////////////////////////////////////
594
// this object defines the mode
595
 
596
  return {
597
    startState:
598
      function() {
599
        return {tokenStack: [],
600
                in_string:  false,
601
                in_atom:    false};
602
      },
603
 
604
    token:
605
      function(stream, state) {
606
        return tokenizer(stream, state);
607
      },
608
 
609
    indent:
610
      function(state, textAfter) {
611
        return indenter(state,textAfter);
612
      },
613
 
614
    lineComment: "%"
615
  };
616
});
617
 
618
});