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