1 // Scintilla source code edit control
2 /** @file LexFlagship.cxx
3  ** Lexer for Harbour and FlagShip.
4  ** (Syntactically compatible to other xBase dialects, like Clipper, dBase, Clip, FoxPro etc.)
5  **/
6 // Copyright 2005 by Randy Butler
7 // Copyright 2010 by Xavi <jarabal/at/gmail.com> (Harbour)
8 // Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
9 // The License.txt file describes the conditions under which this software may be distributed.
10 
11 #include <stdlib.h>
12 #include <string.h>
13 #include <stdio.h>
14 #include <stdarg.h>
15 #include <assert.h>
16 #include <ctype.h>
17 
18 #include "ILexer.h"
19 #include "Scintilla.h"
20 #include "SciLexer.h"
21 
22 #include "WordList.h"
23 #include "LexAccessor.h"
24 #include "Accessor.h"
25 #include "StyleContext.h"
26 #include "CharacterSet.h"
27 #include "LexerModule.h"
28 
29 using namespace Scintilla;
30 
31 // Extended to accept accented characters
IsAWordChar(int ch)32 static inline bool IsAWordChar(int ch)
33 {
34 	return ch >= 0x80 ||
35 				(isalnum(ch) || ch == '_');
36 }
37 
ColouriseFlagShipDoc(Sci_PositionU startPos,Sci_Position length,int initStyle,WordList * keywordlists[],Accessor & styler)38 static void ColouriseFlagShipDoc(Sci_PositionU startPos, Sci_Position length, int initStyle,
39                                  WordList *keywordlists[], Accessor &styler)
40 {
41 
42 	WordList &keywords = *keywordlists[0];
43 	WordList &keywords2 = *keywordlists[1];
44 	WordList &keywords3 = *keywordlists[2];
45 	WordList &keywords4 = *keywordlists[3];
46 	WordList &keywords5 = *keywordlists[4];
47 
48 	// property lexer.flagship.styling.within.preprocessor
49 	//	For Harbour code, determines whether all preprocessor code is styled in the preprocessor style (0) or only from the
50 	//	initial # to the end of the command word(1, the default). It also determines how to present text, dump, and disabled code.
51 	bool stylingWithinPreprocessor = styler.GetPropertyInt("lexer.flagship.styling.within.preprocessor", 1) != 0;
52 
53 	CharacterSet setDoxygen(CharacterSet::setAlpha, "$@\\&<>#{}[]");
54 
55 	int visibleChars = 0;
56 	int closeStringChar = 0;
57 	int styleBeforeDCKeyword = SCE_FS_DEFAULT;
58 	bool bEnableCode = initStyle < SCE_FS_DISABLEDCODE;
59 
60 	StyleContext sc(startPos, length, initStyle, styler);
61 
62 	for (; sc.More(); sc.Forward()) {
63 
64 		// Determine if the current state should terminate.
65 		switch (sc.state) {
66 			case SCE_FS_OPERATOR:
67 			case SCE_FS_OPERATOR_C:
68 			case SCE_FS_WORDOPERATOR:
69 				sc.SetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
70 				break;
71 			case SCE_FS_IDENTIFIER:
72 			case SCE_FS_IDENTIFIER_C:
73 				if (!IsAWordChar(sc.ch)) {
74 					char s[64];
75 					sc.GetCurrentLowered(s, sizeof(s));
76 					if (keywords.InList(s)) {
77 						sc.ChangeState(bEnableCode ? SCE_FS_KEYWORD : SCE_FS_KEYWORD_C);
78 					} else if (keywords2.InList(s)) {
79 						sc.ChangeState(bEnableCode ? SCE_FS_KEYWORD2 : SCE_FS_KEYWORD2_C);
80 					} else if (bEnableCode && keywords3.InList(s)) {
81 						sc.ChangeState(SCE_FS_KEYWORD3);
82 					} else if (bEnableCode && keywords4.InList(s)) {
83 						sc.ChangeState(SCE_FS_KEYWORD4);
84 					}// Else, it is really an identifier...
85 					sc.SetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
86 				}
87 				break;
88 			case SCE_FS_NUMBER:
89 				if (!IsAWordChar(sc.ch) && !(sc.ch == '.' && IsADigit(sc.chNext))) {
90 					sc.SetState(SCE_FS_DEFAULT);
91 				}
92 				break;
93 			case SCE_FS_NUMBER_C:
94 				if (!IsAWordChar(sc.ch) && sc.ch != '.') {
95 					sc.SetState(SCE_FS_DEFAULT_C);
96 				}
97 				break;
98 			case SCE_FS_CONSTANT:
99 				if (!IsAWordChar(sc.ch)) {
100 					sc.SetState(SCE_FS_DEFAULT);
101 				}
102 				break;
103 			case SCE_FS_STRING:
104 			case SCE_FS_STRING_C:
105 				if (sc.ch == closeStringChar) {
106 					sc.ForwardSetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
107 				} else if (sc.atLineEnd) {
108 					sc.ChangeState(bEnableCode ? SCE_FS_STRINGEOL : SCE_FS_STRINGEOL_C);
109 				}
110 				break;
111 			case SCE_FS_STRINGEOL:
112 			case SCE_FS_STRINGEOL_C:
113 				if (sc.atLineStart) {
114 					sc.SetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
115 				}
116 				break;
117 			case SCE_FS_COMMENTDOC:
118 			case SCE_FS_COMMENTDOC_C:
119 				if (sc.Match('*', '/')) {
120 					sc.Forward();
121 					sc.ForwardSetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
122 				} else if (sc.ch == '@' || sc.ch == '\\') { // JavaDoc and Doxygen support
123 					// Verify that we have the conditions to mark a comment-doc-keyword
124 					if ((IsASpace(sc.chPrev) || sc.chPrev == '*') && (!IsASpace(sc.chNext))) {
125 						styleBeforeDCKeyword = bEnableCode ? SCE_FS_COMMENTDOC : SCE_FS_COMMENTDOC_C;
126 						sc.SetState(SCE_FS_COMMENTDOCKEYWORD);
127 					}
128 				}
129 				break;
130 			case SCE_FS_COMMENT:
131 			case SCE_FS_COMMENTLINE:
132 				if (sc.atLineStart) {
133 					sc.SetState(SCE_FS_DEFAULT);
134 				}
135 				break;
136 			case SCE_FS_COMMENTLINEDOC:
137 			case SCE_FS_COMMENTLINEDOC_C:
138 				if (sc.atLineStart) {
139 					sc.SetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
140 				} else if (sc.ch == '@' || sc.ch == '\\') { // JavaDoc and Doxygen support
141 					// Verify that we have the conditions to mark a comment-doc-keyword
142 					if ((IsASpace(sc.chPrev) || sc.chPrev == '/' || sc.chPrev == '!') && (!IsASpace(sc.chNext))) {
143 						styleBeforeDCKeyword = bEnableCode ? SCE_FS_COMMENTLINEDOC : SCE_FS_COMMENTLINEDOC_C;
144 						sc.SetState(SCE_FS_COMMENTDOCKEYWORD);
145 					}
146 				}
147 				break;
148 			case SCE_FS_COMMENTDOCKEYWORD:
149 				if ((styleBeforeDCKeyword == SCE_FS_COMMENTDOC || styleBeforeDCKeyword == SCE_FS_COMMENTDOC_C) &&
150 						sc.Match('*', '/')) {
151 					sc.ChangeState(SCE_FS_COMMENTDOCKEYWORDERROR);
152 					sc.Forward();
153 					sc.ForwardSetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
154 				} else if (!setDoxygen.Contains(sc.ch)) {
155 					char s[64];
156 					sc.GetCurrentLowered(s, sizeof(s));
157 					if (!IsASpace(sc.ch) || !keywords5.InList(s + 1)) {
158 						sc.ChangeState(SCE_FS_COMMENTDOCKEYWORDERROR);
159 					}
160 					sc.SetState(styleBeforeDCKeyword);
161 				}
162 				break;
163 			case SCE_FS_PREPROCESSOR:
164 			case SCE_FS_PREPROCESSOR_C:
165 				if (sc.atLineEnd) {
166 					if (!(sc.chPrev == ';' || sc.GetRelative(-2) == ';')) {
167 						sc.SetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
168 					}
169 				} else if (stylingWithinPreprocessor) {
170 					if (IsASpaceOrTab(sc.ch)) {
171 						sc.SetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
172 					}
173 				} else if (sc.Match('/', '*') || sc.Match('/', '/') || sc.Match('&', '&')) {
174 					sc.SetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
175 				}
176 				break;
177 			case SCE_FS_DISABLEDCODE:
178 				if (sc.ch == '#' && visibleChars == 0) {
179 					sc.SetState(bEnableCode ? SCE_FS_PREPROCESSOR : SCE_FS_PREPROCESSOR_C);
180 					do {	// Skip whitespace between # and preprocessor word
181 						sc.Forward();
182 					} while (IsASpaceOrTab(sc.ch) && sc.More());
183 					if (sc.MatchIgnoreCase("pragma")) {
184 						sc.Forward(6);
185 						do {	// Skip more whitespace until keyword
186 							sc.Forward();
187 						} while (IsASpaceOrTab(sc.ch) && sc.More());
188 						if (sc.MatchIgnoreCase("enddump") || sc.MatchIgnoreCase("__endtext")) {
189 							bEnableCode = true;
190 							sc.SetState(SCE_FS_DISABLEDCODE);
191 							sc.Forward(sc.ch == '_' ? 8 : 6);
192 							sc.ForwardSetState(SCE_FS_DEFAULT);
193 						} else {
194 							sc.ChangeState(SCE_FS_DISABLEDCODE);
195 						}
196 					} else {
197 						sc.ChangeState(SCE_FS_DISABLEDCODE);
198 					}
199 				}
200 				break;
201 			case SCE_FS_DATE:
202 				if (sc.ch == '}') {
203 					sc.ForwardSetState(SCE_FS_DEFAULT);
204 				} else if (sc.atLineEnd) {
205 					sc.ChangeState(SCE_FS_STRINGEOL);
206 				}
207 		}
208 
209 		// Determine if a new state should be entered.
210 		if (sc.state == SCE_FS_DEFAULT || sc.state == SCE_FS_DEFAULT_C) {
211 			if (bEnableCode &&
212 					(sc.MatchIgnoreCase(".and.") || sc.MatchIgnoreCase(".not."))) {
213 				sc.SetState(SCE_FS_WORDOPERATOR);
214 				sc.Forward(4);
215 			} else if (bEnableCode && sc.MatchIgnoreCase(".or.")) {
216 				sc.SetState(SCE_FS_WORDOPERATOR);
217 				sc.Forward(3);
218 			} else if (bEnableCode &&
219 					(sc.MatchIgnoreCase(".t.") || sc.MatchIgnoreCase(".f.") ||
220 					(!IsAWordChar(sc.GetRelative(3)) && sc.MatchIgnoreCase("nil")))) {
221 				sc.SetState(SCE_FS_CONSTANT);
222 				sc.Forward(2);
223 			} else if (sc.Match('/', '*')) {
224 				sc.SetState(bEnableCode ? SCE_FS_COMMENTDOC : SCE_FS_COMMENTDOC_C);
225 				sc.Forward();
226 			} else if (bEnableCode && sc.Match('&', '&')) {
227 				sc.SetState(SCE_FS_COMMENTLINE);
228 				sc.Forward();
229 			} else if (sc.Match('/', '/')) {
230 				sc.SetState(bEnableCode ? SCE_FS_COMMENTLINEDOC : SCE_FS_COMMENTLINEDOC_C);
231 				sc.Forward();
232 			} else if (bEnableCode && sc.ch == '*' && visibleChars == 0) {
233 				sc.SetState(SCE_FS_COMMENT);
234 			} else if (sc.ch == '\"' || sc.ch == '\'') {
235 				sc.SetState(bEnableCode ? SCE_FS_STRING : SCE_FS_STRING_C);
236 				closeStringChar = sc.ch;
237 			} else if (closeStringChar == '>' && sc.ch == '<') {
238 				sc.SetState(bEnableCode ? SCE_FS_STRING : SCE_FS_STRING_C);
239 			} else if (sc.ch == '#' && visibleChars == 0) {
240 				sc.SetState(bEnableCode ? SCE_FS_PREPROCESSOR : SCE_FS_PREPROCESSOR_C);
241 				do {	// Skip whitespace between # and preprocessor word
242 					sc.Forward();
243 				} while (IsASpaceOrTab(sc.ch) && sc.More());
244 				if (sc.atLineEnd) {
245 					sc.SetState(bEnableCode ? SCE_FS_DEFAULT : SCE_FS_DEFAULT_C);
246 				} else if (sc.MatchIgnoreCase("include")) {
247 					if (stylingWithinPreprocessor) {
248 						closeStringChar = '>';
249 					}
250 				} else if (sc.MatchIgnoreCase("pragma")) {
251 					sc.Forward(6);
252 					do {	// Skip more whitespace until keyword
253 						sc.Forward();
254 					} while (IsASpaceOrTab(sc.ch) && sc.More());
255 					if (sc.MatchIgnoreCase("begindump") || sc.MatchIgnoreCase("__cstream")) {
256 						bEnableCode = false;
257 						if (stylingWithinPreprocessor) {
258 							sc.SetState(SCE_FS_DISABLEDCODE);
259 							sc.Forward(8);
260 							sc.ForwardSetState(SCE_FS_DEFAULT_C);
261 						} else {
262 							sc.SetState(SCE_FS_DISABLEDCODE);
263 						}
264 					} else if (sc.MatchIgnoreCase("enddump") || sc.MatchIgnoreCase("__endtext")) {
265 						bEnableCode = true;
266 						sc.SetState(SCE_FS_DISABLEDCODE);
267 						sc.Forward(sc.ch == '_' ? 8 : 6);
268 						sc.ForwardSetState(SCE_FS_DEFAULT);
269 					}
270 				}
271 			} else if (bEnableCode && sc.ch == '{') {
272 				Sci_Position p = 0;
273 				int chSeek;
274 				Sci_PositionU endPos(startPos + length);
275 				do {	// Skip whitespace
276 					chSeek = sc.GetRelative(++p);
277 				} while (IsASpaceOrTab(chSeek) && (sc.currentPos + p < endPos));
278 				if (chSeek == '^') {
279 					sc.SetState(SCE_FS_DATE);
280 				} else {
281 					sc.SetState(SCE_FS_OPERATOR);
282 				}
283 			} else if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
284 				sc.SetState(bEnableCode ? SCE_FS_NUMBER : SCE_FS_NUMBER_C);
285 			} else if (IsAWordChar(sc.ch)) {
286 				sc.SetState(bEnableCode ? SCE_FS_IDENTIFIER : SCE_FS_IDENTIFIER_C);
287 			} else if (isoperator(static_cast<char>(sc.ch)) || (bEnableCode && sc.ch == '@')) {
288 				sc.SetState(bEnableCode ? SCE_FS_OPERATOR : SCE_FS_OPERATOR_C);
289 			}
290 		}
291 
292 		if (sc.atLineEnd) {
293 			visibleChars = 0;
294 			closeStringChar = 0;
295 		}
296 		if (!IsASpace(sc.ch)) {
297 			visibleChars++;
298 		}
299 	}
300 	sc.Complete();
301 }
302 
FoldFlagShipDoc(Sci_PositionU startPos,Sci_Position length,int,WordList * [],Accessor & styler)303 static void FoldFlagShipDoc(Sci_PositionU startPos, Sci_Position length, int,
304 									WordList *[], Accessor &styler)
305 {
306 
307 	Sci_Position endPos = startPos + length;
308 
309 	// Backtrack to previous line in case need to fix its fold status
310 	Sci_Position lineCurrent = styler.GetLine(startPos);
311 	if (startPos > 0 && lineCurrent > 0) {
312 			lineCurrent--;
313 			startPos = styler.LineStart(lineCurrent);
314 	}
315 	int spaceFlags = 0;
316 	int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags);
317 	char chNext = styler[startPos];
318 	for (Sci_Position i = startPos; i < endPos; i++) {
319 		char ch = chNext;
320 		chNext = styler.SafeGetCharAt(i + 1);
321 
322 		if ((ch == '\r' && chNext != '\n') || (ch == '\n') || (i == endPos-1)) {
323 			int lev = indentCurrent;
324 			int indentNext = styler.IndentAmount(lineCurrent + 1, &spaceFlags);
325 			if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG)) {
326 				if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext & SC_FOLDLEVELNUMBERMASK)) {
327 					lev |= SC_FOLDLEVELHEADERFLAG;
328 				} else if (indentNext & SC_FOLDLEVELWHITEFLAG) {
329 					int spaceFlags2 = 0;
330 					int indentNext2 = styler.IndentAmount(lineCurrent + 2, &spaceFlags2);
331 					if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext2 & SC_FOLDLEVELNUMBERMASK)) {
332 						lev |= SC_FOLDLEVELHEADERFLAG;
333 					}
334 				}
335 			}
336 			indentCurrent = indentNext;
337 			styler.SetLevel(lineCurrent, lev);
338 			lineCurrent++;
339 		}
340 	}
341 }
342 
343 static const char * const FSWordListDesc[] = {
344 	"Keywords Commands",
345 	"Std Library Functions",
346 	"Procedure, return, exit",
347 	"Class (oop)",
348 	"Doxygen keywords",
349 	0
350 };
351 
352 LexerModule lmFlagShip(SCLEX_FLAGSHIP, ColouriseFlagShipDoc, "flagship", FoldFlagShipDoc, FSWordListDesc);
353