1 // Scintilla source code edit control
2 // Copyright 1998-2001 by Neil Hodgson <neilh@scintilla.org>
3 // The License.txt file describes the conditions under which this software may be distributed.
4 /** @file LexErlang.cxx
6 ** Enhanced by Etienne 'Lenain' Girondel (lenaing@gmail.com)
7 ** Originally wrote by Peter-Henry Mander,
8 ** based on Matlab lexer by José Fonseca.
19 #include "Scintilla.h"
23 #include "LexAccessor.h"
25 #include "StyleContext.h"
26 #include "CharacterSet.h"
27 #include "LexerModule.h"
30 using namespace Scintilla
;
33 static int is_radix(int radix
, int ch
) {
36 if (36 < radix
|| 2 > radix
)
41 } else if (isalnum(ch
)) {
42 digit
= toupper(ch
) - 'A' + 10;
47 return (digit
< radix
);
74 static inline bool IsAWordChar(const int ch
) {
75 return (ch
< 0x80) && (ch
!= ' ') && (isalnum(ch
) || ch
== '_');
78 static void ColouriseErlangDoc(unsigned int startPos
, int length
, int initStyle
,
79 WordList
*keywordlists
[], Accessor
&styler
) {
81 StyleContext
sc(startPos
, length
, initStyle
, styler
);
82 WordList
&reservedWords
= *keywordlists
[0];
83 WordList
&erlangBIFs
= *keywordlists
[1];
84 WordList
&erlangPreproc
= *keywordlists
[2];
85 WordList
&erlangModulesAtt
= *keywordlists
[3];
86 WordList
&erlangDoc
= *keywordlists
[4];
87 WordList
&erlangDocMacro
= *keywordlists
[5];
89 int exponent_digits
= 0;
90 atom_parse_state_t parse_state
= STATE_NULL
;
91 atom_parse_state_t old_parse_state
= STATE_NULL
;
92 bool to_late_to_comment
= false;
94 int old_style
= SCE_ERLANG_DEFAULT
;
96 styler
.StartAt(startPos
);
98 for (; sc
.More(); sc
.Forward()) {
99 int style
= SCE_ERLANG_DEFAULT
;
100 if (STATE_NULL
!= parse_state
) {
102 switch (parse_state
) {
104 case STATE_NULL
: sc
.SetState(SCE_ERLANG_DEFAULT
); break;
106 /* COMMENTS ------------------------------------------------------*/
109 to_late_to_comment
= true;
110 } else if (!to_late_to_comment
&& sc
.ch
== '%') {
111 // Switch to comment level 2 (Function)
112 sc
.ChangeState(SCE_ERLANG_COMMENT_FUNCTION
);
113 old_style
= SCE_ERLANG_COMMENT_FUNCTION
;
114 parse_state
= COMMENT_FUNCTION
;
118 // V--- Falling through!
119 case COMMENT_FUNCTION
: {
121 to_late_to_comment
= true;
122 } else if (!to_late_to_comment
&& sc
.ch
== '%') {
123 // Switch to comment level 3 (Module)
124 sc
.ChangeState(SCE_ERLANG_COMMENT_MODULE
);
125 old_style
= SCE_ERLANG_COMMENT_MODULE
;
126 parse_state
= COMMENT_MODULE
;
130 // V--- Falling through!
131 case COMMENT_MODULE
: {
132 if (parse_state
!= COMMENT
) {
133 // Search for comment documentation
134 if (sc
.chNext
== '@') {
135 old_parse_state
= parse_state
;
136 parse_state
= ('{' == sc
.ch
)
139 sc
.ForwardSetState(sc
.state
);
143 // All comments types fall here.
145 to_late_to_comment
= false;
146 sc
.SetState(SCE_ERLANG_DEFAULT
);
147 parse_state
= STATE_NULL
;
152 // V--- Falling through!
153 case COMMENT_DOC_MACRO
: {
155 if (!isalnum(sc
.ch
)) {
156 // Try to match documentation comment
157 sc
.GetCurrent(cur
, sizeof(cur
));
159 if (parse_state
== COMMENT_DOC_MACRO
160 && erlangDocMacro
.InList(cur
)) {
161 sc
.ChangeState(SCE_ERLANG_COMMENT_DOC_MACRO
);
162 while (sc
.ch
!= '}' && !sc
.atLineEnd
)
164 } else if (erlangDoc
.InList(cur
)) {
165 sc
.ChangeState(SCE_ERLANG_COMMENT_DOC
);
167 sc
.ChangeState(old_style
);
170 // Switch back to old state
171 sc
.SetState(old_style
);
172 parse_state
= old_parse_state
;
176 to_late_to_comment
= false;
177 sc
.ChangeState(old_style
);
178 sc
.SetState(SCE_ERLANG_DEFAULT
);
179 parse_state
= STATE_NULL
;
183 /* -------------------------------------------------------------- */
184 /* Atoms ---------------------------------------------------------*/
185 case ATOM_UNQUOTED
: {
187 parse_state
= NODE_NAME_UNQUOTED
;
188 } else if (sc
.ch
== ':') {
189 // Searching for module name
190 if (sc
.chNext
== ' ') {
192 sc
.ChangeState(SCE_ERLANG_UNKNOWN
);
193 parse_state
= STATE_NULL
;
196 if (isalnum(sc
.ch
)) {
197 sc
.GetCurrent(cur
, sizeof(cur
));
198 sc
.ChangeState(SCE_ERLANG_MODULES
);
199 sc
.SetState(SCE_ERLANG_MODULES
);
202 } else if (!IsAWordChar(sc
.ch
)) {
204 sc
.GetCurrent(cur
, sizeof(cur
));
205 if (reservedWords
.InList(cur
)) {
206 style
= SCE_ERLANG_KEYWORD
;
207 } else if (erlangBIFs
.InList(cur
)
208 && strcmp(cur
,"erlang:")){
209 style
= SCE_ERLANG_BIFS
;
210 } else if (sc
.ch
== '(' || '/' == sc
.ch
){
211 style
= SCE_ERLANG_FUNCTION_NAME
;
213 style
= SCE_ERLANG_ATOM
;
216 sc
.ChangeState(style
);
217 sc
.SetState(SCE_ERLANG_DEFAULT
);
218 parse_state
= STATE_NULL
;
225 parse_state
= NODE_NAME_QUOTED
;
226 } else if ('\'' == sc
.ch
&& '\\' != sc
.chPrev
) {
227 sc
.ChangeState(SCE_ERLANG_ATOM
);
228 sc
.ForwardSetState(SCE_ERLANG_DEFAULT
);
229 parse_state
= STATE_NULL
;
233 /* -------------------------------------------------------------- */
234 /* Node names ----------------------------------------------------*/
235 case NODE_NAME_UNQUOTED
: {
237 sc
.SetState(SCE_ERLANG_DEFAULT
);
238 parse_state
= STATE_NULL
;
239 } else if (!IsAWordChar(sc
.ch
)) {
240 sc
.ChangeState(SCE_ERLANG_NODE_NAME
);
241 sc
.SetState(SCE_ERLANG_DEFAULT
);
242 parse_state
= STATE_NULL
;
246 case NODE_NAME_QUOTED
: {
248 sc
.SetState(SCE_ERLANG_DEFAULT
);
249 parse_state
= STATE_NULL
;
250 } else if ('\'' == sc
.ch
&& '\\' != sc
.chPrev
) {
251 sc
.ChangeState(SCE_ERLANG_NODE_NAME_QUOTED
);
252 sc
.ForwardSetState(SCE_ERLANG_DEFAULT
);
253 parse_state
= STATE_NULL
;
257 /* -------------------------------------------------------------- */
258 /* Records -------------------------------------------------------*/
259 case RECORD_START
: {
261 parse_state
= RECORD_QUOTED
;
262 } else if (isalpha(sc
.ch
) && islower(sc
.ch
)) {
263 parse_state
= RECORD_UNQUOTED
;
265 sc
.SetState(SCE_ERLANG_DEFAULT
);
266 parse_state
= STATE_NULL
;
270 case RECORD_UNQUOTED
: {
271 if (!IsAWordChar(sc
.ch
)) {
272 sc
.ChangeState(SCE_ERLANG_RECORD
);
273 sc
.SetState(SCE_ERLANG_DEFAULT
);
274 parse_state
= STATE_NULL
;
278 case RECORD_QUOTED
: {
279 if ('\'' == sc
.ch
&& '\\' != sc
.chPrev
) {
280 sc
.ChangeState(SCE_ERLANG_RECORD_QUOTED
);
281 sc
.ForwardSetState(SCE_ERLANG_DEFAULT
);
282 parse_state
= STATE_NULL
;
286 /* -------------------------------------------------------------- */
287 /* Macros --------------------------------------------------------*/
290 parse_state
= MACRO_QUOTED
;
291 } else if (isalpha(sc
.ch
)) {
292 parse_state
= MACRO_UNQUOTED
;
294 sc
.SetState(SCE_ERLANG_DEFAULT
);
295 parse_state
= STATE_NULL
;
299 case MACRO_UNQUOTED
: {
300 if (!IsAWordChar(sc
.ch
)) {
301 sc
.ChangeState(SCE_ERLANG_MACRO
);
302 sc
.SetState(SCE_ERLANG_DEFAULT
);
303 parse_state
= STATE_NULL
;
307 case MACRO_QUOTED
: {
308 if ('\'' == sc
.ch
&& '\\' != sc
.chPrev
) {
309 sc
.ChangeState(SCE_ERLANG_MACRO_QUOTED
);
310 sc
.ForwardSetState(SCE_ERLANG_DEFAULT
);
311 parse_state
= STATE_NULL
;
315 /* -------------------------------------------------------------- */
316 /* Numerics ------------------------------------------------------*/
318 case NUMERAL_START
: {
319 if (isdigit(sc
.ch
)) {
321 radix_digits
+= sc
.ch
- '0'; // Assuming ASCII here!
322 } else if ('#' == sc
.ch
) {
323 if (2 > radix_digits
|| 36 < radix_digits
) {
324 sc
.SetState(SCE_ERLANG_DEFAULT
);
325 parse_state
= STATE_NULL
;
327 parse_state
= NUMERAL_BASE_VALUE
;
329 } else if ('.' == sc
.ch
&& isdigit(sc
.chNext
)) {
331 parse_state
= NUMERAL_FLOAT
;
332 } else if ('e' == sc
.ch
|| 'E' == sc
.ch
) {
334 parse_state
= NUMERAL_EXPONENT
;
337 sc
.ChangeState(SCE_ERLANG_NUMBER
);
338 sc
.SetState(SCE_ERLANG_DEFAULT
);
339 parse_state
= STATE_NULL
;
343 /* Integer in other base than 10 (x#yyy) */
344 case NUMERAL_BASE_VALUE
: {
345 if (!is_radix(radix_digits
,sc
.ch
)) {
349 sc
.ChangeState(SCE_ERLANG_NUMBER
);
351 sc
.SetState(SCE_ERLANG_DEFAULT
);
352 parse_state
= STATE_NULL
;
357 case NUMERAL_FLOAT
: {
358 if ('e' == sc
.ch
|| 'E' == sc
.ch
) {
360 parse_state
= NUMERAL_EXPONENT
;
361 } else if (!isdigit(sc
.ch
)) {
362 sc
.ChangeState(SCE_ERLANG_NUMBER
);
363 sc
.SetState(SCE_ERLANG_DEFAULT
);
364 parse_state
= STATE_NULL
;
368 /* Exponent, either integer or float (xEyy, x.yyEzzz) */
369 case NUMERAL_EXPONENT
: {
370 if (('-' == sc
.ch
|| '+' == sc
.ch
)
371 && (isdigit(sc
.chNext
))) {
373 } else if (!isdigit(sc
.ch
)) {
374 if (0 < exponent_digits
)
375 sc
.ChangeState(SCE_ERLANG_NUMBER
);
376 sc
.SetState(SCE_ERLANG_DEFAULT
);
377 parse_state
= STATE_NULL
;
383 /* -------------------------------------------------------------- */
384 /* Preprocessor --------------------------------------------------*/
385 case PREPROCESSOR
: {
386 if (!IsAWordChar(sc
.ch
)) {
388 sc
.GetCurrent(cur
, sizeof(cur
));
389 if (erlangPreproc
.InList(cur
)) {
390 style
= SCE_ERLANG_PREPROC
;
391 } else if (erlangModulesAtt
.InList(cur
)) {
392 style
= SCE_ERLANG_MODULES_ATT
;
395 sc
.ChangeState(style
);
396 sc
.SetState(SCE_ERLANG_DEFAULT
);
397 parse_state
= STATE_NULL
;
403 } /* End of : STATE_NULL != parse_state */
407 case SCE_ERLANG_VARIABLE
: {
408 if (!IsAWordChar(sc
.ch
))
409 sc
.SetState(SCE_ERLANG_DEFAULT
);
411 case SCE_ERLANG_STRING
: {
412 if (sc
.ch
== '\"' && sc
.chPrev
!= '\\')
413 sc
.ForwardSetState(SCE_ERLANG_DEFAULT
);
415 case SCE_ERLANG_COMMENT
: {
417 sc
.SetState(SCE_ERLANG_DEFAULT
);
419 case SCE_ERLANG_CHARACTER
: {
420 if (sc
.chPrev
== '\\') {
421 sc
.ForwardSetState(SCE_ERLANG_DEFAULT
);
422 } else if (sc
.ch
!= '\\') {
423 sc
.ForwardSetState(SCE_ERLANG_DEFAULT
);
426 case SCE_ERLANG_OPERATOR
: {
427 if (sc
.chPrev
== '.') {
428 if (sc
.ch
== '*' || sc
.ch
== '/' || sc
.ch
== '\\'
430 sc
.ForwardSetState(SCE_ERLANG_DEFAULT
);
431 } else if (sc
.ch
== '\'') {
432 sc
.ForwardSetState(SCE_ERLANG_DEFAULT
);
434 sc
.SetState(SCE_ERLANG_DEFAULT
);
437 sc
.SetState(SCE_ERLANG_DEFAULT
);
443 if (sc
.state
== SCE_ERLANG_DEFAULT
) {
444 bool no_new_state
= false;
447 case '\"' : sc
.SetState(SCE_ERLANG_STRING
); break;
448 case '$' : sc
.SetState(SCE_ERLANG_CHARACTER
); break;
450 parse_state
= COMMENT
;
451 sc
.SetState(SCE_ERLANG_COMMENT
);
454 parse_state
= RECORD_START
;
455 sc
.SetState(SCE_ERLANG_UNKNOWN
);
458 parse_state
= MACRO_START
;
459 sc
.SetState(SCE_ERLANG_UNKNOWN
);
462 parse_state
= ATOM_QUOTED
;
463 sc
.SetState(SCE_ERLANG_UNKNOWN
);
467 if (IsADigit(sc
.chNext
)) {
468 parse_state
= NUMERAL_START
;
470 sc
.SetState(SCE_ERLANG_UNKNOWN
);
471 } else if (sc
.ch
!= '+') {
472 parse_state
= PREPROCESSOR
;
473 sc
.SetState(SCE_ERLANG_UNKNOWN
);
476 default : no_new_state
= true;
480 if (isdigit(sc
.ch
)) {
481 parse_state
= NUMERAL_START
;
482 radix_digits
= sc
.ch
- '0';
483 sc
.SetState(SCE_ERLANG_UNKNOWN
);
484 } else if (isupper(sc
.ch
) || '_' == sc
.ch
) {
485 sc
.SetState(SCE_ERLANG_VARIABLE
);
486 } else if (isalpha(sc
.ch
)) {
487 parse_state
= ATOM_UNQUOTED
;
488 sc
.SetState(SCE_ERLANG_UNKNOWN
);
489 } else if (isoperator(static_cast<char>(sc
.ch
))
491 sc
.SetState(SCE_ERLANG_OPERATOR
);
500 static int ClassifyErlangFoldPoint(
506 if (styler
.Match(keyword_start
,"case")
508 styler
.Match(keyword_start
,"fun")
509 && (SCE_ERLANG_FUNCTION_NAME
!= styleNext
)
511 || styler
.Match(keyword_start
,"if")
512 || styler
.Match(keyword_start
,"query")
513 || styler
.Match(keyword_start
,"receive")
516 } else if (styler
.Match(keyword_start
,"end")) {
523 static void FoldErlangDoc(
524 unsigned int startPos
, int length
, int initStyle
,
525 WordList
** /*keywordlists*/, Accessor
&styler
527 unsigned int endPos
= startPos
+ length
;
528 int currentLine
= styler
.GetLine(startPos
);
530 int previousLevel
= styler
.LevelAt(currentLine
) & SC_FOLDLEVELNUMBERMASK
;
531 int currentLevel
= previousLevel
;
532 int styleNext
= styler
.StyleAt(startPos
);
533 int style
= initStyle
;
535 int keyword_start
= 0;
537 char chNext
= styler
.SafeGetCharAt(startPos
);
540 for (unsigned int i
= startPos
; i
< endPos
; i
++) {
542 chNext
= styler
.SafeGetCharAt(i
+ 1);
547 styleNext
= styler
.StyleAt(i
+ 1);
548 atEOL
= ((ch
== '\r') && (chNext
!= '\n')) || (ch
== '\n');
550 if (stylePrev
!= SCE_ERLANG_KEYWORD
551 && style
== SCE_ERLANG_KEYWORD
) {
556 if (stylePrev
== SCE_ERLANG_KEYWORD
557 && style
!= SCE_ERLANG_KEYWORD
558 && style
!= SCE_ERLANG_ATOM
560 currentLevel
+= ClassifyErlangFoldPoint(styler
,
566 if (style
== SCE_ERLANG_COMMENT
567 || style
== SCE_ERLANG_COMMENT_MODULE
568 || style
== SCE_ERLANG_COMMENT_FUNCTION
) {
570 if (ch
== '%' && chNext
== '{') {
572 } else if (ch
== '%' && chNext
== '}') {
578 if (style
== SCE_ERLANG_OPERATOR
) {
579 if (ch
== '{' || ch
== '(' || ch
== '[') {
581 } else if (ch
== '}' || ch
== ')' || ch
== ']') {
590 if (currentLevel
> previousLevel
)
591 lev
|= SC_FOLDLEVELHEADERFLAG
;
593 if (lev
!= styler
.LevelAt(currentLine
))
594 styler
.SetLevel(currentLine
, lev
);
597 previousLevel
= currentLevel
;
602 // Fill in the real level of the next line, keeping the current flags as they will be filled in later
603 styler
.SetLevel(currentLine
,
605 | (styler
.LevelAt(currentLine
) & ~SC_FOLDLEVELNUMBERMASK
));
608 static const char * const erlangWordListDesc
[] = {
609 "Erlang Reserved words",
611 "Erlang Preprocessor",
612 "Erlang Module Attributes",
613 "Erlang Documentation",
614 "Erlang Documentation Macro",
618 LexerModule
lmErlang(