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