1
var PythonParser = Editor.Parser = (function() {
2
function wordRegexp(words) {
3
return new RegExp("^(?:" + words.join("|") + ")$");
5
var DELIMITERCLASS = 'py-delimiter';
6
var LITERALCLASS = 'py-literal';
7
var ERRORCLASS = 'py-error';
8
var OPERATORCLASS = 'py-operator';
9
var IDENTIFIERCLASS = 'py-identifier';
10
var STRINGCLASS = 'py-string';
11
var BYTESCLASS = 'py-bytes';
12
var UNICODECLASS = 'py-unicode';
13
var RAWCLASS = 'py-raw';
14
var NORMALCONTEXT = 'normal';
15
var STRINGCONTEXT = 'string';
16
var singleOperators = '+-*/%&|^~<>';
17
var doubleOperators = wordRegexp(['==', '!=', '\\<=', '\\>=', '\\<\\>',
18
'\\<\\<', '\\>\\>', '\\/\\/', '\\*\\*']);
19
var singleDelimiters = '()[]{}@,:`=;';
20
var doubleDelimiters = ['\\+=', '\\-=', '\\*=', '/=', '%=', '&=', '\\|=',
22
var tripleDelimiters = wordRegexp(['//=','\\>\\>=','\\<\\<=','\\*\\*=']);
23
var singleStarters = singleOperators + singleDelimiters + '=!';
24
var doubleStarters = '=<>*/';
25
var identifierStarters = /[_A-Za-z]/;
27
var wordOperators = wordRegexp(['and', 'or', 'not', 'is', 'in']);
28
var commonkeywords = ['as', 'assert', 'break', 'class', 'continue',
29
'def', 'del', 'elif', 'else', 'except', 'finally',
30
'for', 'from', 'global', 'if', 'import',
31
'lambda', 'pass', 'raise', 'return',
32
'try', 'while', 'with', 'yield'];
33
var commontypes = ['bool', 'classmethod', 'complex', 'dict', 'enumerate',
34
'float', 'frozenset', 'int', 'list', 'object',
35
'property', 'reversed', 'set', 'slice', 'staticmethod',
36
'str', 'super', 'tuple', 'type'];
37
var py2 = {'types': ['basestring', 'buffer', 'file', 'long', 'unicode',
39
'keywords': ['exec', 'print'],
41
var py3 = {'types': ['bytearray', 'bytes', 'filter', 'map', 'memoryview',
42
'open', 'range', 'zip'],
43
'keywords': ['nonlocal'],
46
var py, keywords, types, stringStarters, stringTypes, config;
48
function configure(conf) {
49
if (!conf.hasOwnProperty('pythonVersion')) {
50
conf.pythonVersion = 2;
52
if (!conf.hasOwnProperty('strictErrors')) {
53
conf.strictErrors = true;
55
if (conf.pythonVersion != 2 && conf.pythonVersion != 3) {
56
alert('CodeMirror: Unknown Python Version "' +
58
'", defaulting to Python 2.x.');
59
conf.pythonVersion = 2;
61
if (conf.pythonVersion == 3) {
63
stringStarters = /[\'\"rbRB]/;
65
doubleDelimiters.push('\\-\\>');
68
stringStarters = /[\'\"RUru]/;
72
keywords = wordRegexp(commonkeywords.concat(py.keywords));
73
types = wordRegexp(commontypes.concat(py.types));
74
doubleDelimiters = wordRegexp(doubleDelimiters);
77
var tokenizePython = (function() {
78
function normal(source, setState) {
79
var stringDelim, threeStr, temp, type, word, possible = {};
80
var ch = source.next();
82
function filterPossible(token, styleIfPossible) {
83
if (!possible.style && !possible.content) {
85
} else if (typeof(token) == STRINGCONTEXT) {
86
token = {content: source.get(), style: token};
88
if (possible.style || styleIfPossible) {
89
token.style = styleIfPossible ? styleIfPossible : possible.style;
91
if (possible.content) {
92
token.content = possible.content + token.content;
100
while (!source.endOfLine()) {
105
// Handle special chars
107
if (!source.endOfLine()) {
108
var whitespace = true;
109
while (!source.endOfLine()) {
110
if(!(/[\s\u00a0]/.test(source.next()))) {
120
// Handle operators and delimiters
121
if (singleStarters.indexOf(ch) != -1 || (ch == "." && !source.matches(/\d/))) {
122
if (doubleStarters.indexOf(source.peek()) != -1) {
123
temp = ch + source.peek();
124
// It must be a double delimiter or operator or triple delimiter
125
if (doubleOperators.test(temp)) {
127
var nextChar = source.peek();
128
if (nextChar && tripleDelimiters.test(temp + nextChar)) {
130
return DELIMITERCLASS;
132
return OPERATORCLASS;
134
} else if (doubleDelimiters.test(temp)) {
136
return DELIMITERCLASS;
139
// It must be a single delimiter or operator
140
if (singleOperators.indexOf(ch) != -1 || ch == ".") {
141
return OPERATORCLASS;
142
} else if (singleDelimiters.indexOf(ch) != -1) {
143
if (ch == '@' && source.matches(/\w/)) {
144
source.nextWhileMatches(/[\w\d_]/);
145
return {style:'py-decorator',
146
content: source.get()};
148
return DELIMITERCLASS;
154
// Handle number literals
155
if (/\d/.test(ch) || (ch == "." && source.matches(/\d/))) {
156
if (ch === '0' && !source.endOfLine()) {
157
switch (source.peek()) {
161
source.nextWhileMatches(/[0-7]/);
162
return filterPossible(LITERALCLASS, ERRORCLASS);
166
source.nextWhileMatches(/[0-9A-Fa-f]/);
167
return filterPossible(LITERALCLASS, ERRORCLASS);
171
source.nextWhileMatches(/[01]/);
172
return filterPossible(LITERALCLASS, ERRORCLASS);
175
source.nextWhileMatches(/\d/);
176
if (ch != '.' && source.peek() == '.') {
178
source.nextWhileMatches(/\d/);
181
if (source.matches(/e/i)) {
183
if (source.peek() == '+' || source.peek() == '-') {
186
if (source.matches(/\d/)) {
187
source.nextWhileMatches(/\d/);
189
return filterPossible(ERRORCLASS);
192
// Grab a complex number
193
if (source.matches(/j/i)) {
197
return filterPossible(LITERALCLASS);
200
if (stringStarters.test(ch)) {
201
var peek = source.peek();
202
var stringType = STRINGCLASS;
203
if ((stringTypes.test(ch)) && (peek == '"' || peek == "'")) {
204
switch (ch.toLowerCase()) {
206
stringType = BYTESCLASS;
209
stringType = RAWCLASS;
212
stringType = UNICODECLASS;
217
if (source.peek() != stringDelim) {
218
setState(inString(stringType, stringDelim));
222
if (source.peek() == stringDelim) {
224
threeStr = stringDelim + stringDelim + stringDelim;
225
setState(inString(stringType, threeStr));
231
} else if (ch == "'" || ch == '"') {
233
if (source.peek() != stringDelim) {
234
setState(inString(stringType, stringDelim));
238
if (source.peek() == stringDelim) {
240
threeStr = stringDelim + stringDelim + stringDelim;
241
setState(inString(stringType, threeStr));
250
if (identifierStarters.test(ch)) {
251
source.nextWhileMatches(/[\w\d_]/);
253
if (wordOperators.test(word)) {
254
type = OPERATORCLASS;
255
} else if (keywords.test(word)) {
257
} else if (types.test(word)) {
260
type = IDENTIFIERCLASS;
261
while (source.peek() == '.') {
263
if (source.matches(identifierStarters)) {
264
source.nextWhileMatches(/[\w\d]/);
270
word = word + source.get();
272
return filterPossible({style: type, content: word});
275
// Register Dollar sign and Question mark as errors. Always!
276
if (/\$\?/.test(ch)) {
277
return filterPossible(ERRORCLASS);
280
return filterPossible(ERRORCLASS);
283
function inString(style, terminator) {
284
return function(source, setState) {
287
while (!found && !source.endOfLine()) {
288
var ch = source.next(), newMatches = [];
289
// Skip escaped characters
291
if (source.peek() == '\n') {
297
if (ch == terminator.charAt(0)) {
298
matches.push(terminator);
300
for (var i = 0; i < matches.length; i++) {
301
var match = matches[i];
302
if (match.charAt(0) == ch) {
303
if (match.length == 1) {
308
newMatches.push(match.slice(1));
312
matches = newMatches;
318
return function(source, startState) {
319
return tokenizer(source, startState || normal);
323
function parsePython(source, basecolumn) {
327
basecolumn = basecolumn || 0;
329
var tokens = tokenizePython(source);
330
var lastToken = null;
331
var column = basecolumn;
332
var context = {prev: null,
334
startNewScope: false,
340
function pushContext(level, type) {
341
type = type ? type : NORMALCONTEXT;
342
context = {prev: context,
344
startNewScope: false,
351
function popContext(remove) {
352
remove = remove ? remove : false;
355
context = context.prev;
358
context.prev.next = context;
359
context = context.prev;
364
function indentPython(context) {
366
return function(nextChars, currentLevel, direction) {
367
if (direction === null || direction === undefined) {
369
while (context.next) {
370
context = context.next;
373
return context.level;
375
else if (direction === true) {
376
if (currentLevel == context.level) {
378
return context.next.level;
380
return context.level;
384
while (temp.prev && temp.prev.level > currentLevel) {
389
} else if (direction === false) {
390
if (currentLevel > context.level) {
391
return context.level;
392
} else if (context.prev) {
394
while (temp.prev && temp.prev.level >= currentLevel) {
398
return temp.prev.level;
404
return context.level;
410
var token = tokens.next();
411
var type = token.style;
412
var content = token.content;
415
if (lastToken.content == 'def' && type == IDENTIFIERCLASS) {
416
token.style = 'py-func';
418
if (lastToken.content == '\n') {
419
var tempCtx = context;
420
// Check for a different scope
421
if (type == 'whitespace' && context.type == NORMALCONTEXT) {
422
if (token.value.length < context.level) {
423
while (token.value.length < context.level) {
427
if (token.value.length != context.level) {
429
if (config.strictErrors) {
430
token.style = ERRORCLASS;
436
} else if (context.level !== basecolumn &&
437
context.type == NORMALCONTEXT) {
438
while (basecolumn !== context.level) {
442
if (context.level !== basecolumn) {
444
if (config.strictErrors) {
445
token.style = ERRORCLASS;
452
// Handle Scope Changes
458
if (context.type !== STRINGCONTEXT) {
459
pushContext(context.level + 1, STRINGCONTEXT);
463
if (context.type === STRINGCONTEXT) {
471
// These delimiters don't appear by themselves
472
if (content !== token.value) {
473
token.style = ERRORCLASS;
477
// Colons only delimit scope inside a normal scope
478
if (context.type === NORMALCONTEXT) {
479
context.startNewScope = context.level+indentUnit;
485
// These start a sequence scope
486
pushContext(column + content.length, 'sequence');
491
// These end a sequence scope
496
// These end a normal scope
497
if (context.type === NORMALCONTEXT) {
498
context.endOfScope = true;
504
// Make any scope changes
505
if (context.endOfScope) {
506
context.endOfScope = false;
508
} else if (context.startNewScope !== false) {
509
var temp = context.startNewScope;
510
context.startNewScope = false;
511
pushContext(temp, NORMALCONTEXT);
513
// Newlines require an indentation function wrapped in a closure for proper context.
514
token.indentation = indentPython(context);
518
// Keep track of current column for certain scopes.
519
if (content != '\n') {
520
column += token.value.length;
528
var _context = context, _tokenState = tokens.state;
529
return function(source) {
530
tokens = tokenizePython(source, _tokenState);
539
return {make: parsePython,
541
configure: configure};