1 // Scintilla source code edit control
2 /** @file LexMSSQL.cxx
3  ** Lexer for MSSQL.
4  **/
5 // By Filip Yaghob <fyaghob@gmail.com>
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 #ifdef SCI_NAMESPACE
27 using namespace Scintilla;
28 #endif
29 
30 #define KW_MSSQL_STATEMENTS         0
31 #define KW_MSSQL_DATA_TYPES         1
32 #define KW_MSSQL_SYSTEM_TABLES      2
33 #define KW_MSSQL_GLOBAL_VARIABLES   3
34 #define KW_MSSQL_FUNCTIONS          4
35 #define KW_MSSQL_STORED_PROCEDURES  5
36 #define KW_MSSQL_OPERATORS          6
37 
isMSSQLOperator(char ch)38 static bool isMSSQLOperator(char ch) {
39 	if (isascii(ch) && isalnum(ch))
40 		return false;
41 	// '.' left out as it is used to make up numbers
42 	if (ch == '%' || ch == '^' || ch == '&' || ch == '*' ||
43         ch == '-' || ch == '+' || ch == '=' || ch == '|' ||
44         ch == '<' || ch == '>' || ch == '/' ||
45         ch == '!' || ch == '~' || ch == '(' || ch == ')' ||
46 		ch == ',')
47 		return true;
48 	return false;
49 }
50 
classifyWordSQL(unsigned int start,unsigned int end,WordList * keywordlists[],Accessor & styler,unsigned int actualState,unsigned int prevState)51 static char classifyWordSQL(unsigned int start,
52                             unsigned int end,
53                             WordList *keywordlists[],
54                             Accessor &styler,
55                             unsigned int actualState,
56 							unsigned int prevState) {
57 	char s[256];
58 	bool wordIsNumber = isdigit(styler[start]) || (styler[start] == '.');
59 
60 	WordList &kwStatements          = *keywordlists[KW_MSSQL_STATEMENTS];
61     WordList &kwDataTypes           = *keywordlists[KW_MSSQL_DATA_TYPES];
62     WordList &kwSystemTables        = *keywordlists[KW_MSSQL_SYSTEM_TABLES];
63     WordList &kwGlobalVariables     = *keywordlists[KW_MSSQL_GLOBAL_VARIABLES];
64     WordList &kwFunctions           = *keywordlists[KW_MSSQL_FUNCTIONS];
65     WordList &kwStoredProcedures    = *keywordlists[KW_MSSQL_STORED_PROCEDURES];
66     WordList &kwOperators           = *keywordlists[KW_MSSQL_OPERATORS];
67 
68 	for (unsigned int i = 0; i < end - start + 1 && i < 128; i++) {
69 		s[i] = static_cast<char>(tolower(styler[start + i]));
70 		s[i + 1] = '\0';
71 	}
72 	char chAttr = SCE_MSSQL_IDENTIFIER;
73 
74 	if (actualState == SCE_MSSQL_GLOBAL_VARIABLE) {
75 
76         if (kwGlobalVariables.InList(&s[2]))
77             chAttr = SCE_MSSQL_GLOBAL_VARIABLE;
78 
79 	} else if (wordIsNumber) {
80 		chAttr = SCE_MSSQL_NUMBER;
81 
82 	} else if (prevState == SCE_MSSQL_DEFAULT_PREF_DATATYPE) {
83 		// Look first in datatypes
84         if (kwDataTypes.InList(s))
85             chAttr = SCE_MSSQL_DATATYPE;
86 		else if (kwOperators.InList(s))
87 			chAttr = SCE_MSSQL_OPERATOR;
88 		else if (kwStatements.InList(s))
89 			chAttr = SCE_MSSQL_STATEMENT;
90 		else if (kwSystemTables.InList(s))
91 			chAttr = SCE_MSSQL_SYSTABLE;
92 		else if (kwFunctions.InList(s))
93             chAttr = SCE_MSSQL_FUNCTION;
94 		else if (kwStoredProcedures.InList(s))
95 			chAttr = SCE_MSSQL_STORED_PROCEDURE;
96 
97 	} else {
98 		if (kwOperators.InList(s))
99 			chAttr = SCE_MSSQL_OPERATOR;
100 		else if (kwStatements.InList(s))
101 			chAttr = SCE_MSSQL_STATEMENT;
102 		else if (kwSystemTables.InList(s))
103 			chAttr = SCE_MSSQL_SYSTABLE;
104 		else if (kwFunctions.InList(s))
105 			chAttr = SCE_MSSQL_FUNCTION;
106 		else if (kwStoredProcedures.InList(s))
107 			chAttr = SCE_MSSQL_STORED_PROCEDURE;
108 		else if (kwDataTypes.InList(s))
109 			chAttr = SCE_MSSQL_DATATYPE;
110 	}
111 
112 	styler.ColourTo(end, chAttr);
113 
114 	return chAttr;
115 }
116 
ColouriseMSSQLDoc(unsigned int startPos,int length,int initStyle,WordList * keywordlists[],Accessor & styler)117 static void ColouriseMSSQLDoc(unsigned int startPos, int length,
118                               int initStyle, WordList *keywordlists[], Accessor &styler) {
119 
120 
121 	styler.StartAt(startPos);
122 
123 	bool fold = styler.GetPropertyInt("fold") != 0;
124 	int lineCurrent = styler.GetLine(startPos);
125 	int spaceFlags = 0;
126 
127 	int state = initStyle;
128 	int prevState = initStyle;
129 	char chPrev = ' ';
130 	char chNext = styler[startPos];
131 	styler.StartSegment(startPos);
132 	unsigned int lengthDoc = startPos + length;
133 	for (unsigned int i = startPos; i < lengthDoc; i++) {
134 		char ch = chNext;
135 		chNext = styler.SafeGetCharAt(i + 1);
136 
137 		if ((ch == '\r' && chNext != '\n') || (ch == '\n')) {
138 			int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags);
139 			int lev = indentCurrent;
140 			if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG)) {
141 				// Only non whitespace lines can be headers
142 				int indentNext = styler.IndentAmount(lineCurrent + 1, &spaceFlags);
143 				if (indentCurrent < (indentNext & ~SC_FOLDLEVELWHITEFLAG)) {
144 					lev |= SC_FOLDLEVELHEADERFLAG;
145 				}
146 			}
147 			if (fold) {
148 				styler.SetLevel(lineCurrent, lev);
149 			}
150 		}
151 
152 		if (styler.IsLeadByte(ch)) {
153 			chNext = styler.SafeGetCharAt(i + 2);
154 			chPrev = ' ';
155 			i += 1;
156 			continue;
157 		}
158 
159 		// When the last char isn't part of the state (have to deal with it too)...
160 		if ( (state == SCE_MSSQL_IDENTIFIER) ||
161                     (state == SCE_MSSQL_STORED_PROCEDURE) ||
162                     (state == SCE_MSSQL_DATATYPE) ||
163                     //~ (state == SCE_MSSQL_COLUMN_NAME) ||
164                     (state == SCE_MSSQL_FUNCTION) ||
165                     //~ (state == SCE_MSSQL_GLOBAL_VARIABLE) ||
166                     (state == SCE_MSSQL_VARIABLE)) {
167 			if (!iswordchar(ch)) {
168 				int stateTmp;
169 
170                 if ((state == SCE_MSSQL_VARIABLE) || (state == SCE_MSSQL_COLUMN_NAME)) {
171                     styler.ColourTo(i - 1, state);
172 					stateTmp = state;
173                 } else
174                     stateTmp = classifyWordSQL(styler.GetStartSegment(), i - 1, keywordlists, styler, state, prevState);
175 
176 				prevState = state;
177 
178 				if (stateTmp == SCE_MSSQL_IDENTIFIER || stateTmp == SCE_MSSQL_VARIABLE)
179 					state = SCE_MSSQL_DEFAULT_PREF_DATATYPE;
180 				else
181 					state = SCE_MSSQL_DEFAULT;
182 			}
183 		} else if (state == SCE_MSSQL_LINE_COMMENT) {
184 			if (ch == '\r' || ch == '\n') {
185 				styler.ColourTo(i - 1, state);
186 				prevState = state;
187 				state = SCE_MSSQL_DEFAULT;
188 			}
189 		} else if (state == SCE_MSSQL_GLOBAL_VARIABLE) {
190 			if ((ch != '@') && !iswordchar(ch)) {
191 				classifyWordSQL(styler.GetStartSegment(), i - 1, keywordlists, styler, state, prevState);
192 				prevState = state;
193 				state = SCE_MSSQL_DEFAULT;
194 			}
195 		}
196 
197 		// If is the default or one of the above succeeded
198 		if (state == SCE_MSSQL_DEFAULT || state == SCE_MSSQL_DEFAULT_PREF_DATATYPE) {
199 			if (iswordstart(ch)) {
200 				styler.ColourTo(i - 1, SCE_MSSQL_DEFAULT);
201 				prevState = state;
202 				state = SCE_MSSQL_IDENTIFIER;
203 			} else if (ch == '/' && chNext == '*') {
204 				styler.ColourTo(i - 1, SCE_MSSQL_DEFAULT);
205 				prevState = state;
206 				state = SCE_MSSQL_COMMENT;
207 			} else if (ch == '-' && chNext == '-') {
208 				styler.ColourTo(i - 1, SCE_MSSQL_DEFAULT);
209 				prevState = state;
210 				state = SCE_MSSQL_LINE_COMMENT;
211 			} else if (ch == '\'') {
212 				styler.ColourTo(i - 1, SCE_MSSQL_DEFAULT);
213 				prevState = state;
214 				state = SCE_MSSQL_STRING;
215 			} else if (ch == '"') {
216 				styler.ColourTo(i - 1, SCE_MSSQL_DEFAULT);
217 				prevState = state;
218 				state = SCE_MSSQL_COLUMN_NAME;
219 			} else if (ch == '[') {
220 				styler.ColourTo(i - 1, SCE_MSSQL_DEFAULT);
221 				prevState = state;
222 				state = SCE_MSSQL_COLUMN_NAME_2;
223 			} else if (isMSSQLOperator(ch)) {
224 				styler.ColourTo(i - 1, SCE_MSSQL_DEFAULT);
225 				styler.ColourTo(i, SCE_MSSQL_OPERATOR);
226                 //~ style = SCE_MSSQL_DEFAULT;
227 				prevState = state;
228 				state = SCE_MSSQL_DEFAULT;
229 			} else if (ch == '@') {
230                 styler.ColourTo(i - 1, SCE_MSSQL_DEFAULT);
231 				prevState = state;
232                 if (chNext == '@') {
233                     state = SCE_MSSQL_GLOBAL_VARIABLE;
234 //                    i += 2;
235                 } else
236                     state = SCE_MSSQL_VARIABLE;
237             }
238 
239 
240 		// When the last char is part of the state...
241 		} else if (state == SCE_MSSQL_COMMENT) {
242 				if (ch == '/' && chPrev == '*') {
243 					if (((i > (styler.GetStartSegment() + 2)) || ((initStyle == SCE_MSSQL_COMMENT) &&
244 					    (styler.GetStartSegment() == startPos)))) {
245 						styler.ColourTo(i, state);
246 						//~ state = SCE_MSSQL_COMMENT;
247 					prevState = state;
248                         state = SCE_MSSQL_DEFAULT;
249 					}
250 				}
251 			} else if (state == SCE_MSSQL_STRING) {
252 				if (ch == '\'') {
253 					if ( chNext == '\'' ) {
254 						i++;
255 					ch = chNext;
256 					chNext = styler.SafeGetCharAt(i + 1);
257 					} else {
258 						styler.ColourTo(i, state);
259 					prevState = state;
260 						state = SCE_MSSQL_DEFAULT;
261 					//i++;
262 					}
263 				//ch = chNext;
264 				//chNext = styler.SafeGetCharAt(i + 1);
265 				}
266 			} else if (state == SCE_MSSQL_COLUMN_NAME) {
267 				if (ch == '"') {
268 					if (chNext == '"') {
269 						i++;
270 					ch = chNext;
271 					chNext = styler.SafeGetCharAt(i + 1);
272 				} else {
273                     styler.ColourTo(i, state);
274 					prevState = state;
275 					state = SCE_MSSQL_DEFAULT_PREF_DATATYPE;
276 					//i++;
277                 }
278                 }
279 		} else if (state == SCE_MSSQL_COLUMN_NAME_2) {
280 			if (ch == ']') {
281                 styler.ColourTo(i, state);
282 				prevState = state;
283                 state = SCE_MSSQL_DEFAULT_PREF_DATATYPE;
284                 //i++;
285 			}
286 		}
287 
288 		chPrev = ch;
289 	}
290 	styler.ColourTo(lengthDoc - 1, state);
291 }
292 
FoldMSSQLDoc(unsigned int startPos,int length,int,WordList * [],Accessor & styler)293 static void FoldMSSQLDoc(unsigned int startPos, int length, int, WordList *[], Accessor &styler) {
294 	bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
295 	bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
296 	unsigned int endPos = startPos + length;
297 	int visibleChars = 0;
298 	int lineCurrent = styler.GetLine(startPos);
299 	int levelPrev = styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK;
300 	int levelCurrent = levelPrev;
301 	char chNext = styler[startPos];
302 	bool inComment = (styler.StyleAt(startPos-1) == SCE_MSSQL_COMMENT);
303     char s[10];
304 	for (unsigned int i = startPos; i < endPos; i++) {
305 		char ch = chNext;
306 		chNext = styler.SafeGetCharAt(i + 1);
307 		int style = styler.StyleAt(i);
308 		bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
309         // Comment folding
310 		if (foldComment) {
311 			if (!inComment && (style == SCE_MSSQL_COMMENT))
312 				levelCurrent++;
313 			else if (inComment && (style != SCE_MSSQL_COMMENT))
314 				levelCurrent--;
315 			inComment = (style == SCE_MSSQL_COMMENT);
316 		}
317         if (style == SCE_MSSQL_STATEMENT) {
318             // Folding between begin or case and end
319             if (ch == 'b' || ch == 'B' || ch == 'c' || ch == 'C' || ch == 'e' || ch == 'E') {
320                 for (unsigned int j = 0; j < 5; j++) {
321 					if (!iswordchar(styler[i + j])) {
322 						break;
323 					}
324 					s[j] = static_cast<char>(tolower(styler[i + j]));
325 					s[j + 1] = '\0';
326                 }
327 				if ((strcmp(s, "begin") == 0) || (strcmp(s, "case") == 0)) {
328 					levelCurrent++;
329 				}
330 				if (strcmp(s, "end") == 0) {
331 					levelCurrent--;
332 				}
333             }
334         }
335 		if (atEOL) {
336 			int lev = levelPrev;
337 			if (visibleChars == 0 && foldCompact)
338 				lev |= SC_FOLDLEVELWHITEFLAG;
339 			if ((levelCurrent > levelPrev) && (visibleChars > 0))
340 				lev |= SC_FOLDLEVELHEADERFLAG;
341 			if (lev != styler.LevelAt(lineCurrent)) {
342 				styler.SetLevel(lineCurrent, lev);
343 			}
344 			lineCurrent++;
345 			levelPrev = levelCurrent;
346 			visibleChars = 0;
347 		}
348 		if (!isspacechar(ch))
349 			visibleChars++;
350 	}
351 	// Fill in the real level of the next line, keeping the current flags as they will be filled in later
352 	int flagsNext = styler.LevelAt(lineCurrent) & ~SC_FOLDLEVELNUMBERMASK;
353 	styler.SetLevel(lineCurrent, levelPrev | flagsNext);
354 }
355 
356 static const char * const sqlWordListDesc[] = {
357 	"Statements",
358     "Data Types",
359     "System tables",
360     "Global variables",
361     "Functions",
362     "System Stored Procedures",
363     "Operators",
364 	0,
365 };
366 
367 LexerModule lmMSSQL(SCLEX_MSSQL, ColouriseMSSQLDoc, "mssql", FoldMSSQLDoc, sqlWordListDesc);
368