You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
770 lines
25 KiB
770 lines
25 KiB
/** |
|
* @file LexFSharp.cxx |
|
* Lexer for F# 5.0 |
|
* Copyright (c) 2021 Robert Di Pardo <dipardo.r@gmail.com> |
|
* Parts of LexerFSharp::Lex were adapted from LexCaml.cxx by Robert Roessler ("RR"). |
|
* Parts of LexerFSharp::Fold were adapted from LexBash.cxx by Neil Hodgson and Kein-Hong Man. |
|
* The License.txt file describes the conditions under which this software may be distributed. |
|
*/ |
|
// clang-format off |
|
#include <cstdlib> |
|
#include <cassert> |
|
|
|
#include <string> |
|
#include <string_view> |
|
#include <map> |
|
#include <functional> |
|
|
|
#include "ILexer.h" |
|
#include "Scintilla.h" |
|
#include "SciLexer.h" |
|
|
|
#include "WordList.h" |
|
#include "LexAccessor.h" |
|
#include "StyleContext.h" |
|
#include "CharacterSet.h" |
|
#include "LexerModule.h" |
|
#include "OptionSet.h" |
|
#include "DefaultLexer.h" |
|
// clang-format on |
|
|
|
using namespace Scintilla; |
|
using namespace Lexilla; |
|
|
|
static const char *const lexerName = "fsharp"; |
|
static constexpr int WORDLIST_SIZE = 5; |
|
static const char *const fsharpWordLists[] = { |
|
"standard language keywords", |
|
"core functions, including those in the FSharp.Collections namespace", |
|
"built-in types, core namespaces, modules", |
|
"optional", |
|
"optional", |
|
nullptr, |
|
}; |
|
static constexpr int keywordClasses[] = { |
|
SCE_FSHARP_KEYWORD, SCE_FSHARP_KEYWORD2, SCE_FSHARP_KEYWORD3, SCE_FSHARP_KEYWORD4, SCE_FSHARP_KEYWORD5, |
|
}; |
|
|
|
namespace { |
|
|
|
struct OptionsFSharp { |
|
bool fold = true; |
|
bool foldCompact = true; |
|
bool foldComment = true; |
|
bool foldCommentStream = true; |
|
bool foldCommentMultiLine = true; |
|
bool foldPreprocessor = false; |
|
bool foldImports = true; |
|
}; |
|
|
|
struct OptionSetFSharp : public OptionSet<OptionsFSharp> { |
|
OptionSetFSharp() { |
|
DefineProperty("fold", &OptionsFSharp::fold); |
|
DefineProperty("fold.compact", &OptionsFSharp::foldCompact); |
|
DefineProperty("fold.comment", &OptionsFSharp::foldComment, |
|
"Setting this option to 0 disables comment folding in F# files."); |
|
|
|
DefineProperty("fold.fsharp.comment.stream", &OptionsFSharp::foldCommentStream, |
|
"Setting this option to 0 disables folding of ML-style comments in F# files when " |
|
"fold.comment=1."); |
|
|
|
DefineProperty("fold.fsharp.comment.multiline", &OptionsFSharp::foldCommentMultiLine, |
|
"Setting this option to 0 disables folding of grouped line comments in F# files when " |
|
"fold.comment=1."); |
|
|
|
DefineProperty("fold.fsharp.preprocessor", &OptionsFSharp::foldPreprocessor, |
|
"Setting this option to 1 enables folding of F# compiler directives."); |
|
|
|
DefineProperty("fold.fsharp.imports", &OptionsFSharp::foldImports, |
|
"Setting this option to 0 disables folding of F# import declarations."); |
|
|
|
DefineWordListSets(fsharpWordLists); |
|
} |
|
}; |
|
|
|
struct FSharpString { |
|
Sci_Position startPos = INVALID_POSITION; |
|
int startChar = '"', nextChar = '\0'; |
|
constexpr bool HasLength() const { |
|
return startPos > INVALID_POSITION; |
|
} |
|
constexpr bool CanInterpolate() const { |
|
return startChar == '$' || (startChar == '@' && nextChar == '$'); |
|
} |
|
constexpr bool IsVerbatim() const { |
|
return startChar == '@' || (startChar == '$' && nextChar == '@'); |
|
} |
|
}; |
|
|
|
class UnicodeChar { |
|
enum class Notation { none, asciiDec, asciiHex, utf16, utf32 }; |
|
Notation type = Notation::none; |
|
// single-byte Unicode char (000 - 255) |
|
int asciiDigits[3] = { 0 }; |
|
int maxDigit = '9'; |
|
int toEnd = 0; |
|
bool invalid = false; |
|
|
|
public: |
|
UnicodeChar() noexcept = default; |
|
explicit UnicodeChar(const int prefix) { |
|
if (IsADigit(prefix)) { |
|
*asciiDigits = prefix; |
|
if (*asciiDigits >= '0' && *asciiDigits <= '2') { |
|
type = Notation::asciiDec; |
|
// count first digit as "prefix" |
|
toEnd = 2; |
|
} |
|
} else if (prefix == 'x' || prefix == 'u' || prefix == 'U') { |
|
switch (prefix) { |
|
case 'x': |
|
type = Notation::asciiHex; |
|
toEnd = 2; |
|
break; |
|
case 'u': |
|
type = Notation::utf16; |
|
toEnd = 4; |
|
break; |
|
case 'U': |
|
type = Notation::utf32; |
|
toEnd = 8; |
|
break; |
|
} |
|
} |
|
} |
|
void Parse(const int ch) { |
|
invalid = false; |
|
switch (type) { |
|
case Notation::asciiDec: { |
|
maxDigit = (*asciiDigits < '2') ? '9' : (asciiDigits[1] <= '4') ? '9' : '5'; |
|
if (IsADigit(ch) && asciiDigits[1] <= maxDigit && ch <= maxDigit) { |
|
asciiDigits[1] = ch; |
|
toEnd--; |
|
} else { |
|
invalid = true; |
|
} |
|
break; |
|
} |
|
case Notation::asciiHex: |
|
case Notation::utf16: |
|
if (IsADigit(ch, 16)) { |
|
toEnd--; |
|
} else { |
|
invalid = true; |
|
} |
|
break; |
|
case Notation::utf32: |
|
if ((toEnd > 6 && ch == '0') || (toEnd <= 6 && IsADigit(ch, 16))) { |
|
toEnd--; |
|
} else { |
|
invalid = true; |
|
} |
|
break; |
|
case Notation::none: |
|
break; |
|
} |
|
} |
|
constexpr bool AtEnd() noexcept { |
|
return invalid || type == Notation::none || (type != Notation::none && toEnd < 0); |
|
} |
|
}; |
|
|
|
inline bool MatchStreamCommentStart(StyleContext &cxt) { |
|
// match (* ... *), but allow point-free usage of the `*` operator, |
|
// e.g. List.fold (*) 1 [ 1; 2; 3 ] |
|
return (cxt.Match('(', '*') && cxt.GetRelative(2) != ')'); |
|
} |
|
|
|
inline bool MatchStreamCommentEnd(const StyleContext &cxt) { |
|
return (cxt.ch == ')' && cxt.chPrev == '*'); |
|
} |
|
|
|
inline bool MatchLineComment(const StyleContext &cxt) { |
|
// style shebang lines as comments in F# scripts: |
|
// https://fsharp.org/specs/language-spec/4.1/FSharpSpec-4.1-latest.pdf#page=30&zoom=auto,-98,537 |
|
return cxt.Match('/', '/') || cxt.Match('#', '!'); |
|
} |
|
|
|
inline bool MatchLineNumberStart(StyleContext &cxt) { |
|
return cxt.atLineStart && (cxt.MatchIgnoreCase("#line") || |
|
(cxt.ch == '#' && (IsADigit(cxt.chNext) || IsADigit(cxt.GetRelative(2))))); |
|
} |
|
|
|
inline bool MatchPPDirectiveStart(const StyleContext &cxt) { |
|
return (cxt.atLineStart && cxt.ch == '#' && iswordstart(cxt.chNext)); |
|
} |
|
|
|
inline bool MatchTypeAttributeStart(const StyleContext &cxt) { |
|
return cxt.Match('[', '<'); |
|
} |
|
|
|
inline bool MatchTypeAttributeEnd(const StyleContext &cxt) { |
|
return (cxt.ch == ']' && cxt.chPrev == '>'); |
|
} |
|
|
|
inline bool MatchQuotedExpressionStart(const StyleContext &cxt) { |
|
return cxt.Match('<', '@'); |
|
} |
|
|
|
inline bool MatchQuotedExpressionEnd(const StyleContext &cxt) { |
|
return (cxt.ch == '>' && cxt.chPrev == '@'); |
|
} |
|
|
|
inline bool MatchStringStart(StyleContext &cxt) { |
|
return (cxt.ch == '"' || cxt.Match("@\"") || cxt.Match("$\"") || cxt.Match("@$\"") || cxt.Match("$@\"") || |
|
cxt.Match("``")); |
|
} |
|
|
|
inline bool FollowsEscapedBackslash(StyleContext &cxt) { |
|
int count = 0; |
|
for (Sci_Position offset = 1; cxt.GetRelative(-offset) == '\\'; offset++) |
|
count++; |
|
return count % 2 != 0; |
|
} |
|
|
|
inline bool MatchStringEnd(StyleContext &cxt, const FSharpString &fsStr) { |
|
return (fsStr.HasLength() && |
|
// end of quoted identifier? |
|
((cxt.ch == '`' && cxt.chPrev == '`') || |
|
// end of literal or interpolated triple-quoted string? |
|
((fsStr.startChar == '"' || (fsStr.CanInterpolate() && !(fsStr.IsVerbatim() || cxt.chPrev == '$'))) && |
|
cxt.MatchIgnoreCase("\"\"\"")) || |
|
// end of verbatim string? |
|
(fsStr.IsVerbatim() && |
|
// embedded quotes must be in pairs |
|
cxt.ch == '"' && cxt.chNext != '"' && |
|
(cxt.chPrev != '"' || |
|
// empty verbatim string? |
|
((cxt.GetRelative(-2) == '@' || cxt.GetRelative(-2) == '$') || |
|
// pair of quotes at end of string? |
|
(cxt.GetRelative(-2) == '"' && !(cxt.GetRelative(-3) == '@' || cxt.GetRelative(-3) == '$'))))))) || |
|
(!fsStr.HasLength() && cxt.ch == '"' && |
|
((cxt.chPrev != '\\' || (cxt.GetRelative(-2) == '\\' && !FollowsEscapedBackslash(cxt))) || |
|
// treat backslashes as char literals in verbatim strings |
|
(fsStr.IsVerbatim() && cxt.chPrev == '\\'))); |
|
} |
|
|
|
inline bool MatchCharacterStart(StyleContext &cxt) { |
|
// don't style generic type parameters: 'a, 'b, 'T, etc. |
|
return (cxt.ch == '\'' && !(cxt.chPrev == ':' || cxt.GetRelative(-2) == ':')); |
|
} |
|
|
|
inline bool CanEmbedQuotes(StyleContext &cxt) { |
|
// allow unescaped double quotes inside literal or interpolated triple-quoted strings, verbatim strings, |
|
// and quoted identifiers: |
|
// - https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/strings |
|
// - https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/interpolated-strings#syntax |
|
// - https://fsharp.org/specs/language-spec/4.1/FSharpSpec-4.1-latest.pdf#page=25&zoom=auto,-98,600 |
|
return cxt.Match("$\"\"\"") || cxt.Match("\"\"\"") || cxt.Match("@$\"\"\"") || cxt.Match("$@\"\"\"") || |
|
cxt.Match('@', '"') || cxt.Match('`', '`'); |
|
} |
|
|
|
inline bool IsLineEnd(StyleContext &cxt, const Sci_Position offset) { |
|
const int ch = cxt.GetRelative(offset, '\n'); |
|
return (ch == '\r' || ch == '\n'); |
|
} |
|
|
|
class LexerFSharp : public DefaultLexer { |
|
WordList keywords[WORDLIST_SIZE]; |
|
OptionsFSharp options; |
|
OptionSetFSharp optionSet; |
|
CharacterSet setOperators; |
|
CharacterSet setFormatSpecs; |
|
CharacterSet setDotNetFormatSpecs; |
|
CharacterSet setFormatFlags; |
|
CharacterSet numericMetaChars1; |
|
CharacterSet numericMetaChars2; |
|
std::map<int, int> numericPrefixes = { { 'b', 2 }, { 'o', 8 }, { 'x', 16 } }; |
|
|
|
public: |
|
explicit LexerFSharp() |
|
: DefaultLexer(lexerName, SCLEX_FSHARP), |
|
setOperators(CharacterSet::setNone, "~^'-+*/%=@|&<>()[]{};,:!?"), |
|
setFormatSpecs(CharacterSet::setNone, ".%aAbBcdeEfFgGiMoOstuxX0123456789"), |
|
setDotNetFormatSpecs(CharacterSet::setNone, "cCdDeEfFgGnNpPxX"), |
|
setFormatFlags(CharacterSet::setNone, ".-+0 "), |
|
numericMetaChars1(CharacterSet::setNone, "_uU"), |
|
numericMetaChars2(CharacterSet::setNone, "fFIlLmMnsy") { |
|
} |
|
LexerFSharp(const LexerFSharp &) = delete; |
|
LexerFSharp(LexerFSharp &&) = delete; |
|
LexerFSharp &operator=(const LexerFSharp &) = delete; |
|
LexerFSharp &operator=(LexerFSharp &&) = delete; |
|
static ILexer5 *LexerFactoryFSharp() { |
|
return new LexerFSharp(); |
|
} |
|
virtual ~LexerFSharp() { |
|
} |
|
void SCI_METHOD Release() noexcept override { |
|
delete this; |
|
} |
|
int SCI_METHOD Version() const noexcept override { |
|
return lvRelease5; |
|
} |
|
const char *SCI_METHOD GetName() noexcept override { |
|
return lexerName; |
|
} |
|
int SCI_METHOD GetIdentifier() noexcept override { |
|
return SCLEX_FSHARP; |
|
} |
|
int SCI_METHOD LineEndTypesSupported() noexcept override { |
|
return SC_LINE_END_TYPE_DEFAULT; |
|
} |
|
void *SCI_METHOD PrivateCall(int, void *) noexcept override { |
|
return nullptr; |
|
} |
|
const char *SCI_METHOD DescribeWordListSets() override { |
|
return optionSet.DescribeWordListSets(); |
|
} |
|
const char *SCI_METHOD PropertyNames() override { |
|
return optionSet.PropertyNames(); |
|
} |
|
int SCI_METHOD PropertyType(const char *name) override { |
|
return optionSet.PropertyType(name); |
|
} |
|
const char *SCI_METHOD DescribeProperty(const char *name) override { |
|
return optionSet.DescribeProperty(name); |
|
} |
|
const char *SCI_METHOD PropertyGet(const char *key) override { |
|
return optionSet.PropertyGet(key); |
|
} |
|
Sci_Position SCI_METHOD PropertySet(const char *key, const char *val) override { |
|
if (optionSet.PropertySet(&options, key, val)) { |
|
return 0; |
|
} |
|
return INVALID_POSITION; |
|
} |
|
Sci_Position SCI_METHOD WordListSet(int n, const char *wl) override; |
|
void SCI_METHOD Lex(Sci_PositionU start, Sci_Position length, int initStyle, IDocument *pAccess) override; |
|
void SCI_METHOD Fold(Sci_PositionU start, Sci_Position length, int initStyle,IDocument *pAccess) override; |
|
|
|
private: |
|
inline bool IsNumber(StyleContext &cxt, const int base = 10) { |
|
return IsADigit(cxt.ch, base) || (IsADigit(cxt.chPrev, base) && numericMetaChars1.Contains(cxt.ch)) || |
|
(IsADigit(cxt.GetRelative(-2), base) && numericMetaChars2.Contains(cxt.ch)); |
|
} |
|
|
|
inline bool IsFloat(StyleContext &cxt) { |
|
if (cxt.MatchIgnoreCase("e+") || cxt.MatchIgnoreCase("e-")) { |
|
cxt.Forward(); |
|
return true; |
|
} |
|
return ((cxt.chPrev == '.' && IsADigit(cxt.ch)) || |
|
(IsADigit(cxt.chPrev) && (cxt.ch == '.' || numericMetaChars2.Contains(cxt.ch)))); |
|
} |
|
}; |
|
|
|
Sci_Position SCI_METHOD LexerFSharp::WordListSet(int n, const char *wl) { |
|
WordList *wordListN = nullptr; |
|
Sci_Position firstModification = INVALID_POSITION; |
|
|
|
if (n < WORDLIST_SIZE) { |
|
wordListN = &keywords[n]; |
|
} |
|
if (wordListN && wordListN->Set(wl)) { |
|
firstModification = 0; |
|
} |
|
return firstModification; |
|
} |
|
|
|
void SCI_METHOD LexerFSharp::Lex(Sci_PositionU start, Sci_Position length, int initStyle, IDocument *pAccess) { |
|
LexAccessor styler(pAccess); |
|
StyleContext sc(start, static_cast<Sci_PositionU>(length), initStyle, styler); |
|
Sci_Position lineCurrent = styler.GetLine(static_cast<Sci_Position>(start)); |
|
Sci_PositionU cursor = 0; |
|
UnicodeChar uniCh = UnicodeChar(); |
|
FSharpString fsStr = FSharpString(); |
|
constexpr Sci_Position MAX_WORD_LEN = 64; |
|
constexpr int SPACE = ' '; |
|
int currentBase = 10; |
|
int levelNesting = (lineCurrent >= 1) ? styler.GetLineState(lineCurrent - 1) : 0; |
|
bool isInterpolated = false; |
|
|
|
while (sc.More()) { |
|
Sci_PositionU colorSpan = sc.currentPos - 1; |
|
int state = -1; |
|
bool advance = true; |
|
|
|
switch (sc.state & 0xff) { |
|
case SCE_FSHARP_DEFAULT: |
|
cursor = sc.currentPos; |
|
|
|
if (MatchLineNumberStart(sc)) { |
|
state = SCE_FSHARP_LINENUM; |
|
} else if (MatchPPDirectiveStart(sc)) { |
|
state = SCE_FSHARP_PREPROCESSOR; |
|
} else if (MatchLineComment(sc)) { |
|
state = SCE_FSHARP_COMMENTLINE; |
|
sc.Forward(); |
|
sc.ch = SPACE; |
|
} else if (MatchStreamCommentStart(sc)) { |
|
state = SCE_FSHARP_COMMENT; |
|
sc.Forward(); |
|
sc.ch = SPACE; |
|
} else if (MatchTypeAttributeStart(sc)) { |
|
state = SCE_FSHARP_ATTRIBUTE; |
|
sc.Forward(); |
|
} else if (MatchQuotedExpressionStart(sc)) { |
|
state = SCE_FSHARP_QUOTATION; |
|
sc.Forward(); |
|
} else if (MatchCharacterStart(sc)) { |
|
state = SCE_FSHARP_CHARACTER; |
|
} else if (MatchStringStart(sc)) { |
|
fsStr.startChar = sc.ch; |
|
fsStr.nextChar = sc.chNext; |
|
fsStr.startPos = INVALID_POSITION; |
|
if (CanEmbedQuotes(sc)) { |
|
// double quotes after this position should be non-terminating |
|
fsStr.startPos = static_cast<Sci_Position>(sc.currentPos - cursor); |
|
} |
|
if (sc.ch == '`') { |
|
state = SCE_FSHARP_QUOT_IDENTIFIER; |
|
} else if (fsStr.IsVerbatim()) { |
|
state = SCE_FSHARP_VERBATIM; |
|
} else { |
|
state = SCE_FSHARP_STRING; |
|
} |
|
} else if (IsADigit(sc.ch, currentBase) || |
|
((sc.ch == '+' || sc.ch == '-') && IsADigit(sc.chNext))) { |
|
state = SCE_FSHARP_NUMBER; |
|
} else if (setOperators.Contains(sc.ch) && |
|
// don't use operator style in async keywords (e.g. `return!`) |
|
!(sc.ch == '!' && iswordstart(sc.chPrev)) && |
|
// don't use operator style in member access, array/string indexing |
|
!(sc.ch == '.' && (sc.chPrev == '\"' || iswordstart(sc.chPrev)) && |
|
(iswordstart(sc.chNext) || sc.chNext == '['))) { |
|
state = SCE_FSHARP_OPERATOR; |
|
} else if (iswordstart(sc.ch)) { |
|
state = SCE_FSHARP_IDENTIFIER; |
|
} else { |
|
state = SCE_FSHARP_DEFAULT; |
|
} |
|
break; |
|
case SCE_FSHARP_LINENUM: |
|
case SCE_FSHARP_PREPROCESSOR: |
|
if (sc.MatchLineEnd()) { |
|
state = SCE_FSHARP_DEFAULT; |
|
} |
|
break; |
|
case SCE_FSHARP_COMMENTLINE: |
|
if (sc.atLineStart) { |
|
state = SCE_FSHARP_DEFAULT; |
|
advance = false; |
|
} |
|
break; |
|
case SCE_FSHARP_COMMENT: |
|
if (MatchStreamCommentStart(sc)) { |
|
sc.Forward(); |
|
sc.ch = SPACE; |
|
levelNesting++; |
|
} else if (MatchStreamCommentEnd(sc)) { |
|
if (levelNesting > 0) |
|
levelNesting--; |
|
else { |
|
state = SCE_FSHARP_DEFAULT; |
|
colorSpan++; |
|
} |
|
} |
|
break; |
|
case SCE_FSHARP_ATTRIBUTE: |
|
case SCE_FSHARP_QUOTATION: |
|
if (MatchTypeAttributeEnd(sc) || MatchQuotedExpressionEnd(sc)) { |
|
state = SCE_FSHARP_DEFAULT; |
|
colorSpan++; |
|
} |
|
break; |
|
case SCE_FSHARP_CHARACTER: |
|
if (sc.chPrev == '\\' && sc.GetRelative(-2) != '\\') { |
|
uniCh = UnicodeChar(sc.ch); |
|
} else if (sc.ch == '\'' && |
|
((sc.chPrev == ' ' && sc.GetRelative(-2) == '\'') || sc.chPrev != '\\' || |
|
(sc.chPrev == '\\' && sc.GetRelative(-2) == '\\'))) { |
|
// byte literal? |
|
if (sc.Match('\'', 'B')) { |
|
sc.Forward(); |
|
colorSpan++; |
|
} |
|
if (!sc.atLineEnd) { |
|
colorSpan++; |
|
} else { |
|
sc.ChangeState(SCE_FSHARP_IDENTIFIER); |
|
} |
|
state = SCE_FSHARP_DEFAULT; |
|
} else { |
|
uniCh.Parse(sc.ch); |
|
if (uniCh.AtEnd() && (sc.currentPos - cursor) >= 2) { |
|
// terminate now, since we left the char behind |
|
sc.ChangeState(SCE_FSHARP_IDENTIFIER); |
|
advance = false; |
|
} |
|
} |
|
break; |
|
case SCE_FSHARP_STRING: |
|
case SCE_FSHARP_VERBATIM: |
|
case SCE_FSHARP_QUOT_IDENTIFIER: |
|
if (MatchStringEnd(sc, fsStr)) { |
|
const Sci_Position strLen = static_cast<Sci_Position>(sc.currentPos - cursor); |
|
// backtrack to start of string |
|
for (Sci_Position i = -strLen; i < 0; i++) { |
|
const int startQuote = sc.GetRelative(i); |
|
if (startQuote == '\"' || (startQuote == '`' && sc.GetRelative(i - 1) == '`')) { |
|
// byte array? |
|
if (sc.Match('\"', 'B')) { |
|
sc.Forward(); |
|
colorSpan++; |
|
} |
|
if (!sc.atLineEnd) { |
|
colorSpan++; |
|
} else { |
|
sc.ChangeState(SCE_FSHARP_IDENTIFIER); |
|
} |
|
state = SCE_FSHARP_DEFAULT; |
|
break; |
|
} |
|
} |
|
} else if (sc.ch == '%' && |
|
!(fsStr.startChar == '`' || sc.MatchIgnoreCase("% ") || sc.MatchIgnoreCase("% \"")) && |
|
(setFormatSpecs.Contains(sc.chNext) || setFormatFlags.Contains(sc.chNext))) { |
|
if (fsStr.CanInterpolate() && sc.chNext != '%') { |
|
for (Sci_Position i = 2; i < length && !IsLineEnd(sc, i); i++) { |
|
if (sc.GetRelative(i) == '{') { |
|
state = setFormatSpecs.Contains(sc.GetRelative(i - 1)) |
|
? SCE_FSHARP_FORMAT_SPEC |
|
: state; |
|
break; |
|
} |
|
} |
|
} else { |
|
state = SCE_FSHARP_FORMAT_SPEC; |
|
} |
|
} else if (isInterpolated) { |
|
if (sc.ch == ',') { |
|
// .NET alignment specifier? |
|
state = (sc.chNext == '+' || sc.chNext == '-' || IsADigit(sc.chNext)) |
|
? SCE_FSHARP_FORMAT_SPEC |
|
: state; |
|
} else if (sc.ch == ':') { |
|
// .NET format specifier? |
|
state = setDotNetFormatSpecs.Contains(sc.chNext) |
|
? SCE_FSHARP_FORMAT_SPEC |
|
: state; |
|
} else if (sc.chNext == '}') { |
|
isInterpolated = false; |
|
sc.Forward(); |
|
state = fsStr.IsVerbatim() ? SCE_FSHARP_VERBATIM : SCE_FSHARP_STRING; |
|
} |
|
} else if (fsStr.CanInterpolate() && sc.ch == '{') { |
|
isInterpolated = true; |
|
} |
|
break; |
|
case SCE_FSHARP_IDENTIFIER: |
|
if (!(iswordstart(sc.ch) || sc.ch == '\'')) { |
|
const Sci_Position wordLen = static_cast<Sci_Position>(sc.currentPos - cursor); |
|
if (wordLen < MAX_WORD_LEN) { |
|
// wordLength is believable as keyword, [re-]construct token - RR |
|
char token[MAX_WORD_LEN] = { 0 }; |
|
for (Sci_Position i = -wordLen; i < 0; i++) { |
|
token[wordLen + i] = static_cast<char>(sc.GetRelative(i)); |
|
} |
|
token[wordLen] = '\0'; |
|
// a snake_case_identifier can never be a keyword |
|
if (!(sc.ch == '_' || sc.GetRelative(-wordLen - 1) == '_')) { |
|
for (int i = 0; i < WORDLIST_SIZE; i++) { |
|
if (keywords[i].InList(token)) { |
|
sc.ChangeState(keywordClasses[i]); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
state = SCE_FSHARP_DEFAULT; |
|
advance = false; |
|
} |
|
break; |
|
case SCE_FSHARP_OPERATOR: |
|
// special-case "()" and "[]" tokens as KEYWORDS - RR |
|
if ((sc.ch == ')' && sc.chPrev == '(') || (sc.ch == ']' && sc.chPrev == '[')) { |
|
sc.ChangeState(SCE_FSHARP_KEYWORD); |
|
colorSpan++; |
|
} else { |
|
advance = false; |
|
} |
|
state = SCE_FSHARP_DEFAULT; |
|
break; |
|
case SCE_FSHARP_NUMBER: |
|
if ((setOperators.Contains(sc.chPrev) || IsASpaceOrTab(sc.chPrev)) && sc.ch == '0') { |
|
if (numericPrefixes.find(sc.chNext) != numericPrefixes.end()) { |
|
currentBase = numericPrefixes[sc.chNext]; |
|
sc.Forward(2); |
|
} |
|
} else if ((setOperators.Contains(sc.GetRelative(-2)) || IsASpaceOrTab(sc.GetRelative(-2))) && |
|
sc.chPrev == '0') { |
|
if (numericPrefixes.find(sc.ch) != numericPrefixes.end()) { |
|
currentBase = numericPrefixes[sc.ch]; |
|
sc.Forward(); |
|
} |
|
} |
|
state = (IsNumber(sc, currentBase) || IsFloat(sc)) |
|
? SCE_FSHARP_NUMBER |
|
// change style even when operators aren't spaced |
|
: setOperators.Contains(sc.ch) ? SCE_FSHARP_OPERATOR : SCE_FSHARP_DEFAULT; |
|
currentBase = (state == SCE_FSHARP_NUMBER) ? currentBase : 10; |
|
break; |
|
case SCE_FSHARP_FORMAT_SPEC: |
|
if (!(isInterpolated && IsADigit(sc.chNext)) && |
|
(!setFormatSpecs.Contains(sc.chNext) || |
|
!(setFormatFlags.Contains(sc.ch) || IsADigit(sc.ch)) || |
|
(setFormatFlags.Contains(sc.ch) && sc.ch == sc.chNext))) { |
|
colorSpan++; |
|
state = fsStr.IsVerbatim() ? SCE_FSHARP_VERBATIM : SCE_FSHARP_STRING; |
|
} |
|
break; |
|
} |
|
|
|
if (sc.MatchLineEnd()) { |
|
styler.SetLineState(lineCurrent++, (sc.state == SCE_FSHARP_COMMENT) ? levelNesting : 0); |
|
advance = true; |
|
} |
|
|
|
if (state >= SCE_FSHARP_DEFAULT) { |
|
styler.ColourTo(colorSpan, sc.state); |
|
sc.ChangeState(state); |
|
} |
|
|
|
if (advance) { |
|
sc.Forward(); |
|
} |
|
} |
|
|
|
sc.Complete(); |
|
} |
|
|
|
bool LineContains(LexAccessor &styler, const char *word, const Sci_Position start, |
|
const int chAttr = SCE_FSHARP_DEFAULT); |
|
|
|
void FoldLexicalGroup(LexAccessor &styler, int &levelNext, const Sci_Position lineCurrent, const char *word, |
|
const int chAttr); |
|
|
|
void SCI_METHOD LexerFSharp::Fold(Sci_PositionU start, Sci_Position length, int initStyle, IDocument *pAccess) { |
|
if (!options.fold) { |
|
return; |
|
} |
|
|
|
LexAccessor styler(pAccess); |
|
Sci_Position startPos = static_cast<Sci_Position>(start); |
|
const Sci_PositionU endPos = start + length; |
|
Sci_Position lineCurrent = styler.GetLine(startPos); |
|
if (lineCurrent > 0) { |
|
lineCurrent--; |
|
startPos = styler.LineStart(lineCurrent); |
|
initStyle = (startPos > 0) ? styler.StyleAt(startPos - 1) : SCE_FSHARP_DEFAULT; |
|
} |
|
Sci_Position lineNext = lineCurrent + 1; |
|
Sci_Position lineStartNext = styler.LineStart(lineNext); |
|
int style = initStyle; |
|
int styleNext = styler.StyleAt(startPos); |
|
char chNext = styler[startPos]; |
|
int levelCurrent = styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK; |
|
int levelNext = levelCurrent; |
|
int visibleChars = 0; |
|
|
|
for (Sci_PositionU i = static_cast<Sci_PositionU>(startPos); i < endPos; i++) { |
|
const Sci_Position currentPos = static_cast<Sci_Position>(i); |
|
const bool atEOL = (currentPos == (lineStartNext - 1)); |
|
const bool atLineOrDocEnd = (atEOL || (i == (endPos - 1))); |
|
const int stylePrev = style; |
|
const char ch = chNext; |
|
const bool inLineComment = (stylePrev == SCE_FSHARP_COMMENTLINE); |
|
style = styleNext; |
|
styleNext = styler.StyleAt(currentPos + 1); |
|
chNext = styler.SafeGetCharAt(currentPos + 1); |
|
|
|
if (options.foldComment) { |
|
if (options.foldCommentMultiLine && inLineComment && atEOL) { |
|
FoldLexicalGroup(styler, levelNext, lineCurrent, "//", SCE_FSHARP_COMMENTLINE); |
|
} |
|
|
|
if (options.foldCommentStream && style == SCE_FSHARP_COMMENT && !inLineComment) { |
|
if (stylePrev != SCE_FSHARP_COMMENT || |
|
(styler.Match(currentPos, "(*") && |
|
!LineContains(styler, "*)", currentPos + 2, SCE_FSHARP_COMMENT))) { |
|
levelNext++; |
|
} else if ((styleNext != SCE_FSHARP_COMMENT || |
|
((styler.Match(currentPos, "*)") && |
|
!LineContains(styler, "(*", styler.LineStart(lineCurrent), SCE_FSHARP_COMMENT)) && |
|
styler.GetLineState(lineCurrent - 1) > 0)) && |
|
!atEOL) { |
|
levelNext--; |
|
} |
|
} |
|
} |
|
|
|
if (options.foldPreprocessor && style == SCE_FSHARP_PREPROCESSOR) { |
|
if (styler.Match(currentPos, "#if")) { |
|
levelNext++; |
|
} else if (styler.Match(currentPos, "#endif")) { |
|
levelNext--; |
|
} |
|
} |
|
|
|
if (options.foldImports && styler.Match(currentPos, "open ") && styleNext == SCE_FSHARP_KEYWORD) { |
|
FoldLexicalGroup(styler, levelNext, lineCurrent, "open ", SCE_FSHARP_KEYWORD); |
|
} |
|
|
|
if (atLineOrDocEnd) { |
|
int levelUse = levelCurrent; |
|
int lev = levelUse | levelNext << 16; |
|
|
|
if (visibleChars == 0 && options.foldCompact) { |
|
lev |= SC_FOLDLEVELWHITEFLAG; |
|
} |
|
if ((levelUse < levelNext) && (visibleChars > 0)) { |
|
lev |= SC_FOLDLEVELHEADERFLAG; |
|
} |
|
if (lev != styler.LevelAt(lineCurrent)) { |
|
styler.SetLevel(lineCurrent, lev); |
|
} |
|
|
|
visibleChars = 0; |
|
lineCurrent++; |
|
lineNext = lineCurrent + 1; |
|
lineStartNext = styler.LineStart(lineNext); |
|
levelCurrent = levelNext; |
|
} |
|
|
|
if (!IsASpace(ch)) { |
|
visibleChars++; |
|
} |
|
} |
|
const int flagsNext = styler.LevelAt(lineCurrent) & ~SC_FOLDLEVELNUMBERMASK; |
|
styler.SetLevel(lineCurrent, levelCurrent | flagsNext); |
|
} |
|
|
|
bool LineContains(LexAccessor &styler, const char *word, const Sci_Position start, const int chAttr) { |
|
bool found = false; |
|
bool requireStyle = (chAttr > SCE_FSHARP_DEFAULT); |
|
for (Sci_Position i = start; i < styler.LineStart(styler.GetLine(start) + 1) - 1; i++) { |
|
if (styler.Match(i, word)) { |
|
found = requireStyle ? styler.StyleAt(i) == chAttr : true; |
|
break; |
|
} |
|
} |
|
return found; |
|
} |
|
|
|
void FoldLexicalGroup(LexAccessor &styler, int &levelNext, const Sci_Position lineCurrent, const char *word, |
|
const int chAttr) { |
|
const Sci_Position linePrev = styler.LineStart(lineCurrent - 1); |
|
const Sci_Position lineNext = styler.LineStart(lineCurrent + 1); |
|
const bool follows = (lineCurrent > 0) && LineContains(styler, word, linePrev, chAttr); |
|
const bool isFollowed = LineContains(styler, word, lineNext, chAttr); |
|
|
|
if (isFollowed && !follows) { |
|
levelNext++; |
|
} else if (!isFollowed && follows && levelNext > SC_FOLDLEVELBASE) { |
|
levelNext--; |
|
} |
|
} |
|
} // namespace |
|
|
|
extern const LexerModule lmFSharp(SCLEX_FSHARP, LexerFSharp::LexerFactoryFSharp, "fsharp", fsharpWordLists);
|
|
|