You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

176 lines
5.5 KiB

  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: http://codemirror.net/LICENSE
  3. // Mathematica mode copyright (c) 2015 by Calin Barbat
  4. // Based on code by Patrick Scheibe (halirutan)
  5. // See: https://github.com/halirutan/Mathematica-Source-Highlighting/tree/master/src/lang-mma.js
  6. (function(mod) {
  7. if (typeof exports == "object" && typeof module == "object") // CommonJS
  8. mod(require("../../lib/codemirror"));
  9. else if (typeof define == "function" && define.amd) // AMD
  10. define(["../../lib/codemirror"], mod);
  11. else // Plain browser env
  12. mod(CodeMirror);
  13. })(function(CodeMirror) {
  14. "use strict";
  15. CodeMirror.defineMode('mathematica', function(_config, _parserConfig) {
  16. // used pattern building blocks
  17. var Identifier = '[a-zA-Z\\$][a-zA-Z0-9\\$]*';
  18. var pBase = "(?:\\d+)";
  19. var pFloat = "(?:\\.\\d+|\\d+\\.\\d*|\\d+)";
  20. var pFloatBase = "(?:\\.\\w+|\\w+\\.\\w*|\\w+)";
  21. var pPrecision = "(?:`(?:`?"+pFloat+")?)";
  22. // regular expressions
  23. var reBaseForm = new RegExp('(?:'+pBase+'(?:\\^\\^'+pFloatBase+pPrecision+'?(?:\\*\\^[+-]?\\d+)?))');
  24. var reFloatForm = new RegExp('(?:' + pFloat + pPrecision + '?(?:\\*\\^[+-]?\\d+)?)');
  25. var reIdInContext = new RegExp('(?:`?)(?:' + Identifier + ')(?:`(?:' + Identifier + '))*(?:`?)');
  26. function tokenBase(stream, state) {
  27. var ch;
  28. // get next character
  29. ch = stream.next();
  30. // string
  31. if (ch === '"') {
  32. state.tokenize = tokenString;
  33. return state.tokenize(stream, state);
  34. }
  35. // comment
  36. if (ch === '(') {
  37. if (stream.eat('*')) {
  38. state.commentLevel++;
  39. state.tokenize = tokenComment;
  40. return state.tokenize(stream, state);
  41. }
  42. }
  43. // go back one character
  44. stream.backUp(1);
  45. // look for numbers
  46. // Numbers in a baseform
  47. if (stream.match(reBaseForm, true, false)) {
  48. return 'number';
  49. }
  50. // Mathematica numbers. Floats (1.2, .2, 1.) can have optionally a precision (`float) or an accuracy definition
  51. // (``float). Note: while 1.2` is possible 1.2`` is not. At the end an exponent (float*^+12) can follow.
  52. if (stream.match(reFloatForm, true, false)) {
  53. return 'number';
  54. }
  55. /* In[23] and Out[34] */
  56. if (stream.match(/(?:In|Out)\[[0-9]*\]/, true, false)) {
  57. return 'atom';
  58. }
  59. // usage
  60. if (stream.match(/([a-zA-Z\$]+(?:`?[a-zA-Z0-9\$])*::usage)/, true, false)) {
  61. return 'meta';
  62. }
  63. // message
  64. if (stream.match(/([a-zA-Z\$]+(?:`?[a-zA-Z0-9\$])*::[a-zA-Z\$][a-zA-Z0-9\$]*):?/, true, false)) {
  65. return 'string-2';
  66. }
  67. // this makes a look-ahead match for something like variable:{_Integer}
  68. // the match is then forwarded to the mma-patterns tokenizer.
  69. if (stream.match(/([a-zA-Z\$][a-zA-Z0-9\$]*\s*:)(?:(?:[a-zA-Z\$][a-zA-Z0-9\$]*)|(?:[^:=>~@\^\&\*\)\[\]'\?,\|])).*/, true, false)) {
  70. return 'variable-2';
  71. }
  72. // catch variables which are used together with Blank (_), BlankSequence (__) or BlankNullSequence (___)
  73. // Cannot start with a number, but can have numbers at any other position. Examples
  74. // blub__Integer, a1_, b34_Integer32
  75. if (stream.match(/[a-zA-Z\$][a-zA-Z0-9\$]*_+[a-zA-Z\$][a-zA-Z0-9\$]*/, true, false)) {
  76. return 'variable-2';
  77. }
  78. if (stream.match(/[a-zA-Z\$][a-zA-Z0-9\$]*_+/, true, false)) {
  79. return 'variable-2';
  80. }
  81. if (stream.match(/_+[a-zA-Z\$][a-zA-Z0-9\$]*/, true, false)) {
  82. return 'variable-2';
  83. }
  84. // Named characters in Mathematica, like \[Gamma].
  85. if (stream.match(/\\\[[a-zA-Z\$][a-zA-Z0-9\$]*\]/, true, false)) {
  86. return 'variable-3';
  87. }
  88. // Match all braces separately
  89. if (stream.match(/(?:\[|\]|{|}|\(|\))/, true, false)) {
  90. return 'bracket';
  91. }
  92. // Catch Slots (#, ##, #3, ##9 and the V10 named slots #name). I have never seen someone using more than one digit after #, so we match
  93. // only one.
  94. if (stream.match(/(?:#[a-zA-Z\$][a-zA-Z0-9\$]*|#+[0-9]?)/, true, false)) {
  95. return 'variable-2';
  96. }
  97. // Literals like variables, keywords, functions
  98. if (stream.match(reIdInContext, true, false)) {
  99. return 'keyword';
  100. }
  101. // operators. Note that operators like @@ or /; are matched separately for each symbol.
  102. if (stream.match(/(?:\\|\+|\-|\*|\/|,|;|\.|:|@|~|=|>|<|&|\||_|`|'|\^|\?|!|%)/, true, false)) {
  103. return 'operator';
  104. }
  105. // everything else is an error
  106. stream.next(); // advance the stream.
  107. return 'error';
  108. }
  109. function tokenString(stream, state) {
  110. var next, end = false, escaped = false;
  111. while ((next = stream.next()) != null) {
  112. if (next === '"' && !escaped) {
  113. end = true;
  114. break;
  115. }
  116. escaped = !escaped && next === '\\';
  117. }
  118. if (end && !escaped) {
  119. state.tokenize = tokenBase;
  120. }
  121. return 'string';
  122. };
  123. function tokenComment(stream, state) {
  124. var prev, next;
  125. while(state.commentLevel > 0 && (next = stream.next()) != null) {
  126. if (prev === '(' && next === '*') state.commentLevel++;
  127. if (prev === '*' && next === ')') state.commentLevel--;
  128. prev = next;
  129. }
  130. if (state.commentLevel <= 0) {
  131. state.tokenize = tokenBase;
  132. }
  133. return 'comment';
  134. }
  135. return {
  136. startState: function() {return {tokenize: tokenBase, commentLevel: 0};},
  137. token: function(stream, state) {
  138. if (stream.eatSpace()) return null;
  139. return state.tokenize(stream, state);
  140. },
  141. blockCommentStart: "(*",
  142. blockCommentEnd: "*)"
  143. };
  144. });
  145. CodeMirror.defineMIME('text/x-mathematica', {
  146. name: 'mathematica'
  147. });
  148. });