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