Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
5 : * This Source Code Form is subject to the terms of the Mozilla Public
6 : * License, v. 2.0. If a copy of the MPL was not distributed with this
7 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
11 : * Licensed to the Apache Software Foundation (ASF) under one or more
12 : * contributor license agreements. See the NOTICE file distributed
13 : * with this work for additional information regarding copyright
14 : * ownership. The ASF licenses this file to you under the Apache
15 : * License, Version 2.0 (the "License"); you may not use this file
16 : * except in compliance with the License. You may obtain a copy of
17 : * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 : */
19 :
20 : #include <sal/config.h>
21 :
22 : #include <cassert>
23 :
24 : #include <unicode/uchar.h>
25 : #include <comphelper/syntaxhighlight.hxx>
26 : #include <comphelper/string.hxx>
27 :
28 : // Flags for character properties
29 : #define CHAR_START_IDENTIFIER 0x0001
30 : #define CHAR_IN_IDENTIFIER 0x0002
31 : #define CHAR_START_NUMBER 0x0004
32 : #define CHAR_IN_NUMBER 0x0008
33 : #define CHAR_IN_HEX_NUMBER 0x0010
34 : #define CHAR_IN_OCT_NUMBER 0x0020
35 : #define CHAR_START_STRING 0x0040
36 : #define CHAR_OPERATOR 0x0080
37 : #define CHAR_SPACE 0x0100
38 : #define CHAR_EOL 0x0200
39 :
40 : // ##########################################################################
41 : // ATTENTION: all these words need to be in lower case
42 : // ##########################################################################
43 : static const char* strListBasicKeyWords[] = {
44 : "access",
45 : "alias",
46 : "and",
47 : "any",
48 : "append",
49 : "as",
50 : "attribute",
51 : "base",
52 : "binary",
53 : "boolean",
54 : "byref",
55 : "byte",
56 : "byval",
57 : "call",
58 : "case",
59 : "cdecl",
60 : "classmodule",
61 : "close",
62 : "compare",
63 : "compatible",
64 : "const",
65 : "currency",
66 : "date",
67 : "declare",
68 : "defbool",
69 : "defcur",
70 : "defdate",
71 : "defdbl",
72 : "deferr",
73 : "defint",
74 : "deflng",
75 : "defobj",
76 : "defsng",
77 : "defstr",
78 : "defvar",
79 : "dim",
80 : "do",
81 : "double",
82 : "each",
83 : "else",
84 : "elseif",
85 : "end",
86 : "end enum",
87 : "end function",
88 : "end if",
89 : "end property",
90 : "end select",
91 : "end sub",
92 : "end type",
93 : "endif",
94 : "enum",
95 : "eqv",
96 : "erase",
97 : "error",
98 : "exit",
99 : "explicit",
100 : "for",
101 : "function",
102 : "get",
103 : "global",
104 : "gosub",
105 : "goto",
106 : "if",
107 : "imp",
108 : "implements",
109 : "in",
110 : "input",
111 : "integer",
112 : "is",
113 : "let",
114 : "lib",
115 : "like",
116 : "line",
117 : "line input",
118 : "local",
119 : "lock",
120 : "long",
121 : "loop",
122 : "lprint",
123 : "lset",
124 : "mod",
125 : "name",
126 : "new",
127 : "next",
128 : "not",
129 : "object",
130 : "on",
131 : "open",
132 : "option",
133 : "optional",
134 : "or",
135 : "output",
136 : "paramarray",
137 : "preserve",
138 : "print",
139 : "private",
140 : "property",
141 : "public",
142 : "random",
143 : "read",
144 : "redim",
145 : "rem",
146 : "resume",
147 : "return",
148 : "rset",
149 : "select",
150 : "set",
151 : "shared",
152 : "single",
153 : "static",
154 : "step",
155 : "stop",
156 : "string",
157 : "sub",
158 : "system",
159 : "text",
160 : "then",
161 : "to",
162 : "type",
163 : "typeof",
164 : "until",
165 : "variant",
166 : "vbasupport",
167 : "wend",
168 : "while",
169 : "with",
170 : "withevents",
171 : "write",
172 : "xor"
173 : };
174 :
175 :
176 : static const char* strListSqlKeyWords[] = {
177 : "all",
178 : "and",
179 : "any",
180 : "as",
181 : "asc",
182 : "avg",
183 : "between",
184 : "by",
185 : "cast",
186 : "corresponding",
187 : "count",
188 : "create",
189 : "cross",
190 : "delete",
191 : "desc",
192 : "distinct",
193 : "drop",
194 : "escape",
195 : "except",
196 : "exists",
197 : "false",
198 : "from",
199 : "full",
200 : "global",
201 : "group",
202 : "having",
203 : "in",
204 : "inner",
205 : "insert",
206 : "intersect",
207 : "into",
208 : "is",
209 : "join",
210 : "left",
211 : "like",
212 : "limit",
213 : "local",
214 : "match",
215 : "max",
216 : "min",
217 : "natural",
218 : "not",
219 : "null",
220 : "on",
221 : "or",
222 : "order",
223 : "outer",
224 : "right",
225 : "select",
226 : "set",
227 : "some",
228 : "sum",
229 : "table",
230 : "temporary",
231 : "true",
232 : "union",
233 : "unique",
234 : "unknown",
235 : "update",
236 : "using",
237 : "values",
238 : "where"
239 : };
240 :
241 :
242 90 : extern "C" int compare_strings( const void *arg1, const void *arg2 )
243 : {
244 90 : return strcmp( static_cast<char const *>(arg1), *static_cast<char * const *>(arg2) );
245 : }
246 :
247 :
248 : namespace
249 : {
250 0 : bool isAlpha(sal_Unicode c)
251 : {
252 0 : if (comphelper::string::isalphaAscii(c))
253 0 : return true;
254 0 : return u_isalpha(c);
255 : }
256 : }
257 :
258 : class SyntaxHighlighter::Tokenizer
259 : {
260 : // Character information tables
261 : sal_uInt16 aCharTypeTab[256];
262 :
263 : // Auxiliary function: testing of the character flags
264 : bool testCharFlags(sal_Unicode c, sal_uInt16 nTestFlags) const;
265 :
266 : // Get new token, EmptyString == nothing more over there
267 : bool getNextToken(const sal_Unicode*& pos, /*out*/TokenTypes& reType,
268 : /*out*/const sal_Unicode*& rpStartPos, /*out*/const sal_Unicode*& rpEndPos) const;
269 :
270 : const char** ppListKeyWords;
271 : sal_uInt16 nKeyWordCount;
272 :
273 : public:
274 : HighlighterLanguage const aLanguage;
275 :
276 : explicit Tokenizer( HighlighterLanguage aLang );
277 : ~Tokenizer();
278 :
279 : void getHighlightPortions(const OUString& rLine,
280 : /*out*/std::vector<HighlightPortion>& portions) const;
281 : void setKeyWords( const char** ppKeyWords, sal_uInt16 nCount );
282 : };
283 :
284 : // Helper function: test character flag
285 245 : bool SyntaxHighlighter::Tokenizer::testCharFlags(sal_Unicode c, sal_uInt16 nTestFlags) const
286 : {
287 245 : bool bRet = false;
288 245 : if( c != 0 && c <= 255 )
289 : {
290 243 : bRet = ( (aCharTypeTab[c] & nTestFlags) != 0 );
291 : }
292 2 : else if( c > 255 )
293 : {
294 0 : bRet = (( CHAR_START_IDENTIFIER | CHAR_IN_IDENTIFIER ) & nTestFlags) != 0
295 0 : && isAlpha(c);
296 : }
297 245 : return bRet;
298 : }
299 :
300 6 : void SyntaxHighlighter::Tokenizer::setKeyWords( const char** ppKeyWords, sal_uInt16 nCount )
301 : {
302 6 : ppListKeyWords = ppKeyWords;
303 6 : nKeyWordCount = nCount;
304 6 : }
305 :
306 68 : bool SyntaxHighlighter::Tokenizer::getNextToken(const sal_Unicode*& pos, /*out*/TokenTypes& reType,
307 : /*out*/const sal_Unicode*& rpStartPos, /*out*/const sal_Unicode*& rpEndPos) const
308 : {
309 68 : reType = TT_UNKNOWN;
310 :
311 68 : rpStartPos = pos;
312 :
313 68 : sal_Unicode c = *pos;
314 68 : if( c == 0 )
315 6 : return false;
316 :
317 62 : ++pos;
318 :
319 : //*** Go through all possibilities ***
320 : // Space?
321 62 : if ( testCharFlags( c, CHAR_SPACE ) )
322 : {
323 41 : while( testCharFlags( *pos, CHAR_SPACE ) )
324 7 : ++pos;
325 :
326 17 : reType = TT_WHITESPACE;
327 : }
328 :
329 : // Identifier?
330 45 : else if ( testCharFlags( c, CHAR_START_IDENTIFIER ) )
331 : {
332 : bool bIdentifierChar;
333 48 : do
334 : {
335 : // Naechstes Zeichen holen
336 48 : c = *pos;
337 48 : bIdentifierChar = testCharFlags( c, CHAR_IN_IDENTIFIER );
338 48 : if( bIdentifierChar )
339 35 : ++pos;
340 : }
341 : while( bIdentifierChar );
342 :
343 13 : reType = TT_IDENTIFIER;
344 :
345 : // Keyword table
346 13 : if (ppListKeyWords != NULL)
347 : {
348 13 : int nCount = pos - rpStartPos;
349 :
350 : // No keyword if string contains char > 255
351 13 : bool bCanBeKeyword = true;
352 61 : for( int i = 0 ; i < nCount ; i++ )
353 : {
354 48 : if( rpStartPos[i] > 255 )
355 : {
356 0 : bCanBeKeyword = false;
357 0 : break;
358 : }
359 : }
360 :
361 13 : if( bCanBeKeyword )
362 : {
363 13 : OUString aKWString(rpStartPos, nCount);
364 : OString aByteStr = OUStringToOString(aKWString,
365 26 : RTL_TEXTENCODING_ASCII_US).toAsciiLowerCase();
366 26 : if ( bsearch( aByteStr.getStr(), ppListKeyWords, nKeyWordCount, sizeof( char* ),
367 26 : compare_strings ) )
368 : {
369 2 : reType = TT_KEYWORDS;
370 :
371 2 : if( aByteStr == "rem" )
372 : {
373 : // Remove all characters until end of line or EOF
374 0 : sal_Unicode cPeek = *pos;
375 0 : while( cPeek != 0 && !testCharFlags( cPeek, CHAR_EOL ) )
376 : {
377 0 : c = *pos++;
378 0 : cPeek = *pos;
379 : }
380 :
381 0 : reType = TT_COMMENT;
382 : }
383 13 : }
384 : }
385 : }
386 : }
387 :
388 : // Operator?
389 : // only for BASIC '\'' should be a comment, otherwise it is a normal string and handled there
390 32 : else if ( testCharFlags( c, CHAR_OPERATOR ) || ( (c == '\'') && (aLanguage==HIGHLIGHT_BASIC)) )
391 : {
392 : // parameters for SQL view
393 24 : if ( (c==':') || (c=='?'))
394 : {
395 0 : if (c!='?')
396 : {
397 : bool bIdentifierChar;
398 0 : do
399 : {
400 : // Get next character
401 0 : c = *pos;
402 0 : bIdentifierChar = isAlpha(c);
403 0 : if( bIdentifierChar )
404 0 : ++pos;
405 : }
406 : while( bIdentifierChar );
407 : }
408 0 : reType = TT_PARAMETER;
409 : }
410 24 : else if (c=='-')
411 : {
412 0 : sal_Unicode cPeekNext = *pos;
413 0 : if (cPeekNext=='-')
414 : {
415 : // Remove all characters until end of line or EOF
416 0 : while( cPeekNext != 0 && !testCharFlags( cPeekNext, CHAR_EOL ) )
417 : {
418 0 : ++pos;
419 0 : cPeekNext = *pos;
420 : }
421 0 : reType = TT_COMMENT;
422 : }
423 : }
424 24 : else if (c=='/')
425 : {
426 0 : sal_Unicode cPeekNext = *pos;
427 0 : if (cPeekNext=='/')
428 : {
429 : // Remove all characters until end of line or EOF
430 0 : while( cPeekNext != 0 && !testCharFlags( cPeekNext, CHAR_EOL ) )
431 : {
432 0 : ++pos;
433 0 : cPeekNext = *pos;
434 : }
435 0 : reType = TT_COMMENT;
436 : }
437 : }
438 : else
439 : {
440 : // Comment?
441 32 : if ( c == '\'' )
442 : {
443 : // Skip all characters until end of input or end of line:
444 : for (;;) {
445 13 : c = *pos;
446 13 : if (c == 0 || testCharFlags(c, CHAR_EOL)) {
447 5 : break;
448 : }
449 8 : ++pos;
450 : }
451 :
452 5 : reType = TT_COMMENT;
453 : }
454 :
455 : // The real operator; can be easily used since not the actual
456 : // operator (e.g. +=) is concerned, but the fact that it is one
457 24 : if( reType != TT_COMMENT )
458 : {
459 19 : reType = TT_OPERATOR;
460 : }
461 :
462 : }
463 : }
464 :
465 : // Object separator? Must be handled before Number
466 8 : else if( c == '.' && ( *pos < '0' || *pos > '9' ) )
467 : {
468 0 : reType = TT_OPERATOR;
469 : }
470 :
471 : // Number?
472 8 : else if( testCharFlags( c, CHAR_START_NUMBER ) )
473 : {
474 4 : reType = TT_NUMBER;
475 :
476 : // Number system, 10 = normal, it is changed for Oct/Hex
477 4 : int nRadix = 10;
478 :
479 : // Is it an Oct or a Hex number?
480 4 : if( c == '&' )
481 : {
482 : // Octal?
483 0 : if( *pos == 'o' || *pos == 'O' )
484 : {
485 : // remove o
486 0 : ++pos;
487 0 : nRadix = 8; // Octal base
488 :
489 : // Read all numbers
490 0 : while( testCharFlags( *pos, CHAR_IN_OCT_NUMBER ) )
491 0 : ++pos;
492 : }
493 : // Hexadecimal?
494 0 : else if( *pos == 'h' || *pos == 'H' )
495 : {
496 : // remove x
497 0 : ++pos;
498 0 : nRadix = 16; // Hexadecimal base
499 :
500 : // Read all numbers
501 0 : while( testCharFlags( *pos, CHAR_IN_HEX_NUMBER ) )
502 0 : ++pos;
503 : }
504 : else
505 : {
506 0 : reType = TT_OPERATOR;
507 : }
508 : }
509 :
510 : // When it is not Oct or Hex, then it is double
511 4 : if( reType == TT_NUMBER && nRadix == 10 )
512 : {
513 : // Flag if the last character is an exponent
514 4 : bool bAfterExpChar = false;
515 :
516 : // Read all numbers
517 16 : while( testCharFlags( *pos, CHAR_IN_NUMBER ) ||
518 4 : (bAfterExpChar && *pos == '+' ) ||
519 0 : (bAfterExpChar && *pos == '-' ) )
520 : // After exponent +/- are OK, too
521 : {
522 0 : c = *pos++;
523 0 : bAfterExpChar = ( c == 'e' || c == 'E' );
524 : }
525 : }
526 : }
527 :
528 : // String?
529 4 : else if( testCharFlags( c, CHAR_START_STRING ) )
530 : {
531 : // Remember which character has opened the string
532 2 : sal_Unicode cEndString = c;
533 2 : if( c == '[' )
534 0 : cEndString = ']';
535 :
536 : // Read all characters
537 8 : while( *pos != cEndString )
538 : {
539 : // Detect EOF before reading next char, so we do not lose EOF
540 4 : if( *pos == 0 )
541 : {
542 : // ERROR: unterminated string literal
543 0 : reType = TT_ERROR;
544 0 : break;
545 : }
546 4 : c = *pos++;
547 4 : if( testCharFlags( c, CHAR_EOL ) )
548 : {
549 : // ERROR: unterminated string literal
550 0 : reType = TT_ERROR;
551 0 : break;
552 : }
553 : }
554 :
555 2 : if( reType != TT_ERROR )
556 : {
557 2 : ++pos;
558 2 : if( cEndString == ']' )
559 0 : reType = TT_IDENTIFIER;
560 : else
561 2 : reType = TT_STRING;
562 : }
563 : }
564 :
565 : // End of line?
566 2 : else if( testCharFlags( c, CHAR_EOL ) )
567 : {
568 : // If another EOL character comes, read it
569 2 : sal_Unicode cNext = *pos;
570 2 : if( cNext != c && testCharFlags( cNext, CHAR_EOL ) )
571 0 : ++pos;
572 :
573 2 : reType = TT_EOL;
574 : }
575 :
576 : // All other will remain TT_UNKNOWN
577 :
578 : // Save end position
579 62 : rpEndPos = pos;
580 62 : return true;
581 : }
582 :
583 6 : SyntaxHighlighter::Tokenizer::Tokenizer( HighlighterLanguage aLang ): aLanguage(aLang)
584 : {
585 6 : memset( aCharTypeTab, 0, sizeof( aCharTypeTab ) );
586 :
587 : // Fill character table
588 : sal_uInt16 i;
589 :
590 : // Allowed characters for identifiers
591 6 : sal_uInt16 nHelpMask = (sal_uInt16)( CHAR_START_IDENTIFIER | CHAR_IN_IDENTIFIER );
592 162 : for( i = 'a' ; i <= 'z' ; i++ )
593 156 : aCharTypeTab[i] |= nHelpMask;
594 162 : for( i = 'A' ; i <= 'Z' ; i++ )
595 156 : aCharTypeTab[i] |= nHelpMask;
596 6 : aCharTypeTab[(int)'_'] |= nHelpMask;
597 6 : aCharTypeTab[(int)'$'] |= nHelpMask;
598 :
599 : // Digit (can be identifier and number)
600 : nHelpMask = (sal_uInt16)( CHAR_IN_IDENTIFIER | CHAR_START_NUMBER |
601 6 : CHAR_IN_NUMBER | CHAR_IN_HEX_NUMBER );
602 66 : for( i = '0' ; i <= '9' ; i++ )
603 60 : aCharTypeTab[i] |= nHelpMask;
604 :
605 : // Add e, E, . and & here manually
606 6 : aCharTypeTab[(int)'e'] |= CHAR_IN_NUMBER;
607 6 : aCharTypeTab[(int)'E'] |= CHAR_IN_NUMBER;
608 6 : aCharTypeTab[(int)'.'] |= (sal_uInt16)( CHAR_IN_NUMBER | CHAR_START_NUMBER );
609 6 : aCharTypeTab[(int)'&'] |= CHAR_START_NUMBER;
610 :
611 : // Hexadecimal digit
612 42 : for( i = 'a' ; i <= 'f' ; i++ )
613 36 : aCharTypeTab[i] |= CHAR_IN_HEX_NUMBER;
614 42 : for( i = 'A' ; i <= 'F' ; i++ )
615 36 : aCharTypeTab[i] |= CHAR_IN_HEX_NUMBER;
616 :
617 : // Octal digit
618 54 : for( i = '0' ; i <= '7' ; i++ )
619 48 : aCharTypeTab[i] |= CHAR_IN_OCT_NUMBER;
620 :
621 : // String literal start/end characters
622 6 : aCharTypeTab[(int)'\''] |= CHAR_START_STRING;
623 6 : aCharTypeTab[(int)'\"'] |= CHAR_START_STRING;
624 6 : aCharTypeTab[(int)'['] |= CHAR_START_STRING;
625 6 : aCharTypeTab[(int)'`'] |= CHAR_START_STRING;
626 :
627 : // Operator characters
628 6 : aCharTypeTab[(int)'!'] |= CHAR_OPERATOR;
629 6 : aCharTypeTab[(int)'%'] |= CHAR_OPERATOR;
630 : // aCharTypeTab[(int)'&'] |= CHAR_OPERATOR; Removed because of #i14140
631 6 : aCharTypeTab[(int)'('] |= CHAR_OPERATOR;
632 6 : aCharTypeTab[(int)')'] |= CHAR_OPERATOR;
633 6 : aCharTypeTab[(int)'*'] |= CHAR_OPERATOR;
634 6 : aCharTypeTab[(int)'+'] |= CHAR_OPERATOR;
635 6 : aCharTypeTab[(int)','] |= CHAR_OPERATOR;
636 6 : aCharTypeTab[(int)'-'] |= CHAR_OPERATOR;
637 6 : aCharTypeTab[(int)'/'] |= CHAR_OPERATOR;
638 6 : aCharTypeTab[(int)':'] |= CHAR_OPERATOR;
639 6 : aCharTypeTab[(int)'<'] |= CHAR_OPERATOR;
640 6 : aCharTypeTab[(int)'='] |= CHAR_OPERATOR;
641 6 : aCharTypeTab[(int)'>'] |= CHAR_OPERATOR;
642 6 : aCharTypeTab[(int)'?'] |= CHAR_OPERATOR;
643 6 : aCharTypeTab[(int)'^'] |= CHAR_OPERATOR;
644 6 : aCharTypeTab[(int)'|'] |= CHAR_OPERATOR;
645 6 : aCharTypeTab[(int)'~'] |= CHAR_OPERATOR;
646 6 : aCharTypeTab[(int)'{'] |= CHAR_OPERATOR;
647 6 : aCharTypeTab[(int)'}'] |= CHAR_OPERATOR;
648 : // aCharTypeTab[(int)'['] |= CHAR_OPERATOR; Removed because of #i17826
649 6 : aCharTypeTab[(int)']'] |= CHAR_OPERATOR;
650 6 : aCharTypeTab[(int)';'] |= CHAR_OPERATOR;
651 :
652 : // Space
653 6 : aCharTypeTab[(int)' ' ] |= CHAR_SPACE;
654 6 : aCharTypeTab[(int)'\t'] |= CHAR_SPACE;
655 :
656 : // End of line characters
657 6 : aCharTypeTab[(int)'\r'] |= CHAR_EOL;
658 6 : aCharTypeTab[(int)'\n'] |= CHAR_EOL;
659 :
660 6 : ppListKeyWords = NULL;
661 6 : nKeyWordCount = 0;
662 6 : }
663 :
664 6 : SyntaxHighlighter::Tokenizer::~Tokenizer()
665 : {
666 6 : }
667 :
668 6 : void SyntaxHighlighter::Tokenizer::getHighlightPortions(const OUString& rLine,
669 : /*out*/std::vector<HighlightPortion>& portions) const
670 : {
671 : // Set the position to the beginning of the source string
672 6 : const sal_Unicode* pos = rLine.getStr();
673 :
674 : // Variables for the out parameter
675 : TokenTypes eType;
676 : const sal_Unicode* pStartPos;
677 : const sal_Unicode* pEndPos;
678 :
679 : // Loop over all the tokens
680 74 : while( getNextToken( pos, eType, pStartPos, pEndPos ) )
681 : {
682 : portions.push_back(
683 : HighlightPortion(
684 62 : pStartPos - rLine.getStr(), pEndPos - rLine.getStr(), eType));
685 : }
686 6 : }
687 :
688 :
689 6 : SyntaxHighlighter::SyntaxHighlighter(HighlighterLanguage language):
690 6 : eLanguage(language), m_tokenizer(new SyntaxHighlighter::Tokenizer(language))
691 : {
692 6 : switch (eLanguage)
693 : {
694 : case HIGHLIGHT_BASIC:
695 : m_tokenizer->setKeyWords( strListBasicKeyWords,
696 6 : sizeof( strListBasicKeyWords ) / sizeof( char* ));
697 6 : break;
698 : case HIGHLIGHT_SQL:
699 : m_tokenizer->setKeyWords( strListSqlKeyWords,
700 0 : sizeof( strListSqlKeyWords ) / sizeof( char* ));
701 0 : break;
702 : default:
703 : assert(false); // this cannot happen
704 : }
705 6 : }
706 :
707 6 : SyntaxHighlighter::~SyntaxHighlighter() {}
708 :
709 6 : void SyntaxHighlighter::getHighlightPortions(const OUString& rLine,
710 : /*out*/std::vector<HighlightPortion>& portions) const
711 : {
712 6 : m_tokenizer->getHighlightPortions( rLine, portions );
713 6 : }
714 :
715 0 : HighlighterLanguage SyntaxHighlighter::GetLanguage()
716 : {
717 0 : return m_tokenizer->aLanguage;
718 : }
719 :
720 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|