mirror of
https://github.com/ScintillaOrg/lexilla.git
synced 2025-05-09 03:11:32 +08:00
717 lines
22 KiB
C++
717 lines
22 KiB
C++
/**
|
|
* @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 LexCPP.cxx by Neil Hodgson and Udo Lechner.
|
|
* The License.txt file describes the conditions under which this software may be distributed.
|
|
*/
|
|
// clang-format off
|
|
#include <cstdlib>
|
|
#include <cassert>
|
|
|
|
#include <string>
|
|
#include <map>
|
|
|
|
#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;
|
|
|
|
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;
|
|
bool foldCompact;
|
|
bool foldComment;
|
|
bool foldCommentStream;
|
|
bool foldCommentMultiLine;
|
|
bool foldPreprocessor;
|
|
bool foldImports;
|
|
OptionsFSharp() {
|
|
fold = true;
|
|
foldCompact = true;
|
|
foldComment = true;
|
|
foldCommentStream = true;
|
|
foldCommentMultiLine = true;
|
|
foldPreprocessor = false;
|
|
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);
|
|
}
|
|
};
|
|
|
|
const CharacterSet setOperators = CharacterSet(CharacterSet::setNone, "~^'-+*/%=@|&<>()[]{};,:!?");
|
|
const CharacterSet setClosingTokens = CharacterSet(CharacterSet::setNone, ")}]");
|
|
const CharacterSet numericMetaChars1 = CharacterSet(CharacterSet::setNone, "_IbeEflmnosuxy");
|
|
const CharacterSet numericMetaChars2 = CharacterSet(CharacterSet::setNone,"lnsy");
|
|
std::map<int, int> numericPrefixes = { { 'b', 2 }, { 'o', 8 }, { 'x', 16 } };
|
|
constexpr Sci_Position ZERO_LENGTH = -1;
|
|
|
|
struct FSharpString {
|
|
Sci_Position startPos;
|
|
int startChar;
|
|
FSharpString() {
|
|
startPos = ZERO_LENGTH;
|
|
startChar = '"';
|
|
}
|
|
constexpr bool HasLength() const {
|
|
return startPos > ZERO_LENGTH;
|
|
}
|
|
};
|
|
|
|
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(const StyleContext &cxt) {
|
|
return (cxt.ch == '"' || cxt.Match('@', '"') || cxt.Match('$', '"') || cxt.Match('`', '`'));
|
|
}
|
|
|
|
inline bool MatchStringEnd(StyleContext &cxt, const FSharpString &fsStr) {
|
|
return (fsStr.HasLength() &&
|
|
// end of quoted identifier?
|
|
((cxt.ch == '`' && cxt.chPrev == '`') ||
|
|
// end of triple-quoted-string?
|
|
(fsStr.startChar == '"' && cxt.MatchIgnoreCase("\"\"\"")) ||
|
|
// end of verbatim string?
|
|
(fsStr.startChar == '@' &&
|
|
// embedded quotes must be in pairs
|
|
cxt.ch == '"' && cxt.chNext != '"' &&
|
|
(cxt.chPrev != '"' || (cxt.chPrev == '"' &&
|
|
// empty verbatim string?
|
|
(cxt.GetRelative(-2) == '@' ||
|
|
// pair of quotes at end of string?
|
|
(cxt.GetRelative(-2) == '"' && cxt.GetRelative(-3) != '@'))))))) ||
|
|
(!fsStr.HasLength() && cxt.ch == '"' &&
|
|
(cxt.chPrev != '\\' ||
|
|
// treat backslashes as char literals in verbatim strings
|
|
(fsStr.startChar == '@' && 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 verbatim and triple-quoted strings, and quoted identifiers:
|
|
// - https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/strings
|
|
// - https://fsharp.org/specs/language-spec/4.1/FSharpSpec-4.1-latest.pdf#page=25&zoom=auto,-98,600
|
|
return cxt.MatchIgnoreCase("\"\"\"") || cxt.Match('@', '"') || cxt.Match('`', '`');
|
|
}
|
|
|
|
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(const StyleContext &cxt) {
|
|
return (cxt.ch == '.' && IsADigit(cxt.chPrev)) ||
|
|
((cxt.ch == '+' || cxt.ch == '-' ) && IsADigit(cxt.chNext));
|
|
}
|
|
|
|
class LexerFSharp : public DefaultLexer {
|
|
WordList keywords[WORDLIST_SIZE];
|
|
OptionsFSharp options;
|
|
OptionSetFSharp optionSet;
|
|
|
|
public:
|
|
explicit LexerFSharp() : DefaultLexer(lexerName, SCLEX_FSHARP) {
|
|
}
|
|
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 ZERO_LENGTH;
|
|
}
|
|
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;
|
|
};
|
|
|
|
Sci_Position SCI_METHOD LexerFSharp::WordListSet(int n, const char *wl) {
|
|
WordList *wordListN = nullptr;
|
|
Sci_Position firstModification = ZERO_LENGTH;
|
|
|
|
if (n < WORDLIST_SIZE) {
|
|
wordListN = &keywords[n];
|
|
}
|
|
if (wordListN) {
|
|
WordList wlNew;
|
|
wlNew.Set(wl);
|
|
|
|
if (*wordListN != wlNew) {
|
|
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_PositionU cursor = 0;
|
|
UnicodeChar uniCh = UnicodeChar();
|
|
FSharpString fsStr = FSharpString();
|
|
constexpr Sci_Position MAX_WORD_LEN = 64;
|
|
constexpr int SPACE = ' ';
|
|
int currentBase = 10;
|
|
|
|
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.startPos = ZERO_LENGTH;
|
|
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 (sc.ch == '@') {
|
|
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;
|
|
if (sc.ch == '0') {
|
|
const int prefix = sc.chNext;
|
|
if (numericPrefixes.find(prefix) != numericPrefixes.end()) {
|
|
currentBase = numericPrefixes[prefix];
|
|
}
|
|
}
|
|
} 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:
|
|
case SCE_FSHARP_COMMENTLINE:
|
|
// TestLexers.cxx will warn about splitting styles across CRLF line endings
|
|
// without the second condition
|
|
if (sc.atLineEnd || sc.ch == '\r') {
|
|
state = SCE_FSHARP_DEFAULT;
|
|
advance = false;
|
|
}
|
|
break;
|
|
case SCE_FSHARP_COMMENT:
|
|
case SCE_FSHARP_ATTRIBUTE:
|
|
case SCE_FSHARP_QUOTATION:
|
|
if (MatchStreamCommentEnd(sc) || 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;
|
|
}
|
|
}
|
|
}
|
|
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 (setClosingTokens.Contains(sc.ch) &&
|
|
((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:
|
|
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;
|
|
}
|
|
|
|
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 Sci_Position end = 1);
|
|
bool MatchPPDirectiveBranch(LexAccessor &styler, const Sci_Position line);
|
|
|
|
void SCI_METHOD LexerFSharp::Fold(Sci_PositionU start, Sci_Position length, int initStyle, IDocument *pAccess) {
|
|
if (!options.fold) {
|
|
return;
|
|
}
|
|
|
|
LexAccessor styler(pAccess);
|
|
const Sci_Position startPos = static_cast<Sci_Position>(start);
|
|
const Sci_PositionU endPos = start + length;
|
|
Sci_Position lineCurrent = styler.GetLine(startPos);
|
|
Sci_Position lineNext = lineCurrent + 1;
|
|
Sci_Position lineStartNext = styler.LineStart(lineNext);
|
|
Sci_Position commentLinesInFold = ZERO_LENGTH;
|
|
Sci_Position importsInFold = ZERO_LENGTH;
|
|
int style = initStyle;
|
|
int styleNext = styler.StyleAt(startPos);
|
|
char chNext = styler[startPos];
|
|
int levelNext;
|
|
int levelCurrent = SC_FOLDLEVELBASE;
|
|
int visibleChars = 0;
|
|
|
|
if (lineCurrent > 0) {
|
|
levelCurrent = styler.LevelAt(lineCurrent - 1) >> 0x10;
|
|
}
|
|
|
|
levelNext = levelCurrent;
|
|
|
|
for (Sci_PositionU i = start; i < endPos; i++) {
|
|
const Sci_Position currentPos = static_cast<Sci_Position>(i);
|
|
const bool atEOL = currentPos == (lineStartNext - 1);
|
|
const int stylePrev = style;
|
|
const char ch = chNext;
|
|
const bool inLineComment = (style == SCE_FSHARP_COMMENTLINE);
|
|
style = styleNext;
|
|
styleNext = styler.StyleAt(currentPos + 1);
|
|
chNext = styler.SafeGetCharAt(currentPos + 1);
|
|
|
|
if (options.foldComment) {
|
|
if (options.foldCommentMultiLine && inLineComment &&
|
|
(lineCurrent > 0 || styler.StyleAt(lineStartNext) == SCE_FSHARP_COMMENTLINE)) {
|
|
const bool haveCommentList = LineContains(styler, "//", lineStartNext, lineNext);
|
|
if (haveCommentList) {
|
|
// begin fold region
|
|
if (commentLinesInFold == ZERO_LENGTH) {
|
|
levelNext++;
|
|
}
|
|
// start line count at 0
|
|
commentLinesInFold++;
|
|
} else {
|
|
Sci_Position lineFold = lineCurrent;
|
|
while (lineFold > 0) {
|
|
commentLinesInFold--;
|
|
lineFold--;
|
|
if (!LineContains(styler, "//", lineFold)) {
|
|
break;
|
|
}
|
|
}
|
|
// line count should be reduced to 0, and no less
|
|
if (commentLinesInFold > ZERO_LENGTH) {
|
|
levelNext--;
|
|
}
|
|
commentLinesInFold = ZERO_LENGTH;
|
|
}
|
|
}
|
|
|
|
if (options.foldCommentStream && style == SCE_FSHARP_COMMENT && !inLineComment) {
|
|
if (stylePrev != SCE_FSHARP_COMMENT) {
|
|
levelNext++;
|
|
} else if (styleNext != SCE_FSHARP_COMMENT && !atEOL) {
|
|
levelNext--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (options.foldPreprocessor && style == SCE_FSHARP_PREPROCESSOR) {
|
|
if (styler.Match(currentPos, "#if")) {
|
|
levelNext++;
|
|
} else if (styler.Match(currentPos, "#endif")) {
|
|
levelNext--;
|
|
// compensate for #else branches and import lists
|
|
Sci_Position lineFold = lineCurrent;
|
|
while (lineFold > 0) {
|
|
lineFold--;
|
|
if (MatchPPDirectiveBranch(styler, lineFold)) {
|
|
levelNext--;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (options.foldImports && style == SCE_FSHARP_KEYWORD && LineContains(styler, "open", lineCurrent)) {
|
|
const bool haveImportList = LineContains(styler, "open", lineStartNext, lineNext);
|
|
if (haveImportList) {
|
|
if (importsInFold == ZERO_LENGTH) {
|
|
levelNext++;
|
|
}
|
|
importsInFold++;
|
|
} else {
|
|
Sci_Position lineFold = lineCurrent;
|
|
while (lineFold > 0) {
|
|
importsInFold--;
|
|
lineFold--;
|
|
if (!LineContains(styler, "open", lineFold)) {
|
|
break;
|
|
}
|
|
}
|
|
if (importsInFold > ZERO_LENGTH) {
|
|
levelNext--;
|
|
}
|
|
importsInFold = ZERO_LENGTH;
|
|
}
|
|
}
|
|
|
|
if (!IsASpace(ch)) {
|
|
visibleChars++;
|
|
}
|
|
|
|
if (atEOL || (i == (endPos - 1))) {
|
|
int levelUse = levelCurrent;
|
|
int lev = levelUse | levelNext << 16;
|
|
|
|
if (visibleChars == 0 && options.foldCompact) {
|
|
lev |= SC_FOLDLEVELWHITEFLAG;
|
|
}
|
|
if (levelUse < levelNext) {
|
|
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 (atEOL && (currentPos == (styler.Length() - 1))) {
|
|
styler.SetLevel(lineCurrent, (levelCurrent | levelCurrent << 16) | SC_FOLDLEVELWHITEFLAG);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool LineContains(LexAccessor &styler, const char *word, const Sci_Position start, const Sci_Position end) {
|
|
bool found = false;
|
|
const Sci_Position limit = (end > 1) ? end : start;
|
|
for (Sci_Position i = start; i < styler.LineEnd(limit); i++) {
|
|
if (styler.Match(i, word)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
bool MatchPPDirectiveBranch(LexAccessor &styler, const Sci_Position line) {
|
|
const Sci_Position linePrev = line - 1;
|
|
const Sci_Position lineStart = styler.LineStart(line);
|
|
const Sci_Position lineStartPrev = styler.LineStart(linePrev);
|
|
return (styler.StyleAt(lineStart) == SCE_FSHARP_PREPROCESSOR && !styler.Match(lineStart, "#if")) ||
|
|
(LineContains(styler, "open", lineStartPrev, linePrev) && LineContains(styler, "open", lineStart, line));
|
|
}
|
|
} // namespace
|
|
|
|
LexerModule lmFSharp(SCLEX_FSHARP, LexerFSharp::LexerFactoryFSharp, lexerName, fsharpWordLists);
|