1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights.  These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33 
34 #include "specificrules.h"
35 #include "highlightdefinition.h"
36 #include "keywordlist.h"
37 #include "progressdata.h"
38 #include "reuse.h"
39 #include <QDebug>
40 
41 #include <QtCore/QLatin1Char>
42 
43 using namespace TextEditor;
44 using namespace Internal;
45 
46 namespace {
47 
replaceByCaptures(QChar * c,const QStringList & captures)48 void replaceByCaptures(QChar *c, const QStringList &captures)
49 {
50     int index = c->digitValue();
51     if (index > 0) {
52         const QString &capture = captures.at(index);
53         if (!capture.isEmpty())
54             *c = capture.at(0);
55     }
56 }
57 
replaceByCaptures(QString * s,const QStringList & captures)58 void replaceByCaptures(QString *s, const QStringList &captures)
59 {
60     static const QLatin1Char kPercent('%');
61 
62     int index;
63     int from = 0;
64     while ((index = s->indexOf(kPercent, from)) != -1) {
65         from = index + 1;
66 
67         QString accumulator;
68         while (from < s->length() && s->at(from).isDigit()) {
69             accumulator.append(s->at(from));
70             ++from;
71         }
72 
73         bool ok;
74         int number = accumulator.toInt(&ok);
75         Q_ASSERT(ok);
76 
77         s->replace(index, accumulator.length() + 1, captures.at(number));
78     }
79 }
80 }
81 
82 // DetectChar
setChar(const QString & character)83 void DetectCharRule::setChar(const QString &character)
84 { setStartCharacter(&m_char, character); }
85 
doReplaceExpressions(const QStringList & captures)86 void DetectCharRule::doReplaceExpressions(const QStringList &captures)
87 { replaceByCaptures(&m_char, captures); }
88 
doMatchSucceed(const QString & text,const int length,ProgressData * progress)89 bool DetectCharRule::doMatchSucceed(const QString &text,
90                                     const int length,
91                                     ProgressData *progress)
92 {
93     if (matchCharacter(text, length, progress, m_char)) {
94         return true;
95     }
96     return false;
97 //    if (matchCharacter(text, length, progress, m_char)) {
98 //        return true;
99 //        // This is to make code folding have a control flow style look in the case of braces.
100 //        // Naturally, this assumes that language definitions use braces with this meaning.
101 //        if (m_char == kOpeningBrace && progress->isOnlySpacesSoFar() && !isLookAhead()) {
102 //            progress->setOpeningBraceMatchAtFirstNonSpace(true);
103 //        } else if (m_char == kClosingBrace &&
104 //                   !text.right(length - progress->offset()).trimmed().isEmpty()) {
105 //            progress->setClosingBraceMatchAtNonEnd(true);
106 //        }
107 //        return true;
108 //    }
109 //    return false;
110 
111 //    if (matchCharacter(text, length, progress, m_char)) {
112 //        qDebug() << m_char << text;
113 //        if (text.trimmed() == "} else {" && m_char == '}') {
114 //            //qDebug() << "error";
115 //             progress->setClosingBraceMatchAtNonEnd(true);
116 //        }
117 //        return true;
118 //        // This is to make code folding have a control flow style look in the case of braces.
119 //        // Naturally, this assumes that language definitions use braces with this meaning.
120 //        if ( (m_char == '{' || m_char == '(' || m_char == '[')
121 //             && progress->isOnlySpacesSoFar() && !isLookAhead()) {
122 //            progress->setOpeningBraceMatchAtFirstNonSpace(true);
123 //        } else if ( (m_char == '}' || m_char == ')' || m_char == ']') &&
124 //                    !text.right(length - progress->offset()).trimmed().isEmpty()) {
125 //            progress->setClosingBraceMatchAtNonEnd(true);
126 //        }
127 //        if (m_char == kOpeningBrace && progress->isOnlySpacesSoFar() && !isLookAhead()) {
128 //            progress->setOpeningBraceMatchAtFirstNonSpace(true);
129 //        } else if (m_char == kClosingBrace &&
130 //                   !text.right(length - progress->offset()).trimmed().isEmpty()) {
131 //            progress->setClosingBraceMatchAtNonEnd(true);
132 //        }
133 //        return true;
134 //    }
135 //    return false;
136 }
137 
138 // Detect2Chars
setChar(const QString & character)139 void Detect2CharsRule::setChar(const QString &character)
140 { setStartCharacter(&m_char, character); }
141 
setChar1(const QString & character)142 void Detect2CharsRule::setChar1(const QString &character)
143 { setStartCharacter(&m_char1, character); }
144 
doReplaceExpressions(const QStringList & captures)145 void Detect2CharsRule::doReplaceExpressions(const QStringList &captures)
146 {
147     replaceByCaptures(&m_char, captures);
148     replaceByCaptures(&m_char1, captures);
149 }
150 
doMatchSucceed(const QString & text,const int length,ProgressData * progress)151 bool Detect2CharsRule::doMatchSucceed(const QString &text,
152                                       const int length,
153                                       ProgressData *progress)
154 {
155     if (matchCharacter(text, length, progress, m_char)) {
156         if (progress->offset() < length && matchCharacter(text, length, progress, m_char1, false))
157             return true;
158         else
159             progress->restoreOffset();
160     }
161 
162     return false;
163 }
164 
165 // AnyChar
setCharacterSet(const QString & s)166 void AnyCharRule::setCharacterSet(const QString &s)
167 { m_characterSet = s; }
168 
doMatchSucceed(const QString & text,const int length,ProgressData * progress)169 bool AnyCharRule::doMatchSucceed(const QString &text,
170                                  const int length,
171                                  ProgressData *progress)
172 {
173     Q_UNUSED(length)
174 
175     if (m_characterSet.contains(text.at(progress->offset()))) {
176         progress->incrementOffset();
177         return true;
178     }
179 
180     return false;
181 }
182 
183 // StringDetect
setString(const QString & s)184 void StringDetectRule::setString(const QString &s)
185 {
186     m_string = s;
187     m_length = m_string.length();
188 }
189 
setInsensitive(const QString & insensitive)190 void StringDetectRule::setInsensitive(const QString &insensitive)
191 { m_caseSensitivity = toCaseSensitivity(!toBool(insensitive)); }
192 
doReplaceExpressions(const QStringList & captures)193 void StringDetectRule::doReplaceExpressions(const QStringList &captures)
194 {
195     replaceByCaptures(&m_string, captures);
196     m_length = m_string.length();
197 }
198 
doMatchSucceed(const QString & text,const int length,ProgressData * progress)199 bool StringDetectRule::doMatchSucceed(const QString &text,
200                                       const int length,
201                                       ProgressData *progress)
202 {
203     if (length - progress->offset() >= m_length) {
204         QString candidate = text.fromRawData(text.unicode() + progress->offset(), m_length);
205         if (candidate.compare(m_string, m_caseSensitivity) == 0) {
206             progress->incrementOffset(m_length);
207             return true;
208         }
209     }
210 
211     return false;
212 }
213 
214 // RegExpr
setPattern(const QString & pattern)215 void RegExprRule::setPattern(const QString &pattern)
216 {
217     if (pattern.startsWith(QLatin1Char('^')))
218         m_onlyBegin = true;
219     m_expression.setPattern(pattern);
220 }
221 
setInsensitive(const QString & insensitive)222 void RegExprRule::setInsensitive(const QString &insensitive)
223 { m_expression.setCaseSensitivity(toCaseSensitivity(!toBool(insensitive))); }
224 
setMinimal(const QString & minimal)225 void RegExprRule::setMinimal(const QString &minimal)
226 { m_expression.setMinimal(toBool(minimal)); }
227 
doReplaceExpressions(const QStringList & captures)228 void RegExprRule::doReplaceExpressions(const QStringList &captures)
229 {
230     QString s = m_expression.pattern();
231     replaceByCaptures(&s, captures);
232     m_expression.setPattern(s);
233 }
234 
doProgressFinished()235 void RegExprRule::doProgressFinished()
236 {
237     m_isCached = false;
238 }
239 
isExactMatch(ProgressData * progress)240 bool RegExprRule::isExactMatch(ProgressData *progress)
241 {
242     if (progress->offset() == m_offset && m_length > 0) {
243         progress->incrementOffset(m_length);
244         progress->setCaptures(m_captures);
245         return true;
246     }
247     return false;
248 }
249 
doMatchSucceed(const QString & text,const int length,ProgressData * progress)250 bool RegExprRule::doMatchSucceed(const QString &text,
251                                  const int length,
252                                  ProgressData *progress)
253 {
254     Q_UNUSED(length)
255 
256     // A regular expression match is considered valid if it happens at the current position
257     // and if the match length is not zero.
258     const int offset = progress->offset();
259     if (offset > 0 && m_onlyBegin)
260         return false;
261 
262     if (m_isCached) {
263         if (offset < m_offset || m_offset == -1 || m_length == 0)
264             return false;
265         if (isExactMatch(progress))
266             return true;
267     }
268 
269     m_offset = m_expression.indexIn(text, offset, QRegExp::CaretAtOffset);
270     m_length = m_expression.matchedLength();
271     m_captures = m_expression.capturedTexts();
272 
273     if (isExactMatch(progress))
274         return true;
275 
276     m_isCached = true;
277     progress->trackRule(this);
278 
279     return false;
280 }
281 
282 // Keyword
KeywordRule(const QSharedPointer<HighlightDefinition> & definition)283 KeywordRule::KeywordRule(const QSharedPointer<HighlightDefinition> &definition) :
284     m_overrideGlobal(false),
285     m_localCaseSensitivity(Qt::CaseSensitive)
286 {
287     setDefinition(definition);
288 }
289 
~KeywordRule()290 KeywordRule::~KeywordRule()
291 {}
292 
setInsensitive(const QString & insensitive)293 void KeywordRule::setInsensitive(const QString &insensitive)
294 {
295     if (!insensitive.isEmpty()) {
296         m_overrideGlobal = true;
297         m_localCaseSensitivity = toCaseSensitivity(!toBool(insensitive));
298     }
299 }
300 
setList(const QString & listName)301 void KeywordRule::setList(const QString &listName)
302 { m_list = definition()->keywordList(listName); }
303 
doMatchSucceed(const QString & text,const int length,ProgressData * progress)304 bool KeywordRule::doMatchSucceed(const QString &text,
305                                  const int length,
306                                  ProgressData *progress)
307 {
308     int current = progress->offset();
309 
310     if (current > 0 && !definition()->isDelimiter(text.at(current - 1)))
311         return false;
312     if (definition()->isDelimiter(text.at(current)))
313         return false;
314 
315     while (current < length && !definition()->isDelimiter(text.at(current)))
316         ++current;
317 
318     QString candidate =
319         QString::fromRawData(text.unicode() + progress->offset(), current - progress->offset());
320     if ((m_overrideGlobal && m_list->isKeyword(candidate, m_localCaseSensitivity)) ||
321         (!m_overrideGlobal && m_list->isKeyword(candidate, definition()->keywordsSensitive()))) {
322         progress->setOffset(current);
323         return true;
324     }
325 
326     return false;
327 }
328 
329 // Int
doMatchSucceed(const QString & text,const int length,ProgressData * progress)330 bool IntRule::doMatchSucceed(const QString &text,
331                              const int length,
332                              ProgressData *progress)
333 {
334     const int offset = progress->offset();
335 
336     // This is necessary to correctly highlight an invalid octal like 09, for example.
337     if (offset > 0 && text.at(offset - 1).isDigit())
338         return false;
339 
340     if (text.at(offset).isDigit() && text.at(offset) != kZero) {
341         progress->incrementOffset();
342         charPredicateMatchSucceed(text, length, progress, &QChar::isDigit);
343         return true;
344     }
345 
346     return false;
347 }
348 
349 // Float
doMatchSucceed(const QString & text,const int length,ProgressData * progress)350 bool FloatRule::doMatchSucceed(const QString &text, const int length, ProgressData *progress)
351 {
352     progress->saveOffset();
353 
354     bool integralPart = charPredicateMatchSucceed(text, length, progress, &QChar::isDigit);
355 
356     bool decimalPoint = false;
357     if (progress->offset() < length && text.at(progress->offset()) == kDot) {
358         progress->incrementOffset();
359         decimalPoint = true;
360     }
361 
362     bool fractionalPart = charPredicateMatchSucceed(text, length, progress, &QChar::isDigit);
363 
364     bool exponentialPart = false;
365     int offset = progress->offset();
366     if (offset < length && (text.at(offset) == kE || text.at(offset).toLower() == kE)) {
367         progress->incrementOffset();
368 
369         offset = progress->offset();
370         if (offset < length && (text.at(offset) == kPlus || text.at(offset) == kMinus))
371             progress->incrementOffset();
372 
373         if (charPredicateMatchSucceed(text, length, progress, &QChar::isDigit)) {
374             exponentialPart = true;
375         } else {
376             progress->restoreOffset();
377             return false;
378         }
379     }
380 
381     if ((integralPart || fractionalPart) && (decimalPoint || exponentialPart))
382         return true;
383 
384     progress->restoreOffset();
385     return false;
386 }
387 
388 // COctal
doMatchSucceed(const QString & text,const int length,ProgressData * progress)389 bool HlCOctRule::doMatchSucceed(const QString &text,
390                                 const int length,
391                                 ProgressData *progress)
392 {
393     if (matchCharacter(text, length, progress, kZero)) {
394         // In the definition files the number matching rules which are more restrictive should
395         // appear before the rules which are least resctritive. Although this happens in general
396         // there is at least one case where this is not strictly followed for existent definition
397         // files (specifically, HlCHex comes before HlCOct). So the condition below.
398         const int offset = progress->offset();
399         if (offset < length && (text.at(offset) == kX || text.at(offset).toLower() == kX)) {
400             progress->restoreOffset();
401             return false;
402         }
403 
404         charPredicateMatchSucceed(text, length, progress, &isOctalDigit);
405         return true;
406     }
407 
408     return false;
409 }
410 
411 // CHex
doMatchSucceed(const QString & text,const int length,ProgressData * progress)412 bool HlCHexRule::doMatchSucceed(const QString &text,
413                                 const int length,
414                                 ProgressData *progress)
415 {
416     if (matchCharacter(text, length, progress, kZero)) {
417         const int offset = progress->offset();
418         if (offset < length && text.at(offset) != kX && text.at(offset).toLower() != kX) {
419             progress->restoreOffset();
420             return false;
421         }
422 
423         progress->incrementOffset();
424         if (charPredicateMatchSucceed(text, length, progress, &isHexDigit))
425             return true;
426         else
427             progress->restoreOffset();
428     }
429 
430     return false;
431 }
432 
433 // CString
doMatchSucceed(const QString & text,const int length,ProgressData * progress)434 bool HlCStringCharRule::doMatchSucceed(const QString &text,
435                                        const int length,
436                                        ProgressData *progress)
437 {
438     if (matchEscapeSequence(text, length, progress))
439         return true;
440 
441     if (matchOctalSequence(text, length, progress))
442         return true;
443 
444     if (matchHexSequence(text, length, progress))
445         return true;
446 
447     return false;
448 }
449 
450 // CChar
doMatchSucceed(const QString & text,const int length,ProgressData * progress)451 bool HlCCharRule::doMatchSucceed(const QString &text,
452                                  const int length,
453                                  ProgressData *progress)
454 {
455     if (matchCharacter(text, length, progress, kSingleQuote)) {
456         if (progress->offset() < length) {
457             if (text.at(progress->offset()) != kBackSlash &&
458                 text.at(progress->offset()) != kSingleQuote) {
459                 progress->incrementOffset();
460             } else if (!matchEscapeSequence(text, length, progress, false)) {
461                 progress->restoreOffset();
462                 return false;
463             }
464 
465             if (progress->offset() < length &&
466                 matchCharacter(text, length, progress, kSingleQuote, false)) {
467                 return true;
468             } else {
469                 progress->restoreOffset();
470             }
471         } else {
472             progress->restoreOffset();
473         }
474     }
475 
476     return false;
477 }
478 
479 // RangeDetect
setChar(const QString & character)480 void RangeDetectRule::setChar(const QString &character)
481 { setStartCharacter(&m_char, character); }
482 
setChar1(const QString & character)483 void RangeDetectRule::setChar1(const QString &character)
484 { setStartCharacter(&m_char1, character); }
485 
doMatchSucceed(const QString & text,const int length,ProgressData * progress)486 bool RangeDetectRule::doMatchSucceed(const QString &text,
487                                      const int length,
488                                      ProgressData *progress)
489 {
490     if (matchCharacter(text, length, progress, m_char)) {
491         while (progress->offset() < length) {
492             if (matchCharacter(text, length, progress, m_char1, false))
493                 return true;
494             progress->incrementOffset();
495         }
496         progress->restoreOffset();
497     }
498 
499     return false;
500 }
501 
502 // LineContinue
doMatchSucceed(const QString & text,const int length,ProgressData * progress)503 bool LineContinueRule::doMatchSucceed(const QString &text,
504                                       const int length,
505                                       ProgressData *progress)
506 {
507     if (progress->offset() != length - 1)
508         return false;
509 
510     if (text.at(progress->offset()) == kBackSlash) {
511         progress->incrementOffset();
512         progress->setWillContinueLine(true);
513         return true;
514     }
515 
516     return false;
517 }
518 
519 // DetectSpaces
DetectSpacesRule()520 DetectSpacesRule::DetectSpacesRule() : Rule(false)
521 {}
522 
doMatchSucceed(const QString & text,const int length,ProgressData * progress)523 bool DetectSpacesRule::doMatchSucceed(const QString &text,
524                                       const int length,
525                                       ProgressData *progress)
526 {
527     return charPredicateMatchSucceed(text, length, progress, &QChar::isSpace);
528 }
529 
530 // DetectIdentifier
doMatchSucceed(const QString & text,const int length,ProgressData * progress)531 bool DetectIdentifierRule::doMatchSucceed(const QString &text,
532                                           const int length,
533                                           ProgressData *progress)
534 {
535     // Identifiers are characterized by a letter or underscore as the first character and then
536     // zero or more word characters (\w*).
537     if (text.at(progress->offset()).isLetter() || text.at(progress->offset()) == kUnderscore) {
538         progress->incrementOffset();
539         while (progress->offset() < length) {
540             const QChar &current = text.at(progress->offset());
541             if (current.isLetterOrNumber() || current.isMark() || current == kUnderscore)
542                 progress->incrementOffset();
543             else
544                 break;
545         }
546         return true;
547     }
548     return false;
549 }
550