1 // Scintilla source code edit control
\r
2 /** @file LexBasic.cxx
\r
3 ** Lexer for BlitzBasic and PureBasic.
\r
5 // Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
\r
6 // The License.txt file describes the conditions under which this software may be distributed.
\r
8 // This tries to be a unified Lexer/Folder for all the BlitzBasic/BlitzMax/PurBasic basics
\r
9 // and derivatives. Once they diverge enough, might want to split it into multiple
\r
10 // lexers for more code clearity.
\r
12 // Mail me (elias <at> users <dot> sf <dot> net) for any bugs.
\r
14 // Folding only works for simple things like functions or types.
\r
16 // You may want to have a look at my ctags lexer as well, if you additionally to coloring
\r
17 // and folding need to extract things like label tags in your editor.
\r
25 #include "Platform.h"
\r
27 #include "PropSet.h"
\r
28 #include "Accessor.h"
\r
29 #include "StyleContext.h"
\r
30 #include "KeyWords.h"
\r
31 #include "Scintilla.h"
\r
32 #include "SciLexer.h"
\r
34 #ifdef SCI_NAMESPACE
\r
35 using namespace Scintilla;
\r
46 static int character_classification[128] =
\r
48 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
\r
49 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
\r
50 1, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10, 2,
\r
51 60, 60, 28, 28, 28, 28, 28, 28, 28, 28, 2, 2, 2, 2, 2, 2,
\r
52 2, 20, 20, 20, 20, 20, 20, 4, 4, 4, 4, 4, 4, 4, 4, 4,
\r
53 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 4,
\r
54 2, 20, 20, 20, 20, 20, 20, 4, 4, 4, 4, 4, 4, 4, 4, 4,
\r
55 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 0
\r
58 static bool IsSpace(int c) {
\r
59 return c < 128 && (character_classification[c] & 1);
\r
62 static bool IsOperator(int c) {
\r
63 return c < 128 && (character_classification[c] & 2);
\r
66 static bool IsIdentifier(int c) {
\r
67 return c < 128 && (character_classification[c] & 4);
\r
70 static bool IsDigit(int c) {
\r
71 return c < 128 && (character_classification[c] & 8);
\r
74 static bool IsHexDigit(int c) {
\r
75 return c < 128 && (character_classification[c] & 16);
\r
78 static bool IsBinDigit(int c) {
\r
79 return c < 128 && (character_classification[c] & 32);
\r
82 static int LowerCase(int c)
\r
84 if (c >= 'A' && c <= 'Z')
\r
85 return 'a' + c - 'A';
\r
89 static void ColouriseBasicDoc(unsigned int startPos, int length, int initStyle,
\r
90 WordList *keywordlists[], Accessor &styler, char comment_char) {
\r
91 bool wasfirst = true, isfirst = true; // true if first token in a line
\r
92 styler.StartAt(startPos);
\r
94 StyleContext sc(startPos, length, initStyle, styler);
\r
96 // Can't use sc.More() here else we miss the last character
\r
97 for (; ; sc.Forward()) {
\r
98 if (sc.state == SCE_B_IDENTIFIER) {
\r
99 if (!IsIdentifier(sc.ch)) {
\r
101 if (wasfirst && sc.Match(':')) {
\r
102 sc.ChangeState(SCE_B_LABEL);
\r
103 sc.ForwardSetState(SCE_B_DEFAULT);
\r
112 sc.GetCurrentLowered(s, sizeof(s));
\r
113 for (int i = 0; i < 4; i++) {
\r
114 if (keywordlists[i]->InList(s)) {
\r
115 sc.ChangeState(kstates[i]);
\r
118 // Types, must set them as operator else they will be
\r
119 // matched as number/constant
\r
120 if (sc.Match('.') || sc.Match('$') || sc.Match('%') ||
\r
122 sc.SetState(SCE_B_OPERATOR);
\r
124 sc.SetState(SCE_B_DEFAULT);
\r
128 } else if (sc.state == SCE_B_OPERATOR) {
\r
129 if (!IsOperator(sc.ch) || sc.Match('#'))
\r
130 sc.SetState(SCE_B_DEFAULT);
\r
131 } else if (sc.state == SCE_B_LABEL) {
\r
132 if (!IsIdentifier(sc.ch))
\r
133 sc.SetState(SCE_B_DEFAULT);
\r
134 } else if (sc.state == SCE_B_CONSTANT) {
\r
135 if (!IsIdentifier(sc.ch))
\r
136 sc.SetState(SCE_B_DEFAULT);
\r
137 } else if (sc.state == SCE_B_NUMBER) {
\r
138 if (!IsDigit(sc.ch))
\r
139 sc.SetState(SCE_B_DEFAULT);
\r
140 } else if (sc.state == SCE_B_HEXNUMBER) {
\r
141 if (!IsHexDigit(sc.ch))
\r
142 sc.SetState(SCE_B_DEFAULT);
\r
143 } else if (sc.state == SCE_B_BINNUMBER) {
\r
144 if (!IsBinDigit(sc.ch))
\r
145 sc.SetState(SCE_B_DEFAULT);
\r
146 } else if (sc.state == SCE_B_STRING) {
\r
147 if (sc.ch == '"') {
\r
148 sc.ForwardSetState(SCE_B_DEFAULT);
\r
150 if (sc.atLineEnd) {
\r
151 sc.ChangeState(SCE_B_ERROR);
\r
152 sc.SetState(SCE_B_DEFAULT);
\r
154 } else if (sc.state == SCE_B_COMMENT || sc.state == SCE_B_PREPROCESSOR) {
\r
155 if (sc.atLineEnd) {
\r
156 sc.SetState(SCE_B_DEFAULT);
\r
160 if (sc.atLineStart)
\r
163 if (sc.state == SCE_B_DEFAULT || sc.state == SCE_B_ERROR) {
\r
164 if (isfirst && sc.Match('.')) {
\r
165 sc.SetState(SCE_B_LABEL);
\r
166 } else if (isfirst && sc.Match('#')) {
\r
167 wasfirst = isfirst;
\r
168 sc.SetState(SCE_B_IDENTIFIER);
\r
169 } else if (sc.Match(comment_char)) {
\r
170 // Hack to make deprecated QBASIC '$Include show
\r
171 // up in freebasic with SCE_B_PREPROCESSOR.
\r
172 if (comment_char == '\'' && sc.Match(comment_char, '$'))
\r
173 sc.SetState(SCE_B_PREPROCESSOR);
\r
175 sc.SetState(SCE_B_COMMENT);
\r
176 } else if (sc.Match('"')) {
\r
177 sc.SetState(SCE_B_STRING);
\r
178 } else if (IsDigit(sc.ch)) {
\r
179 sc.SetState(SCE_B_NUMBER);
\r
180 } else if (sc.Match('$')) {
\r
181 sc.SetState(SCE_B_HEXNUMBER);
\r
182 } else if (sc.Match('%')) {
\r
183 sc.SetState(SCE_B_BINNUMBER);
\r
184 } else if (sc.Match('#')) {
\r
185 sc.SetState(SCE_B_CONSTANT);
\r
186 } else if (IsOperator(sc.ch)) {
\r
187 sc.SetState(SCE_B_OPERATOR);
\r
188 } else if (IsIdentifier(sc.ch)) {
\r
189 wasfirst = isfirst;
\r
190 sc.SetState(SCE_B_IDENTIFIER);
\r
191 } else if (!IsSpace(sc.ch)) {
\r
192 sc.SetState(SCE_B_ERROR);
\r
196 if (!IsSpace(sc.ch))
\r
205 static int CheckBlitzFoldPoint(char const *token, int &level) {
\r
206 if (!strcmp(token, "function") ||
\r
207 !strcmp(token, "type")) {
\r
208 level |= SC_FOLDLEVELHEADERFLAG;
\r
211 if (!strcmp(token, "end function") ||
\r
212 !strcmp(token, "end type")) {
\r
218 static int CheckPureFoldPoint(char const *token, int &level) {
\r
219 if (!strcmp(token, "procedure") ||
\r
220 !strcmp(token, "enumeration") ||
\r
221 !strcmp(token, "interface") ||
\r
222 !strcmp(token, "structure")) {
\r
223 level |= SC_FOLDLEVELHEADERFLAG;
\r
226 if (!strcmp(token, "endprocedure") ||
\r
227 !strcmp(token, "endenumeration") ||
\r
228 !strcmp(token, "endinterface") ||
\r
229 !strcmp(token, "endstructure")) {
\r
235 static int CheckFreeFoldPoint(char const *token, int &level) {
\r
236 if (!strcmp(token, "function") ||
\r
237 !strcmp(token, "sub") ||
\r
238 !strcmp(token, "type")) {
\r
239 level |= SC_FOLDLEVELHEADERFLAG;
\r
242 if (!strcmp(token, "end function") ||
\r
243 !strcmp(token, "end sub") ||
\r
244 !strcmp(token, "end type")) {
\r
250 static void FoldBasicDoc(unsigned int startPos, int length,
\r
251 Accessor &styler, int (*CheckFoldPoint)(char const *, int &)) {
\r
252 int line = styler.GetLine(startPos);
\r
253 int level = styler.LevelAt(line);
\r
254 int go = 0, done = 0;
\r
255 int endPos = startPos + length;
\r
259 bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
\r
260 // Scan for tokens at the start of the line (they may include
\r
261 // whitespace, for tokens like "End Function"
\r
262 for (i = startPos; i < endPos; i++) {
\r
263 int c = styler.SafeGetCharAt(i);
\r
264 if (!done && !go) {
\r
265 if (wordlen) { // are we scanning a token already?
\r
266 word[wordlen] = static_cast<char>(LowerCase(c));
\r
267 if (!IsIdentifier(c)) { // done with token
\r
268 word[wordlen] = '\0';
\r
269 go = CheckFoldPoint(word, level);
\r
271 // Treat any whitespace as single blank, for
\r
272 // things like "End Function".
\r
273 if (IsSpace(c) && IsIdentifier(word[wordlen - 1])) {
\r
274 word[wordlen] = ' ';
\r
278 else // done with this line
\r
281 } else if (wordlen < 255) {
\r
284 } else { // start scanning at first non-whitespace character
\r
286 if (IsIdentifier(c)) {
\r
287 word[0] = static_cast<char>(LowerCase(c));
\r
289 } else // done with this line
\r
294 if (c == '\n') { // line end
\r
295 if (!done && wordlen == 0 && foldCompact) // line was only space
\r
296 level |= SC_FOLDLEVELWHITEFLAG;
\r
297 if (level != styler.LevelAt(line))
\r
298 styler.SetLevel(line, level);
\r
303 level &= ~SC_FOLDLEVELHEADERFLAG;
\r
304 level &= ~SC_FOLDLEVELWHITEFLAG;
\r
311 static void ColouriseBlitzBasicDoc(unsigned int startPos, int length, int initStyle,
\r
312 WordList *keywordlists[], Accessor &styler) {
\r
313 ColouriseBasicDoc(startPos, length, initStyle, keywordlists, styler, ';');
\r
316 static void ColourisePureBasicDoc(unsigned int startPos, int length, int initStyle,
\r
317 WordList *keywordlists[], Accessor &styler) {
\r
318 ColouriseBasicDoc(startPos, length, initStyle, keywordlists, styler, ';');
\r
321 static void ColouriseFreeBasicDoc(unsigned int startPos, int length, int initStyle,
\r
322 WordList *keywordlists[], Accessor &styler) {
\r
323 ColouriseBasicDoc(startPos, length, initStyle, keywordlists, styler, '\'');
\r
326 static void FoldBlitzBasicDoc(unsigned int startPos, int length, int,
\r
327 WordList *[], Accessor &styler) {
\r
328 FoldBasicDoc(startPos, length, styler, CheckBlitzFoldPoint);
\r
331 static void FoldPureBasicDoc(unsigned int startPos, int length, int,
\r
332 WordList *[], Accessor &styler) {
\r
333 FoldBasicDoc(startPos, length, styler, CheckPureFoldPoint);
\r
336 static void FoldFreeBasicDoc(unsigned int startPos, int length, int,
\r
337 WordList *[], Accessor &styler) {
\r
338 FoldBasicDoc(startPos, length, styler, CheckFreeFoldPoint);
\r
341 static const char * const blitzbasicWordListDesc[] = {
\r
342 "BlitzBasic Keywords",
\r
349 static const char * const purebasicWordListDesc[] = {
\r
350 "PureBasic Keywords",
\r
351 "PureBasic PreProcessor Keywords",
\r
357 static const char * const freebasicWordListDesc[] = {
\r
358 "FreeBasic Keywords",
\r
359 "FreeBasic PreProcessor Keywords",
\r
365 LexerModule lmBlitzBasic(SCLEX_BLITZBASIC, ColouriseBlitzBasicDoc, "blitzbasic",
\r
366 FoldBlitzBasicDoc, blitzbasicWordListDesc);
\r
368 LexerModule lmPureBasic(SCLEX_PUREBASIC, ColourisePureBasicDoc, "purebasic",
\r
369 FoldPureBasicDoc, purebasicWordListDesc);
\r
371 LexerModule lmFreeBasic(SCLEX_FREEBASIC, ColouriseFreeBasicDoc, "freebasic",
\r
372 FoldFreeBasicDoc, freebasicWordListDesc);
\r