diff options
Diffstat (limited to 'lexers/LexLPeg.cxx')
-rw-r--r-- | lexers/LexLPeg.cxx | 795 |
1 files changed, 795 insertions, 0 deletions
diff --git a/lexers/LexLPeg.cxx b/lexers/LexLPeg.cxx new file mode 100644 index 000000000..9a44a3cdd --- /dev/null +++ b/lexers/LexLPeg.cxx @@ -0,0 +1,795 @@ +/** + * Copyright 2006-2018 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 <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <assert.h> +#include <ctype.h> +#if CURSES +#include <curses.h> +#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" +LUALIB_API int luaopen_lpeg(lua_State *L); +} + +#if _WIN32 +#define strcasecmp _stricmp +#endif +#define streq(s1, s2) (strcasecmp((s1), (s2)) == 0) + +#if SCI_NAMESPACE +using namespace Scintilla; +#endif + +#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<void *>(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. + * For use with SciTE, all of the style property strings generated for the + * current lexer are placed in here. + */ + 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<PropSetSimple *>(lua_touserdata(L, -1)); + lua_pop(L, 1); // props + props->Set("lexer.lpeg.error", str ? str : lua_tostring(L, -1)); + 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<IDocument *>(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<IDocument *>(lua_touserdata(L, -1)); + lua_getfield(L, LUA_REGISTRYINDEX, "sci_props"); + PropSetSimple *props = static_cast<PropSetSimple *>(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)); + } 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<char *>(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<sptr_t>(p)); + else if (streq(option, "size") && p) + SS(sci, SCI_STYLESETSIZE, num, static_cast<int>(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<int>(strtol(p, NULL, 0)); + if (*p == '#') { // #RRGGBB format; Scintilla format is 0xBBGGRR + color = static_cast<int>(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<int>(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, or for SciTE, generates the set of style properties + * instead of directly setting style properties. + */ + 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_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<void *>(&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<void *>(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<void *>(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", ""); + 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<char *>(lparam), str); + return reinterpret_cast<void *>(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); + l_openlib(luaopen_lpeg, "lpeg"); +#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<void *>(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<void *>(&props)); + lua_setfield(L, LUA_REGISTRYINDEX, "sci_props"); + lua_pushlightuserdata(L, reinterpret_cast<void *>(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<size_t>(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 initStyle, IDocument *buffer) { + if ((reinit && !Init()) || !L) return; + lua_pushlightuserdata(L, reinterpret_cast<void *>(&props)); + lua_setfield(L, LUA_REGISTRYINDEX, "sci_props"); + lua_pushlightuserdata(L, reinterpret_cast<void *>(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"); + } + + /** Returning the version of the lexer is not implemented. */ + virtual int SCI_METHOD Version() const { return 0; } + /** 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 *name) { return 0; } + /** Returning property descriptions is not implemented. */ + virtual const char * SCI_METHOD DescribeProperty(const char *name) { + 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 ? value : " "); // ensure property is cleared + if (reinit) + Init(); + else if (L && SS && sci && strncmp(key, "style.", 6) == 0) { + 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)) + SetStyle(lua_tointeger(L, -2), lua_tostring(L, -1)); + 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 n, const char *wl) { + 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<sptr_t>(arg); + const char *val = NULL; + switch(code) { + case SCI_GETDIRECTFUNCTION: + SS = reinterpret_cast<SciFnDirect>(lParam); + return NULL; + case SCI_SETDOCPOINTER: + sci = lParam; + return NULL; + case SCI_CHANGELEXERSTATE: + if (own_lua) lua_close(L); + L = reinterpret_cast<lua_State *>(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<const char *>(arg)) != 0) { + reinit = true; + props.Set("lexer.lpeg.error", ""); + PropertySet("lexer.name", reinterpret_cast<const char *>(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 <stdlib.h> +#include <assert.h> + +#include "ILexer.h" +#include "Scintilla.h" +#include "SciLexer.h" + +#include "WordList.h" +#include "LexAccessor.h" +#include "Accessor.h" +#include "LexerModule.h" + +#if SCI_NAMESPACE +using namespace Scintilla; +#endif + +static void LPegLex(Sci_PositionU startPos, Sci_Position lengthDoc, + int initStyle, WordList *keywordlists[], Accessor &styler) { + return; +} + +LexerModule lmLPeg(SCLEX_LPEG, LPegLex, "lpeg"); + +#endif // LPEG_LEXER |