1 // Scintilla source code edit control
3 ** Lexer for BlitzBasic and PureBasic.
4 ** Converted to lexer object and added further folding features/properties by "Udo Lechner" <dlchnr(at)gmx(dot)net>
6 // Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
7 // The License.txt file describes the conditions under which this software may be distributed.
9 // This tries to be a unified Lexer/Folder for all the BlitzBasic/BlitzMax/PurBasic basics
10 // and derivatives. Once they diverge enough, might want to split it into multiple
11 // lexers for more code clearity.
13 // Mail me (elias <at> users <dot> sf <dot> net) for any bugs.
15 // Folding only works for simple things like functions or types.
17 // You may want to have a look at my ctags lexer as well, if you additionally to coloring
18 // and folding need to extract things like label tags in your editor.
28 #pragma warning(disable: 4786)
35 #include "Scintilla.h"
39 #include "LexAccessor.h"
40 #include "StyleContext.h"
41 #include "CharacterSet.h"
42 #include "LexerModule.h"
43 #include "OptionSet.h"
46 using namespace Scintilla
;
57 static int character_classification
[128] =
59 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
60 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
61 1, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10, 2,
62 60, 60, 28, 28, 28, 28, 28, 28, 28, 28, 2, 2, 2, 2, 2, 2,
63 2, 20, 20, 20, 20, 20, 20, 4, 4, 4, 4, 4, 4, 4, 4, 4,
64 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 4,
65 2, 20, 20, 20, 20, 20, 20, 4, 4, 4, 4, 4, 4, 4, 4, 4,
66 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 0
69 static bool IsSpace(int c
) {
70 return c
< 128 && (character_classification
[c
] & 1);
73 static bool IsOperator(int c
) {
74 return c
< 128 && (character_classification
[c
] & 2);
77 static bool IsIdentifier(int c
) {
78 return c
< 128 && (character_classification
[c
] & 4);
81 static bool IsDigit(int c
) {
82 return c
< 128 && (character_classification
[c
] & 8);
85 static bool IsHexDigit(int c
) {
86 return c
< 128 && (character_classification
[c
] & 16);
89 static bool IsBinDigit(int c
) {
90 return c
< 128 && (character_classification
[c
] & 32);
93 static int LowerCase(int c
)
95 if (c
>= 'A' && c
<= 'Z')
100 static int CheckBlitzFoldPoint(char const *token
, int &level
) {
101 if (!strcmp(token
, "function") ||
102 !strcmp(token
, "type")) {
103 level
|= SC_FOLDLEVELHEADERFLAG
;
106 if (!strcmp(token
, "end function") ||
107 !strcmp(token
, "end type")) {
113 static int CheckPureFoldPoint(char const *token
, int &level
) {
114 if (!strcmp(token
, "procedure") ||
115 !strcmp(token
, "enumeration") ||
116 !strcmp(token
, "interface") ||
117 !strcmp(token
, "structure")) {
118 level
|= SC_FOLDLEVELHEADERFLAG
;
121 if (!strcmp(token
, "endprocedure") ||
122 !strcmp(token
, "endenumeration") ||
123 !strcmp(token
, "endinterface") ||
124 !strcmp(token
, "endstructure")) {
130 static int CheckFreeFoldPoint(char const *token
, int &level
) {
131 if (!strcmp(token
, "function") ||
132 !strcmp(token
, "sub") ||
133 !strcmp(token
, "type")) {
134 level
|= SC_FOLDLEVELHEADERFLAG
;
137 if (!strcmp(token
, "end function") ||
138 !strcmp(token
, "end sub") ||
139 !strcmp(token
, "end type")) {
145 // An individual named option for use in an OptionSet
147 // Options used for LexerBasic
148 struct OptionsBasic
{
150 bool foldSyntaxBased
;
151 bool foldCommentExplicit
;
152 std::string foldExplicitStart
;
153 std::string foldExplicitEnd
;
154 bool foldExplicitAnywhere
;
158 foldSyntaxBased
= true;
159 foldCommentExplicit
= false;
160 foldExplicitStart
= "";
161 foldExplicitEnd
= "";
162 foldExplicitAnywhere
= false;
167 static const char * const blitzbasicWordListDesc
[] = {
168 "BlitzBasic Keywords",
175 static const char * const purebasicWordListDesc
[] = {
176 "PureBasic Keywords",
177 "PureBasic PreProcessor Keywords",
183 static const char * const freebasicWordListDesc
[] = {
184 "FreeBasic Keywords",
185 "FreeBasic PreProcessor Keywords",
191 struct OptionSetBasic
: public OptionSet
<OptionsBasic
> {
192 OptionSetBasic(const char * const wordListDescriptions
[]) {
193 DefineProperty("fold", &OptionsBasic::fold
);
195 DefineProperty("fold.basic.syntax.based", &OptionsBasic::foldSyntaxBased
,
196 "Set this property to 0 to disable syntax based folding.");
198 DefineProperty("fold.basic.comment.explicit", &OptionsBasic::foldCommentExplicit
,
199 "This option enables folding explicit fold points when using the Basic lexer. "
200 "Explicit fold points allows adding extra folding by placing a ;{ (BB/PB) or '{ (FB) comment at the start "
201 "and a ;} (BB/PB) or '} (FB) at the end of a section that should be folded.");
203 DefineProperty("fold.basic.explicit.start", &OptionsBasic::foldExplicitStart
,
204 "The string to use for explicit fold start points, replacing the standard ;{ (BB/PB) or '{ (FB).");
206 DefineProperty("fold.basic.explicit.end", &OptionsBasic::foldExplicitEnd
,
207 "The string to use for explicit fold end points, replacing the standard ;} (BB/PB) or '} (FB).");
209 DefineProperty("fold.basic.explicit.anywhere", &OptionsBasic::foldExplicitAnywhere
,
210 "Set this property to 1 to enable explicit fold points anywhere, not just in line comments.");
212 DefineProperty("fold.compact", &OptionsBasic::foldCompact
);
214 DefineWordListSets(wordListDescriptions
);
218 class LexerBasic
: public ILexer
{
220 int (*CheckFoldPoint
)(char const *, int &);
221 WordList keywordlists
[4];
222 OptionsBasic options
;
223 OptionSetBasic osBasic
;
225 LexerBasic(char comment_char_
, int (*CheckFoldPoint_
)(char const *, int &), const char * const wordListDescriptions
[]) :
226 comment_char(comment_char_
),
227 CheckFoldPoint(CheckFoldPoint_
),
228 osBasic(wordListDescriptions
) {
232 void SCI_METHOD
Release() {
235 int SCI_METHOD
Version() const {
238 const char * SCI_METHOD
PropertyNames() {
239 return osBasic
.PropertyNames();
241 int SCI_METHOD
PropertyType(const char *name
) {
242 return osBasic
.PropertyType(name
);
244 const char * SCI_METHOD
DescribeProperty(const char *name
) {
245 return osBasic
.DescribeProperty(name
);
247 int SCI_METHOD
PropertySet(const char *key
, const char *val
);
248 const char * SCI_METHOD
DescribeWordListSets() {
249 return osBasic
.DescribeWordListSets();
251 int SCI_METHOD
WordListSet(int n
, const char *wl
);
252 void SCI_METHOD
Lex(unsigned int startPos
, int length
, int initStyle
, IDocument
*pAccess
);
253 void SCI_METHOD
Fold(unsigned int startPos
, int length
, int initStyle
, IDocument
*pAccess
);
255 void * SCI_METHOD
PrivateCall(int, void *) {
258 static ILexer
*LexerFactoryBlitzBasic() {
259 return new LexerBasic(';', CheckBlitzFoldPoint
, blitzbasicWordListDesc
);
261 static ILexer
*LexerFactoryPureBasic() {
262 return new LexerBasic(';', CheckPureFoldPoint
, purebasicWordListDesc
);
264 static ILexer
*LexerFactoryFreeBasic() {
265 return new LexerBasic('\'', CheckFreeFoldPoint
, freebasicWordListDesc
);
269 int SCI_METHOD
LexerBasic::PropertySet(const char *key
, const char *val
) {
270 if (osBasic
.PropertySet(&options
, key
, val
)) {
276 int SCI_METHOD
LexerBasic::WordListSet(int n
, const char *wl
) {
277 WordList
*wordListN
= 0;
280 wordListN
= &keywordlists
[0];
283 wordListN
= &keywordlists
[1];
286 wordListN
= &keywordlists
[2];
289 wordListN
= &keywordlists
[3];
292 int firstModification
= -1;
296 if (*wordListN
!= wlNew
) {
298 firstModification
= 0;
301 return firstModification
;
304 void SCI_METHOD
LexerBasic::Lex(unsigned int startPos
, int length
, int initStyle
, IDocument
*pAccess
) {
305 LexAccessor
styler(pAccess
);
307 bool wasfirst
= true, isfirst
= true; // true if first token in a line
308 styler
.StartAt(startPos
);
310 StyleContext
sc(startPos
, length
, initStyle
, styler
);
312 // Can't use sc.More() here else we miss the last character
313 for (; ; sc
.Forward()) {
314 if (sc
.state
== SCE_B_IDENTIFIER
) {
315 if (!IsIdentifier(sc
.ch
)) {
317 if (wasfirst
&& sc
.Match(':')) {
318 sc
.ChangeState(SCE_B_LABEL
);
319 sc
.ForwardSetState(SCE_B_DEFAULT
);
328 sc
.GetCurrentLowered(s
, sizeof(s
));
329 for (int i
= 0; i
< 4; i
++) {
330 if (keywordlists
[i
].InList(s
)) {
331 sc
.ChangeState(kstates
[i
]);
334 // Types, must set them as operator else they will be
335 // matched as number/constant
336 if (sc
.Match('.') || sc
.Match('$') || sc
.Match('%') ||
338 sc
.SetState(SCE_B_OPERATOR
);
340 sc
.SetState(SCE_B_DEFAULT
);
344 } else if (sc
.state
== SCE_B_OPERATOR
) {
345 if (!IsOperator(sc
.ch
) || sc
.Match('#'))
346 sc
.SetState(SCE_B_DEFAULT
);
347 } else if (sc
.state
== SCE_B_LABEL
) {
348 if (!IsIdentifier(sc
.ch
))
349 sc
.SetState(SCE_B_DEFAULT
);
350 } else if (sc
.state
== SCE_B_CONSTANT
) {
351 if (!IsIdentifier(sc
.ch
))
352 sc
.SetState(SCE_B_DEFAULT
);
353 } else if (sc
.state
== SCE_B_NUMBER
) {
355 sc
.SetState(SCE_B_DEFAULT
);
356 } else if (sc
.state
== SCE_B_HEXNUMBER
) {
357 if (!IsHexDigit(sc
.ch
))
358 sc
.SetState(SCE_B_DEFAULT
);
359 } else if (sc
.state
== SCE_B_BINNUMBER
) {
360 if (!IsBinDigit(sc
.ch
))
361 sc
.SetState(SCE_B_DEFAULT
);
362 } else if (sc
.state
== SCE_B_STRING
) {
364 sc
.ForwardSetState(SCE_B_DEFAULT
);
367 sc
.ChangeState(SCE_B_ERROR
);
368 sc
.SetState(SCE_B_DEFAULT
);
370 } else if (sc
.state
== SCE_B_COMMENT
|| sc
.state
== SCE_B_PREPROCESSOR
) {
372 sc
.SetState(SCE_B_DEFAULT
);
379 if (sc
.state
== SCE_B_DEFAULT
|| sc
.state
== SCE_B_ERROR
) {
380 if (isfirst
&& sc
.Match('.')) {
381 sc
.SetState(SCE_B_LABEL
);
382 } else if (isfirst
&& sc
.Match('#')) {
384 sc
.SetState(SCE_B_IDENTIFIER
);
385 } else if (sc
.Match(comment_char
)) {
386 // Hack to make deprecated QBASIC '$Include show
387 // up in freebasic with SCE_B_PREPROCESSOR.
388 if (comment_char
== '\'' && sc
.Match(comment_char
, '$'))
389 sc
.SetState(SCE_B_PREPROCESSOR
);
391 sc
.SetState(SCE_B_COMMENT
);
392 } else if (sc
.Match('"')) {
393 sc
.SetState(SCE_B_STRING
);
394 } else if (IsDigit(sc
.ch
)) {
395 sc
.SetState(SCE_B_NUMBER
);
396 } else if (sc
.Match('$')) {
397 sc
.SetState(SCE_B_HEXNUMBER
);
398 } else if (sc
.Match('%')) {
399 sc
.SetState(SCE_B_BINNUMBER
);
400 } else if (sc
.Match('#')) {
401 sc
.SetState(SCE_B_CONSTANT
);
402 } else if (IsOperator(sc
.ch
)) {
403 sc
.SetState(SCE_B_OPERATOR
);
404 } else if (IsIdentifier(sc
.ch
)) {
406 sc
.SetState(SCE_B_IDENTIFIER
);
407 } else if (!IsSpace(sc
.ch
)) {
408 sc
.SetState(SCE_B_ERROR
);
422 void SCI_METHOD
LexerBasic::Fold(unsigned int startPos
, int length
, int /* initStyle */, IDocument
*pAccess
) {
427 LexAccessor
styler(pAccess
);
429 int line
= styler
.GetLine(startPos
);
430 int level
= styler
.LevelAt(line
);
431 int go
= 0, done
= 0;
432 int endPos
= startPos
+ length
;
435 const bool userDefinedFoldMarkers
= !options
.foldExplicitStart
.empty() && !options
.foldExplicitEnd
.empty();
436 int cNext
= styler
[startPos
];
438 // Scan for tokens at the start of the line (they may include
439 // whitespace, for tokens like "End Function"
440 for (int i
= startPos
; i
< endPos
; i
++) {
442 cNext
= styler
.SafeGetCharAt(i
+ 1);
443 bool atEOL
= (c
== '\r' && cNext
!= '\n') || (c
== '\n');
444 if (options
.foldSyntaxBased
&& !done
&& !go
) {
445 if (wordlen
) { // are we scanning a token already?
446 word
[wordlen
] = static_cast<char>(LowerCase(c
));
447 if (!IsIdentifier(c
)) { // done with token
448 word
[wordlen
] = '\0';
449 go
= CheckFoldPoint(word
, level
);
451 // Treat any whitespace as single blank, for
452 // things like "End Function".
453 if (IsSpace(c
) && IsIdentifier(word
[wordlen
- 1])) {
458 else // done with this line
461 } else if (wordlen
< 255) {
464 } else { // start scanning at first non-whitespace character
466 if (IsIdentifier(c
)) {
467 word
[0] = static_cast<char>(LowerCase(c
));
469 } else // done with this line
474 if (options
.foldCommentExplicit
&& ((styler
.StyleAt(i
) == SCE_B_COMMENT
) || options
.foldExplicitAnywhere
)) {
475 if (userDefinedFoldMarkers
) {
476 if (styler
.Match(i
, options
.foldExplicitStart
.c_str())) {
477 level
|= SC_FOLDLEVELHEADERFLAG
;
479 } else if (styler
.Match(i
, options
.foldExplicitEnd
.c_str())) {
483 if (c
== comment_char
) {
485 level
|= SC_FOLDLEVELHEADERFLAG
;
487 } else if (cNext
== '}') {
493 if (atEOL
) { // line end
494 if (!done
&& wordlen
== 0 && options
.foldCompact
) // line was only space
495 level
|= SC_FOLDLEVELWHITEFLAG
;
496 if (level
!= styler
.LevelAt(line
))
497 styler
.SetLevel(line
, level
);
502 level
&= ~SC_FOLDLEVELHEADERFLAG
;
503 level
&= ~SC_FOLDLEVELWHITEFLAG
;
510 LexerModule
lmBlitzBasic(SCLEX_BLITZBASIC
, LexerBasic::LexerFactoryBlitzBasic
, "blitzbasic", blitzbasicWordListDesc
);
512 LexerModule
lmPureBasic(SCLEX_PUREBASIC
, LexerBasic::LexerFactoryPureBasic
, "purebasic", purebasicWordListDesc
);
514 LexerModule
lmFreeBasic(SCLEX_FREEBASIC
, LexerBasic::LexerFactoryFreeBasic
, "freebasic", freebasicWordListDesc
);