1 // Scintilla source code edit control
2 /** @file LexECL.cxx
3  ** Lexer for ECL.
4  **/
5 // Copyright 1998-2001 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
7 
8 #include <stdlib.h>
9 #include <string.h>
10 #include <stdio.h>
11 #include <stdarg.h>
12 #include <assert.h>
13 #include <ctype.h>
14 
15 #ifdef _MSC_VER
16 #pragma warning(disable: 4786)
17 #endif
18 #ifdef __BORLANDC__
19 // Borland C++ displays warnings in vector header without this
20 #pragma option -w-ccc -w-rch
21 #endif
22 
23 #include <string>
24 #include <vector>
25 #include <map>
26 #include <algorithm>
27 
28 #include "ILexer.h"
29 #include "Scintilla.h"
30 #include "SciLexer.h"
31 
32 #include "PropSetSimple.h"
33 #include "WordList.h"
34 #include "LexAccessor.h"
35 #include "Accessor.h"
36 #include "StyleContext.h"
37 #include "CharacterSet.h"
38 #include "LexerModule.h"
39 #include "OptionSet.h"
40 
41 #define SET_LOWER "abcdefghijklmnopqrstuvwxyz"
42 #define SET_UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
43 #define SET_DIGITS "0123456789"
44 
45 using namespace Scintilla;
46 
IsSpaceEquiv(int state)47 static bool IsSpaceEquiv(int state) {
48 	switch (state) {
49 	case SCE_ECL_DEFAULT:
50 	case SCE_ECL_COMMENT:
51 	case SCE_ECL_COMMENTLINE:
52 	case SCE_ECL_COMMENTLINEDOC:
53 	case SCE_ECL_COMMENTDOCKEYWORD:
54 	case SCE_ECL_COMMENTDOCKEYWORDERROR:
55 	case SCE_ECL_COMMENTDOC:
56 		return true;
57 
58 	default:
59 		return false;
60 	}
61 }
62 
ColouriseEclDoc(Sci_PositionU startPos,Sci_Position length,int initStyle,WordList * keywordlists[],Accessor & styler)63 static void ColouriseEclDoc(Sci_PositionU startPos, Sci_Position length, int initStyle, WordList *keywordlists[],
64                             Accessor &styler) {
65 	WordList &keywords0 = *keywordlists[0];
66 	WordList &keywords1 = *keywordlists[1];
67 	WordList &keywords2 = *keywordlists[2];
68 	WordList &keywords3 = *keywordlists[3]; //Value Types
69 	WordList &keywords4 = *keywordlists[4];
70 	WordList &keywords5 = *keywordlists[5];
71 	WordList &keywords6 = *keywordlists[6];	//Javadoc Tags
72 	WordList cplusplus;
73 	cplusplus.Set("beginc endc");
74 
75 	bool stylingWithinPreprocessor = false;
76 
77 	CharacterSet setOKBeforeRE(CharacterSet::setNone, "(=,");
78 	CharacterSet setDoxygen(CharacterSet::setLower, "$@\\&<>#{}[]");
79 	CharacterSet setWordStart(CharacterSet::setAlpha, "_", 0x80, true);
80 	CharacterSet setWord(CharacterSet::setAlphaNum, "._", 0x80, true);
81 	CharacterSet setQualified(CharacterSet::setNone, "uUxX");
82 
83 	int chPrevNonWhite = ' ';
84 	int visibleChars = 0;
85 	bool lastWordWasUUID = false;
86 	int styleBeforeDCKeyword = SCE_ECL_DEFAULT;
87 	bool continuationLine = false;
88 
89 	if (initStyle == SCE_ECL_PREPROCESSOR) {
90 		// Set continuationLine if last character of previous line is '\'
91 		Sci_Position lineCurrent = styler.GetLine(startPos);
92 		if (lineCurrent > 0) {
93 			int chBack = styler.SafeGetCharAt(startPos-1, 0);
94 			int chBack2 = styler.SafeGetCharAt(startPos-2, 0);
95 			int lineEndChar = '!';
96 			if (chBack2 == '\r' && chBack == '\n') {
97 				lineEndChar = styler.SafeGetCharAt(startPos-3, 0);
98 			} else if (chBack == '\n' || chBack == '\r') {
99 				lineEndChar = chBack2;
100 			}
101 			continuationLine = lineEndChar == '\\';
102 		}
103 	}
104 
105 	// look back to set chPrevNonWhite properly for better regex colouring
106 	if (startPos > 0) {
107 		Sci_Position back = startPos;
108 		while (--back && IsSpaceEquiv(styler.StyleAt(back)))
109 			;
110 		if (styler.StyleAt(back) == SCE_ECL_OPERATOR) {
111 			chPrevNonWhite = styler.SafeGetCharAt(back);
112 		}
113 	}
114 
115 	StyleContext sc(startPos, length, initStyle, styler);
116 
117 	for (; sc.More(); sc.Forward()) {
118 		if (sc.atLineStart) {
119 			if (sc.state == SCE_ECL_STRING) {
120 				// Prevent SCE_ECL_STRINGEOL from leaking back to previous line which
121 				// ends with a line continuation by locking in the state upto this position.
122 				sc.SetState(SCE_ECL_STRING);
123 			}
124 			// Reset states to begining of colourise so no surprises
125 			// if different sets of lines lexed.
126 			visibleChars = 0;
127 			lastWordWasUUID = false;
128 		}
129 
130 		// Handle line continuation generically.
131 		if (sc.ch == '\\') {
132 			if (sc.chNext == '\n' || sc.chNext == '\r') {
133 				sc.Forward();
134 				if (sc.ch == '\r' && sc.chNext == '\n') {
135 					sc.Forward();
136 				}
137 				continuationLine = true;
138 				continue;
139 			}
140 		}
141 
142 		// Determine if the current state should terminate.
143 		switch (sc.state) {
144 			case SCE_ECL_ADDED:
145 			case SCE_ECL_DELETED:
146 			case SCE_ECL_CHANGED:
147 			case SCE_ECL_MOVED:
148 			if (sc.atLineStart)
149 					sc.SetState(SCE_ECL_DEFAULT);
150 				break;
151 			case SCE_ECL_OPERATOR:
152 				sc.SetState(SCE_ECL_DEFAULT);
153 				break;
154 			case SCE_ECL_NUMBER:
155 				// We accept almost anything because of hex. and number suffixes
156 				if (!setWord.Contains(sc.ch)) {
157 					sc.SetState(SCE_ECL_DEFAULT);
158 				}
159 				break;
160 			case SCE_ECL_IDENTIFIER:
161 				if (!setWord.Contains(sc.ch) || (sc.ch == '.')) {
162 					char s[1000];
163 					sc.GetCurrentLowered(s, sizeof(s));
164 					if (keywords0.InList(s)) {
165 						lastWordWasUUID = strcmp(s, "uuid") == 0;
166 						sc.ChangeState(SCE_ECL_WORD0);
167 					} else if (keywords1.InList(s)) {
168 						sc.ChangeState(SCE_ECL_WORD1);
169 					} else if (keywords2.InList(s)) {
170 						sc.ChangeState(SCE_ECL_WORD2);
171 					} else if (keywords4.InList(s)) {
172 						sc.ChangeState(SCE_ECL_WORD4);
173 					} else if (keywords5.InList(s)) {
174 						sc.ChangeState(SCE_ECL_WORD5);
175 					}
176 					else	//Data types are of from KEYWORD##
177 					{
178 						int i = static_cast<int>(strlen(s)) - 1;
179 						while(i >= 0 && (isdigit(s[i]) || s[i] == '_'))
180 							--i;
181 
182 						char s2[1000];
183 						strncpy(s2, s, i + 1);
184 						s2[i + 1] = 0;
185 						if (keywords3.InList(s2)) {
186 							sc.ChangeState(SCE_ECL_WORD3);
187 						}
188 					}
189 					sc.SetState(SCE_ECL_DEFAULT);
190 				}
191 				break;
192 			case SCE_ECL_PREPROCESSOR:
193 				if (sc.atLineStart && !continuationLine) {
194 					sc.SetState(SCE_ECL_DEFAULT);
195 				} else if (stylingWithinPreprocessor) {
196 					if (IsASpace(sc.ch)) {
197 						sc.SetState(SCE_ECL_DEFAULT);
198 					}
199 				} else {
200 					if (sc.Match('/', '*') || sc.Match('/', '/')) {
201 						sc.SetState(SCE_ECL_DEFAULT);
202 					}
203 				}
204 				break;
205 			case SCE_ECL_COMMENT:
206 				if (sc.Match('*', '/')) {
207 					sc.Forward();
208 					sc.ForwardSetState(SCE_ECL_DEFAULT);
209 				}
210 				break;
211 			case SCE_ECL_COMMENTDOC:
212 				if (sc.Match('*', '/')) {
213 					sc.Forward();
214 					sc.ForwardSetState(SCE_ECL_DEFAULT);
215 				} else if (sc.ch == '@' || sc.ch == '\\') { // JavaDoc and Doxygen support
216 					// Verify that we have the conditions to mark a comment-doc-keyword
217 					if ((IsASpace(sc.chPrev) || sc.chPrev == '*') && (!IsASpace(sc.chNext))) {
218 						styleBeforeDCKeyword = SCE_ECL_COMMENTDOC;
219 						sc.SetState(SCE_ECL_COMMENTDOCKEYWORD);
220 					}
221 				}
222 				break;
223 			case SCE_ECL_COMMENTLINE:
224 				if (sc.atLineStart) {
225 					sc.SetState(SCE_ECL_DEFAULT);
226 				}
227 				break;
228 			case SCE_ECL_COMMENTLINEDOC:
229 				if (sc.atLineStart) {
230 					sc.SetState(SCE_ECL_DEFAULT);
231 				} else if (sc.ch == '@' || sc.ch == '\\') { // JavaDoc and Doxygen support
232 					// Verify that we have the conditions to mark a comment-doc-keyword
233 					if ((IsASpace(sc.chPrev) || sc.chPrev == '/' || sc.chPrev == '!') && (!IsASpace(sc.chNext))) {
234 						styleBeforeDCKeyword = SCE_ECL_COMMENTLINEDOC;
235 						sc.SetState(SCE_ECL_COMMENTDOCKEYWORD);
236 					}
237 				}
238 				break;
239 			case SCE_ECL_COMMENTDOCKEYWORD:
240 				if ((styleBeforeDCKeyword == SCE_ECL_COMMENTDOC) && sc.Match('*', '/')) {
241 					sc.ChangeState(SCE_ECL_COMMENTDOCKEYWORDERROR);
242 					sc.Forward();
243 					sc.ForwardSetState(SCE_ECL_DEFAULT);
244 				} else if (!setDoxygen.Contains(sc.ch)) {
245 					char s[1000];
246 					sc.GetCurrentLowered(s, sizeof(s));
247 					if (!IsASpace(sc.ch) || !keywords6.InList(s+1)) {
248 						sc.ChangeState(SCE_ECL_COMMENTDOCKEYWORDERROR);
249 					}
250 					sc.SetState(styleBeforeDCKeyword);
251 				}
252 				break;
253 			case SCE_ECL_STRING:
254 				if (sc.atLineEnd) {
255 					sc.ChangeState(SCE_ECL_STRINGEOL);
256 				} else if (sc.ch == '\\') {
257 					if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') {
258 						sc.Forward();
259 					}
260 				} else if (sc.ch == '\"') {
261 					sc.ForwardSetState(SCE_ECL_DEFAULT);
262 				}
263 				break;
264 			case SCE_ECL_CHARACTER:
265 				if (sc.atLineEnd) {
266 					sc.ChangeState(SCE_ECL_STRINGEOL);
267 				} else if (sc.ch == '\\') {
268 					if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') {
269 						sc.Forward();
270 					}
271 				} else if (sc.ch == '\'') {
272 					sc.ForwardSetState(SCE_ECL_DEFAULT);
273 				}
274 				break;
275 			case SCE_ECL_REGEX:
276 				if (sc.atLineStart) {
277 					sc.SetState(SCE_ECL_DEFAULT);
278 				} else if (sc.ch == '/') {
279 					sc.Forward();
280 					while ((sc.ch < 0x80) && islower(sc.ch))
281 						sc.Forward();    // gobble regex flags
282 					sc.SetState(SCE_ECL_DEFAULT);
283 				} else if (sc.ch == '\\') {
284 					// Gobble up the quoted character
285 					if (sc.chNext == '\\' || sc.chNext == '/') {
286 						sc.Forward();
287 					}
288 				}
289 				break;
290 			case SCE_ECL_STRINGEOL:
291 				if (sc.atLineStart) {
292 					sc.SetState(SCE_ECL_DEFAULT);
293 				}
294 				break;
295 			case SCE_ECL_VERBATIM:
296 				if (sc.ch == '\"') {
297 					if (sc.chNext == '\"') {
298 						sc.Forward();
299 					} else {
300 						sc.ForwardSetState(SCE_ECL_DEFAULT);
301 					}
302 				}
303 				break;
304 			case SCE_ECL_UUID:
305 				if (sc.ch == '\r' || sc.ch == '\n' || sc.ch == ')') {
306 					sc.SetState(SCE_ECL_DEFAULT);
307 				}
308 				break;
309 		}
310 
311 		// Determine if a new state should be entered.
312 		Sci_Position lineCurrent = styler.GetLine(sc.currentPos);
313 		int lineState = styler.GetLineState(lineCurrent);
314 		if (sc.state == SCE_ECL_DEFAULT) {
315 			if (lineState) {
316 				sc.SetState(lineState);
317 			}
318 			else if (sc.Match('@', '\"')) {
319 				sc.SetState(SCE_ECL_VERBATIM);
320 				sc.Forward();
321 			} else if (setQualified.Contains(sc.ch) && sc.chNext == '\'') {
322 				sc.SetState(SCE_ECL_CHARACTER);
323 				sc.Forward();
324 			} else if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
325 				if (lastWordWasUUID) {
326 					sc.SetState(SCE_ECL_UUID);
327 					lastWordWasUUID = false;
328 				} else {
329 					sc.SetState(SCE_ECL_NUMBER);
330 				}
331 			} else if (setWordStart.Contains(sc.ch) || (sc.ch == '@')) {
332 				if (lastWordWasUUID) {
333 					sc.SetState(SCE_ECL_UUID);
334 					lastWordWasUUID = false;
335 				} else {
336 					sc.SetState(SCE_ECL_IDENTIFIER);
337 				}
338 			} else if (sc.Match('/', '*')) {
339 				if (sc.Match("/**") || sc.Match("/*!")) {	// Support of Qt/Doxygen doc. style
340 					sc.SetState(SCE_ECL_COMMENTDOC);
341 				} else {
342 					sc.SetState(SCE_ECL_COMMENT);
343 				}
344 				sc.Forward();	// Eat the * so it isn't used for the end of the comment
345 			} else if (sc.Match('/', '/')) {
346 				if ((sc.Match("///") && !sc.Match("////")) || sc.Match("//!"))
347 					// Support of Qt/Doxygen doc. style
348 					sc.SetState(SCE_ECL_COMMENTLINEDOC);
349 				else
350 					sc.SetState(SCE_ECL_COMMENTLINE);
351 			} else if (sc.ch == '/' && setOKBeforeRE.Contains(chPrevNonWhite)) {
352 				sc.SetState(SCE_ECL_REGEX);	// JavaScript's RegEx
353 //			} else if (sc.ch == '\"') {
354 //				sc.SetState(SCE_ECL_STRING);
355 			} else if (sc.ch == '\'') {
356 				sc.SetState(SCE_ECL_CHARACTER);
357 			} else if (sc.ch == '#' && visibleChars == 0) {
358 				// Preprocessor commands are alone on their line
359 				sc.SetState(SCE_ECL_PREPROCESSOR);
360 				// Skip whitespace between # and preprocessor word
361 				do {
362 					sc.Forward();
363 				} while ((sc.ch == ' ' || sc.ch == '\t') && sc.More());
364 				if (sc.atLineEnd) {
365 					sc.SetState(SCE_ECL_DEFAULT);
366 				}
367 			} else if (isoperator(static_cast<char>(sc.ch))) {
368 				sc.SetState(SCE_ECL_OPERATOR);
369 			}
370 		}
371 
372 		if (!IsASpace(sc.ch) && !IsSpaceEquiv(sc.state)) {
373 			chPrevNonWhite = sc.ch;
374 			visibleChars++;
375 		}
376 		continuationLine = false;
377 	}
378 	sc.Complete();
379 
380 }
381 
IsStreamCommentStyle(int style)382 static bool IsStreamCommentStyle(int style) {
383 	return style == SCE_ECL_COMMENT ||
384 		style == SCE_ECL_COMMENTDOC ||
385 		style == SCE_ECL_COMMENTDOCKEYWORD ||
386 		style == SCE_ECL_COMMENTDOCKEYWORDERROR;
387 }
388 
MatchNoCase(Accessor & styler,Sci_PositionU & pos,const char * s)389 static bool MatchNoCase(Accessor & styler, Sci_PositionU & pos, const char *s) {
390 	Sci_Position i=0;
391 	for (; *s; i++) {
392 		char compare_char = tolower(*s);
393 		char styler_char = tolower(styler.SafeGetCharAt(pos+i));
394 		if (compare_char != styler_char)
395 			return false;
396 		s++;
397 	}
398 	pos+=i-1;
399 	return true;
400 }
401 
402 
403 // Store both the current line's fold level and the next lines in the
404 // level store to make it easy to pick up with each increment
405 // and to make it possible to fiddle the current level for "} else {".
FoldEclDoc(Sci_PositionU startPos,Sci_Position length,int initStyle,WordList * [],Accessor & styler)406 static void FoldEclDoc(Sci_PositionU startPos, Sci_Position length, int initStyle,
407 					   WordList *[], Accessor &styler) {
408 	bool foldComment = true;
409 	bool foldPreprocessor = true;
410 	bool foldCompact = true;
411 	bool foldAtElse = true;
412 	Sci_PositionU endPos = startPos + length;
413 	int visibleChars = 0;
414 	Sci_Position lineCurrent = styler.GetLine(startPos);
415 	int levelCurrent = SC_FOLDLEVELBASE;
416 	if (lineCurrent > 0)
417 		levelCurrent = styler.LevelAt(lineCurrent-1) >> 16;
418 	int levelMinCurrent = levelCurrent;
419 	int levelNext = levelCurrent;
420 	char chNext = styler[startPos];
421 	int styleNext = styler.StyleAt(startPos);
422 	int style = initStyle;
423 	for (Sci_PositionU i = startPos; i < endPos; i++) {
424 		char ch = chNext;
425 		chNext = styler.SafeGetCharAt(i + 1);
426 		int stylePrev = style;
427 		style = styleNext;
428 		styleNext = styler.StyleAt(i + 1);
429 		bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
430 		if (foldComment && IsStreamCommentStyle(style)) {
431 			if (!IsStreamCommentStyle(stylePrev) && (stylePrev != SCE_ECL_COMMENTLINEDOC)) {
432 				levelNext++;
433 			} else if (!IsStreamCommentStyle(styleNext) && (styleNext != SCE_ECL_COMMENTLINEDOC) && !atEOL) {
434 				// Comments don't end at end of line and the next character may be unstyled.
435 				levelNext--;
436 			}
437 		}
438 		if (foldComment && (style == SCE_ECL_COMMENTLINE)) {
439 			if ((ch == '/') && (chNext == '/')) {
440 				char chNext2 = styler.SafeGetCharAt(i + 2);
441 				if (chNext2 == '{') {
442 					levelNext++;
443 				} else if (chNext2 == '}') {
444 					levelNext--;
445 				}
446 			}
447 		}
448 		if (foldPreprocessor && (style == SCE_ECL_PREPROCESSOR)) {
449 			if (ch == '#') {
450 				Sci_PositionU j = i + 1;
451 				while ((j < endPos) && IsASpaceOrTab(styler.SafeGetCharAt(j))) {
452 					j++;
453 				}
454 				if (MatchNoCase(styler, j, "region") || MatchNoCase(styler, j, "if")) {
455 					levelNext++;
456 				} else if (MatchNoCase(styler, j, "endregion") || MatchNoCase(styler, j, "end")) {
457 					levelNext--;
458 				}
459 			}
460 		}
461 		if (style == SCE_ECL_OPERATOR) {
462 			if (ch == '{') {
463 				// Measure the minimum before a '{' to allow
464 				// folding on "} else {"
465 				if (levelMinCurrent > levelNext) {
466 					levelMinCurrent = levelNext;
467 				}
468 				levelNext++;
469 			} else if (ch == '}') {
470 				levelNext--;
471 			}
472 		}
473 		if (style == SCE_ECL_WORD2) {
474 			if (MatchNoCase(styler, i, "record") || MatchNoCase(styler, i, "transform") || MatchNoCase(styler, i, "type") || MatchNoCase(styler, i, "function") ||
475 				MatchNoCase(styler, i, "module") || MatchNoCase(styler, i, "service") || MatchNoCase(styler, i, "interface") || MatchNoCase(styler, i, "ifblock") ||
476 				MatchNoCase(styler, i, "macro") || MatchNoCase(styler, i, "beginc++")) {
477 				levelNext++;
478 			} else if (MatchNoCase(styler, i, "endmacro") || MatchNoCase(styler, i, "endc++") || MatchNoCase(styler, i, "end")) {
479 				levelNext--;
480 			}
481 		}
482 		if (atEOL || (i == endPos-1)) {
483 			int levelUse = levelCurrent;
484 			if (foldAtElse) {
485 				levelUse = levelMinCurrent;
486 			}
487 			int lev = levelUse | levelNext << 16;
488 			if (visibleChars == 0 && foldCompact)
489 				lev |= SC_FOLDLEVELWHITEFLAG;
490 			if (levelUse < levelNext)
491 				lev |= SC_FOLDLEVELHEADERFLAG;
492 			if (lev != styler.LevelAt(lineCurrent)) {
493 				styler.SetLevel(lineCurrent, lev);
494 			}
495 			lineCurrent++;
496 			levelCurrent = levelNext;
497 			levelMinCurrent = levelCurrent;
498 			if (atEOL && (i == static_cast<Sci_PositionU>(styler.Length()-1))) {
499 				// There is an empty line at end of file so give it same level and empty
500 				styler.SetLevel(lineCurrent, (levelCurrent | levelCurrent << 16) | SC_FOLDLEVELWHITEFLAG);
501 			}
502 			visibleChars = 0;
503 		}
504 		if (!IsASpace(ch))
505 			visibleChars++;
506 	}
507 }
508 
509 static const char * const EclWordListDesc[] = {
510 	"Keywords",
511 	0
512 };
513 
514 LexerModule lmECL(
515    SCLEX_ECL,
516    ColouriseEclDoc,
517    "ecl",
518    FoldEclDoc,
519    EclWordListDesc);
520