From c8f861b739b4703b1198dc1f88b09edbeb0d9f2e Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 15 Jun 2019 12:10:12 +0200 Subject: tree-sitter: rename tree_sitter => treesitter for consistency --- src/nvim/lua/treesitter.c | 524 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 524 insertions(+) create mode 100644 src/nvim/lua/treesitter.c (limited to 'src/nvim/lua/treesitter.c') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c new file mode 100644 index 0000000000..794bdc6749 --- /dev/null +++ b/src/nvim/lua/treesitter.c @@ -0,0 +1,524 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// lua bindings for tree-siter. +// NB: this file should contain a generic lua interface for +// tree-sitter trees and nodes, and could be broken out as a reusable library + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "tree_sitter/api.h" + +// NOT state-safe, delete when GC is confimed working: +static int debug_n_trees = 0, debug_n_cursors = 0; + +#define REG_KEY "treesitter-private" + +#include "nvim/lua/treesitter.h" +#include "nvim/api/private/handle.h" +#include "nvim/memline.h" + +typedef struct { + TSParser *parser; + TSTree *tree; +} Tslua_parser; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/treesitter.c.generated.h" +#endif + +static struct luaL_Reg parser_meta[] = { + {"__gc", parser_gc}, + {"__tostring", parser_tostring}, + {"parse_buf", parser_parse_buf}, + {"edit", parser_edit}, + {"tree", parser_tree}, + {NULL, NULL} +}; + +static struct luaL_Reg tree_meta[] = { + {"__gc", tree_gc}, + {"__tostring", tree_tostring}, + {"root", tree_root}, + {NULL, NULL} +}; + +static struct luaL_Reg node_meta[] = { + {"__tostring", node_tostring}, + {"__len", node_child_count}, + {"range", node_range}, + {"start", node_start}, + {"type", node_type}, + {"symbol", node_symbol}, + {"child_count", node_child_count}, + {"child", node_child}, + {"descendant_for_point_range", node_descendant_for_point_range}, + {"parent", node_parent}, + {NULL, NULL} +}; + +PMap(cstr_t) *langs; + +void build_meta(lua_State *L, const luaL_Reg *meta) +{ + // [env, target] + for (size_t i = 0; meta[i].name != NULL; i++) { + lua_pushcfunction(L, meta[i].func); // [env, target, func] + lua_pushvalue(L, -3); // [env, target, func, env] + lua_setfenv(L, -2); // [env, target, func] + lua_setfield(L, -2, meta[i].name); // [env, target] + } + + lua_pushvalue(L, -1); // [env, target, target] + lua_setfield(L, -2, "__index"); // [env, target] +} + + + +/// init the tslua library +/// +/// all global state is stored in the regirstry of the lua_State +void tslua_init(lua_State *L) +{ + + langs = pmap_new(cstr_t)(); + + lua_createtable(L, 0, 0); + + // type metatables + lua_createtable(L, 0, 0); + build_meta(L, parser_meta); + lua_setfield(L, -2, "parser-meta"); + + lua_createtable(L, 0, 0); + build_meta(L, tree_meta); + lua_setfield(L, -2, "tree-meta"); + + lua_createtable(L, 0, 0); + build_meta(L, node_meta); + lua_setfield(L, -2, "node-meta"); + + lua_setfield(L, LUA_REGISTRYINDEX, REG_KEY); + + lua_pushcfunction(L, tslua_debug); + lua_setglobal(L, "_tslua_debug"); +} + +static int tslua_debug(lua_State *L) +{ + lua_pushinteger(L, debug_n_trees); + lua_pushinteger(L, debug_n_cursors); + return 2; +} + + +int ts_lua_register_lang(lua_State *L) +{ + if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { + return luaL_error(L, "string expected"); + } + + const char *path = lua_tostring(L,1); + const char *lang_name = lua_tostring(L,2); + + if (pmap_has(cstr_t)(langs, lang_name)) { + return 0; + } + + // TODO: unsafe! + char symbol_buf[128] = "tree_sitter_"; + STRCAT(symbol_buf, lang_name); + + // TODO: we should maybe keep the uv_lib_t around, and close them + // at exit, to keep LeakSanitizer happy. + uv_lib_t lib; + if (uv_dlopen(path, &lib)) { + return luaL_error(L, "Failed to load parser: uv_dlopen: %s", uv_dlerror(&lib)); + } + + TSLanguage *(*lang_parser)(void); + if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { + return luaL_error(L, "Failed to load parser: uv_dlsym: %s", uv_dlerror(&lib)); + } + + TSLanguage *lang = lang_parser(); + if (lang == NULL) { + return luaL_error(L, "Failed to load parser: internal error"); + } + + pmap_put(cstr_t)(langs, xstrdup(lang_name), lang); + + lua_pushboolean(L, true); + return 1; +} + +int tslua_push_parser(lua_State *L, const char *lang_name) +{ + TSParser *parser = ts_parser_new(); + TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name); + if (!lang) { + return luaL_error(L, "no such language: %s", lang_name); + } + + ts_parser_set_language(parser, lang); + Tslua_parser *p = lua_newuserdata(L, sizeof(Tslua_parser)); // [udata] + p->parser = parser; + p->tree = NULL; + + lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] + lua_getfield(L, -1, "parser-meta"); // [udata, env, meta] + lua_setmetatable(L, -3); // [udata, env] + lua_pop(L, 1); // [udata] + return 1; +} + +static Tslua_parser *parser_check(lua_State *L) +{ + if (!lua_gettop(L)) { + return 0; + } + if (!lua_isuserdata(L, 1)) { + return 0; + } + // TODO: typecheck! + return lua_touserdata(L, 1); +} + +static int parser_gc(lua_State *L) +{ + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + ts_parser_delete(p->parser); + if (p->tree) { + ts_tree_delete(p->tree); + } + + return 0; +} + +static int parser_tostring(lua_State *L) +{ + lua_pushstring(L, ""); + return 1; +} + +static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position, uint32_t *bytes_read) +{ + buf_T *bp = payload; + static char buf[200]; + if ((linenr_T)position.row >= bp->b_ml.ml_line_count) { + *bytes_read = 0; + return ""; + } + char_u *line = ml_get_buf(bp, position.row+1, false); + size_t len = STRLEN(line); + size_t tocopy = MIN(len-position.column,200); + + // TODO: translate embedded \n to \000 + memcpy(buf, line+position.column, tocopy); + *bytes_read = (uint32_t)tocopy; + if (tocopy < 200) { + buf[tocopy] = '\n'; + (*bytes_read)++; + } + return buf; +} + +static int parser_parse_buf(lua_State *L) +{ + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + long bufnr = lua_tointeger(L, 2); + void *payload = handle_get_buffer(bufnr); + TSInput input = {payload, input_cb, TSInputEncodingUTF8}; + TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); + if (p->tree) { + ts_tree_delete(p->tree); + } + p->tree = new_tree; + + tslua_push_tree(L, ts_tree_copy(p->tree)); + return 1; +} + +static int parser_tree(lua_State *L) +{ + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + if (p->tree) { + tslua_push_tree(L, ts_tree_copy(p->tree)); + } else { + lua_pushnil(L); + } + return 1; +} + +static int parser_edit(lua_State *L) +{ + if(lua_gettop(L) < 10) { + lua_pushstring(L, "not enough args to parser:edit()"); + lua_error(L); + return 0; // unreachable + } + + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + if (!p->tree) { + return 0; + } + + long start_byte = lua_tointeger(L, 2); + long old_end_byte = lua_tointeger(L, 3); + long new_end_byte = lua_tointeger(L, 4); + TSPoint start_point = { lua_tointeger(L, 5), lua_tointeger(L, 6) }; + TSPoint old_end_point = { lua_tointeger(L, 7), lua_tointeger(L, 8) }; + TSPoint new_end_point = { lua_tointeger(L, 9), lua_tointeger(L, 10) }; + + TSInputEdit edit = { start_byte, old_end_byte, new_end_byte, + start_point, old_end_point, new_end_point }; + + ts_tree_edit(p->tree, &edit); + + return 0; +} + + +// Tree methods + +/// push tree interface on lua stack. +/// +/// This takes "ownership" of the tree and will free it +/// when the wrapper object is garbage collected +void tslua_push_tree(lua_State *L, TSTree *tree) +{ + TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata] + *ud = tree; + lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] + lua_getfield(L, -1, "tree-meta"); // [udata, env, meta] + lua_setmetatable(L, -3); // [udata, env] + lua_pop(L, 1); // [udata] + + // table used for node wrappers to keep a reference to tree wrapper + // NB: in lua 5.3 the uservalue for the node could just be the tree, but + // in lua 5.1 the uservalue (fenv) must be a table. + lua_createtable(L, 1, 0); // [udata, reftable] + lua_pushvalue(L, -2); // [udata, reftable, udata] + lua_rawseti(L, -2, 1); // [udata, reftable] + lua_setfenv(L, -2); // [udata] + debug_n_trees++; +} + +static TSTree *tree_check(lua_State *L) +{ + if (!lua_gettop(L)) { + return 0; + } + if (!lua_isuserdata(L, 1)) { + return 0; + } + // TODO: typecheck! + TSTree **ud = lua_touserdata(L, 1); + return *ud; +} + +static int tree_gc(lua_State *L) +{ + TSTree *tree = tree_check(L); + if (!tree) { + return 0; + } + + ts_tree_delete(tree); + debug_n_trees--; + return 0; +} + +static int tree_tostring(lua_State *L) +{ + lua_pushstring(L, ""); + return 1; +} + +static int tree_root(lua_State *L) +{ + TSTree *tree = tree_check(L); + if (!tree) { + return 0; + } + TSNode root = ts_tree_root_node(tree); + push_node(L, root); + return 1; +} + +// Node methods + +/// push node interface on lua stack +/// +/// top of stack must either be the tree this node belongs to or another node +/// of the same tree! This value is not popped. Can only be called inside a +/// cfunction with the tslua environment. +static void push_node(lua_State *L, TSNode node) +{ + if (ts_node_is_null(node)) { + lua_pushnil(L); // [src, nil] + return; + } + TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [src, udata] + *ud = node; + lua_getfield(L, LUA_ENVIRONINDEX, "node-meta"); // [src, udata, meta] + lua_setmetatable(L, -2); // [src, udata] + lua_getfenv(L, -2); // [src, udata, reftable] + lua_setfenv(L, -2); // [src, udata] +} + +static bool node_check(lua_State *L, TSNode *res) +{ + if (!lua_gettop(L)) { + return 0; + } + if (!lua_isuserdata(L, 1)) { + return 0; + } + // TODO: typecheck! + TSNode *ud = lua_touserdata(L, 1); + *res = *ud; + return true; +} + + +static int node_tostring(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushstring(L, ""); + lua_concat(L, 3); + return 1; +} + +static int node_range(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint start = ts_node_start_point(node); + TSPoint end = ts_node_end_point(node); + lua_pushnumber(L, start.row); + lua_pushnumber(L, start.column); + lua_pushnumber(L, end.row); + lua_pushnumber(L, end.column); + return 4; +} + +static int node_start(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint start = ts_node_start_point(node); + uint32_t start_byte = ts_node_start_byte(node); + lua_pushnumber(L, start.row); + lua_pushnumber(L, start.column); + lua_pushnumber(L, start_byte); + return 3; +} + +static int node_child_count(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + uint32_t count = ts_node_child_count(node); + lua_pushnumber(L, count); + return 1; +} + +static int node_type(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushstring(L, ts_node_type(node)); + return 1; +} + +static int node_symbol(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSSymbol symbol = ts_node_symbol(node); + lua_pushnumber(L, symbol); + return 1; +} + +static int node_child(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + long num = lua_tointeger(L, 2); + TSNode child = ts_node_child(node, (uint32_t)num); + + lua_pushvalue(L, 1); + push_node(L, child); + return 1; +} + +static int node_descendant_for_point_range(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint start = {(uint32_t)lua_tointeger(L, 2), + (uint32_t)lua_tointeger(L, 3)}; + TSPoint end = {(uint32_t)lua_tointeger(L, 4), + (uint32_t)lua_tointeger(L, 5)}; + TSNode child = ts_node_descendant_for_point_range(node, start, end); + + lua_pushvalue(L, 1); + push_node(L, child); + return 1; +} + +static int node_parent(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSNode parent = ts_node_parent(node); + push_node(L, parent); + return 1; +} + -- cgit From a361e09cc531caf9dfb41bf860ca2d540ac2789d Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 15 Jun 2019 12:50:41 +0200 Subject: tree-sitter: use standard luaL_newmetatable and luaL_checkudata pattern --- src/nvim/lua/treesitter.c | 94 +++++++++++++++-------------------------------- 1 file changed, 30 insertions(+), 64 deletions(-) (limited to 'src/nvim/lua/treesitter.c') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 794bdc6749..8d24d15ccd 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -20,15 +20,13 @@ // NOT state-safe, delete when GC is confimed working: static int debug_n_trees = 0, debug_n_cursors = 0; -#define REG_KEY "treesitter-private" - #include "nvim/lua/treesitter.h" #include "nvim/api/private/handle.h" #include "nvim/memline.h" typedef struct { TSParser *parser; - TSTree *tree; + TSTree *tree; // internal tree, used for editing/reparsing } Tslua_parser; #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -67,18 +65,18 @@ static struct luaL_Reg node_meta[] = { PMap(cstr_t) *langs; -void build_meta(lua_State *L, const luaL_Reg *meta) +void build_meta(lua_State *L, const char* tname, const luaL_Reg *meta) { - // [env, target] - for (size_t i = 0; meta[i].name != NULL; i++) { - lua_pushcfunction(L, meta[i].func); // [env, target, func] - lua_pushvalue(L, -3); // [env, target, func, env] - lua_setfenv(L, -2); // [env, target, func] - lua_setfield(L, -2, meta[i].name); // [env, target] - } + if (luaL_newmetatable(L, tname)) { // [meta] + for (size_t i = 0; meta[i].name != NULL; i++) { + lua_pushcfunction(L, meta[i].func); // [meta, func] + lua_setfield(L, -2, meta[i].name); // [meta] + } - lua_pushvalue(L, -1); // [env, target, target] - lua_setfield(L, -2, "__index"); // [env, target] + lua_pushvalue(L, -1); // [meta, meta] + lua_setfield(L, -2, "__index"); // [meta] + } + lua_pop(L, 1); // [] (don't use it now) } @@ -91,22 +89,12 @@ void tslua_init(lua_State *L) langs = pmap_new(cstr_t)(); - lua_createtable(L, 0, 0); - // type metatables - lua_createtable(L, 0, 0); - build_meta(L, parser_meta); - lua_setfield(L, -2, "parser-meta"); + build_meta(L, "treesitter_parser", parser_meta); - lua_createtable(L, 0, 0); - build_meta(L, tree_meta); - lua_setfield(L, -2, "tree-meta"); + build_meta(L, "treesitter_tree", tree_meta); - lua_createtable(L, 0, 0); - build_meta(L, node_meta); - lua_setfield(L, -2, "node-meta"); - - lua_setfield(L, LUA_REGISTRYINDEX, REG_KEY); + build_meta(L, "treesitter_node", node_meta); lua_pushcfunction(L, tslua_debug); lua_setglobal(L, "_tslua_debug"); @@ -173,23 +161,14 @@ int tslua_push_parser(lua_State *L, const char *lang_name) p->parser = parser; p->tree = NULL; - lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] - lua_getfield(L, -1, "parser-meta"); // [udata, env, meta] - lua_setmetatable(L, -3); // [udata, env] - lua_pop(L, 1); // [udata] + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_parser"); // [udata, meta] + lua_setmetatable(L, -2); // [udata] return 1; } static Tslua_parser *parser_check(lua_State *L) { - if (!lua_gettop(L)) { - return 0; - } - if (!lua_isuserdata(L, 1)) { - return 0; - } - // TODO: typecheck! - return lua_touserdata(L, 1); + return luaL_checkudata(L, 1, "treesitter_parser"); } static int parser_gc(lua_State *L) @@ -313,31 +292,22 @@ void tslua_push_tree(lua_State *L, TSTree *tree) { TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata] *ud = tree; - lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] - lua_getfield(L, -1, "tree-meta"); // [udata, env, meta] - lua_setmetatable(L, -3); // [udata, env] - lua_pop(L, 1); // [udata] + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_tree"); // [udata, meta] + lua_setmetatable(L, -2); // [udata] // table used for node wrappers to keep a reference to tree wrapper // NB: in lua 5.3 the uservalue for the node could just be the tree, but // in lua 5.1 the uservalue (fenv) must be a table. - lua_createtable(L, 1, 0); // [udata, reftable] - lua_pushvalue(L, -2); // [udata, reftable, udata] - lua_rawseti(L, -2, 1); // [udata, reftable] - lua_setfenv(L, -2); // [udata] + lua_createtable(L, 1, 0); // [udata, reftable] + lua_pushvalue(L, -2); // [udata, reftable, udata] + lua_rawseti(L, -2, 1); // [udata, reftable] + lua_setfenv(L, -2); // [udata] debug_n_trees++; } static TSTree *tree_check(lua_State *L) { - if (!lua_gettop(L)) { - return 0; - } - if (!lua_isuserdata(L, 1)) { - return 0; - } - // TODO: typecheck! - TSTree **ud = lua_touserdata(L, 1); + TSTree **ud = luaL_checkudata(L, 1, "treesitter_tree"); return *ud; } @@ -385,7 +355,7 @@ static void push_node(lua_State *L, TSNode node) } TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [src, udata] *ud = node; - lua_getfield(L, LUA_ENVIRONINDEX, "node-meta"); // [src, udata, meta] + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_node"); // [src, udata, meta] lua_setmetatable(L, -2); // [src, udata] lua_getfenv(L, -2); // [src, udata, reftable] lua_setfenv(L, -2); // [src, udata] @@ -393,16 +363,12 @@ static void push_node(lua_State *L, TSNode node) static bool node_check(lua_State *L, TSNode *res) { - if (!lua_gettop(L)) { - return 0; - } - if (!lua_isuserdata(L, 1)) { - return 0; + TSNode *ud = luaL_checkudata(L, 1, "treesitter_node"); + if (ud) { + *res = *ud; + return true; } - // TODO: typecheck! - TSNode *ud = lua_touserdata(L, 1); - *res = *ud; - return true; + return false; } -- cgit From c1dc1bedba4e0e3db2cd2e52d9241991567f8218 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 15 Jun 2019 13:12:59 +0200 Subject: tree-sitter: style --- src/nvim/lua/treesitter.c | 113 ++++++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 55 deletions(-) (limited to 'src/nvim/lua/treesitter.c') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 8d24d15ccd..2337d598b3 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -34,38 +34,38 @@ typedef struct { #endif static struct luaL_Reg parser_meta[] = { - {"__gc", parser_gc}, - {"__tostring", parser_tostring}, - {"parse_buf", parser_parse_buf}, - {"edit", parser_edit}, - {"tree", parser_tree}, - {NULL, NULL} + { "__gc", parser_gc }, + { "__tostring", parser_tostring }, + { "parse_buf", parser_parse_buf }, + { "edit", parser_edit }, + { "tree", parser_tree }, + { NULL, NULL } }; static struct luaL_Reg tree_meta[] = { - {"__gc", tree_gc}, - {"__tostring", tree_tostring}, - {"root", tree_root}, - {NULL, NULL} + { "__gc", tree_gc }, + { "__tostring", tree_tostring }, + { "root", tree_root }, + { NULL, NULL } }; static struct luaL_Reg node_meta[] = { - {"__tostring", node_tostring}, - {"__len", node_child_count}, - {"range", node_range}, - {"start", node_start}, - {"type", node_type}, - {"symbol", node_symbol}, - {"child_count", node_child_count}, - {"child", node_child}, - {"descendant_for_point_range", node_descendant_for_point_range}, - {"parent", node_parent}, - {NULL, NULL} + { "__tostring", node_tostring }, + { "__len", node_child_count }, + { "range", node_range }, + { "start", node_start }, + { "type", node_type }, + { "symbol", node_symbol }, + { "child_count", node_child_count }, + { "child", node_child }, + { "descendant_for_point_range", node_descendant_for_point_range }, + { "parent", node_parent }, + { NULL, NULL } }; PMap(cstr_t) *langs; -void build_meta(lua_State *L, const char* tname, const luaL_Reg *meta) +void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta) { if (luaL_newmetatable(L, tname)) { // [meta] for (size_t i = 0; meta[i].name != NULL; i++) { @@ -86,7 +86,6 @@ void build_meta(lua_State *L, const char* tname, const luaL_Reg *meta) /// all global state is stored in the regirstry of the lua_State void tslua_init(lua_State *L) { - langs = pmap_new(cstr_t)(); // type metatables @@ -114,8 +113,8 @@ int ts_lua_register_lang(lua_State *L) return luaL_error(L, "string expected"); } - const char *path = lua_tostring(L,1); - const char *lang_name = lua_tostring(L,2); + const char *path = lua_tostring(L, 1); + const char *lang_name = lua_tostring(L, 2); if (pmap_has(cstr_t)(langs, lang_name)) { return 0; @@ -129,12 +128,14 @@ int ts_lua_register_lang(lua_State *L) // at exit, to keep LeakSanitizer happy. uv_lib_t lib; if (uv_dlopen(path, &lib)) { - return luaL_error(L, "Failed to load parser: uv_dlopen: %s", uv_dlerror(&lib)); + return luaL_error(L, "Failed to load parser: uv_dlopen: %s", + uv_dlerror(&lib)); } TSLanguage *(*lang_parser)(void); if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { - return luaL_error(L, "Failed to load parser: uv_dlsym: %s", uv_dlerror(&lib)); + return luaL_error(L, "Failed to load parser: uv_dlsym: %s", + uv_dlerror(&lib)); } TSLanguage *lang = lang_parser(); @@ -192,26 +193,29 @@ static int parser_tostring(lua_State *L) return 1; } -static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position, uint32_t *bytes_read) +static const char *input_cb(void *payload, uint32_t byte_index, + TSPoint position, uint32_t *bytes_read) { - buf_T *bp = payload; - static char buf[200]; - if ((linenr_T)position.row >= bp->b_ml.ml_line_count) { - *bytes_read = 0; - return ""; - } - char_u *line = ml_get_buf(bp, position.row+1, false); - size_t len = STRLEN(line); - size_t tocopy = MIN(len-position.column,200); - - // TODO: translate embedded \n to \000 - memcpy(buf, line+position.column, tocopy); - *bytes_read = (uint32_t)tocopy; - if (tocopy < 200) { - buf[tocopy] = '\n'; - (*bytes_read)++; - } - return buf; + buf_T *bp = payload; +#define BUFSIZE 256 + static char buf[BUFSIZE]; + if ((linenr_T)position.row >= bp->b_ml.ml_line_count) { + *bytes_read = 0; + return ""; + } + char_u *line = ml_get_buf(bp, position.row+1, false); + size_t len = STRLEN(line); + size_t tocopy = MIN(len-position.column, BUFSIZE); + + // TODO: translate embedded \n to \000 + memcpy(buf, line+position.column, tocopy); + *bytes_read = (uint32_t)tocopy; + if (tocopy < 200) { + buf[tocopy] = '\n'; + (*bytes_read)++; + } + return buf; +#undef BUFSIZE } static int parser_parse_buf(lua_State *L) @@ -223,7 +227,7 @@ static int parser_parse_buf(lua_State *L) long bufnr = lua_tointeger(L, 2); void *payload = handle_get_buffer(bufnr); - TSInput input = {payload, input_cb, TSInputEncodingUTF8}; + TSInput input = { payload, input_cb, TSInputEncodingUTF8 }; TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); if (p->tree) { ts_tree_delete(p->tree); @@ -251,10 +255,9 @@ static int parser_tree(lua_State *L) static int parser_edit(lua_State *L) { - if(lua_gettop(L) < 10) { + if (lua_gettop(L) < 10) { lua_pushstring(L, "not enough args to parser:edit()"); - lua_error(L); - return 0; // unreachable + return lua_error(L); } Tslua_parser *p = parser_check(L); @@ -350,7 +353,7 @@ static int tree_root(lua_State *L) static void push_node(lua_State *L, TSNode node) { if (ts_node_is_null(node)) { - lua_pushnil(L); // [src, nil] + lua_pushnil(L); // [src, nil] return; } TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [src, udata] @@ -466,10 +469,10 @@ static int node_descendant_for_point_range(lua_State *L) if (!node_check(L, &node)) { return 0; } - TSPoint start = {(uint32_t)lua_tointeger(L, 2), - (uint32_t)lua_tointeger(L, 3)}; - TSPoint end = {(uint32_t)lua_tointeger(L, 4), - (uint32_t)lua_tointeger(L, 5)}; + TSPoint start = { (uint32_t)lua_tointeger(L, 2), + (uint32_t)lua_tointeger(L, 3) }; + TSPoint end = { (uint32_t)lua_tointeger(L, 4), + (uint32_t)lua_tointeger(L, 5) }; TSNode child = ts_node_descendant_for_point_range(node, start, end); lua_pushvalue(L, 1); -- cgit From a88a9f128e29b27315a87d0119fbc649196999bc Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 15 Jun 2019 13:43:30 +0200 Subject: tree-sitter: add some more API --- src/nvim/lua/treesitter.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) (limited to 'src/nvim/lua/treesitter.c') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 2337d598b3..9d599da85f 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -54,11 +54,19 @@ static struct luaL_Reg node_meta[] = { { "__len", node_child_count }, { "range", node_range }, { "start", node_start }, + { "end_", node_end }, { "type", node_type }, { "symbol", node_symbol }, + { "named", node_named }, + { "missing", node_missing }, + { "has_error", node_has_error }, + { "sexpr", node_sexpr }, { "child_count", node_child_count }, + { "named_child_count", node_named_child_count }, { "child", node_child }, + { "named_child", node_named_child }, { "descendant_for_point_range", node_descendant_for_point_range }, + { "named_descendant_for_point_range", node_named_descendant_for_point_range }, { "parent", node_parent }, { NULL, NULL } }; @@ -417,6 +425,20 @@ static int node_start(lua_State *L) return 3; } +static int node_end(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint end = ts_node_end_point(node); + uint32_t end_byte = ts_node_end_byte(node); + lua_pushnumber(L, end.row); + lua_pushnumber(L, end.column); + lua_pushnumber(L, end_byte); + return 3; +} + static int node_child_count(lua_State *L) { TSNode node; @@ -428,6 +450,17 @@ static int node_child_count(lua_State *L) return 1; } +static int node_named_child_count(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + uint32_t count = ts_node_named_child_count(node); + lua_pushnumber(L, count); + return 1; +} + static int node_type(lua_State *L) { TSNode node; @@ -449,6 +482,48 @@ static int node_symbol(lua_State *L) return 1; } +static int node_named(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushboolean(L, ts_node_is_named(node)); + return 1; +} + +static int node_sexpr(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + char *allocated = ts_node_string(node); + lua_pushstring(L, allocated); + xfree(allocated); + return 1; +} + +static int node_missing(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushboolean(L, ts_node_is_missing(node)); + return 1; +} + +static int node_has_error(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushboolean(L, ts_node_has_error(node)); + return 1; +} + static int node_child(lua_State *L) { TSNode node; @@ -463,6 +538,20 @@ static int node_child(lua_State *L) return 1; } +static int node_named_child(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + long num = lua_tointeger(L, 2); + TSNode child = ts_node_named_child(node, (uint32_t)num); + + lua_pushvalue(L, 1); + push_node(L, child); + return 1; +} + static int node_descendant_for_point_range(lua_State *L) { TSNode node; @@ -480,6 +569,23 @@ static int node_descendant_for_point_range(lua_State *L) return 1; } +static int node_named_descendant_for_point_range(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint start = { (uint32_t)lua_tointeger(L, 2), + (uint32_t)lua_tointeger(L, 3) }; + TSPoint end = { (uint32_t)lua_tointeger(L, 4), + (uint32_t)lua_tointeger(L, 5) }; + TSNode child = ts_node_named_descendant_for_point_range(node, start, end); + + lua_pushvalue(L, 1); + push_node(L, child); + return 1; +} + static int node_parent(lua_State *L) { TSNode node; -- cgit From d24dec596c25690aba0aca658546ffdfcc6a952c Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 15 Jun 2019 14:05:35 +0200 Subject: tree-sitter: inspect language --- src/nvim/lua/treesitter.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) (limited to 'src/nvim/lua/treesitter.c') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 9d599da85f..db337db533 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -157,6 +157,51 @@ int ts_lua_register_lang(lua_State *L) return 1; } +int ts_lua_inspect_lang(lua_State *L) +{ + if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) { + return luaL_error(L, "string expected"); + } + const char *lang_name = lua_tostring(L, 1); + + TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name); + if (!lang) { + return luaL_error(L, "no such language: %s", lang_name); + } + + lua_createtable(L, 0, 2); // [retval] + + size_t nsymbols = (size_t)ts_language_symbol_count(lang); + + lua_createtable(L, nsymbols-1, 1); // [retval, symbols] + for (size_t i = 0; i < nsymbols; i++) { + TSSymbolType t = ts_language_symbol_type(lang, i); + if (t == TSSymbolTypeAuxiliary) { + // not used by the API + continue; + } + lua_createtable(L, 2, 0); // [retval, symbols, elem] + lua_pushstring(L, ts_language_symbol_name(lang, i)); + lua_rawseti(L, -2, 1); + lua_pushboolean(L, t == TSSymbolTypeRegular); + lua_rawseti(L, -2, 2); // [retval, symbols, elem] + lua_rawseti(L, -2, i); // [retval, symbols] + } + + lua_setfield(L, -2, "symbols"); // [retval] + + // TODO: this seems to be empty, what langs have fields? + size_t nfields = (size_t)ts_language_field_count(lang); + lua_createtable(L, nfields-1, 1); // [retval, fields] + for (size_t i = 0; i < nfields; i++) { + lua_pushstring(L, ts_language_field_name_for_id(lang, i)); + lua_rawseti(L, -2, i); // [retval, fields] + } + + lua_setfield(L, -2, "fields"); // [retval] + return 1; +} + int tslua_push_parser(lua_State *L, const char *lang_name) { TSParser *parser = ts_parser_new(); -- cgit From d697841a9d3030efaf10dbddaee9f3c0a8fe1b78 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 16 Jun 2019 14:09:18 +0200 Subject: tree-sitter: cleanup tree refcounting --- src/nvim/lua/treesitter.c | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) (limited to 'src/nvim/lua/treesitter.c') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index db337db533..016c175b59 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -17,9 +17,6 @@ #include "tree_sitter/api.h" -// NOT state-safe, delete when GC is confimed working: -static int debug_n_trees = 0, debug_n_cursors = 0; - #include "nvim/lua/treesitter.h" #include "nvim/api/private/handle.h" #include "nvim/memline.h" @@ -102,19 +99,8 @@ void tslua_init(lua_State *L) build_meta(L, "treesitter_tree", tree_meta); build_meta(L, "treesitter_node", node_meta); - - lua_pushcfunction(L, tslua_debug); - lua_setglobal(L, "_tslua_debug"); } -static int tslua_debug(lua_State *L) -{ - lua_pushinteger(L, debug_n_trees); - lua_pushinteger(L, debug_n_cursors); - return 2; -} - - int ts_lua_register_lang(lua_State *L) { if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { @@ -280,6 +266,9 @@ static int parser_parse_buf(lua_State *L) long bufnr = lua_tointeger(L, 2); void *payload = handle_get_buffer(bufnr); + if (!payload) { + return luaL_error(L, "invalid buffer handle: %d", bufnr); + } TSInput input = { payload, input_cb, TSInputEncodingUTF8 }; TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); if (p->tree) { @@ -287,7 +276,7 @@ static int parser_parse_buf(lua_State *L) } p->tree = new_tree; - tslua_push_tree(L, ts_tree_copy(p->tree)); + tslua_push_tree(L, p->tree); return 1; } @@ -298,11 +287,7 @@ static int parser_tree(lua_State *L) return 0; } - if (p->tree) { - tslua_push_tree(L, ts_tree_copy(p->tree)); - } else { - lua_pushnil(L); - } + tslua_push_tree(L, p->tree); return 1; } @@ -342,12 +327,15 @@ static int parser_edit(lua_State *L) /// push tree interface on lua stack. /// -/// This takes "ownership" of the tree and will free it -/// when the wrapper object is garbage collected +/// This makes a copy of the tree, so ownership of the argument is unaffected. void tslua_push_tree(lua_State *L, TSTree *tree) { + if (tree == NULL) { + lua_pushnil(L); + return; + } TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata] - *ud = tree; + *ud = ts_tree_copy(tree); lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_tree"); // [udata, meta] lua_setmetatable(L, -2); // [udata] @@ -358,7 +346,6 @@ void tslua_push_tree(lua_State *L, TSTree *tree) lua_pushvalue(L, -2); // [udata, reftable, udata] lua_rawseti(L, -2, 1); // [udata, reftable] lua_setfenv(L, -2); // [udata] - debug_n_trees++; } static TSTree *tree_check(lua_State *L) @@ -375,7 +362,6 @@ static int tree_gc(lua_State *L) } ts_tree_delete(tree); - debug_n_trees--; return 0; } -- cgit From 06ee45b9b1c14c7ce6cb23403cdbe2852d495cad Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Fri, 21 Jun 2019 14:14:51 +0200 Subject: tree-sitter: fix lint, delete "demo" plugin (replaced by functional tests) --- src/nvim/lua/treesitter.c | 70 +++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 33 deletions(-) (limited to 'src/nvim/lua/treesitter.c') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 016c175b59..c27ae8c877 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -1,9 +1,9 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -// lua bindings for tree-siter. -// NB: this file should contain a generic lua interface for -// tree-sitter trees and nodes, and could be broken out as a reusable library +// lua bindings for tree-sitter. +// NB: this file mostly contains a generic lua interface for tree-sitter +// trees and nodes, and could be broken out as a reusable lua package #include #include @@ -22,9 +22,9 @@ #include "nvim/memline.h" typedef struct { - TSParser *parser; - TSTree *tree; // internal tree, used for editing/reparsing -} Tslua_parser; + TSParser *parser; + TSTree *tree; // internal tree, used for editing/reparsing +} TSLua_parser; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/treesitter.c.generated.h" @@ -68,9 +68,9 @@ static struct luaL_Reg node_meta[] = { { NULL, NULL } }; -PMap(cstr_t) *langs; +static PMap(cstr_t) *langs; -void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta) +static void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta) { if (luaL_newmetatable(L, tname)) { // [meta] for (size_t i = 0; meta[i].name != NULL; i++) { @@ -84,8 +84,6 @@ void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta) lua_pop(L, 1); // [] (don't use it now) } - - /// init the tslua library /// /// all global state is stored in the regirstry of the lua_State @@ -95,13 +93,11 @@ void tslua_init(lua_State *L) // type metatables build_meta(L, "treesitter_parser", parser_meta); - build_meta(L, "treesitter_tree", tree_meta); - build_meta(L, "treesitter_node", node_meta); } -int ts_lua_register_lang(lua_State *L) +int tslua_register_lang(lua_State *L) { if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { return luaL_error(L, "string expected"); @@ -114,22 +110,27 @@ int ts_lua_register_lang(lua_State *L) return 0; } - // TODO: unsafe! - char symbol_buf[128] = "tree_sitter_"; - STRCAT(symbol_buf, lang_name); +#define BUFSIZE 128 + char symbol_buf[BUFSIZE]; + snprintf(symbol_buf, BUFSIZE, "tree_sitter_%s", lang_name); +#undef BUFSIZE - // TODO: we should maybe keep the uv_lib_t around, and close them - // at exit, to keep LeakSanitizer happy. uv_lib_t lib; if (uv_dlopen(path, &lib)) { - return luaL_error(L, "Failed to load parser: uv_dlopen: %s", - uv_dlerror(&lib)); + snprintf((char *)IObuff, IOSIZE, "Failed to load parser: uv_dlopen: %s", + uv_dlerror(&lib)); + uv_dlclose(&lib); + lua_pushstring(L, (char *)IObuff); + return lua_error(L); } TSLanguage *(*lang_parser)(void); if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { - return luaL_error(L, "Failed to load parser: uv_dlsym: %s", - uv_dlerror(&lib)); + snprintf((char *)IObuff, IOSIZE, "Failed to load parser: uv_dlsym: %s", + uv_dlerror(&lib)); + uv_dlclose(&lib); + lua_pushstring(L, (char *)IObuff); + return lua_error(L); } TSLanguage *lang = lang_parser(); @@ -143,7 +144,7 @@ int ts_lua_register_lang(lua_State *L) return 1; } -int ts_lua_inspect_lang(lua_State *L) +int tslua_inspect_lang(lua_State *L) { if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) { return luaL_error(L, "string expected"); @@ -176,7 +177,6 @@ int ts_lua_inspect_lang(lua_State *L) lua_setfield(L, -2, "symbols"); // [retval] - // TODO: this seems to be empty, what langs have fields? size_t nfields = (size_t)ts_language_field_count(lang); lua_createtable(L, nfields-1, 1); // [retval, fields] for (size_t i = 0; i < nfields; i++) { @@ -190,14 +190,14 @@ int ts_lua_inspect_lang(lua_State *L) int tslua_push_parser(lua_State *L, const char *lang_name) { - TSParser *parser = ts_parser_new(); TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name); if (!lang) { return luaL_error(L, "no such language: %s", lang_name); } + TSParser *parser = ts_parser_new(); ts_parser_set_language(parser, lang); - Tslua_parser *p = lua_newuserdata(L, sizeof(Tslua_parser)); // [udata] + TSLua_parser *p = lua_newuserdata(L, sizeof(TSLua_parser)); // [udata] p->parser = parser; p->tree = NULL; @@ -206,14 +206,14 @@ int tslua_push_parser(lua_State *L, const char *lang_name) return 1; } -static Tslua_parser *parser_check(lua_State *L) +static TSLua_parser *parser_check(lua_State *L) { return luaL_checkudata(L, 1, "treesitter_parser"); } static int parser_gc(lua_State *L) { - Tslua_parser *p = parser_check(L); + TSLua_parser *p = parser_check(L); if (!p) { return 0; } @@ -238,6 +238,7 @@ static const char *input_cb(void *payload, uint32_t byte_index, buf_T *bp = payload; #define BUFSIZE 256 static char buf[BUFSIZE]; + if ((linenr_T)position.row >= bp->b_ml.ml_line_count) { *bytes_read = 0; return ""; @@ -246,10 +247,13 @@ static const char *input_cb(void *payload, uint32_t byte_index, size_t len = STRLEN(line); size_t tocopy = MIN(len-position.column, BUFSIZE); - // TODO: translate embedded \n to \000 memcpy(buf, line+position.column, tocopy); + // Translate embedded \n to NUL + memchrsub(buf, '\n', '\0', tocopy); *bytes_read = (uint32_t)tocopy; - if (tocopy < 200) { + if (tocopy < BUFSIZE) { + // now add the final \n. If it didn't fit, input_cb will be called again + // on the same line with advanced column. buf[tocopy] = '\n'; (*bytes_read)++; } @@ -259,7 +263,7 @@ static const char *input_cb(void *payload, uint32_t byte_index, static int parser_parse_buf(lua_State *L) { - Tslua_parser *p = parser_check(L); + TSLua_parser *p = parser_check(L); if (!p) { return 0; } @@ -282,7 +286,7 @@ static int parser_parse_buf(lua_State *L) static int parser_tree(lua_State *L) { - Tslua_parser *p = parser_check(L); + TSLua_parser *p = parser_check(L); if (!p) { return 0; } @@ -298,7 +302,7 @@ static int parser_edit(lua_State *L) return lua_error(L); } - Tslua_parser *p = parser_check(L); + TSLua_parser *p = parser_check(L); if (!p) { return 0; } -- cgit From e0d6228978dd1389f75a3e0351f6e6d5625643ae Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 21 Sep 2019 10:10:47 +0200 Subject: tree-sitter: use "range" instead of "point_range" consistently in lua API --- src/nvim/lua/treesitter.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/nvim/lua/treesitter.c') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index c27ae8c877..a71234d2c4 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -62,8 +62,8 @@ static struct luaL_Reg node_meta[] = { { "named_child_count", node_named_child_count }, { "child", node_child }, { "named_child", node_named_child }, - { "descendant_for_point_range", node_descendant_for_point_range }, - { "named_descendant_for_point_range", node_named_descendant_for_point_range }, + { "descendant_for_range", node_descendant_for_range }, + { "named_descendant_for_range", node_named_descendant_for_range }, { "parent", node_parent }, { NULL, NULL } }; @@ -587,7 +587,7 @@ static int node_named_child(lua_State *L) return 1; } -static int node_descendant_for_point_range(lua_State *L) +static int node_descendant_for_range(lua_State *L) { TSNode node; if (!node_check(L, &node)) { @@ -604,7 +604,7 @@ static int node_descendant_for_point_range(lua_State *L) return 1; } -static int node_named_descendant_for_point_range(lua_State *L) +static int node_named_descendant_for_range(lua_State *L) { TSNode node; if (!node_check(L, &node)) { -- cgit From d5a69eb07648a515d03aa5c9e268aef852015ea9 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 22 Sep 2019 11:33:55 +0200 Subject: tree-sitter: handle node equality --- src/nvim/lua/treesitter.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src/nvim/lua/treesitter.c') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index a71234d2c4..d2072402bb 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -48,6 +48,7 @@ static struct luaL_Reg tree_meta[] = { static struct luaL_Reg node_meta[] = { { "__tostring", node_tostring }, + { "__eq", node_eq }, { "__len", node_child_count }, { "range", node_range }, { "start", node_start }, @@ -431,6 +432,23 @@ static int node_tostring(lua_State *L) return 1; } +static int node_eq(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + // This should only be called if both x and y in "x == y" has the + // treesitter_node metatable. So it is ok to error out otherwise. + TSNode *ud = luaL_checkudata(L, 2, "treesitter_node"); + if (!ud) { + return 0; + } + TSNode node2 = *ud; + lua_pushboolean(L, ts_node_eq(node, node2)); + return 1; +} + static int node_range(lua_State *L) { TSNode node; -- cgit