804 lines
28 KiB
C++
Executable File
804 lines
28 KiB
C++
Executable File
/**
|
|
* 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 <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"
|
|
#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<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.
|
|
*/
|
|
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
|
|
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<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),
|
|
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<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.
|
|
*/
|
|
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<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", "", 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<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);
|
|
#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<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, 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");
|
|
}
|
|
|
|
/** 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<void *>(&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<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", "", strlen("lexer.lpeg.error"), 0);
|
|
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"
|
|
|
|
using namespace Scintilla;
|
|
|
|
static void LPegLex(Sci_PositionU, Sci_Position, int, WordList*[], Accessor&) {
|
|
return;
|
|
}
|
|
|
|
LexerModule lmLPeg(SCLEX_LPEG, LPegLex, "lpeg");
|
|
|
|
#endif // LPEG_LEXER
|