1 | | 1 | | // Scintilla source code edit control |
|
|
3 | | ** Lexer for SQL. | | 3 | | ** Lexer for SQL, including PL/SQL and SQL*Plus. |
|
5 | | // Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org> | | 5 | | // Copyright 1998-2005 by Neil Hodgson <neilh@scintilla.org> |
6 | | 6 | | // The License.txt file describes the conditions under which this software may be distributed. |
|
|
|
6 skipped lines |
|
16 | | 16 | | #include "PropSet.h" |
|
17 | | 17 | | #include "Accessor.h" |
|
| | 18 | | #include "StyleContext.h" |
18 | | 19 | | #include "KeyWords.h" |
|
19 | | 20 | | #include "Scintilla.h" |
|
20 | | 21 | | #include "SciLexer.h" |
|
|
22 | | static void classifyWordSQL(unsigned int start, unsigned int end, WordList &keywords, Accessor &styler) { | | 23 | | static inline bool IsAWordChar(int ch) { |
23 | | char s[100]; | | |
24 | | bool wordIsNumber = isdigit(styler[start]) || (styler[start] == '.'); | | 24 | | return (ch < 0x80) && (isalnum(ch) || ch == '.' || ch == '_'); |
25 | | for (unsigned int i = 0; i < end - start + 1 && i < 30; i++) { | | 25 | | } |
26 | | s[i] = static_cast<char>(tolower(styler[start + i])); | | 26 | | |
27 | | s[i + 1] = '\0'; | | 27 | | static inline bool IsAWordStart(int ch) { |
28 | | } | | |
29 | | char chAttr = SCE_C_IDENTIFIER; | | |
30 | | if (wordIsNumber) | | |
31 | | chAttr = SCE_C_NUMBER; | | |
32 | | else { | | |
33 | | if (keywords.InList(s)) | | |
34 | | chAttr = SCE_C_WORD; | | |
35 | | } | | |
36 | | styler.ColourTo(end, chAttr); | | 28 | | return (ch < 0x80) && (isalpha(ch) || ch == '_'); |
|
|
39 | | static void ColouriseSQLDoc(unsigned int startPos, int length, | | 31 | | static inline bool IsADoxygenChar(int ch) { |
40 | | int initStyle, WordList *keywordlists[], Accessor &styler) { | | 32 | | return (islower(ch) || ch == '$' || ch == '@' || |
| | 33 | | ch == '\\' || ch == '&' || ch == '<' || |
| | 34 | | ch == '>' || ch == '#' || ch == '{' || |
| | 35 | | ch == '}' || ch == '[' || ch == ']'); |
| | 36 | | } |
| | 37 | | |
| | 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. |
| | 41 | | return (ch < 0x80) && |
| | 42 | | (isdigit(ch) || toupper(ch) == 'E' || |
| | 43 | | ch == '.' || ch == '-' || ch == '+'); |
| | 44 | | } |
|
42 | | WordList &keywords = *keywordlists[0]; | | |
|
| | 47 | | static void ColouriseSQLDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[], |
| | 48 | | Accessor &styler) { |
| | 49 | | |
| | 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]; |
| | 58 | | |
44 | | styler.StartAt(startPos); | | 59 | | StyleContext sc(startPos, length, initStyle, styler); |
|
46 | | bool fold = styler.GetPropertyInt("fold") != 0; | | |
47 | | 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; |
48 | | 65 | | int lineCurrent = styler.GetLine(startPos); |
|
49 | | int spaceFlags = 0; | | |
50 | | | | |
51 | | int state = initStyle; | | |
52 | | char chPrev = ' '; | | |
53 | | char chNext = styler[startPos]; | | |
54 | | styler.StartSegment(startPos); | | |
55 | | unsigned int lengthDoc = startPos + length; | | |
56 | | for (unsigned int i = startPos; i < lengthDoc; i++) { | | |
57 | | char ch = chNext; | | |
58 | | chNext = styler.SafeGetCharAt(i + 1); | | |
|
60 | | if ((ch == '\r' && chNext != '\n') || (ch == '\n')) { | | 67 | | for (; sc.More(); sc.Forward()) { |
| | 68 | | // Fold based on indentation |
| | 69 | | if (sc.atLineStart) { |
| | 70 | | int spaceFlags = 0; |
61 | | 71 | | int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags); |
|
62 | | int lev = indentCurrent; | | 72 | | int level = indentCurrent; |
63 | | 73 | | if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG)) { |
|
64 | | 74 | | // Only non whitespace lines can be headers |
|
65 | | 75 | | int indentNext = styler.IndentAmount(lineCurrent + 1, &spaceFlags); |
|
66 | | 76 | | if (indentCurrent < (indentNext & ~SC_FOLDLEVELWHITEFLAG)) { |
|
67 | | lev |= SC_FOLDLEVELHEADERFLAG; | | 77 | | level |= SC_FOLDLEVELHEADERFLAG; |
|
|
|
71 | | styler.SetLevel(lineCurrent, lev); | | 81 | | styler.SetLevel(lineCurrent, level); |
|
|
|
75 | | if (styler.IsLeadByte(ch)) { | | |
76 | | chNext = styler.SafeGetCharAt(i + 2); | | |
77 | | chPrev = ' '; | | |
78 | | i += 1; | | |
79 | | continue; | | |
80 | | } | | |
81 | | | | |
82 | | if (state == SCE_C_DEFAULT) { | | |
83 | | if (iswordstart(ch)) { | | |
84 | | styler.ColourTo(i - 1, state); | | 85 | | // Determine if the current state should terminate. |
85 | | state = SCE_C_WORD; | | |
86 | | } else if (ch == '/' && chNext == '*') { | | |
87 | | styler.ColourTo(i - 1, state); | | |
88 | | state = SCE_C_COMMENT; | | 86 | | switch (sc.state) { |
89 | | } else if (ch == '-' && chNext == '-') { | | |
90 | | styler.ColourTo(i - 1, state); | | |
91 | | state = SCE_C_COMMENTLINE; | | 87 | | case SCE_SQL_OPERATOR: |
92 | | } else if (ch == '#') { | | |
93 | | styler.ColourTo(i - 1, state); | | |
94 | | state = SCE_C_COMMENTLINEDOC; | | 88 | | sc.SetState(SCE_SQL_DEFAULT); |
95 | | } else if (ch == '\'') { | | 89 | | break; |
96 | | styler.ColourTo(i - 1, state); | | |
97 | | state = SCE_C_CHARACTER; | | 90 | | case SCE_SQL_NUMBER: |
98 | | } else if (ch == '"') { | | 91 | | // We stop the number definition on non-numerical non-dot non-eE non-sign char |
99 | | styler.ColourTo(i - 1, state); | | |
100 | | state = SCE_C_STRING; | | |
101 | | } else if (isoperator(ch)) { | | 92 | | if (!IsANumberChar(sc.ch)) { |
102 | | styler.ColourTo(i - 1, state); | | |
103 | | styler.ColourTo(i, SCE_C_OPERATOR); | | 93 | | sc.SetState(SCE_SQL_DEFAULT); |
|
| | 95 | | break; |
105 | | } else if (state == SCE_C_WORD) { | | 96 | | case SCE_SQL_IDENTIFIER: |
106 | | if (!iswordchar(ch)) { | | 97 | | if (!IsAWordChar(sc.ch)) { |
| | 98 | | int nextState = SCE_SQL_DEFAULT; |
| | 99 | | char s[1000]; |
107 | | classifyWordSQL(styler.GetStartSegment(), i - 1, keywords, styler); | | 100 | | sc.GetCurrentLowered(s, sizeof(s)); |
| | 101 | | if (keywords1.InList(s)) { |
108 | | state = SCE_C_DEFAULT; | | 102 | | sc.ChangeState(SCE_SQL_WORD); |
109 | | if (ch == '/' && chNext == '*') { | | 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) { |
110 | | state = SCE_C_COMMENT; | | 108 | | nextState = SCE_SQL_SQLPLUS_COMMENT; |
111 | | } else if (ch == '-' && chNext == '-') { | | 109 | | } else if (strncmp(s, "pro", 3) == 0) { |
112 | | state = SCE_C_COMMENTLINE; | | 110 | | nextState = SCE_SQL_SQLPLUS_PROMPT; |
| | 111 | | } |
113 | | } else if (ch == '#') { | | 112 | | } else if (kw_user1.InList(s)) { |
114 | | state = SCE_C_COMMENTLINEDOC; | | 113 | | sc.ChangeState(SCE_SQL_USER1); |
115 | | } else if (ch == '\'') { | | 114 | | } else if (kw_user2.InList(s)) { |
116 | | state = SCE_C_CHARACTER; | | 115 | | sc.ChangeState(SCE_SQL_USER2); |
117 | | } else if (ch == '"') { | | 116 | | } else if (kw_user3.InList(s)) { |
118 | | state = SCE_C_STRING; | | 117 | | sc.ChangeState(SCE_SQL_USER3); |
119 | | } else if (isoperator(ch)) { | | 118 | | } else if (kw_user4.InList(s)) { |
120 | | styler.ColourTo(i, SCE_C_OPERATOR); | | 119 | | sc.ChangeState(SCE_SQL_USER4); |
|
| | 121 | | sc.SetState(nextState); |
|
123 | | } else { | | 123 | | break; |
| | 124 | | case SCE_SQL_QUOTEDIDENTIFIER: |
124 | | if (state == SCE_C_COMMENT) { | | 125 | | if (sc.ch == 0x60) { |
125 | | if (ch == '/' && chPrev == '*') { | | 126 | | if (sc.chNext == 0x60) { |
126 | | if (((i > (styler.GetStartSegment() + 2)) || ((initStyle == SCE_C_COMMENT) && | | 127 | | sc.Forward(); // Ignore it |
127 | | (styler.GetStartSegment() == startPos)))) { | | 128 | | } else { |
128 | | styler.ColourTo(i, state); | | |
129 | | state = SCE_C_DEFAULT; | | 129 | | sc.ForwardSetState(SCE_SQL_DEFAULT); |
130 | | } | | |
|
| | 131 | | } |
| | 132 | | break; |
| | 133 | | case SCE_SQL_COMMENT: |
| | 134 | | if (sc.Match('*', '/')) { |
| | 135 | | sc.Forward(); |
| | 136 | | sc.ForwardSetState(SCE_SQL_DEFAULT); |
| | 137 | | } |
| | 138 | | break; |
132 | | } else if (state == SCE_C_COMMENTLINE || state == SCE_C_COMMENTLINEDOC) { | | 139 | | case SCE_SQL_COMMENTDOC: |
| | 140 | | if (sc.Match('*', '/')) { |
| | 141 | | sc.Forward(); |
| | 142 | | sc.ForwardSetState(SCE_SQL_DEFAULT); |
133 | | if (ch == '\r' || ch == '\n') { | | 143 | | } else if (sc.ch == '@' || sc.ch == '\\') { // Doxygen support |
134 | | styler.ColourTo(i - 1, state); | | 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; |
135 | | state = SCE_C_DEFAULT; | | 147 | | sc.SetState(SCE_SQL_COMMENTDOCKEYWORD); |
|
| | 149 | | } |
| | 150 | | break; |
| | 151 | | case SCE_SQL_COMMENTLINE: |
| | 152 | | case SCE_SQL_COMMENTLINEDOC: |
137 | | } else if (state == SCE_C_CHARACTER) { | | 153 | | case SCE_SQL_SQLPLUS_COMMENT: |
| | 154 | | case SCE_SQL_SQLPLUS_PROMPT: |
138 | | if (sqlBackslashEscapes && ch == '\\') { | | 155 | | if (sc.atLineStart) { |
139 | | i++; | | |
140 | | ch = chNext; | | |
141 | | chNext = styler.SafeGetCharAt(i + 1); | | 156 | | sc.SetState(SCE_SQL_DEFAULT); |
| | 157 | | } |
| | 158 | | break; |
| | 159 | | case SCE_SQL_COMMENTDOCKEYWORD: |
142 | | } else if (ch == '\'') { | | 160 | | if ((styleBeforeDCKeyword == SCE_SQL_COMMENTDOC) && sc.Match('*', '/')) { |
143 | | if (chNext == '\'') { | | 161 | | sc.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR); |
144 | | i++; | | |
145 | | } else { | | |
146 | | styler.ColourTo(i, state); | | 162 | | sc.Forward(); |
147 | | state = SCE_C_DEFAULT; | | 163 | | sc.ForwardSetState(SCE_SQL_DEFAULT); |
148 | | i++; | | 164 | | } else if (!IsADoxygenChar(sc.ch)) { |
149 | | } | | 165 | | char s[100]; |
150 | | ch = chNext; | | 166 | | sc.GetCurrentLowered(s, sizeof(s)); |
| | 167 | | if (!isspace(sc.ch) || !kw_pldoc.InList(s + 1)) { |
151 | | chNext = styler.SafeGetCharAt(i + 1); | | 168 | | sc.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR); |
|
| | 170 | | sc.SetState(styleBeforeDCKeyword); |
| | 171 | | } |
| | 172 | | break; |
| | 173 | | case SCE_SQL_CHARACTER: |
153 | | } else if (state == SCE_C_STRING) { | | 174 | | if (sqlBackslashEscapes && sc.ch == '\\') { |
| | 175 | | sc.Forward(); |
154 | | if (ch == '"') { | | 176 | | } else if (sc.ch == '\'') { |
155 | | if (chNext == '"') { | | 177 | | if (sc.chNext == '\"') { |
156 | | i++; | | 178 | | sc.Forward(); |
157 | | } else { | | 179 | | } else { |
158 | | styler.ColourTo(i, state); | | |
159 | | state = SCE_C_DEFAULT; | | 180 | | sc.ForwardSetState(SCE_SQL_DEFAULT); |
160 | | i++; | | |
161 | | } | | |
162 | | ch = chNext; | | |
163 | | chNext = styler.SafeGetCharAt(i + 1); | | |
|
|
| | 183 | | break; |
| | 184 | | case SCE_SQL_STRING: |
166 | | if (state == SCE_C_DEFAULT) { // One of the above succeeded | | 185 | | if (sc.ch == '\\') { |
| | 186 | | // Escape sequence |
| | 187 | | sc.Forward(); |
| | 188 | | } else if (sc.ch == '\"') { |
167 | | if (ch == '/' && chNext == '*') { | | 189 | | if (sc.chNext == '\"') { |
| | 190 | | sc.Forward(); |
| | 191 | | } else { |
168 | | state = SCE_C_COMMENT; | | 192 | | sc.ForwardSetState(SCE_SQL_DEFAULT); |
169 | | } else if (ch == '-' && chNext == '-') { | | 193 | | } |
| | 194 | | } |
| | 195 | | break; |
| | 196 | | } |
| | 197 | | |
| | 198 | | // Determine if a new state should be entered. |
170 | | state = SCE_C_COMMENTLINE; | | 199 | | if (sc.state == SCE_SQL_DEFAULT) { |
171 | | } else if (ch == '#') { | | 200 | | if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) { |
172 | | state = SCE_C_COMMENTLINEDOC; | | 201 | | sc.SetState(SCE_SQL_NUMBER); |
173 | | } else if (ch == '\'') { | | 202 | | } else if (IsAWordStart(sc.ch)) { |
174 | | state = SCE_C_CHARACTER; | | 203 | | sc.SetState(SCE_SQL_IDENTIFIER); |
175 | | } else if (ch == '"') { | | 204 | | } else if (sc.ch == 0x60 && sqlBackticksIdentifier) { |
176 | | state = SCE_C_STRING; | | 205 | | sc.SetState(SCE_SQL_QUOTEDIDENTIFIER); |
177 | | } else if (iswordstart(ch)) { | | 206 | | } else if (sc.Match('/', '*')) { |
| | 207 | | if (sc.Match("/**") || sc.Match("/*!")) { // Support of Doxygen doc. style |
178 | | state = SCE_C_WORD; | | 208 | | sc.SetState(SCE_SQL_COMMENTDOC); |
179 | | } else if (isoperator(ch)) { | | 209 | | } else { |
180 | | styler.ColourTo(i, SCE_C_OPERATOR); | | 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: |
5 skipped lines |
| | 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); |
|
|
184 | | chPrev = ch; | | |
|
| | 230 | | sc.Complete(); |
| | 231 | | } |
| | 232 | | |
| | 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; |
| | 238 | | } |
| | 239 | | |
| | 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) { |
186 | | styler.ColourTo(lengthDoc - 1, state); | | 251 | | levelCurrent = styler.LevelAt(lineCurrent - 1) & SC_FOLDLEVELNUMBERMASK; |
| | 252 | | } |
| | 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++) { |
| | 259 | | char ch = chNext; |
| | 260 | | chNext = styler.SafeGetCharAt(i + 1); |
| | 261 | | int stylePrev = style; |
77 skipped lines |
| | 339 | | } |
| | 340 | | lineCurrent++; |
| | 341 | | levelCurrent = levelNext; |
| | 342 | | visibleChars = 0; |
| | 343 | | endFound = false; |
| | 344 | | } |
| | 345 | | if (!isspacechar(ch)) { |
| | 346 | | visibleChars++; |
| | 347 | | } |
| | 348 | | } |
|
|
189 | | 351 | | static const char * const sqlWordListDesc[] = { |
|
|
| | 353 | | "Database Objects", |
| | 354 | | "PLDoc", |
| | 355 | | "SQL*Plus", |
| | 356 | | "User Keywords 1", |
| | 357 | | "User Keywords 2", |
| | 358 | | "User Keywords 3", |
| | 359 | | "User Keywords 4", |
|
|
|
194 | | LexerModule lmSQL(SCLEX_SQL, ColouriseSQLDoc, "sql", 0, sqlWordListDesc); | | 363 | | LexerModule lmSQL(SCLEX_SQL, ColouriseSQLDoc, "sql", FoldSQLDoc, sqlWordListDesc); |
|