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"
24 using namespace Scintilla
;
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.
46 (isdigit(ch
) || toupper(ch
) == 'E' ||
47 ch
== '.' || ch
== '-' || ch
== '+');
50 static void ColouriseSQLDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
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.
73 case SCE_SQL_OPERATOR
:
74 sc
.SetState(SCE_SQL_DEFAULT
);
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
);
82 case SCE_SQL_IDENTIFIER
:
83 if (!IsAWordChar(sc
.ch
)) {
84 int nextState
= SCE_SQL_DEFAULT
;
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
);
110 case SCE_SQL_QUOTEDIDENTIFIER
:
112 if (sc
.chNext
== 0x60) {
113 sc
.Forward(); // Ignore it
115 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
119 case SCE_SQL_COMMENT
:
120 if (sc
.Match('*', '/')) {
122 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
125 case SCE_SQL_COMMENTDOC
:
126 if (sc
.Match('*', '/')) {
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
);
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
);
145 case SCE_SQL_COMMENTDOCKEYWORD
:
146 if ((styleBeforeDCKeyword
== SCE_SQL_COMMENTDOC
) && sc
.Match('*', '/')) {
147 sc
.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR
);
149 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
150 } else if (!IsADoxygenChar(sc
.ch
)) {
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
);
159 case SCE_SQL_CHARACTER
:
160 if (sqlBackslashEscapes
&& sc
.ch
== '\\') {
162 } else if (sc
.ch
== '\'') {
163 if (sc
.chNext
== '\"') {
166 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
174 } else if (sc
.ch
== '\"') {
175 if (sc
.chNext
== '\"') {
178 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
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
);
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
);
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
++) {
252 chNext
= styler
.SafeGetCharAt(i
+ 1);
253 int stylePrev
= style
;
255 styleNext
= styler
.StyleAt(i
+ 1);
256 bool atEOL
= (ch
== '\r' && chNext
!= '\n') || (ch
== '\n');
257 if (foldComment
&& IsStreamCommentStyle(style
)) {
258 if (!IsStreamCommentStyle(stylePrev
)) {
260 } else if (!IsStreamCommentStyle(styleNext
) && !atEOL
) {
261 // Comments don't end at end of line and the next character may be unstyled.
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
== '{') {
272 } else if (chNext2
== '}' || chNext3
== '}') {
277 if (style
== SCE_SQL_OPERATOR
) {
280 } else if (ch
== ')') {
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];
289 for (; j
< MAX_KW_LEN
+ 1; j
++) {
290 if (!iswordchar(styler
[i
+ j
])) {
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
301 if ((!foldOnlyBegin
) && (strcmp(s
, "if") == 0 || strcmp(s
, "loop") == 0)) {
308 } else if (strcmp(s
, "begin") == 0) {
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
316 (strcmp(s
, "endif") == 0)) {
319 if (levelNext
< SC_FOLDLEVELBASE
) {
320 levelNext
= SC_FOLDLEVELBASE
;
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
);
335 levelCurrent
= levelNext
;
339 if (!isspacechar(ch
)) {
345 static const char * const sqlWordListDesc
[] = {
357 LexerModule
lmSQL(SCLEX_SQL
, ColouriseSQLDoc
, "sql", FoldSQLDoc
, sqlWordListDesc
);