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  **/
9 
10 /*
11 
12 A few words about features of the new completely rewritten LexPascal...
13 
14 Generally speaking LexPascal tries to support all available Delphi features (up
15 to Delphi XE4 at this time).
16 
17 ~ HIGHLIGHTING:
18 
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).
23 
24 For example, keywords "read" and "write" will only be highlighted if they are in
25 property declaration:
26 
27 property MyProperty: boolean read FMyProperty write FMyProperty;
28 
29 ~ FOLDING:
30 
31 Folding is supported in the following cases:
32 
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)
41 
42 Remarks:
43 
44 - Folding of code blocks tries to handle all special cases in which folding
45 should not occur. As implemented those are:
46 
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")
57 5. Forward (disp)interface declarations ("type IMyInterface = interface;") are
58 ignored as fold points
59 
60 - Folding of code blocks inside preprocessor blocks is disabled (any comments
61 inside them will be folded fine) because there is no guarantee that complete
62 code block will be contained inside folded preprocessor block in which case
63 folded code block could end prematurely at the end of preprocessor block if
64 there is no closing statement inside. This was done in order to properly process
65 document that may contain something like this:
66 
67 type
68 {$IFDEF UNICODE}
69   TMyClass = class(UnicodeAncestor)
70 {$ELSE}
71   TMyClass = class(AnsiAncestor)
72 {$ENDIF}
73   private
74   ...
75   public
76   ...
77   published
78   ...
79 end;
80 
81 If class declarations were folded, then the second class declaration would end
82 at "$ENDIF" statement, first class statement would end at "end;" statement and
83 preprocessor "$IFDEF" block would go all the way to the end of document.
84 However, having in mind all this, if you want to enable folding of code blocks
85 inside preprocessor blocks, you can disable folding of preprocessor blocks by
86 changing "fold.preprocessor" property, in which case everything inside them
87 would be folded.
88 
89 ~ KEYWORDS:
90 
91 The list of keywords that can be used in pascal.properties file (up to Delphi
92 XE4):
93 
94 - Keywords: absolute abstract and array as asm assembler automated begin case
95 cdecl class const constructor delayed deprecated destructor dispid dispinterface
96 div do downto dynamic else end except experimental export exports external far
97 file final finalization finally for forward function goto helper if
98 implementation in inherited initialization inline interface is label library
99 message mod near nil not object of on operator or out overload override packed
100 pascal platform private procedure program property protected public published
101 raise record reference register reintroduce repeat resourcestring safecall
102 sealed set shl shr static stdcall strict string then threadvar to try type unit
103 unsafe until uses var varargs virtual while winapi with xor
104 
105 - Keywords related to the "smart highlithing" feature: add default implements
106 index name nodefault read readonly remove stored write writeonly
107 
108 - Keywords related to Delphi packages (in addition to all above): package
109 contains requires
110 
111 */
112 
113 #include <stdlib.h>
114 #include <string.h>
115 #include <stdio.h>
116 #include <stdarg.h>
117 #include <assert.h>
118 #include <ctype.h>
119 
120 #include "ILexer.h"
121 #include "Scintilla.h"
122 #include "SciLexer.h"
123 
124 #include "WordList.h"
125 #include "LexAccessor.h"
126 #include "Accessor.h"
127 #include "StyleContext.h"
128 #include "CharacterSet.h"
129 #include "LexerModule.h"
130 
131 #ifdef SCI_NAMESPACE
132 using namespace Scintilla;
133 #endif
134 
GetRangeLowered(unsigned int start,unsigned int end,Accessor & styler,char * s,unsigned int len)135 static void GetRangeLowered(unsigned int start,
136 		unsigned int end,
137 		Accessor &styler,
138 		char *s,
139 		unsigned int len) {
140 	unsigned int i = 0;
141 	while ((i < end - start + 1) && (i < len-1)) {
142 		s[i] = static_cast<char>(tolower(styler[start + i]));
143 		i++;
144 	}
145 	s[i] = '\0';
146 }
147 
GetForwardRangeLowered(unsigned int start,CharacterSet & charSet,Accessor & styler,char * s,unsigned int len)148 static void GetForwardRangeLowered(unsigned int start,
149 		CharacterSet &charSet,
150 		Accessor &styler,
151 		char *s,
152 		unsigned int len) {
153 	unsigned int i = 0;
154 	while ((i < len-1) && charSet.Contains(styler.SafeGetCharAt(start + i))) {
155 		s[i] = static_cast<char>(tolower(styler.SafeGetCharAt(start + i)));
156 		i++;
157 	}
158 	s[i] = '\0';
159 
160 }
161 
162 enum {
163 	stateInAsm = 0x1000,
164 	stateInProperty = 0x2000,
165 	stateInExport = 0x4000,
166 	stateFoldInPreprocessor = 0x0100,
167 	stateFoldInRecord = 0x0200,
168 	stateFoldInPreprocessorLevelMask = 0x00FF,
169 	stateFoldMaskAll = 0x0FFF
170 };
171 
ClassifyPascalWord(WordList * keywordlists[],StyleContext & sc,int & curLineState,bool bSmartHighlighting)172 static void ClassifyPascalWord(WordList *keywordlists[], StyleContext &sc, int &curLineState, bool bSmartHighlighting) {
173 	WordList& keywords = *keywordlists[0];
174 
175 	char s[100];
176 	sc.GetCurrentLowered(s, sizeof(s));
177 	if (keywords.InList(s)) {
178 		if (curLineState & stateInAsm) {
179 			if (strcmp(s, "end") == 0 && sc.GetRelative(-4) != '@') {
180 				curLineState &= ~stateInAsm;
181 				sc.ChangeState(SCE_PAS_WORD);
182 			} else {
183 				sc.ChangeState(SCE_PAS_ASM);
184 			}
185 		} else {
186 			bool ignoreKeyword = false;
187 			if (strcmp(s, "asm") == 0) {
188 				curLineState |= stateInAsm;
189 			} else if (bSmartHighlighting) {
190 				if (strcmp(s, "property") == 0) {
191 					curLineState |= stateInProperty;
192 				} else if (strcmp(s, "exports") == 0) {
193 					curLineState |= stateInExport;
194 				} else if (!(curLineState & (stateInProperty | stateInExport)) && strcmp(s, "index") == 0) {
195 					ignoreKeyword = true;
196 				} else if (!(curLineState & stateInExport) && strcmp(s, "name") == 0) {
197 					ignoreKeyword = true;
198 				} else if (!(curLineState & stateInProperty) &&
199 					(strcmp(s, "read") == 0 || strcmp(s, "write") == 0 ||
200 					 strcmp(s, "default") == 0 || strcmp(s, "nodefault") == 0 ||
201 					 strcmp(s, "stored") == 0 || strcmp(s, "implements") == 0 ||
202 					 strcmp(s, "readonly") == 0 || strcmp(s, "writeonly") == 0 ||
203 					 strcmp(s, "add") == 0 || strcmp(s, "remove") == 0)) {
204 					ignoreKeyword = true;
205 				}
206 			}
207 			if (!ignoreKeyword) {
208 				sc.ChangeState(SCE_PAS_WORD);
209 			}
210 		}
211 	} else if (curLineState & stateInAsm) {
212 		sc.ChangeState(SCE_PAS_ASM);
213 	}
214 	sc.SetState(SCE_PAS_DEFAULT);
215 }
216 
ColourisePascalDoc(unsigned int startPos,int length,int initStyle,WordList * keywordlists[],Accessor & styler)217 static void ColourisePascalDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[],
218 		Accessor &styler) {
219 	bool bSmartHighlighting = styler.GetPropertyInt("lexer.pascal.smart.highlighting", 1) != 0;
220 
221 	CharacterSet setWordStart(CharacterSet::setAlpha, "_", 0x80, true);
222 	CharacterSet setWord(CharacterSet::setAlphaNum, "_", 0x80, true);
223 	CharacterSet setNumber(CharacterSet::setDigits, ".-+eE");
224 	CharacterSet setHexNumber(CharacterSet::setDigits, "abcdefABCDEF");
225 	CharacterSet setOperator(CharacterSet::setNone, "#$&'()*+,-./:;<=>@[]^{}");
226 
227 	int curLine = styler.GetLine(startPos);
228 	int curLineState = curLine > 0 ? styler.GetLineState(curLine - 1) : 0;
229 
230 	StyleContext sc(startPos, length, initStyle, styler);
231 
232 	for (; sc.More(); sc.Forward()) {
233 		if (sc.atLineEnd) {
234 			// Update the line state, so it can be seen by next line
235 			curLine = styler.GetLine(sc.currentPos);
236 			styler.SetLineState(curLine, curLineState);
237 		}
238 
239 		// Determine if the current state should terminate.
240 		switch (sc.state) {
241 			case SCE_PAS_NUMBER:
242 				if (!setNumber.Contains(sc.ch) || (sc.ch == '.' && sc.chNext == '.')) {
243 					sc.SetState(SCE_PAS_DEFAULT);
244 				} else if (sc.ch == '-' || sc.ch == '+') {
245 					if (sc.chPrev != 'E' && sc.chPrev != 'e') {
246 						sc.SetState(SCE_PAS_DEFAULT);
247 					}
248 				}
249 				break;
250 			case SCE_PAS_IDENTIFIER:
251 				if (!setWord.Contains(sc.ch)) {
252 					ClassifyPascalWord(keywordlists, sc, curLineState, bSmartHighlighting);
253 				}
254 				break;
255 			case SCE_PAS_HEXNUMBER:
256 				if (!setHexNumber.Contains(sc.ch)) {
257 					sc.SetState(SCE_PAS_DEFAULT);
258 				}
259 				break;
260 			case SCE_PAS_COMMENT:
261 			case SCE_PAS_PREPROCESSOR:
262 				if (sc.ch == '}') {
263 					sc.ForwardSetState(SCE_PAS_DEFAULT);
264 				}
265 				break;
266 			case SCE_PAS_COMMENT2:
267 			case SCE_PAS_PREPROCESSOR2:
268 				if (sc.Match('*', ')')) {
269 					sc.Forward();
270 					sc.ForwardSetState(SCE_PAS_DEFAULT);
271 				}
272 				break;
273 			case SCE_PAS_COMMENTLINE:
274 				if (sc.atLineStart) {
275 					sc.SetState(SCE_PAS_DEFAULT);
276 				}
277 				break;
278 			case SCE_PAS_STRING:
279 				if (sc.atLineEnd) {
280 					sc.ChangeState(SCE_PAS_STRINGEOL);
281 				} else if (sc.ch == '\'' && sc.chNext == '\'') {
282 					sc.Forward();
283 				} else if (sc.ch == '\'') {
284 					sc.ForwardSetState(SCE_PAS_DEFAULT);
285 				}
286 				break;
287 			case SCE_PAS_STRINGEOL:
288 				if (sc.atLineStart) {
289 					sc.SetState(SCE_PAS_DEFAULT);
290 				}
291 				break;
292 			case SCE_PAS_CHARACTER:
293 				if (!setHexNumber.Contains(sc.ch) && sc.ch != '$') {
294 					sc.SetState(SCE_PAS_DEFAULT);
295 				}
296 				break;
297 			case SCE_PAS_OPERATOR:
298 				if (bSmartHighlighting && sc.chPrev == ';') {
299 					curLineState &= ~(stateInProperty | stateInExport);
300 				}
301 				sc.SetState(SCE_PAS_DEFAULT);
302 				break;
303 			case SCE_PAS_ASM:
304 				sc.SetState(SCE_PAS_DEFAULT);
305 				break;
306 		}
307 
308 		// Determine if a new state should be entered.
309 		if (sc.state == SCE_PAS_DEFAULT) {
310 			if (IsADigit(sc.ch) && !(curLineState & stateInAsm)) {
311 				sc.SetState(SCE_PAS_NUMBER);
312 			} else if (setWordStart.Contains(sc.ch)) {
313 				sc.SetState(SCE_PAS_IDENTIFIER);
314 			} else if (sc.ch == '$' && !(curLineState & stateInAsm)) {
315 				sc.SetState(SCE_PAS_HEXNUMBER);
316 			} else if (sc.Match('{', '$')) {
317 				sc.SetState(SCE_PAS_PREPROCESSOR);
318 			} else if (sc.ch == '{') {
319 				sc.SetState(SCE_PAS_COMMENT);
320 			} else if (sc.Match("(*$")) {
321 				sc.SetState(SCE_PAS_PREPROCESSOR2);
322 			} else if (sc.Match('(', '*')) {
323 				sc.SetState(SCE_PAS_COMMENT2);
324 				sc.Forward();	// Eat the * so it isn't used for the end of the comment
325 			} else if (sc.Match('/', '/')) {
326 				sc.SetState(SCE_PAS_COMMENTLINE);
327 			} else if (sc.ch == '\'') {
328 				sc.SetState(SCE_PAS_STRING);
329 			} else if (sc.ch == '#') {
330 				sc.SetState(SCE_PAS_CHARACTER);
331 			} else if (setOperator.Contains(sc.ch) && !(curLineState & stateInAsm)) {
332 				sc.SetState(SCE_PAS_OPERATOR);
333 			} else if (curLineState & stateInAsm) {
334 				sc.SetState(SCE_PAS_ASM);
335 			}
336 		}
337 	}
338 
339 	if (sc.state == SCE_PAS_IDENTIFIER && setWord.Contains(sc.chPrev)) {
340 		ClassifyPascalWord(keywordlists, sc, curLineState, bSmartHighlighting);
341 	}
342 
343 	sc.Complete();
344 }
345 
IsStreamCommentStyle(int style)346 static bool IsStreamCommentStyle(int style) {
347 	return style == SCE_PAS_COMMENT || style == SCE_PAS_COMMENT2;
348 }
349 
IsCommentLine(int line,Accessor & styler)350 static bool IsCommentLine(int line, Accessor &styler) {
351 	int pos = styler.LineStart(line);
352 	int eolPos = styler.LineStart(line + 1) - 1;
353 	for (int i = pos; i < eolPos; i++) {
354 		char ch = styler[i];
355 		char chNext = styler.SafeGetCharAt(i + 1);
356 		int style = styler.StyleAt(i);
357 		if (ch == '/' && chNext == '/' && style == SCE_PAS_COMMENTLINE) {
358 			return true;
359 		} else if (!IsASpaceOrTab(ch)) {
360 			return false;
361 		}
362 	}
363 	return false;
364 }
365 
GetFoldInPreprocessorLevelFlag(int lineFoldStateCurrent)366 static unsigned int GetFoldInPreprocessorLevelFlag(int lineFoldStateCurrent) {
367 	return lineFoldStateCurrent & stateFoldInPreprocessorLevelMask;
368 }
369 
SetFoldInPreprocessorLevelFlag(int & lineFoldStateCurrent,unsigned int nestLevel)370 static void SetFoldInPreprocessorLevelFlag(int &lineFoldStateCurrent, unsigned int nestLevel) {
371 	lineFoldStateCurrent &= ~stateFoldInPreprocessorLevelMask;
372 	lineFoldStateCurrent |= nestLevel & stateFoldInPreprocessorLevelMask;
373 }
374 
ClassifyPascalPreprocessorFoldPoint(int & levelCurrent,int & lineFoldStateCurrent,unsigned int startPos,Accessor & styler)375 static void ClassifyPascalPreprocessorFoldPoint(int &levelCurrent, int &lineFoldStateCurrent,
376 		unsigned int startPos, Accessor &styler) {
377 	CharacterSet setWord(CharacterSet::setAlpha);
378 
379 	char s[11];	// Size of the longest possible keyword + one additional character + null
380 	GetForwardRangeLowered(startPos, setWord, styler, s, sizeof(s));
381 
382 	unsigned int nestLevel = GetFoldInPreprocessorLevelFlag(lineFoldStateCurrent);
383 
384 	if (strcmp(s, "if") == 0 ||
385 		strcmp(s, "ifdef") == 0 ||
386 		strcmp(s, "ifndef") == 0 ||
387 		strcmp(s, "ifopt") == 0 ||
388 		strcmp(s, "region") == 0) {
389 		nestLevel++;
390 		SetFoldInPreprocessorLevelFlag(lineFoldStateCurrent, nestLevel);
391 		lineFoldStateCurrent |= stateFoldInPreprocessor;
392 		levelCurrent++;
393 	} else if (strcmp(s, "endif") == 0 ||
394 		strcmp(s, "ifend") == 0 ||
395 		strcmp(s, "endregion") == 0) {
396 		nestLevel--;
397 		SetFoldInPreprocessorLevelFlag(lineFoldStateCurrent, nestLevel);
398 		if (nestLevel == 0) {
399 			lineFoldStateCurrent &= ~stateFoldInPreprocessor;
400 		}
401 		levelCurrent--;
402 		if (levelCurrent < SC_FOLDLEVELBASE) {
403 			levelCurrent = SC_FOLDLEVELBASE;
404 		}
405 	}
406 }
407 
SkipWhiteSpace(unsigned int currentPos,unsigned int endPos,Accessor & styler,bool includeChars=false)408 static unsigned int SkipWhiteSpace(unsigned int currentPos, unsigned int endPos,
409 		Accessor &styler, bool includeChars = false) {
410 	CharacterSet setWord(CharacterSet::setAlphaNum, "_");
411 	unsigned int j = currentPos + 1;
412 	char ch = styler.SafeGetCharAt(j);
413 	while ((j < endPos) && (IsASpaceOrTab(ch) || ch == '\r' || ch == '\n' ||
414 		IsStreamCommentStyle(styler.StyleAt(j)) || (includeChars && setWord.Contains(ch)))) {
415 		j++;
416 		ch = styler.SafeGetCharAt(j);
417 	}
418 	return j;
419 }
420 
ClassifyPascalWordFoldPoint(int & levelCurrent,int & lineFoldStateCurrent,int startPos,unsigned int endPos,unsigned int lastStart,unsigned int currentPos,Accessor & styler)421 static void ClassifyPascalWordFoldPoint(int &levelCurrent, int &lineFoldStateCurrent,
422 		int startPos, unsigned int endPos,
423 		unsigned int lastStart, unsigned int currentPos, Accessor &styler) {
424 	char s[100];
425 	GetRangeLowered(lastStart, currentPos, styler, s, sizeof(s));
426 
427 	if (strcmp(s, "record") == 0) {
428 		lineFoldStateCurrent |= stateFoldInRecord;
429 		levelCurrent++;
430 	} else if (strcmp(s, "begin") == 0 ||
431 		strcmp(s, "asm") == 0 ||
432 		strcmp(s, "try") == 0 ||
433 		(strcmp(s, "case") == 0 && !(lineFoldStateCurrent & stateFoldInRecord))) {
434 		levelCurrent++;
435 	} else if (strcmp(s, "class") == 0 || strcmp(s, "object") == 0) {
436 		// "class" & "object" keywords require special handling...
437 		bool ignoreKeyword = false;
438 		unsigned int j = SkipWhiteSpace(currentPos, endPos, styler);
439 		if (j < endPos) {
440 			CharacterSet setWordStart(CharacterSet::setAlpha, "_");
441 			CharacterSet setWord(CharacterSet::setAlphaNum, "_");
442 
443 			if (styler.SafeGetCharAt(j) == ';') {
444 				// Handle forward class declarations ("type TMyClass = class;")
445 				// and object method declarations ("TNotifyEvent = procedure(Sender: TObject) of object;")
446 				ignoreKeyword = true;
447 			} else if (strcmp(s, "class") == 0) {
448 				// "class" keyword has a few more special cases...
449 				if (styler.SafeGetCharAt(j) == '(') {
450 					// Handle simplified complete class declarations ("type TMyClass = class(TObject);")
451 					j = SkipWhiteSpace(j, endPos, styler, true);
452 					if (j < endPos && styler.SafeGetCharAt(j) == ')') {
453 						j = SkipWhiteSpace(j, endPos, styler);
454 						if (j < endPos && styler.SafeGetCharAt(j) == ';') {
455 							ignoreKeyword = true;
456 						}
457 					}
458 				} else if (setWordStart.Contains(styler.SafeGetCharAt(j))) {
459 					char s2[11];	// Size of the longest possible keyword + one additional character + null
460 					GetForwardRangeLowered(j, setWord, styler, s2, sizeof(s2));
461 
462 					if (strcmp(s2, "procedure") == 0 ||
463 						strcmp(s2, "function") == 0 ||
464 						strcmp(s2, "of") == 0 ||
465 						strcmp(s2, "var") == 0 ||
466 						strcmp(s2, "property") == 0 ||
467 						strcmp(s2, "operator") == 0) {
468 						ignoreKeyword = true;
469 					}
470 				}
471 			}
472 		}
473 		if (!ignoreKeyword) {
474 			levelCurrent++;
475 		}
476 	} else if (strcmp(s, "interface") == 0) {
477 		// "interface" keyword requires special handling...
478 		bool ignoreKeyword = true;
479 		int j = lastStart - 1;
480 		char ch = styler.SafeGetCharAt(j);
481 		while ((j >= startPos) && (IsASpaceOrTab(ch) || ch == '\r' || ch == '\n' ||
482 			IsStreamCommentStyle(styler.StyleAt(j)))) {
483 			j--;
484 			ch = styler.SafeGetCharAt(j);
485 		}
486 		if (j >= startPos && styler.SafeGetCharAt(j) == '=') {
487 			ignoreKeyword = false;
488 		}
489 		if (!ignoreKeyword) {
490 			unsigned int k = SkipWhiteSpace(currentPos, endPos, styler);
491 			if (k < endPos && styler.SafeGetCharAt(k) == ';') {
492 				// Handle forward interface declarations ("type IMyInterface = interface;")
493 				ignoreKeyword = true;
494 			}
495 		}
496 		if (!ignoreKeyword) {
497 			levelCurrent++;
498 		}
499 	} else if (strcmp(s, "dispinterface") == 0) {
500 		// "dispinterface" keyword requires special handling...
501 		bool ignoreKeyword = false;
502 		unsigned int j = SkipWhiteSpace(currentPos, endPos, styler);
503 		if (j < endPos && styler.SafeGetCharAt(j) == ';') {
504 			// Handle forward dispinterface declarations ("type IMyInterface = dispinterface;")
505 			ignoreKeyword = true;
506 		}
507 		if (!ignoreKeyword) {
508 			levelCurrent++;
509 		}
510 	} else if (strcmp(s, "end") == 0) {
511 		lineFoldStateCurrent &= ~stateFoldInRecord;
512 		levelCurrent--;
513 		if (levelCurrent < SC_FOLDLEVELBASE) {
514 			levelCurrent = SC_FOLDLEVELBASE;
515 		}
516 	}
517 }
518 
FoldPascalDoc(unsigned int startPos,int length,int initStyle,WordList * [],Accessor & styler)519 static void FoldPascalDoc(unsigned int startPos, int length, int initStyle, WordList *[],
520 		Accessor &styler) {
521 	bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
522 	bool foldPreprocessor = styler.GetPropertyInt("fold.preprocessor") != 0;
523 	bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
524 	unsigned int endPos = startPos + length;
525 	int visibleChars = 0;
526 	int lineCurrent = styler.GetLine(startPos);
527 	int levelPrev = styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK;
528 	int levelCurrent = levelPrev;
529 	int lineFoldStateCurrent = lineCurrent > 0 ? styler.GetLineState(lineCurrent - 1) & stateFoldMaskAll : 0;
530 	char chNext = styler[startPos];
531 	int styleNext = styler.StyleAt(startPos);
532 	int style = initStyle;
533 
534 	int lastStart = 0;
535 	CharacterSet setWord(CharacterSet::setAlphaNum, "_", 0x80, true);
536 
537 	for (unsigned int i = startPos; i < endPos; i++) {
538 		char ch = chNext;
539 		chNext = styler.SafeGetCharAt(i + 1);
540 		int stylePrev = style;
541 		style = styleNext;
542 		styleNext = styler.StyleAt(i + 1);
543 		bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
544 
545 		if (foldComment && IsStreamCommentStyle(style)) {
546 			if (!IsStreamCommentStyle(stylePrev)) {
547 				levelCurrent++;
548 			} else if (!IsStreamCommentStyle(styleNext) && !atEOL) {
549 				// Comments don't end at end of line and the next character may be unstyled.
550 				levelCurrent--;
551 			}
552 		}
553 		if (foldComment && atEOL && IsCommentLine(lineCurrent, styler))
554 		{
555 			if (!IsCommentLine(lineCurrent - 1, styler)
556 			    && IsCommentLine(lineCurrent + 1, styler))
557 				levelCurrent++;
558 			else if (IsCommentLine(lineCurrent - 1, styler)
559 			         && !IsCommentLine(lineCurrent+1, styler))
560 				levelCurrent--;
561 		}
562 		if (foldPreprocessor) {
563 			if (style == SCE_PAS_PREPROCESSOR && ch == '{' && chNext == '$') {
564 				ClassifyPascalPreprocessorFoldPoint(levelCurrent, lineFoldStateCurrent, i + 2, styler);
565 			} else if (style == SCE_PAS_PREPROCESSOR2 && ch == '(' && chNext == '*'
566 			           && styler.SafeGetCharAt(i + 2) == '$') {
567 				ClassifyPascalPreprocessorFoldPoint(levelCurrent, lineFoldStateCurrent, i + 3, styler);
568 			}
569 		}
570 
571 		if (stylePrev != SCE_PAS_WORD && style == SCE_PAS_WORD)
572 		{
573 			// Store last word start point.
574 			lastStart = i;
575 		}
576 		if (stylePrev == SCE_PAS_WORD && !(lineFoldStateCurrent & stateFoldInPreprocessor)) {
577 			if(setWord.Contains(ch) && !setWord.Contains(chNext)) {
578 				ClassifyPascalWordFoldPoint(levelCurrent, lineFoldStateCurrent, startPos, endPos, lastStart, i, styler);
579 			}
580 		}
581 
582 		if (!IsASpace(ch))
583 			visibleChars++;
584 
585 		if (atEOL) {
586 			int lev = levelPrev;
587 			if (visibleChars == 0 && foldCompact)
588 				lev |= SC_FOLDLEVELWHITEFLAG;
589 			if ((levelCurrent > levelPrev) && (visibleChars > 0))
590 				lev |= SC_FOLDLEVELHEADERFLAG;
591 			if (lev != styler.LevelAt(lineCurrent)) {
592 				styler.SetLevel(lineCurrent, lev);
593 			}
594 			int newLineState = (styler.GetLineState(lineCurrent) & ~stateFoldMaskAll) | lineFoldStateCurrent;
595 			styler.SetLineState(lineCurrent, newLineState);
596 			lineCurrent++;
597 			levelPrev = levelCurrent;
598 			visibleChars = 0;
599 		}
600 	}
601 
602 	// If we didn't reach the EOL in previous loop, store line level and whitespace information.
603 	// The rest will be filled in later...
604 	int lev = levelPrev;
605 	if (visibleChars == 0 && foldCompact)
606 		lev |= SC_FOLDLEVELWHITEFLAG;
607 	styler.SetLevel(lineCurrent, lev);
608 }
609 
610 static const char * const pascalWordListDesc[] = {
611 	"Keywords",
612 	0
613 };
614 
615 LexerModule lmPascal(SCLEX_PASCAL, ColourisePascalDoc, "pascal", FoldPascalDoc, pascalWordListDesc);
616