1 // Scintilla source code edit control
3 ** Lexer for SQL, including PL/SQL and SQL*Plus.
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.
18 #include "StyleContext.h"
20 #include "Scintilla.h"
23 static inline bool IsAWordChar(int ch
) {
24 return (ch
< 0x80) && (isalnum(ch
) || ch
== '.' || ch
== '_');
27 static inline bool IsAWordStart(int ch
) {
28 return (ch
< 0x80) && (isalpha(ch
) || ch
== '_');
31 static inline bool IsADoxygenChar(int ch
) {
32 return (islower(ch
) || ch
== '$' || ch
== '@' ||
33 ch
== '\\' || ch
== '&' || ch
== '<' ||
34 ch
== '>' || ch
== '#' || ch
== '{' ||
35 ch
== '}' || ch
== '[' || ch
== ']');
38 static inline bool IsANumberChar(int ch
) {
39 // Not exactly following number definition (several dots are seen as OK, etc.)
40 // but probably enough in most cases.
42 (isdigit(ch
) || toupper(ch
) == 'E' ||
43 ch
== '.' || ch
== '-' || ch
== '+');
47 static void ColouriseSQLDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
50 WordList
&keywords1
= *keywordlists
[0];
51 WordList
&keywords2
= *keywordlists
[1];
52 WordList
&kw_pldoc
= *keywordlists
[2];
53 WordList
&kw_sqlplus
= *keywordlists
[3];
54 WordList
&kw_user1
= *keywordlists
[4];
55 WordList
&kw_user2
= *keywordlists
[5];
56 WordList
&kw_user3
= *keywordlists
[6];
57 WordList
&kw_user4
= *keywordlists
[7];
59 StyleContext
sc(startPos
, length
, initStyle
, styler
);
61 bool sqlBackslashEscapes
= styler
.GetPropertyInt("sql.backslash.escapes", 0) != 0;
62 bool sqlBackticksIdentifier
= styler
.GetPropertyInt("lexer.sql.backticks.identifier", 0) != 0;
63 int styleBeforeDCKeyword
= SCE_C_DEFAULT
;
64 bool fold
= styler
.GetPropertyInt("fold") != 0;
65 int lineCurrent
= styler
.GetLine(startPos
);
67 for (; sc
.More(); sc
.Forward()) {
68 // Fold based on indentation
71 int indentCurrent
= styler
.IndentAmount(lineCurrent
, &spaceFlags
);
72 int level
= indentCurrent
;
73 if (!(indentCurrent
& SC_FOLDLEVELWHITEFLAG
)) {
74 // Only non whitespace lines can be headers
75 int indentNext
= styler
.IndentAmount(lineCurrent
+ 1, &spaceFlags
);
76 if (indentCurrent
< (indentNext
& ~SC_FOLDLEVELWHITEFLAG
)) {
77 level
|= SC_FOLDLEVELHEADERFLAG
;
81 styler
.SetLevel(lineCurrent
, level
);
85 // Determine if the current state should terminate.
87 case SCE_SQL_OPERATOR
:
88 sc
.SetState(SCE_SQL_DEFAULT
);
91 // We stop the number definition on non-numerical non-dot non-eE non-sign char
92 if (!IsANumberChar(sc
.ch
)) {
93 sc
.SetState(SCE_SQL_DEFAULT
);
96 case SCE_SQL_IDENTIFIER
:
97 if (!IsAWordChar(sc
.ch
)) {
98 int nextState
= SCE_SQL_DEFAULT
;
100 sc
.GetCurrentLowered(s
, sizeof(s
));
101 if (keywords1
.InList(s
)) {
102 sc
.ChangeState(SCE_SQL_WORD
);
103 } else if (keywords2
.InList(s
)) {
104 sc
.ChangeState(SCE_SQL_WORD2
);
105 } else if (kw_sqlplus
.InListAbbreviated(s
, '~')) {
106 sc
.ChangeState(SCE_SQL_SQLPLUS
);
107 if (strncmp(s
, "rem", 3) == 0) {
108 nextState
= SCE_SQL_SQLPLUS_COMMENT
;
109 } else if (strncmp(s
, "pro", 3) == 0) {
110 nextState
= SCE_SQL_SQLPLUS_PROMPT
;
112 } else if (kw_user1
.InList(s
)) {
113 sc
.ChangeState(SCE_SQL_USER1
);
114 } else if (kw_user2
.InList(s
)) {
115 sc
.ChangeState(SCE_SQL_USER2
);
116 } else if (kw_user3
.InList(s
)) {
117 sc
.ChangeState(SCE_SQL_USER3
);
118 } else if (kw_user4
.InList(s
)) {
119 sc
.ChangeState(SCE_SQL_USER4
);
121 sc
.SetState(nextState
);
124 case SCE_SQL_QUOTEDIDENTIFIER
:
126 if (sc
.chNext
== 0x60) {
127 sc
.Forward(); // Ignore it
129 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
133 case SCE_SQL_COMMENT
:
134 if (sc
.Match('*', '/')) {
136 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
139 case SCE_SQL_COMMENTDOC
:
140 if (sc
.Match('*', '/')) {
142 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
143 } else if (sc
.ch
== '@' || sc
.ch
== '\\') { // Doxygen support
144 // Verify that we have the conditions to mark a comment-doc-keyword
145 if ((IsASpace(sc
.chPrev
) || sc
.chPrev
== '*') && (!IsASpace(sc
.chNext
))) {
146 styleBeforeDCKeyword
= SCE_SQL_COMMENTDOC
;
147 sc
.SetState(SCE_SQL_COMMENTDOCKEYWORD
);
151 case SCE_SQL_COMMENTLINE
:
152 case SCE_SQL_COMMENTLINEDOC
:
153 case SCE_SQL_SQLPLUS_COMMENT
:
154 case SCE_SQL_SQLPLUS_PROMPT
:
155 if (sc
.atLineStart
) {
156 sc
.SetState(SCE_SQL_DEFAULT
);
159 case SCE_SQL_COMMENTDOCKEYWORD
:
160 if ((styleBeforeDCKeyword
== SCE_SQL_COMMENTDOC
) && sc
.Match('*', '/')) {
161 sc
.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR
);
163 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
164 } else if (!IsADoxygenChar(sc
.ch
)) {
166 sc
.GetCurrentLowered(s
, sizeof(s
));
167 if (!isspace(sc
.ch
) || !kw_pldoc
.InList(s
+ 1)) {
168 sc
.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR
);
170 sc
.SetState(styleBeforeDCKeyword
);
173 case SCE_SQL_CHARACTER
:
174 if (sqlBackslashEscapes
&& sc
.ch
== '\\') {
176 } else if (sc
.ch
== '\'') {
177 if (sc
.chNext
== '\"') {
180 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
188 } else if (sc
.ch
== '\"') {
189 if (sc
.chNext
== '\"') {
192 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
198 // Determine if a new state should be entered.
199 if (sc
.state
== SCE_SQL_DEFAULT
) {
200 if (IsADigit(sc
.ch
) || (sc
.ch
== '.' && IsADigit(sc
.chNext
))) {
201 sc
.SetState(SCE_SQL_NUMBER
);
202 } else if (IsAWordStart(sc
.ch
)) {
203 sc
.SetState(SCE_SQL_IDENTIFIER
);
204 } else if (sc
.ch
== 0x60 && sqlBackticksIdentifier
) {
205 sc
.SetState(SCE_SQL_QUOTEDIDENTIFIER
);
206 } else if (sc
.Match('/', '*')) {
207 if (sc
.Match("/**") || sc
.Match("/*!")) { // Support of Doxygen doc. style
208 sc
.SetState(SCE_SQL_COMMENTDOC
);
210 sc
.SetState(SCE_SQL_COMMENT
);
212 sc
.Forward(); // Eat the * so it isn't used for the end of the comment
213 } else if (sc
.Match('-', '-')) {
214 // MySQL requires a space or control char after --
215 // http://dev.mysql.com/doc/mysql/en/ansi-diff-comments.html
216 // Perhaps we should enforce that with proper property:
217 //~ } else if (sc.Match("-- ")) {
218 sc
.SetState(SCE_SQL_COMMENTLINE
);
219 } else if (sc
.ch
== '#') {
220 sc
.SetState(SCE_SQL_COMMENTLINEDOC
);
221 } else if (sc
.ch
== '\'') {
222 sc
.SetState(SCE_SQL_CHARACTER
);
223 } else if (sc
.ch
== '\"') {
224 sc
.SetState(SCE_SQL_STRING
);
225 } else if (isoperator(static_cast<char>(sc
.ch
))) {
226 sc
.SetState(SCE_SQL_OPERATOR
);
233 static bool IsStreamCommentStyle(int style
) {
234 return style
== SCE_SQL_COMMENT
||
235 style
== SCE_SQL_COMMENTDOC
||
236 style
== SCE_SQL_COMMENTDOCKEYWORD
||
237 style
== SCE_SQL_COMMENTDOCKEYWORDERROR
;
240 // Store both the current line's fold level and the next lines in the
241 // level store to make it easy to pick up with each increment.
242 static void FoldSQLDoc(unsigned int startPos
, int length
, int initStyle
,
243 WordList
*[], Accessor
&styler
) {
244 bool foldComment
= styler
.GetPropertyInt("fold.comment") != 0;
245 bool foldCompact
= styler
.GetPropertyInt("fold.compact", 1) != 0;
246 unsigned int endPos
= startPos
+ length
;
247 int visibleChars
= 0;
248 int lineCurrent
= styler
.GetLine(startPos
);
249 int levelCurrent
= SC_FOLDLEVELBASE
;
250 if (lineCurrent
> 0) {
251 levelCurrent
= styler
.LevelAt(lineCurrent
- 1) & SC_FOLDLEVELNUMBERMASK
;
253 int levelNext
= levelCurrent
;
254 char chNext
= styler
[startPos
];
255 int styleNext
= styler
.StyleAt(startPos
);
256 int style
= initStyle
;
257 bool endFound
= false;
258 for (unsigned int i
= startPos
; i
< endPos
; i
++) {
260 chNext
= styler
.SafeGetCharAt(i
+ 1);
261 int stylePrev
= style
;
263 styleNext
= styler
.StyleAt(i
+ 1);
264 bool atEOL
= (ch
== '\r' && chNext
!= '\n') || (ch
== '\n');
265 if (foldComment
&& IsStreamCommentStyle(style
)) {
266 if (!IsStreamCommentStyle(stylePrev
)) {
268 } else if (!IsStreamCommentStyle(styleNext
) && !atEOL
) {
269 // Comments don't end at end of line and the next character may be unstyled.
273 if (foldComment
&& (style
== SCE_SQL_COMMENTLINE
)) {
274 // MySQL needs -- comments to be followed by space or control char
275 if ((ch
== '-') && (chNext
== '-')) {
276 char chNext2
= styler
.SafeGetCharAt(i
+ 2);
277 char chNext3
= styler
.SafeGetCharAt(i
+ 3);
278 if (chNext2
== '{' || chNext3
== '{') {
280 } else if (chNext2
== '}' || chNext3
== '}') {
285 if (style
== SCE_SQL_OPERATOR
) {
288 } else if (ch
== ')') {
292 // If new keyword (cannot trigger on elseif or nullif, does less tests)
293 if (style
== SCE_SQL_WORD
&& stylePrev
!= SCE_SQL_WORD
) {
294 const int MAX_KW_LEN
= 6; // Maximum length of folding keywords
295 char s
[MAX_KW_LEN
+ 2];
297 for (; j
< MAX_KW_LEN
+ 1; j
++) {
298 if (!iswordchar(styler
[i
+ j
])) {
301 s
[j
] = static_cast<char>(tolower(styler
[i
+ j
]));
303 if (j
== MAX_KW_LEN
+ 1) {
304 // Keyword too long, don't test it
309 if (strcmp(s
, "if") == 0 || strcmp(s
, "loop") == 0) {
316 } else if (strcmp(s
, "begin") == 0) {
318 } else if (strcmp(s
, "end") == 0 ||
319 // DROP TABLE IF EXISTS or CREATE TABLE IF NOT EXISTS
320 strcmp(s
, "exists") == 0) {
323 if (levelNext
< SC_FOLDLEVELBASE
) {
324 levelNext
= SC_FOLDLEVELBASE
;
329 int level
= levelCurrent
;
330 if (visibleChars
== 0 && foldCompact
) {
332 level
|= SC_FOLDLEVELWHITEFLAG
;
334 if (visibleChars
> 0 && levelNext
> levelCurrent
) {
335 level
|= SC_FOLDLEVELHEADERFLAG
;
337 if (level
!= styler
.LevelAt(lineCurrent
)) {
338 styler
.SetLevel(lineCurrent
, level
);
341 levelCurrent
= levelNext
;
345 if (!isspacechar(ch
)) {
351 static const char * const sqlWordListDesc
[] = {
363 LexerModule
lmSQL(SCLEX_SQL
, ColouriseSQLDoc
, "sql", FoldSQLDoc
, sqlWordListDesc
);