source: trunk/PrefsPane/src/JavascriptSyntaxHighlighter.m @ 1046

Revision 789, 22.5 KB checked in by speck, 3 years ago (diff)

Compile Prefs Panel in 64 bit mode. Fix lots of 64->32 bit conversions because NSInteger becomes 64 bit while 'int' remains 32 bit.

Line 
1/* Copyright (C) 2008-2009 Peter Speck
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#import "JavascriptSyntaxHighlighter.h"
18#import "GBDebug.h"
19
20#ifdef DEBUG
21//#define DEBUG_JS
22#endif
23
24static inline NSColor* rgb(int r, int g, int b)
25{
26    return [NSColor colorWithDeviceRed:(r / 255.0f) green:(g / 255.0f) blue:(b / 255.0f) alpha:1];
27}
28
29@interface JsColor : NSObject {
30    NSString* _name;
31    NSColor* _foreground;
32    NSColor* _background;
33}
34@end
35
36@implementation JsColor
37
38- (id)init:(NSString*)name r:(int)r g:(int)g b:(int)b background:(NSColor*)background
39{
40    if (!(self = [super init]))
41        return nil;
42    _name = [name retain];
43    _foreground = [rgb(r, g, b) retain];
44    _background = [background retain];
45    return self;
46}
47
48- (id)init:(NSString*)name r:(int)r g:(int)g b:(int)b
49{
50    return [self init:name r:r g:g b:b background:NULL];
51}
52
53- (void)dealloc
54{
55    [_name release];
56    [_foreground release];
57    [_background release];
58    [super dealloc];
59}
60
61- (NSColor*)foreground
62{
63    return _foreground;
64}
65
66- (NSColor*)background
67{
68    return _background;
69}
70
71- (NSString*)name
72{
73    return _name;
74}
75@end
76
77//===================================================================================
78
79@interface JavascriptSyntaxHighlighter ()
80
81- (id)initWithAttributedString:(NSMutableAttributedString*)attributedString
82                  isJavaRegexp:(BOOL)isJavaRegexp;
83+ (void)setupGlobals;
84
85- (void)javaRegexpMain;
86- (void)javascriptMain;
87
88@end
89
90@implementation JavascriptSyntaxHighlighter
91
92static JsColor* plainColor;
93static JsColor* commentColor;
94static JsColor* stringColor;
95static JsColor* stringBackslashColor;
96static JsColor* regexpStringColor;
97static JsColor* regexpGroupParanColor;
98static JsColor* regexpGroupOptionsColor;
99static JsColor* regexpSpecialsColor;
100static JsColor* regexpSpecialsDotColor;
101static JsColor* regexpCharClassColor;
102static JsColor* regexpBackslashColor;
103static JsColor* keywordColor;
104static JsColor* errorColor;
105static NSMutableDictionary* jsKeywords;
106static NSCharacterSet* letterSet;
107static NSCharacterSet* letterDigitSet;
108static NSFont* plainFont = NULL;
109static NSFont* boldFont = NULL;
110static NSFont* dotFont = NULL;
111static NSFont* bracesFont = NULL;
112
113+ (void)setupGlobals
114{
115    if (plainColor)
116        return;
117    NSColor* bg = rgb(255, 240, 240);
118    plainColor = [[JsColor alloc] init:@"plain" r:0 g:0 b:0];
119    commentColor = [[JsColor alloc] init:@"comment" r:0 g:116 b:0];
120    stringColor = [[JsColor alloc] init:@"string" r:196 g:27 b:22];
121    stringBackslashColor = [[JsColor alloc] init:@"string-backslash" r:178 g:178 b:178];
122    regexpStringColor = [[JsColor alloc] init:@"regexp" r:160 g:25 b:20 background:bg];
123    regexpGroupParanColor = [[JsColor alloc] init:@"regexp-group-paran" r:0 g:153 b:0 background:bg];
124    regexpGroupOptionsColor = [[JsColor alloc] init:@"regexp-group-opts" r:0 g:153 b:0 background:bg];
125    regexpSpecialsColor = [[JsColor alloc] init:@"regexp-specials" r:30 g:60 b:160 background:bg];
126    regexpSpecialsDotColor = [[JsColor alloc] init:@"regexp-specials-dot" r:30 g:60 b:255 background:bg];
127    regexpCharClassColor = [[JsColor alloc] init:@"regexp-charclass" r:255 g:128 b:0 background:bg];
128    regexpBackslashColor = [[JsColor alloc] init:@"string-backslash" r:205 g:185 b:185 background:bg];
129    keywordColor = [[JsColor alloc] init:@"keyword" r:158 g:0 b:140];
130    errorColor = [[JsColor alloc] init:@"regexp-error" r:255 g:255 b:255 background:rgb(255, 0, 0)];
131    //
132    plainFont = [[NSFont systemFontOfSize:11] retain];
133    boldFont = [[NSFont boldSystemFontOfSize:12] retain];
134    dotFont = [[NSFont boldSystemFontOfSize:11] retain]; // much more visible than dot in plain Lucida Grande
135    bracesFont = [[NSFont boldSystemFontOfSize:11] retain];
136    //
137    NSString* kw = @"abstract boolean break byte case catch char class const continue debugger "
138    /**/ "default delete do double else enum export extends false final finally float "
139    /**/ "for function goto if implements import in instanceof int interface let long native "
140    /**/ "new null package private protected public return short static super switch "
141    /**/ "synchronized this throw throws transient true try typeof var void volatile while with";
142    jsKeywords = [[NSMutableDictionary dictionaryWithCapacity:60] retain];
143    for (NSString* s in [kw componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]])
144        [jsKeywords setObject:@"x" forKey:s];
145    //
146    letterSet = [[NSCharacterSet letterCharacterSet] retain];
147    letterDigitSet = [[NSCharacterSet alphanumericCharacterSet] retain];
148}
149
150+ (void)highlightJavascript:(NSMutableAttributedString*)attributedString
151{
152    NSAssert(attributedString, @"attributedString");
153    JavascriptSyntaxHighlighter* jsh = [[JavascriptSyntaxHighlighter alloc] initWithAttributedString:attributedString isJavaRegexp:NO];
154    [jsh javascriptMain];
155    [jsh release];
156}
157
158+ (void)highlightJavaRegExp:(NSMutableAttributedString*)attributedString
159{
160    NSAssert(attributedString, @"attributedString");
161    JavascriptSyntaxHighlighter* jsh = [[JavascriptSyntaxHighlighter alloc] initWithAttributedString:attributedString isJavaRegexp:YES];
162    [jsh javaRegexpMain];
163    [jsh release];
164}
165
166
167- (id)initWithAttributedString:(NSMutableAttributedString*)attributedString
168                  isJavaRegexp:(BOOL)isJavaRegexp;
169{
170    [JavascriptSyntaxHighlighter setupGlobals];
171    if (!(self = [super init]))
172        return nil;
173    _isJavaRegexp = isJavaRegexp;
174    _attributedString = [attributedString retain];
175    _uncommittedColor = plainColor;
176    _uncommittedStart = 0;
177    _txt = [[_attributedString string] retain];
178    _txtLength = [_txt length];
179    _indexOfNextChar = 0;
180    return self;
181}
182
183- (void)dealloc
184{
185    [_attributedString release];
186    [_txt release];
187    [super dealloc];
188}
189
190- (void)setAttribute:(NSString*)attrName toValue:(id)wantedValue
191{
192    NSRange range = NSMakeRange(_uncommittedStart, _uncommittedEnd - _uncommittedStart);
193    NSRange attrRange = NSMakeRange(0, 0);
194    id attr = [_attributedString attribute:attrName
195                                   atIndex:_uncommittedStart
196                     longestEffectiveRange:&attrRange
197                                   inRange:NSMakeRange(0, _txtLength)];
198    NSInteger rangeEnd = range.location + range.length;
199    NSInteger attrRangeEnd = attrRange.location + attrRange.length;
200    if (attr == wantedValue && attrRange.location <= range.location && attrRangeEnd >= rangeEnd)
201        return;
202    if (wantedValue) {
203        [_attributedString addAttribute:attrName
204                                  value:wantedValue
205                                  range:range];
206    } else {
207        [_attributedString removeAttribute:attrName
208                                     range:range];
209    }
210}
211
212- (void)commitTextAttributes
213{
214#ifdef DEBUG_JS
215    DebugNSLog(@"Commits: %d -> %d with %@", _uncommittedStart, _uncommittedEnd, [_uncommittedColor name]);
216#endif
217    if (_uncommittedEnd <= _uncommittedStart)
218        return;
219    [self setAttribute:NSFontAttributeName toValue:_uncommittedFont];
220    if (_isJavaRegexp && _uncommittedColor == errorColor)
221        [self setAttribute:NSForegroundColorAttributeName toValue:[NSColor redColor]]; // white text without colored background is invisible.
222    else
223        [self setAttribute:NSForegroundColorAttributeName toValue:[_uncommittedColor foreground]];
224    if (!_isJavaRegexp) // ugly, don't want background color in host/path/query fields.
225        [self setAttribute:NSBackgroundColorAttributeName toValue:[_uncommittedColor background]];
226    _uncommittedStart = _uncommittedEnd;
227}
228
229- (void)setColor:(JsColor*)color font:(NSFont*)font toIndex:(NSInteger)toIndex
230{
231    if (color == _uncommittedColor && font == _uncommittedFont) {
232        //DebugNSLog(@"Extends uncommit: %d -> %d -> %d of %@", _uncommittedStart, _uncommittedEnd, toIndex, [color name]);
233        _uncommittedEnd = toIndex;
234    } else {
235        [self commitTextAttributes];
236        _uncommittedColor = color;
237        _uncommittedFont = font;
238        _uncommittedEnd = MIN(toIndex, _txtLength);
239#ifdef DEBUG_JS
240        DebugNSLog(@"Starts uncommitted: %d -> %d using: %@ and %@", _uncommittedStart, _uncommittedEnd, [color name], [font displayName]);
241#endif
242    }
243}
244
245- (void)setColor:(JsColor*)color toIndex:(NSInteger)toIndex
246{
247    [self setColor:color font:plainFont toIndex:toIndex];
248}
249
250- (unichar)peek:(NSInteger)delta
251{
252    NSInteger idx = _indexOfNextChar + delta;
253    if (idx >= 0 && idx < _txtLength)
254        return [_txt characterAtIndex:idx];
255    else
256        return ' ';
257}
258
259- (unichar)eat
260{
261    unichar ch = [self peek:0];
262    _indexOfNextChar++;
263    return ch;
264}
265
266inline static BOOL isDecimalDigit(unichar ch)
267{
268    return ch >= '0' && ch <= '9';
269}
270
271inline static BOOL isHexDigit(unichar ch)
272{
273    return isDecimalDigit(ch) || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f');
274}
275
276- (void)parseEscapeHexDigits:(int)numDigits
277{
278    for (int i = 0; i < numDigits; i++) {
279        if (!isHexDigit([self eat])) {
280            [self setColor:errorColor toIndex:_indexOfNextChar];
281            return;
282        }
283    }
284    [self setColor:regexpSpecialsColor toIndex:_indexOfNextChar];
285}
286
287- (void)regexpGroupOptions
288{
289    [self eat]; // '?'
290    unichar ch2 = [self eat];
291    switch (ch2) {
292        case ':': //  (?:  simple anon group
293        case '=': //  (?=  positive look-ahead
294        case '!': //  (?!  negative look-ahead
295            return;
296        case ')': //  (?)  is invalid
297            _indexOfNextChar--;
298            [self setColor:errorColor toIndex:_indexOfNextChar];
299            return;
300    }
301    if (!_isJavaRegexp) {
302        [self setColor:errorColor toIndex:_indexOfNextChar];
303        return;
304    }
305    if (ch2 == '>') //  (?>  anon group without backtracking.
306        return;
307    if (ch2 == '<') {
308        switch ([self peek:0]) {
309            case '=': //  (?<=  zero-width positive lookbehind
310            case '!': //  (?<!  zero-width negative lookbehind
311                [self eat];
312                break;
313            default:
314                [self setColor:errorColor toIndex:_indexOfNextChar];
315        }
316        return;
317    }
318    int dash = 0;
319    for (int i = 0; _indexOfNextChar < _txtLength; i++) {
320        if (i > 0)
321            ch2 = [self eat];
322        if (ch2 == ':')
323            return;
324        if (ch2 == ')') {
325            _indexOfNextChar--;
326            return;
327        }
328        if (ch2 == '-') {
329            if (++dash > 1) {
330                [self setColor:errorColor toIndex:_indexOfNextChar];
331                return;
332            }
333            continue;
334        }
335        if (ch2 == 'i' || ch2 == 'd' || ch2 == 'm' || ch2 == 's' || ch2 == 'u' || ch2 == 'x')
336            continue;
337        int ch3 = [self peek:0];
338        if (!i || ch3 != ')')
339            [self setColor:errorColor toIndex:_indexOfNextChar];
340        return;
341    }
342}
343
344- (void)regexpRepeatBraces
345{
346    int commas = 0;
347    NSInteger startIndex = _indexOfNextChar;
348    int min = 0;
349    int max = 0;
350    BOOL hasMin = NO;
351    BOOL hasMax = NO;
352    for (int i = 0; true; i++) {
353        if (i > 200) {
354            [self setColor:errorColor font:bracesFont toIndex:startIndex];
355            [self setColor:errorColor toIndex:_indexOfNextChar]; // unmatched '{'
356            return;
357        }
358        unichar ch = [self eat];
359        if (ch >= '0' && ch <= '9') {
360            if (commas > 0) {
361                max = max * 10 + ch - '0';
362                hasMax = YES;
363            } else {
364                min = min * 10 + ch - '0';
365                hasMin = YES;
366            }
367            continue;
368        }
369        if (ch == ',') {
370            commas++;
371            continue;
372        }
373        if (ch != '}') {
374            [self setColor:errorColor font:bracesFont toIndex:startIndex];
375            [self setColor:errorColor toIndex:_indexOfNextChar];
376            return;
377        }
378        // i == 0 means empty {}
379        BOOL hasError = !hasMin || (hasMax && max < min) || i == 0 || commas > 1;
380        JsColor* color = hasError ? errorColor : regexpSpecialsColor;
381        [self setColor:color font:bracesFont toIndex:startIndex];
382        [self setColor:color toIndex:_indexOfNextChar - 1];
383        [self setColor:color font:bracesFont toIndex:_indexOfNextChar];
384        return;
385    }
386}
387
388- (void)parseRegexp
389{
390    [self setColor:regexpStringColor toIndex:_indexOfNextChar];
391    // http://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Regular_Expressions
392    // http://java.sun.com/j2se/1.5.0/docs/api/java/util/regex/Pattern.html
393    int groupNestingLevel = 0;
394    int captureGroupCount = 0;
395    BOOL hasRepeatableItem = NO;
396    int squareCount = 0;
397    while (_indexOfNextChar < _txtLength) {
398        unichar ch = [self eat];
399        if (ch == '\n' || ch == '\r') {
400            [self setColor:errorColor toIndex:_indexOfNextChar];
401            return;
402        }
403        if (ch == '\\') {
404            hasRepeatableItem = YES;
405            ch = [self eat];
406            if (_isJavaRegexp) switch (ch) {
407                case 'a': // alert/bell (ascii 7)
408                case 'e': // escape (ascii 27)
409                case 'z': // end of input
410                case 'Z': // end of the line
411                    [self setColor:regexpSpecialsColor toIndex:_indexOfNextChar];
412                    continue;
413                case 'p': // posix class: \p{Lower}
414                    if (![self eat] == '{') {
415                        [self setColor:errorColor toIndex:_indexOfNextChar];
416                        continue;
417                    }
418                    while ([self eat] != '}') {
419                        if (_indexOfNextChar >= _txtLength) {
420                            [self setColor:errorColor toIndex:_indexOfNextChar];
421                            break;
422                        }
423                    }
424                    [self setColor:regexpSpecialsColor toIndex:_indexOfNextChar];
425                    continue;
426                case 'E': // end of \Q quoting  (unmatched)
427                    [self setColor:errorColor toIndex:_indexOfNextChar];
428                    continue;
429                case 'Q': // quotes until \E
430                    [self setColor:regexpSpecialsColor toIndex:_indexOfNextChar];
431                    while (_indexOfNextChar < _txtLength) {
432                        ch = [self eat];
433                        if (ch == '\\' && [self peek:0] == 'E') {
434                            [self setColor:regexpStringColor toIndex:_indexOfNextChar - 1];
435                            [self eat];
436                            [self setColor:regexpSpecialsColor toIndex:_indexOfNextChar];
437                            break;
438                        }
439                    }
440                    [self setColor:regexpStringColor toIndex:_indexOfNextChar]; // missing \E is allowed.
441                    continue;
442            }
443            switch (ch) {
444                case 'c': // \cM is control-M
445                    ch = [self eat];
446                    if ((ch >= 64 && ch <= 64+31) || (ch >= 96 && ch <= 96+31))
447                        [self setColor:regexpSpecialsColor toIndex:_indexOfNextChar];
448                    else
449                        [self setColor:errorColor toIndex:_indexOfNextChar];
450                    break;
451                case 'x': // \xHH  hexchar
452                    [self parseEscapeHexDigits:2];
453                    break;
454                case 'u': // \uHHHH  unicode hexchar
455                    [self parseEscapeHexDigits:4];
456                    break;
457                case 'b': // word boundary
458                case 'B': // not \b
459                    if (squareCount == 0)
460                        hasRepeatableItem = NO;
461                    [self setColor:regexpSpecialsColor toIndex:_indexOfNextChar];
462                    break;
463                case 'd': // digit
464                case 'D': // not \d
465                case 'f': // form feed
466                case 'n': // linefeed
467                case 'r': // carriage return
468                case 's': // whitespace
469                case 'S': // not whitespace
470                case 't': // tab
471                case 'v': // vertical tab
472                case 'w': // alphanum + underscore
473                case 'W': // not \w
474                case '0': // NUL char.
475                    [self setColor:regexpSpecialsColor toIndex:_indexOfNextChar];
476                    break;
477                case '.':
478                    [self setColor:regexpBackslashColor toIndex:_indexOfNextChar - 1];
479                    [self setColor:regexpStringColor font:dotFont toIndex:_indexOfNextChar];
480                    break;
481                default:
482                    if (squareCount == 0 && ch >= '1' && ch <= '9') {
483                        int num = ch - '0';
484                        while ([self peek:0] >= '0' && [self peek:0] <= '9')
485                            num = num * 10 + [self eat] - '0';
486                        if (num <= captureGroupCount)
487                            [self setColor:regexpSpecialsColor toIndex:_indexOfNextChar];
488                        else
489                            [self setColor:errorColor toIndex:_indexOfNextChar];
490                    } else {
491                        [self setColor:regexpBackslashColor toIndex:_indexOfNextChar - 1];
492                        [self setColor:regexpStringColor toIndex:_indexOfNextChar];
493                    }
494            }
495            continue;
496        }
497        if (ch == ']') {
498            if (squareCount) {
499                squareCount--;
500                [self setColor:regexpCharClassColor toIndex:_indexOfNextChar];
501            } else {
502                [self setColor:errorColor toIndex:_indexOfNextChar];
503            }
504            hasRepeatableItem = YES;
505            continue;
506        }
507        if (ch == '[') {
508            if (squareCount > (_isJavaRegexp ? 1 : 0)) {
509                [self setColor:errorColor toIndex:_indexOfNextChar];
510                continue;
511            }
512            int idx = 0;
513            if ([self peek:0] == '^')
514                idx++;
515            if ([self peek:idx] == '$')
516                idx++;
517            if ([self peek:idx] == ']') {
518                for (int i = 0; i <= idx; i++) // include ']'
519                    [self eat];
520                [self setColor:errorColor toIndex:_indexOfNextChar]; // empty [] is invalid
521                continue;
522            }
523            squareCount++;
524            [self setColor:regexpCharClassColor toIndex:_indexOfNextChar];
525            continue;
526        }
527        if (squareCount) {
528            if (ch == '-' && [self peek:0] != ']' && [self peek:-2] != '[' && [self peek:-2] != '^') {
529                // interval: [a-z]
530                [self setColor:regexpSpecialsColor toIndex:_indexOfNextChar];
531                continue;
532            }
533            if (ch == '^' && [self peek:-2] == '[') {
534                // anchor: [^a]
535                [self setColor:regexpSpecialsColor toIndex:_indexOfNextChar];
536                continue;
537            }
538            if (ch == '$' && [self peek:0] == ']') {
539                // anchor: [a$]
540                [self setColor:regexpSpecialsColor toIndex:_indexOfNextChar];
541                continue;
542            }
543            if (_isJavaRegexp && ch == '&' && [self peek:0] == '&' && [self peek:1] == '[') {
544                [self eat];
545                [self setColor:regexpSpecialsColor toIndex:_indexOfNextChar];
546            } else {
547                [self setColor:regexpCharClassColor toIndex:_indexOfNextChar];
548            }
549            continue;
550        }
551        if (ch == '(') {
552            [self setColor:regexpGroupParanColor font:boldFont toIndex:_indexOfNextChar];
553            groupNestingLevel++;
554            BOOL anon = ([self peek:0] == '?');
555            if (anon)
556                [self regexpGroupOptions];
557            else
558                captureGroupCount++;
559            [self setColor:regexpGroupOptionsColor toIndex:_indexOfNextChar];
560            hasRepeatableItem = NO;
561            continue;
562        }
563        if (ch == ')') {
564            if (groupNestingLevel > 0) {
565                groupNestingLevel--;
566                [self setColor:regexpGroupParanColor font:boldFont toIndex:_indexOfNextChar];
567            } else {
568                [self setColor:errorColor toIndex:_indexOfNextChar];
569            }
570            hasRepeatableItem = YES;
571            continue;
572        }
573        if (ch == '{') {
574            if (hasRepeatableItem)
575                [self regexpRepeatBraces];
576            else
577                [self setColor:errorColor font:bracesFont toIndex:_indexOfNextChar];
578            continue;
579        }
580        if (ch == '}') {
581            [self setColor:errorColor font:bracesFont toIndex:_indexOfNextChar]; // unmatched '}'
582            continue;
583        }
584        if (ch == '.') {
585            hasRepeatableItem = YES;
586            [self setColor:regexpSpecialsDotColor font:dotFont toIndex:_indexOfNextChar];
587            continue;
588        }
589        if (ch == '^' || ch == '$' || ch == '|') {
590            hasRepeatableItem = NO;
591            [self setColor:regexpSpecialsColor toIndex:_indexOfNextChar];
592            continue;
593        }
594        if (ch == '?' || ch == '*' || ch == '+') {
595            if (hasRepeatableItem)
596                [self setColor:regexpSpecialsColor toIndex:_indexOfNextChar];
597            else
598                [self setColor:errorColor toIndex:_indexOfNextChar];
599            continue;
600        }
601        if (!_isJavaRegexp && ch == '/')
602            break;
603        [self setColor:regexpStringColor toIndex:_indexOfNextChar];
604        hasRepeatableItem = YES;
605    }
606    if (_isJavaRegexp)
607        return;
608    [self setColor:regexpSpecialsColor font:boldFont toIndex:_indexOfNextChar];
609    while ([self peek:0] == 'g' || [self peek:0] == 'i' || [self peek:0] == 'm')
610        [self eat];
611    [self setColor:regexpSpecialsColor toIndex:_indexOfNextChar];
612}
613
614- (void)parseString:(unichar)quote
615{
616    while (true) {
617        unichar ch = [self eat];
618        if (ch == quote) {
619            [self setColor:stringColor toIndex:_indexOfNextChar];
620            return;
621        }
622        if (_indexOfNextChar >= _txtLength || ch == '\r' || ch == '\n') {
623            [self setColor:errorColor toIndex:_indexOfNextChar];
624            return;
625        }
626        if (ch == '.') {
627            [self setColor:stringColor font:dotFont toIndex:_indexOfNextChar];
628            continue;
629        }
630        if (ch != '\\') {
631            [self setColor:stringColor toIndex:_indexOfNextChar];
632            continue;
633        }
634        ch = [self eat];
635        if (isDecimalDigit(ch)) {
636            // old obsolete octal escape. Mark as error as it's 99% probable a bug in the transform script.
637            for (int i = 0; isDecimalDigit(ch) && i < 2; i++)
638                ch = [self eat];
639            [self setColor:errorColor toIndex:_indexOfNextChar];
640            continue;
641        }
642        switch (ch) {
643            case 'x':
644                [self parseEscapeHexDigits:2];
645                continue;
646            case 'u':
647                [self parseEscapeHexDigits:4];
648                continue;
649            case '0': // NUL char.
650            case 'b': // backspace, ascii 8
651            case 't': // tab
652            case 'n': // linefeed
653            case 'v': // vertical tab
654            case 'f': // form feed
655            case 'r': // carriage return
656                [self setColor:regexpSpecialsColor toIndex:_indexOfNextChar];
657                break;
658            default:
659                [self setColor:stringBackslashColor toIndex:_indexOfNextChar - 1];
660                if (ch == '.')
661                    [self setColor:stringColor font:dotFont toIndex:_indexOfNextChar];
662                else
663                    [self setColor:stringColor toIndex:_indexOfNextChar];
664        }
665    }
666}
667
668- (void)javaRegexpMain
669{
670#ifdef DEBUG_JS
671    DebugNSLog(@"==============================================================");
672    DebugNSLog(@"%@", _txt);
673    DebugNSLog(@"==============================================================");
674#endif
675    [self parseRegexp];
676    [self setColor:plainColor toIndex:_txtLength];
677    [self commitTextAttributes];
678}
679
680- (void)javascriptMain
681{
682#ifdef DEBUG_JS
683    DebugNSLog(@"==============================================================");
684    DebugNSLog(@"%@", _txt);
685    DebugNSLog(@"==============================================================");
686#endif
687    BOOL regexpCanStartHere = YES; // whicked hack to avoid creating full tokenizer. Seems to work, though.
688    while (_indexOfNextChar < _txtLength) {
689        unichar ch = [self eat];
690        unichar q;
691        NSInteger start;
692        switch (ch) {
693            case '/':
694                if ([self peek:0] == '/') {
695                    while (ch != 13 && ch != 10 && _indexOfNextChar < _txtLength)
696                        ch = [self eat];
697                    [self setColor:commentColor toIndex:_indexOfNextChar];
698                    continue;
699                }
700                if ([self peek:0] == '*') {
701                    [self eat];
702                    while ([self eat] != '*' || [self peek:0] != '/') {
703                        if (_indexOfNextChar >= _txtLength)  {
704                            [self setColor:errorColor toIndex:_indexOfNextChar];
705                            break;
706                        }
707                    }
708                    [self eat]; // last '/'
709                    [self setColor:commentColor toIndex:_indexOfNextChar];
710                    continue;
711                }
712                if (regexpCanStartHere) {
713                    [self setColor:regexpSpecialsColor font:boldFont toIndex:_indexOfNextChar];
714                    [self parseRegexp];
715                    regexpCanStartHere = NO;
716                    continue;
717                }
718                break;
719            case '"':
720            case '\'':
721                [self parseString:ch];
722                continue;
723            case '(':
724            case '=':
725            case ',':
726            case '?':
727            case ':':
728            case ';':
729            case '\n':
730                regexpCanStartHere = YES;
731                break;
732            case ' ':
733            case '\t':
734                break;
735            case '{':
736            case '}':
737                [self setColor:plainColor font:bracesFont toIndex:_indexOfNextChar];
738                regexpCanStartHere = YES;
739                continue;
740            default:
741                regexpCanStartHere = NO;
742                q = [self peek:-2];
743                if ([letterSet characterIsMember:ch] && ![letterDigitSet characterIsMember:q]) {
744                    start = _indexOfNextChar - 1;
745                    while (_indexOfNextChar < _txtLength) {
746                        ch = [self eat];
747                        if (![letterDigitSet characterIsMember:ch]) {
748                            _indexOfNextChar--;
749                            break;
750                        }
751                    }
752                    NSInteger len = MIN(_txtLength, _indexOfNextChar - start);
753                    NSString* s = [_txt substringWithRange:NSMakeRange(start, len)];
754                    //DebugNSLog(@"token %d -> %d = %d: {{%@}}", start, _indexOfNextChar, len, s);
755                    if ([jsKeywords objectForKey:s]) {
756                        [self setColor:keywordColor toIndex:_indexOfNextChar];
757                        continue;
758                    }
759                }
760        }
761        [self setColor:plainColor toIndex:_indexOfNextChar];
762    }
763    [self setColor:plainColor toIndex:_txtLength];
764    [self commitTextAttributes];
765}
766
767@end
Note: See TracBrowser for help on using the repository browser.