1 // Scintilla source code edit control
2 /** @file LexNim.cxx
3 ** Lexer for Nim
4 ** Written by Jad Altahan (github.com/xv)
5 ** Nim manual: https://nim-lang.org/docs/manual.html
6 **/
7 // Copyright 1998-2001 by Neil Hodgson <neilh@scintilla.org>
8 // The License.txt file describes the conditions under which this software may be distributed.
9 
10 #include <stdlib.h>
11 #include <string.h>
12 #include <stdio.h>
13 #include <stdarg.h>
14 #include <assert.h>
15 #include <ctype.h>
16 
17 #include <string>
18 #include <map>
19 #include <algorithm>
20 
21 #include "ILexer.h"
22 #include "Scintilla.h"
23 #include "SciLexer.h"
24 
25 #include "StringCopy.h"
26 #include "WordList.h"
27 #include "LexAccessor.h"
28 #include "Accessor.h"
29 #include "StyleContext.h"
30 #include "CharacterSet.h"
31 #include "LexerModule.h"
32 #include "OptionSet.h"
33 #include "DefaultLexer.h"
34 
35 using namespace Scintilla;
36 
37 namespace {
38     // Use an unnamed namespace to protect the functions and classes from name conflicts
39 
40 enum NumType {
41     Binary,
42     Octal,
43     Exponent,
44     Hexadecimal,
45     Decimal,
46     FormatError
47 };
48 
GetNumStyle(const int numType)49 int GetNumStyle(const int numType) noexcept {
50     if (numType == NumType::FormatError) {
51         return SCE_NIM_NUMERROR;
52     }
53 
54     return SCE_NIM_NUMBER;
55 }
56 
IsLetter(const int ch)57 constexpr bool IsLetter(const int ch) noexcept {
58     // 97 to 122 || 65 to 90
59     return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
60 }
61 
IsAWordChar(const int ch)62 bool IsAWordChar(const int ch) noexcept {
63     return ch < 0x80 && (isalnum(ch) || ch == '_' || ch == '.');
64 }
65 
IsNumHex(const StyleContext & sc)66 int IsNumHex(const StyleContext &sc) noexcept {
67     return sc.chNext == 'x' || sc.chNext == 'X';
68 }
69 
IsNumBinary(const StyleContext & sc)70 int IsNumBinary(const StyleContext &sc) noexcept {
71     return sc.chNext == 'b' || sc.chNext == 'B';
72 }
73 
IsNumOctal(const StyleContext & sc)74 int IsNumOctal(const StyleContext &sc) {
75     return IsADigit(sc.chNext) || sc.chNext == 'o';
76 }
77 
IsNewline(const int ch)78 constexpr bool IsNewline(const int ch) noexcept {
79     return (ch == '\n' || ch == '\r');
80 }
81 
IsFuncName(const char * str)82 bool IsFuncName(const char *str) noexcept {
83     const char *identifiers[] = {
84         "proc",
85         "func",
86         "macro",
87         "method",
88         "template",
89         "iterator",
90         "converter"
91     };
92 
93     for (const char *id : identifiers) {
94         if (strcmp(str, id) == 0) {
95             return true;
96         }
97     }
98 
99     return false;
100 }
101 
IsTripleLiteral(const int style)102 constexpr bool IsTripleLiteral(const int style) noexcept {
103     return style == SCE_NIM_TRIPLE || style == SCE_NIM_TRIPLEDOUBLE;
104 }
105 
IsLineComment(const int style)106 constexpr bool IsLineComment(const int style) noexcept {
107     return style == SCE_NIM_COMMENTLINE || style == SCE_NIM_COMMENTLINEDOC;
108 }
109 
IsStreamComment(const int style)110 constexpr bool IsStreamComment(const int style) noexcept {
111     return style == SCE_NIM_COMMENT || style == SCE_NIM_COMMENTDOC;
112 }
113 
114 // Adopted from Accessor.cxx
GetIndent(const Sci_Position line,Accessor & styler)115 int GetIndent(const Sci_Position line, Accessor &styler) {
116     Sci_Position startPos = styler.LineStart(line);
117     const Sci_Position eolPos = styler.LineStart(line + 1) - 1;
118 
119     char ch = styler[startPos];
120     int style = styler.StyleAt(startPos);
121 
122     int indent = 0;
123     bool inPrevPrefix = line > 0;
124     Sci_Position posPrev = inPrevPrefix ? styler.LineStart(line - 1) : 0;
125 
126     // No fold points inside triple literals
127     while ((IsASpaceOrTab(ch) || IsTripleLiteral(style)) && (startPos < eolPos)) {
128         if (inPrevPrefix) {
129             const char chPrev = styler[posPrev++];
130             if (chPrev != ' ' && chPrev != '\t') {
131                 inPrevPrefix = false;
132             }
133         }
134 
135         if (ch == '\t') {
136             indent = (indent / 8 + 1) * 8;
137         } else {
138             indent++;
139         }
140 
141         startPos++;
142         ch = styler[startPos];
143         style = styler.StyleAt(startPos);
144     }
145 
146     // Prevent creating fold lines for comments if indented
147     if (!(IsStreamComment(style) || IsLineComment(style)))
148         indent += SC_FOLDLEVELBASE;
149 
150     if (styler.LineStart(line) == styler.Length()
151         || IsASpaceOrTab(ch)
152         || IsNewline(ch)
153         || IsStreamComment(style)
154         || IsLineComment(style)) {
155         return indent | SC_FOLDLEVELWHITEFLAG;
156     } else {
157         return indent;
158     }
159 }
160 
IndentAmount(const Sci_Position line,Accessor & styler)161 int IndentAmount(const Sci_Position line, Accessor &styler) {
162     const int indent = GetIndent(line, styler);
163     const int indentLevel = indent & SC_FOLDLEVELNUMBERMASK;
164     return indentLevel <= SC_FOLDLEVELBASE ? indent : indentLevel | (indent & ~SC_FOLDLEVELNUMBERMASK);
165 }
166 
167 struct OptionsNim {
168     bool fold;
169     bool foldCompact;
170     bool highlightRawStrIdent;
171 
OptionsNim__anon5197a16d0111::OptionsNim172     OptionsNim() {
173         fold = true;
174         foldCompact = true;
175         highlightRawStrIdent = false;
176     }
177 };
178 
179 static const char *const nimWordListDesc[] = {
180     "Keywords",
181     nullptr
182 };
183 
184 struct OptionSetNim : public OptionSet<OptionsNim> {
OptionSetNim__anon5197a16d0111::OptionSetNim185     OptionSetNim() {
186         DefineProperty("lexer.nim.raw.strings.highlight.ident", &OptionsNim::highlightRawStrIdent,
187             "Set to 1 to enable highlighting generalized raw string identifiers. "
188             "Generalized raw string identifiers are anything other than r (or R).");
189 
190         DefineProperty("fold", &OptionsNim::fold);
191         DefineProperty("fold.compact", &OptionsNim::foldCompact);
192 
193         DefineWordListSets(nimWordListDesc);
194     }
195 };
196 
197 LexicalClass lexicalClasses[] = {
198     // Lexer Nim SCLEX_NIM SCE_NIM_:
199     0,  "SCE_NIM_DEFAULT",        "default",              "White space",
200     1,  "SCE_NIM_COMMENT",        "comment block",        "Block comment",
201     2,  "SCE_NIM_COMMENTDOC",     "comment block doc",    "Block doc comment",
202     3,  "SCE_NIM_COMMENTLINE",    "comment line",         "Line comment",
203     4,  "SCE_NIM_COMMENTLINEDOC", "comment doc",          "Line doc comment",
204     5,  "SCE_NIM_NUMBER",         "literal numeric",      "Number",
205     6,  "SCE_NIM_STRING",         "literal string",       "String",
206     7,  "SCE_NIM_CHARACTER",      "literal string",       "Single quoted string",
207     8,  "SCE_NIM_WORD",           "keyword",              "Keyword",
208     9,  "SCE_NIM_TRIPLE",         "literal string",       "Triple quotes",
209     10, "SCE_NIM_TRIPLEDOUBLE",   "literal string",       "Triple double quotes",
210     11, "SCE_NIM_BACKTICKS",      "operator definition",  "Identifiers",
211     12, "SCE_NIM_FUNCNAME",       "identifier",           "Function name definition",
212     13, "SCE_NIM_STRINGEOL",      "error literal string", "String is not closed",
213     14, "SCE_NIM_NUMERROR",       "numeric error",        "Numeric format error",
214     15, "SCE_NIM_OPERATOR",       "operator",             "Operators",
215     16, "SCE_NIM_IDENTIFIER",     "identifier",           "Identifiers",
216 };
217 
218 }
219 
220 class LexerNim : public DefaultLexer {
221     CharacterSet setWord;
222     WordList keywords;
223     OptionsNim options;
224     OptionSetNim osNim;
225 
226 public:
LexerNim()227     LexerNim() :
228         DefaultLexer("nim", SCLEX_NIM, lexicalClasses, ELEMENTS(lexicalClasses)),
229         setWord(CharacterSet::setAlphaNum, "_", 0x80, true) { }
230 
~LexerNim()231     virtual ~LexerNim() { }
232 
Release()233     void SCI_METHOD Release() noexcept override {
234         delete this;
235     }
236 
Version() const237     int SCI_METHOD Version() const noexcept override {
238         return lvRelease5;
239     }
240 
PropertyNames()241     const char * SCI_METHOD PropertyNames() override {
242         return osNim.PropertyNames();
243     }
244 
PropertyType(const char * name)245     int SCI_METHOD PropertyType(const char *name) override {
246         return osNim.PropertyType(name);
247     }
248 
DescribeProperty(const char * name)249     const char * SCI_METHOD DescribeProperty(const char *name) override {
250         return osNim.DescribeProperty(name);
251     }
252 
253     Sci_Position SCI_METHOD PropertySet(const char *key, const char *val) override;
254 
PropertyGet(const char * key)255 	const char * SCI_METHOD PropertyGet(const char* key) override {
256 		return osNim.PropertyGet(key);
257 	}
258 
DescribeWordListSets()259     const char * SCI_METHOD DescribeWordListSets() override {
260         return osNim.DescribeWordListSets();
261     }
262 
263     Sci_Position SCI_METHOD WordListSet(int n, const char *wl) override;
264 
265     void SCI_METHOD Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override;
266     void SCI_METHOD Fold(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override;
267 
PrivateCall(int,void *)268     void * SCI_METHOD PrivateCall(int, void *) noexcept override {
269         return nullptr;
270     }
271 
LineEndTypesSupported()272     int SCI_METHOD LineEndTypesSupported() noexcept override {
273         return SC_LINE_END_TYPE_UNICODE;
274     }
275 
PrimaryStyleFromStyle(int style)276     int SCI_METHOD PrimaryStyleFromStyle(int style) noexcept override {
277         return style;
278     }
279 
LexerFactoryNim()280     static ILexer5 *LexerFactoryNim() {
281         return new LexerNim();
282     }
283 };
284 
PropertySet(const char * key,const char * val)285 Sci_Position SCI_METHOD LexerNim::PropertySet(const char *key, const char *val) {
286     if (osNim.PropertySet(&options, key, val)) {
287         return 0;
288     }
289 
290     return -1;
291 }
292 
WordListSet(int n,const char * wl)293 Sci_Position SCI_METHOD LexerNim::WordListSet(int n, const char *wl) {
294     WordList *wordListN = nullptr;
295 
296     switch (n) {
297         case 0:
298             wordListN = &keywords;
299             break;
300     }
301 
302     Sci_Position firstModification = -1;
303 
304     if (wordListN) {
305         WordList wlNew;
306         wlNew.Set(wl);
307 
308         if (*wordListN != wlNew) {
309             wordListN->Set(wl);
310             firstModification = 0;
311         }
312     }
313 
314     return firstModification;
315 }
316 
Lex(Sci_PositionU startPos,Sci_Position length,int initStyle,IDocument * pAccess)317 void SCI_METHOD LexerNim::Lex(Sci_PositionU startPos, Sci_Position length,
318                               int initStyle, IDocument *pAccess) {
319     // No one likes a leaky string
320     if (initStyle == SCE_NIM_STRINGEOL) {
321         initStyle = SCE_NIM_DEFAULT;
322     }
323 
324     Accessor styler(pAccess, nullptr);
325     StyleContext sc(startPos, length, initStyle, styler);
326 
327     // Nim supports nested block comments!
328     Sci_Position lineCurrent = styler.GetLine(startPos);
329     int commentNestLevel = lineCurrent > 0 ? styler.GetLineState(lineCurrent - 1) : 0;
330 
331     int numType = NumType::Decimal;
332     int decimalCount = 0;
333 
334     bool funcNameExists = false;
335     bool isStylingRawString = false;
336     bool isStylingRawStringIdent = false;
337 
338     for (; sc.More(); sc.Forward()) {
339         if (sc.atLineStart) {
340             if (sc.state == SCE_NIM_STRING) {
341                 sc.SetState(SCE_NIM_STRING);
342             }
343 
344             lineCurrent = styler.GetLine(sc.currentPos);
345             styler.SetLineState(lineCurrent, commentNestLevel);
346         }
347 
348         // Handle string line continuation
349         if (sc.ch == '\\' && (sc.chNext == '\n' || sc.chNext == '\r') &&
350            (sc.state == SCE_NIM_STRING || sc.state == SCE_NIM_CHARACTER) && !isStylingRawString) {
351             sc.Forward();
352 
353             if (sc.ch == '\r' && sc.chNext == '\n') {
354                 sc.Forward();
355             }
356 
357             continue;
358         }
359 
360         switch (sc.state) {
361             case SCE_NIM_OPERATOR:
362                 funcNameExists = false;
363                 sc.SetState(SCE_NIM_DEFAULT);
364                 break;
365             case SCE_NIM_NUMBER:
366                 // For a type suffix, such as 0x80'u8
367                 if (sc.ch == '\'') {
368                     if (sc.chNext == 'i' || sc.chNext == 'I' ||
369                         sc.chNext == 'u' || sc.chNext == 'U' ||
370                         sc.chNext == 'f' || sc.chNext == 'F' ||
371                         sc.chNext == 'd' || sc.chNext == 'D') {
372                         sc.Forward(2);
373                     }
374                 } else if (sc.ch == '.') {
375                     if (IsADigit(sc.chNext)) {
376                         sc.Forward();
377                     } else if (numType <= NumType::Exponent) {
378                         sc.SetState(SCE_NIM_OPERATOR);
379                         break;
380                     } else {
381                         decimalCount++;
382 
383                         if (numType == NumType::Decimal) {
384                             if (decimalCount <= 1 && !IsAWordChar(sc.chNext)) {
385                                 break;
386                             }
387                         } else if (numType == NumType::Hexadecimal) {
388                             if (decimalCount <= 1 && IsADigit(sc.chNext, 16)) {
389                                 break;
390                             }
391 
392                             sc.SetState(SCE_NIM_OPERATOR);
393                             break;
394                         }
395                     }
396                 } else if (sc.ch == '_') {
397                     // Accept only one underscore between digits
398                     if (IsADigit(sc.chNext)) {
399                         sc.Forward();
400                     }
401                 } else if (numType == NumType::Decimal) {
402                     if (sc.chPrev != '\'' && (sc.ch == 'e' || sc.ch == 'E')) {
403                         numType = NumType::Exponent;
404 
405                         if (sc.chNext == '-' || sc.chNext == '+') {
406                             sc.Forward();
407                         }
408 
409                         break;
410                     }
411 
412                     if (IsADigit(sc.ch)) {
413                         break;
414                     }
415                 } else if (numType == NumType::Hexadecimal) {
416                     if (IsADigit(sc.ch, 16)) {
417                         break;
418                     }
419                 } else if (IsADigit(sc.ch)) {
420                     if (numType == NumType::Exponent) {
421                         break;
422                     }
423 
424                     if (numType == NumType::Octal) {
425                         // Accept only 0-7
426                         if (sc.ch <= '7') {
427                             break;
428                         }
429                     } else if (numType == NumType::Binary) {
430                         // Accept only 0 and 1
431                         if (sc.ch <= '1') {
432                             break;
433                         }
434                     }
435 
436                     numType = NumType::FormatError;
437                     break;
438                 }
439 
440                 sc.ChangeState(GetNumStyle(numType));
441                 sc.SetState(SCE_NIM_DEFAULT);
442                 break;
443             case SCE_NIM_IDENTIFIER:
444                 if (sc.ch == '.' || !IsAWordChar(sc.ch)) {
445                     char s[100];
446                     sc.GetCurrent(s, sizeof(s));
447                     int style = SCE_NIM_IDENTIFIER;
448 
449                     if (keywords.InList(s) && !funcNameExists) {
450                         // Prevent styling keywords if they are sub-identifiers
451                         const Sci_Position segStart = styler.GetStartSegment() - 1;
452                         if (segStart < 0 || styler.SafeGetCharAt(segStart, '\0') != '.') {
453                             style = SCE_NIM_WORD;
454                         }
455                     } else if (funcNameExists) {
456                         style = SCE_NIM_FUNCNAME;
457                     }
458 
459                     sc.ChangeState(style);
460                     sc.SetState(SCE_NIM_DEFAULT);
461 
462                     if (style == SCE_NIM_WORD) {
463                         funcNameExists = IsFuncName(s);
464                     } else {
465                         funcNameExists = false;
466                     }
467                 }
468 
469                 if (IsAlphaNumeric(sc.ch) && sc.chNext == '\"') {
470                     isStylingRawStringIdent = true;
471 
472                     if (options.highlightRawStrIdent) {
473                         if (styler.SafeGetCharAt(sc.currentPos + 2) == '\"' &&
474                             styler.SafeGetCharAt(sc.currentPos + 3) == '\"') {
475                             sc.ChangeState(SCE_NIM_TRIPLEDOUBLE);
476                         } else {
477                             sc.ChangeState(SCE_NIM_STRING);
478                         }
479                     }
480 
481                     sc.ForwardSetState(SCE_NIM_DEFAULT);
482                 }
483                 break;
484             case SCE_NIM_FUNCNAME:
485                 if (sc.ch == '`') {
486                     funcNameExists = false;
487                     sc.ForwardSetState(SCE_NIM_DEFAULT);
488                 } else if (sc.atLineEnd) {
489                     // Prevent leaking the style to the next line if not closed
490                     funcNameExists = false;
491 
492                     sc.ChangeState(SCE_NIM_STRINGEOL);
493                     sc.ForwardSetState(SCE_NIM_DEFAULT);
494                 }
495                 break;
496             case SCE_NIM_COMMENT:
497                 if (sc.Match(']', '#')) {
498                     if (commentNestLevel > 0) {
499                         commentNestLevel--;
500                     }
501 
502                     lineCurrent = styler.GetLine(sc.currentPos);
503                     styler.SetLineState(lineCurrent, commentNestLevel);
504                     sc.Forward();
505 
506                     if (commentNestLevel == 0) {
507                         sc.ForwardSetState(SCE_NIM_DEFAULT);
508                     }
509                 } else if (sc.Match('#', '[')) {
510                     commentNestLevel++;
511                     lineCurrent = styler.GetLine(sc.currentPos);
512                     styler.SetLineState(lineCurrent, commentNestLevel);
513                 }
514                 break;
515             case SCE_NIM_COMMENTDOC:
516                 if (sc.Match("]##")) {
517                     if (commentNestLevel > 0) {
518                         commentNestLevel--;
519                     }
520 
521                     lineCurrent = styler.GetLine(sc.currentPos);
522                     styler.SetLineState(lineCurrent, commentNestLevel);
523                     sc.Forward(2);
524 
525                     if (commentNestLevel == 0) {
526                         sc.ForwardSetState(SCE_NIM_DEFAULT);
527                     }
528                 } else if (sc.Match("##[")) {
529                     commentNestLevel++;
530                     lineCurrent = styler.GetLine(sc.currentPos);
531                     styler.SetLineState(lineCurrent, commentNestLevel);
532                 }
533                 break;
534             case SCE_NIM_COMMENTLINE:
535             case SCE_NIM_COMMENTLINEDOC:
536                 if (sc.atLineStart) {
537                     sc.SetState(SCE_NIM_DEFAULT);
538                 }
539                 break;
540             case SCE_NIM_STRING:
541                 if (!isStylingRawStringIdent && !isStylingRawString && sc.ch == '\\') {
542                     if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') {
543                         sc.Forward();
544                     }
545                 } else if (isStylingRawString && sc.ch == '\"' && sc.chNext == '\"') {
546                     // Forward in situations such as r"a""bc\" so that "bc\" wouldn't be
547                     // considered a string of its own
548                     sc.Forward();
549                 } else if (sc.ch == '\"') {
550                     sc.ForwardSetState(SCE_NIM_DEFAULT);
551                 } else if (sc.atLineEnd) {
552                     sc.ChangeState(SCE_NIM_STRINGEOL);
553                     sc.ForwardSetState(SCE_NIM_DEFAULT);
554                 }
555                 break;
556             case SCE_NIM_CHARACTER:
557                 if (sc.ch == '\\') {
558                     if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') {
559                         sc.Forward();
560                     }
561                 } else if (sc.ch == '\'') {
562                     sc.ForwardSetState(SCE_NIM_DEFAULT);
563                 } else if (sc.atLineEnd) {
564                     sc.ChangeState(SCE_NIM_STRINGEOL);
565                     sc.ForwardSetState(SCE_NIM_DEFAULT);
566                 }
567                 break;
568             case SCE_NIM_BACKTICKS:
569                 if (sc.ch == '`' ) {
570                     sc.ForwardSetState(SCE_NIM_DEFAULT);
571                 } else if (sc.atLineEnd) {
572                     sc.ChangeState(SCE_NIM_STRINGEOL);
573                     sc.ForwardSetState(SCE_NIM_DEFAULT);
574                 }
575                 break;
576             case SCE_NIM_TRIPLEDOUBLE:
577                 if (sc.Match(R"(""")")) {
578 
579                     // Outright forward all " after the closing """ as a triple double
580                     //
581                     // A valid example where this is needed is: """8 double quotes->""""""""
582                     // You can have as many """ at the end as you wish, as long as the actual
583                     // closing literal is there
584                     while (sc.ch == '"') {
585                         sc.Forward();
586                     }
587 
588                     sc.SetState(SCE_NIM_DEFAULT);
589                 }
590                 break;
591             case SCE_NIM_TRIPLE:
592                 if (sc.Match("'''")) {
593                     sc.Forward(2);
594                     sc.ForwardSetState(SCE_NIM_DEFAULT);
595                 }
596                 break;
597         }
598 
599         if (sc.state == SCE_NIM_DEFAULT) {
600             // Number
601             if (IsADigit(sc.ch)) {
602                 sc.SetState(SCE_NIM_NUMBER);
603 
604                 numType = NumType::Decimal;
605                 decimalCount = 0;
606 
607                 if (sc.ch == '0') {
608                     if (IsNumHex(sc)) {
609                         numType = NumType::Hexadecimal;
610                     } else if (IsNumBinary(sc)) {
611                         numType = NumType::Binary;
612                     } else if (IsNumOctal(sc)) {
613                         numType = NumType::Octal;
614                     }
615 
616                     if (numType != NumType::Decimal) {
617                         sc.Forward();
618                     }
619                 }
620             }
621             // Raw string
622             else if (IsAlphaNumeric(sc.ch) && sc.chNext == '\"') {
623                 isStylingRawString = true;
624 
625                 // Triple doubles can be raw strings too. How sweet
626                 if (styler.SafeGetCharAt(sc.currentPos + 2) == '\"' &&
627                     styler.SafeGetCharAt(sc.currentPos + 3) == '\"') {
628                     sc.SetState(SCE_NIM_TRIPLEDOUBLE);
629                 } else {
630                     sc.SetState(SCE_NIM_STRING);
631                 }
632 
633                 const int rawStrStyle = options.highlightRawStrIdent ? IsLetter(sc.ch) :
634                                         (sc.ch == 'r' || sc.ch == 'R');
635 
636                 if (rawStrStyle) {
637                     sc.Forward();
638 
639                     if (sc.state == SCE_NIM_TRIPLEDOUBLE) {
640                         sc.Forward(2);
641                     }
642                 } else {
643                     // Anything other than r/R is considered a general raw string identifier
644                     isStylingRawStringIdent = true;
645                     sc.SetState(SCE_NIM_IDENTIFIER);
646                 }
647             }
648             // String and triple double literal
649             else if (sc.ch == '\"') {
650                 isStylingRawString = false;
651 
652                 if (sc.Match(R"(""")")) {
653                     sc.SetState(SCE_NIM_TRIPLEDOUBLE);
654 
655                     // Keep forwarding until the total opening literal count is 5
656                     // A valid example where this is needed is: """""<-5 double quotes"""
657                     while (sc.ch == '"') {
658                         sc.Forward();
659 
660                         if (sc.Match(R"(""")")) {
661                             sc.Forward();
662                             break;
663                         }
664                     }
665                 } else {
666                     sc.SetState(SCE_NIM_STRING);
667                 }
668             }
669             // Charecter and triple literal
670             else if (sc.ch == '\'') {
671                 if (sc.Match("'''")) {
672                     sc.SetState(SCE_NIM_TRIPLE);
673                 } else {
674                     sc.SetState(SCE_NIM_CHARACTER);
675                 }
676             }
677             // Operator definition
678             else if (sc.ch == '`') {
679                 if (funcNameExists) {
680                     sc.SetState(SCE_NIM_FUNCNAME);
681                 } else {
682                     sc.SetState(SCE_NIM_BACKTICKS);
683                 }
684             }
685             // Keyword
686             else if (iswordstart(sc.ch)) {
687                 sc.SetState(SCE_NIM_IDENTIFIER);
688             }
689             // Comments
690             else if (sc.ch == '#') {
691                 if (sc.Match("##[") || sc.Match("#[")) {
692                     commentNestLevel++;
693                     lineCurrent = styler.GetLine(sc.currentPos);
694                     styler.SetLineState(lineCurrent, commentNestLevel);
695                 }
696 
697                 if (sc.Match("##[")) {
698                     sc.SetState(SCE_NIM_COMMENTDOC);
699                     sc.Forward();
700                 } else if (sc.Match("#[")) {
701                     sc.SetState(SCE_NIM_COMMENT);
702                     sc.Forward();
703                 } else if (sc.Match("##")) {
704                     sc.SetState(SCE_NIM_COMMENTLINEDOC);
705                 } else {
706                     sc.SetState(SCE_NIM_COMMENTLINE);
707                 }
708             }
709             // Operators
710             else if (strchr("()[]{}:=;-\\/&%$!+<>|^?,.*~@", sc.ch)) {
711                 sc.SetState(SCE_NIM_OPERATOR);
712             }
713         }
714 
715         if (sc.atLineEnd) {
716             funcNameExists = false;
717             isStylingRawString = false;
718             isStylingRawStringIdent = false;
719         }
720     }
721 
722     sc.Complete();
723 }
724 
Fold(Sci_PositionU startPos,Sci_Position length,int,IDocument * pAccess)725 void SCI_METHOD LexerNim::Fold(Sci_PositionU startPos, Sci_Position length, int, IDocument *pAccess) {
726     if (!options.fold) {
727         return;
728     }
729 
730     Accessor styler(pAccess, nullptr);
731 
732     const Sci_Position docLines = styler.GetLine(styler.Length());
733     const Sci_Position maxPos = startPos + length;
734     const Sci_Position maxLines = styler.GetLine(maxPos == styler.Length() ? maxPos : maxPos - 1);
735 
736     Sci_Position lineCurrent = styler.GetLine(startPos);
737     int indentCurrent = IndentAmount(lineCurrent, styler);
738 
739     while (lineCurrent > 0) {
740         lineCurrent--;
741         indentCurrent = IndentAmount(lineCurrent, styler);
742 
743         if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG)) {
744             break;
745         }
746     }
747 
748     int indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK;
749     indentCurrent = indentCurrentLevel | (indentCurrent & ~SC_FOLDLEVELNUMBERMASK);
750 
751     while (lineCurrent <= docLines && lineCurrent <= maxLines) {
752         Sci_Position lineNext = lineCurrent + 1;
753         int indentNext = indentCurrent;
754         int lev = indentCurrent;
755 
756         if (lineNext <= docLines) {
757             indentNext = IndentAmount(lineNext, styler);
758         }
759 
760         if (indentNext & SC_FOLDLEVELWHITEFLAG) {
761             indentNext = SC_FOLDLEVELWHITEFLAG | indentCurrentLevel;
762         }
763 
764         while (lineNext < docLines && (indentNext & SC_FOLDLEVELWHITEFLAG)) {
765             lineNext++;
766             indentNext = IndentAmount(lineNext, styler);
767         }
768 
769         const int indentNextLevel = indentNext & SC_FOLDLEVELNUMBERMASK;
770         indentNext = indentNextLevel | (indentNext & ~SC_FOLDLEVELNUMBERMASK);
771 
772         const int levelBeforeComments = std::max(indentCurrentLevel, indentNextLevel);
773 
774         Sci_Position skipLine = lineNext;
775         int skipLevel = indentNextLevel;
776 
777         while (--skipLine > lineCurrent) {
778             const int skipLineIndent = IndentAmount(skipLine, styler);
779 
780             if (options.foldCompact) {
781                 if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > indentNextLevel) {
782                     skipLevel = levelBeforeComments;
783                 }
784 
785                 const int whiteFlag = skipLineIndent & SC_FOLDLEVELWHITEFLAG;
786                 styler.SetLevel(skipLine, skipLevel | whiteFlag);
787             } else {
788                 if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > indentNextLevel &&
789                    !(skipLineIndent & SC_FOLDLEVELWHITEFLAG)) {
790                     skipLevel = levelBeforeComments;
791                 }
792 
793                 styler.SetLevel(skipLine, skipLevel);
794             }
795         }
796 
797         if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG)) {
798             if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext & SC_FOLDLEVELNUMBERMASK)) {
799                 lev |= SC_FOLDLEVELHEADERFLAG;
800             }
801         }
802 
803         styler.SetLevel(lineCurrent, options.foldCompact ? lev : lev & ~SC_FOLDLEVELWHITEFLAG);
804 
805         indentCurrent = indentNext;
806         indentCurrentLevel = indentNextLevel;
807         lineCurrent = lineNext;
808     }
809 }
810 
811 LexerModule lmNim(SCLEX_NIM, LexerNim::LexerFactoryNim, "nim", nimWordListDesc);