r5116 | eht16 | 2010-08-05 22:13:47 +0100 (Thu, 05 Aug 2010) | 3 lines
[geany-mirror.git] / scintilla / LexSQL.cxx
blob7a4335bd2ca618c94ff888bcdecef6c867488a7c
1 // Scintilla source code edit control
2 /** @file LexSQL.cxx
3 ** Lexer for SQL, including PL/SQL and SQL*Plus.
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.
8 #include <stdlib.h>
9 #include <string.h>
10 #include <ctype.h>
11 #include <stdio.h>
12 #include <stdarg.h>
14 #include "Platform.h"
16 #include "PropSet.h"
17 #include "Accessor.h"
18 #include "StyleContext.h"
19 #include "KeyWords.h"
20 #include "Scintilla.h"
21 #include "SciLexer.h"
23 #ifdef SCI_NAMESPACE
24 using namespace Scintilla;
25 #endif
27 static inline bool IsAWordChar(int ch) {
28 return (ch < 0x80) && (isalnum(ch) || ch == '_');
31 static inline bool IsAWordStart(int ch) {
32 return (ch < 0x80) && (isalpha(ch) || ch == '_');
35 static inline bool IsADoxygenChar(int ch) {
36 return (islower(ch) || ch == '$' || ch == '@' ||
37 ch == '\\' || ch == '&' || ch == '<' ||
38 ch == '>' || ch == '#' || ch == '{' ||
39 ch == '}' || ch == '[' || ch == ']');
42 static inline bool IsANumberChar(int ch) {
43 // Not exactly following number definition (several dots are seen as OK, etc.)
44 // but probably enough in most cases.
45 return (ch < 0x80) &&
46 (isdigit(ch) || toupper(ch) == 'E' ||
47 ch == '.' || ch == '-' || ch == '+');
50 static void ColouriseSQLDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[],
51 Accessor &styler) {
53 WordList &keywords1 = *keywordlists[0];
54 WordList &keywords2 = *keywordlists[1];
55 WordList &kw_pldoc = *keywordlists[2];
56 WordList &kw_sqlplus = *keywordlists[3];
57 WordList &kw_user1 = *keywordlists[4];
58 WordList &kw_user2 = *keywordlists[5];
59 WordList &kw_user3 = *keywordlists[6];
60 WordList &kw_user4 = *keywordlists[7];
62 StyleContext sc(startPos, length, initStyle, styler);
64 // property sql.backslash.escapes
65 // Enables backslash as an escape character in SQL.
66 bool sqlBackslashEscapes = styler.GetPropertyInt("sql.backslash.escapes", 0) != 0;
68 bool sqlBackticksIdentifier = styler.GetPropertyInt("lexer.sql.backticks.identifier", 0) != 0;
69 int styleBeforeDCKeyword = SCE_SQL_DEFAULT;
70 for (; sc.More(); sc.Forward()) {
71 // Determine if the current state should terminate.
72 switch (sc.state) {
73 case SCE_SQL_OPERATOR:
74 sc.SetState(SCE_SQL_DEFAULT);
75 break;
76 case SCE_SQL_NUMBER:
77 // We stop the number definition on non-numerical non-dot non-eE non-sign char
78 if (!IsANumberChar(sc.ch)) {
79 sc.SetState(SCE_SQL_DEFAULT);
81 break;
82 case SCE_SQL_IDENTIFIER:
83 if (!IsAWordChar(sc.ch)) {
84 int nextState = SCE_SQL_DEFAULT;
85 char s[1000];
86 sc.GetCurrentLowered(s, sizeof(s));
87 if (keywords1.InList(s)) {
88 sc.ChangeState(SCE_SQL_WORD);
89 } else if (keywords2.InList(s)) {
90 sc.ChangeState(SCE_SQL_WORD2);
91 } else if (kw_sqlplus.InListAbbreviated(s, '~')) {
92 sc.ChangeState(SCE_SQL_SQLPLUS);
93 if (strncmp(s, "rem", 3) == 0) {
94 nextState = SCE_SQL_SQLPLUS_COMMENT;
95 } else if (strncmp(s, "pro", 3) == 0) {
96 nextState = SCE_SQL_SQLPLUS_PROMPT;
98 } else if (kw_user1.InList(s)) {
99 sc.ChangeState(SCE_SQL_USER1);
100 } else if (kw_user2.InList(s)) {
101 sc.ChangeState(SCE_SQL_USER2);
102 } else if (kw_user3.InList(s)) {
103 sc.ChangeState(SCE_SQL_USER3);
104 } else if (kw_user4.InList(s)) {
105 sc.ChangeState(SCE_SQL_USER4);
107 sc.SetState(nextState);
109 break;
110 case SCE_SQL_QUOTEDIDENTIFIER:
111 if (sc.ch == 0x60) {
112 if (sc.chNext == 0x60) {
113 sc.Forward(); // Ignore it
114 } else {
115 sc.ForwardSetState(SCE_SQL_DEFAULT);
118 break;
119 case SCE_SQL_COMMENT:
120 if (sc.Match('*', '/')) {
121 sc.Forward();
122 sc.ForwardSetState(SCE_SQL_DEFAULT);
124 break;
125 case SCE_SQL_COMMENTDOC:
126 if (sc.Match('*', '/')) {
127 sc.Forward();
128 sc.ForwardSetState(SCE_SQL_DEFAULT);
129 } else if (sc.ch == '@' || sc.ch == '\\') { // Doxygen support
130 // Verify that we have the conditions to mark a comment-doc-keyword
131 if ((IsASpace(sc.chPrev) || sc.chPrev == '*') && (!IsASpace(sc.chNext))) {
132 styleBeforeDCKeyword = SCE_SQL_COMMENTDOC;
133 sc.SetState(SCE_SQL_COMMENTDOCKEYWORD);
136 break;
137 case SCE_SQL_COMMENTLINE:
138 case SCE_SQL_COMMENTLINEDOC:
139 case SCE_SQL_SQLPLUS_COMMENT:
140 case SCE_SQL_SQLPLUS_PROMPT:
141 if (sc.atLineStart) {
142 sc.SetState(SCE_SQL_DEFAULT);
144 break;
145 case SCE_SQL_COMMENTDOCKEYWORD:
146 if ((styleBeforeDCKeyword == SCE_SQL_COMMENTDOC) && sc.Match('*', '/')) {
147 sc.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR);
148 sc.Forward();
149 sc.ForwardSetState(SCE_SQL_DEFAULT);
150 } else if (!IsADoxygenChar(sc.ch)) {
151 char s[100];
152 sc.GetCurrentLowered(s, sizeof(s));
153 if (!isspace(sc.ch) || !kw_pldoc.InList(s + 1)) {
154 sc.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR);
156 sc.SetState(styleBeforeDCKeyword);
158 break;
159 case SCE_SQL_CHARACTER:
160 if (sqlBackslashEscapes && sc.ch == '\\') {
161 sc.Forward();
162 } else if (sc.ch == '\'') {
163 if (sc.chNext == '\"') {
164 sc.Forward();
165 } else {
166 sc.ForwardSetState(SCE_SQL_DEFAULT);
169 break;
170 case SCE_SQL_STRING:
171 if (sc.ch == '\\') {
172 // Escape sequence
173 sc.Forward();
174 } else if (sc.ch == '\"') {
175 if (sc.chNext == '\"') {
176 sc.Forward();
177 } else {
178 sc.ForwardSetState(SCE_SQL_DEFAULT);
181 break;
184 // Determine if a new state should be entered.
185 if (sc.state == SCE_SQL_DEFAULT) {
186 if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
187 sc.SetState(SCE_SQL_NUMBER);
188 } else if (IsAWordStart(sc.ch)) {
189 sc.SetState(SCE_SQL_IDENTIFIER);
190 } else if (sc.ch == 0x60 && sqlBackticksIdentifier) {
191 sc.SetState(SCE_SQL_QUOTEDIDENTIFIER);
192 } else if (sc.Match('/', '*')) {
193 if (sc.Match("/**") || sc.Match("/*!")) { // Support of Doxygen doc. style
194 sc.SetState(SCE_SQL_COMMENTDOC);
195 } else {
196 sc.SetState(SCE_SQL_COMMENT);
198 sc.Forward(); // Eat the * so it isn't used for the end of the comment
199 } else if (sc.Match('-', '-')) {
200 // MySQL requires a space or control char after --
201 // http://dev.mysql.com/doc/mysql/en/ansi-diff-comments.html
202 // Perhaps we should enforce that with proper property:
203 //~ } else if (sc.Match("-- ")) {
204 sc.SetState(SCE_SQL_COMMENTLINE);
205 } else if (sc.ch == '#') {
206 sc.SetState(SCE_SQL_COMMENTLINEDOC);
207 } else if (sc.ch == '\'') {
208 sc.SetState(SCE_SQL_CHARACTER);
209 } else if (sc.ch == '\"') {
210 sc.SetState(SCE_SQL_STRING);
211 } else if (isoperator(static_cast<char>(sc.ch))) {
212 sc.SetState(SCE_SQL_OPERATOR);
216 sc.Complete();
219 static bool IsStreamCommentStyle(int style) {
220 return style == SCE_SQL_COMMENT ||
221 style == SCE_SQL_COMMENTDOC ||
222 style == SCE_SQL_COMMENTDOCKEYWORD ||
223 style == SCE_SQL_COMMENTDOCKEYWORDERROR;
226 // Store both the current line's fold level and the next lines in the
227 // level store to make it easy to pick up with each increment.
228 static void FoldSQLDoc(unsigned int startPos, int length, int initStyle,
229 WordList *[], Accessor &styler) {
230 bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
231 bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
232 bool foldOnlyBegin = styler.GetPropertyInt("fold.sql.only.begin", 0) != 0;
234 // property fold.sql.exists
235 // Enables "EXISTS" to end a fold as is started by "IF" in "DROP TABLE IF EXISTS".
236 bool foldSqlExists = styler.GetPropertyInt("fold.sql.exists", 1) != 0;
238 unsigned int endPos = startPos + length;
239 int visibleChars = 0;
240 int lineCurrent = styler.GetLine(startPos);
241 int levelCurrent = SC_FOLDLEVELBASE;
242 if (lineCurrent > 0) {
243 levelCurrent = styler.LevelAt(lineCurrent - 1) >> 16;
245 int levelNext = levelCurrent;
246 char chNext = styler[startPos];
247 int styleNext = styler.StyleAt(startPos);
248 int style = initStyle;
249 bool endFound = false;
250 for (unsigned int i = startPos; i < endPos; i++) {
251 char ch = chNext;
252 chNext = styler.SafeGetCharAt(i + 1);
253 int stylePrev = style;
254 style = styleNext;
255 styleNext = styler.StyleAt(i + 1);
256 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
257 if (foldComment && IsStreamCommentStyle(style)) {
258 if (!IsStreamCommentStyle(stylePrev)) {
259 levelNext++;
260 } else if (!IsStreamCommentStyle(styleNext) && !atEOL) {
261 // Comments don't end at end of line and the next character may be unstyled.
262 levelNext--;
265 if (foldComment && (style == SCE_SQL_COMMENTLINE)) {
266 // MySQL needs -- comments to be followed by space or control char
267 if ((ch == '-') && (chNext == '-')) {
268 char chNext2 = styler.SafeGetCharAt(i + 2);
269 char chNext3 = styler.SafeGetCharAt(i + 3);
270 if (chNext2 == '{' || chNext3 == '{') {
271 levelNext++;
272 } else if (chNext2 == '}' || chNext3 == '}') {
273 levelNext--;
277 if (style == SCE_SQL_OPERATOR) {
278 if (ch == '(') {
279 levelNext++;
280 } else if (ch == ')') {
281 levelNext--;
284 // If new keyword (cannot trigger on elseif or nullif, does less tests)
285 if (style == SCE_SQL_WORD && stylePrev != SCE_SQL_WORD) {
286 const int MAX_KW_LEN = 6; // Maximum length of folding keywords
287 char s[MAX_KW_LEN + 2];
288 unsigned int j = 0;
289 for (; j < MAX_KW_LEN + 1; j++) {
290 if (!iswordchar(styler[i + j])) {
291 break;
293 s[j] = static_cast<char>(tolower(styler[i + j]));
295 if (j == MAX_KW_LEN + 1) {
296 // Keyword too long, don't test it
297 s[0] = '\0';
298 } else {
299 s[j] = '\0';
301 if ((!foldOnlyBegin) && (strcmp(s, "if") == 0 || strcmp(s, "loop") == 0)) {
302 if (endFound) {
303 // ignore
304 endFound = false;
305 } else {
306 levelNext++;
308 } else if (strcmp(s, "begin") == 0) {
309 levelNext++;
310 } else if ((strcmp(s, "end") == 0) ||
311 // // DROP TABLE IF EXISTS or CREATE TABLE IF NOT EXISTS
312 (foldSqlExists && (strcmp(s, "exists") == 0)) ||
313 // // SQL Anywhere permits IF ... ELSE ... ENDIF
314 // // will only be active if "endif" appears in the
315 // // keyword list.
316 (strcmp(s, "endif") == 0)) {
317 endFound = true;
318 levelNext--;
319 if (levelNext < SC_FOLDLEVELBASE) {
320 levelNext = SC_FOLDLEVELBASE;
324 if (atEOL) {
325 int levelUse = levelCurrent;
326 int lev = levelUse | levelNext << 16;
327 if (visibleChars == 0 && foldCompact)
328 lev |= SC_FOLDLEVELWHITEFLAG;
329 if (levelUse < levelNext)
330 lev |= SC_FOLDLEVELHEADERFLAG;
331 if (lev != styler.LevelAt(lineCurrent)) {
332 styler.SetLevel(lineCurrent, lev);
334 lineCurrent++;
335 levelCurrent = levelNext;
336 visibleChars = 0;
337 endFound = false;
339 if (!isspacechar(ch)) {
340 visibleChars++;
345 static const char * const sqlWordListDesc[] = {
346 "Keywords",
347 "Database Objects",
348 "PLDoc",
349 "SQL*Plus",
350 "User Keywords 1",
351 "User Keywords 2",
352 "User Keywords 3",
353 "User Keywords 4",
357 LexerModule lmSQL(SCLEX_SQL, ColouriseSQLDoc, "sql", FoldSQLDoc, sqlWordListDesc);