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  | 
