// 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-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 #include #include #include #include #include #include #include "tree_sitter/api.h" #include "nvim/lua/treesitter.h" #include "nvim/api/private/handle.h" #include "nvim/memline.h" #include "nvim/buffer.h" typedef struct { TSParser *parser; TSTree *tree; // internal tree, used for editing/reparsing } TSLua_parser; typedef struct { TSQueryCursor *cursor; int predicated_match; } TSLua_cursor; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/treesitter.c.generated.h" #endif static struct luaL_Reg parser_meta[] = { { "__gc", parser_gc }, { "__tostring", parser_tostring }, { "parse", parser_parse }, { "edit", parser_edit }, { "tree", parser_tree }, { "set_included_ranges", parser_set_ranges }, { 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 }, { "__eq", node_eq }, { "__len", node_child_count }, { "range", node_range }, { "start", node_start }, { "end_", node_end }, { "type", node_type }, { "symbol", node_symbol }, { "field", node_field }, { "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_range", node_descendant_for_range }, { "named_descendant_for_range", node_named_descendant_for_range }, { "parent", node_parent }, { "iter_children", node_iter_children }, { "_rawquery", node_rawquery }, { NULL, NULL } }; static struct luaL_Reg query_meta[] = { { "__gc", query_gc }, { "__tostring", query_tostring }, { "inspect", query_inspect }, { NULL, NULL } }; // cursors are not exposed, but still needs garbage collection static struct luaL_Reg querycursor_meta[] = { { "__gc", querycursor_gc }, { NULL, NULL } }; static struct luaL_Reg treecursor_meta[] = { { "__gc", treecursor_gc }, { NULL, NULL } }; static PMap(cstr_t) *langs; static void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta) { if (luaL_newmetatable(L, tname)) { // [meta] luaL_register(L, NULL, meta); lua_pushvalue(L, -1); // [meta, meta] lua_setfield(L, -2, "__index"); // [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 void tslua_init(lua_State *L) { langs = pmap_new(cstr_t)(); // type metatables build_meta(L, "treesitter_parser", parser_meta); build_meta(L, "treesitter_tree", tree_meta); build_meta(L, "treesitter_node", node_meta); build_meta(L, "treesitter_query", query_meta); build_meta(L, "treesitter_querycursor", querycursor_meta); build_meta(L, "treesitter_treecursor", treecursor_meta); } int tslua_has_language(lua_State *L) { const char *lang_name = luaL_checkstring(L, 1); lua_pushboolean(L, pmap_has(cstr_t)(langs, lang_name)); return 1; } int tslua_add_language(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; } #define BUFSIZE 128 char symbol_buf[BUFSIZE]; snprintf(symbol_buf, BUFSIZE, "tree_sitter_%s", lang_name); #undef BUFSIZE uv_lib_t lib; if (uv_dlopen(path, &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)) { 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(); 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_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] 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) { // Gather language 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); } TSParser *parser = ts_parser_new(); 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, "treesitter_parser"); // [udata, meta] lua_setmetatable(L, -2); // [udata] return 1; } 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); 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; #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); if (position.column > len) { *bytes_read = 0; return ""; } size_t tocopy = MIN(len-position.column, BUFSIZE); memcpy(buf, line+position.column, tocopy); // Translate embedded \n to NUL memchrsub(buf, '\n', '\0', tocopy); *bytes_read = (uint32_t)tocopy; 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)++; } return buf; #undef BUFSIZE } static int parser_parse(lua_State *L) { TSLua_parser *p = parser_check(L); if (!p) { return 0; } TSTree *new_tree; size_t len; const char *str; long bufnr; buf_T *buf; TSInput input; // This switch is necessary because of the behavior of lua_isstring, that // consider numbers as strings... switch (lua_type(L, 2)) { case LUA_TSTRING: str = lua_tolstring(L, 2, &len); new_tree = ts_parser_parse_string(p->parser, p->tree, str, len); break; case LUA_TNUMBER: bufnr = lua_tointeger(L, 2); buf = handle_get_buffer(bufnr); if (!buf) { return luaL_error(L, "invalid buffer handle: %d", bufnr); } input = (TSInput){ (void *)buf, input_cb, TSInputEncodingUTF8 }; new_tree = ts_parser_parse(p->parser, p->tree, input); break; default: return luaL_error(L, "invalid argument to parser:parse()"); } uint32_t n_ranges = 0; TSRange *changed = p->tree ? ts_tree_get_changed_ranges(p->tree, new_tree, &n_ranges) : NULL; if (p->tree) { ts_tree_delete(p->tree); } p->tree = new_tree; tslua_push_tree(L, p->tree); lua_createtable(L, n_ranges, 0); for (size_t i = 0; i < n_ranges; i++) { lua_createtable(L, 4, 0); lua_pushinteger(L, changed[i].start_point.row); lua_rawseti(L, -2, 1); lua_pushinteger(L, changed[i].start_point.column); lua_rawseti(L, -2, 2); lua_pushinteger(L, changed[i].end_point.row); lua_rawseti(L, -2, 3); lua_pushinteger(L, changed[i].end_point.column); lua_rawseti(L, -2, 4); lua_rawseti(L, -2, i+1); } xfree(changed); return 2; } static int parser_tree(lua_State *L) { TSLua_parser *p = parser_check(L); if (!p) { return 0; } tslua_push_tree(L, p->tree); return 1; } static int parser_edit(lua_State *L) { if (lua_gettop(L) < 10) { lua_pushstring(L, "not enough args to parser:edit()"); return lua_error(L); } 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; } static int parser_set_ranges(lua_State *L) { if (lua_gettop(L) < 2) { return luaL_error( L, "not enough args to parser:set_included_ranges()"); } TSLua_parser *p = parser_check(L); if (!p || !p->tree) { return 0; } if (!lua_istable(L, 2)) { return luaL_error( L, "argument for parser:set_included_ranges() should be a table."); } size_t tbl_len = lua_objlen(L, 2); TSRange *ranges = xmalloc(sizeof(TSRange) * tbl_len); // [ parser, ranges ] for (size_t index = 0; index < tbl_len; index++) { lua_rawgeti(L, 2, index + 1); // [ parser, ranges, range ] TSNode node; if (!node_check(L, -1, &node)) { xfree(ranges); return luaL_error( L, "ranges should be tables of nodes."); } lua_pop(L, 1); // [ parser, ranges ] ranges[index] = (TSRange) { .start_point = ts_node_start_point(node), .end_point = ts_node_end_point(node), .start_byte = ts_node_start_byte(node), .end_byte = ts_node_end_byte(node) }; } // This memcpies ranges, thus we can free it afterwards ts_parser_set_included_ranges(p->parser, ranges, tbl_len); xfree(ranges); return 0; } // Tree methods /// push tree interface on lua stack. /// /// 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 = ts_tree_copy(tree); 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] } static TSTree *tree_check(lua_State *L) { TSTree **ud = luaL_checkudata(L, 1, "treesitter_tree"); return *ud; } static int tree_gc(lua_State *L) { TSTree *tree = tree_check(L); if (!tree) { return 0; } ts_tree_delete(tree); 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, 1); 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, int uindex) { assert(uindex > 0 || uindex < -LUA_MINSTACK); if (ts_node_is_null(node)) { lua_pushnil(L); // [nil] return; } TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [udata] *ud = node; lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_node"); // [udata, meta] lua_setmetatable(L, -2); // [udata] lua_getfenv(L, uindex); // [udata, reftable] lua_setfenv(L, -2); // [udata] } static bool node_check(lua_State *L, int index, TSNode *res) { TSNode *ud = luaL_checkudata(L, index, "treesitter_node"); if (ud) { *res = *ud; return true; } return false; } static int node_tostring(lua_State *L) { TSNode node; if (!node_check(L, 1, &node)) { return 0; } lua_pushstring(L, ""); lua_concat(L, 3); return 1; } static int node_eq(lua_State *L) { TSNode node; if (!node_check(L, 1, &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; if (!node_check(L, 1, &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, 1, &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_end(lua_State *L) { TSNode node; if (!node_check(L, 1, &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; if (!node_check(L, 1, &node)) { return 0; } uint32_t count = ts_node_child_count(node); lua_pushnumber(L, count); return 1; } static int node_named_child_count(lua_State *L) { TSNode node; if (!node_check(L, 1, &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; if (!node_check(L, 1, &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, 1, &node)) { return 0; } TSSymbol symbol = ts_node_symbol(node); lua_pushnumber(L, symbol); return 1; } static int node_field(lua_State *L) { TSNode node; if (!node_check(L, 1, &node)) { return 0; } size_t name_len; const char *field_name = luaL_checklstring(L, 2, &name_len); TSTreeCursor cursor = ts_tree_cursor_new(node); lua_newtable(L); // [table] unsigned int curr_index = 0; if (ts_tree_cursor_goto_first_child(&cursor)) { do { if (!STRCMP(field_name, ts_tree_cursor_current_field_name(&cursor))) { push_node(L, ts_tree_cursor_current_node(&cursor), 1); // [table, node] lua_rawseti(L, -2, ++curr_index); } } while (ts_tree_cursor_goto_next_sibling(&cursor)); } ts_tree_cursor_delete(&cursor); return 1; } static int node_named(lua_State *L) { TSNode node; if (!node_check(L, 1, &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, 1, &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, 1, &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, 1, &node)) { return 0; } lua_pushboolean(L, ts_node_has_error(node)); return 1; } static int node_child(lua_State *L) { TSNode node; if (!node_check(L, 1, &node)) { return 0; } long num = lua_tointeger(L, 2); TSNode child = ts_node_child(node, (uint32_t)num); push_node(L, child, 1); return 1; } static int node_named_child(lua_State *L) { TSNode node; if (!node_check(L, 1, &node)) { return 0; } long num = lua_tointeger(L, 2); TSNode child = ts_node_named_child(node, (uint32_t)num); push_node(L, child, 1); return 1; } static int node_descendant_for_range(lua_State *L) { TSNode node; if (!node_check(L, 1, &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); push_node(L, child, 1); return 1; } static int node_named_descendant_for_range(lua_State *L) { TSNode node; if (!node_check(L, 1, &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); push_node(L, child, 1); return 1; } static int node_next_child(lua_State *L) { TSTreeCursor *ud = luaL_checkudata( L, lua_upvalueindex(1), "treesitter_treecursor"); if (!ud) { return 0; } TSNode source; if (!node_check(L, lua_upvalueindex(2), &source)) { return 0; } // First call should return first child if (ts_node_eq(source, ts_tree_cursor_current_node(ud))) { if (ts_tree_cursor_goto_first_child(ud)) { goto push; } else { goto end; } } if (ts_tree_cursor_goto_next_sibling(ud)) { push: push_node( L, ts_tree_cursor_current_node(ud), lua_upvalueindex(2)); // [node] const char * field = ts_tree_cursor_current_field_name(ud); if (field != NULL) { lua_pushstring(L, ts_tree_cursor_current_field_name(ud)); } else { lua_pushnil(L); } // [node, field_name_or_nil] return 2; } end: return 0; } static int node_iter_children(lua_State *L) { TSNode source; if (!node_check(L, 1, &source)) { return 0; } TSTreeCursor *ud = lua_newuserdata(L, sizeof(TSTreeCursor)); // [udata] *ud = ts_tree_cursor_new(source); lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_treecursor"); // [udata, mt] lua_setmetatable(L, -2); // [udata] lua_pushvalue(L, 1); // [udata, source_node] lua_pushcclosure(L, node_next_child, 2); return 1; } static int treecursor_gc(lua_State *L) { TSTreeCursor *ud = luaL_checkudata(L, 1, "treesitter_treecursor"); ts_tree_cursor_delete(ud); return 0; } static int node_parent(lua_State *L) { TSNode node; if (!node_check(L, 1, &node)) { return 0; } TSNode parent = ts_node_parent(node); push_node(L, parent, 1); return 1; } /// assumes the match table being on top of the stack static void set_match(lua_State *L, TSQueryMatch *match, int nodeidx) { for (int i = 0; i < match->capture_count; i++) { push_node(L, match->captures[i].node, nodeidx); lua_rawseti(L, -2, match->captures[i].index+1); } } static int query_next_match(lua_State *L) { TSLua_cursor *ud = lua_touserdata(L, lua_upvalueindex(1)); TSQueryCursor *cursor = ud->cursor; TSQuery *query = query_check(L, lua_upvalueindex(3)); TSQueryMatch match; if (ts_query_cursor_next_match(cursor, &match)) { lua_pushinteger(L, match.pattern_index+1); // [index] lua_createtable(L, ts_query_capture_count(query), 2); // [index, match] set_match(L, &match, lua_upvalueindex(2)); return 2; } return 0; } static int query_next_capture(lua_State *L) { TSLua_cursor *ud = lua_touserdata(L, lua_upvalueindex(1)); TSQueryCursor *cursor = ud->cursor; TSQuery *query = query_check(L, lua_upvalueindex(3)); if (ud->predicated_match > -1) { lua_getfield(L, lua_upvalueindex(4), "active"); bool active = lua_toboolean(L, -1); lua_pop(L, 1); if (!active) { ts_query_cursor_remove_match(cursor, ud->predicated_match); } ud->predicated_match = -1; } TSQueryMatch match; uint32_t capture_index; if (ts_query_cursor_next_capture(cursor, &match, &capture_index)) { TSQueryCapture capture = match.captures[capture_index]; lua_pushinteger(L, capture.index+1); // [index] push_node(L, capture.node, lua_upvalueindex(2)); // [index, node] uint32_t n_pred; ts_query_predicates_for_pattern(query, match.pattern_index, &n_pred); if (n_pred > 0 && capture_index == 0) { lua_pushvalue(L, lua_upvalueindex(4)); // [index, node, match] set_match(L, &match, lua_upvalueindex(2)); lua_pushinteger(L, match.pattern_index+1); lua_setfield(L, -2, "pattern"); if (match.capture_count > 1) { ud->predicated_match = match.id; lua_pushboolean(L, false); lua_setfield(L, -2, "active"); } return 3; } return 2; } return 0; } static int node_rawquery(lua_State *L) { TSNode node; if (!node_check(L, 1, &node)) { return 0; } TSQuery *query = query_check(L, 2); // TODO(bfredl): these are expensive allegedly, // use a reuse list later on? TSQueryCursor *cursor = ts_query_cursor_new(); ts_query_cursor_exec(cursor, query, node); bool captures = lua_toboolean(L, 3); if (lua_gettop(L) >= 4) { int start = luaL_checkinteger(L, 4); int end = lua_gettop(L) >= 5 ? luaL_checkinteger(L, 5) : MAXLNUM; ts_query_cursor_set_point_range(cursor, (TSPoint){ start, 0 }, (TSPoint){ end, 0 }); } TSLua_cursor *ud = lua_newuserdata(L, sizeof(*ud)); // [udata] ud->cursor = cursor; ud->predicated_match = -1; lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_querycursor"); lua_setmetatable(L, -2); // [udata] lua_pushvalue(L, 1); // [udata, node] // include query separately, as to keep a ref to it for gc lua_pushvalue(L, 2); // [udata, node, query] if (captures) { // placeholder for match state lua_createtable(L, ts_query_capture_count(query), 2); // [u, n, q, match] lua_pushcclosure(L, query_next_capture, 4); // [closure] } else { lua_pushcclosure(L, query_next_match, 3); // [closure] } return 1; } static int querycursor_gc(lua_State *L) { TSLua_cursor *ud = luaL_checkudata(L, 1, "treesitter_querycursor"); ts_query_cursor_delete(ud->cursor); return 0; } // Query methods int ts_lua_parse_query(lua_State *L) { if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { 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); } size_t len; const char *src = lua_tolstring(L, 2, &len); uint32_t error_offset; TSQueryError error_type; TSQuery *query = ts_query_new(lang, src, len, &error_offset, &error_type); if (!query) { return luaL_error(L, "query: %s at position %d", query_err_string(error_type), (int)error_offset); } TSQuery **ud = lua_newuserdata(L, sizeof(TSQuery *)); // [udata] *ud = query; lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_query"); // [udata, meta] lua_setmetatable(L, -2); // [udata] return 1; } static const char *query_err_string(TSQueryError err) { switch (err) { case TSQueryErrorSyntax: return "invalid syntax"; case TSQueryErrorNodeType: return "invalid node type"; case TSQueryErrorField: return "invalid field"; case TSQueryErrorCapture: return "invalid capture"; default: return "error"; } } static TSQuery *query_check(lua_State *L, int index) { TSQuery **ud = luaL_checkudata(L, index, "treesitter_query"); return *ud; } static int query_gc(lua_State *L) { TSQuery *query = query_check(L, 1); if (!query) { return 0; } ts_query_delete(query); return 0; } static int query_tostring(lua_State *L) { lua_pushstring(L, ""); return 1; } static int query_inspect(lua_State *L) { TSQuery *query = query_check(L, 1); if (!query) { return 0; } uint32_t n_pat = ts_query_pattern_count(query); lua_createtable(L, 0, 2); // [retval] lua_createtable(L, n_pat, 1); // [retval, patterns] for (size_t i = 0; i < n_pat; i++) { uint32_t len; const TSQueryPredicateStep *step = ts_query_predicates_for_pattern(query, i, &len); if (len == 0) { continue; } lua_createtable(L, len/4, 1); // [retval, patterns, pat] lua_createtable(L, 3, 0); // [retval, patterns, pat, pred] int nextpred = 1; int nextitem = 1; for (size_t k = 0; k < len; k++) { if (step[k].type == TSQueryPredicateStepTypeDone) { lua_rawseti(L, -2, nextpred++); // [retval, patterns, pat] lua_createtable(L, 3, 0); // [retval, patterns, pat, pred] nextitem = 1; continue; } if (step[k].type == TSQueryPredicateStepTypeString) { uint32_t strlen; const char *str = ts_query_string_value_for_id(query, step[k].value_id, &strlen); lua_pushlstring(L, str, strlen); // [retval, patterns, pat, pred, item] } else if (step[k].type == TSQueryPredicateStepTypeCapture) { lua_pushnumber(L, step[k].value_id+1); // [..., pat, pred, item] } else { abort(); } lua_rawseti(L, -2, nextitem++); // [retval, patterns, pat, pred] } // last predicate should have ended with TypeDone lua_pop(L, 1); // [retval, patters, pat] lua_rawseti(L, -2, i+1); // [retval, patterns] } lua_setfield(L, -2, "patterns"); // [retval] uint32_t n_captures = ts_query_capture_count(query); lua_createtable(L, n_captures, 0); // [retval, captures] for (size_t i = 0; i < n_captures; i++) { uint32_t strlen; const char *str = ts_query_capture_name_for_id(query, i, &strlen); lua_pushlstring(L, str, strlen); // [retval, captures, capture] lua_rawseti(L, -2, i+1); } lua_setfield(L, -2, "captures"); // [retval] return 1; }