1 | /** |
2 | * @file LexFSharp.cxx |
3 | * Lexer for F# 5.0 |
4 | * Copyright (c) 2021 Robert Di Pardo <dipardo.r@gmail.com> |
5 | * Parts of LexerFSharp::Lex were adapted from LexCaml.cxx by Robert Roessler ("RR"). |
6 | * Parts of LexerFSharp::Fold were adapted from LexCPP.cxx by Neil Hodgson and Udo Lechner. |
7 | * The License.txt file describes the conditions under which this software may be distributed. |
8 | */ |
9 | // clang-format off |
10 | #include <cstdlib> |
11 | #include <cassert> |
12 | |
13 | #include <string> |
14 | #include <string_view> |
15 | #include <map> |
16 | #include <functional> |
17 | |
18 | #include "ILexer.h" |
19 | #include "Scintilla.h" |
20 | #include "SciLexer.h" |
21 | |
22 | #include "WordList.h" |
23 | #include "LexAccessor.h" |
24 | #include "StyleContext.h" |
25 | #include "CharacterSet.h" |
26 | #include "LexerModule.h" |
27 | #include "OptionSet.h" |
28 | #include "DefaultLexer.h" |
29 | // clang-format on |
30 | |
31 | using namespace Scintilla; |
32 | using namespace Lexilla; |
33 | |
34 | static const char *const lexerName = "fsharp" ; |
35 | static constexpr int WORDLIST_SIZE = 5; |
36 | static const char *const fsharpWordLists[] = { |
37 | "standard language keywords" , |
38 | "core functions, including those in the FSharp.Collections namespace" , |
39 | "built-in types, core namespaces, modules" , |
40 | "optional" , |
41 | "optional" , |
42 | nullptr, |
43 | }; |
44 | static constexpr int keywordClasses[] = { |
45 | SCE_FSHARP_KEYWORD, SCE_FSHARP_KEYWORD2, SCE_FSHARP_KEYWORD3, SCE_FSHARP_KEYWORD4, SCE_FSHARP_KEYWORD5, |
46 | }; |
47 | |
48 | namespace { |
49 | |
50 | struct OptionsFSharp { |
51 | bool fold; |
52 | bool foldCompact; |
53 | bool ; |
54 | bool ; |
55 | bool ; |
56 | bool foldPreprocessor; |
57 | bool foldImports; |
58 | OptionsFSharp() { |
59 | fold = true; |
60 | foldCompact = true; |
61 | foldComment = true; |
62 | foldCommentStream = true; |
63 | foldCommentMultiLine = true; |
64 | foldPreprocessor = false; |
65 | foldImports = true; |
66 | } |
67 | }; |
68 | |
69 | struct OptionSetFSharp : public OptionSet<OptionsFSharp> { |
70 | OptionSetFSharp() { |
71 | DefineProperty("fold" , &OptionsFSharp::fold); |
72 | DefineProperty("fold.compact" , &OptionsFSharp::foldCompact); |
73 | DefineProperty("fold.comment" , &OptionsFSharp::foldComment, |
74 | "Setting this option to 0 disables comment folding in F# files." ); |
75 | |
76 | DefineProperty("fold.fsharp.comment.stream" , &OptionsFSharp::foldCommentStream, |
77 | "Setting this option to 0 disables folding of ML-style comments in F# files when " |
78 | "fold.comment=1." ); |
79 | |
80 | DefineProperty("fold.fsharp.comment.multiline" , &OptionsFSharp::foldCommentMultiLine, |
81 | "Setting this option to 0 disables folding of grouped line comments in F# files when " |
82 | "fold.comment=1." ); |
83 | |
84 | DefineProperty("fold.fsharp.preprocessor" , &OptionsFSharp::foldPreprocessor, |
85 | "Setting this option to 1 enables folding of F# compiler directives." ); |
86 | |
87 | DefineProperty("fold.fsharp.imports" , &OptionsFSharp::foldImports, |
88 | "Setting this option to 0 disables folding of F# import declarations." ); |
89 | |
90 | DefineWordListSets(fsharpWordLists); |
91 | } |
92 | }; |
93 | |
94 | const CharacterSet setOperators = CharacterSet(CharacterSet::setNone, "~^'-+*/%=@|&<>()[]{};,:!?" ); |
95 | const CharacterSet setClosingTokens = CharacterSet(CharacterSet::setNone, ")}]" ); |
96 | const CharacterSet setFormatSpecs = CharacterSet(CharacterSet::setNone, ".%aAbcdeEfFgGiMoOstuxX0123456789" ); |
97 | const CharacterSet setFormatFlags = CharacterSet(CharacterSet::setNone, ".-+0 " ); |
98 | const CharacterSet numericMetaChars1 = CharacterSet(CharacterSet::setNone, "_IbeEflmnosuxy" ); |
99 | const CharacterSet numericMetaChars2 = CharacterSet(CharacterSet::setNone, "lnsy" ); |
100 | std::map<int, int> numericPrefixes = { { 'b', 2 }, { 'o', 8 }, { 'x', 16 } }; |
101 | constexpr Sci_Position ZERO_LENGTH = -1; |
102 | |
103 | struct FSharpString { |
104 | Sci_Position startPos; |
105 | int startChar; |
106 | FSharpString() { |
107 | startPos = ZERO_LENGTH; |
108 | startChar = '"'; |
109 | } |
110 | constexpr bool HasLength() const { |
111 | return startPos > ZERO_LENGTH; |
112 | } |
113 | constexpr bool CanInterpolate() const { |
114 | return startChar == '$'; |
115 | } |
116 | }; |
117 | |
118 | class UnicodeChar { |
119 | enum class Notation { none, asciiDec, asciiHex, utf16, utf32 }; |
120 | Notation type = Notation::none; |
121 | // single-byte Unicode char (000 - 255) |
122 | int asciiDigits[3] = { 0 }; |
123 | int maxDigit = '9'; |
124 | int toEnd = 0; |
125 | bool invalid = false; |
126 | |
127 | public: |
128 | UnicodeChar() noexcept = default; |
129 | explicit UnicodeChar(const int prefix) { |
130 | if (IsADigit(prefix)) { |
131 | *asciiDigits = prefix; |
132 | if (*asciiDigits >= '0' && *asciiDigits <= '2') { |
133 | type = Notation::asciiDec; |
134 | // count first digit as "prefix" |
135 | toEnd = 2; |
136 | } |
137 | } else if (prefix == 'x' || prefix == 'u' || prefix == 'U') { |
138 | switch (prefix) { |
139 | case 'x': |
140 | type = Notation::asciiHex; |
141 | toEnd = 2; |
142 | break; |
143 | case 'u': |
144 | type = Notation::utf16; |
145 | toEnd = 4; |
146 | break; |
147 | case 'U': |
148 | type = Notation::utf32; |
149 | toEnd = 8; |
150 | break; |
151 | } |
152 | } |
153 | } |
154 | void Parse(const int ch) { |
155 | invalid = false; |
156 | switch (type) { |
157 | case Notation::asciiDec: { |
158 | maxDigit = (*asciiDigits < '2') ? '9' : (asciiDigits[1] <= '4') ? '9' : '5'; |
159 | if (IsADigit(ch) && asciiDigits[1] <= maxDigit && ch <= maxDigit) { |
160 | asciiDigits[1] = ch; |
161 | toEnd--; |
162 | } else { |
163 | invalid = true; |
164 | } |
165 | break; |
166 | } |
167 | case Notation::asciiHex: |
168 | case Notation::utf16: |
169 | if (IsADigit(ch, 16)) { |
170 | toEnd--; |
171 | } else { |
172 | invalid = true; |
173 | } |
174 | break; |
175 | case Notation::utf32: |
176 | if ((toEnd > 6 && ch == '0') || (toEnd <= 6 && IsADigit(ch, 16))) { |
177 | toEnd--; |
178 | } else { |
179 | invalid = true; |
180 | } |
181 | break; |
182 | case Notation::none: |
183 | break; |
184 | } |
185 | } |
186 | constexpr bool AtEnd() noexcept { |
187 | return invalid || type == Notation::none || (type != Notation::none && toEnd < 0); |
188 | } |
189 | }; |
190 | |
191 | inline bool (StyleContext &cxt) { |
192 | // match (* ... *), but allow point-free usage of the `*` operator, |
193 | // e.g. List.fold (*) 1 [ 1; 2; 3 ] |
194 | return (cxt.Match('(', '*') && cxt.GetRelative(2) != ')'); |
195 | } |
196 | |
197 | inline bool (const StyleContext &cxt) { |
198 | return (cxt.ch == ')' && cxt.chPrev == '*'); |
199 | } |
200 | |
201 | inline bool (const StyleContext &cxt) { |
202 | // style shebang lines as comments in F# scripts: |
203 | // https://fsharp.org/specs/language-spec/4.1/FSharpSpec-4.1-latest.pdf#page=30&zoom=auto,-98,537 |
204 | return cxt.Match('/', '/') || cxt.Match('#', '!'); |
205 | } |
206 | |
207 | inline bool MatchLineNumberStart(StyleContext &cxt) { |
208 | return cxt.atLineStart && (cxt.MatchIgnoreCase("#line" ) || |
209 | (cxt.ch == '#' && (IsADigit(cxt.chNext) || IsADigit(cxt.GetRelative(2))))); |
210 | } |
211 | |
212 | inline bool MatchPPDirectiveStart(const StyleContext &cxt) { |
213 | return (cxt.atLineStart && cxt.ch == '#' && iswordstart(cxt.chNext)); |
214 | } |
215 | |
216 | inline bool MatchTypeAttributeStart(const StyleContext &cxt) { |
217 | return cxt.Match('[', '<'); |
218 | } |
219 | |
220 | inline bool MatchTypeAttributeEnd(const StyleContext &cxt) { |
221 | return (cxt.ch == ']' && cxt.chPrev == '>'); |
222 | } |
223 | |
224 | inline bool MatchQuotedExpressionStart(const StyleContext &cxt) { |
225 | return cxt.Match('<', '@'); |
226 | } |
227 | |
228 | inline bool MatchQuotedExpressionEnd(const StyleContext &cxt) { |
229 | return (cxt.ch == '>' && cxt.chPrev == '@'); |
230 | } |
231 | |
232 | inline bool MatchStringStart(const StyleContext &cxt) { |
233 | return (cxt.ch == '"' || cxt.Match('@', '"') || cxt.Match('$', '"') || cxt.Match('`', '`')); |
234 | } |
235 | |
236 | inline bool FollowsEscapedBackslash(StyleContext &cxt) { |
237 | int count = 0; |
238 | for (Sci_Position offset = 1; cxt.GetRelative(-offset) == '\\'; offset++) |
239 | count++; |
240 | return count % 2 != 0; |
241 | } |
242 | |
243 | inline bool MatchStringEnd(StyleContext &cxt, const FSharpString &fsStr) { |
244 | return (fsStr.HasLength() && |
245 | // end of quoted identifier? |
246 | ((cxt.ch == '`' && cxt.chPrev == '`') || |
247 | // end of literal or interpolated triple-quoted string? |
248 | ((fsStr.startChar == '"' || (fsStr.CanInterpolate() && cxt.chPrev != '$')) && |
249 | cxt.MatchIgnoreCase("\"\"\"" )) || |
250 | // end of verbatim string? |
251 | (fsStr.startChar == '@' && |
252 | // embedded quotes must be in pairs |
253 | cxt.ch == '"' && cxt.chNext != '"' && |
254 | (cxt.chPrev != '"' || (cxt.chPrev == '"' && |
255 | // empty verbatim string? |
256 | (cxt.GetRelative(-2) == '@' || |
257 | // pair of quotes at end of string? |
258 | (cxt.GetRelative(-2) == '"' && cxt.GetRelative(-3) != '@'))))))) || |
259 | (!fsStr.HasLength() && cxt.ch == '"' && |
260 | ((cxt.chPrev != '\\' || (cxt.GetRelative(-2) == '\\' && !FollowsEscapedBackslash(cxt))) || |
261 | // treat backslashes as char literals in verbatim strings |
262 | (fsStr.startChar == '@' && cxt.chPrev == '\\'))); |
263 | } |
264 | |
265 | inline bool MatchCharacterStart(StyleContext &cxt) { |
266 | // don't style generic type parameters: 'a, 'b, 'T, etc. |
267 | return (cxt.ch == '\'' && !(cxt.chPrev == ':' || cxt.GetRelative(-2) == ':')); |
268 | } |
269 | |
270 | inline bool CanEmbedQuotes(StyleContext &cxt) { |
271 | // allow unescaped double quotes inside literal or interpolated triple-quoted strings, verbatim strings, |
272 | // and quoted identifiers: |
273 | // - https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/strings |
274 | // - https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/interpolated-strings#syntax |
275 | // - https://fsharp.org/specs/language-spec/4.1/FSharpSpec-4.1-latest.pdf#page=25&zoom=auto,-98,600 |
276 | return cxt.MatchIgnoreCase("$\"\"\"" ) || cxt.MatchIgnoreCase("\"\"\"" ) || cxt.Match('@', '"') || |
277 | cxt.Match('`', '`'); |
278 | } |
279 | |
280 | inline bool IsNumber(StyleContext &cxt, const int base = 10) { |
281 | return IsADigit(cxt.ch, base) || (IsADigit(cxt.chPrev, base) && numericMetaChars1.Contains(cxt.ch)) || |
282 | (IsADigit(cxt.GetRelative(-2), base) && numericMetaChars2.Contains(cxt.ch)); |
283 | } |
284 | |
285 | inline bool IsFloat(const StyleContext &cxt) { |
286 | return (cxt.ch == '.' && IsADigit(cxt.chPrev)) || |
287 | ((cxt.ch == '+' || cxt.ch == '-' ) && IsADigit(cxt.chNext)); |
288 | } |
289 | |
290 | inline bool IsLineEnd(StyleContext &cxt, const Sci_Position offset) { |
291 | const int ch = cxt.GetRelative(offset, '\n'); |
292 | return (ch == '\r' || ch == '\n'); |
293 | } |
294 | |
295 | class LexerFSharp : public DefaultLexer { |
296 | WordList keywords[WORDLIST_SIZE]; |
297 | OptionsFSharp options; |
298 | OptionSetFSharp optionSet; |
299 | |
300 | public: |
301 | explicit LexerFSharp() : DefaultLexer(lexerName, SCLEX_FSHARP) { |
302 | } |
303 | static ILexer5 *LexerFactoryFSharp() { |
304 | return new LexerFSharp(); |
305 | } |
306 | virtual ~LexerFSharp() { |
307 | } |
308 | void SCI_METHOD Release() noexcept override { |
309 | delete this; |
310 | } |
311 | int SCI_METHOD Version() const noexcept override { |
312 | return lvRelease5; |
313 | } |
314 | const char *SCI_METHOD GetName() noexcept override { |
315 | return lexerName; |
316 | } |
317 | int SCI_METHOD GetIdentifier() noexcept override { |
318 | return SCLEX_FSHARP; |
319 | } |
320 | int SCI_METHOD LineEndTypesSupported() noexcept override { |
321 | return SC_LINE_END_TYPE_DEFAULT; |
322 | } |
323 | void *SCI_METHOD PrivateCall(int, void *) noexcept override { |
324 | return nullptr; |
325 | } |
326 | const char *SCI_METHOD DescribeWordListSets() override { |
327 | return optionSet.DescribeWordListSets(); |
328 | } |
329 | const char *SCI_METHOD PropertyNames() override { |
330 | return optionSet.PropertyNames(); |
331 | } |
332 | int SCI_METHOD PropertyType(const char *name) override { |
333 | return optionSet.PropertyType(name); |
334 | } |
335 | const char *SCI_METHOD DescribeProperty(const char *name) override { |
336 | return optionSet.DescribeProperty(name); |
337 | } |
338 | const char *SCI_METHOD PropertyGet(const char *key) override { |
339 | return optionSet.PropertyGet(key); |
340 | } |
341 | Sci_Position SCI_METHOD PropertySet(const char *key, const char *val) override { |
342 | if (optionSet.PropertySet(&options, key, val)) { |
343 | return 0; |
344 | } |
345 | return ZERO_LENGTH; |
346 | } |
347 | Sci_Position SCI_METHOD WordListSet(int n, const char *wl) override; |
348 | void SCI_METHOD Lex(Sci_PositionU start, Sci_Position length, int initStyle, IDocument *pAccess) override; |
349 | void SCI_METHOD Fold(Sci_PositionU start, Sci_Position length, int initStyle,IDocument *pAccess) override; |
350 | }; |
351 | |
352 | Sci_Position SCI_METHOD LexerFSharp::WordListSet(int n, const char *wl) { |
353 | WordList *wordListN = nullptr; |
354 | Sci_Position firstModification = ZERO_LENGTH; |
355 | |
356 | if (n < WORDLIST_SIZE) { |
357 | wordListN = &keywords[n]; |
358 | } |
359 | if (wordListN) { |
360 | WordList wlNew; |
361 | wlNew.Set(wl); |
362 | |
363 | if (*wordListN != wlNew) { |
364 | wordListN->Set(wl); |
365 | firstModification = 0; |
366 | } |
367 | } |
368 | return firstModification; |
369 | } |
370 | |
371 | void SCI_METHOD LexerFSharp::Lex(Sci_PositionU start, Sci_Position length, int initStyle, IDocument *pAccess) { |
372 | LexAccessor styler(pAccess); |
373 | StyleContext sc(start, static_cast<Sci_PositionU>(length), initStyle, styler); |
374 | Sci_PositionU cursor = 0; |
375 | UnicodeChar uniCh = UnicodeChar(); |
376 | FSharpString fsStr = FSharpString(); |
377 | constexpr Sci_Position MAX_WORD_LEN = 64; |
378 | constexpr int SPACE = ' '; |
379 | int currentBase = 10; |
380 | |
381 | while (sc.More()) { |
382 | Sci_PositionU colorSpan = sc.currentPos - 1; |
383 | int state = -1; |
384 | bool advance = true; |
385 | |
386 | switch (sc.state & 0xff) { |
387 | case SCE_FSHARP_DEFAULT: |
388 | cursor = sc.currentPos; |
389 | |
390 | if (MatchLineNumberStart(sc)) { |
391 | state = SCE_FSHARP_LINENUM; |
392 | } else if (MatchPPDirectiveStart(sc)) { |
393 | state = SCE_FSHARP_PREPROCESSOR; |
394 | } else if (MatchLineComment(sc)) { |
395 | state = SCE_FSHARP_COMMENTLINE; |
396 | sc.Forward(); |
397 | sc.ch = SPACE; |
398 | } else if (MatchStreamCommentStart(sc)) { |
399 | state = SCE_FSHARP_COMMENT; |
400 | sc.Forward(); |
401 | sc.ch = SPACE; |
402 | } else if (MatchTypeAttributeStart(sc)) { |
403 | state = SCE_FSHARP_ATTRIBUTE; |
404 | sc.Forward(); |
405 | } else if (MatchQuotedExpressionStart(sc)) { |
406 | state = SCE_FSHARP_QUOTATION; |
407 | sc.Forward(); |
408 | } else if (MatchCharacterStart(sc)) { |
409 | state = SCE_FSHARP_CHARACTER; |
410 | } else if (MatchStringStart(sc)) { |
411 | fsStr.startChar = sc.ch; |
412 | fsStr.startPos = ZERO_LENGTH; |
413 | if (CanEmbedQuotes(sc)) { |
414 | // double quotes after this position should be non-terminating |
415 | fsStr.startPos = static_cast<Sci_Position>(sc.currentPos - cursor); |
416 | } |
417 | if (sc.ch == '`') { |
418 | state = SCE_FSHARP_QUOT_IDENTIFIER; |
419 | } else if (sc.ch == '@') { |
420 | state = SCE_FSHARP_VERBATIM; |
421 | } else { |
422 | state = SCE_FSHARP_STRING; |
423 | } |
424 | } else if (IsADigit(sc.ch, currentBase) || |
425 | ((sc.ch == '+' || sc.ch == '-') && IsADigit(sc.chNext))) { |
426 | state = SCE_FSHARP_NUMBER; |
427 | if (sc.ch == '0') { |
428 | const int prefix = sc.chNext; |
429 | if (numericPrefixes.find(prefix) != numericPrefixes.end()) { |
430 | currentBase = numericPrefixes[prefix]; |
431 | } |
432 | } |
433 | } else if (setOperators.Contains(sc.ch) && |
434 | // don't use operator style in async keywords (e.g. `return!`) |
435 | !(sc.ch == '!' && iswordstart(sc.chPrev)) && |
436 | // don't use operator style in member access, array/string indexing |
437 | !(sc.ch == '.' && (sc.chPrev == '\"' || iswordstart(sc.chPrev)) && |
438 | (iswordstart(sc.chNext) || sc.chNext == '['))) { |
439 | state = SCE_FSHARP_OPERATOR; |
440 | } else if (iswordstart(sc.ch)) { |
441 | state = SCE_FSHARP_IDENTIFIER; |
442 | } else { |
443 | state = SCE_FSHARP_DEFAULT; |
444 | } |
445 | break; |
446 | case SCE_FSHARP_LINENUM: |
447 | case SCE_FSHARP_PREPROCESSOR: |
448 | case SCE_FSHARP_COMMENTLINE: |
449 | if (sc.MatchLineEnd()) { |
450 | state = SCE_FSHARP_DEFAULT; |
451 | advance = false; |
452 | } |
453 | break; |
454 | case SCE_FSHARP_COMMENT: |
455 | case SCE_FSHARP_ATTRIBUTE: |
456 | case SCE_FSHARP_QUOTATION: |
457 | if (MatchStreamCommentEnd(sc) || MatchTypeAttributeEnd(sc) || MatchQuotedExpressionEnd(sc)) { |
458 | state = SCE_FSHARP_DEFAULT; |
459 | colorSpan++; |
460 | } |
461 | break; |
462 | case SCE_FSHARP_CHARACTER: |
463 | if (sc.chPrev == '\\' && sc.GetRelative(-2) != '\\') { |
464 | uniCh = UnicodeChar(sc.ch); |
465 | } else if (sc.ch == '\'' && |
466 | ((sc.chPrev == ' ' && sc.GetRelative(-2) == '\'') || sc.chPrev != '\\' || |
467 | (sc.chPrev == '\\' && sc.GetRelative(-2) == '\\'))) { |
468 | // byte literal? |
469 | if (sc.Match('\'', 'B')) { |
470 | sc.Forward(); |
471 | colorSpan++; |
472 | } |
473 | if (!sc.atLineEnd) { |
474 | colorSpan++; |
475 | } else { |
476 | sc.ChangeState(SCE_FSHARP_IDENTIFIER); |
477 | } |
478 | state = SCE_FSHARP_DEFAULT; |
479 | } else { |
480 | uniCh.Parse(sc.ch); |
481 | if (uniCh.AtEnd() && (sc.currentPos - cursor) >= 2) { |
482 | // terminate now, since we left the char behind |
483 | sc.ChangeState(SCE_FSHARP_IDENTIFIER); |
484 | advance = false; |
485 | } |
486 | } |
487 | break; |
488 | case SCE_FSHARP_STRING: |
489 | case SCE_FSHARP_VERBATIM: |
490 | case SCE_FSHARP_QUOT_IDENTIFIER: |
491 | if (MatchStringEnd(sc, fsStr)) { |
492 | const Sci_Position strLen = static_cast<Sci_Position>(sc.currentPos - cursor); |
493 | // backtrack to start of string |
494 | for (Sci_Position i = -strLen; i < 0; i++) { |
495 | const int startQuote = sc.GetRelative(i); |
496 | if (startQuote == '\"' || (startQuote == '`' && sc.GetRelative(i - 1) == '`')) { |
497 | // byte array? |
498 | if (sc.Match('\"', 'B')) { |
499 | sc.Forward(); |
500 | colorSpan++; |
501 | } |
502 | if (!sc.atLineEnd) { |
503 | colorSpan++; |
504 | } else { |
505 | sc.ChangeState(SCE_FSHARP_IDENTIFIER); |
506 | } |
507 | state = SCE_FSHARP_DEFAULT; |
508 | break; |
509 | } |
510 | } |
511 | } else if (sc.ch == '%' && |
512 | !(fsStr.startChar == '`' || sc.MatchIgnoreCase("% " ) || sc.MatchIgnoreCase("% \"" )) && |
513 | (setFormatSpecs.Contains(sc.chNext) || setFormatFlags.Contains(sc.chNext))) { |
514 | if (fsStr.CanInterpolate() && sc.chNext != '%') { |
515 | for (Sci_Position i = 2; i < length && !IsLineEnd(sc, i); i++) { |
516 | if (sc.GetRelative(i) == '{') { |
517 | state = setFormatSpecs.Contains(sc.GetRelative(i - 1)) |
518 | ? SCE_FSHARP_FORMAT_SPEC |
519 | : state; |
520 | break; |
521 | } |
522 | } |
523 | } else { |
524 | state = SCE_FSHARP_FORMAT_SPEC; |
525 | } |
526 | } |
527 | break; |
528 | case SCE_FSHARP_IDENTIFIER: |
529 | if (!(iswordstart(sc.ch) || sc.ch == '\'')) { |
530 | const Sci_Position wordLen = static_cast<Sci_Position>(sc.currentPos - cursor); |
531 | if (wordLen < MAX_WORD_LEN) { |
532 | // wordLength is believable as keyword, [re-]construct token - RR |
533 | char token[MAX_WORD_LEN] = { 0 }; |
534 | for (Sci_Position i = -wordLen; i < 0; i++) { |
535 | token[wordLen + i] = static_cast<char>(sc.GetRelative(i)); |
536 | } |
537 | token[wordLen] = '\0'; |
538 | // a snake_case_identifier can never be a keyword |
539 | if (!(sc.ch == '_' || sc.GetRelative(-wordLen - 1) == '_')) { |
540 | for (int i = 0; i < WORDLIST_SIZE; i++) { |
541 | if (keywords[i].InList(token)) { |
542 | sc.ChangeState(keywordClasses[i]); |
543 | break; |
544 | } |
545 | } |
546 | } |
547 | } |
548 | state = SCE_FSHARP_DEFAULT; |
549 | advance = false; |
550 | } |
551 | break; |
552 | case SCE_FSHARP_OPERATOR: |
553 | // special-case "()" and "[]" tokens as KEYWORDS - RR |
554 | if (setClosingTokens.Contains(sc.ch) && |
555 | ((sc.ch == ')' && sc.chPrev == '(') || (sc.ch == ']' && sc.chPrev == '['))) { |
556 | sc.ChangeState(SCE_FSHARP_KEYWORD); |
557 | colorSpan++; |
558 | } else { |
559 | advance = false; |
560 | } |
561 | state = SCE_FSHARP_DEFAULT; |
562 | break; |
563 | case SCE_FSHARP_NUMBER: |
564 | state = (IsNumber(sc, currentBase) || IsFloat(sc)) |
565 | ? SCE_FSHARP_NUMBER |
566 | // change style even when operators aren't spaced |
567 | : setOperators.Contains(sc.ch) ? SCE_FSHARP_OPERATOR : SCE_FSHARP_DEFAULT; |
568 | currentBase = (state == SCE_FSHARP_NUMBER) ? currentBase : 10; |
569 | break; |
570 | case SCE_FSHARP_FORMAT_SPEC: |
571 | if (!setFormatSpecs.Contains(sc.chNext) || |
572 | !(setFormatFlags.Contains(sc.ch) || IsADigit(sc.ch)) || |
573 | (setFormatFlags.Contains(sc.ch) && sc.ch == sc.chNext)) { |
574 | colorSpan++; |
575 | state = (fsStr.startChar == '@') ? SCE_FSHARP_VERBATIM : SCE_FSHARP_STRING; |
576 | } |
577 | break; |
578 | } |
579 | |
580 | if (state >= SCE_FSHARP_DEFAULT) { |
581 | styler.ColourTo(colorSpan, sc.state); |
582 | sc.ChangeState(state); |
583 | } |
584 | |
585 | if (advance) { |
586 | sc.Forward(); |
587 | } |
588 | } |
589 | |
590 | sc.Complete(); |
591 | } |
592 | |
593 | bool LineContains(LexAccessor &styler, const char *word, const Sci_Position start, const Sci_Position end = 1, |
594 | const int chAttr = SCE_FSHARP_DEFAULT); |
595 | |
596 | void FoldLexicalGroup(LexAccessor &styler, int &levelNext, const Sci_Position lineCurrent, const char *word, |
597 | const int chAttr); |
598 | |
599 | void SCI_METHOD LexerFSharp::Fold(Sci_PositionU start, Sci_Position length, int initStyle, IDocument *pAccess) { |
600 | if (!options.fold) { |
601 | return; |
602 | } |
603 | |
604 | LexAccessor styler(pAccess); |
605 | const Sci_Position startPos = static_cast<Sci_Position>(start); |
606 | const Sci_PositionU endPos = start + length; |
607 | Sci_Position lineCurrent = styler.GetLine(startPos); |
608 | Sci_Position lineNext = lineCurrent + 1; |
609 | Sci_Position lineStartNext = styler.LineStart(lineNext); |
610 | int style = initStyle; |
611 | int styleNext = styler.StyleAt(startPos); |
612 | char chNext = styler[startPos]; |
613 | int levelNext; |
614 | int levelCurrent = SC_FOLDLEVELBASE; |
615 | int visibleChars = 0; |
616 | |
617 | if (lineCurrent > 0) { |
618 | levelCurrent = styler.LevelAt(lineCurrent - 1) >> 0x10; |
619 | } |
620 | |
621 | levelNext = levelCurrent; |
622 | |
623 | for (Sci_PositionU i = start; i < endPos; i++) { |
624 | const Sci_Position currentPos = static_cast<Sci_Position>(i); |
625 | const bool atEOL = currentPos == (lineStartNext - 1); |
626 | const bool atLineOrDocEnd = (atEOL || (i == (endPos - 1))); |
627 | const bool atDosOrMacEOL = (atEOL || styler.SafeGetCharAt(currentPos) == '\r'); |
628 | const int stylePrev = style; |
629 | const char ch = chNext; |
630 | const bool = (stylePrev == SCE_FSHARP_COMMENTLINE); |
631 | style = styleNext; |
632 | styleNext = styler.StyleAt(currentPos + 1); |
633 | chNext = styler.SafeGetCharAt(currentPos + 1); |
634 | |
635 | if (options.foldComment) { |
636 | if (options.foldCommentMultiLine && inLineComment && atDosOrMacEOL && |
637 | (lineCurrent > 0 || styler.StyleAt(lineStartNext) == SCE_FSHARP_COMMENTLINE)) { |
638 | FoldLexicalGroup(styler, levelNext, lineCurrent, "//" , SCE_FSHARP_COMMENTLINE); |
639 | } |
640 | |
641 | if (options.foldCommentStream && style == SCE_FSHARP_COMMENT && !inLineComment) { |
642 | if (stylePrev != SCE_FSHARP_COMMENT) { |
643 | levelNext++; |
644 | } else if (styleNext != SCE_FSHARP_COMMENT && !atEOL) { |
645 | levelNext--; |
646 | } |
647 | } |
648 | } |
649 | |
650 | if (options.foldPreprocessor && style == SCE_FSHARP_PREPROCESSOR) { |
651 | if (styler.Match(currentPos, "#if" )) { |
652 | levelNext++; |
653 | } else if (styler.Match(currentPos, "#endif" )) { |
654 | levelNext--; |
655 | } |
656 | } |
657 | |
658 | if (options.foldImports && atLineOrDocEnd && |
659 | LineContains(styler, "open " , lineCurrent, 1, SCE_FSHARP_KEYWORD)) { |
660 | FoldLexicalGroup(styler, levelNext, lineCurrent, "open " , SCE_FSHARP_KEYWORD); |
661 | } |
662 | |
663 | if (!IsASpace(ch)) { |
664 | visibleChars++; |
665 | } |
666 | |
667 | if (atLineOrDocEnd) { |
668 | int levelUse = levelCurrent; |
669 | int lev = levelUse | levelNext << 16; |
670 | |
671 | if (visibleChars == 0 && options.foldCompact) { |
672 | lev |= SC_FOLDLEVELWHITEFLAG; |
673 | } |
674 | if (levelUse < levelNext) { |
675 | lev |= SC_FOLDLEVELHEADERFLAG; |
676 | } |
677 | if (lev != styler.LevelAt(lineCurrent)) { |
678 | styler.SetLevel(lineCurrent, lev); |
679 | } |
680 | |
681 | visibleChars = 0; |
682 | lineCurrent++; |
683 | lineNext = lineCurrent + 1; |
684 | lineStartNext = styler.LineStart(lineNext); |
685 | levelCurrent = levelNext; |
686 | |
687 | if (atEOL && (currentPos == (styler.Length() - 1))) { |
688 | styler.SetLevel(lineCurrent, (levelCurrent | levelCurrent << 16) | SC_FOLDLEVELWHITEFLAG); |
689 | } |
690 | } |
691 | } |
692 | } |
693 | |
694 | bool LineContains(LexAccessor &styler, const char *word, const Sci_Position start, const Sci_Position end, |
695 | const int chAttr) { |
696 | bool found = false; |
697 | bool requireStyle = (chAttr > SCE_FSHARP_DEFAULT); |
698 | const Sci_Position limit = (end > 1) ? end : start; |
699 | for (Sci_Position i = start; i < styler.LineEnd(limit); i++) { |
700 | if (styler.Match(i, word)) { |
701 | found = requireStyle ? styler.StyleAt(i) == chAttr : true; |
702 | break; |
703 | } |
704 | } |
705 | return found; |
706 | } |
707 | |
708 | void FoldLexicalGroup(LexAccessor &styler, int &levelNext, const Sci_Position lineCurrent, const char *word, |
709 | const int chAttr) { |
710 | const Sci_Position linePrev = lineCurrent - 1; |
711 | const Sci_Position lineNext = lineCurrent + 1; |
712 | const Sci_Position lineStartPrev = styler.LineStart(linePrev); |
713 | const Sci_Position lineStartNext = styler.LineStart(lineNext); |
714 | const bool atFoldGroupStart = |
715 | (lineCurrent == 0 || !LineContains(styler, word, lineStartPrev, linePrev, chAttr)) && |
716 | LineContains(styler, word, lineStartNext, lineNext, chAttr); |
717 | const bool atFoldGroupEnd = |
718 | LineContains(styler, word, lineStartPrev, linePrev, chAttr) && |
719 | !LineContains(styler, word, lineStartNext, lineNext, chAttr); |
720 | |
721 | if (atFoldGroupStart) { |
722 | levelNext++; |
723 | } else if (atFoldGroupEnd && levelNext > SC_FOLDLEVELBASE) { |
724 | levelNext--; |
725 | } |
726 | } |
727 | } // namespace |
728 | |
729 | LexerModule lmFSharp(SCLEX_FSHARP, LexerFSharp::LexerFactoryFSharp, "fsharp" , fsharpWordLists); |
730 | |