1 // Scintilla source code edit control
2 /** @file LexPascal.cxx
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
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.
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
24 For example, keywords "read" and "write" will only be highlighted if they are in
27 property MyProperty: boolean read FMyProperty write FMyProperty;
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)
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:
67 TMyClass = class(UnicodeAncestor)
69 TMyClass = class(AnsiAncestor)
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
89 The list of keywords that can be used in pascal.properties file (up to Delphi
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
116 #include "Platform.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"
127 using namespace Scintilla
;
130 static void GetRangeLowered(unsigned int start
,
136 while ((i
< end
- start
+ 1) && (i
< len
-1)) {
137 s
[i
] = static_cast<char>(tolower(styler
[start
+ i
]));
143 static void GetForwardRangeLowered(unsigned int start
,
144 CharacterSet
&charSet
,
149 while ((i
< len
-1) && charSet
.Contains(styler
.SafeGetCharAt(start
+ i
))) {
150 s
[i
] = static_cast<char>(tolower(styler
.SafeGetCharAt(start
+ i
)));
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];
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
);
178 sc
.ChangeState(SCE_PAS_ASM
);
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
[],
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()) {
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.
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
);
245 case SCE_PAS_IDENTIFIER
:
246 if (!setWord
.Contains(sc
.ch
)) {
247 ClassifyPascalWord(keywordlists
, sc
, curLineState
, bSmartHighlighting
);
250 case SCE_PAS_HEXNUMBER
:
251 if (!setHexNumber
.Contains(sc
.ch
)) {
252 sc
.SetState(SCE_PAS_DEFAULT
);
255 case SCE_PAS_COMMENT
:
256 case SCE_PAS_PREPROCESSOR
:
258 sc
.ForwardSetState(SCE_PAS_DEFAULT
);
261 case SCE_PAS_COMMENT2
:
262 case SCE_PAS_PREPROCESSOR2
:
263 if (sc
.Match('*', ')')) {
265 sc
.ForwardSetState(SCE_PAS_DEFAULT
);
268 case SCE_PAS_COMMENTLINE
:
269 if (sc
.atLineStart
) {
270 sc
.SetState(SCE_PAS_DEFAULT
);
275 sc
.ChangeState(SCE_PAS_STRINGEOL
);
276 } else if (sc
.ch
== '\'' && sc
.chNext
== '\'') {
278 } else if (sc
.ch
== '\'') {
279 sc
.ForwardSetState(SCE_PAS_DEFAULT
);
282 case SCE_PAS_STRINGEOL
:
283 if (sc
.atLineStart
) {
284 sc
.SetState(SCE_PAS_DEFAULT
);
287 case SCE_PAS_CHARACTER
:
288 if (!setHexNumber
.Contains(sc
.ch
) && sc
.ch
!= '$') {
289 sc
.SetState(SCE_PAS_DEFAULT
);
292 case SCE_PAS_OPERATOR
:
293 if (bSmartHighlighting
&& sc
.chPrev
== ';') {
294 curLineState
&= ~(stateInProperty
| stateInExport
);
296 sc
.SetState(SCE_PAS_DEFAULT
);
299 sc
.SetState(SCE_PAS_DEFAULT
);
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
);
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
++) {
350 char chNext
= styler
.SafeGetCharAt(i
+ 1);
351 int style
= styler
.StyleAt(i
);
352 if (ch
== '/' && chNext
== '/' && style
== SCE_PAS_COMMENTLINE
) {
354 } else if (!IsASpaceOrTab(ch
)) {
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) {
385 SetFoldInPreprocessorLevelFlag(lineFoldStateCurrent
, nestLevel
);
386 lineFoldStateCurrent
|= stateFoldInPreprocessor
;
388 } else if (strcmp(s
, "endif") == 0 ||
389 strcmp(s
, "ifend") == 0 ||
390 strcmp(s
, "endregion") == 0) {
392 SetFoldInPreprocessorLevelFlag(lineFoldStateCurrent
, nestLevel
);
393 if (nestLevel
== 0) {
394 lineFoldStateCurrent
&= ~stateFoldInPreprocessor
;
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
)))) {
411 ch
= styler
.SafeGetCharAt(j
);
416 static void ClassifyPascalWordFoldPoint(int &levelCurrent
, int &lineFoldStateCurrent
,
417 int startPos
, unsigned int endPos
,
418 unsigned int lastStart
, unsigned int currentPos
, Accessor
&styler
) {
420 GetRangeLowered(lastStart
, currentPos
, styler
, s
, sizeof(s
));
422 if (strcmp(s
, "record") == 0) {
423 lineFoldStateCurrent
|= stateFoldInRecord
;
425 } else if (strcmp(s
, "begin") == 0 ||
426 strcmp(s
, "asm") == 0 ||
427 strcmp(s
, "try") == 0 ||
428 (strcmp(s
, "case") == 0 && !(lineFoldStateCurrent
& stateFoldInRecord
))) {
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
);
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
) {
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
)))) {
479 ch
= styler
.SafeGetCharAt(j
);
481 if (j
>= startPos
&& styler
.SafeGetCharAt(j
) == '=') {
482 ignoreKeyword
= false;
484 if (!ignoreKeyword
) {
487 } else if (strcmp(s
, "end") == 0) {
488 lineFoldStateCurrent
&= ~stateFoldInRecord
;
490 if (levelCurrent
< SC_FOLDLEVELBASE
) {
491 levelCurrent
= SC_FOLDLEVELBASE
;
496 static void FoldPascalDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*[],
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
;
512 CharacterSet
setWord(CharacterSet::setAlphaNum
, "_", 0x80, true);
514 for (unsigned int i
= startPos
; i
< endPos
; i
++) {
516 chNext
= styler
.SafeGetCharAt(i
+ 1);
517 int stylePrev
= style
;
519 styleNext
= styler
.StyleAt(i
+ 1);
520 bool atEOL
= (ch
== '\r' && chNext
!= '\n') || (ch
== '\n');
522 if (foldComment
&& IsStreamCommentStyle(style
)) {
523 if (!IsStreamCommentStyle(stylePrev
)) {
525 } else if (!IsStreamCommentStyle(styleNext
) && !atEOL
) {
526 // Comments don't end at end of line and the next character may be unstyled.
530 if (foldComment
&& atEOL
&& IsCommentLine(lineCurrent
, styler
))
532 if (!IsCommentLine(lineCurrent
- 1, styler
)
533 && IsCommentLine(lineCurrent
+ 1, styler
))
535 else if (IsCommentLine(lineCurrent
- 1, styler
)
536 && !IsCommentLine(lineCurrent
+1, styler
))
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.
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
);
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
);
574 levelPrev
= levelCurrent
;
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...
582 if (visibleChars
== 0 && foldCompact
)
583 lev
|= SC_FOLDLEVELWHITEFLAG
;
584 styler
.SetLevel(lineCurrent
, lev
);
587 static const char * const pascalWordListDesc
[] = {
592 LexerModule
lmPascal(SCLEX_PASCAL
, ColourisePascalDoc
, "pascal", FoldPascalDoc
, pascalWordListDesc
);