github.com/jancarloviray/community@v0.41.1-0.20170124221257-33a66c87cf2f/app/public/codemirror/mode/crystal/crystal.js (about)

     1  // CodeMirror, copyright (c) by Marijn Haverbeke and others
     2  // Distributed under an MIT license: http://codemirror.net/LICENSE
     3  
     4  (function(mod) {
     5    if (typeof exports == "object" && typeof module == "object") // CommonJS
     6      mod(require("../../lib/codemirror"));
     7    else if (typeof define == "function" && define.amd) // AMD
     8      define(["../../lib/codemirror"], mod);
     9    else // Plain browser env
    10      mod(CodeMirror);
    11  })(function(CodeMirror) {
    12    "use strict";
    13  
    14    CodeMirror.defineMode("crystal", function(config) {
    15      function wordRegExp(words, end) {
    16        return new RegExp((end ? "" : "^") + "(?:" + words.join("|") + ")" + (end ? "$" : "\\b"));
    17      }
    18  
    19      function chain(tokenize, stream, state) {
    20        state.tokenize.push(tokenize);
    21        return tokenize(stream, state);
    22      }
    23  
    24      var operators = /^(?:[-+/%|&^]|\*\*?|[<>]{2})/;
    25      var conditionalOperators = /^(?:[=!]~|===|<=>|[<>=!]=?|[|&]{2}|~)/;
    26      var indexingOperators = /^(?:\[\][?=]?)/;
    27      var anotherOperators = /^(?:\.(?:\.{2})?|->|[?:])/;
    28      var idents = /^[a-z_\u009F-\uFFFF][a-zA-Z0-9_\u009F-\uFFFF]*/;
    29      var types = /^[A-Z_\u009F-\uFFFF][a-zA-Z0-9_\u009F-\uFFFF]*/;
    30      var keywords = wordRegExp([
    31        "abstract", "alias", "as", "asm", "begin", "break", "case", "class", "def", "do",
    32        "else", "elsif", "end", "ensure", "enum", "extend", "for", "fun", "if", "ifdef",
    33        "include", "instance_sizeof", "lib", "macro", "module", "next", "of", "out", "pointerof",
    34        "private", "protected", "rescue", "return", "require", "sizeof", "struct",
    35        "super", "then", "type", "typeof", "union", "unless", "until", "when", "while", "with",
    36        "yield", "__DIR__", "__FILE__", "__LINE__"
    37      ]);
    38      var atomWords = wordRegExp(["true", "false", "nil", "self"]);
    39      var indentKeywordsArray = [
    40        "def", "fun", "macro",
    41        "class", "module", "struct", "lib", "enum", "union",
    42        "if", "unless", "case", "while", "until", "begin", "then",
    43        "do",
    44        "for", "ifdef"
    45      ];
    46      var indentKeywords = wordRegExp(indentKeywordsArray);
    47      var dedentKeywordsArray = [
    48        "end",
    49        "else", "elsif",
    50        "rescue", "ensure"
    51      ];
    52      var dedentKeywords = wordRegExp(dedentKeywordsArray);
    53      var dedentPunctualsArray = ["\\)", "\\}", "\\]"];
    54      var dedentPunctuals = new RegExp("^(?:" + dedentPunctualsArray.join("|") + ")$");
    55      var nextTokenizer = {
    56        "def": tokenFollowIdent, "fun": tokenFollowIdent, "macro": tokenMacroDef,
    57        "class": tokenFollowType, "module": tokenFollowType, "struct": tokenFollowType,
    58        "lib": tokenFollowType, "enum": tokenFollowType, "union": tokenFollowType
    59      };
    60      var matching = {"[": "]", "{": "}", "(": ")", "<": ">"};
    61  
    62      function tokenBase(stream, state) {
    63        if (stream.eatSpace()) {
    64          return null;
    65        }
    66  
    67        // Macros
    68        if (state.lastToken != "\\" && stream.match("{%", false)) {
    69          return chain(tokenMacro("%", "%"), stream, state);
    70        }
    71  
    72        if (state.lastToken != "\\" && stream.match("{{", false)) {
    73          return chain(tokenMacro("{", "}"), stream, state);
    74        }
    75  
    76        // Comments
    77        if (stream.peek() == "#") {
    78          stream.skipToEnd();
    79          return "comment";
    80        }
    81  
    82        // Variables and keywords
    83        var matched;
    84        if (stream.match(idents)) {
    85          stream.eat(/[?!]/);
    86  
    87          matched = stream.current();
    88          if (stream.eat(":")) {
    89            return "atom";
    90          } else if (state.lastToken == ".") {
    91            return "property";
    92          } else if (keywords.test(matched)) {
    93            if (state.lastToken != "abstract" && indentKeywords.test(matched)) {
    94              if (!(matched == "fun" && state.blocks.indexOf("lib") >= 0)) {
    95                state.blocks.push(matched);
    96                state.currentIndent += 1;
    97              }
    98            } else if (dedentKeywords.test(matched)) {
    99              state.blocks.pop();
   100              state.currentIndent -= 1;
   101            }
   102  
   103            if (nextTokenizer.hasOwnProperty(matched)) {
   104              state.tokenize.push(nextTokenizer[matched]);
   105            }
   106  
   107            return "keyword";
   108          } else if (atomWords.test(matched)) {
   109            return "atom";
   110          }
   111  
   112          return "variable";
   113        }
   114  
   115        // Class variables and instance variables
   116        // or attributes
   117        if (stream.eat("@")) {
   118          if (stream.peek() == "[") {
   119            return chain(tokenNest("[", "]", "meta"), stream, state);
   120          }
   121  
   122          stream.eat("@");
   123          stream.match(idents) || stream.match(types);
   124          return "variable-2";
   125        }
   126  
   127        // Global variables
   128        if (stream.eat("$")) {
   129          stream.eat(/[0-9]+|\?/) || stream.match(idents) || stream.match(types);
   130          return "variable-3";
   131        }
   132  
   133        // Constants and types
   134        if (stream.match(types)) {
   135          return "tag";
   136        }
   137  
   138        // Symbols or ':' operator
   139        if (stream.eat(":")) {
   140          if (stream.eat("\"")) {
   141            return chain(tokenQuote("\"", "atom", false), stream, state);
   142          } else if (stream.match(idents) || stream.match(types) ||
   143                     stream.match(operators) || stream.match(conditionalOperators) || stream.match(indexingOperators)) {
   144            return "atom";
   145          }
   146          stream.eat(":");
   147          return "operator";
   148        }
   149  
   150        // Strings
   151        if (stream.eat("\"")) {
   152          return chain(tokenQuote("\"", "string", true), stream, state);
   153        }
   154  
   155        // Strings or regexps or macro variables or '%' operator
   156        if (stream.peek() == "%") {
   157          var style = "string";
   158          var embed = true;
   159          var delim;
   160  
   161          if (stream.match("%r")) {
   162            // Regexps
   163            style = "string-2";
   164            delim = stream.next();
   165          } else if (stream.match("%w")) {
   166            embed = false;
   167            delim = stream.next();
   168          } else {
   169            if(delim = stream.match(/^%([^\w\s=])/)) {
   170              delim = delim[1];
   171            } else if (stream.match(/^%[a-zA-Z0-9_\u009F-\uFFFF]*/)) {
   172              // Macro variables
   173              return "meta";
   174            } else {
   175              // '%' operator
   176              return "operator";
   177            }
   178          }
   179  
   180          if (matching.hasOwnProperty(delim)) {
   181            delim = matching[delim];
   182          }
   183          return chain(tokenQuote(delim, style, embed), stream, state);
   184        }
   185  
   186        // Characters
   187        if (stream.eat("'")) {
   188          stream.match(/^(?:[^']|\\(?:[befnrtv0'"]|[0-7]{3}|u(?:[0-9a-fA-F]{4}|\{[0-9a-fA-F]{1,6}\})))/);
   189          stream.eat("'");
   190          return "atom";
   191        }
   192  
   193        // Numbers
   194        if (stream.eat("0")) {
   195          if (stream.eat("x")) {
   196            stream.match(/^[0-9a-fA-F]+/);
   197          } else if (stream.eat("o")) {
   198            stream.match(/^[0-7]+/);
   199          } else if (stream.eat("b")) {
   200            stream.match(/^[01]+/);
   201          }
   202          return "number";
   203        }
   204  
   205        if (stream.eat(/\d/)) {
   206          stream.match(/^\d*(?:\.\d+)?(?:[eE][+-]?\d+)?/);
   207          return "number";
   208        }
   209  
   210        // Operators
   211        if (stream.match(operators)) {
   212          stream.eat("="); // Operators can follow assigin symbol.
   213          return "operator";
   214        }
   215  
   216        if (stream.match(conditionalOperators) || stream.match(anotherOperators)) {
   217          return "operator";
   218        }
   219  
   220        // Parens and braces
   221        if (matched = stream.match(/[({[]/, false)) {
   222          matched = matched[0];
   223          return chain(tokenNest(matched, matching[matched], null), stream, state);
   224        }
   225  
   226        // Escapes
   227        if (stream.eat("\\")) {
   228          stream.next();
   229          return "meta";
   230        }
   231  
   232        stream.next();
   233        return null;
   234      }
   235  
   236      function tokenNest(begin, end, style, started) {
   237        return function (stream, state) {
   238          if (!started && stream.match(begin)) {
   239            state.tokenize[state.tokenize.length - 1] = tokenNest(begin, end, style, true);
   240            state.currentIndent += 1;
   241            return style;
   242          }
   243  
   244          var nextStyle = tokenBase(stream, state);
   245          if (stream.current() === end) {
   246            state.tokenize.pop();
   247            state.currentIndent -= 1;
   248            nextStyle = style;
   249          }
   250  
   251          return nextStyle;
   252        };
   253      }
   254  
   255      function tokenMacro(begin, end, started) {
   256        return function (stream, state) {
   257          if (!started && stream.match("{" + begin)) {
   258            state.currentIndent += 1;
   259            state.tokenize[state.tokenize.length - 1] = tokenMacro(begin, end, true);
   260            return "meta";
   261          }
   262  
   263          if (stream.match(end + "}")) {
   264            state.currentIndent -= 1;
   265            state.tokenize.pop();
   266            return "meta";
   267          }
   268  
   269          return tokenBase(stream, state);
   270        };
   271      }
   272  
   273      function tokenMacroDef(stream, state) {
   274        if (stream.eatSpace()) {
   275          return null;
   276        }
   277  
   278        var matched;
   279        if (matched = stream.match(idents)) {
   280          if (matched == "def") {
   281            return "keyword";
   282          }
   283          stream.eat(/[?!]/);
   284        }
   285  
   286        state.tokenize.pop();
   287        return "def";
   288      }
   289  
   290      function tokenFollowIdent(stream, state) {
   291        if (stream.eatSpace()) {
   292          return null;
   293        }
   294  
   295        if (stream.match(idents)) {
   296          stream.eat(/[!?]/);
   297        } else {
   298          stream.match(operators) || stream.match(conditionalOperators) || stream.match(indexingOperators);
   299        }
   300        state.tokenize.pop();
   301        return "def";
   302      }
   303  
   304      function tokenFollowType(stream, state) {
   305        if (stream.eatSpace()) {
   306          return null;
   307        }
   308  
   309        stream.match(types);
   310        state.tokenize.pop();
   311        return "def";
   312      }
   313  
   314      function tokenQuote(end, style, embed) {
   315        return function (stream, state) {
   316          var escaped = false;
   317  
   318          while (stream.peek()) {
   319            if (!escaped) {
   320              if (stream.match("{%", false)) {
   321                state.tokenize.push(tokenMacro("%", "%"));
   322                return style;
   323              }
   324  
   325              if (stream.match("{{", false)) {
   326                state.tokenize.push(tokenMacro("{", "}"));
   327                return style;
   328              }
   329  
   330              if (embed && stream.match("#{", false)) {
   331                state.tokenize.push(tokenNest("#{", "}", "meta"));
   332                return style;
   333              }
   334  
   335              var ch = stream.next();
   336  
   337              if (ch == end) {
   338                state.tokenize.pop();
   339                return style;
   340              }
   341  
   342              escaped = ch == "\\";
   343            } else {
   344              stream.next();
   345              escaped = false;
   346            }
   347          }
   348  
   349          return style;
   350        };
   351      }
   352  
   353      return {
   354        startState: function () {
   355          return {
   356            tokenize: [tokenBase],
   357            currentIndent: 0,
   358            lastToken: null,
   359            blocks: []
   360          };
   361        },
   362  
   363        token: function (stream, state) {
   364          var style = state.tokenize[state.tokenize.length - 1](stream, state);
   365          var token = stream.current();
   366  
   367          if (style && style != "comment") {
   368            state.lastToken = token;
   369          }
   370  
   371          return style;
   372        },
   373  
   374        indent: function (state, textAfter) {
   375          textAfter = textAfter.replace(/^\s*(?:\{%)?\s*|\s*(?:%\})?\s*$/g, "");
   376  
   377          if (dedentKeywords.test(textAfter) || dedentPunctuals.test(textAfter)) {
   378            return config.indentUnit * (state.currentIndent - 1);
   379          }
   380  
   381          return config.indentUnit * state.currentIndent;
   382        },
   383  
   384        fold: "indent",
   385        electricInput: wordRegExp(dedentPunctualsArray.concat(dedentKeywordsArray), true),
   386        lineComment: '#'
   387      };
   388    });
   389  
   390    CodeMirror.defineMIME("text/x-crystal", "crystal");
   391  });