1/****************************************************************************
2**
3** Copyright (C) 2018 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtSCriptTools module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qscriptsyntaxhighlighter_p.h"
41
42#include <algorithm>
43
44#ifndef QT_NO_SYNTAXHIGHLIGHTER
45
46QT_BEGIN_NAMESPACE
47
48enum ScriptIds {
49 Comment = 1,
50 Number,
51 String,
52 Type,
53 Keyword,
54 PreProcessor,
55 Label
56};
57
58#define MAX_KEYWORD 63
59static const char *const keywords[MAX_KEYWORD] = {
60 "Infinity",
61 "NaN",
62 "abstract",
63 "boolean",
64 "break",
65 "byte",
66 "case",
67 "catch",
68 "char",
69 "class",
70 "const",
71 "constructor",
72 "continue",
73 "debugger",
74 "default",
75 "delete",
76 "do",
77 "double",
78 "else",
79 "enum",
80 "export",
81 "extends",
82 "false",
83 "final",
84 "finally",
85 "float",
86 "for",
87 "function",
88 "goto",
89 "if",
90 "implements",
91 "import",
92 "in",
93 "instanceof",
94 "int",
95 "interface",
96 "long",
97 "native",
98 "new",
99 "package",
100 "private",
101 "protected",
102 "public",
103 "return",
104 "short",
105 "static",
106 "super",
107 "switch",
108 "synchronized",
109 "this",
110 "throw",
111 "throws",
112 "transient",
113 "true",
114 "try",
115 "typeof",
116 "undefined",
117 "var",
118 "void",
119 "volatile",
120 "while",
121 "with", // end of array
122 0
123};
124
125struct KeywordHelper
126{
127 inline KeywordHelper(const QString &word) : needle(word) {}
128 const QString needle;
129};
130
131static bool operator<(const KeywordHelper &helper, const char *kw)
132{
133 return helper.needle < QLatin1String(kw);
134}
135
136static bool operator<(const char *kw, const KeywordHelper &helper)
137{
138 return QLatin1String(kw) < helper.needle;
139}
140
141static bool isKeyword(const QString &word)
142{
143 const char * const *start = &keywords[0];
144 const char * const *end = &keywords[MAX_KEYWORD - 1];
145
146 const KeywordHelper keywordHelper(word);
147 const char * const *kw = std::lower_bound(first: start, last: end, val: keywordHelper);
148
149 return kw != end && !(keywordHelper < *kw);
150}
151
152QScriptSyntaxHighlighter::QScriptSyntaxHighlighter(QTextDocument *document)
153 : QSyntaxHighlighter(document)
154{
155
156 m_formats[ScriptNumberFormat].setForeground(Qt::darkBlue);
157 m_formats[ScriptStringFormat].setForeground(Qt::darkGreen);
158 m_formats[ScriptTypeFormat].setForeground(Qt::darkMagenta);
159 m_formats[ScriptKeywordFormat].setForeground(Qt::darkYellow);
160 m_formats[ScriptPreprocessorFormat].setForeground(Qt::darkBlue);
161 m_formats[ScriptLabelFormat].setForeground(Qt::darkRed);
162 m_formats[ScriptCommentFormat].setForeground(Qt::darkGreen);
163 m_formats[ScriptCommentFormat].setFontItalic(true);
164}
165
166QScriptSyntaxHighlighter::~QScriptSyntaxHighlighter()
167{
168}
169
170void QScriptSyntaxHighlighter::highlightBlock(const QString &text)
171{
172
173 // states
174 enum States { StateStandard, StateCommentStart1, StateCCommentStart2,
175 StateScriptCommentStart2, StateCComment, StateScriptComment, StateCCommentEnd1,
176 StateCCommentEnd2, StateStringStart, StateString, StateStringEnd,
177 StateString2Start, StateString2, StateString2End,
178 StateNumber, StatePreProcessor, NumStates };
179
180 // tokens
181 enum Tokens { InputAlpha, InputNumber, InputAsterix, InputSlash, InputParen,
182 InputSpace, InputHash, InputQuotation, InputApostrophe, InputSep, NumTokens };
183
184 static uchar table[NumStates][NumTokens] = {
185 { StateStandard, StateNumber, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateStandard
186 { StateStandard, StateNumber, StateCCommentStart2, StateScriptCommentStart2, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateCommentStart1
187 { StateCComment, StateCComment, StateCCommentEnd1, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment }, // StateCCommentStart2
188 { StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment }, // ScriptCommentStart2
189 { StateCComment, StateCComment, StateCCommentEnd1, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment }, // StateCComment
190 { StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment }, // StateScriptComment
191 { StateCComment, StateCComment, StateCCommentEnd1, StateCCommentEnd2, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment }, // StateCCommentEnd1
192 { StateStandard, StateNumber, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateCCommentEnd2
193 { StateString, StateString, StateString, StateString, StateString, StateString, StateString, StateStringEnd, StateString, StateString }, // StateStringStart
194 { StateString, StateString, StateString, StateString, StateString, StateString, StateString, StateStringEnd, StateString, StateString }, // StateString
195 { StateStandard, StateStandard, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateStringEnd
196 { StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2End, StateString2 }, // StateString2Start
197 { StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2End, StateString2 }, // StateString2
198 { StateStandard, StateStandard, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateString2End
199 { StateNumber, StateNumber, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateNumber
200 { StatePreProcessor, StateStandard, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard } // StatePreProcessor
201 };
202
203 QString buffer;
204 buffer.reserve(asize: text.length());
205
206 QTextCharFormat emptyFormat;
207
208 int state = StateStandard;
209 int braceDepth = 0;
210 const int previousState = previousBlockState();
211 if (previousState != -1) {
212 state = previousState & 0xff;
213 braceDepth = previousState >> 8;
214 }
215
216 if (text.isEmpty()) {
217 setCurrentBlockState(previousState);
218#if 0
219 TextEditDocumentLayout::clearParentheses(currentBlock());
220#endif
221 return;
222 }
223#if 0
224 Parentheses parentheses;
225 parentheses.reserve(20); // assume wizard level ;-)
226#endif
227 int input = -1;
228 int i = 0;
229 bool lastWasBackSlash = false;
230 bool makeLastStandard = false;
231
232 static const QString alphabeth = QLatin1String("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
233 static const QString mathChars = QLatin1String("xXeE");
234 static const QString numbers = QLatin1String("0123456789");
235 bool questionMark = false;
236 QChar lastChar;
237
238 int firstNonSpace = -1;
239
240 for (;;) {
241 const QChar c = text.at(i);
242
243 if (lastWasBackSlash) {
244 input = InputSep;
245 } else {
246 switch (c.toLatin1()) {
247 case '*':
248 input = InputAsterix;
249 break;
250 case '/':
251 input = InputSlash;
252 break;
253 case '{':
254 braceDepth++;
255 // fall through
256 case '(': case '[':
257 input = InputParen;
258 switch (state) {
259 case StateStandard:
260 case StateNumber:
261 case StatePreProcessor:
262 case StateCCommentEnd2:
263 case StateCCommentEnd1:
264 case StateString2End:
265 case StateStringEnd:
266// parentheses.push_back(Parenthesis(Parenthesis::Opened, c, i));
267 break;
268 default:
269 break;
270 }
271 break;
272 case '}':
273 if (--braceDepth < 0)
274 braceDepth = 0;
275 // fall through
276 case ')': case ']':
277 input = InputParen;
278 switch (state) {
279 case StateStandard:
280 case StateNumber:
281 case StatePreProcessor:
282 case StateCCommentEnd2:
283 case StateCCommentEnd1:
284 case StateString2End:
285 case StateStringEnd:
286// parentheses.push_back(Parenthesis(Parenthesis::Closed, c, i));
287 break;
288 default:
289 break;
290 }
291 break;
292 case '#':
293 input = InputHash;
294 break;
295 case '"':
296 input = InputQuotation;
297 break;
298 case '\'':
299 input = InputApostrophe;
300 break;
301 case ' ':
302 input = InputSpace;
303 break;
304 case '1': case '2': case '3': case '4': case '5':
305 case '6': case '7': case '8': case '9': case '0':
306 if (alphabeth.contains(c: lastChar)
307 && (!mathChars.contains(c: lastChar) || !numbers.contains(c: text.at(i: i - 1)))
308 ) {
309 input = InputAlpha;
310 } else {
311 if (input == InputAlpha && numbers.contains(c: lastChar))
312 input = InputAlpha;
313 else
314 input = InputNumber;
315 }
316 break;
317 case ':': {
318 input = InputAlpha;
319 const QChar colon = QLatin1Char(':');
320 if (state == StateStandard && !questionMark && lastChar != colon) {
321 const QChar nextChar = i < text.length() - 1 ? text.at(i: i + 1) : QLatin1Char(' ');
322 if (nextChar != colon)
323 for (int j = 0; j < i; ++j) {
324 if (format(pos: j) == emptyFormat )
325 setFormat(start: j, count: 1, format: m_formats[ScriptLabelFormat]);
326 }
327 }
328 } break;
329 default:
330 if (!questionMark && c == QLatin1Char('?'))
331 questionMark = true;
332 if (c.isLetter() || c == QLatin1Char('_'))
333 input = InputAlpha;
334 else
335 input = InputSep;
336 break;
337 }
338 }
339
340 if (input != InputSpace) {
341 if (firstNonSpace < 0)
342 firstNonSpace = i;
343 }
344
345 lastWasBackSlash = !lastWasBackSlash && c == QLatin1Char('\\');
346
347 if (input == InputAlpha)
348 buffer += c;
349
350 state = table[state][input];
351
352 switch (state) {
353 case StateStandard: {
354 setFormat(start: i, count: 1, format: emptyFormat);
355 if (makeLastStandard)
356 setFormat(start: i - 1, count: 1, format: emptyFormat);
357 makeLastStandard = false;
358 if (input != InputAlpha) {
359 highlightWord(currentPos: i, buffer);
360 buffer = QString();
361 }
362 } break;
363 case StateCommentStart1:
364 if (makeLastStandard)
365 setFormat(start: i - 1, count: 1, format: emptyFormat);
366 makeLastStandard = true;
367 buffer = QString();
368 break;
369 case StateCCommentStart2:
370 setFormat(start: i - 1, count: 2, format: m_formats[ScriptCommentFormat]);
371 makeLastStandard = false;
372// parentheses.push_back(Parenthesis(Parenthesis::Opened, QLatin1Char('/'), i-1));
373 buffer = QString();
374 break;
375 case StateScriptCommentStart2:
376 setFormat(start: i - 1, count: 2, format: m_formats[ScriptCommentFormat]);
377 makeLastStandard = false;
378 buffer = QString();
379 break;
380 case StateCComment:
381 if (makeLastStandard)
382 setFormat(start: i - 1, count: 1, format: emptyFormat);
383 makeLastStandard = false;
384 setFormat(start: i, count: 1, format: m_formats[ScriptCommentFormat]);
385 buffer = QString();
386 break;
387 case StateScriptComment:
388 if (makeLastStandard)
389 setFormat(start: i - 1, count: 1, format: emptyFormat);
390 makeLastStandard = false;
391 setFormat(start: i, count: 1, format: m_formats[ScriptCommentFormat]);
392 buffer = QString();
393 break;
394 case StateCCommentEnd1:
395 if (makeLastStandard)
396 setFormat(start: i - 1, count: 1, format: emptyFormat);
397 makeLastStandard = false;
398 setFormat(start: i, count: 1, format: m_formats[ScriptCommentFormat]);
399 buffer = QString();
400 break;
401 case StateCCommentEnd2:
402 if (makeLastStandard)
403 setFormat(start: i - 1, count: 1, format: emptyFormat);
404 makeLastStandard = false;
405 setFormat(start: i, count: 1, format: m_formats[ScriptCommentFormat]);
406// parentheses.push_back(Parenthesis(Parenthesis::Closed, QLatin1Char('/'), i));
407 buffer = QString();
408 break;
409 case StateStringStart:
410 if (makeLastStandard)
411 setFormat(start: i - 1, count: 1, format: emptyFormat);
412 makeLastStandard = false;
413 setFormat(start: i, count: 1, format: emptyFormat);
414 buffer = QString();
415 break;
416 case StateString:
417 if (makeLastStandard)
418 setFormat(start: i - 1, count: 1, format: emptyFormat);
419 makeLastStandard = false;
420 setFormat(start: i, count: 1, format: m_formats[ScriptStringFormat]);
421 buffer = QString();
422 break;
423 case StateStringEnd:
424 if (makeLastStandard)
425 setFormat(start: i - 1, count: 1, format: emptyFormat);
426 makeLastStandard = false;
427 setFormat(start: i, count: 1, format: emptyFormat);
428 buffer = QString();
429 break;
430 case StateString2Start:
431 if (makeLastStandard)
432 setFormat(start: i - 1, count: 1, format: emptyFormat);
433 makeLastStandard = false;
434 setFormat(start: i, count: 1, format: emptyFormat);
435 buffer = QString();
436 break;
437 case StateString2:
438 if (makeLastStandard)
439 setFormat(start: i - 1, count: 1, format: emptyFormat);
440 makeLastStandard = false;
441 setFormat(start: i, count: 1, format: m_formats[ScriptStringFormat]);
442 buffer = QString();
443 break;
444 case StateString2End:
445 if (makeLastStandard)
446 setFormat(start: i - 1, count: 1, format: emptyFormat);
447 makeLastStandard = false;
448 setFormat(start: i, count: 1, format: emptyFormat);
449 buffer = QString();
450 break;
451 case StateNumber:
452 if (makeLastStandard)
453 setFormat(start: i - 1, count: 1, format: emptyFormat);
454 makeLastStandard = false;
455 setFormat(start: i, count: 1, format: m_formats[ScriptNumberFormat]);
456 buffer = QString();
457 break;
458 case StatePreProcessor:
459 if (makeLastStandard)
460 setFormat(start: i - 1, count: 1, format: emptyFormat);
461 makeLastStandard = false;
462 setFormat(start: i, count: 1, format: m_formats[ScriptPreprocessorFormat]);
463 buffer = QString();
464 break;
465 }
466
467 lastChar = c;
468 i++;
469 if (i >= text.length()) {
470#if 0
471 if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(currentBlock())) {
472 userData->setHasClosingCollapse(false);
473 userData->setCollapseMode(TextBlockUserData::NoCollapse);
474 }
475 int collapse = Parenthesis::collapseAtPos(parentheses);
476 if (collapse >= 0) {
477 if (collapse == firstNonSpace)
478 TextEditDocumentLayout::userData(currentBlock())->setCollapseMode(TextBlockUserData::CollapseThis);
479 else
480 TextEditDocumentLayout::userData(currentBlock())->setCollapseMode(TextBlockUserData::CollapseAfter);
481 }
482 if (Parenthesis::hasClosingCollapse(parentheses)) {
483 TextEditDocumentLayout::userData(currentBlock())->setHasClosingCollapse(true);
484 }
485#endif
486
487 break;
488 }
489 }
490
491 highlightWord(currentPos: text.length(), buffer);
492
493 switch (state) {
494 case StateCComment:
495 case StateCCommentEnd1:
496 case StateCCommentStart2:
497 state = StateCComment;
498 break;
499 case StateString:
500 // quotes cannot span multiple lines, so if somebody starts
501 // typing a quoted string we don't need to look for the ending
502 // quote in another line (or highlight until the end of the
503 // document) and therefore slow down editing.
504 state = StateStandard;
505 break;
506 case StateString2:
507 state = StateStandard;
508 break;
509 default:
510 state = StateStandard;
511 break;
512 }
513
514#if 0
515 TextEditDocumentLayout::setParentheses(currentBlock(), parentheses);
516#endif
517
518 setCurrentBlockState((braceDepth << 8) | state);
519}
520
521void QScriptSyntaxHighlighter::highlightWord(int currentPos, const QString &buffer)
522{
523 if (buffer.isEmpty())
524 return;
525
526 // try to highlight Qt 'identifiers' like QObject and Q_PROPERTY
527 // but don't highlight words like 'Query'
528 if (buffer.length() > 1
529 && buffer.at(i: 0) == QLatin1Char('Q')
530 && (buffer.at(i: 1).isUpper()
531 || buffer.at(i: 1) == QLatin1Char('_')
532 || buffer.at(i: 1) == QLatin1Char('t'))) {
533 setFormat(start: currentPos - buffer.length(), count: buffer.length(), format: m_formats[ScriptTypeFormat]);
534 } else {
535 if (isKeyword(word: buffer))
536 setFormat(start: currentPos - buffer.length(), count: buffer.length(), format: m_formats[ScriptKeywordFormat]);
537 }
538}
539
540QT_END_NAMESPACE
541
542#endif // QT_NO_SYNTAXHIGHLIGHTER
543
544

source code of qtscript/src/scripttools/debugging/qscriptsyntaxhighlighter.cpp