lexilla/lexers/LexFSharp.cxx
2021-04-16 21:50:48 +10:00

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);