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.
1119 lines
36 KiB
1119 lines
36 KiB
/****************************************************************** |
|
* LexHaskell.cxx |
|
* |
|
* A haskell lexer for the scintilla code control. |
|
* Some stuff "lended" from LexPython.cxx and LexCPP.cxx. |
|
* External lexer stuff inspired from the caml external lexer. |
|
* Folder copied from Python's. |
|
* |
|
* Written by Tobias Engvall - tumm at dtek dot chalmers dot se |
|
* |
|
* Several bug fixes by Krasimir Angelov - kr.angelov at gmail.com |
|
* |
|
* Improved by kudah <kudahkukarek@gmail.com> |
|
* |
|
* TODO: |
|
* * A proper lexical folder to fold group declarations, comments, pragmas, |
|
* #ifdefs, explicit layout, lists, tuples, quasi-quotes, splces, etc, etc, |
|
* etc. |
|
* |
|
*****************************************************************/ |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <stdio.h> |
|
#include <stdarg.h> |
|
#include <assert.h> |
|
#include <ctype.h> |
|
|
|
#include <string> |
|
#include <string_view> |
|
#include <vector> |
|
#include <map> |
|
#include <functional> |
|
|
|
#include "ILexer.h" |
|
#include "Scintilla.h" |
|
#include "SciLexer.h" |
|
|
|
#include "PropSetSimple.h" |
|
#include "WordList.h" |
|
#include "LexAccessor.h" |
|
#include "Accessor.h" |
|
#include "StyleContext.h" |
|
#include "CharacterSet.h" |
|
#include "CharacterCategory.h" |
|
#include "LexerModule.h" |
|
#include "OptionSet.h" |
|
#include "DefaultLexer.h" |
|
|
|
using namespace Scintilla; |
|
using namespace Lexilla; |
|
|
|
// See https://github.com/ghc/ghc/blob/master/compiler/parser/Lexer.x#L1682 |
|
// Note, letter modifiers are prohibited. |
|
|
|
static int u_iswupper (int ch) { |
|
CharacterCategory c = CategoriseCharacter(ch); |
|
return c == ccLu || c == ccLt; |
|
} |
|
|
|
static int u_iswalpha (int ch) { |
|
CharacterCategory c = CategoriseCharacter(ch); |
|
return c == ccLl || c == ccLu || c == ccLt || c == ccLo; |
|
} |
|
|
|
static int u_iswalnum (int ch) { |
|
CharacterCategory c = CategoriseCharacter(ch); |
|
return c == ccLl || c == ccLu || c == ccLt || c == ccLo |
|
|| c == ccNd || c == ccNo; |
|
} |
|
|
|
static int u_IsHaskellSymbol(int ch) { |
|
CharacterCategory c = CategoriseCharacter(ch); |
|
return c == ccPc || c == ccPd || c == ccPo |
|
|| c == ccSm || c == ccSc || c == ccSk || c == ccSo; |
|
} |
|
|
|
static inline bool IsHaskellLetter(const int ch) { |
|
if (IsASCII(ch)) { |
|
return (ch >= 'a' && ch <= 'z') |
|
|| (ch >= 'A' && ch <= 'Z'); |
|
} else { |
|
return u_iswalpha(ch) != 0; |
|
} |
|
} |
|
|
|
static inline bool IsHaskellAlphaNumeric(const int ch) { |
|
if (IsASCII(ch)) { |
|
return IsAlphaNumeric(ch); |
|
} else { |
|
return u_iswalnum(ch) != 0; |
|
} |
|
} |
|
|
|
static inline bool IsHaskellUpperCase(const int ch) { |
|
if (IsASCII(ch)) { |
|
return ch >= 'A' && ch <= 'Z'; |
|
} else { |
|
return u_iswupper(ch) != 0; |
|
} |
|
} |
|
|
|
static inline bool IsAnHaskellOperatorChar(const int ch) { |
|
if (IsASCII(ch)) { |
|
return |
|
( ch == '!' || ch == '#' || ch == '$' || ch == '%' |
|
|| ch == '&' || ch == '*' || ch == '+' || ch == '-' |
|
|| ch == '.' || ch == '/' || ch == ':' || ch == '<' |
|
|| ch == '=' || ch == '>' || ch == '?' || ch == '@' |
|
|| ch == '^' || ch == '|' || ch == '~' || ch == '\\'); |
|
} else { |
|
return u_IsHaskellSymbol(ch) != 0; |
|
} |
|
} |
|
|
|
static inline bool IsAHaskellWordStart(const int ch) { |
|
return IsHaskellLetter(ch) || ch == '_'; |
|
} |
|
|
|
static inline bool IsAHaskellWordChar(const int ch) { |
|
return ( IsHaskellAlphaNumeric(ch) |
|
|| ch == '_' |
|
|| ch == '\''); |
|
} |
|
|
|
static inline bool IsCommentBlockStyle(int style) { |
|
return (style >= SCE_HA_COMMENTBLOCK && style <= SCE_HA_COMMENTBLOCK3); |
|
} |
|
|
|
static inline bool IsCommentStyle(int style) { |
|
return (style >= SCE_HA_COMMENTLINE && style <= SCE_HA_COMMENTBLOCK3) |
|
|| ( style == SCE_HA_LITERATE_COMMENT |
|
|| style == SCE_HA_LITERATE_CODEDELIM); |
|
} |
|
|
|
// styles which do not belong to Haskell, but to external tools |
|
static inline bool IsExternalStyle(int style) { |
|
return ( style == SCE_HA_PREPROCESSOR |
|
|| style == SCE_HA_LITERATE_COMMENT |
|
|| style == SCE_HA_LITERATE_CODEDELIM); |
|
} |
|
|
|
static inline int CommentBlockStyleFromNestLevel(const unsigned int nestLevel) { |
|
return SCE_HA_COMMENTBLOCK + (nestLevel % 3); |
|
} |
|
|
|
// Mangled version of lexlib/Accessor.cxx IndentAmount. |
|
// Modified to treat comment blocks as whitespace |
|
// plus special case for commentline/preprocessor. |
|
static int HaskellIndentAmount(Accessor &styler, const Sci_Position line) { |
|
|
|
// Determines the indentation level of the current line |
|
// Comment blocks are treated as whitespace |
|
|
|
Sci_Position pos = styler.LineStart(line); |
|
Sci_Position eol_pos = styler.LineStart(line + 1) - 1; |
|
|
|
char ch = styler[pos]; |
|
int style = styler.StyleAt(pos); |
|
|
|
int indent = 0; |
|
bool inPrevPrefix = line > 0; |
|
|
|
Sci_Position posPrev = inPrevPrefix ? styler.LineStart(line-1) : 0; |
|
|
|
while (( ch == ' ' || ch == '\t' |
|
|| IsCommentBlockStyle(style) |
|
|| style == SCE_HA_LITERATE_CODEDELIM) |
|
&& (pos < eol_pos)) { |
|
if (inPrevPrefix) { |
|
char chPrev = styler[posPrev++]; |
|
if (chPrev != ' ' && chPrev != '\t') { |
|
inPrevPrefix = false; |
|
} |
|
} |
|
if (ch == '\t') { |
|
indent = (indent / 8 + 1) * 8; |
|
} else { // Space or comment block |
|
indent++; |
|
} |
|
pos++; |
|
ch = styler[pos]; |
|
style = styler.StyleAt(pos); |
|
} |
|
|
|
indent += SC_FOLDLEVELBASE; |
|
// if completely empty line or the start of a comment or preprocessor... |
|
if ( styler.LineStart(line) == styler.Length() |
|
|| ch == ' ' |
|
|| ch == '\t' |
|
|| ch == '\n' |
|
|| ch == '\r' |
|
|| IsCommentStyle(style) |
|
|| style == SCE_HA_PREPROCESSOR) |
|
return indent | SC_FOLDLEVELWHITEFLAG; |
|
else |
|
return indent; |
|
} |
|
|
|
struct OptionsHaskell { |
|
bool magicHash; |
|
bool allowQuotes; |
|
bool implicitParams; |
|
bool highlightSafe; |
|
bool cpp; |
|
bool stylingWithinPreprocessor; |
|
bool fold; |
|
bool foldComment; |
|
bool foldCompact; |
|
bool foldImports; |
|
OptionsHaskell() { |
|
magicHash = true; // Widespread use, enabled by default. |
|
allowQuotes = true; // Widespread use, enabled by default. |
|
implicitParams = false; // Fell out of favor, seldom used, disabled. |
|
highlightSafe = true; // Moderately used, doesn't hurt to enable. |
|
cpp = true; // Widespread use, enabled by default; |
|
stylingWithinPreprocessor = false; |
|
fold = false; |
|
foldComment = false; |
|
foldCompact = false; |
|
foldImports = false; |
|
} |
|
}; |
|
|
|
static const char * const haskellWordListDesc[] = { |
|
"Keywords", |
|
"FFI", |
|
"Reserved operators", |
|
0 |
|
}; |
|
|
|
struct OptionSetHaskell : public OptionSet<OptionsHaskell> { |
|
OptionSetHaskell() { |
|
DefineProperty("lexer.haskell.allow.hash", &OptionsHaskell::magicHash, |
|
"Set to 0 to disallow the '#' character at the end of identifiers and " |
|
"literals with the haskell lexer " |
|
"(GHC -XMagicHash extension)"); |
|
|
|
DefineProperty("lexer.haskell.allow.quotes", &OptionsHaskell::allowQuotes, |
|
"Set to 0 to disable highlighting of Template Haskell name quotations " |
|
"and promoted constructors " |
|
"(GHC -XTemplateHaskell and -XDataKinds extensions)"); |
|
|
|
DefineProperty("lexer.haskell.allow.questionmark", &OptionsHaskell::implicitParams, |
|
"Set to 1 to allow the '?' character at the start of identifiers " |
|
"with the haskell lexer " |
|
"(GHC & Hugs -XImplicitParams extension)"); |
|
|
|
DefineProperty("lexer.haskell.import.safe", &OptionsHaskell::highlightSafe, |
|
"Set to 0 to disallow \"safe\" keyword in imports " |
|
"(GHC -XSafe, -XTrustworthy, -XUnsafe extensions)"); |
|
|
|
DefineProperty("lexer.haskell.cpp", &OptionsHaskell::cpp, |
|
"Set to 0 to disable C-preprocessor highlighting " |
|
"(-XCPP extension)"); |
|
|
|
DefineProperty("styling.within.preprocessor", &OptionsHaskell::stylingWithinPreprocessor, |
|
"For Haskell code, determines whether all preprocessor code is styled in the " |
|
"preprocessor style (0, the default) or only from the initial # to the end " |
|
"of the command word(1)." |
|
); |
|
|
|
DefineProperty("fold", &OptionsHaskell::fold); |
|
|
|
DefineProperty("fold.comment", &OptionsHaskell::foldComment); |
|
|
|
DefineProperty("fold.compact", &OptionsHaskell::foldCompact); |
|
|
|
DefineProperty("fold.haskell.imports", &OptionsHaskell::foldImports, |
|
"Set to 1 to enable folding of import declarations"); |
|
|
|
DefineWordListSets(haskellWordListDesc); |
|
} |
|
}; |
|
|
|
class LexerHaskell : public DefaultLexer { |
|
bool literate; |
|
Sci_Position firstImportLine; |
|
int firstImportIndent; |
|
WordList keywords; |
|
WordList ffi; |
|
WordList reserved_operators; |
|
OptionsHaskell options; |
|
OptionSetHaskell osHaskell; |
|
|
|
enum HashCount { |
|
oneHash |
|
,twoHashes |
|
,unlimitedHashes |
|
}; |
|
|
|
enum KeywordMode { |
|
HA_MODE_DEFAULT = 0 |
|
,HA_MODE_IMPORT1 = 1 // after "import", before "qualified" or "safe" or package name or module name. |
|
,HA_MODE_IMPORT2 = 2 // after module name, before "as" or "hiding". |
|
,HA_MODE_IMPORT3 = 3 // after "as", before "hiding" |
|
,HA_MODE_MODULE = 4 // after "module", before module name. |
|
,HA_MODE_FFI = 5 // after "foreign", before FFI keywords |
|
,HA_MODE_TYPE = 6 // after "type" or "data", before "family" |
|
}; |
|
|
|
enum LiterateMode { |
|
LITERATE_BIRD = 0 // if '>' is the first character on the line, |
|
// color '>' as a codedelim and the rest of |
|
// the line as code. |
|
// else if "\begin{code}" is the only word on the |
|
// line except whitespace, switch to LITERATE_BLOCK |
|
// otherwise color the line as a literate comment. |
|
,LITERATE_BLOCK = 1 // if the string "\end{code}" is encountered at column |
|
// 0 ignoring all later characters, color the line |
|
// as a codedelim and switch to LITERATE_BIRD |
|
// otherwise color the line as code. |
|
}; |
|
|
|
struct HaskellLineInfo { |
|
unsigned int nestLevel; // 22 bits ought to be enough for anybody |
|
unsigned int nonexternalStyle; // 5 bits, widen if number of styles goes |
|
// beyond 31. |
|
bool pragma; |
|
LiterateMode lmode; |
|
KeywordMode mode; |
|
|
|
HaskellLineInfo(int state) : |
|
nestLevel (state >> 10) |
|
, nonexternalStyle ((state >> 5) & 0x1F) |
|
, pragma ((state >> 4) & 0x1) |
|
, lmode (static_cast<LiterateMode>((state >> 3) & 0x1)) |
|
, mode (static_cast<KeywordMode>(state & 0x7)) |
|
{} |
|
|
|
int ToLineState() { |
|
return |
|
(nestLevel << 10) |
|
| (nonexternalStyle << 5) |
|
| (pragma << 4) |
|
| (lmode << 3) |
|
| mode; |
|
} |
|
}; |
|
|
|
inline void skipMagicHash(StyleContext &sc, const HashCount hashes) const { |
|
if (options.magicHash && sc.ch == '#') { |
|
sc.Forward(); |
|
if (hashes == twoHashes && sc.ch == '#') { |
|
sc.Forward(); |
|
} else if (hashes == unlimitedHashes) { |
|
while (sc.ch == '#') { |
|
sc.Forward(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
bool LineContainsImport(const Sci_Position line, Accessor &styler) const { |
|
if (options.foldImports) { |
|
Sci_Position currentPos = styler.LineStart(line); |
|
int style = styler.StyleAt(currentPos); |
|
|
|
Sci_Position eol_pos = styler.LineStart(line + 1) - 1; |
|
|
|
while (currentPos < eol_pos) { |
|
int ch = styler[currentPos]; |
|
style = styler.StyleAt(currentPos); |
|
|
|
if (ch == ' ' || ch == '\t' |
|
|| IsCommentBlockStyle(style) |
|
|| style == SCE_HA_LITERATE_CODEDELIM) { |
|
currentPos++; |
|
} else { |
|
break; |
|
} |
|
} |
|
|
|
return (style == SCE_HA_KEYWORD |
|
&& styler.Match(currentPos, "import")); |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
inline int IndentAmountWithOffset(Accessor &styler, const Sci_Position line) const { |
|
const int indent = HaskellIndentAmount(styler, line); |
|
const int indentLevel = indent & SC_FOLDLEVELNUMBERMASK; |
|
return indentLevel <= ((firstImportIndent - 1) + SC_FOLDLEVELBASE) |
|
? indent |
|
: (indentLevel + firstImportIndent) | (indent & ~SC_FOLDLEVELNUMBERMASK); |
|
} |
|
|
|
inline int IndentLevelRemoveIndentOffset(const int indentLevel) const { |
|
return indentLevel <= ((firstImportIndent - 1) + SC_FOLDLEVELBASE) |
|
? indentLevel |
|
: indentLevel - firstImportIndent; |
|
} |
|
|
|
public: |
|
LexerHaskell(bool literate_) |
|
: DefaultLexer(literate_ ? "literatehaskell" : "haskell", literate_ ? SCLEX_LITERATEHASKELL : SCLEX_HASKELL) |
|
, literate(literate_) |
|
, firstImportLine(-1) |
|
, firstImportIndent(0) |
|
{} |
|
virtual ~LexerHaskell() {} |
|
|
|
void SCI_METHOD Release() override { |
|
delete this; |
|
} |
|
|
|
int SCI_METHOD Version() const override { |
|
return lvRelease5; |
|
} |
|
|
|
const char * SCI_METHOD PropertyNames() override { |
|
return osHaskell.PropertyNames(); |
|
} |
|
|
|
int SCI_METHOD PropertyType(const char *name) override { |
|
return osHaskell.PropertyType(name); |
|
} |
|
|
|
const char * SCI_METHOD DescribeProperty(const char *name) override { |
|
return osHaskell.DescribeProperty(name); |
|
} |
|
|
|
Sci_Position SCI_METHOD PropertySet(const char *key, const char *val) override; |
|
|
|
const char * SCI_METHOD PropertyGet(const char *key) override { |
|
return osHaskell.PropertyGet(key); |
|
} |
|
|
|
const char * SCI_METHOD DescribeWordListSets() override { |
|
return osHaskell.DescribeWordListSets(); |
|
} |
|
|
|
Sci_Position SCI_METHOD WordListSet(int n, const char *wl) override; |
|
|
|
void SCI_METHOD Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override; |
|
|
|
void SCI_METHOD Fold(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override; |
|
|
|
void * SCI_METHOD PrivateCall(int, void *) override { |
|
return 0; |
|
} |
|
|
|
static ILexer5 *LexerFactoryHaskell() { |
|
return new LexerHaskell(false); |
|
} |
|
|
|
static ILexer5 *LexerFactoryLiterateHaskell() { |
|
return new LexerHaskell(true); |
|
} |
|
}; |
|
|
|
Sci_Position SCI_METHOD LexerHaskell::PropertySet(const char *key, const char *val) { |
|
if (osHaskell.PropertySet(&options, key, val)) { |
|
return 0; |
|
} |
|
return -1; |
|
} |
|
|
|
Sci_Position SCI_METHOD LexerHaskell::WordListSet(int n, const char *wl) { |
|
WordList *wordListN = 0; |
|
switch (n) { |
|
case 0: |
|
wordListN = &keywords; |
|
break; |
|
case 1: |
|
wordListN = &ffi; |
|
break; |
|
case 2: |
|
wordListN = &reserved_operators; |
|
break; |
|
} |
|
Sci_Position firstModification = -1; |
|
if (wordListN) { |
|
WordList wlNew; |
|
wlNew.Set(wl); |
|
if (*wordListN != wlNew) { |
|
wordListN->Set(wl); |
|
firstModification = 0; |
|
} |
|
} |
|
return firstModification; |
|
} |
|
|
|
void SCI_METHOD LexerHaskell::Lex(Sci_PositionU startPos, Sci_Position length, int initStyle |
|
,IDocument *pAccess) { |
|
LexAccessor styler(pAccess); |
|
|
|
Sci_Position lineCurrent = styler.GetLine(startPos); |
|
|
|
HaskellLineInfo hs = HaskellLineInfo(lineCurrent ? styler.GetLineState(lineCurrent-1) : 0); |
|
|
|
// Do not leak onto next line |
|
if (initStyle == SCE_HA_STRINGEOL) |
|
initStyle = SCE_HA_DEFAULT; |
|
else if (initStyle == SCE_HA_LITERATE_CODEDELIM) |
|
initStyle = hs.nonexternalStyle; |
|
|
|
StyleContext sc(startPos, length, initStyle, styler); |
|
|
|
int base = 10; |
|
bool dot = false; |
|
|
|
bool inDashes = false; |
|
bool alreadyInTheMiddleOfOperator = false; |
|
|
|
assert(!(IsCommentBlockStyle(initStyle) && hs.nestLevel == 0)); |
|
|
|
while (sc.More()) { |
|
// Check for state end |
|
|
|
if (!IsExternalStyle(sc.state)) { |
|
hs.nonexternalStyle = sc.state; |
|
} |
|
|
|
// For lexer to work, states should unconditionally forward at least one |
|
// character. |
|
// If they don't, they should still check if they are at line end and |
|
// forward if so. |
|
// If a state forwards more than one character, it should check every time |
|
// that it is not a line end and cease forwarding otherwise. |
|
if (sc.atLineEnd) { |
|
// Remember the line state for future incremental lexing |
|
styler.SetLineState(lineCurrent, hs.ToLineState()); |
|
lineCurrent++; |
|
} |
|
|
|
// Handle line continuation generically. |
|
if (sc.ch == '\\' && (sc.chNext == '\n' || sc.chNext == '\r') |
|
&& ( sc.state == SCE_HA_STRING |
|
|| sc.state == SCE_HA_PREPROCESSOR)) { |
|
// Remember the line state for future incremental lexing |
|
styler.SetLineState(lineCurrent, hs.ToLineState()); |
|
lineCurrent++; |
|
|
|
sc.Forward(); |
|
if (sc.ch == '\r' && sc.chNext == '\n') { |
|
sc.Forward(); |
|
} |
|
sc.Forward(); |
|
|
|
continue; |
|
} |
|
|
|
if (sc.atLineStart) { |
|
|
|
if (sc.state == SCE_HA_STRING || sc.state == SCE_HA_CHARACTER) { |
|
// Prevent SCE_HA_STRINGEOL from leaking back to previous line |
|
sc.SetState(sc.state); |
|
} |
|
|
|
if (literate && hs.lmode == LITERATE_BIRD) { |
|
if (!IsExternalStyle(sc.state)) { |
|
sc.SetState(SCE_HA_LITERATE_COMMENT); |
|
} |
|
} |
|
} |
|
|
|
// External |
|
// Literate |
|
if ( literate && hs.lmode == LITERATE_BIRD && sc.atLineStart |
|
&& sc.ch == '>') { |
|
sc.SetState(SCE_HA_LITERATE_CODEDELIM); |
|
sc.ForwardSetState(hs.nonexternalStyle); |
|
} |
|
else if (literate && hs.lmode == LITERATE_BIRD && sc.atLineStart |
|
&& ( sc.ch == ' ' || sc.ch == '\t' |
|
|| sc.Match("\\begin{code}"))) { |
|
sc.SetState(sc.state); |
|
|
|
while ((sc.ch == ' ' || sc.ch == '\t') && sc.More()) |
|
sc.Forward(); |
|
|
|
if (sc.Match("\\begin{code}")) { |
|
sc.Forward(static_cast<int>(strlen("\\begin{code}"))); |
|
|
|
bool correct = true; |
|
|
|
while (!sc.atLineEnd && sc.More()) { |
|
if (sc.ch != ' ' && sc.ch != '\t') { |
|
correct = false; |
|
} |
|
sc.Forward(); |
|
} |
|
|
|
if (correct) { |
|
sc.ChangeState(SCE_HA_LITERATE_CODEDELIM); // color the line end |
|
hs.lmode = LITERATE_BLOCK; |
|
} |
|
} |
|
} |
|
else if (literate && hs.lmode == LITERATE_BLOCK && sc.atLineStart |
|
&& sc.Match("\\end{code}")) { |
|
sc.SetState(SCE_HA_LITERATE_CODEDELIM); |
|
|
|
sc.Forward(static_cast<int>(strlen("\\end{code}"))); |
|
|
|
while (!sc.atLineEnd && sc.More()) { |
|
sc.Forward(); |
|
} |
|
|
|
sc.SetState(SCE_HA_LITERATE_COMMENT); |
|
hs.lmode = LITERATE_BIRD; |
|
} |
|
// Preprocessor |
|
else if (sc.atLineStart && sc.ch == '#' && options.cpp |
|
&& (!options.stylingWithinPreprocessor || sc.state == SCE_HA_DEFAULT)) { |
|
sc.SetState(SCE_HA_PREPROCESSOR); |
|
sc.Forward(); |
|
} |
|
// Literate |
|
else if (sc.state == SCE_HA_LITERATE_COMMENT) { |
|
sc.Forward(); |
|
} |
|
else if (sc.state == SCE_HA_LITERATE_CODEDELIM) { |
|
sc.ForwardSetState(hs.nonexternalStyle); |
|
} |
|
// Preprocessor |
|
else if (sc.state == SCE_HA_PREPROCESSOR) { |
|
if (sc.atLineEnd) { |
|
sc.SetState(options.stylingWithinPreprocessor |
|
? SCE_HA_DEFAULT |
|
: hs.nonexternalStyle); |
|
sc.Forward(); // prevent double counting a line |
|
} else if (options.stylingWithinPreprocessor && !IsHaskellLetter(sc.ch)) { |
|
sc.SetState(SCE_HA_DEFAULT); |
|
} else { |
|
sc.Forward(); |
|
} |
|
} |
|
// Haskell |
|
// Operator |
|
else if (sc.state == SCE_HA_OPERATOR) { |
|
int style = SCE_HA_OPERATOR; |
|
|
|
if ( sc.ch == ':' |
|
&& !alreadyInTheMiddleOfOperator |
|
// except "::" |
|
&& !( sc.chNext == ':' |
|
&& !IsAnHaskellOperatorChar(sc.GetRelative(2)))) { |
|
style = SCE_HA_CAPITAL; |
|
} |
|
|
|
alreadyInTheMiddleOfOperator = false; |
|
|
|
while (IsAnHaskellOperatorChar(sc.ch)) |
|
sc.Forward(); |
|
|
|
char s[100]; |
|
sc.GetCurrent(s, sizeof(s)); |
|
|
|
if (reserved_operators.InList(s)) |
|
style = SCE_HA_RESERVED_OPERATOR; |
|
|
|
sc.ChangeState(style); |
|
sc.SetState(SCE_HA_DEFAULT); |
|
} |
|
// String |
|
else if (sc.state == SCE_HA_STRING) { |
|
if (sc.atLineEnd) { |
|
sc.ChangeState(SCE_HA_STRINGEOL); |
|
sc.ForwardSetState(SCE_HA_DEFAULT); |
|
} else if (sc.ch == '\"') { |
|
sc.Forward(); |
|
skipMagicHash(sc, oneHash); |
|
sc.SetState(SCE_HA_DEFAULT); |
|
} else if (sc.ch == '\\') { |
|
sc.Forward(2); |
|
} else { |
|
sc.Forward(); |
|
} |
|
} |
|
// Char |
|
else if (sc.state == SCE_HA_CHARACTER) { |
|
if (sc.atLineEnd) { |
|
sc.ChangeState(SCE_HA_STRINGEOL); |
|
sc.ForwardSetState(SCE_HA_DEFAULT); |
|
} else if (sc.ch == '\'') { |
|
sc.Forward(); |
|
skipMagicHash(sc, oneHash); |
|
sc.SetState(SCE_HA_DEFAULT); |
|
} else if (sc.ch == '\\') { |
|
sc.Forward(2); |
|
} else { |
|
sc.Forward(); |
|
} |
|
} |
|
// Number |
|
else if (sc.state == SCE_HA_NUMBER) { |
|
if (sc.atLineEnd) { |
|
sc.SetState(SCE_HA_DEFAULT); |
|
sc.Forward(); // prevent double counting a line |
|
} else if (IsADigit(sc.ch, base)) { |
|
sc.Forward(); |
|
} else if (sc.ch=='.' && dot && IsADigit(sc.chNext, base)) { |
|
sc.Forward(2); |
|
dot = false; |
|
} else if ((base == 10) && |
|
(sc.ch == 'e' || sc.ch == 'E') && |
|
(IsADigit(sc.chNext) || sc.chNext == '+' || sc.chNext == '-')) { |
|
sc.Forward(); |
|
if (sc.ch == '+' || sc.ch == '-') |
|
sc.Forward(); |
|
} else { |
|
skipMagicHash(sc, twoHashes); |
|
sc.SetState(SCE_HA_DEFAULT); |
|
} |
|
} |
|
// Keyword or Identifier |
|
else if (sc.state == SCE_HA_IDENTIFIER) { |
|
int style = IsHaskellUpperCase(sc.ch) ? SCE_HA_CAPITAL : SCE_HA_IDENTIFIER; |
|
|
|
assert(IsAHaskellWordStart(sc.ch)); |
|
|
|
sc.Forward(); |
|
|
|
while (sc.More()) { |
|
if (IsAHaskellWordChar(sc.ch)) { |
|
sc.Forward(); |
|
} else if (sc.ch == '.' && style == SCE_HA_CAPITAL) { |
|
if (IsHaskellUpperCase(sc.chNext)) { |
|
sc.Forward(); |
|
style = SCE_HA_CAPITAL; |
|
} else if (IsAHaskellWordStart(sc.chNext)) { |
|
sc.Forward(); |
|
style = SCE_HA_IDENTIFIER; |
|
} else if (IsAnHaskellOperatorChar(sc.chNext)) { |
|
sc.Forward(); |
|
style = sc.ch == ':' ? SCE_HA_CAPITAL : SCE_HA_OPERATOR; |
|
while (IsAnHaskellOperatorChar(sc.ch)) |
|
sc.Forward(); |
|
break; |
|
} else { |
|
break; |
|
} |
|
} else { |
|
break; |
|
} |
|
} |
|
|
|
skipMagicHash(sc, unlimitedHashes); |
|
|
|
char s[100]; |
|
sc.GetCurrent(s, sizeof(s)); |
|
|
|
KeywordMode new_mode = HA_MODE_DEFAULT; |
|
|
|
if (keywords.InList(s)) { |
|
style = SCE_HA_KEYWORD; |
|
} else if (style == SCE_HA_CAPITAL) { |
|
if (hs.mode == HA_MODE_IMPORT1 || hs.mode == HA_MODE_IMPORT3) { |
|
style = SCE_HA_MODULE; |
|
new_mode = HA_MODE_IMPORT2; |
|
} else if (hs.mode == HA_MODE_MODULE) { |
|
style = SCE_HA_MODULE; |
|
} |
|
} else if (hs.mode == HA_MODE_IMPORT1 && |
|
strcmp(s,"qualified") == 0) { |
|
style = SCE_HA_KEYWORD; |
|
new_mode = HA_MODE_IMPORT1; |
|
} else if (options.highlightSafe && |
|
hs.mode == HA_MODE_IMPORT1 && |
|
strcmp(s,"safe") == 0) { |
|
style = SCE_HA_KEYWORD; |
|
new_mode = HA_MODE_IMPORT1; |
|
} else if (hs.mode == HA_MODE_IMPORT2) { |
|
if (strcmp(s,"as") == 0) { |
|
style = SCE_HA_KEYWORD; |
|
new_mode = HA_MODE_IMPORT3; |
|
} else if (strcmp(s,"hiding") == 0) { |
|
style = SCE_HA_KEYWORD; |
|
} |
|
} else if (hs.mode == HA_MODE_TYPE) { |
|
if (strcmp(s,"family") == 0) |
|
style = SCE_HA_KEYWORD; |
|
} |
|
|
|
if (hs.mode == HA_MODE_FFI) { |
|
if (ffi.InList(s)) { |
|
style = SCE_HA_KEYWORD; |
|
new_mode = HA_MODE_FFI; |
|
} |
|
} |
|
|
|
sc.ChangeState(style); |
|
sc.SetState(SCE_HA_DEFAULT); |
|
|
|
if (strcmp(s,"import") == 0 && hs.mode != HA_MODE_FFI) |
|
new_mode = HA_MODE_IMPORT1; |
|
else if (strcmp(s,"module") == 0) |
|
new_mode = HA_MODE_MODULE; |
|
else if (strcmp(s,"foreign") == 0) |
|
new_mode = HA_MODE_FFI; |
|
else if (strcmp(s,"type") == 0 |
|
|| strcmp(s,"data") == 0) |
|
new_mode = HA_MODE_TYPE; |
|
|
|
hs.mode = new_mode; |
|
} |
|
|
|
// Comments |
|
// Oneliner |
|
else if (sc.state == SCE_HA_COMMENTLINE) { |
|
if (sc.atLineEnd) { |
|
sc.SetState(hs.pragma ? SCE_HA_PRAGMA : SCE_HA_DEFAULT); |
|
sc.Forward(); // prevent double counting a line |
|
} else if (inDashes && sc.ch != '-' && !hs.pragma) { |
|
inDashes = false; |
|
if (IsAnHaskellOperatorChar(sc.ch)) { |
|
alreadyInTheMiddleOfOperator = true; |
|
sc.ChangeState(SCE_HA_OPERATOR); |
|
} |
|
} else { |
|
sc.Forward(); |
|
} |
|
} |
|
// Nested |
|
else if (IsCommentBlockStyle(sc.state)) { |
|
if (sc.Match('{','-')) { |
|
sc.SetState(CommentBlockStyleFromNestLevel(hs.nestLevel)); |
|
sc.Forward(2); |
|
hs.nestLevel++; |
|
} else if (sc.Match('-','}')) { |
|
sc.Forward(2); |
|
assert(hs.nestLevel > 0); |
|
if (hs.nestLevel > 0) |
|
hs.nestLevel--; |
|
sc.SetState( |
|
hs.nestLevel == 0 |
|
? (hs.pragma ? SCE_HA_PRAGMA : SCE_HA_DEFAULT) |
|
: CommentBlockStyleFromNestLevel(hs.nestLevel - 1)); |
|
} else { |
|
sc.Forward(); |
|
} |
|
} |
|
// Pragma |
|
else if (sc.state == SCE_HA_PRAGMA) { |
|
if (sc.Match("#-}")) { |
|
hs.pragma = false; |
|
sc.Forward(3); |
|
sc.SetState(SCE_HA_DEFAULT); |
|
} else if (sc.Match('-','-')) { |
|
sc.SetState(SCE_HA_COMMENTLINE); |
|
sc.Forward(2); |
|
inDashes = false; |
|
} else if (sc.Match('{','-')) { |
|
sc.SetState(CommentBlockStyleFromNestLevel(hs.nestLevel)); |
|
sc.Forward(2); |
|
hs.nestLevel = 1; |
|
} else { |
|
sc.Forward(); |
|
} |
|
} |
|
// New state? |
|
else if (sc.state == SCE_HA_DEFAULT) { |
|
// Digit |
|
if (IsADigit(sc.ch)) { |
|
hs.mode = HA_MODE_DEFAULT; |
|
|
|
sc.SetState(SCE_HA_NUMBER); |
|
if (sc.ch == '0' && (sc.chNext == 'X' || sc.chNext == 'x')) { |
|
// Match anything starting with "0x" or "0X", too |
|
sc.Forward(2); |
|
base = 16; |
|
dot = false; |
|
} else if (sc.ch == '0' && (sc.chNext == 'O' || sc.chNext == 'o')) { |
|
// Match anything starting with "0o" or "0O", too |
|
sc.Forward(2); |
|
base = 8; |
|
dot = false; |
|
} else { |
|
sc.Forward(); |
|
base = 10; |
|
dot = true; |
|
} |
|
} |
|
// Pragma |
|
else if (sc.Match("{-#")) { |
|
hs.pragma = true; |
|
sc.SetState(SCE_HA_PRAGMA); |
|
sc.Forward(3); |
|
} |
|
// Comment line |
|
else if (sc.Match('-','-')) { |
|
sc.SetState(SCE_HA_COMMENTLINE); |
|
sc.Forward(2); |
|
inDashes = true; |
|
} |
|
// Comment block |
|
else if (sc.Match('{','-')) { |
|
sc.SetState(CommentBlockStyleFromNestLevel(hs.nestLevel)); |
|
sc.Forward(2); |
|
hs.nestLevel = 1; |
|
} |
|
// String |
|
else if (sc.ch == '\"') { |
|
sc.SetState(SCE_HA_STRING); |
|
sc.Forward(); |
|
} |
|
// Character or quoted name or promoted term |
|
else if (sc.ch == '\'') { |
|
hs.mode = HA_MODE_DEFAULT; |
|
|
|
sc.SetState(SCE_HA_CHARACTER); |
|
sc.Forward(); |
|
|
|
if (options.allowQuotes) { |
|
// Quoted type ''T |
|
if (sc.ch=='\'' && IsAHaskellWordStart(sc.chNext)) { |
|
sc.Forward(); |
|
sc.ChangeState(SCE_HA_IDENTIFIER); |
|
} else if (sc.chNext != '\'') { |
|
// Quoted name 'n or promoted constructor 'N |
|
if (IsAHaskellWordStart(sc.ch)) { |
|
sc.ChangeState(SCE_HA_IDENTIFIER); |
|
// Promoted constructor operator ':~> |
|
} else if (sc.ch == ':') { |
|
alreadyInTheMiddleOfOperator = false; |
|
sc.ChangeState(SCE_HA_OPERATOR); |
|
// Promoted list or tuple '[T] |
|
} else if (sc.ch == '[' || sc.ch== '(') { |
|
sc.ChangeState(SCE_HA_OPERATOR); |
|
sc.ForwardSetState(SCE_HA_DEFAULT); |
|
} |
|
} |
|
} |
|
} |
|
// Operator starting with '?' or an implicit parameter |
|
else if (sc.ch == '?') { |
|
hs.mode = HA_MODE_DEFAULT; |
|
|
|
alreadyInTheMiddleOfOperator = false; |
|
sc.SetState(SCE_HA_OPERATOR); |
|
|
|
if ( options.implicitParams |
|
&& IsAHaskellWordStart(sc.chNext) |
|
&& !IsHaskellUpperCase(sc.chNext)) { |
|
sc.Forward(); |
|
sc.ChangeState(SCE_HA_IDENTIFIER); |
|
} |
|
} |
|
// Operator |
|
else if (IsAnHaskellOperatorChar(sc.ch)) { |
|
hs.mode = HA_MODE_DEFAULT; |
|
|
|
sc.SetState(SCE_HA_OPERATOR); |
|
} |
|
// Braces and punctuation |
|
else if (sc.ch == ',' || sc.ch == ';' |
|
|| sc.ch == '(' || sc.ch == ')' |
|
|| sc.ch == '[' || sc.ch == ']' |
|
|| sc.ch == '{' || sc.ch == '}') { |
|
sc.SetState(SCE_HA_OPERATOR); |
|
sc.ForwardSetState(SCE_HA_DEFAULT); |
|
} |
|
// Keyword or Identifier |
|
else if (IsAHaskellWordStart(sc.ch)) { |
|
sc.SetState(SCE_HA_IDENTIFIER); |
|
// Something we don't care about |
|
} else { |
|
sc.Forward(); |
|
} |
|
} |
|
// This branch should never be reached. |
|
else { |
|
assert(false); |
|
sc.Forward(); |
|
} |
|
} |
|
sc.Complete(); |
|
} |
|
|
|
void SCI_METHOD LexerHaskell::Fold(Sci_PositionU startPos, Sci_Position length, int // initStyle |
|
,IDocument *pAccess) { |
|
if (!options.fold) |
|
return; |
|
|
|
Accessor styler(pAccess, NULL); |
|
|
|
Sci_Position lineCurrent = styler.GetLine(startPos); |
|
|
|
if (lineCurrent <= firstImportLine) { |
|
firstImportLine = -1; // readjust first import position |
|
firstImportIndent = 0; |
|
} |
|
|
|
const Sci_Position maxPos = startPos + length; |
|
const Sci_Position maxLines = |
|
maxPos == styler.Length() |
|
? styler.GetLine(maxPos) |
|
: styler.GetLine(maxPos - 1); // Requested last line |
|
const Sci_Position docLines = styler.GetLine(styler.Length()); // Available last line |
|
|
|
// Backtrack to previous non-blank line so we can determine indent level |
|
// for any white space lines |
|
// and so we can fix any preceding fold level (which is why we go back |
|
// at least one line in all cases) |
|
bool importHere = LineContainsImport(lineCurrent, styler); |
|
int indentCurrent = IndentAmountWithOffset(styler, lineCurrent); |
|
|
|
while (lineCurrent > 0) { |
|
lineCurrent--; |
|
importHere = LineContainsImport(lineCurrent, styler); |
|
indentCurrent = IndentAmountWithOffset(styler, lineCurrent); |
|
if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG)) |
|
break; |
|
} |
|
|
|
int indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK; |
|
|
|
if (importHere) { |
|
indentCurrentLevel = IndentLevelRemoveIndentOffset(indentCurrentLevel); |
|
if (firstImportLine == -1) { |
|
firstImportLine = lineCurrent; |
|
firstImportIndent = (1 + indentCurrentLevel) - SC_FOLDLEVELBASE; |
|
} |
|
if (firstImportLine != lineCurrent) { |
|
indentCurrentLevel++; |
|
} |
|
} |
|
|
|
indentCurrent = indentCurrentLevel | (indentCurrent & ~SC_FOLDLEVELNUMBERMASK); |
|
|
|
// Process all characters to end of requested range |
|
//that hangs over the end of the range. Cap processing in all cases |
|
// to end of document. |
|
while (lineCurrent <= docLines && lineCurrent <= maxLines) { |
|
|
|
// Gather info |
|
Sci_Position lineNext = lineCurrent + 1; |
|
importHere = false; |
|
int indentNext = indentCurrent; |
|
|
|
if (lineNext <= docLines) { |
|
// Information about next line is only available if not at end of document |
|
importHere = LineContainsImport(lineNext, styler); |
|
indentNext = IndentAmountWithOffset(styler, lineNext); |
|
} |
|
if (indentNext & SC_FOLDLEVELWHITEFLAG) |
|
indentNext = SC_FOLDLEVELWHITEFLAG | indentCurrentLevel; |
|
|
|
// Skip past any blank lines for next indent level info; we skip also |
|
// comments (all comments, not just those starting in column 0) |
|
// which effectively folds them into surrounding code rather |
|
// than screwing up folding. |
|
|
|
while (lineNext < docLines && (indentNext & SC_FOLDLEVELWHITEFLAG)) { |
|
lineNext++; |
|
importHere = LineContainsImport(lineNext, styler); |
|
indentNext = IndentAmountWithOffset(styler, lineNext); |
|
} |
|
|
|
int indentNextLevel = indentNext & SC_FOLDLEVELNUMBERMASK; |
|
|
|
if (importHere) { |
|
indentNextLevel = IndentLevelRemoveIndentOffset(indentNextLevel); |
|
if (firstImportLine == -1) { |
|
firstImportLine = lineNext; |
|
firstImportIndent = (1 + indentNextLevel) - SC_FOLDLEVELBASE; |
|
} |
|
if (firstImportLine != lineNext) { |
|
indentNextLevel++; |
|
} |
|
} |
|
|
|
indentNext = indentNextLevel | (indentNext & ~SC_FOLDLEVELNUMBERMASK); |
|
|
|
const int levelBeforeComments = Maximum(indentCurrentLevel,indentNextLevel); |
|
|
|
// Now set all the indent levels on the lines we skipped |
|
// Do this from end to start. Once we encounter one line |
|
// which is indented more than the line after the end of |
|
// the comment-block, use the level of the block before |
|
|
|
Sci_Position skipLine = lineNext; |
|
int skipLevel = indentNextLevel; |
|
|
|
while (--skipLine > lineCurrent) { |
|
int skipLineIndent = IndentAmountWithOffset(styler, skipLine); |
|
|
|
if (options.foldCompact) { |
|
if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > indentNextLevel) { |
|
skipLevel = levelBeforeComments; |
|
} |
|
|
|
int whiteFlag = skipLineIndent & SC_FOLDLEVELWHITEFLAG; |
|
|
|
styler.SetLevel(skipLine, skipLevel | whiteFlag); |
|
} else { |
|
if ( (skipLineIndent & SC_FOLDLEVELNUMBERMASK) > indentNextLevel |
|
&& !(skipLineIndent & SC_FOLDLEVELWHITEFLAG)) { |
|
skipLevel = levelBeforeComments; |
|
} |
|
|
|
styler.SetLevel(skipLine, skipLevel); |
|
} |
|
} |
|
|
|
int lev = indentCurrent; |
|
|
|
if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG)) { |
|
if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext & SC_FOLDLEVELNUMBERMASK)) |
|
lev |= SC_FOLDLEVELHEADERFLAG; |
|
} |
|
|
|
// Set fold level for this line and move to next line |
|
styler.SetLevel(lineCurrent, options.foldCompact ? lev : lev & ~SC_FOLDLEVELWHITEFLAG); |
|
|
|
indentCurrent = indentNext; |
|
indentCurrentLevel = indentNextLevel; |
|
lineCurrent = lineNext; |
|
} |
|
|
|
// NOTE: Cannot set level of last line here because indentCurrent doesn't have |
|
// header flag set; the loop above is crafted to take care of this case! |
|
//styler.SetLevel(lineCurrent, indentCurrent); |
|
} |
|
|
|
LexerModule lmHaskell(SCLEX_HASKELL, LexerHaskell::LexerFactoryHaskell, "haskell", haskellWordListDesc); |
|
LexerModule lmLiterateHaskell(SCLEX_LITERATEHASKELL, LexerHaskell::LexerFactoryLiterateHaskell, "literatehaskell", haskellWordListDesc);
|
|
|