1 // Scintilla source code edit control
2 // Encoding: UTF-8
3 /** @file LexJulia.cxx
4  ** Lexer for Julia.
5  ** Reusing code from LexMatlab, LexPython and LexRust
6  **
7  ** Written by Bertrand Lacoste
8  **
9  **/
10 // Copyright 1998-2001 by Neil Hodgson <neilh@scintilla.org>
11 // The License.txt file describes the conditions under which this software may be distributed.
12 
13 #include <cstdlib>
14 #include <cassert>
15 #include <cstring>
16 
17 #include <string>
18 #include <string_view>
19 #include <vector>
20 #include <map>
21 #include <algorithm>
22 #include <functional>
23 
24 #include "ILexer.h"
25 #include "Scintilla.h"
26 #include "SciLexer.h"
27 
28 #include "StringCopy.h"
29 #include "PropSetSimple.h"
30 #include "StringCopy.h"
31 #include "WordList.h"
32 #include "LexAccessor.h"
33 #include "Accessor.h"
34 #include "StyleContext.h"
35 #include "CharacterSet.h"
36 #include "CharacterCategory.h"
37 #include "LexerModule.h"
38 #include "OptionSet.h"
39 #include "DefaultLexer.h"
40 
41 using namespace Scintilla;
42 // Geany still uses Scintilla v3.5
43 //using namespace Lexilla;
44 
45 static const int MAX_JULIA_IDENT_CHARS = 1023;
46 
47 // Options used for LexerJulia
48 struct OptionsJulia {
49     bool fold;
50     bool foldComment;
51     bool foldCompact;
52     bool foldDocstring;
53     bool foldSyntaxBased;
54     bool highlightTypeannotation;
55     bool highlightLexerror;
OptionsJuliaOptionsJulia56 	OptionsJulia() {
57         fold = true;
58         foldComment = true;
59         foldCompact = false;
60         foldDocstring = true;
61         foldSyntaxBased = true;
62         highlightTypeannotation = false;
63         highlightLexerror = false;
64 	}
65 };
66 
67 const char * const juliaWordLists[] = {
68     "Primary keywords and identifiers",
69     "Built in types",
70     "Other keywords",
71     "Built in functions",
72     0,
73 };
74 
75 struct OptionSetJulia : public OptionSet<OptionsJulia> {
OptionSetJuliaOptionSetJulia76 	OptionSetJulia() {
77 		DefineProperty("fold", &OptionsJulia::fold);
78 
79 		DefineProperty("fold.compact", &OptionsJulia::foldCompact);
80 
81 		DefineProperty("fold.comment", &OptionsJulia::foldComment);
82 
83 		DefineProperty("fold.julia.docstring", &OptionsJulia::foldDocstring,
84 			"Fold multiline triple-doublequote strings, usually used to document a function or type above the definition.");
85 
86 		DefineProperty("fold.julia.syntax.based", &OptionsJulia::foldSyntaxBased,
87 			"Set this property to 0 to disable syntax based folding.");
88 
89 		DefineProperty("lexer.julia.highlight.typeannotation", &OptionsJulia::highlightTypeannotation,
90 			"This option enables highlighting of the type identifier after `::`.");
91 
92 		DefineProperty("lexer.julia.highlight.lexerror", &OptionsJulia::highlightLexerror,
93 			"This option enables highlighting of syntax error int character or number definition.");
94 
95 		DefineWordListSets(juliaWordLists);
96 	}
97 };
98 
99 LexicalClass juliaLexicalClasses[] = {
100 	// Lexer Julia SCLEX_JULIA SCE_JULIA_:
101 	0,  "SCE_JULIA_DEFAULT", "default", "White space",
102 	1,  "SCE_JULIA_COMMENT", "comment", "Comment",
103 	2,  "SCE_JULIA_NUMBER", "literal numeric", "Number",
104 	3,  "SCE_JULIA_KEYWORD1", "keyword", "Reserved keywords",
105 	4,  "SCE_JULIA_KEYWORD2", "identifier", "Builtin type names",
106 	5,  "SCE_JULIA_KEYWORD3", "identifier", "Constants",
107 	6,  "SCE_JULIA_CHAR", "literal string character", "Single quoted string",
108 	7,  "SCE_JULIA_OPERATOR", "operator", "Operator",
109 	8,  "SCE_JULIA_BRACKET", "bracket operator", "Bracket operator",
110 	9,  "SCE_JULIA_IDENTIFIER", "identifier", "Identifier",
111 	10, "SCE_JULIA_STRING", "literal string", "Double quoted String",
112 	11, "SCE_JULIA_SYMBOL", "literal string symbol", "Symbol",
113 	12, "SCE_JULIA_MACRO", "macro preprocessor", "Macro",
114 	13, "SCE_JULIA_STRINGINTERP", "literal string interpolated", "String interpolation",
115 	14, "SCE_JULIA_DOCSTRING", "literal string documentation", "Docstring",
116 	15, "SCE_JULIA_STRINGLITERAL", "literal string", "String literal prefix",
117 	16, "SCE_JULIA_COMMAND", "literal string command", "Command",
118 	17, "SCE_JULIA_COMMANDLITERAL", "literal string command", "Command literal prefix",
119 	18, "SCE_JULIA_TYPEANNOT", "identifier type", "Type annotation identifier",
120 	19, "SCE_JULIA_LEXERROR", "lexer error", "Lexing error",
121 	20, "SCE_JULIA_KEYWORD4", "identifier", "Builtin function names",
122 	21, "SCE_JULIA_TYPEOPERATOR", "operator type", "Type annotation operator",
123 };
124 
125 class LexerJulia : public DefaultLexer {
126 	WordList keywords;
127 	WordList identifiers2;
128 	WordList identifiers3;
129 	WordList identifiers4;
130 	OptionsJulia options;
131 	OptionSetJulia osJulia;
132 public:
LexerJulia()133 	explicit LexerJulia() :
134 		DefaultLexer("julia", SCLEX_JULIA, juliaLexicalClasses, ELEMENTS(juliaLexicalClasses)) {
135 	}
~LexerJulia()136 	virtual ~LexerJulia() {
137 	}
Release()138 	void SCI_METHOD Release() override {
139 		delete this;
140 	}
Version() const141 	int SCI_METHOD Version() const override {
142 		// Geany still uses Scintilla v3.5
143 		//return lvRelease5;
144 		return lvIdentity;
145 	}
PropertyNames()146 	const char * SCI_METHOD PropertyNames() override {
147 		return osJulia.PropertyNames();
148 	}
PropertyType(const char * name)149 	int SCI_METHOD PropertyType(const char *name) override {
150 		return osJulia.PropertyType(name);
151 	}
DescribeProperty(const char * name)152 	const char * SCI_METHOD DescribeProperty(const char *name) override {
153 		return osJulia.DescribeProperty(name);
154 	}
155 	Sci_Position SCI_METHOD PropertySet(const char *key, const char *val) override;
PropertyGet(const char * key)156 	const char * SCI_METHOD PropertyGet(const char *key) override {
157 		return osJulia.PropertyGet(key);
158 	}
DescribeWordListSets()159 	const char * SCI_METHOD DescribeWordListSets() override {
160 		return osJulia.DescribeWordListSets();
161 	}
162 	Sci_Position SCI_METHOD WordListSet(int n, const char *wl) override;
163 	void SCI_METHOD Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override;
164 	void SCI_METHOD Fold(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override;
PrivateCall(int,void *)165 	void * SCI_METHOD PrivateCall(int, void *) override {
166 		return 0;
167 	}
168 
169 	// Geany still uses Scintilla v3.5
170 	//static ILexer5 *LexerFactoryJulia() {
LexerFactoryJulia()171 	static ILexer *LexerFactoryJulia() {
172 		return new LexerJulia();
173 	}
174 };
175 
PropertySet(const char * key,const char * val)176 Sci_Position SCI_METHOD LexerJulia::PropertySet(const char *key, const char *val) {
177 	if (osJulia.PropertySet(&options, key, val)) {
178 		return 0;
179 	}
180 	return -1;
181 }
182 
WordListSet(int n,const char * wl)183 Sci_Position SCI_METHOD LexerJulia::WordListSet(int n, const char *wl) {
184 	WordList *wordListN = nullptr;
185 	switch (n) {
186 	case 0:
187 		wordListN = &keywords;
188 		break;
189 	case 1:
190 		wordListN = &identifiers2;
191 		break;
192 	case 2:
193 		wordListN = &identifiers3;
194 		break;
195 	case 3:
196 		wordListN = &identifiers4;
197 		break;
198 	}
199 	Sci_Position firstModification = -1;
200 	if (wordListN) {
201 		WordList wlNew;
202 		wlNew.Set(wl);
203 		if (*wordListN != wlNew) {
204 			wordListN->Set(wl);
205 			firstModification = 0;
206 		}
207 	}
208 	return firstModification;
209 }
210 
IsJuliaOperator(int ch)211 static inline bool IsJuliaOperator(int ch) {
212     if (ch == '%' || ch == '^' || ch == '&' || ch == '*' ||
213         ch == '-' || ch == '+' || ch == '=' || ch == '|' ||
214         ch == '<' || ch == '>' || ch == '/' || ch == '~' ||
215         ch == '\\' ) {
216         return true;
217     }
218     return false;
219 }
220 
221 // The list contains non-ascii unary operators
IsJuliaUnaryOperator(int ch)222 static inline bool IsJuliaUnaryOperator (int ch) {
223     if (ch == 0x00ac || ch == 0x221a || ch == 0x221b ||
224         ch == 0x221c || ch == 0x22c6 || ch == 0x00b1 ||
225         ch == 0x2213 ) {
226         return true;
227     }
228     return false;
229 }
230 
IsJuliaParen(int ch)231 static inline bool IsJuliaParen (int ch) {
232     if (ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
233         ch == '[' || ch == ']' ) {
234         return true;
235     }
236     return false;
237 }
238 
239 // Unicode parsing from Julia source code:
240 // https://github.com/JuliaLang/julia/blob/master/src/flisp/julia_extensions.c
241 // keep the same function name to be easy to find again
is_wc_cat_id_start(uint32_t wc)242 static int is_wc_cat_id_start(uint32_t wc) {
243     const CharacterCategory cat = CategoriseCharacter((int) wc);
244 
245     return (cat == ccLu || cat == ccLl ||
246             cat == ccLt || cat == ccLm ||
247             cat == ccLo || cat == ccNl ||
248             cat == ccSc ||  // allow currency symbols
249             // other symbols, but not arrows or replacement characters
250             (cat == ccSo && !(wc >= 0x2190 && wc <= 0x21FF) &&
251              wc != 0xfffc && wc != 0xfffd &&
252              wc != 0x233f &&  // notslash
253              wc != 0x00a6) || // broken bar
254 
255             // math symbol (category Sm) whitelist
256             (wc >= 0x2140 && wc <= 0x2a1c &&
257              ((wc >= 0x2140 && wc <= 0x2144) || // ⅀, ⅁, ⅂, ⅃, ⅄
258               wc == 0x223f || wc == 0x22be || wc == 0x22bf || // ∿, ⊾, ⊿
259               wc == 0x22a4 || wc == 0x22a5 ||   // ⊤ ⊥
260 
261               (wc >= 0x2202 && wc <= 0x2233 &&
262                (wc == 0x2202 || wc == 0x2205 || wc == 0x2206 || // ∂, ∅, ∆
263                 wc == 0x2207 || wc == 0x220e || wc == 0x220f || // ∇, ∎, ∏
264                 wc == 0x2210 || wc == 0x2211 || // ∐, ∑
265                 wc == 0x221e || wc == 0x221f || // ∞, ∟
266                 wc >= 0x222b)) || // ∫, ∬, ∭, ∮, ∯, ∰, ∱, ∲, ∳
267 
268               (wc >= 0x22c0 && wc <= 0x22c3) ||  // N-ary big ops: ⋀, ⋁, ⋂, ⋃
269               (wc >= 0x25F8 && wc <= 0x25ff) ||  // ◸, ◹, ◺, ◻, ◼, ◽, ◾, ◿
270 
271               (wc >= 0x266f &&
272                (wc == 0x266f || wc == 0x27d8 || wc == 0x27d9 || // ♯, ⟘, ⟙
273                 (wc >= 0x27c0 && wc <= 0x27c1) ||  // ⟀, ⟁
274                 (wc >= 0x29b0 && wc <= 0x29b4) ||  // ⦰, ⦱, ⦲, ⦳, ⦴
275                 (wc >= 0x2a00 && wc <= 0x2a06) ||  // ⨀, ⨁, ⨂, ⨃, ⨄, ⨅, ⨆
276                 (wc >= 0x2a09 && wc <= 0x2a16) ||  // ⨉, ⨊, ⨋, ⨌, ⨍, ⨎, ⨏, ⨐, ⨑, ⨒, ⨓, ⨔, ⨕, ⨖
277                 wc == 0x2a1b || wc == 0x2a1c)))) || // ⨛, ⨜
278 
279             (wc >= 0x1d6c1 && // variants of \nabla and \partial
280              (wc == 0x1d6c1 || wc == 0x1d6db ||
281               wc == 0x1d6fb || wc == 0x1d715 ||
282               wc == 0x1d735 || wc == 0x1d74f ||
283               wc == 0x1d76f || wc == 0x1d789 ||
284               wc == 0x1d7a9 || wc == 0x1d7c3)) ||
285 
286             // super- and subscript +-=()
287             (wc >= 0x207a && wc <= 0x207e) ||
288             (wc >= 0x208a && wc <= 0x208e) ||
289 
290             // angle symbols
291             (wc >= 0x2220 && wc <= 0x2222) || // ∠, ∡, ∢
292             (wc >= 0x299b && wc <= 0x29af) || // ⦛, ⦜, ⦝, ⦞, ⦟, ⦠, ⦡, ⦢, ⦣, ⦤, ⦥, ⦦, ⦧, ⦨, ⦩, ⦪, ⦫, ⦬, ⦭, ⦮, ⦯
293 
294             // Other_ID_Start
295             wc == 0x2118 || wc == 0x212E || // ℘, ℮
296             (wc >= 0x309B && wc <= 0x309C) || // katakana-hiragana sound marks
297 
298             // bold-digits and double-struck digits
299             (wc >= 0x1D7CE && wc <= 0x1D7E1)); // �� through �� (inclusive), �� through �� (inclusive)
300 }
301 
IsIdentifierFirstCharacter(int ch)302 static inline bool IsIdentifierFirstCharacter (int ch) {
303     if (IsASCII(ch)) {
304         return (bool) (isalpha(ch) || ch == '_');
305     }
306     if (ch < 0xA1 || ch > 0x10ffff) {
307         return false;
308     }
309 
310     return is_wc_cat_id_start((uint32_t) ch);
311 }
312 
IsIdentifierCharacter(int ch)313 static inline bool IsIdentifierCharacter (int ch) {
314     if (IsASCII(ch)) {
315         return (bool) (isalnum(ch) || ch == '_' || ch == '!');
316     }
317     if (ch < 0xA1 || ch > 0x10ffff) {
318         return false;
319     }
320 
321     if (is_wc_cat_id_start((uint32_t) ch)) {
322         return true;
323     }
324 
325     const CharacterCategory cat = CategoriseCharacter(ch);
326 
327     if (cat == ccMn || cat == ccMc ||
328         cat == ccNd || cat == ccPc ||
329         cat == ccSk || cat == ccMe ||
330         cat == ccNo ||
331         // primes (single, double, triple, their reverses, and quadruple)
332         (ch >= 0x2032 && ch <= 0x2037) || (ch == 0x2057)) {
333         return true;
334     }
335     return false;
336 }
337 
338 // keep the same function name to be easy to find again
339 static const uint32_t opsuffs[] = {
340    0x00b2, // ²
341    0x00b3, // ³
342    0x00b9, // ¹
343    0x02b0, // ʰ
344    0x02b2, // ʲ
345    0x02b3, // ʳ
346    0x02b7, // ʷ
347    0x02b8, // ʸ
348    0x02e1, // ˡ
349    0x02e2, // ˢ
350    0x02e3, // ˣ
351    0x1d2c, // ᴬ
352    0x1d2e, // ᴮ
353    0x1d30, // ᴰ
354    0x1d31, // ᴱ
355    0x1d33, // ᴳ
356    0x1d34, // ᴴ
357    0x1d35, // ᴵ
358    0x1d36, // ᴶ
359    0x1d37, // ᴷ
360    0x1d38, // ᴸ
361    0x1d39, // ᴹ
362    0x1d3a, // ᴺ
363    0x1d3c, // ᴼ
364    0x1d3e, // ᴾ
365    0x1d3f, // ᴿ
366    0x1d40, // ᵀ
367    0x1d41, // ᵁ
368    0x1d42, // ᵂ
369    0x1d43, // ᵃ
370    0x1d47, // ᵇ
371    0x1d48, // ᵈ
372    0x1d49, // ᵉ
373    0x1d4d, // ᵍ
374    0x1d4f, // ᵏ
375    0x1d50, // ᵐ
376    0x1d52, // ᵒ
377    0x1d56, // ᵖ
378    0x1d57, // ᵗ
379    0x1d58, // ᵘ
380    0x1d5b, // ᵛ
381    0x1d5d, // ᵝ
382    0x1d5e, // ᵞ
383    0x1d5f, // ᵟ
384    0x1d60, // ᵠ
385    0x1d61, // ᵡ
386    0x1d62, // ᵢ
387    0x1d63, // ᵣ
388    0x1d64, // ᵤ
389    0x1d65, // ᵥ
390    0x1d66, // ᵦ
391    0x1d67, // ᵧ
392    0x1d68, // ᵨ
393    0x1d69, // ᵩ
394    0x1d6a, // ᵪ
395    0x1d9c, // ᶜ
396    0x1da0, // ᶠ
397    0x1da5, // ᶥ
398    0x1da6, // ᶦ
399    0x1dab, // ᶫ
400    0x1db0, // ᶰ
401    0x1db8, // ᶸ
402    0x1dbb, // ᶻ
403    0x1dbf, // ᶿ
404    0x2032, // ′
405    0x2033, // ″
406    0x2034, // ‴
407    0x2035, // ‵
408    0x2036, // ‶
409    0x2037, // ‷
410    0x2057, // ⁗
411    0x2070, // ⁰
412    0x2071, // ⁱ
413    0x2074, // ⁴
414    0x2075, // ⁵
415    0x2076, // ⁶
416    0x2077, // ⁷
417    0x2078, // ⁸
418    0x2079, // ⁹
419    0x207a, // ⁺
420    0x207b, // ⁻
421    0x207c, // ⁼
422    0x207d, // ⁽
423    0x207e, // ⁾
424    0x207f, // ⁿ
425    0x2080, // ₀
426    0x2081, // ₁
427    0x2082, // ₂
428    0x2083, // ₃
429    0x2084, // ₄
430    0x2085, // ₅
431    0x2086, // ₆
432    0x2087, // ₇
433    0x2088, // ₈
434    0x2089, // ₉
435    0x208a, // ₊
436    0x208b, // ₋
437    0x208c, // ₌
438    0x208d, // ₍
439    0x208e, // ₎
440    0x2090, // ₐ
441    0x2091, // ₑ
442    0x2092, // ₒ
443    0x2093, // ₓ
444    0x2095, // ₕ
445    0x2096, // ₖ
446    0x2097, // ₗ
447    0x2098, // ₘ
448    0x2099, // ₙ
449    0x209a, // ₚ
450    0x209b, // ₛ
451    0x209c, // ₜ
452    0x2c7c, // ⱼ
453    0x2c7d, // ⱽ
454    0xa71b, // ꜛ
455    0xa71c, // ꜜ
456    0xa71d  // ꜝ
457 };
458 static const size_t opsuffs_len = sizeof(opsuffs) / (sizeof(uint32_t));
459 
460 // keep the same function name to be easy to find again
jl_op_suffix_char(uint32_t wc)461 static bool jl_op_suffix_char(uint32_t wc) {
462     if (wc < 0xA1 || wc > 0x10ffff) {
463         return false;
464     }
465     const CharacterCategory cat = CategoriseCharacter((int) wc);
466     if (cat == ccMn || cat == ccMc ||
467         cat == ccMe) {
468         return true;
469     }
470 
471     for (size_t i = 0; i < opsuffs_len; ++i) {
472         if (wc == opsuffs[i]) {
473             return true;
474         }
475     }
476     return false;
477 }
478 
479 // keep the same function name to be easy to find again
never_id_char(uint32_t wc)480 static bool never_id_char(uint32_t wc) {
481      const CharacterCategory cat = CategoriseCharacter((int) wc);
482      return (
483           // spaces and control characters:
484           (cat >= ccZs && cat <= ccCs) ||
485 
486           // ASCII and Latin1 non-connector punctuation
487           (wc < 0xff &&
488            cat >= ccPd && cat <= ccPo) ||
489 
490           wc == '`' ||
491 
492           // mathematical brackets
493           (wc >= 0x27e6 && wc <= 0x27ef) ||
494           // angle, corner, and lenticular brackets
495           (wc >= 0x3008 && wc <= 0x3011) ||
496           // tortoise shell, square, and more lenticular brackets
497           (wc >= 0x3014 && wc <= 0x301b) ||
498           // fullwidth parens
499           (wc == 0xff08 || wc == 0xff09) ||
500           // fullwidth square brackets
501           (wc == 0xff3b || wc == 0xff3d));
502 }
503 
504 
IsOperatorFirstCharacter(int ch)505 static bool IsOperatorFirstCharacter (int ch) {
506     if (IsASCII(ch)) {
507         if (IsJuliaOperator(ch) ||
508             ch == '!' || ch == '?' ||
509             ch == ':' || ch == ';' ||
510             ch == ',' || ch == '.' ) {
511             return true;
512         }else {
513             return false;
514         }
515     } else if (is_wc_cat_id_start((uint32_t) ch)) {
516         return false;
517     } else if (IsJuliaUnaryOperator(ch) ||
518                ! never_id_char((uint32_t) ch)) {
519         return true;
520     }
521     return false;
522 }
523 
IsOperatorCharacter(int ch)524 static bool IsOperatorCharacter (int ch) {
525     if (IsOperatorFirstCharacter(ch) ||
526         (!IsASCII(ch) && jl_op_suffix_char((uint32_t) ch)) ) {
527         return true;
528     }
529     return false;
530 }
531 
CheckBoundsIndexing(char * str)532 static bool CheckBoundsIndexing(char *str) {
533     if (strcmp("begin", str) == 0 || strcmp("end", str) == 0 ) {
534         return true;
535     }
536     return false;
537 }
538 
CheckKeywordFoldPoint(char * str)539 static int CheckKeywordFoldPoint(char *str) {
540     if (strcmp ("if", str) == 0 ||
541         strcmp ("for", str) == 0 ||
542         strcmp ("while", str) == 0 ||
543         strcmp ("try", str) == 0 ||
544         strcmp ("do", str) == 0 ||
545         strcmp ("begin", str) == 0 ||
546         strcmp ("let", str) == 0 ||
547         strcmp ("baremodule", str) == 0 ||
548         strcmp ("quote", str) == 0 ||
549         strcmp ("module", str) == 0 ||
550         strcmp ("struct", str) == 0 ||
551         strcmp ("type", str) == 0 ||
552         strcmp ("macro", str) == 0 ||
553         strcmp ("function", str) == 0) {
554         return 1;
555     }
556     if (strcmp("end", str) == 0) {
557         return -1;
558     }
559     return 0;
560 }
561 
IsNumberExpon(int ch,int base)562 static bool IsNumberExpon(int ch, int base) {
563     if ((base == 10 && (ch == 'e' || ch == 'E' || ch == 'f')) ||
564         (base == 16 && (ch == 'p' || ch == 'P'))) {
565         return true;
566     }
567     return false;
568 }
569 
570 /* Scans a sequence of digits, returning true if it found any. */
ScanDigits(StyleContext & sc,int base,bool allow_sep)571 static bool ScanDigits(StyleContext& sc, int base, bool allow_sep) {
572 	bool found = false;
573     for (;;) {
574 		if (IsADigit(sc.chNext, base) || (allow_sep && sc.chNext == '_')) {
575 			found = true;
576             sc.Forward();
577 		} else {
578 			break;
579         }
580 	}
581 	return found;
582 }
583 
ScanNHexas(StyleContext & sc,int max)584 static inline bool ScanNHexas(StyleContext &sc, int max) {
585     int n = 0;
586     bool error = false;
587 
588     sc.Forward();
589     if (!IsADigit(sc.ch, 16)) {
590         error = true;
591     } else {
592         while (IsADigit(sc.ch, 16) && n < max) {
593             sc.Forward();
594             n++;
595         }
596     }
597     return error;
598 }
599 
resumeCharacter(StyleContext & sc,bool lexerror)600 static void resumeCharacter(StyleContext &sc, bool lexerror) {
601     bool error = false;
602 
603     //  ''' case
604     if (sc.chPrev == '\'' && sc.ch == '\'' && sc.chNext == '\'') {
605         sc.Forward();
606         sc.ForwardSetState(SCE_JULIA_DEFAULT);
607         return;
608     } else if (lexerror && sc.chPrev == '\'' && sc.ch == '\'') {
609         sc.ChangeState(SCE_JULIA_LEXERROR);
610         sc.ForwardSetState(SCE_JULIA_DEFAULT);
611 
612     // Escape characters
613     } else if (sc.ch == '\\') {
614         sc.Forward();
615         if (sc.ch == '\'' || sc.ch == '\\' ) {
616             sc.Forward();
617         } else if (sc.ch == 'n' || sc.ch == 't' || sc.ch == 'a' ||
618                    sc.ch == 'b' || sc.ch == 'e' || sc.ch == 'f' ||
619                    sc.ch == 'r' || sc.ch == 'v' ) {
620             sc.Forward();
621         } else if (sc.ch == 'x') {
622             error |= ScanNHexas(sc, 2);
623         } else if (sc.ch == 'u') {
624             error |= ScanNHexas(sc, 4);
625         } else if (sc.ch == 'U') {
626             error |= ScanNHexas(sc, 8);
627         } else if (IsADigit(sc.ch, 8)) {
628             int n = 1;
629             int max = 3;
630             sc.Forward();
631             while (IsADigit(sc.ch, 8) && n < max) {
632                 sc.Forward();
633                 n++;
634             }
635         }
636 
637         if (lexerror) {
638             if (sc.ch != '\'') {
639                 error = true;
640                 while (sc.ch != '\'' &&
641                        sc.ch != '\r' &&
642                        sc.ch != '\n') {
643                     sc.Forward();
644                 }
645             }
646 
647             if (error) {
648                 sc.ChangeState(SCE_JULIA_LEXERROR);
649                 sc.ForwardSetState(SCE_JULIA_DEFAULT);
650             }
651         }
652     } else if (lexerror) {
653         if (sc.ch < 0x20 || sc.ch > 0x10ffff) {
654             error = true;
655         } else {
656             // single character
657             sc.Forward();
658 
659             if (sc.ch != '\'') {
660                 error = true;
661                 while (sc.ch != '\'' &&
662                        sc.ch != '\r' &&
663                        sc.ch != '\n') {
664                     sc.Forward();
665                 }
666             }
667         }
668 
669         if (error) {
670             sc.ChangeState(SCE_JULIA_LEXERROR);
671             sc.ForwardSetState(SCE_JULIA_DEFAULT);
672         }
673     }
674 
675     // closing quote
676     if (sc.ch == '\'') {
677         if (sc.chNext == '\'') {
678             sc.Forward();
679         } else {
680             sc.ForwardSetState(SCE_JULIA_DEFAULT);
681         }
682     }
683 }
684 
IsACharacter(StyleContext & sc)685 static inline bool IsACharacter(StyleContext &sc) {
686     return (sc.chPrev == '\'' && sc.chNext == '\'');
687 }
688 
ScanParenInterpolation(StyleContext & sc)689 static void ScanParenInterpolation(StyleContext &sc) {
690     // TODO: no syntax highlighting inside a string interpolation
691 
692     // Level of nested parenthesis
693     int interp_level = 0;
694 
695     // If true, it is inside a string and parenthesis are not counted.
696     bool allow_paren_string = false;
697 
698 
699     // check for end of states
700     for (; sc.More(); sc.Forward()) {
701         // TODO: check corner cases for nested string interpolation
702         // TODO: check corner cases with Command inside interpolation
703 
704         if ( sc.ch == '\"' && sc.chPrev != '\\') {
705             // Toggle the string environment (parenthesis are not counted inside a string)
706             allow_paren_string = !allow_paren_string;
707         } else if ( !allow_paren_string ) {
708             if ( sc.ch == '(' && !IsACharacter(sc) ) {
709                 interp_level ++;
710             } else if ( sc.ch == ')' && !IsACharacter(sc) && interp_level > 0 ) {
711                 interp_level --;
712                 if (interp_level == 0) {
713                     // Exit interpolation
714                     return;
715                 }
716             }
717         }
718     }
719 }
720 /*
721  * Start parsing a number, parse the base.
722  */
initNumber(StyleContext & sc,int & base,bool & with_dot)723 static void initNumber (StyleContext &sc, int &base, bool &with_dot) {
724     base = 10;
725     with_dot = false;
726     sc.SetState(SCE_JULIA_NUMBER);
727     if (sc.ch == '0') {
728         if (sc.chNext == 'x') {
729             sc.Forward();
730             base = 16;
731             if (sc.chNext == '.') {
732                 sc.Forward();
733                 with_dot = true;
734             }
735         } else if (sc.chNext == 'o') {
736             sc.Forward();
737             base = 8;
738         } else if (sc.chNext == 'b') {
739             sc.Forward();
740             base = 2;
741         }
742     } else if (sc.ch == '.') {
743         with_dot = true;
744     }
745 }
746 
747 /*
748  * Resume parsing a String or Command, bounded by the `quote` character (\" or \`)
749  * The `triple` argument specifies if it is a triple-quote String or Command.
750  * Interpolation is detected (with `$`), and parsed if `allow_interp` is true.
751  */
resumeStringLike(StyleContext & sc,int quote,bool triple,bool allow_interp,bool full_highlight)752 static void resumeStringLike(StyleContext &sc, int quote, bool triple, bool allow_interp, bool full_highlight) {
753     int stylePrev = sc.state;
754     bool checkcurrent = false;
755 
756     // Escape characters
757     if (sc.ch == '\\') {
758         if (sc.chNext == quote || sc.chNext == '\\' || sc.chNext == '$') {
759             sc.Forward();
760         }
761     } else if (allow_interp && sc.ch == '$') {
762         // If the interpolation is only of a variable, do not change state
763         if (sc.chNext == '(') {
764             if (full_highlight) {
765                 sc.SetState(SCE_JULIA_STRINGINTERP);
766             } else {
767                 sc.ForwardSetState(SCE_JULIA_STRINGINTERP);
768             }
769             ScanParenInterpolation(sc);
770             sc.ForwardSetState(stylePrev);
771 
772             checkcurrent = true;
773 
774         } else if (full_highlight && IsIdentifierFirstCharacter(sc.chNext)) {
775             sc.SetState(SCE_JULIA_STRINGINTERP);
776             sc.Forward();
777             sc.Forward();
778             for (; sc.More(); sc.Forward()) {
779                 if (! IsIdentifierCharacter(sc.ch)) {
780                     break;
781                 }
782             }
783             sc.SetState(stylePrev);
784 
785             checkcurrent = true;
786         }
787 
788         if (checkcurrent) {
789             // Check that the current character is not a special char,
790             // otherwise it will be skipped
791             resumeStringLike(sc, quote, triple, allow_interp, full_highlight);
792         }
793 
794     } else if (sc.ch == quote) {
795         if (triple) {
796             if (sc.chNext == quote && sc.GetRelativeCharacter(2) == quote) {
797                 // Move to the end of the triple quotes
798                 Sci_PositionU nextIndex = sc.currentPos + 2;
799                 while (nextIndex > sc.currentPos && sc.More()) {
800                     sc.Forward();
801                 }
802                 sc.ForwardSetState(SCE_JULIA_DEFAULT);
803             }
804         } else {
805             sc.ForwardSetState(SCE_JULIA_DEFAULT);
806         }
807     }
808 }
809 
resumeCommand(StyleContext & sc,bool triple,bool allow_interp)810 static void resumeCommand(StyleContext &sc, bool triple, bool allow_interp) {
811     return resumeStringLike(sc, '`', triple, allow_interp, true);
812 }
813 
resumeString(StyleContext & sc,bool triple,bool allow_interp)814 static void resumeString(StyleContext &sc, bool triple, bool allow_interp) {
815     return resumeStringLike(sc, '"', triple, allow_interp, true);
816 }
817 
resumeNumber(StyleContext & sc,int base,bool & with_dot,bool lexerror)818 static void resumeNumber (StyleContext &sc, int base, bool &with_dot, bool lexerror) {
819     if (IsNumberExpon(sc.ch, base)) {
820         if (IsADigit(sc.chNext) || sc.chNext == '+' || sc.chNext == '-') {
821             sc.Forward();
822             // Capture all digits
823             ScanDigits(sc, 10, false);
824             sc.Forward();
825         }
826         sc.SetState(SCE_JULIA_DEFAULT);
827     } else if (sc.ch == '.' && sc.chNext == '.') {
828         // Interval operator `..`
829         sc.SetState(SCE_JULIA_OPERATOR);
830         sc.Forward();
831         sc.ForwardSetState(SCE_JULIA_DEFAULT);
832     } else if (sc.ch == '.' && !with_dot) {
833         with_dot = true;
834         ScanDigits(sc, base, true);
835     } else if (IsADigit(sc.ch, base) || sc.ch == '_') {
836         ScanDigits(sc, base, true);
837     } else if (IsADigit(sc.ch) && !IsADigit(sc.ch, base)) {
838         if (lexerror) {
839             sc.ChangeState(SCE_JULIA_LEXERROR);
840         }
841         ScanDigits(sc, 10, false);
842         sc.ForwardSetState(SCE_JULIA_DEFAULT);
843     } else {
844         sc.SetState(SCE_JULIA_DEFAULT);
845     }
846 }
847 
resumeOperator(StyleContext & sc)848 static void resumeOperator (StyleContext &sc) {
849     if (sc.chNext == ':' && (sc.ch == ':' || sc.ch == '<' ||
850                     (sc.ch == '>' && (sc.chPrev != '-' && sc.chPrev != '=')))) {
851         // Case `:a=>:b`
852         sc.Forward();
853         sc.ForwardSetState(SCE_JULIA_DEFAULT);
854     } else if (sc.ch == ':') {
855         // Case `foo(:baz,:baz)` or `:one+:two`
856         // Let the default case switch decide if it is a symbol
857         sc.SetState(SCE_JULIA_DEFAULT);
858     } else if (sc.ch == '\'') {
859         sc.SetState(SCE_JULIA_DEFAULT);
860     } else if ((sc.ch == '.' && sc.chPrev != '.') || IsIdentifierFirstCharacter(sc.ch) ||
861                (! (sc.chPrev == '.' && IsOperatorFirstCharacter(sc.ch)) &&
862                 ! IsOperatorCharacter(sc.ch)) ) {
863         sc.SetState(SCE_JULIA_DEFAULT);
864     }
865 }
866 
Lex(Sci_PositionU startPos,Sci_Position length,int initStyle,IDocument * pAccess)867 void SCI_METHOD LexerJulia::Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) {
868 	PropSetSimple props;
869 	Accessor styler(pAccess, &props);
870 
871 	Sci_Position pos = startPos;
872 	styler.StartAt(pos);
873 	styler.StartSegment(pos);
874 
875     // use the line state of each line to store block/multiline states
876     Sci_Position curLine = styler.GetLine(startPos);
877     // Default is false for everything and 0 counters.
878 	int lineState = (curLine > 0) ? styler.GetLineState(curLine-1) : 0;
879 
880     bool transpose = (lineState >> 0) & 0x01;                // 1 bit to know if ' is allowed to mean transpose
881     bool istripledocstring = (lineState >> 1) & 0x01;        // 1 bit to know if we are in a triple doublequotes string
882 	bool triple_backtick = (lineState >> 2) & 0x01;          // 1 bit to know if we are in a triple backtick command
883 	bool israwstring = (lineState >> 3) & 0x01;              // 1 bit to know if we are in a raw string
884     int indexing_level = (int)((lineState >> 4) & 0x0F);     // 4 bits of bracket nesting counter
885     int list_comprehension = (int)((lineState >> 8) & 0x0F); // 4 bits of parenthesis nesting counter
886     int commentDepth = (int)((lineState >> 12) & 0x0F);      // 4 bits of nested comment counter
887 
888     // base for parsing number
889     int base = 10;
890     // number has a float dot ?
891     bool with_dot = false;
892 
893     StyleContext sc(startPos, length, initStyle, styler);
894 
895     for (; sc.More(); sc.Forward()) {
896 
897         //// check for end of states
898         switch (sc.state) {
899             case SCE_JULIA_BRACKET:
900                 sc.SetState(SCE_JULIA_DEFAULT);
901                 break;
902             case SCE_JULIA_OPERATOR:
903                 resumeOperator(sc);
904                 break;
905             case SCE_JULIA_TYPEOPERATOR:
906                 sc.SetState(SCE_JULIA_DEFAULT);
907                 break;
908             case SCE_JULIA_TYPEANNOT:
909                 if (! IsIdentifierCharacter(sc.ch)) {
910                     sc.SetState(SCE_JULIA_DEFAULT);
911                 }
912                 break;
913             case SCE_JULIA_IDENTIFIER:
914                 // String literal
915                 if (sc.ch == '\"') {
916                     // If the string literal has a prefix, interpolation is disabled
917                     israwstring = true;
918                     sc.ChangeState(SCE_JULIA_STRINGLITERAL);
919                     sc.SetState(SCE_JULIA_DEFAULT);
920 
921                 } else if (sc.ch == '`') {
922                     // If the string literal has a prefix, interpolation is disabled
923                     israwstring = true;
924                     sc.ChangeState(SCE_JULIA_COMMANDLITERAL);
925                     sc.SetState(SCE_JULIA_DEFAULT);
926 
927                 // Continue if the character is an identifier character
928                 } else if (! IsIdentifierCharacter(sc.ch)) {
929                     char s[MAX_JULIA_IDENT_CHARS + 1];
930                     sc.GetCurrent(s, sizeof(s));
931 
932                     // Treat the keywords differently if we are indexing or not
933                     if ( indexing_level > 0 && CheckBoundsIndexing(s)) {
934                         // Inside [], (), `begin` and `end` are numbers not block keywords
935                         sc.ChangeState(SCE_JULIA_NUMBER);
936                         transpose = false;
937 
938                     } else {
939                         if (keywords.InList(s)) {
940                             sc.ChangeState(SCE_JULIA_KEYWORD1);
941                             transpose = false;
942                         } else if (identifiers2.InList(s)) {
943                             sc.ChangeState(SCE_JULIA_KEYWORD2);
944                             transpose = false;
945                         } else if (identifiers3.InList(s)) {
946                             sc.ChangeState(SCE_JULIA_KEYWORD3);
947                             transpose = false;
948                         } else if (identifiers4.InList(s)) {
949                             sc.ChangeState(SCE_JULIA_KEYWORD4);
950                             // These identifiers can be used for variable names also,
951                             // so transpose is not forbidden.
952                             //transpose = false;
953                         }
954                     }
955                     sc.SetState(SCE_JULIA_DEFAULT);
956 
957                     // TODO: recognize begin-end blocks inside list comprehension
958                     // b = [(begin n%2; n*2 end) for n in 1:10]
959                     // TODO: recognize better comprehension for-if to avoid problem with code-folding
960                     // c = [(if isempty(a); missing else first(b) end) for (a, b) in zip(l1, l2)]
961                 }
962                 break;
963             case SCE_JULIA_NUMBER:
964                 resumeNumber(sc, base, with_dot, options.highlightLexerror);
965                 break;
966             case SCE_JULIA_CHAR:
967                 resumeCharacter(sc, options.highlightLexerror);
968                 break;
969             case SCE_JULIA_DOCSTRING:
970                 resumeString(sc, true, !israwstring);
971                 if (sc.state == SCE_JULIA_DEFAULT && israwstring) {
972                     israwstring = false;
973                 }
974                 break;
975             case SCE_JULIA_STRING:
976                 resumeString(sc, false, !israwstring);
977                 if (sc.state == SCE_JULIA_DEFAULT && israwstring) {
978                     israwstring = false;
979                 }
980                 break;
981             case SCE_JULIA_COMMAND:
982                 resumeCommand(sc, triple_backtick, !israwstring);
983                 break;
984             case SCE_JULIA_MACRO:
985                 if (IsASpace(sc.ch) || ! IsIdentifierCharacter(sc.ch)) {
986                     sc.SetState(SCE_JULIA_DEFAULT);
987                 }
988                 break;
989             case SCE_JULIA_SYMBOL:
990                 if (! IsIdentifierCharacter(sc.ch)) {
991                     sc.SetState(SCE_JULIA_DEFAULT);
992                 }
993                 break;
994             case SCE_JULIA_COMMENT:
995                 if( commentDepth > 0 ) {
996                     // end or start of a nested a block comment
997                     if ( sc.ch == '=' && sc.chNext == '#') {
998                         commentDepth --;
999                         sc.Forward();
1000 
1001                         if (commentDepth == 0) {
1002                             sc.ForwardSetState(SCE_JULIA_DEFAULT);
1003                         }
1004                     } else if( sc.ch == '#' && sc.chNext == '=') {
1005                         commentDepth ++;
1006                         sc.Forward();
1007                     }
1008                 } else {
1009                     // single line comment
1010                     if (sc.atLineEnd || sc.ch == '\r' || sc.ch == '\n') {
1011                         sc.SetState(SCE_JULIA_DEFAULT);
1012                         transpose = false;
1013                     }
1014                 }
1015                 break;
1016         }
1017 
1018         // check start of a new state
1019         if (sc.state == SCE_JULIA_DEFAULT) {
1020             if (sc.ch == '#') {
1021                 sc.SetState(SCE_JULIA_COMMENT);
1022                 // increment depth if we are a block comment
1023                 if(sc.chNext == '=') {
1024                     commentDepth ++;
1025                     sc.Forward();
1026                 }
1027             } else if (sc.ch == '!') {
1028                 sc.SetState(SCE_JULIA_OPERATOR);
1029             } else if (sc.ch == '\'') {
1030                 if (transpose) {
1031                     sc.SetState(SCE_JULIA_OPERATOR);
1032                 } else {
1033                     sc.SetState(SCE_JULIA_CHAR);
1034                 }
1035             } else if (sc.ch == '\"') {
1036                 istripledocstring = (sc.chNext == '\"' && sc.GetRelativeCharacter(2) == '\"');
1037                 if (istripledocstring) {
1038                     sc.SetState(SCE_JULIA_DOCSTRING);
1039                     // Move to the end of the triple quotes
1040                     Sci_PositionU nextIndex = sc.currentPos + 2;
1041                     while (nextIndex > sc.currentPos && sc.More()) {
1042                         sc.Forward();
1043                     }
1044                 } else {
1045                     sc.SetState(SCE_JULIA_STRING);
1046                 }
1047             } else if (sc.ch == '`') {
1048                 triple_backtick = (sc.chNext == '`' && sc.GetRelativeCharacter(2) == '`');
1049                 sc.SetState(SCE_JULIA_COMMAND);
1050                 if (triple_backtick) {
1051                     // Move to the end of the triple backticks
1052                     Sci_PositionU nextIndex = sc.currentPos + 2;
1053                     while (nextIndex > sc.currentPos && sc.More()) {
1054                         sc.Forward();
1055                     }
1056                 }
1057             } else if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
1058                 initNumber(sc, base, with_dot);
1059             } else if (IsIdentifierFirstCharacter(sc.ch)) {
1060                 sc.SetState(SCE_JULIA_IDENTIFIER);
1061                 transpose = true;
1062             } else if (sc.ch == '@') {
1063                 sc.SetState(SCE_JULIA_MACRO);
1064                 transpose = false;
1065 
1066             // Several parsing of operators, should keep the order of `if` blocks
1067             } else if ((sc.ch == ':' || sc.ch == '<' || sc.ch == '>') && sc.chNext == ':') {
1068                 sc.SetState(SCE_JULIA_TYPEOPERATOR);
1069                 sc.Forward();
1070                 // Highlight the next identifier, if option is set
1071                 if (options.highlightTypeannotation &&
1072                     IsIdentifierFirstCharacter(sc.chNext)) {
1073                     sc.ForwardSetState(SCE_JULIA_TYPEANNOT);
1074                 }
1075             } else if (sc.ch == ':') {
1076                 // TODO: improve detection of range
1077                 // should be solved with begin-end parsing
1078                 // `push!(arr, s1 :s2)` and `a[begin :end]
1079                 if (IsIdentifierFirstCharacter(sc.chNext) &&
1080                     ! IsIdentifierCharacter(sc.chPrev) &&
1081                     sc.chPrev != ')' && sc.chPrev != ']' ) {
1082                     sc.SetState(SCE_JULIA_SYMBOL);
1083                 } else {
1084                     sc.SetState(SCE_JULIA_OPERATOR);
1085                 }
1086             } else if (IsJuliaParen(sc.ch)) {
1087                 if (sc.ch == '[') {
1088                     list_comprehension ++;
1089                     indexing_level ++;
1090                 } else if (sc.ch == ']' && (indexing_level > 0)) {
1091                     list_comprehension --;
1092                     indexing_level --;
1093                 } else if (sc.ch == '(') {
1094                     list_comprehension ++;
1095                 } else if (sc.ch == ')' && (list_comprehension > 0)) {
1096                     list_comprehension --;
1097                 }
1098 
1099                 if (sc.ch == ')' || sc.ch == ']' || sc.ch == '}') {
1100                     transpose = true;
1101                 } else {
1102                     transpose = false;
1103                 }
1104                 sc.SetState(SCE_JULIA_BRACKET);
1105             } else if (IsOperatorFirstCharacter(sc.ch)) {
1106                 transpose = false;
1107                 sc.SetState(SCE_JULIA_OPERATOR);
1108             } else {
1109                 transpose = false;
1110             }
1111         }
1112 
1113         // update the line information (used for line-by-line lexing and folding)
1114         if (sc.atLineEnd) {
1115             // set the line state to the current state
1116             curLine = styler.GetLine(sc.currentPos);
1117 
1118             lineState = ((transpose ? 1 : 0) << 0) |
1119                         ((istripledocstring ? 1 : 0) << 1) |
1120                         ((triple_backtick ? 1 : 0) << 2) |
1121                         ((israwstring ? 1 : 0) << 3) |
1122                         ((indexing_level & 0x0F) << 4) |
1123                         ((list_comprehension & 0x0F) << 8) |
1124                         ((commentDepth & 0x0F) << 12);
1125             styler.SetLineState(curLine, lineState);
1126         }
1127     }
1128     sc.Complete();
1129 }
1130 
Fold(Sci_PositionU startPos,Sci_Position length,int initStyle,IDocument * pAccess)1131 void SCI_METHOD LexerJulia::Fold(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) {
1132 
1133 	if (!options.fold)
1134 		return;
1135 
1136 	LexAccessor styler(pAccess);
1137 
1138 	Sci_PositionU endPos = startPos + length;
1139 	int visibleChars = 0;
1140 	Sci_Position lineCurrent = styler.GetLine(startPos);
1141 	int levelCurrent = SC_FOLDLEVELBASE;
1142     int lineState = 0;
1143 	if (lineCurrent > 0) {
1144 		levelCurrent = styler.LevelAt(lineCurrent-1) >> 16;
1145         lineState = styler.GetLineState(lineCurrent-1);
1146     }
1147 
1148     // level of nested brackets
1149     int indexing_level = (int)((lineState >> 4) & 0x0F);     // 4 bits of bracket nesting counter
1150     // level of nested parenthesis or brackets
1151     int list_comprehension = (int)((lineState >> 8) & 0x0F); // 4 bits of parenthesis nesting counter
1152     //int commentDepth = (int)((lineState >> 12) & 0x0F);      // 4 bits of nested comment counter
1153 
1154 	Sci_PositionU lineStartNext = styler.LineStart(lineCurrent+1);
1155 	int levelNext = levelCurrent;
1156 	char chNext = styler[startPos];
1157 	int stylePrev = styler.StyleAt(startPos - 1);
1158 	int styleNext = styler.StyleAt(startPos);
1159 	int style = initStyle;
1160     char word[100];
1161     int wordlen = 0;
1162     for (Sci_PositionU i = startPos; i < endPos; i++) {
1163 		char ch = chNext;
1164 		chNext = styler.SafeGetCharAt(i + 1);
1165 		style = styleNext;
1166 		styleNext = styler.StyleAt(i + 1);
1167 		bool atEOL = i == (lineStartNext-1);
1168 
1169         // a start/end of comment block
1170         if (options.foldComment && style == SCE_JULIA_COMMENT) {
1171             // start of block comment
1172             if (ch == '#' && chNext == '=') {
1173                 levelNext ++;
1174             }
1175             // end of block comment
1176             if (ch == '=' && chNext == '#' && levelNext > 0) {
1177                 levelNext --;
1178             }
1179         }
1180 
1181         // Syntax based folding, accounts for list comprehension
1182         if (options.foldSyntaxBased) {
1183             // list comprehension allow `for`, `if` and `begin` without `end`
1184             if (style == SCE_JULIA_BRACKET) {
1185                 if (ch == '[') {
1186                     list_comprehension ++;
1187                     indexing_level ++;
1188                     levelNext ++;
1189                 } else if (ch == ']') {
1190                     list_comprehension --;
1191                     indexing_level --;
1192                     levelNext --;
1193                 } else if (ch == '(') {
1194                     list_comprehension ++;
1195                     levelNext ++;
1196                 } else if (ch == ')') {
1197                     list_comprehension --;
1198                     levelNext --;
1199                 }
1200                 // check non-negative
1201                 if (indexing_level < 0) {
1202                     indexing_level = 0;
1203                 }
1204                 if (list_comprehension < 0) {
1205                     list_comprehension = 0;
1206                 }
1207             }
1208 
1209             // keyword
1210             if (style == SCE_JULIA_KEYWORD1) {
1211                 word[wordlen++] = static_cast<char>(ch);
1212                 if (wordlen == 100) {  // prevent overflow
1213                     word[0] = '\0';
1214                     wordlen = 1;
1215                 }
1216                 if (styleNext != SCE_JULIA_KEYWORD1) {
1217                     word[wordlen] = '\0';
1218                     wordlen = 0;
1219                     if (list_comprehension <= 0 && indexing_level <= 0) {
1220                         levelNext += CheckKeywordFoldPoint(word);
1221                     }
1222                 }
1223             }
1224         }
1225 
1226         // Docstring
1227         if (options.foldDocstring) {
1228             if (stylePrev != SCE_JULIA_DOCSTRING && style == SCE_JULIA_DOCSTRING) {
1229                 levelNext ++;
1230             } else if (style == SCE_JULIA_DOCSTRING && styleNext != SCE_JULIA_DOCSTRING) {
1231                 levelNext --;
1232             }
1233         }
1234 
1235         // check non-negative level
1236         if (levelNext < 0) {
1237             levelNext = 0;
1238         }
1239 
1240         if (!IsASpace(ch)) {
1241             visibleChars++;
1242         }
1243         stylePrev = style;
1244 
1245         if (atEOL || (i == endPos-1)) {
1246             int levelUse = levelCurrent;
1247             int lev = levelUse | levelNext << 16;
1248             if (visibleChars == 0 && options.foldCompact) {
1249                 lev |= SC_FOLDLEVELWHITEFLAG;
1250             }
1251             if (levelUse < levelNext) {
1252                 lev |= SC_FOLDLEVELHEADERFLAG;
1253             }
1254             if (lev != styler.LevelAt(lineCurrent)) {
1255                 styler.SetLevel(lineCurrent, lev);
1256             }
1257             lineCurrent++;
1258             lineStartNext = styler.LineStart(lineCurrent+1);
1259             levelCurrent = levelNext;
1260             if (atEOL && (i == static_cast<Sci_PositionU>(styler.Length() - 1))) {
1261                 // There is an empty line at end of file so give it same level and empty
1262                 styler.SetLevel(lineCurrent, (levelCurrent | levelCurrent << 16) | SC_FOLDLEVELWHITEFLAG);
1263             }
1264             visibleChars = 0;
1265         }
1266     }
1267 }
1268 
1269 LexerModule lmJulia(SCLEX_JULIA, LexerJulia::LexerFactoryJulia, "julia", juliaWordLists);
1270