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