]>
Commit | Line | Data |
---|---|---|
1e9bafca RD |
1 | // Scintilla source code edit control |
2 | /** @file LexBasic.cxx | |
3 | ** Lexer for BlitzBasic and PureBasic. | |
4 | **/ | |
5 | // Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org> | |
6 | // The License.txt file describes the conditions under which this software may be distributed. | |
7 | ||
8 | // This tries to be a unified Lexer/Folder for all the BlitzBasic/BlitzMax/PurBasic basics | |
9 | // and derivatives. Once they diverge enough, might want to split it into multiple | |
10 | // lexers for more code clearity. | |
11 | // | |
12 | // Mail me (elias <at> users <dot> sf <dot> net) for any bugs. | |
13 | ||
14 | // Folding only works for simple things like functions or types. | |
15 | ||
16 | // You may want to have a look at my ctags lexer as well, if you additionally to coloring | |
17 | // and folding need to extract things like label tags in your editor. | |
18 | ||
19 | #include <stdlib.h> | |
20 | #include <string.h> | |
21 | #include <stdio.h> | |
22 | #include <ctype.h> | |
23 | #include <stdarg.h> | |
24 | ||
25 | #include "Platform.h" | |
26 | ||
27 | #include "PropSet.h" | |
28 | #include "Accessor.h" | |
29 | #include "StyleContext.h" | |
30 | #include "KeyWords.h" | |
31 | #include "Scintilla.h" | |
32 | #include "SciLexer.h" | |
33 | ||
7e0c58e9 RD |
34 | #ifdef SCI_NAMESPACE |
35 | using namespace Scintilla; | |
36 | #endif | |
37 | ||
1e9bafca RD |
38 | /* Bits: |
39 | * 1 - whitespace | |
40 | * 2 - operator | |
41 | * 4 - identifier | |
42 | * 8 - decimal digit | |
43 | * 16 - hex digit | |
44 | * 32 - bin digit | |
45 | */ | |
46 | static int character_classification[128] = | |
47 | { | |
48 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, | |
49 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
50 | 1, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10, 2, | |
51 | 60, 60, 28, 28, 28, 28, 28, 28, 28, 28, 2, 2, 2, 2, 2, 2, | |
52 | 2, 20, 20, 20, 20, 20, 20, 4, 4, 4, 4, 4, 4, 4, 4, 4, | |
53 | 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 4, | |
54 | 2, 20, 20, 20, 20, 20, 20, 4, 4, 4, 4, 4, 4, 4, 4, 4, | |
55 | 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 0 | |
56 | }; | |
57 | ||
58 | static bool IsSpace(int c) { | |
59 | return c < 128 && (character_classification[c] & 1); | |
60 | } | |
61 | ||
62 | static bool IsOperator(int c) { | |
63 | return c < 128 && (character_classification[c] & 2); | |
64 | } | |
65 | ||
66 | static bool IsIdentifier(int c) { | |
67 | return c < 128 && (character_classification[c] & 4); | |
68 | } | |
69 | ||
70 | static bool IsDigit(int c) { | |
71 | return c < 128 && (character_classification[c] & 8); | |
72 | } | |
73 | ||
74 | static bool IsHexDigit(int c) { | |
75 | return c < 128 && (character_classification[c] & 16); | |
76 | } | |
77 | ||
78 | static bool IsBinDigit(int c) { | |
79 | return c < 128 && (character_classification[c] & 32); | |
80 | } | |
81 | ||
82 | static int LowerCase(int c) | |
83 | { | |
84 | if (c >= 'A' && c <= 'Z') | |
85 | return 'a' + c - 'A'; | |
86 | return c; | |
87 | } | |
88 | ||
89 | static void ColouriseBasicDoc(unsigned int startPos, int length, int initStyle, | |
90 | WordList *keywordlists[], Accessor &styler, char comment_char) { | |
91 | bool wasfirst = true, isfirst = true; // true if first token in a line | |
92 | styler.StartAt(startPos); | |
93 | ||
94 | StyleContext sc(startPos, length, initStyle, styler); | |
95 | ||
96 | // Can't use sc.More() here else we miss the last character | |
97 | for (; ; sc.Forward()) { | |
98 | if (sc.state == SCE_B_IDENTIFIER) { | |
99 | if (!IsIdentifier(sc.ch)) { | |
100 | // Labels | |
101 | if (wasfirst && sc.Match(':')) { | |
102 | sc.ChangeState(SCE_B_LABEL); | |
103 | sc.ForwardSetState(SCE_B_DEFAULT); | |
104 | } else { | |
105 | char s[100]; | |
106 | int kstates[4] = { | |
107 | SCE_B_KEYWORD, | |
108 | SCE_B_KEYWORD2, | |
109 | SCE_B_KEYWORD3, | |
110 | SCE_B_KEYWORD4, | |
111 | }; | |
112 | sc.GetCurrentLowered(s, sizeof(s)); | |
113 | for (int i = 0; i < 4; i++) { | |
114 | if (keywordlists[i]->InList(s)) { | |
115 | sc.ChangeState(kstates[i]); | |
116 | } | |
117 | } | |
118 | // Types, must set them as operator else they will be | |
119 | // matched as number/constant | |
120 | if (sc.Match('.') || sc.Match('$') || sc.Match('%') || | |
121 | sc.Match('#')) { | |
122 | sc.SetState(SCE_B_OPERATOR); | |
123 | } else { | |
124 | sc.SetState(SCE_B_DEFAULT); | |
125 | } | |
126 | } | |
127 | } | |
128 | } else if (sc.state == SCE_B_OPERATOR) { | |
129 | if (!IsOperator(sc.ch) || sc.Match('#')) | |
130 | sc.SetState(SCE_B_DEFAULT); | |
131 | } else if (sc.state == SCE_B_LABEL) { | |
132 | if (!IsIdentifier(sc.ch)) | |
133 | sc.SetState(SCE_B_DEFAULT); | |
134 | } else if (sc.state == SCE_B_CONSTANT) { | |
135 | if (!IsIdentifier(sc.ch)) | |
136 | sc.SetState(SCE_B_DEFAULT); | |
137 | } else if (sc.state == SCE_B_NUMBER) { | |
138 | if (!IsDigit(sc.ch)) | |
139 | sc.SetState(SCE_B_DEFAULT); | |
140 | } else if (sc.state == SCE_B_HEXNUMBER) { | |
141 | if (!IsHexDigit(sc.ch)) | |
142 | sc.SetState(SCE_B_DEFAULT); | |
143 | } else if (sc.state == SCE_B_BINNUMBER) { | |
144 | if (!IsBinDigit(sc.ch)) | |
145 | sc.SetState(SCE_B_DEFAULT); | |
146 | } else if (sc.state == SCE_B_STRING) { | |
147 | if (sc.ch == '"') { | |
148 | sc.ForwardSetState(SCE_B_DEFAULT); | |
149 | } | |
150 | if (sc.atLineEnd) { | |
151 | sc.ChangeState(SCE_B_ERROR); | |
152 | sc.SetState(SCE_B_DEFAULT); | |
153 | } | |
b8193d80 | 154 | } else if (sc.state == SCE_B_COMMENT || sc.state == SCE_B_PREPROCESSOR) { |
1e9bafca RD |
155 | if (sc.atLineEnd) { |
156 | sc.SetState(SCE_B_DEFAULT); | |
157 | } | |
158 | } | |
159 | ||
160 | if (sc.atLineStart) | |
161 | isfirst = true; | |
162 | ||
163 | if (sc.state == SCE_B_DEFAULT || sc.state == SCE_B_ERROR) { | |
164 | if (isfirst && sc.Match('.')) { | |
165 | sc.SetState(SCE_B_LABEL); | |
166 | } else if (isfirst && sc.Match('#')) { | |
167 | wasfirst = isfirst; | |
168 | sc.SetState(SCE_B_IDENTIFIER); | |
169 | } else if (sc.Match(comment_char)) { | |
b8193d80 RD |
170 | // Hack to make deprecated QBASIC '$Include show |
171 | // up in freebasic with SCE_B_PREPROCESSOR. | |
172 | if (comment_char == '\'' && sc.Match(comment_char, '$')) | |
173 | sc.SetState(SCE_B_PREPROCESSOR); | |
174 | else | |
175 | sc.SetState(SCE_B_COMMENT); | |
1e9bafca RD |
176 | } else if (sc.Match('"')) { |
177 | sc.SetState(SCE_B_STRING); | |
178 | } else if (IsDigit(sc.ch)) { | |
179 | sc.SetState(SCE_B_NUMBER); | |
180 | } else if (sc.Match('$')) { | |
181 | sc.SetState(SCE_B_HEXNUMBER); | |
182 | } else if (sc.Match('%')) { | |
183 | sc.SetState(SCE_B_BINNUMBER); | |
184 | } else if (sc.Match('#')) { | |
185 | sc.SetState(SCE_B_CONSTANT); | |
186 | } else if (IsOperator(sc.ch)) { | |
187 | sc.SetState(SCE_B_OPERATOR); | |
188 | } else if (IsIdentifier(sc.ch)) { | |
189 | wasfirst = isfirst; | |
190 | sc.SetState(SCE_B_IDENTIFIER); | |
191 | } else if (!IsSpace(sc.ch)) { | |
192 | sc.SetState(SCE_B_ERROR); | |
193 | } | |
194 | } | |
195 | ||
196 | if (!IsSpace(sc.ch)) | |
197 | isfirst = false; | |
198 | ||
199 | if (!sc.More()) | |
200 | break; | |
201 | } | |
202 | sc.Complete(); | |
203 | } | |
204 | ||
205 | static int CheckBlitzFoldPoint(char const *token, int &level) { | |
206 | if (!strcmp(token, "function") || | |
207 | !strcmp(token, "type")) { | |
208 | level |= SC_FOLDLEVELHEADERFLAG; | |
209 | return 1; | |
210 | } | |
211 | if (!strcmp(token, "end function") || | |
212 | !strcmp(token, "end type")) { | |
213 | return -1; | |
214 | } | |
215 | return 0; | |
216 | } | |
217 | ||
218 | static int CheckPureFoldPoint(char const *token, int &level) { | |
219 | if (!strcmp(token, "procedure") || | |
220 | !strcmp(token, "enumeration") || | |
221 | !strcmp(token, "interface") || | |
222 | !strcmp(token, "structure")) { | |
223 | level |= SC_FOLDLEVELHEADERFLAG; | |
224 | return 1; | |
225 | } | |
226 | if (!strcmp(token, "endprocedure") || | |
227 | !strcmp(token, "endenumeration") || | |
228 | !strcmp(token, "endinterface") || | |
229 | !strcmp(token, "endstructure")) { | |
230 | return -1; | |
231 | } | |
232 | return 0; | |
233 | } | |
234 | ||
235 | static int CheckFreeFoldPoint(char const *token, int &level) { | |
236 | if (!strcmp(token, "function") || | |
237 | !strcmp(token, "sub") || | |
238 | !strcmp(token, "type")) { | |
239 | level |= SC_FOLDLEVELHEADERFLAG; | |
240 | return 1; | |
241 | } | |
242 | if (!strcmp(token, "end function") || | |
243 | !strcmp(token, "end sub") || | |
244 | !strcmp(token, "end type")) { | |
245 | return -1; | |
246 | } | |
247 | return 0; | |
248 | } | |
249 | ||
250 | static void FoldBasicDoc(unsigned int startPos, int length, | |
251 | Accessor &styler, int (*CheckFoldPoint)(char const *, int &)) { | |
252 | int line = styler.GetLine(startPos); | |
253 | int level = styler.LevelAt(line); | |
254 | int go = 0, done = 0; | |
255 | int endPos = startPos + length; | |
256 | char word[256]; | |
257 | int wordlen = 0; | |
258 | int i; | |
259 | bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0; | |
260 | // Scan for tokens at the start of the line (they may include | |
261 | // whitespace, for tokens like "End Function" | |
262 | for (i = startPos; i < endPos; i++) { | |
263 | int c = styler.SafeGetCharAt(i); | |
264 | if (!done && !go) { | |
265 | if (wordlen) { // are we scanning a token already? | |
266 | word[wordlen] = static_cast<char>(LowerCase(c)); | |
267 | if (!IsIdentifier(c)) { // done with token | |
268 | word[wordlen] = '\0'; | |
269 | go = CheckFoldPoint(word, level); | |
270 | if (!go) { | |
271 | // Treat any whitespace as single blank, for | |
272 | // things like "End Function". | |
273 | if (IsSpace(c) && IsIdentifier(word[wordlen - 1])) { | |
274 | word[wordlen] = ' '; | |
275 | if (wordlen < 255) | |
276 | wordlen++; | |
277 | } | |
278 | else // done with this line | |
279 | done = 1; | |
280 | } | |
281 | } else if (wordlen < 255) { | |
282 | wordlen++; | |
283 | } | |
284 | } else { // start scanning at first non-whitespace character | |
285 | if (!IsSpace(c)) { | |
286 | if (IsIdentifier(c)) { | |
287 | word[0] = static_cast<char>(LowerCase(c)); | |
288 | wordlen = 1; | |
289 | } else // done with this line | |
290 | done = 1; | |
291 | } | |
292 | } | |
293 | } | |
294 | if (c == '\n') { // line end | |
295 | if (!done && wordlen == 0 && foldCompact) // line was only space | |
296 | level |= SC_FOLDLEVELWHITEFLAG; | |
297 | if (level != styler.LevelAt(line)) | |
298 | styler.SetLevel(line, level); | |
299 | level += go; | |
300 | line++; | |
301 | // reset state | |
302 | wordlen = 0; | |
303 | level &= ~SC_FOLDLEVELHEADERFLAG; | |
304 | level &= ~SC_FOLDLEVELWHITEFLAG; | |
305 | go = 0; | |
306 | done = 0; | |
307 | } | |
308 | } | |
309 | } | |
310 | ||
311 | static void ColouriseBlitzBasicDoc(unsigned int startPos, int length, int initStyle, | |
312 | WordList *keywordlists[], Accessor &styler) { | |
313 | ColouriseBasicDoc(startPos, length, initStyle, keywordlists, styler, ';'); | |
314 | } | |
315 | ||
316 | static void ColourisePureBasicDoc(unsigned int startPos, int length, int initStyle, | |
317 | WordList *keywordlists[], Accessor &styler) { | |
318 | ColouriseBasicDoc(startPos, length, initStyle, keywordlists, styler, ';'); | |
319 | } | |
320 | ||
321 | static void ColouriseFreeBasicDoc(unsigned int startPos, int length, int initStyle, | |
322 | WordList *keywordlists[], Accessor &styler) { | |
323 | ColouriseBasicDoc(startPos, length, initStyle, keywordlists, styler, '\''); | |
324 | } | |
325 | ||
326 | static void FoldBlitzBasicDoc(unsigned int startPos, int length, int, | |
327 | WordList *[], Accessor &styler) { | |
328 | FoldBasicDoc(startPos, length, styler, CheckBlitzFoldPoint); | |
329 | } | |
330 | ||
331 | static void FoldPureBasicDoc(unsigned int startPos, int length, int, | |
332 | WordList *[], Accessor &styler) { | |
333 | FoldBasicDoc(startPos, length, styler, CheckPureFoldPoint); | |
334 | } | |
335 | ||
336 | static void FoldFreeBasicDoc(unsigned int startPos, int length, int, | |
337 | WordList *[], Accessor &styler) { | |
338 | FoldBasicDoc(startPos, length, styler, CheckFreeFoldPoint); | |
339 | } | |
340 | ||
341 | static const char * const blitzbasicWordListDesc[] = { | |
342 | "BlitzBasic Keywords", | |
343 | "user1", | |
344 | "user2", | |
345 | "user3", | |
346 | 0 | |
347 | }; | |
348 | ||
349 | static const char * const purebasicWordListDesc[] = { | |
350 | "PureBasic Keywords", | |
351 | "PureBasic PreProcessor Keywords", | |
352 | "user defined 1", | |
353 | "user defined 2", | |
354 | 0 | |
355 | }; | |
356 | ||
357 | static const char * const freebasicWordListDesc[] = { | |
358 | "FreeBasic Keywords", | |
359 | "FreeBasic PreProcessor Keywords", | |
360 | "user defined 1", | |
361 | "user defined 2", | |
362 | 0 | |
363 | }; | |
364 | ||
365 | LexerModule lmBlitzBasic(SCLEX_BLITZBASIC, ColouriseBlitzBasicDoc, "blitzbasic", | |
366 | FoldBlitzBasicDoc, blitzbasicWordListDesc); | |
367 | ||
368 | LexerModule lmPureBasic(SCLEX_PUREBASIC, ColourisePureBasicDoc, "purebasic", | |
369 | FoldPureBasicDoc, purebasicWordListDesc); | |
370 | ||
371 | LexerModule lmFreeBasic(SCLEX_FREEBASIC, ColouriseFreeBasicDoc, "freebasic", | |
372 | FoldFreeBasicDoc, freebasicWordListDesc); | |
373 |