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);