1 // Scintilla source code edit control
2 /** @file LexVB.cxx
3  ** Lexer for Visual Basic and VBScript.
4  **/
5 // Copyright 1998-2005 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 #include "ILexer.h"
16 #include "Scintilla.h"
17 #include "SciLexer.h"
18 
19 #include "WordList.h"
20 #include "LexAccessor.h"
21 #include "Accessor.h"
22 #include "StyleContext.h"
23 #include "CharacterSet.h"
24 #include "LexerModule.h"
25 
26 using namespace Scintilla;
27 
28 // Internal state, highlighted as number
29 #define SCE_B_FILENUMBER SCE_B_DEFAULT+100
30 
31 
IsVBComment(Accessor & styler,Sci_Position pos,Sci_Position len)32 static bool IsVBComment(Accessor &styler, Sci_Position pos, Sci_Position len) {
33 	return len > 0 && styler[pos] == '\'';
34 }
35 
IsTypeCharacter(int ch)36 static inline bool IsTypeCharacter(int ch) {
37 	return ch == '%' || ch == '&' || ch == '@' || ch == '!' || ch == '#' || ch == '$';
38 }
39 
40 // Extended to accept accented characters
IsAWordChar(int ch)41 static inline bool IsAWordChar(int ch) {
42 	return ch >= 0x80 ||
43 	       (isalnum(ch) || ch == '.' || ch == '_');
44 }
45 
IsAWordStart(int ch)46 static inline bool IsAWordStart(int ch) {
47 	return ch >= 0x80 ||
48 	       (isalpha(ch) || ch == '_');
49 }
50 
IsANumberChar(int ch)51 static inline bool IsANumberChar(int ch) {
52 	// Not exactly following number definition (several dots are seen as OK, etc.)
53 	// but probably enough in most cases.
54 	return (ch < 0x80) &&
55 	        (isdigit(ch) || toupper(ch) == 'E' ||
56              ch == '.' || ch == '-' || ch == '+');
57 }
58 
ColouriseVBDoc(Sci_PositionU startPos,Sci_Position length,int initStyle,WordList * keywordlists[],Accessor & styler,bool vbScriptSyntax)59 static void ColouriseVBDoc(Sci_PositionU startPos, Sci_Position length, int initStyle,
60                            WordList *keywordlists[], Accessor &styler, bool vbScriptSyntax) {
61 
62 	WordList &keywords = *keywordlists[0];
63 	WordList &keywords2 = *keywordlists[1];
64 	WordList &keywords3 = *keywordlists[2];
65 	WordList &keywords4 = *keywordlists[3];
66 
67 	styler.StartAt(startPos);
68 
69 	int visibleChars = 0;
70 	int fileNbDigits = 0;
71 
72 	// Do not leak onto next line
73 	if (initStyle == SCE_B_STRINGEOL || initStyle == SCE_B_COMMENT || initStyle == SCE_B_PREPROCESSOR) {
74 		initStyle = SCE_B_DEFAULT;
75 	}
76 
77 	StyleContext sc(startPos, length, initStyle, styler);
78 
79 	for (; sc.More(); sc.Forward()) {
80 
81 		if (sc.state == SCE_B_OPERATOR) {
82 			sc.SetState(SCE_B_DEFAULT);
83 		} else if (sc.state == SCE_B_IDENTIFIER) {
84 			if (!IsAWordChar(sc.ch)) {
85 				// In Basic (except VBScript), a variable name or a function name
86 				// can end with a special character indicating the type of the value
87 				// held or returned.
88 				bool skipType = false;
89 				if (!vbScriptSyntax && IsTypeCharacter(sc.ch)) {
90 					sc.Forward();	// Skip it
91 					skipType = true;
92 				}
93 				if (sc.ch == ']') {
94 					sc.Forward();
95 				}
96 				char s[100];
97 				sc.GetCurrentLowered(s, sizeof(s));
98 				if (skipType) {
99 					s[strlen(s) - 1] = '\0';
100 				}
101 				if (strcmp(s, "rem") == 0) {
102 					sc.ChangeState(SCE_B_COMMENT);
103 				} else {
104 					if (keywords.InList(s)) {
105 						sc.ChangeState(SCE_B_KEYWORD);
106 					} else if (keywords2.InList(s)) {
107 						sc.ChangeState(SCE_B_KEYWORD2);
108 					} else if (keywords3.InList(s)) {
109 						sc.ChangeState(SCE_B_KEYWORD3);
110 					} else if (keywords4.InList(s)) {
111 						sc.ChangeState(SCE_B_KEYWORD4);
112 					}	// Else, it is really an identifier...
113 					sc.SetState(SCE_B_DEFAULT);
114 				}
115 			}
116 		} else if (sc.state == SCE_B_NUMBER) {
117 			// We stop the number definition on non-numerical non-dot non-eE non-sign char
118 			// Also accepts A-F for hex. numbers
119 			if (!IsANumberChar(sc.ch) && !(tolower(sc.ch) >= 'a' && tolower(sc.ch) <= 'f')) {
120 				sc.SetState(SCE_B_DEFAULT);
121 			}
122 		} else if (sc.state == SCE_B_STRING) {
123 			// VB doubles quotes to preserve them, so just end this string
124 			// state now as a following quote will start again
125 			if (sc.ch == '\"') {
126 				if (sc.chNext == '\"') {
127 					sc.Forward();
128 				} else {
129 					if (tolower(sc.chNext) == 'c') {
130 						sc.Forward();
131 					}
132 					sc.ForwardSetState(SCE_B_DEFAULT);
133 				}
134 			} else if (sc.atLineEnd) {
135 				visibleChars = 0;
136 				sc.ChangeState(SCE_B_STRINGEOL);
137 				sc.ForwardSetState(SCE_B_DEFAULT);
138 			}
139 		} else if (sc.state == SCE_B_COMMENT) {
140 			if (sc.atLineEnd) {
141 				visibleChars = 0;
142 				sc.ForwardSetState(SCE_B_DEFAULT);
143 			}
144 		} else if (sc.state == SCE_B_PREPROCESSOR) {
145 			if (sc.atLineEnd) {
146 				visibleChars = 0;
147 				sc.ForwardSetState(SCE_B_DEFAULT);
148 			}
149 		} else if (sc.state == SCE_B_FILENUMBER) {
150 			if (IsADigit(sc.ch)) {
151 				fileNbDigits++;
152 				if (fileNbDigits > 3) {
153 					sc.ChangeState(SCE_B_DATE);
154 				}
155 			} else if (sc.ch == '\r' || sc.ch == '\n' || sc.ch == ',') {
156 				// Regular uses: Close #1; Put #1, ...; Get #1, ... etc.
157 				// Too bad if date is format #27, Oct, 2003# or something like that...
158 				// Use regular number state
159 				sc.ChangeState(SCE_B_NUMBER);
160 				sc.SetState(SCE_B_DEFAULT);
161 			} else if (sc.ch == '#') {
162 				sc.ChangeState(SCE_B_DATE);
163 				sc.ForwardSetState(SCE_B_DEFAULT);
164 			} else {
165 				sc.ChangeState(SCE_B_DATE);
166 			}
167 			if (sc.state != SCE_B_FILENUMBER) {
168 				fileNbDigits = 0;
169 			}
170 		} else if (sc.state == SCE_B_DATE) {
171 			if (sc.atLineEnd) {
172 				visibleChars = 0;
173 				sc.ChangeState(SCE_B_STRINGEOL);
174 				sc.ForwardSetState(SCE_B_DEFAULT);
175 			} else if (sc.ch == '#') {
176 				sc.ForwardSetState(SCE_B_DEFAULT);
177 			}
178 		}
179 
180 		if (sc.state == SCE_B_DEFAULT) {
181 			if (sc.ch == '\'') {
182 				sc.SetState(SCE_B_COMMENT);
183 			} else if (sc.ch == '\"') {
184 				sc.SetState(SCE_B_STRING);
185 			} else if (sc.ch == '#' && visibleChars == 0) {
186 				// Preprocessor commands are alone on their line
187 				sc.SetState(SCE_B_PREPROCESSOR);
188 			} else if (sc.ch == '#') {
189 				// It can be a date literal, ending with #, or a file number, from 1 to 511
190 				// The date literal depends on the locale, so anything can go between #'s.
191 				// Can be #January 1, 1993# or #1 Jan 93# or #05/11/2003#, etc.
192 				// So we set the FILENUMBER state, and switch to DATE if it isn't a file number
193 				sc.SetState(SCE_B_FILENUMBER);
194 			} else if (sc.ch == '&' && tolower(sc.chNext) == 'h') {
195 				// Hexadecimal number
196 				sc.SetState(SCE_B_NUMBER);
197 				sc.Forward();
198 			} else if (sc.ch == '&' && tolower(sc.chNext) == 'o') {
199 				// Octal number
200 				sc.SetState(SCE_B_NUMBER);
201 				sc.Forward();
202 			} else if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
203 				sc.SetState(SCE_B_NUMBER);
204 			} else if (IsAWordStart(sc.ch) || (sc.ch == '[')) {
205 				sc.SetState(SCE_B_IDENTIFIER);
206 			} else if (isoperator(static_cast<char>(sc.ch)) || (sc.ch == '\\')) {	// Integer division
207 				sc.SetState(SCE_B_OPERATOR);
208 			}
209 		}
210 
211 		if (sc.atLineEnd) {
212 			visibleChars = 0;
213 		}
214 		if (!IsASpace(sc.ch)) {
215 			visibleChars++;
216 		}
217 	}
218 
219 	if (sc.state == SCE_B_IDENTIFIER && !IsAWordChar(sc.ch)) {
220 		// In Basic (except VBScript), a variable name or a function name
221 		// can end with a special character indicating the type of the value
222 		// held or returned.
223 		bool skipType = false;
224 		if (!vbScriptSyntax && IsTypeCharacter(sc.ch)) {
225 			sc.Forward();	// Skip it
226 			skipType = true;
227 		}
228 		if (sc.ch == ']') {
229 			sc.Forward();
230 		}
231 		char s[100];
232 		sc.GetCurrentLowered(s, sizeof(s));
233 		if (skipType) {
234 			s[strlen(s) - 1] = '\0';
235 		}
236 		if (strcmp(s, "rem") == 0) {
237 			sc.ChangeState(SCE_B_COMMENT);
238 		} else {
239 			if (keywords.InList(s)) {
240 				sc.ChangeState(SCE_B_KEYWORD);
241 			} else if (keywords2.InList(s)) {
242 				sc.ChangeState(SCE_B_KEYWORD2);
243 			} else if (keywords3.InList(s)) {
244 				sc.ChangeState(SCE_B_KEYWORD3);
245 			} else if (keywords4.InList(s)) {
246 				sc.ChangeState(SCE_B_KEYWORD4);
247 			}	// Else, it is really an identifier...
248 			sc.SetState(SCE_B_DEFAULT);
249 		}
250 	}
251 
252 	sc.Complete();
253 }
254 
FoldVBDoc(Sci_PositionU startPos,Sci_Position length,int,WordList * [],Accessor & styler)255 static void FoldVBDoc(Sci_PositionU startPos, Sci_Position length, int,
256 						   WordList *[], Accessor &styler) {
257 	Sci_Position endPos = startPos + length;
258 
259 	// Backtrack to previous line in case need to fix its fold status
260 	Sci_Position lineCurrent = styler.GetLine(startPos);
261 	if (startPos > 0) {
262 		if (lineCurrent > 0) {
263 			lineCurrent--;
264 			startPos = styler.LineStart(lineCurrent);
265 		}
266 	}
267 	int spaceFlags = 0;
268 	int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, IsVBComment);
269 	char chNext = styler[startPos];
270 	for (Sci_Position i = startPos; i < endPos; i++) {
271 		char ch = chNext;
272 		chNext = styler.SafeGetCharAt(i + 1);
273 
274 		if ((ch == '\r' && chNext != '\n') || (ch == '\n') || (i == endPos)) {
275 			int lev = indentCurrent;
276 			int indentNext = styler.IndentAmount(lineCurrent + 1, &spaceFlags, IsVBComment);
277 			if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG)) {
278 				// Only non whitespace lines can be headers
279 				if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext & SC_FOLDLEVELNUMBERMASK)) {
280 					lev |= SC_FOLDLEVELHEADERFLAG;
281 				} else if (indentNext & SC_FOLDLEVELWHITEFLAG) {
282 					// Line after is blank so check the next - maybe should continue further?
283 					int spaceFlags2 = 0;
284 					int indentNext2 = styler.IndentAmount(lineCurrent + 2, &spaceFlags2, IsVBComment);
285 					if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext2 & SC_FOLDLEVELNUMBERMASK)) {
286 						lev |= SC_FOLDLEVELHEADERFLAG;
287 					}
288 				}
289 			}
290 			indentCurrent = indentNext;
291 			styler.SetLevel(lineCurrent, lev);
292 			lineCurrent++;
293 		}
294 	}
295 }
296 
ColouriseVBNetDoc(Sci_PositionU startPos,Sci_Position length,int initStyle,WordList * keywordlists[],Accessor & styler)297 static void ColouriseVBNetDoc(Sci_PositionU startPos, Sci_Position length, int initStyle,
298                            WordList *keywordlists[], Accessor &styler) {
299 	ColouriseVBDoc(startPos, length, initStyle, keywordlists, styler, false);
300 }
301 
ColouriseVBScriptDoc(Sci_PositionU startPos,Sci_Position length,int initStyle,WordList * keywordlists[],Accessor & styler)302 static void ColouriseVBScriptDoc(Sci_PositionU startPos, Sci_Position length, int initStyle,
303                            WordList *keywordlists[], Accessor &styler) {
304 	ColouriseVBDoc(startPos, length, initStyle, keywordlists, styler, true);
305 }
306 
307 static const char * const vbWordListDesc[] = {
308 	"Keywords",
309 	"user1",
310 	"user2",
311 	"user3",
312 	0
313 };
314 
315 LexerModule lmVB(SCLEX_VB, ColouriseVBNetDoc, "vb", FoldVBDoc, vbWordListDesc);
316 LexerModule lmVBScript(SCLEX_VBSCRIPT, ColouriseVBScriptDoc, "vbscript", FoldVBDoc, vbWordListDesc);
317 
318