r5079
[geany-mirror.git] / scintilla / LexPascal.cxx
blob3dcf35ad531eaaa9d946adf011602ff4c5cef322
1 // Scintilla source code edit control
2 /** @file LexPascal.cxx
3 ** Lexer for Pascal.
4 ** Written by Laurent le Tynevez
5 ** Updated by Simon Steele <s.steele@pnotepad.org> September 2002
6 ** Updated by Mathias Rauen <scite@madshi.net> May 2003 (Delphi adjustments)
7 ** Completely rewritten by Marko Njezic <sf@maxempire.com> October 2008
8 **/
12 A few words about features of the new completely rewritten LexPascal...
14 Generally speaking LexPascal tries to support all available Delphi features (up
15 to Delphi 2009 at this time), including .NET specific features.
17 ~ HIGHLIGHTING:
19 If you enable "lexer.pascal.smart.highlighting" property, some keywords will
20 only be highlighted in appropriate context. As implemented those are keywords
21 related to property and DLL exports declarations (similar to how Delphi IDE
22 works).
24 For example, keywords "read" and "write" will only be highlighted if they are in
25 property declaration:
27 property MyProperty: boolean read FMyProperty write FMyProperty;
29 ~ FOLDING:
31 Folding is supported in the following cases:
33 - Folding of stream-like comments
34 - Folding of groups of consecutive line comments
35 - Folding of preprocessor blocks (the following preprocessor blocks are
36 supported: IF / IFEND; IFDEF, IFNDEF, IFOPT / ENDIF and REGION / ENDREGION
37 blocks), including nesting of preprocessor blocks up to 255 levels
38 - Folding of code blocks on appropriate keywords (the following code blocks are
39 supported: "begin, asm, record, try, case / end" blocks, class & object
40 declarations and interface declarations)
42 Remarks:
44 - Folding of code blocks tries to handle all special cases in which folding
45 should not occur. As implemented those are:
47 1. Structure "record case / end" (there's only one "end" statement and "case" is
48 ignored as fold point)
49 2. Forward class declarations ("type TMyClass = class;") and object method
50 declarations ("TNotifyEvent = procedure(Sender: TObject) of object;") are
51 ignored as fold points
52 3. Simplified complete class declarations ("type TMyClass = class(TObject);")
53 are ignored as fold points
54 4. Every other situation when class keyword doesn't actually start class
55 declaration ("class procedure", "class function", "class of", "class var",
56 "class property" and "class operator")
58 - Folding of code blocks inside preprocessor blocks is disabled (any comments
59 inside them will be folded fine) because there is no guarantee that complete
60 code block will be contained inside folded preprocessor block in which case
61 folded code block could end prematurely at the end of preprocessor block if
62 there is no closing statement inside. This was done in order to properly process
63 document that may contain something like this:
65 type
66 {$IFDEF UNICODE}
67 TMyClass = class(UnicodeAncestor)
68 {$ELSE}
69 TMyClass = class(AnsiAncestor)
70 {$ENDIF}
71 private
72 ...
73 public
74 ...
75 published
76 ...
77 end;
79 If class declarations were folded, then the second class declaration would end
80 at "$ENDIF" statement, first class statement would end at "end;" statement and
81 preprocessor "$IFDEF" block would go all the way to the end of document.
82 However, having in mind all this, if you want to enable folding of code blocks
83 inside preprocessor blocks, you can disable folding of preprocessor blocks by
84 changing "fold.preprocessor" property, in which case everything inside them
85 would be folded.
87 ~ KEYWORDS:
89 The list of keywords that can be used in pascal.properties file (up to Delphi
90 2009):
92 - Keywords: absolute abstract and array as asm assembler automated begin case
93 cdecl class const constructor deprecated destructor dispid dispinterface div do
94 downto dynamic else end except export exports external far file final
95 finalization finally for forward function goto if implementation in inherited
96 initialization inline interface is label library message mod near nil not object
97 of on or out overload override packed pascal platform private procedure program
98 property protected public published raise record register reintroduce repeat
99 resourcestring safecall sealed set shl shr static stdcall strict string then
100 threadvar to try type unit unsafe until uses var varargs virtual while with xor
102 - Keywords related to the "smart highlithing" feature: add default implements
103 index name nodefault read readonly remove stored write writeonly
105 - Keywords related to Delphi packages (in addition to all above): package
106 contains requires
110 #include <stdlib.h>
111 #include <string.h>
112 #include <ctype.h>
113 #include <stdio.h>
114 #include <stdarg.h>
116 #include "Platform.h"
118 #include "PropSet.h"
119 #include "Accessor.h"
120 #include "KeyWords.h"
121 #include "Scintilla.h"
122 #include "SciLexer.h"
123 #include "StyleContext.h"
124 #include "CharacterSet.h"
126 #ifdef SCI_NAMESPACE
127 using namespace Scintilla;
128 #endif
130 static void GetRangeLowered(unsigned int start,
131 unsigned int end,
132 Accessor &styler,
133 char *s,
134 unsigned int len) {
135 unsigned int i = 0;
136 while ((i < end - start + 1) && (i < len-1)) {
137 s[i] = static_cast<char>(tolower(styler[start + i]));
138 i++;
140 s[i] = '\0';
143 static void GetForwardRangeLowered(unsigned int start,
144 CharacterSet &charSet,
145 Accessor &styler,
146 char *s,
147 unsigned int len) {
148 unsigned int i = 0;
149 while ((i < len-1) && charSet.Contains(styler.SafeGetCharAt(start + i))) {
150 s[i] = static_cast<char>(tolower(styler.SafeGetCharAt(start + i)));
151 i++;
153 s[i] = '\0';
157 enum {
158 stateInAsm = 0x1000,
159 stateInProperty = 0x2000,
160 stateInExport = 0x4000,
161 stateFoldInPreprocessor = 0x0100,
162 stateFoldInRecord = 0x0200,
163 stateFoldInPreprocessorLevelMask = 0x00FF,
164 stateFoldMaskAll = 0x0FFF
167 static void ClassifyPascalWord(WordList *keywordlists[], StyleContext &sc, int &curLineState, bool bSmartHighlighting) {
168 WordList& keywords = *keywordlists[0];
170 char s[100];
171 sc.GetCurrentLowered(s, sizeof(s));
172 if (keywords.InList(s)) {
173 if (curLineState & stateInAsm) {
174 if (strcmp(s, "end") == 0 && sc.GetRelative(-4) != '@') {
175 curLineState &= ~stateInAsm;
176 sc.ChangeState(SCE_PAS_WORD);
177 } else {
178 sc.ChangeState(SCE_PAS_ASM);
180 } else {
181 bool ignoreKeyword = false;
182 if (strcmp(s, "asm") == 0) {
183 curLineState |= stateInAsm;
184 } else if (bSmartHighlighting) {
185 if (strcmp(s, "property") == 0) {
186 curLineState |= stateInProperty;
187 } else if (strcmp(s, "exports") == 0) {
188 curLineState |= stateInExport;
189 } else if (!(curLineState & (stateInProperty | stateInExport)) && strcmp(s, "index") == 0) {
190 ignoreKeyword = true;
191 } else if (!(curLineState & stateInExport) && strcmp(s, "name") == 0) {
192 ignoreKeyword = true;
193 } else if (!(curLineState & stateInProperty) &&
194 (strcmp(s, "read") == 0 || strcmp(s, "write") == 0 ||
195 strcmp(s, "default") == 0 || strcmp(s, "nodefault") == 0 ||
196 strcmp(s, "stored") == 0 || strcmp(s, "implements") == 0 ||
197 strcmp(s, "readonly") == 0 || strcmp(s, "writeonly") == 0 ||
198 strcmp(s, "add") == 0 || strcmp(s, "remove") == 0)) {
199 ignoreKeyword = true;
202 if (!ignoreKeyword) {
203 sc.ChangeState(SCE_PAS_WORD);
206 } else if (curLineState & stateInAsm) {
207 sc.ChangeState(SCE_PAS_ASM);
209 sc.SetState(SCE_PAS_DEFAULT);
212 static void ColourisePascalDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[],
213 Accessor &styler) {
214 bool bSmartHighlighting = styler.GetPropertyInt("lexer.pascal.smart.highlighting", 1) != 0;
216 CharacterSet setWordStart(CharacterSet::setAlpha, "_", 0x80, true);
217 CharacterSet setWord(CharacterSet::setAlphaNum, "_", 0x80, true);
218 CharacterSet setNumber(CharacterSet::setDigits, ".-+eE");
219 CharacterSet setHexNumber(CharacterSet::setDigits, "abcdefABCDEF");
220 CharacterSet setOperator(CharacterSet::setNone, "#$&'()*+,-./:;<=>@[]^{}");
222 int curLine = styler.GetLine(startPos);
223 int curLineState = curLine > 0 ? styler.GetLineState(curLine - 1) : 0;
225 StyleContext sc(startPos, length, initStyle, styler);
227 for (; sc.More(); sc.Forward()) {
228 if (sc.atLineEnd) {
229 // Update the line state, so it can be seen by next line
230 curLine = styler.GetLine(sc.currentPos);
231 styler.SetLineState(curLine, curLineState);
234 // Determine if the current state should terminate.
235 switch (sc.state) {
236 case SCE_PAS_NUMBER:
237 if (!setNumber.Contains(sc.ch) || (sc.ch == '.' && sc.chNext == '.')) {
238 sc.SetState(SCE_PAS_DEFAULT);
239 } else if (sc.ch == '-' || sc.ch == '+') {
240 if (sc.chPrev != 'E' && sc.chPrev != 'e') {
241 sc.SetState(SCE_PAS_DEFAULT);
244 break;
245 case SCE_PAS_IDENTIFIER:
246 if (!setWord.Contains(sc.ch)) {
247 ClassifyPascalWord(keywordlists, sc, curLineState, bSmartHighlighting);
249 break;
250 case SCE_PAS_HEXNUMBER:
251 if (!setHexNumber.Contains(sc.ch)) {
252 sc.SetState(SCE_PAS_DEFAULT);
254 break;
255 case SCE_PAS_COMMENT:
256 case SCE_PAS_PREPROCESSOR:
257 if (sc.ch == '}') {
258 sc.ForwardSetState(SCE_PAS_DEFAULT);
260 break;
261 case SCE_PAS_COMMENT2:
262 case SCE_PAS_PREPROCESSOR2:
263 if (sc.Match('*', ')')) {
264 sc.Forward();
265 sc.ForwardSetState(SCE_PAS_DEFAULT);
267 break;
268 case SCE_PAS_COMMENTLINE:
269 if (sc.atLineStart) {
270 sc.SetState(SCE_PAS_DEFAULT);
272 break;
273 case SCE_PAS_STRING:
274 if (sc.atLineEnd) {
275 sc.ChangeState(SCE_PAS_STRINGEOL);
276 } else if (sc.ch == '\'' && sc.chNext == '\'') {
277 sc.Forward();
278 } else if (sc.ch == '\'') {
279 sc.ForwardSetState(SCE_PAS_DEFAULT);
281 break;
282 case SCE_PAS_STRINGEOL:
283 if (sc.atLineStart) {
284 sc.SetState(SCE_PAS_DEFAULT);
286 break;
287 case SCE_PAS_CHARACTER:
288 if (!setHexNumber.Contains(sc.ch) && sc.ch != '$') {
289 sc.SetState(SCE_PAS_DEFAULT);
291 break;
292 case SCE_PAS_OPERATOR:
293 if (bSmartHighlighting && sc.chPrev == ';') {
294 curLineState &= ~(stateInProperty | stateInExport);
296 sc.SetState(SCE_PAS_DEFAULT);
297 break;
298 case SCE_PAS_ASM:
299 sc.SetState(SCE_PAS_DEFAULT);
300 break;
303 // Determine if a new state should be entered.
304 if (sc.state == SCE_PAS_DEFAULT) {
305 if (IsADigit(sc.ch) && !(curLineState & stateInAsm)) {
306 sc.SetState(SCE_PAS_NUMBER);
307 } else if (setWordStart.Contains(sc.ch)) {
308 sc.SetState(SCE_PAS_IDENTIFIER);
309 } else if (sc.ch == '$' && !(curLineState & stateInAsm)) {
310 sc.SetState(SCE_PAS_HEXNUMBER);
311 } else if (sc.Match('{', '$')) {
312 sc.SetState(SCE_PAS_PREPROCESSOR);
313 } else if (sc.ch == '{') {
314 sc.SetState(SCE_PAS_COMMENT);
315 } else if (sc.Match("(*$")) {
316 sc.SetState(SCE_PAS_PREPROCESSOR2);
317 } else if (sc.Match('(', '*')) {
318 sc.SetState(SCE_PAS_COMMENT2);
319 sc.Forward(); // Eat the * so it isn't used for the end of the comment
320 } else if (sc.Match('/', '/')) {
321 sc.SetState(SCE_PAS_COMMENTLINE);
322 } else if (sc.ch == '\'') {
323 sc.SetState(SCE_PAS_STRING);
324 } else if (sc.ch == '#') {
325 sc.SetState(SCE_PAS_CHARACTER);
326 } else if (setOperator.Contains(sc.ch) && !(curLineState & stateInAsm)) {
327 sc.SetState(SCE_PAS_OPERATOR);
328 } else if (curLineState & stateInAsm) {
329 sc.SetState(SCE_PAS_ASM);
334 if (sc.state == SCE_PAS_IDENTIFIER && setWord.Contains(sc.chPrev)) {
335 ClassifyPascalWord(keywordlists, sc, curLineState, bSmartHighlighting);
338 sc.Complete();
341 static bool IsStreamCommentStyle(int style) {
342 return style == SCE_PAS_COMMENT || style == SCE_PAS_COMMENT2;
345 static bool IsCommentLine(int line, Accessor &styler) {
346 int pos = styler.LineStart(line);
347 int eolPos = styler.LineStart(line + 1) - 1;
348 for (int i = pos; i < eolPos; i++) {
349 char ch = styler[i];
350 char chNext = styler.SafeGetCharAt(i + 1);
351 int style = styler.StyleAt(i);
352 if (ch == '/' && chNext == '/' && style == SCE_PAS_COMMENTLINE) {
353 return true;
354 } else if (!IsASpaceOrTab(ch)) {
355 return false;
358 return false;
361 static unsigned int GetFoldInPreprocessorLevelFlag(int lineFoldStateCurrent) {
362 return lineFoldStateCurrent & stateFoldInPreprocessorLevelMask;
365 static void SetFoldInPreprocessorLevelFlag(int &lineFoldStateCurrent, unsigned int nestLevel) {
366 lineFoldStateCurrent &= ~stateFoldInPreprocessorLevelMask;
367 lineFoldStateCurrent |= nestLevel & stateFoldInPreprocessorLevelMask;
370 static void ClassifyPascalPreprocessorFoldPoint(int &levelCurrent, int &lineFoldStateCurrent,
371 unsigned int startPos, Accessor &styler) {
372 CharacterSet setWord(CharacterSet::setAlpha);
374 char s[11]; // Size of the longest possible keyword + one additional character + null
375 GetForwardRangeLowered(startPos, setWord, styler, s, sizeof(s));
377 unsigned int nestLevel = GetFoldInPreprocessorLevelFlag(lineFoldStateCurrent);
379 if (strcmp(s, "if") == 0 ||
380 strcmp(s, "ifdef") == 0 ||
381 strcmp(s, "ifndef") == 0 ||
382 strcmp(s, "ifopt") == 0 ||
383 strcmp(s, "region") == 0) {
384 nestLevel++;
385 SetFoldInPreprocessorLevelFlag(lineFoldStateCurrent, nestLevel);
386 lineFoldStateCurrent |= stateFoldInPreprocessor;
387 levelCurrent++;
388 } else if (strcmp(s, "endif") == 0 ||
389 strcmp(s, "ifend") == 0 ||
390 strcmp(s, "endregion") == 0) {
391 nestLevel--;
392 SetFoldInPreprocessorLevelFlag(lineFoldStateCurrent, nestLevel);
393 if (nestLevel == 0) {
394 lineFoldStateCurrent &= ~stateFoldInPreprocessor;
396 levelCurrent--;
397 if (levelCurrent < SC_FOLDLEVELBASE) {
398 levelCurrent = SC_FOLDLEVELBASE;
403 static unsigned int SkipWhiteSpace(unsigned int currentPos, unsigned int endPos,
404 Accessor &styler, bool includeChars = false) {
405 CharacterSet setWord(CharacterSet::setAlphaNum, "_");
406 unsigned int j = currentPos + 1;
407 char ch = styler.SafeGetCharAt(j);
408 while ((j < endPos) && (IsASpaceOrTab(ch) || ch == '\r' || ch == '\n' ||
409 IsStreamCommentStyle(styler.StyleAt(j)) || (includeChars && setWord.Contains(ch)))) {
410 j++;
411 ch = styler.SafeGetCharAt(j);
413 return j;
416 static void ClassifyPascalWordFoldPoint(int &levelCurrent, int &lineFoldStateCurrent,
417 int startPos, unsigned int endPos,
418 unsigned int lastStart, unsigned int currentPos, Accessor &styler) {
419 char s[100];
420 GetRangeLowered(lastStart, currentPos, styler, s, sizeof(s));
422 if (strcmp(s, "record") == 0) {
423 lineFoldStateCurrent |= stateFoldInRecord;
424 levelCurrent++;
425 } else if (strcmp(s, "begin") == 0 ||
426 strcmp(s, "asm") == 0 ||
427 strcmp(s, "try") == 0 ||
428 (strcmp(s, "case") == 0 && !(lineFoldStateCurrent & stateFoldInRecord))) {
429 levelCurrent++;
430 } else if (strcmp(s, "class") == 0 || strcmp(s, "object") == 0) {
431 // "class" & "object" keywords require special handling...
432 bool ignoreKeyword = false;
433 unsigned int j = SkipWhiteSpace(currentPos, endPos, styler);
434 if (j < endPos) {
435 CharacterSet setWordStart(CharacterSet::setAlpha, "_");
436 CharacterSet setWord(CharacterSet::setAlphaNum, "_");
438 if (styler.SafeGetCharAt(j) == ';') {
439 // Handle forward class declarations ("type TMyClass = class;")
440 // and object method declarations ("TNotifyEvent = procedure(Sender: TObject) of object;")
441 ignoreKeyword = true;
442 } else if (strcmp(s, "class") == 0) {
443 // "class" keyword has a few more special cases...
444 if (styler.SafeGetCharAt(j) == '(') {
445 // Handle simplified complete class declarations ("type TMyClass = class(TObject);")
446 j = SkipWhiteSpace(j, endPos, styler, true);
447 if (j < endPos && styler.SafeGetCharAt(j) == ')') {
448 j = SkipWhiteSpace(j, endPos, styler);
449 if (j < endPos && styler.SafeGetCharAt(j) == ';') {
450 ignoreKeyword = true;
453 } else if (setWordStart.Contains(styler.SafeGetCharAt(j))) {
454 char s2[11]; // Size of the longest possible keyword + one additional character + null
455 GetForwardRangeLowered(j, setWord, styler, s2, sizeof(s2));
457 if (strcmp(s2, "procedure") == 0 ||
458 strcmp(s2, "function") == 0 ||
459 strcmp(s2, "of") == 0 ||
460 strcmp(s2, "var") == 0 ||
461 strcmp(s2, "property") == 0 ||
462 strcmp(s2, "operator") == 0) {
463 ignoreKeyword = true;
468 if (!ignoreKeyword) {
469 levelCurrent++;
471 } else if (strcmp(s, "interface") == 0) {
472 // "interface" keyword requires special handling...
473 bool ignoreKeyword = true;
474 int j = lastStart - 1;
475 char ch = styler.SafeGetCharAt(j);
476 while ((j >= startPos) && (IsASpaceOrTab(ch) || ch == '\r' || ch == '\n' ||
477 IsStreamCommentStyle(styler.StyleAt(j)))) {
478 j--;
479 ch = styler.SafeGetCharAt(j);
481 if (j >= startPos && styler.SafeGetCharAt(j) == '=') {
482 ignoreKeyword = false;
484 if (!ignoreKeyword) {
485 levelCurrent++;
487 } else if (strcmp(s, "end") == 0) {
488 lineFoldStateCurrent &= ~stateFoldInRecord;
489 levelCurrent--;
490 if (levelCurrent < SC_FOLDLEVELBASE) {
491 levelCurrent = SC_FOLDLEVELBASE;
496 static void FoldPascalDoc(unsigned int startPos, int length, int initStyle, WordList *[],
497 Accessor &styler) {
498 bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
499 bool foldPreprocessor = styler.GetPropertyInt("fold.preprocessor") != 0;
500 bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
501 unsigned int endPos = startPos + length;
502 int visibleChars = 0;
503 int lineCurrent = styler.GetLine(startPos);
504 int levelPrev = styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK;
505 int levelCurrent = levelPrev;
506 int lineFoldStateCurrent = lineCurrent > 0 ? styler.GetLineState(lineCurrent - 1) & stateFoldMaskAll : 0;
507 char chNext = styler[startPos];
508 int styleNext = styler.StyleAt(startPos);
509 int style = initStyle;
511 int lastStart = 0;
512 CharacterSet setWord(CharacterSet::setAlphaNum, "_", 0x80, true);
514 for (unsigned int i = startPos; i < endPos; i++) {
515 char ch = chNext;
516 chNext = styler.SafeGetCharAt(i + 1);
517 int stylePrev = style;
518 style = styleNext;
519 styleNext = styler.StyleAt(i + 1);
520 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
522 if (foldComment && IsStreamCommentStyle(style)) {
523 if (!IsStreamCommentStyle(stylePrev)) {
524 levelCurrent++;
525 } else if (!IsStreamCommentStyle(styleNext) && !atEOL) {
526 // Comments don't end at end of line and the next character may be unstyled.
527 levelCurrent--;
530 if (foldComment && atEOL && IsCommentLine(lineCurrent, styler))
532 if (!IsCommentLine(lineCurrent - 1, styler)
533 && IsCommentLine(lineCurrent + 1, styler))
534 levelCurrent++;
535 else if (IsCommentLine(lineCurrent - 1, styler)
536 && !IsCommentLine(lineCurrent+1, styler))
537 levelCurrent--;
539 if (foldPreprocessor) {
540 if (style == SCE_PAS_PREPROCESSOR && ch == '{' && chNext == '$') {
541 ClassifyPascalPreprocessorFoldPoint(levelCurrent, lineFoldStateCurrent, i + 2, styler);
542 } else if (style == SCE_PAS_PREPROCESSOR2 && ch == '(' && chNext == '*'
543 && styler.SafeGetCharAt(i + 2) == '$') {
544 ClassifyPascalPreprocessorFoldPoint(levelCurrent, lineFoldStateCurrent, i + 3, styler);
548 if (stylePrev != SCE_PAS_WORD && style == SCE_PAS_WORD)
550 // Store last word start point.
551 lastStart = i;
553 if (stylePrev == SCE_PAS_WORD && !(lineFoldStateCurrent & stateFoldInPreprocessor)) {
554 if(setWord.Contains(ch) && !setWord.Contains(chNext)) {
555 ClassifyPascalWordFoldPoint(levelCurrent, lineFoldStateCurrent, startPos, endPos, lastStart, i, styler);
559 if (!IsASpace(ch))
560 visibleChars++;
562 if (atEOL) {
563 int lev = levelPrev;
564 if (visibleChars == 0 && foldCompact)
565 lev |= SC_FOLDLEVELWHITEFLAG;
566 if ((levelCurrent > levelPrev) && (visibleChars > 0))
567 lev |= SC_FOLDLEVELHEADERFLAG;
568 if (lev != styler.LevelAt(lineCurrent)) {
569 styler.SetLevel(lineCurrent, lev);
571 int newLineState = (styler.GetLineState(lineCurrent) & ~stateFoldMaskAll) | lineFoldStateCurrent;
572 styler.SetLineState(lineCurrent, newLineState);
573 lineCurrent++;
574 levelPrev = levelCurrent;
575 visibleChars = 0;
579 // If we didn't reach the EOL in previous loop, store line level and whitespace information.
580 // The rest will be filled in later...
581 int lev = levelPrev;
582 if (visibleChars == 0 && foldCompact)
583 lev |= SC_FOLDLEVELWHITEFLAG;
584 styler.SetLevel(lineCurrent, lev);
587 static const char * const pascalWordListDesc[] = {
588 "Keywords",
592 LexerModule lmPascal(SCLEX_PASCAL, ColourisePascalDoc, "pascal", FoldPascalDoc, pascalWordListDesc);