diff options
| -rw-r--r-- | lexers/LexLPeg.cxx | 215 | ||||
| -rw-r--r-- | lexlua/lexer.lua | 46 |
2 files changed, 155 insertions, 106 deletions
diff --git a/lexers/LexLPeg.cxx b/lexers/LexLPeg.cxx index 33c60f75d..6d5167387 100644 --- a/lexers/LexLPeg.cxx +++ b/lexers/LexLPeg.cxx @@ -17,6 +17,8 @@ #include <curses.h> #endif +#include <vector> + #include "ILexer.h" #include "Scintilla.h" #include "SciLexer.h" @@ -47,7 +49,8 @@ using namespace Scintilla; (lua_pushcfunction(l, f), lua_pushstring(l, s), lua_call(l, 1, 1)) #define lua_rawlen lua_objlen #define LUA_OK 0 -#define lua_rawgetp(l, i, p) (lua_pushlightuserdata(l, p), lua_rawget(l, i)) +#define lua_rawgetp(l, i, p) \ + (lua_pushlightuserdata(l, p), lua_rawget(l, i), lua_type(l, -1)) #define lua_rawsetp(l, i, p) \ (lua_pushlightuserdata(l, p), lua_insert(l, -2), lua_rawset(l, i)) #endif @@ -113,6 +116,7 @@ static int lexer_property_newindex(lua_State *L) { if (strcmp(property, "property") == 0) { lua_getfield(L, -1, "_PROPS"); auto props = static_cast<PropSetSimple *>(lua_touserdata(L, -1)); + // TODO: ideally would use lexer->PropertySet(). props->Set( luaL_checkstring(L, 2), luaL_checkstring(L, 3), lua_rawlen(L, 2), lua_rawlen(L, 3)); @@ -184,6 +188,12 @@ static void expand_property(lua_State *L) { /** The LPeg Scintilla lexer. */ class LexerLPeg : public ILexer { + // Lexer property keys. + const char * const LexerErrorKey = "lexer.lpeg.error"; + const char * const LexerHomeKey = "lexer.lpeg.home"; + const char * const LexerNameKey = "lexer.name"; + const char * const LexerThemeKey = "lexer.lpeg.color.theme"; + /** * The lexer's Lua state. * It is cleared each time the lexer language changes unless `own_lua` is @@ -196,8 +206,8 @@ class LexerLPeg : public ILexer { bool own_lua = true; /** * 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. + * The LexerHomeKey and LexerNameKey properties must be defined before running + * the lexer. */ PropSetSimple props; /** The function to send Scintilla messages with. */ @@ -223,15 +233,14 @@ class LexerLPeg : public ILexer { /** * 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. + * Error messages are logged to the LexerErrorKey property. * @param L The Lua State. * @param str The error message to log and print. If `nullptr`, logs and * prints the Lua error message at the top of the stack. */ void log_error(lua_State *L, const char *str = nullptr) { - const char *key = "lexer.lpeg.error"; const char *value = str ? str : lua_tostring(L, -1); - props.Set(key, value, strlen(key), strlen(value)); + PropertySet(LexerErrorKey, value); fprintf(stderr, "Lua Error: %s.\n", value); lua_settop(L, 0); } @@ -343,9 +352,7 @@ class LexerLPeg : public ILexer { 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)); + PropertySet(lua_tostring(L, -1), lua_tostring(L, -2)); lua_pop(L, 1); // style name } lua_pop(L, 1); // value @@ -403,15 +410,17 @@ class LexerLPeg : public ILexer { } /** - * Initializes the lexer once the `lexer.lpeg.home` and `lexer.name` - * properties are set. + * Initializes the lexer once the LexerHomeKey and LexerNameKey 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; + if (!props.GetExpanded(LexerHomeKey, nullptr) || + !*props.Get(LexerNameKey) || !L) + return false; + char *home = reinterpret_cast<char *>( + malloc(props.GetExpanded(LexerHomeKey, nullptr) + 1)); + props.GetExpanded(LexerHomeKey, home); + const char *lexer = props.Get(LexerNameKey); RECORD_STACK_TOP(L); // Designate the currently running LexerLPeg instance. @@ -422,48 +431,61 @@ class LexerLPeg : public ILexer { lua_pushlightuserdata(L, reinterpret_cast<void *>(this)); lua_setfield(L, LUA_REGISTRYINDEX, "sci_lexer_lpeg"); - // If necessary, load the lexer module and theme. + // Determine where to look for the lexer module and themes. + std::vector<char *> dirs; + dirs.push_back(home); + for (char *p = strstr(home, ";"); p; p = strstr(p, ";")) { + *p++ = '\0'; + dirs.push_back(p); + } + + // If necessary, load the lexer module. lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); if (lua_getfield(L, -1, "lexer") == LUA_TNIL) { - 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_pushcfunction(L, lua_error_handler); - lua_getglobal(L, "require"); - lua_pushstring(L, "lexer"); - if (lua_pcall(L, 1, 1, -3) != LUA_OK) return (log_error(L), false); + for (char *dir : dirs) { + lua_pushstring(L, dir); + lua_pushstring(L, "/lexer.lua"); + lua_concat(L, 2); + int status = luaL_loadfile(L, lua_tostring(L, -1)); + if (status == LUA_ERRFILE) { + lua_pop(L, 2); // error message, filename + continue; // try next directory + } + lua_remove(L, -2); // filename + lua_pushcfunction(L, lua_error_handler); + lua_insert(L, -2); + if (status == LUA_OK && lua_pcall(L, 0, 1, -2) == LUA_OK) break; + return (free(home), log_error(L), false); + } lua_remove(L, -2); // lua_error_handler + lua_replace(L, -2); // nil lua_pushinteger(L, SC_FOLDLEVELBASE); lua_setfield(L, -2, "FOLD_BASE"); lua_pushinteger(L, SC_FOLDLEVELWHITEFLAG); lua_setfield(L, -2, "FOLD_BLANK"); lua_pushinteger(L, SC_FOLDLEVELHEADERFLAG); lua_setfield(L, -2, "FOLD_HEADER"); - if (luaL_newmetatable(L, "sci_lexer")) { - lua_pushcfunction(L, lexer_index), lua_setfield(L, -2, "__index"); - lua_pushcfunction(L, lexer_newindex), lua_setfield(L, -2, "__newindex"); - } + lua_createtable(L, 0, 2); + lua_pushcfunction(L, lexer_index), lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, lexer_newindex), lua_setfield(L, -2, "__newindex"); lua_setmetatable(L, -2); - - // 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 + lua_pushvalue(L, -1), lua_setfield(L, -3, "lexer"); + } + lua_replace(L, -2); + // Update the userdata needed by lexer metamethods. + lua_pushlightuserdata(L, reinterpret_cast<void *>(&props)); + lua_setfield(L, -2, "_PROPS"); + lua_pushvalue(L, -1); + lua_rawsetp(L, LUA_REGISTRYINDEX, reinterpret_cast<void *>(this)); // Load the language lexer. if (lua_getfield(L, -1, "load") != LUA_TFUNCTION) - return (log_error(L, "'lexer.load' function not found"), false); + return ( + free(home), log_error(L, "'lexer.load' function not found"), false); lua_pushcfunction(L, lua_error_handler), lua_insert(L, -2); lua_pushstring(L, lexer), lua_pushnil(L), lua_pushboolean(L, 1); - if (lua_pcall(L, 3, 1, -5) != LUA_OK) return (log_error(L), false); + if (lua_pcall(L, 3, 1, -5) != LUA_OK) + return (free(home), log_error(L), false); lua_remove(L, -2); // lua_error_handler lua_remove(L, -2); // lexer module lua_pushlightuserdata(L, reinterpret_cast<void *>(&props)); @@ -471,19 +493,33 @@ class LexerLPeg : public ILexer { lua_rawsetp(L, LUA_REGISTRYINDEX, reinterpret_cast<void *>(this)); // Load the theme and set up styles. - if (*theme) { - lua_pushcfunction(L, lua_error_handler); - 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); + if (props.GetExpanded(LexerThemeKey, nullptr)) { + char *theme = reinterpret_cast<char *>( + malloc(props.GetExpanded(LexerThemeKey, nullptr) + 1)); + props.GetExpanded(LexerThemeKey, theme); + if (!strstr(theme, "/") && !strstr(theme, "\\")) { // theme name + for (char *dir : dirs) { + lua_pushstring(L, dir); + lua_pushstring(L, "/themes/"); + lua_pushstring(L, theme); + lua_pushstring(L, ".lua"); + lua_concat(L, 4); + if (luaL_loadfile(L, lua_tostring(L, -1)) != LUA_ERRFILE || + dir == dirs.back()) { + lua_pop(L, 1); // function, leaving filename on top + break; + } + lua_pop(L, 2); // error message, filename + } } else lua_pushstring(L, theme); // path to theme - if (luaL_loadfile(L, lua_tostring(L, -1)) != LUA_OK || - lua_pcall(L, 0, 0, -2) != LUA_OK) - return (log_error(L), false); - lua_pop(L, 2); // theme, lua_error_handler + lua_pushcfunction(L, lua_error_handler); + lua_insert(L, -2); + if (luaL_loadfile(L, lua_tostring(L, -1)) == LUA_OK && + lua_pcall(L, 0, 0, -3) == LUA_OK) + lua_pop(L, 2); // theme, lua_error_handler + else + log_error(L); + free(theme); } SetStyles(); @@ -503,7 +539,8 @@ class LexerLPeg : public ILexer { lua_pop(L, 2); // _CHILDREN, lexer object reinit = false; - props.Set("lexer.lpeg.error", "", strlen("lexer.lpeg.error"), 0); + PropertySet(LexerErrorKey, ""); + free(home); ASSERT_STACK_TOP(L); return true; } @@ -537,12 +574,8 @@ public: #endif luaL_requiref(L, LUA_TABLIBNAME, luaopen_table, 1), lua_pop(L, 1); luaL_requiref(L, LUA_STRLIBNAME, luaopen_string, 1), lua_pop(L, 1); -#if LUA_VERSION_NUM < 502 - // `package.searchpath()` emulation requires io. - luaL_requiref(L, LUA_IOLIBNAME, luaopen_io, 1), lua_pop(L, 1); -#endif - luaL_requiref(L, LUA_LOADLIBNAME, luaopen_package, 1), lua_pop(L, 1); - luaL_requiref(L, "lpeg", luaopen_lpeg, 1), lua_pop(L, 1); + // TODO: figure out why lua_setglobal() is needed for lpeg. + luaL_requiref(L, "lpeg", luaopen_lpeg, 1), lua_setglobal(L, "lpeg"); #if _WIN32 lua_pushboolean(L, 1), lua_setglobal(L, "WIN32"); #endif @@ -726,25 +759,29 @@ public: const char *key, const char *value) override { props.Set(key, value, strlen(key), strlen(value)); - if (reinit) + if (reinit && + (strcmp(key, LexerHomeKey) == 0 || strcmp(key, LexerNameKey) == 0)) Init(); else if (L && SS && sci && strncmp(key, "style.", 6) == 0) { + // The container is managing styles manually. RECORD_STACK_TOP(L); lua_pushlightuserdata(L, reinterpret_cast<void *>(this)); lua_setfield(L, LUA_REGISTRYINDEX, "sci_lexer_lpeg"); - lua_rawgetp(L, LUA_REGISTRYINDEX, reinterpret_cast<void *>(this)); - lua_getfield(L, -1, "_TOKENSTYLES"); - lua_pushstring(L, key + 6); - if (lua_rawget(L, -2) == LUA_TNUMBER) { - lua_pushstring(L, key), expand_property(L); - 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); + if (lua_rawgetp(L, LUA_REGISTRYINDEX, reinterpret_cast<void *>(this))) { + lua_getfield(L, -1, "_TOKENSTYLES"); + lua_pushstring(L, key + 6); + if (lua_rawget(L, -2) == LUA_TNUMBER) { + lua_pushstring(L, key), expand_property(L); + 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 } - lua_pop(L, 4); // style, style number, _TOKENSTYLES, lexer object + lua_pop(L, 1); // lexer object or nil ASSERT_STACK_TOP(L); } return -1; // no need to re-lex @@ -776,13 +813,27 @@ public: if (own_lua) lua_close(L); L = reinterpret_cast<lua_State *>(lParam), own_lua = false; return nullptr; - case SCI_SETLEXERLANGUAGE: - char lexer_name[50]; - props.GetExpanded("lexer.name", lexer_name); - if (strcmp(lexer_name, reinterpret_cast<const char *>(arg)) != 0) { + case SCI_LOADLEXERLIBRARY: { + const char *path = reinterpret_cast<const char*>(arg); + const char *old_home = props.Get(LexerHomeKey); + char *home = reinterpret_cast<char *>( + malloc(strlen(old_home) + 1 + strlen(path) + 1)); + char *p = home; + if (*old_home) { + strcpy(p, old_home), p += strlen(old_home); + *p++ = ';'; + } + strcpy(p, path); + PropertySet(LexerHomeKey, home); + free(home); + return nullptr; + } case SCI_SETLEXERLANGUAGE: + if (strcmp( + props.Get(LexerNameKey), + reinterpret_cast<const char *>(arg)) != 0) { reinit = true; - props.Set("lexer.lpeg.error", "", strlen("lexer.lpeg.error"), 0); - PropertySet("lexer.name", reinterpret_cast<const char *>(arg)); + PropertySet(LexerErrorKey, ""); + PropertySet(LexerNameKey, reinterpret_cast<const char *>(arg)); } else if (L) own_lua ? SetStyles() : static_cast<void>(Init()); return nullptr; @@ -810,7 +861,7 @@ public: ASSERT_STACK_TOP(L); return StringResult(lParam, val); } case SCI_GETSTATUS: - return StringResult(lParam, props.Get("lexer.lpeg.error")); + return StringResult(lParam, props.Get(LexerErrorKey)); default: // retrieve style names if (code < 0 || code > STYLE_MAX) return nullptr; const char *val = L ? GetStyleName(code) : nullptr; diff --git a/lexlua/lexer.lua b/lexlua/lexer.lua index b51f73bb4..658f7f004 100644 --- a/lexlua/lexer.lua +++ b/lexlua/lexer.lua @@ -772,10 +772,6 @@ local M = {} -- that inspired me, and thanks to Roberto Ierusalimschy for LPeg. -- -- [lexer post]: http://lua-users.org/lists/lua-l/2007-04/msg00116.html --- @field path (string) --- The path used to search for a lexer to load. --- Identical in format to Lua's `package.path` string. --- The default value is `package.path`. -- @field DEFAULT (string) -- The token name for default tokens. -- @field WHITESPACE (string) @@ -948,31 +944,31 @@ local M = {} -- Table of style names at positions in the buffer starting from 1. module('lexer')]=] +if not require then + -- Substitute for Lua's require() function, which does not require the package + -- module to be loaded. + -- Note: all modules must be in the global namespace, which is the case in + -- LexerLPeg's default Lua State. + function require(name) return name == 'lexer' and M or _G[name] end +end + local lpeg = require('lpeg') local lpeg_P, lpeg_R, lpeg_S, lpeg_V = lpeg.P, lpeg.R, lpeg.S, lpeg.V local lpeg_Ct, lpeg_Cc, lpeg_Cp = lpeg.Ct, lpeg.Cc, lpeg.Cp local lpeg_Cmt, lpeg_C = lpeg.Cmt, lpeg.C local lpeg_match = lpeg.match -M.path = package.path - -if not package.searchpath then - -- Searches for the given *name* in the given *path*. - -- This is an implementation of Lua 5.2's `package.searchpath()` function for - -- Lua 5.1. - function package.searchpath(name, path) - local tried = {} - for part in path:gmatch('[^;]+') do - local filename = part:gsub('%?', name) - local f = io.open(filename, 'r') - if f then - f:close() - return filename - end - tried[#tried + 1] = string.format("no file '%s'", filename) - end - return nil, table.concat(tried, '\n') +-- Searches for the given *name* in the given *path*. +-- This is a safe implementation of Lua 5.2's `package.searchpath()` function +-- that does not require the package module to be loaded. +local function searchpath(name, path) + local tried = {} + for part in path:gmatch('[^;]+') do + local filename = part:gsub('%?', name) + if loadfile(filename) then return filename end + tried[#tried + 1] = string.format("no file '%s'", filename) end + return nil, table.concat(tried, '\n') end local string_upper = string.upper @@ -1540,7 +1536,8 @@ function M.load(name, alt_name, cache) -- `property_int` tables do not exist (they are not useful). Create them in -- order prevent errors from occurring. if not M.property then - M.property, M.property_int = {}, setmetatable({}, { + M.property = {['lexer.lpeg.home'] = package.path:gsub('/%?%.lua', '')} + M.property_int = setmetatable({}, { __index = function(t, k) return tonumber(M.property[k]) or 0 end, __newindex = function() error('read-only property') end }) @@ -1553,7 +1550,8 @@ function M.load(name, alt_name, cache) -- loading embedded lexers changes `WHITESPACE` again, so when adding it -- later, do not reference the potentially incorrect value. M.WHITESPACE = (alt_name or name)..'_whitespace' - local lexer = dofile(assert(package.searchpath(name, M.path))) + local path = M.property['lexer.lpeg.home']:gsub(';', '/?.lua;')..'/?.lua' + local lexer = dofile(assert(searchpath(name, path))) assert(lexer, string.format("'%s.lua' did not return a lexer", name)) if alt_name then lexer._NAME = alt_name end if not getmetatable(lexer) or lexer._LEGACY then |
