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