// Scintilla source code edit control /** @file LexLua.cxx ** Lexer for Lua language. ** ** Written by Paul Winwood. ** Folder by Alexey Yutkin. ** Modified by Marcos E. Wurzius & Philippe Lhoste **/ #include #include #include #include #include #include #include #include "ILexer.h" #include "Scintilla.h" #include "SciLexer.h" #include "WordList.h" #include "LexAccessor.h" #include "Accessor.h" #include "StyleContext.h" #include "CharacterSet.h" #include "LexerModule.h" #include "OptionSet.h" #include "SubStyles.h" #include "DefaultLexer.h" using namespace Scintilla; using namespace Lexilla; namespace { // Test for [=[ ... ]=] delimiters, returns 0 if it's only a [ or ], // return 1 for [[ or ]], returns >=2 for [=[ or ]=] and so on. // The maximum number of '=' characters allowed is 254. int LongDelimCheck(StyleContext &sc) { int sep = 1; while (sc.GetRelative(sep) == '=' && sep < 0xFF) sep++; if (sc.GetRelative(sep) == sc.ch) return sep; return 0; } const char *const luaWordListDesc[] = { "Keywords", "Basic functions", "String, (table) & math functions", "(coroutines), I/O & system facilities", "user1", "user2", "user3", "user4", nullptr }; const char styleSubable[] = { SCE_LUA_IDENTIFIER, 0 }; const LexicalClass lexicalClasses[] = { // Lexer Lua SCLEX_LUA SCE_LUA_: 0, "SCE_LUA_DEFAULT", "default", "White space: Visible only in View Whitespace mode (or if it has a back colour)", 1, "SCE_LUA_COMMENT", "comment", "Block comment (Lua 5.0)", 2, "SCE_LUA_COMMENTLINE", "comment line", "Line comment", 3, "SCE_LUA_COMMENTDOC", "comment documentation", "Doc comment", 4, "SCE_LUA_NUMBER", "literal numeric", "Number", 5, "SCE_LUA_WORD", "keyword", "Keyword", 6, "SCE_LUA_STRING", "literal string", "(Double quoted) String", 7, "SCE_LUA_CHARACTER", "literal string character", "Character (Single quoted string)", 8, "SCE_LUA_LITERALSTRING", "literal string", "Literal string", 9, "SCE_LUA_PREPROCESSOR", "preprocessor", "Preprocessor (obsolete in Lua 4.0 and up)", 10, "SCE_LUA_OPERATOR", "operator", "Operators", 11, "SCE_LUA_IDENTIFIER", "identifier", "Identifier (everything else...)", 12, "SCE_LUA_STRINGEOL", "error literal string", "End of line where string is not closed", 13, "SCE_LUA_WORD2", "identifier", "Other keywords", 14, "SCE_LUA_WORD3", "identifier", "Other keywords", 15, "SCE_LUA_WORD4", "identifier", "Other keywords", 16, "SCE_LUA_WORD5", "identifier", "Other keywords", 17, "SCE_LUA_WORD6", "identifier", "Other keywords", 18, "SCE_LUA_WORD7", "identifier", "Other keywords", 19, "SCE_LUA_WORD8", "identifier", "Other keywords", 20, "SCE_LUA_LABEL", "label", "Labels", }; // Options used for LexerLua struct OptionsLua { bool foldCompact = true; }; struct OptionSetLua : public OptionSet { OptionSetLua() { DefineProperty("fold.compact", &OptionsLua::foldCompact); DefineWordListSets(luaWordListDesc); } }; class LexerLua : public DefaultLexer { WordList keywords; WordList keywords2; WordList keywords3; WordList keywords4; WordList keywords5; WordList keywords6; WordList keywords7; WordList keywords8; OptionsLua options; OptionSetLua osLua; SubStyles subStyles{styleSubable}; public: explicit LexerLua() : DefaultLexer("lua", SCLEX_LUA, lexicalClasses, std::size(lexicalClasses)) { } ~LexerLua() override = default; void SCI_METHOD Release() noexcept override { delete this; } int SCI_METHOD Version() const noexcept override { return lvRelease5; } const char *SCI_METHOD PropertyNames() noexcept override { return osLua.PropertyNames(); } int SCI_METHOD PropertyType(const char *name) override { return osLua.PropertyType(name); } const char *SCI_METHOD DescribeProperty(const char *name) override { return osLua.DescribeProperty(name); } Sci_Position SCI_METHOD PropertySet(const char *key, const char *val) override; const char *SCI_METHOD PropertyGet(const char *key) override { return osLua.PropertyGet(key); } const char *SCI_METHOD DescribeWordListSets() noexcept override { return osLua.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; int SCI_METHOD AllocateSubStyles(int styleBase, int numberStyles) override { return subStyles.Allocate(styleBase, numberStyles); } int SCI_METHOD SubStylesStart(int styleBase) override { return subStyles.Start(styleBase); } int SCI_METHOD SubStylesLength(int styleBase) override { return subStyles.Length(styleBase); } int SCI_METHOD StyleFromSubStyle(int subStyle) override { const int styleBase = subStyles.BaseStyle(subStyle); return styleBase; } int SCI_METHOD PrimaryStyleFromStyle(int style) override { return style; } void SCI_METHOD FreeSubStyles() override { subStyles.Free(); } void SCI_METHOD SetIdentifiers(int style, const char *identifiers) override { subStyles.SetIdentifiers(style, identifiers); } int SCI_METHOD DistanceToSecondaryStyles() override { return 0; } const char *SCI_METHOD GetSubStyleBases() override { return styleSubable; } static ILexer5 *LexerFactoryLua() { return new LexerLua(); } }; Sci_Position SCI_METHOD LexerLua::PropertySet(const char *key, const char *val) { if (osLua.PropertySet(&options, key, val)) { return 0; } return -1; } Sci_Position SCI_METHOD LexerLua::WordListSet(int n, const char *wl) { WordList *wordListN = nullptr; switch (n) { case 0: wordListN = &keywords; break; case 1: wordListN = &keywords2; break; case 2: wordListN = &keywords3; break; case 3: wordListN = &keywords4; break; case 4: wordListN = &keywords5; break; case 5: wordListN = &keywords6; break; case 6: wordListN = &keywords7; break; case 7: wordListN = &keywords8; break; default: break; } Sci_Position firstModification = -1; if (wordListN) { if (wordListN->Set(wl)) { firstModification = 0; } } return firstModification; } constexpr int maskSeparator = 0xFF; constexpr int maskStringWs = 0x100; constexpr int maskDocComment = 0x200; void LexerLua::Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) { LexAccessor styler(pAccess); // Accepts accented characters const CharacterSet setWordStart(CharacterSet::setAlpha, "_", true); const CharacterSet setWord(CharacterSet::setAlphaNum, "_", true); // Not exactly following number definition (several dots are seen as OK, etc.) // but probably enough in most cases. [pP] is for hex floats. const CharacterSet setNumber(CharacterSet::setDigits, ".-+abcdefpABCDEFP"); const CharacterSet setExponent("eEpP"); const CharacterSet setLuaOperator("*/-+()={}~[];<>,.^%:#&|"); const CharacterSet setEscapeSkip("\"'\\"); const WordClassifier &classifierIdentifiers = subStyles.Classifier(SCE_LUA_IDENTIFIER); Sci_Position currentLine = styler.GetLine(startPos); // Initialize long string [[ ... ]] or block comment --[[ ... ]], // if we are inside such a string. Block comment was introduced in Lua 5.0, // blocks with separators [=[ ... ]=] in Lua 5.1. // Continuation of a string (\z whitespace escaping) is controlled by stringWs. int sepCount = 0; int stringWs = 0; int lastLineDocComment = 0; if ((currentLine > 0) && AnyOf(initStyle, SCE_LUA_DEFAULT, SCE_LUA_LITERALSTRING, SCE_LUA_COMMENT, SCE_LUA_COMMENTDOC, SCE_LUA_STRING, SCE_LUA_CHARACTER)) { const int lineState = styler.GetLineState(currentLine - 1); sepCount = lineState & maskSeparator; stringWs = lineState & maskStringWs; lastLineDocComment = lineState & maskDocComment; } // results of identifier/keyword matching Sci_Position idenPos = 0; Sci_Position idenWordPos = 0; int idenStyle = SCE_LUA_IDENTIFIER; bool foundGoto = false; // Do not leak onto next line if (AnyOf(initStyle, SCE_LUA_STRINGEOL, SCE_LUA_COMMENTLINE, SCE_LUA_COMMENTDOC, SCE_LUA_PREPROCESSOR)) { initStyle = SCE_LUA_DEFAULT; } StyleContext sc(startPos, length, initStyle, styler); if (startPos == 0 && sc.ch == '#' && sc.chNext == '!') { // shbang line: "#!" is a comment only if located at the start of the script sc.SetState(SCE_LUA_COMMENTLINE); } for (; sc.More(); sc.Forward()) { if (sc.atLineEnd) { // Update the line state, so it can be seen by next line currentLine = styler.GetLine(sc.currentPos); switch (sc.state) { case SCE_LUA_DEFAULT: case SCE_LUA_LITERALSTRING: case SCE_LUA_COMMENT: case SCE_LUA_COMMENTDOC: case SCE_LUA_STRING: case SCE_LUA_CHARACTER: // Inside a literal string, block comment or string, we set the line state styler.SetLineState(currentLine, lastLineDocComment | stringWs | sepCount); break; default: // Reset the line state styler.SetLineState(currentLine, 0); break; } } if (sc.atLineStart && (sc.state == SCE_LUA_STRING)) { // Prevent SCE_LUA_STRINGEOL from leaking back to previous line sc.SetState(SCE_LUA_STRING); } // Handle string line continuation if ((sc.state == SCE_LUA_STRING || sc.state == SCE_LUA_CHARACTER) && sc.ch == '\\') { if (sc.chNext == '\n' || sc.chNext == '\r') { sc.Forward(); if (sc.ch == '\r' && sc.chNext == '\n') { sc.Forward(); } continue; } } // Determine if the current state should terminate. if (sc.state == SCE_LUA_OPERATOR) { if (sc.ch == ':' && sc.chPrev == ':') { // ::