/** * Copyright 2006-2019 Mitchell mitchell.att.foicica.com. See License.txt. * * Lua-powered dynamic language lexer for Scintilla. * * For documentation on writing lexers, see *../doc/LPegLexer.html*. */ #if LPEG_LEXER #include #include #include #include #include #if CURSES #include #endif #include "ILexer.h" #include "Scintilla.h" #include "SciLexer.h" #include "PropSetSimple.h" #include "LexAccessor.h" #include "LexerModule.h" extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" #ifdef LINK_LPEG LUALIB_API int luaopen_lpeg(lua_State *L); #endif } #if _WIN32 #define strcasecmp _stricmp #endif #define streq(s1, s2) (strcasecmp((s1), (s2)) == 0) using namespace Scintilla; #define l_setmetatable(l, k, mtf) \ if (luaL_newmetatable(l, k)) { \ lua_pushcfunction(l, mtf), lua_setfield(l, -2, "__index"); \ lua_pushcfunction(l, mtf), lua_setfield(l, -2, "__newindex"); \ } \ lua_setmetatable(l, -2); #define l_pushlexerp(l, mtf) do { \ lua_newtable(l); \ lua_pushvalue(l, 2), lua_setfield(l, -2, "property"); \ l_setmetatable(l, "sci_lexerp", mtf); \ } while(0) #define l_getlexerobj(l) \ lua_getfield(l, LUA_REGISTRYINDEX, "sci_lexers"); \ lua_pushlightuserdata(l, reinterpret_cast(this)); \ lua_gettable(l, -2), lua_replace(l, -2); #define l_getlexerfield(l, k) \ l_getlexerobj(l); \ lua_getfield(l, -1, k), lua_replace(l, -2); #if LUA_VERSION_NUM < 502 #define l_openlib(f, s) \ (lua_pushcfunction(L, f), lua_pushstring(L, s), lua_call(L, 1, 0)) #define LUA_BASELIBNAME "" #define lua_rawlen lua_objlen #define LUA_OK 0 #define lua_compare(l, a, b, _) lua_equal(l, a, b) #define LUA_OPEQ 0 #else #define l_openlib(f, s) (luaL_requiref(L, s, f, 1), lua_pop(L, 1)) #define LUA_BASELIBNAME "_G" #endif #define l_setfunction(l, f, k) (lua_pushcfunction(l, f), lua_setfield(l, -2, k)) #define l_setconstant(l, c, k) (lua_pushinteger(l, c), lua_setfield(l, -2, k)) #if CURSES #define A_COLORCHAR (A_COLOR | A_CHARTEXT) #endif /** The LPeg Scintilla lexer. */ class LexerLPeg : public ILexer { /** * The lexer's Lua state. * It is cleared each time the lexer language changes unless `own_lua` is * `true`. */ lua_State *L; /** * The flag indicating whether or not an existing Lua state was supplied as * the lexer's Lua state. */ bool own_lua; /** * The set of properties for the lexer. * The `lexer.name`, `lexer.lpeg.home`, and `lexer.lpeg.color.theme` * properties must be defined before running the lexer. */ PropSetSimple props; /** The function to send Scintilla messages with. */ SciFnDirect SS; /** The Scintilla object the lexer belongs to. */ sptr_t sci; /** * The flag indicating whether or not the lexer needs to be re-initialized. * Re-initialization is required after the lexer language changes. */ bool reinit; /** * The flag indicating whether or not the lexer language has embedded lexers. */ bool multilang; /** * The list of style numbers considered to be whitespace styles. * This is used in multi-language lexers when backtracking to whitespace to * determine which lexer grammar to use. */ bool ws[STYLE_MAX + 1]; /** * Logs the given error message or a Lua error message, prints it, and clears * the stack. * Error messages are logged to the "lexer.lpeg.error" property. * @param str The error message to log and print. If `NULL`, logs and prints * the Lua error message at the top of the stack. */ static void l_error(lua_State *L, const char *str=NULL) { lua_getfield(L, LUA_REGISTRYINDEX, "sci_props"); PropSetSimple *props = static_cast(lua_touserdata(L, -1)); lua_pop(L, 1); // props const char *key = "lexer.lpeg.error"; const char *value = str ? str : lua_tostring(L, -1); props->Set(key, value, strlen(key), strlen(value)); fprintf(stderr, "Lua Error: %s.\n", str ? str : lua_tostring(L, -1)); lua_settop(L, 0); } /** The lexer's `line_from_position` Lua function. */ static int l_line_from_position(lua_State *L) { lua_getfield(L, LUA_REGISTRYINDEX, "sci_buffer"); IDocument *buffer = static_cast(lua_touserdata(L, -1)); lua_pushinteger(L, buffer->LineFromPosition(luaL_checkinteger(L, 1) - 1)); return 1; } /** The lexer's `__index` Lua metatable. */ static int llexer_property(lua_State *L) { int newindex = (lua_gettop(L) == 3); luaL_getmetatable(L, "sci_lexer"); lua_getmetatable(L, 1); // metatable can be either sci_lexer or sci_lexerp int is_lexer = lua_compare(L, -1, -2, LUA_OPEQ); lua_pop(L, 2); // metatable, metatable lua_getfield(L, LUA_REGISTRYINDEX, "sci_buffer"); IDocument *buffer = static_cast(lua_touserdata(L, -1)); lua_getfield(L, LUA_REGISTRYINDEX, "sci_props"); PropSetSimple *props = static_cast(lua_touserdata(L, -1)); lua_pop(L, 2); // sci_props and sci_buffer if (is_lexer) lua_pushvalue(L, 2); // key is given else lua_getfield(L, 1, "property"); // indexible property const char *key = lua_tostring(L, -1); if (strcmp(key, "fold_level") == 0) { luaL_argcheck(L, !newindex, 3, "read-only property"); if (is_lexer) l_pushlexerp(L, llexer_property); else lua_pushinteger(L, buffer->GetLevel(luaL_checkinteger(L, 2))); } else if (strcmp(key, "indent_amount") == 0) { luaL_argcheck(L, !newindex, 3, "read-only property"); if (is_lexer) l_pushlexerp(L, llexer_property); else lua_pushinteger(L, buffer->GetLineIndentation(luaL_checkinteger(L, 2))); } else if (strcmp(key, "property") == 0) { luaL_argcheck(L, !is_lexer || !newindex, 3, "read-only property"); if (is_lexer) l_pushlexerp(L, llexer_property); else if (!newindex) lua_pushstring(L, props->Get(luaL_checkstring(L, 2))); else props->Set(luaL_checkstring(L, 2), luaL_checkstring(L, 3), lua_rawlen(L, 2), lua_rawlen(L, 3)); } else if (strcmp(key, "property_int") == 0) { luaL_argcheck(L, !newindex, 3, "read-only property"); if (is_lexer) l_pushlexerp(L, llexer_property); else { lua_pushstring(L, props->Get(luaL_checkstring(L, 2))); lua_pushinteger(L, lua_tointeger(L, -1)); } } else if (strcmp(key, "style_at") == 0) { luaL_argcheck(L, !newindex, 3, "read-only property"); if (is_lexer) l_pushlexerp(L, llexer_property); else { int style = buffer->StyleAt(luaL_checkinteger(L, 2) - 1); lua_getfield(L, LUA_REGISTRYINDEX, "sci_lexer_obj"); lua_getfield(L, -1, "_TOKENSTYLES"), lua_replace(L, -2); lua_pushnil(L); while (lua_next(L, -2)) { if (luaL_checkinteger(L, -1) == style) break; lua_pop(L, 1); // value } lua_pop(L, 1); // style_num } } else if (strcmp(key, "line_state") == 0) { luaL_argcheck(L, !is_lexer || !newindex, 3, "read-only property"); if (is_lexer) l_pushlexerp(L, llexer_property); else if (!newindex) lua_pushinteger(L, buffer->GetLineState(luaL_checkinteger(L, 2))); else buffer->SetLineState(luaL_checkinteger(L, 2), luaL_checkinteger(L, 3)); } else return !newindex ? (lua_rawget(L, 1), 1) : (lua_rawset(L, 1), 0); return 1; } /** * Expands value of the string property key at index *index* and pushes the * result onto the stack. * @param L The Lua State. * @param index The index the string property key. */ void lL_getexpanded(lua_State *L, int index) { lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"), lua_getfield(L, -1, "lexer"); lua_getfield(L, -1, "property_expanded"); lua_pushvalue(L, (index > 0) ? index : index - 3), lua_gettable(L, -2); lua_replace(L, -4), lua_pop(L, 2); // property_expanded and lexer module } /** * Parses the given style string to set the properties for the given style * number. * @param num The style number to set properties for. * @param style The style string containing properties to set. */ void SetStyle(int num, const char *style) { char *style_copy = static_cast(malloc(strlen(style) + 1)); char *option = strcpy(style_copy, style), *next = NULL, *p = NULL; while (option) { if ((next = strchr(option, ','))) *next++ = '\0'; if ((p = strchr(option, ':'))) *p++ = '\0'; if (streq(option, "font") && p) SS(sci, SCI_STYLESETFONT, num, reinterpret_cast(p)); else if (streq(option, "size") && p) SS(sci, SCI_STYLESETSIZE, num, static_cast(atoi(p))); else if (streq(option, "bold") || streq(option, "notbold") || streq(option, "weight")) { #if !CURSES int weight = SC_WEIGHT_NORMAL; if (*option == 'b') weight = SC_WEIGHT_BOLD; else if (*option == 'w' && p) weight = atoi(p); SS(sci, SCI_STYLESETWEIGHT, num, weight); #else // Scintilla curses requires font attributes to be stored in the "font // weight" style attribute. // First, clear any existing SC_WEIGHT_NORMAL, SC_WEIGHT_SEMIBOLD, or // SC_WEIGHT_BOLD values stored in the lower 16 bits. Then set the // appropriate curses attr. sptr_t weight = SS(sci, SCI_STYLEGETWEIGHT, num, 0) & ~A_COLORCHAR; int bold = *option == 'b' || (*option == 'w' && p && atoi(p) > SC_WEIGHT_NORMAL); SS(sci, SCI_STYLESETWEIGHT, num, bold ? weight | A_BOLD : weight & ~A_BOLD); #endif } else if (streq(option, "italics") || streq(option, "notitalics")) SS(sci, SCI_STYLESETITALIC, num, *option == 'i'); else if (streq(option, "underlined") || streq(option, "notunderlined")) { #if !CURSES SS(sci, SCI_STYLESETUNDERLINE, num, *option == 'u'); #else // Scintilla curses requires font attributes to be stored in the "font // weight" style attribute. // First, clear any existing SC_WEIGHT_NORMAL, SC_WEIGHT_SEMIBOLD, or // SC_WEIGHT_BOLD values stored in the lower 16 bits. Then set the // appropriate curses attr. sptr_t weight = SS(sci, SCI_STYLEGETWEIGHT, num, 0) & ~A_COLORCHAR; SS(sci, SCI_STYLESETWEIGHT, num, (*option == 'u') ? weight | A_UNDERLINE : weight & ~A_UNDERLINE); #endif } else if ((streq(option, "fore") || streq(option, "back")) && p) { int msg = (*option == 'f') ? SCI_STYLESETFORE : SCI_STYLESETBACK; int color = static_cast(strtol(p, NULL, 0)); if (*p == '#') { // #RRGGBB format; Scintilla format is 0xBBGGRR color = static_cast(strtol(p + 1, NULL, 16)); color = ((color & 0xFF0000) >> 16) | (color & 0xFF00) | ((color & 0xFF) << 16); // convert to 0xBBGGRR } SS(sci, msg, num, color); } else if (streq(option, "eolfilled") || streq(option, "noteolfilled")) SS(sci, SCI_STYLESETEOLFILLED, num, *option == 'e'); else if (streq(option, "characterset") && p) SS(sci, SCI_STYLESETCHARACTERSET, num, static_cast(atoi(p))); else if (streq(option, "case") && p) { if (*p == 'u') SS(sci, SCI_STYLESETCASE, num, SC_CASE_UPPER); else if (*p == 'l') SS(sci, SCI_STYLESETCASE, num, SC_CASE_LOWER); } else if (streq(option, "visible") || streq(option, "notvisible")) SS(sci, SCI_STYLESETVISIBLE, num, *option == 'v'); else if (streq(option, "changeable") || streq(option, "notchangeable")) SS(sci, SCI_STYLESETCHANGEABLE, num, *option == 'c'); else if (streq(option, "hotspot") || streq(option, "nothotspot")) SS(sci, SCI_STYLESETHOTSPOT, num, *option == 'h'); option = next; } free(style_copy); } /** * Iterates through the lexer's `_TOKENSTYLES`, setting the style properties * for all defined styles. */ bool SetStyles() { // If the lexer defines additional styles, set their properties first (if // the user has not already defined them). l_getlexerfield(L, "_EXTRASTYLES"); lua_pushnil(L); while (lua_next(L, -2)) { if (lua_isstring(L, -2) && lua_isstring(L, -1)) { lua_pushstring(L, "style."), lua_pushvalue(L, -3), lua_concat(L, 2); if (!*props.Get(lua_tostring(L, -1))) props.Set(lua_tostring(L, -1), lua_tostring(L, -2), lua_rawlen(L, -1), lua_rawlen(L, -2)); lua_pop(L, 1); // style name } lua_pop(L, 1); // value } lua_pop(L, 1); // _EXTRASTYLES l_getlexerfield(L, "_TOKENSTYLES"); if (!SS || !sci) { lua_pop(L, 1); // _TOKENSTYLES // Skip, but do not report an error since `reinit` would remain `false` // and subsequent calls to `Lex()` and `Fold()` would repeatedly call this // function and error. return true; } lua_pushstring(L, "style.default"), lL_getexpanded(L, -1); SetStyle(STYLE_DEFAULT, lua_tostring(L, -1)); lua_pop(L, 2); // style and "style.default" SS(sci, SCI_STYLECLEARALL, 0, 0); // set default styles lua_pushnil(L); while (lua_next(L, -2)) { if (lua_isstring(L, -2) && lua_isnumber(L, -1) && lua_tointeger(L, -1) != STYLE_DEFAULT) { lua_pushstring(L, "style."), lua_pushvalue(L, -3), lua_concat(L, 2); lL_getexpanded(L, -1), lua_replace(L, -2); SetStyle(lua_tointeger(L, -2), lua_tostring(L, -1)); lua_pop(L, 1); // style } lua_pop(L, 1); // value } lua_pop(L, 1); // _TOKENSTYLES return true; } /** * Returns the style name for the given style number. * @param style The style number to get the style name for. * @return style name or NULL */ const char *GetStyleName(int style) { if (!L) return NULL; const char *name = NULL; l_getlexerfield(L, "_TOKENSTYLES"); lua_pushnil(L); while (lua_next(L, -2)) if (lua_tointeger(L, -1) == style) { name = lua_tostring(L, -2); lua_pop(L, 2); // value and key break; } else lua_pop(L, 1); // value lua_pop(L, 1); // _TOKENSTYLES return name; } /** * Initializes the lexer once the `lexer.lpeg.home` and `lexer.name` * properties are set. */ bool Init() { char home[FILENAME_MAX], lexer[50], theme[FILENAME_MAX]; props.GetExpanded("lexer.lpeg.home", home); props.GetExpanded("lexer.name", lexer); props.GetExpanded("lexer.lpeg.color.theme", theme); if (!*home || !*lexer || !L) return false; lua_pushlightuserdata(L, reinterpret_cast(&props)); lua_setfield(L, LUA_REGISTRYINDEX, "sci_props"); // If necessary, load the lexer module and theme. lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"), lua_getfield(L, -1, "lexer"); if (lua_isnil(L, -1)) { lua_pop(L, 2); // nil and _LOADED // Modify `package.path` to find lexers. lua_getglobal(L, "package"), lua_getfield(L, -1, "path"); int orig_path = luaL_ref(L, LUA_REGISTRYINDEX); // restore later lua_pushstring(L, home), lua_pushstring(L, "/?.lua"), lua_concat(L, 2); lua_setfield(L, -2, "path"), lua_pop(L, 1); // package // Load the lexer module. lua_getglobal(L, "require"); lua_pushstring(L, "lexer"); if (lua_pcall(L, 1, 1, 0) != LUA_OK) return (l_error(L), false); l_setfunction(L, l_line_from_position, "line_from_position"); l_setconstant(L, SC_FOLDLEVELBASE, "FOLD_BASE"); l_setconstant(L, SC_FOLDLEVELWHITEFLAG, "FOLD_BLANK"); l_setconstant(L, SC_FOLDLEVELHEADERFLAG, "FOLD_HEADER"); l_setmetatable(L, "sci_lexer", llexer_property); if (*theme) { // Load the theme. if (!(strstr(theme, "/") || strstr(theme, "\\"))) { // theme name lua_pushstring(L, home); lua_pushstring(L, "/themes/"); lua_pushstring(L, theme); lua_pushstring(L, ".lua"); lua_concat(L, 4); } else lua_pushstring(L, theme); // path to theme if (luaL_loadfile(L, lua_tostring(L, -1)) != LUA_OK || lua_pcall(L, 0, 0, 0) != LUA_OK) return (l_error(L), false); lua_pop(L, 1); // theme } // Restore `package.path`. lua_getglobal(L, "package"); lua_getfield(L, -1, "path"), lua_setfield(L, -3, "path"); // lexer.path = lua_rawgeti(L, LUA_REGISTRYINDEX, orig_path), lua_setfield(L, -2, "path"); luaL_unref(L, LUA_REGISTRYINDEX, orig_path), lua_pop(L, 1); // package } else lua_remove(L, -2); // _LOADED // Load the language lexer. lua_getfield(L, -1, "load"); if (lua_isfunction(L, -1)) { lua_pushstring(L, lexer), lua_pushnil(L), lua_pushboolean(L, 1); if (lua_pcall(L, 3, 1, 0) != LUA_OK) return (l_error(L), false); } else return (l_error(L, "'lexer.load' function not found"), false); lua_getfield(L, LUA_REGISTRYINDEX, "sci_lexers"); lua_pushlightuserdata(L, reinterpret_cast(this)); lua_pushvalue(L, -3), lua_settable(L, -3), lua_pop(L, 1); // sci_lexers lua_pushvalue(L, -1), lua_setfield(L, LUA_REGISTRYINDEX, "sci_lexer_obj"); lua_remove(L, -2); // lexer module if (!SetStyles()) return false; // If the lexer is a parent, it will have children in its _CHILDREN table. lua_getfield(L, -1, "_CHILDREN"); if (lua_istable(L, -1)) { multilang = true; // Determine which styles are language whitespace styles // ([lang]_whitespace). This is necessary for determining which language // to start lexing with. char style_name[50]; for (int i = 0; i <= STYLE_MAX; i++) { PrivateCall(i, reinterpret_cast(style_name)); ws[i] = strstr(style_name, "whitespace") ? true : false; } } lua_pop(L, 2); // _CHILDREN and lexer object reinit = false; props.Set("lexer.lpeg.error", "", strlen("lexer.lpeg.error"), 0); return true; } /** * When *lparam* is `0`, returns the size of the buffer needed to store the * given string *str* in; otherwise copies *str* into the buffer *lparam* and * returns the number of bytes copied. * @param lparam `0` to get the number of bytes needed to store *str* or a * pointer to a buffer large enough to copy *str* into. * @param str The string to copy. * @return number of bytes needed to hold *str* */ void *StringResult(long lparam, const char *str) { if (lparam) strcpy(reinterpret_cast(lparam), str); return reinterpret_cast(strlen(str)); } public: /** Constructor. */ LexerLPeg() : own_lua(true), reinit(true), multilang(false) { // Initialize the Lua state, load libraries, and set platform variables. if ((L = luaL_newstate())) { l_openlib(luaopen_base, LUA_BASELIBNAME); l_openlib(luaopen_table, LUA_TABLIBNAME); l_openlib(luaopen_string, LUA_STRLIBNAME); #if LUA_VERSION_NUM < 502 l_openlib(luaopen_io, LUA_IOLIBNAME); // for `package.searchpath()` #endif l_openlib(luaopen_package, LUA_LOADLIBNAME); #ifdef LINK_LPEG l_openlib(luaopen_lpeg, "lpeg"); #endif #if _WIN32 lua_pushboolean(L, 1), lua_setglobal(L, "WIN32"); #endif #if __APPLE__ lua_pushboolean(L, 1), lua_setglobal(L, "OSX"); #endif #if GTK lua_pushboolean(L, 1), lua_setglobal(L, "GTK"); #endif #if CURSES lua_pushboolean(L, 1), lua_setglobal(L, "CURSES"); #endif lua_newtable(L), lua_setfield(L, LUA_REGISTRYINDEX, "sci_lexers"); } else fprintf(stderr, "Lua failed to initialize.\n"); SS = NULL, sci = 0; } /** Destructor. */ virtual ~LexerLPeg() {} /** Destroys the lexer object. */ virtual void SCI_METHOD Release() { if (own_lua && L) lua_close(L); else if (!own_lua) { lua_getfield(L, LUA_REGISTRYINDEX, "sci_lexers"); lua_pushlightuserdata(L, reinterpret_cast(this)); lua_pushnil(L), lua_settable(L, -3), lua_pop(L, 1); // sci_lexers } L = NULL; delete this; } /** * Lexes the Scintilla document. * @param startPos The position in the document to start lexing at. * @param lengthDoc The number of bytes in the document to lex. * @param initStyle The initial style at position *startPos* in the document. * @param buffer The document interface. */ virtual void SCI_METHOD Lex(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyle, IDocument *buffer) { LexAccessor styler(buffer); if ((reinit && !Init()) || !L) { // Style everything in the default style. styler.StartAt(startPos); styler.StartSegment(startPos); styler.ColourTo(startPos + lengthDoc - 1, STYLE_DEFAULT); styler.Flush(); return; } lua_pushlightuserdata(L, reinterpret_cast(&props)); lua_setfield(L, LUA_REGISTRYINDEX, "sci_props"); lua_pushlightuserdata(L, reinterpret_cast(buffer)); lua_setfield(L, LUA_REGISTRYINDEX, "sci_buffer"); // Ensure the lexer has a grammar. // This could be done in the lexer module's `lex()`, but for large files, // passing string arguments from C to Lua is expensive. l_getlexerfield(L, "_GRAMMAR"); int has_grammar = !lua_isnil(L, -1); lua_pop(L, 1); // _GRAMMAR if (!has_grammar) { // Style everything in the default style. styler.StartAt(startPos); styler.StartSegment(startPos); styler.ColourTo(startPos + lengthDoc - 1, STYLE_DEFAULT); styler.Flush(); return; } // Start from the beginning of the current style so LPeg matches it. // For multilang lexers, start at whitespace since embedded languages have // [lang]_whitespace styles. This is so LPeg can start matching child // languages instead of parent ones if necessary. if (startPos > 0) { Sci_PositionU i = startPos; while (i > 0 && styler.StyleAt(i - 1) == initStyle) i--; if (multilang) while (i > 0 && !ws[static_cast(styler.StyleAt(i))]) i--; lengthDoc += startPos - i, startPos = i; } Sci_PositionU startSeg = startPos, endSeg = startPos + lengthDoc; int style = 0; l_getlexerfield(L, "lex") if (lua_isfunction(L, -1)) { l_getlexerobj(L); lua_pushlstring(L, buffer->BufferPointer() + startPos, lengthDoc); lua_pushinteger(L, styler.StyleAt(startPos)); if (lua_pcall(L, 3, 1, 0) != LUA_OK) l_error(L); // Style the text from the token table returned. if (lua_istable(L, -1)) { int len = lua_rawlen(L, -1); if (len > 0) { styler.StartAt(startPos); styler.StartSegment(startPos); l_getlexerfield(L, "_TOKENSTYLES"); // Loop through token-position pairs. for (int i = 1; i < len; i += 2) { style = STYLE_DEFAULT; lua_rawgeti(L, -2, i), lua_rawget(L, -2); // _TOKENSTYLES[token] if (!lua_isnil(L, -1)) style = lua_tointeger(L, -1); lua_pop(L, 1); // _TOKENSTYLES[token] lua_rawgeti(L, -2, i + 1); // pos unsigned int position = lua_tointeger(L, -1) - 1; lua_pop(L, 1); // pos if (style >= 0 && style <= STYLE_MAX) styler.ColourTo(startSeg + position - 1, style); else l_error(L, "Bad style number"); if (position > endSeg) break; } lua_pop(L, 2); // _TOKENSTYLES and token table returned styler.ColourTo(endSeg - 1, style); styler.Flush(); } } else l_error(L, "Table of tokens expected from 'lexer.lex'"); } else l_error(L, "'lexer.lex' function not found"); } /** * Folds the Scintilla document. * @param startPos The position in the document to start folding at. * @param lengthDoc The number of bytes in the document to fold. * @param initStyle The initial style at position *startPos* in the document. * @param buffer The document interface. */ virtual void SCI_METHOD Fold(Sci_PositionU startPos, Sci_Position lengthDoc, int, IDocument *buffer) { if ((reinit && !Init()) || !L) return; lua_pushlightuserdata(L, reinterpret_cast(&props)); lua_setfield(L, LUA_REGISTRYINDEX, "sci_props"); lua_pushlightuserdata(L, reinterpret_cast(buffer)); lua_setfield(L, LUA_REGISTRYINDEX, "sci_buffer"); LexAccessor styler(buffer); l_getlexerfield(L, "fold"); if (lua_isfunction(L, -1)) { l_getlexerobj(L); Sci_Position currentLine = styler.GetLine(startPos); lua_pushlstring(L, buffer->BufferPointer() + startPos, lengthDoc); lua_pushinteger(L, startPos); lua_pushinteger(L, currentLine); lua_pushinteger(L, styler.LevelAt(currentLine) & SC_FOLDLEVELNUMBERMASK); if (lua_pcall(L, 5, 1, 0) != LUA_OK) l_error(L); // Fold the text from the fold table returned. if (lua_istable(L, -1)) { lua_pushnil(L); while (lua_next(L, -2)) { // line = level styler.SetLevel(lua_tointeger(L, -2), lua_tointeger(L, -1)); lua_pop(L, 1); // level } lua_pop(L, 1); // fold table returned } else l_error(L, "Table of folds expected from 'lexer.fold'"); } else l_error(L, "'lexer.fold' function not found"); } /** This lexer implements the original lexer interface. */ virtual int SCI_METHOD Version() const { return lvOriginal; } /** Returning property names is not implemented. */ virtual const char * SCI_METHOD PropertyNames() { return ""; } /** Returning property types is not implemented. */ virtual int SCI_METHOD PropertyType(const char *) { return 0; } /** Returning property descriptions is not implemented. */ virtual const char * SCI_METHOD DescribeProperty(const char *) { return ""; } /** * Sets the *key* lexer property to *value*. * If *key* starts with "style.", also set the style for the token. * @param key The string keyword. * @param val The string value. */ virtual Sci_Position SCI_METHOD PropertySet(const char *key, const char *value) { props.Set(key, value, strlen(key), strlen(value)); if (reinit) Init(); else if (L && SS && sci && strncmp(key, "style.", 6) == 0) { lua_pushlightuserdata(L, reinterpret_cast(&props)); lua_setfield(L, LUA_REGISTRYINDEX, "sci_props"); l_getlexerfield(L, "_TOKENSTYLES"); lua_pushstring(L, key + 6), lua_rawget(L, -2); lua_pushstring(L, key), lL_getexpanded(L, -1), lua_replace(L, -2); if (lua_isnumber(L, -2)) { int style_num = lua_tointeger(L, -2); SetStyle(style_num, lua_tostring(L, -1)); if (style_num == STYLE_DEFAULT) // Assume a theme change, with the default style being set first. // Subsequent style settings will be based on the default. SS(sci, SCI_STYLECLEARALL, 0, 0); } lua_pop(L, 3); // style, style number, _TOKENSTYLES } return -1; // no need to re-lex } /** Returning keyword list descriptions is not implemented. */ virtual const char * SCI_METHOD DescribeWordListSets() { return ""; } /** Setting keyword lists is not applicable. */ virtual Sci_Position SCI_METHOD WordListSet(int, const char *) { return -1; } /** * Allows for direct communication between the application and the lexer. * The application uses this to set `SS`, `sci`, `L`, and lexer properties, * and to retrieve style names. * @param code The communication code. * @param arg The argument. * @return void *data */ virtual void * SCI_METHOD PrivateCall(int code, void *arg) { sptr_t lParam = reinterpret_cast(arg); const char *val = NULL; switch(code) { case SCI_GETDIRECTFUNCTION: SS = reinterpret_cast(lParam); return NULL; case SCI_SETDOCPOINTER: sci = lParam; return NULL; case SCI_CHANGELEXERSTATE: if (own_lua) lua_close(L); L = reinterpret_cast(lParam); lua_getfield(L, LUA_REGISTRYINDEX, "sci_lexers"); if (lua_isnil(L, -1)) lua_newtable(L), lua_setfield(L, LUA_REGISTRYINDEX, "sci_lexers"); lua_pop(L, 1); // sci_lexers or nil own_lua = false; return NULL; case SCI_SETLEXERLANGUAGE: char lexer_name[50]; props.GetExpanded("lexer.name", lexer_name); if (strcmp(lexer_name, reinterpret_cast(arg)) != 0) { reinit = true; props.Set("lexer.lpeg.error", "", strlen("lexer.lpeg.error"), 0); PropertySet("lexer.name", reinterpret_cast(arg)); } else if (L) own_lua ? SetStyles() : Init(); return NULL; case SCI_GETLEXERLANGUAGE: if (L) { l_getlexerfield(L, "_NAME"); if (SS && sci && multilang) { int pos = SS(sci, SCI_GETCURRENTPOS, 0, 0); while (pos >= 0 && !ws[SS(sci, SCI_GETSTYLEAT, pos, 0)]) pos--; const char *name = NULL, *p = NULL; if (pos >= 0) { name = GetStyleName(SS(sci, SCI_GETSTYLEAT, pos, 0)); if (name) p = strstr(name, "_whitespace"); } if (!name) name = lua_tostring(L, -1); // "lexer:lexer" fallback if (!p) p = name + strlen(name); // "lexer:lexer" fallback lua_pushstring(L, "/"); lua_pushlstring(L, name, p - name); lua_concat(L, 3); } val = lua_tostring(L, -1); lua_pop(L, 1); // lexer_name or lexer language string } return StringResult(lParam, val ? val : "null"); case SCI_GETSTATUS: return StringResult(lParam, props.Get("lexer.lpeg.error")); default: // style-related if (code >= 0 && code <= STYLE_MAX) { // retrieve style names val = GetStyleName(code); return StringResult(lParam, val ? val : "Not Available"); } else return NULL; } } /** Constructs a new instance of the lexer. */ static ILexer *LexerFactoryLPeg() { return new LexerLPeg(); } }; LexerModule lmLPeg(SCLEX_LPEG, LexerLPeg::LexerFactoryLPeg, "lpeg"); #else #include #include #include "ILexer.h" #include "Scintilla.h" #include "SciLexer.h" #include "WordList.h" #include "LexAccessor.h" #include "Accessor.h" #include "LexerModule.h" using namespace Scintilla; static void LPegLex(Sci_PositionU, Sci_Position, int, WordList*[], Accessor&) { return; } LexerModule lmLPeg(SCLEX_LPEG, LPegLex, "lpeg"); #endif // LPEG_LEXER