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 bool sqlBackslashEscapes
= styler
.GetPropertyInt("sql.backslash.escapes", 0) != 0;
65 bool sqlBackticksIdentifier
= styler
.GetPropertyInt("lexer.sql.backticks.identifier", 0) != 0;
66 int styleBeforeDCKeyword
= SCE_SQL_DEFAULT
;
67 for (; sc
.More(); sc
.Forward()) {
68 // Determine if the current state should terminate.
70 case SCE_SQL_OPERATOR
:
71 sc
.SetState(SCE_SQL_DEFAULT
);
74 // We stop the number definition on non-numerical non-dot non-eE non-sign char
75 if (!IsANumberChar(sc
.ch
)) {
76 sc
.SetState(SCE_SQL_DEFAULT
);
79 case SCE_SQL_IDENTIFIER
:
80 if (!IsAWordChar(sc
.ch
)) {
81 int nextState
= SCE_SQL_DEFAULT
;
83 sc
.GetCurrentLowered(s
, sizeof(s
));
84 if (keywords1
.InList(s
)) {
85 sc
.ChangeState(SCE_SQL_WORD
);
86 } else if (keywords2
.InList(s
)) {
87 sc
.ChangeState(SCE_SQL_WORD2
);
88 } else if (kw_sqlplus
.InListAbbreviated(s
, '~')) {
89 sc
.ChangeState(SCE_SQL_SQLPLUS
);
90 if (strncmp(s
, "rem", 3) == 0) {
91 nextState
= SCE_SQL_SQLPLUS_COMMENT
;
92 } else if (strncmp(s
, "pro", 3) == 0) {
93 nextState
= SCE_SQL_SQLPLUS_PROMPT
;
95 } else if (kw_user1
.InList(s
)) {
96 sc
.ChangeState(SCE_SQL_USER1
);
97 } else if (kw_user2
.InList(s
)) {
98 sc
.ChangeState(SCE_SQL_USER2
);
99 } else if (kw_user3
.InList(s
)) {
100 sc
.ChangeState(SCE_SQL_USER3
);
101 } else if (kw_user4
.InList(s
)) {
102 sc
.ChangeState(SCE_SQL_USER4
);
104 sc
.SetState(nextState
);
107 case SCE_SQL_QUOTEDIDENTIFIER
:
109 if (sc
.chNext
== 0x60) {
110 sc
.Forward(); // Ignore it
112 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
116 case SCE_SQL_COMMENT
:
117 if (sc
.Match('*', '/')) {
119 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
122 case SCE_SQL_COMMENTDOC
:
123 if (sc
.Match('*', '/')) {
125 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
126 } else if (sc
.ch
== '@' || sc
.ch
== '\\') { // Doxygen support
127 // Verify that we have the conditions to mark a comment-doc-keyword
128 if ((IsASpace(sc
.chPrev
) || sc
.chPrev
== '*') && (!IsASpace(sc
.chNext
))) {
129 styleBeforeDCKeyword
= SCE_SQL_COMMENTDOC
;
130 sc
.SetState(SCE_SQL_COMMENTDOCKEYWORD
);
134 case SCE_SQL_COMMENTLINE
:
135 case SCE_SQL_COMMENTLINEDOC
:
136 case SCE_SQL_SQLPLUS_COMMENT
:
137 case SCE_SQL_SQLPLUS_PROMPT
:
138 if (sc
.atLineStart
) {
139 sc
.SetState(SCE_SQL_DEFAULT
);
142 case SCE_SQL_COMMENTDOCKEYWORD
:
143 if ((styleBeforeDCKeyword
== SCE_SQL_COMMENTDOC
) && sc
.Match('*', '/')) {
144 sc
.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR
);
146 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
147 } else if (!IsADoxygenChar(sc
.ch
)) {
149 sc
.GetCurrentLowered(s
, sizeof(s
));
150 if (!isspace(sc
.ch
) || !kw_pldoc
.InList(s
+ 1)) {
151 sc
.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR
);
153 sc
.SetState(styleBeforeDCKeyword
);
156 case SCE_SQL_CHARACTER
:
157 if (sqlBackslashEscapes
&& sc
.ch
== '\\') {
159 } else if (sc
.ch
== '\'') {
160 if (sc
.chNext
== '\"') {
163 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
171 } else if (sc
.ch
== '\"') {
172 if (sc
.chNext
== '\"') {
175 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
181 // Determine if a new state should be entered.
182 if (sc
.state
== SCE_SQL_DEFAULT
) {
183 if (IsADigit(sc
.ch
) || (sc
.ch
== '.' && IsADigit(sc
.chNext
))) {
184 sc
.SetState(SCE_SQL_NUMBER
);
185 } else if (IsAWordStart(sc
.ch
)) {
186 sc
.SetState(SCE_SQL_IDENTIFIER
);
187 } else if (sc
.ch
== 0x60 && sqlBackticksIdentifier
) {
188 sc
.SetState(SCE_SQL_QUOTEDIDENTIFIER
);
189 } else if (sc
.Match('/', '*')) {
190 if (sc
.Match("/**") || sc
.Match("/*!")) { // Support of Doxygen doc. style
191 sc
.SetState(SCE_SQL_COMMENTDOC
);
193 sc
.SetState(SCE_SQL_COMMENT
);
195 sc
.Forward(); // Eat the * so it isn't used for the end of the comment
196 } else if (sc
.Match('-', '-')) {
197 // MySQL requires a space or control char after --
198 // http://dev.mysql.com/doc/mysql/en/ansi-diff-comments.html
199 // Perhaps we should enforce that with proper property:
200 //~ } else if (sc.Match("-- ")) {
201 sc
.SetState(SCE_SQL_COMMENTLINE
);
202 } else if (sc
.ch
== '#') {
203 sc
.SetState(SCE_SQL_COMMENTLINEDOC
);
204 } else if (sc
.ch
== '\'') {
205 sc
.SetState(SCE_SQL_CHARACTER
);
206 } else if (sc
.ch
== '\"') {
207 sc
.SetState(SCE_SQL_STRING
);
208 } else if (isoperator(static_cast<char>(sc
.ch
))) {
209 sc
.SetState(SCE_SQL_OPERATOR
);
216 static bool IsStreamCommentStyle(int style
) {
217 return style
== SCE_SQL_COMMENT
||
218 style
== SCE_SQL_COMMENTDOC
||
219 style
== SCE_SQL_COMMENTDOCKEYWORD
||
220 style
== SCE_SQL_COMMENTDOCKEYWORDERROR
;
223 // Store both the current line's fold level and the next lines in the
224 // level store to make it easy to pick up with each increment.
225 static void FoldSQLDoc(unsigned int startPos
, int length
, int initStyle
,
226 WordList
*[], Accessor
&styler
) {
227 bool foldComment
= styler
.GetPropertyInt("fold.comment") != 0;
228 bool foldCompact
= styler
.GetPropertyInt("fold.compact", 1) != 0;
229 bool foldOnlyBegin
= styler
.GetPropertyInt("fold.sql.only.begin", 0) != 0;
231 unsigned int endPos
= startPos
+ length
;
232 int visibleChars
= 0;
233 int lineCurrent
= styler
.GetLine(startPos
);
234 int levelCurrent
= SC_FOLDLEVELBASE
;
235 if (lineCurrent
> 0) {
236 levelCurrent
= styler
.LevelAt(lineCurrent
- 1) >> 16;
238 int levelNext
= levelCurrent
;
239 char chNext
= styler
[startPos
];
240 int styleNext
= styler
.StyleAt(startPos
);
241 int style
= initStyle
;
242 bool endFound
= false;
243 for (unsigned int i
= startPos
; i
< endPos
; i
++) {
245 chNext
= styler
.SafeGetCharAt(i
+ 1);
246 int stylePrev
= style
;
248 styleNext
= styler
.StyleAt(i
+ 1);
249 bool atEOL
= (ch
== '\r' && chNext
!= '\n') || (ch
== '\n');
250 if (foldComment
&& IsStreamCommentStyle(style
)) {
251 if (!IsStreamCommentStyle(stylePrev
)) {
253 } else if (!IsStreamCommentStyle(styleNext
) && !atEOL
) {
254 // Comments don't end at end of line and the next character may be unstyled.
258 if (foldComment
&& (style
== SCE_SQL_COMMENTLINE
)) {
259 // MySQL needs -- comments to be followed by space or control char
260 if ((ch
== '-') && (chNext
== '-')) {
261 char chNext2
= styler
.SafeGetCharAt(i
+ 2);
262 char chNext3
= styler
.SafeGetCharAt(i
+ 3);
263 if (chNext2
== '{' || chNext3
== '{') {
265 } else if (chNext2
== '}' || chNext3
== '}') {
270 if (style
== SCE_SQL_OPERATOR
) {
273 } else if (ch
== ')') {
277 // If new keyword (cannot trigger on elseif or nullif, does less tests)
278 if (style
== SCE_SQL_WORD
&& stylePrev
!= SCE_SQL_WORD
) {
279 const int MAX_KW_LEN
= 6; // Maximum length of folding keywords
280 char s
[MAX_KW_LEN
+ 2];
282 for (; j
< MAX_KW_LEN
+ 1; j
++) {
283 if (!iswordchar(styler
[i
+ j
])) {
286 s
[j
] = static_cast<char>(tolower(styler
[i
+ j
]));
288 if (j
== MAX_KW_LEN
+ 1) {
289 // Keyword too long, don't test it
294 if ((!foldOnlyBegin
) && (strcmp(s
, "if") == 0 || strcmp(s
, "loop") == 0)) {
301 } else if (strcmp(s
, "begin") == 0) {
303 } else if (strcmp(s
, "end") == 0 ||
304 // DROP TABLE IF EXISTS or CREATE TABLE IF NOT EXISTS
305 strcmp(s
, "exists") == 0) {
308 if (levelNext
< SC_FOLDLEVELBASE
) {
309 levelNext
= SC_FOLDLEVELBASE
;
314 int levelUse
= levelCurrent
;
315 int lev
= levelUse
| levelNext
<< 16;
316 if (visibleChars
== 0 && foldCompact
)
317 lev
|= SC_FOLDLEVELWHITEFLAG
;
318 if (levelUse
< levelNext
)
319 lev
|= SC_FOLDLEVELHEADERFLAG
;
320 if (lev
!= styler
.LevelAt(lineCurrent
)) {
321 styler
.SetLevel(lineCurrent
, lev
);
324 levelCurrent
= levelNext
;
328 if (!isspacechar(ch
)) {
334 static const char * const sqlWordListDesc
[] = {
346 LexerModule
lmSQL(SCLEX_SQL
, ColouriseSQLDoc
, "sql", FoldSQLDoc
, sqlWordListDesc
);