Packages

Tree-sitter bindings (nif) for elixir. Currently small subset of the tree-sitter api is supported, but happy to accept merge requests/patches

Current section

6 Versions

Jump to

Compare versions

4 files changed
+286 additions
-33 deletions
  @@ -2,7 +2,7 @@
2 2 [{<<"issues">>,<<"https://codeberg.org/edwinvanl/treesitter_elixir/issues">>},
3 3 {<<"source">>,<<"https://codeberg.org/edwinvanl/treesitter_elixir">>}]}.
4 4 {<<"name">>,<<"treesitter_elixir">>}.
5 - {<<"version">>,<<"0.1.1">>}.
5 + {<<"version">>,<<"0.1.2">>}.
6 6 {<<"description">>,
7 7 <<"Tree-sitter bindings (nif) for elixir. Currently small subset of the tree-sitter api is supported, but happy to accept merge requests/patches">>}.
8 8 {<<"elixir">>,<<"~> 1.18">>}.
  @@ -26,11 +26,11 @@ defmodule TreeSitter.Nif do
26 26 end
27 27
28 28 def parser_new() do
29 - raise "NIF not implemented"
29 + :erlang.nif_error(:nif_not_loaded)
30 30 end
31 31
32 32 @doc """
33 - Set the langage given a full name of the language and the path.
33 + Get the langage given a full name of the language and the path to the shared library implementing the language.
34 34
35 35 `language_path` is a useful helper function to get the full language name and path if needed
36 36
  @@ -38,57 +38,94 @@ defmodule TreeSitter.Nif do
38 38
39 39 TreeSitter.Nif.language("tree_sitter_c", "/usr/lib/libtree-sitter-c.so")
40 40 """
41 - def parser_set_language(parser, language, full_path)
41 + def language(language, full_path)
42 42
43 - def parser_set_language(_, _, _) do
44 - raise "NIF not implemented"
43 + def language(_, _) do
44 + :erlang.nif_error(:nif_not_loaded)
45 + end
46 +
47 + def parser_set_language(parser, language)
48 +
49 + def parser_set_language(_, _) do
50 + :erlang.nif_error(:nif_not_loaded)
45 51 end
46 52
47 53 def parser_parse_string(_, _) do
48 - raise "NIF not implemented"
54 + :erlang.nif_error(:nif_not_loaded)
49 55 end
50 56
51 57 def parse(_, _) do
52 - raise "NIF not implemented"
58 + :erlang.nif_error(:nif_not_loaded)
53 59 end
54 60
55 61 def tree_root_node(_) do
56 - raise "NIF not implemented"
62 + :erlang.nif_error(:nif_not_loaded)
57 63 end
58 64
59 65 def node_parent(_) do
60 - raise "NIF not implemented"
66 + :erlang.nif_error(:nif_not_loaded)
61 67 end
62 68
63 69 def node_child(_) do
64 - raise "NIF not implemented"
70 + :erlang.nif_error(:nif_not_loaded)
65 71 end
66 72
67 73 def node_next_sibling(_) do
68 - raise "NIF not implemented"
74 + :erlang.nif_error(:nif_not_loaded)
69 75 end
70 76
71 77 def node_prev_sibling(_) do
72 - raise "NIF not implemented"
78 + :erlang.nif_error(:nif_not_loaded)
73 79 end
74 80
75 81 def node_string(_) do
76 - raise "NIF not implemented"
82 + :erlang.nif_error(:nif_not_loaded)
77 83 end
78 84
79 85 def node_type(_) do
80 - raise "NIF not implemented"
86 + :erlang.nif_error(:nif_not_loaded)
81 87 end
82 88
83 89 @doc """
84 90 Return the start and end index of the bytestring
85 91 """
86 92 def node_byte_range(_) do
87 - raise "NIF not implemented"
93 + :erlang.nif_error(:nif_not_loaded)
94 + # :erlang.nif_error(:nif_not_loaded)
88 95 end
89 96
90 97 def node_point_range(_) do
91 - raise "NIF not implemented"
98 + :erlang.nif_error(:nif_not_loaded)
99 + end
100 +
101 + def query_new(language, query_string)
102 +
103 + def query_new(_, _) do
104 + :erlang.nif_error(:nif_not_loaded)
105 + end
106 +
107 + def query_cursor_exec(query, node)
108 +
109 + def query_cursor_exec(_, _) do
110 + :erlang.nif_error(:nif_not_loaded)
111 + end
112 +
113 + def query_cursor_next_match(cursor)
114 +
115 + def query_cursor_next_match(_) do
116 + :erlang.nif_error(:nif_not_loaded)
117 + end
118 +
119 + def query_pattern_byte_range(query, pattern_index)
120 +
121 + def query_pattern_byte_range(_, _) do
122 + :erlang.nif_error(:nif_not_loaded)
123 + end
124 +
125 + def query_capture_name_for_id(query, pattern_index)
126 +
127 + def query_capture_name_for_id(_, _) do
128 + :erlang.nif_error(:nif_not_loaded)
92 129 end
93 130 end
  @@ -6,7 +6,7 @@ defmodule Treesitter.MixProject do
6 6 app: :treesitter_elixir,
7 7 description:
8 8 "Tree-sitter bindings (nif) for elixir. Currently small subset of the tree-sitter api is supported, but happy to accept merge requests/patches",
9 - version: "0.1.1",
9 + version: "0.1.2",
10 10 elixir: "~> 1.18",
11 11 compilers: [:elixir_make] ++ Mix.compilers(),
12 12 start_permanent: Mix.env() == :prod,
  @@ -1,11 +1,16 @@
1 1 #include "erl_nif.h"
2 2 #include "tree_sitter/api.h"
3 3 #include <dlfcn.h>
4 + #include <stdint.h>
4 5
5 6 // Setup external resources
6 7 static ErlNifResourceType *parser_resource_type = NULL;
7 8 static ErlNifResourceType *tree_resource_type = NULL;
8 9 static ErlNifResourceType *node_resource_type = NULL;
10 + static ErlNifResourceType *language_resource_type = NULL;
11 + static ErlNifResourceType *query_resource_type = NULL;
12 + static ErlNifResourceType *querycursor_resource_type = NULL;
13 +
9 14 // Wrappers
10 15 typedef struct {
11 16 TSParser *data;
  @@ -20,9 +25,22 @@ typedef struct {
20 25 // freed first. We could have a wrapper that has the parser, the tree, and the
21 26 // current node?
22 27 // Could also hold the source_code/text
28 + // I think this is why the cursor stuff exists, so maybe rely on that.
23 29 TSNode data;
24 30 } WRNode;
25 31
32 + typedef struct {
33 + TSLanguage *data;
34 + } WRLanguage;
35 +
36 + typedef struct {
37 + TSQuery *data;
38 + } WRQuery;
39 +
40 + typedef struct {
41 + TSQueryCursor *data;
42 + } WRQueryCursor;
43 +
26 44 static void parser_dtor(ErlNifEnv *env, void *obj) {
27 45 WRParser *my_struct = (WRParser *)obj;
28 46 if (my_struct->data) {
  @@ -45,6 +63,30 @@ static void node_dtor(ErlNifEnv *env, void *obj) {
45 63 enif_release_resource(my_struct);
46 64 }
47 65
66 + static void language_dtor(ErlNifEnv *env, void *obj) {
67 + WRLanguage *my_struct = (WRLanguage *)obj;
68 + if (my_struct->data) {
69 + ts_language_delete(my_struct->data);
70 + }
71 + enif_release_resource(my_struct);
72 + }
73 +
74 + static void query_dtor(ErlNifEnv *env, void *obj) {
75 + WRQuery *my_struct = (WRQuery *)obj;
76 + if (my_struct->data) {
77 + ts_query_delete(my_struct->data);
78 + }
79 + enif_release_resource(my_struct);
80 + }
81 +
82 + static void querycursor_dtor(ErlNifEnv *env, void *obj) {
83 + WRQueryCursor *my_struct = (WRQueryCursor *)obj;
84 + if (my_struct->data) {
85 + ts_query_cursor_delete(my_struct->data);
86 + }
87 + enif_release_resource(my_struct);
88 + }
89 +
48 90 static int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) {
49 91 ErlNifResourceFlags flags =
50 92 (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER);
  @@ -56,6 +98,12 @@ static int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) {
56 98
57 99 node_resource_type = enif_open_resource_type(env, NULL, "node_resource",
58 100 node_dtor, flags, NULL);
101 + language_resource_type = enif_open_resource_type(
102 + env, NULL, "language_resource", language_dtor, flags, NULL);
103 + query_resource_type = enif_open_resource_type(env, NULL, "query_resource",
104 + query_dtor, flags, NULL);
105 + querycursor_resource_type = enif_open_resource_type(
106 + env, NULL, "querycursor_resource", querycursor_dtor, flags, NULL);
59 107 return 0;
60 108 }
61 109
  @@ -86,23 +134,17 @@ static ERL_NIF_TERM parser_new(ErlNifEnv *env, int argc,
86 134 }
87 135
88 136 typedef TSLanguage *(tree_sitter_lang)(void);
89 - static ERL_NIF_TERM parser_set_language(ErlNifEnv *env, int argc,
90 - const ERL_NIF_TERM argv[]) {
137 + static ERL_NIF_TERM language(ErlNifEnv *env, int argc,
138 + const ERL_NIF_TERM argv[]) {
91 139 char language[1024];
92 140 char lib_path[1024];
93 - if (!enif_get_string(env, argv[1], language, 1024, ERL_NIF_UTF8)) {
141 + if (!enif_get_string(env, argv[0], language, 1024, ERL_NIF_UTF8)) {
94 142 return to_error_string(env, language);
95 143 }
96 - if (!enif_get_string(env, argv[2], lib_path, 1024, ERL_NIF_UTF8)) {
144 + if (!enif_get_string(env, argv[1], lib_path, 1024, ERL_NIF_UTF8)) {
97 145 return to_error_string(env, lib_path);
98 146 }
99 147
100 - WRParser *wrap;
101 - if (!enif_get_resource(env, argv[0], parser_resource_type, (void **)&wrap)) {
102 - return to_error_string(env, "Unable to unwrap parser");
103 - }
104 -
105 - // Close lib properly, currently seems to result in segmentation vault.
106 148 void *lib = dlopen(lib_path, RTLD_NOW);
107 149 const char *err = dlerror();
108 150 if (err != NULL) {
  @@ -110,9 +152,26 @@ static ERL_NIF_TERM parser_set_language(ErlNifEnv *env, int argc,
110 152 }
111 153
112 154 tree_sitter_lang *make_ts_language = (tree_sitter_lang *)dlsym(lib, language);
113 - TSLanguage *lang = make_ts_language();
114 - if (!ts_parser_set_language(wrap->data, lang)) {
115 - dlclose(lib);
155 + WRLanguage *wrap =
156 + enif_alloc_resource(language_resource_type, sizeof(WRLanguage));
157 + wrap->data = make_ts_language();
158 + return to_ok_term(env, enif_make_resource(env, wrap));
159 + }
160 +
161 + static ERL_NIF_TERM parser_set_language(ErlNifEnv *env, int argc,
162 + const ERL_NIF_TERM argv[]) {
163 + WRParser *wrap;
164 + if (!enif_get_resource(env, argv[0], parser_resource_type, (void **)&wrap)) {
165 + return to_error_string(env, "Unable to unwrap parser");
166 + }
167 +
168 + WRLanguage *lang_wrap;
169 + if (!enif_get_resource(env, argv[1], language_resource_type,
170 + (void **)&lang_wrap)) {
171 + return to_error_string(env, "Unable to unwrap language");
172 + }
173 +
174 + if (!ts_parser_set_language(wrap->data, lang_wrap->data)) {
116 175 return to_error_string(env, "Unable to set the language to the parser");
117 176 }
118 177
  @@ -142,6 +201,11 @@ static ERL_NIF_TERM parse(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
142 201 free(source_code);
143 202 return to_ok_term(env, enif_make_resource(env, wraptree));
144 203 }
204 + static ERL_NIF_TERM enif_make_node_wrap(ErlNifEnv *env, const TSNode node) {
205 + WRNode *wrapnode = enif_alloc_resource(node_resource_type, sizeof(WRNode));
206 + wrapnode->data = node;
207 + return enif_make_resource(env, wrapnode);
208 + }
145 209
146 210 static ERL_NIF_TERM tree_root_node(ErlNifEnv *env, int argc,
147 211 const ERL_NIF_TERM argv[]) {
  @@ -283,9 +347,156 @@ static ERL_NIF_TERM node_point_range(ErlNifEnv *env, int argc,
283 347 return enif_make_tuple2(env, point_to_tuple(env, i), point_to_tuple(env, j));
284 348 }
285 349
350 + /**
351 + * Query
352 + */
353 + // TODO: Have a tslanguage wrapper and support getting it
354 + // integrate this above
355 + // TSQueryCapture to ERL_NIF_TERM (list of nodes)
356 + // TSQueryMatch to ERL_NIF_TERM (dict with pattern_index and the list of
357 + // nodes(tsqueraycapture)
358 +
359 + // TSQuery *ts_query_new(
360 + // const TSLanguage *language,
361 + // const char *source,
362 + // uint32_t source_len,
363 + // uint32_t *error_offset,
364 + // TSQueryError *error_type
365 + // );
366 +
367 + static ERL_NIF_TERM query_new(ErlNifEnv *env, int argc,
368 + const ERL_NIF_TERM argv[]) {
369 + WRLanguage *lang_wrap;
370 + if (!enif_get_resource(env, argv[0], language_resource_type,
371 + (void **)&lang_wrap)) {
372 + return to_error_string(env, "Unable to unwrap language");
373 + }
374 + unsigned int len;
375 + enif_get_string_length(env, argv[1], &len, ERL_NIF_UTF8);
376 + char *source_code = malloc(len + 1);
377 + if (!enif_get_string(env, argv[1], source_code, len + 1, ERL_NIF_UTF8)) {
378 + return to_error_string(env, "Failed loading source code");
379 + }
380 +
381 + WRQuery *wrap = enif_alloc_resource(query_resource_type, sizeof(WRQuery));
382 + uint32_t error_offset;
383 + TSQueryError error_type;
384 + wrap->data = ts_query_new(lang_wrap->data, source_code, len, &error_offset,
385 + &error_type);
386 + if (wrap->data) {
387 + return to_ok_term(env, enif_make_resource(env, wrap));
388 + }
389 + char error_string[50];
390 + sprintf(error_string, "Found an error in the query at %d, type %d",
391 + error_offset, error_type);
392 +
393 + return to_error_string(env, error_string);
394 + }
395 +
396 + // void ts_query_cursor_exec(TSQueryCursor *, const TSQuery *, TSNode);
397 + static ERL_NIF_TERM query_cursor_exec(ErlNifEnv *env, int argc,
398 + const ERL_NIF_TERM argv[]) {
399 + WRQuery *query_wrap;
400 + if (!enif_get_resource(env, argv[0], query_resource_type,
401 + (void **)&query_wrap)) {
402 + return to_error_string(env, "Unable to unwrap query");
403 + }
404 + WRNode *node_wrap;
405 + if (!enif_get_resource(env, argv[1], node_resource_type,
406 + (void **)&node_wrap)) {
407 + return to_error_string(env, "Unable to unwrap node");
408 + }
409 + WRQueryCursor *wrap =
410 + enif_alloc_resource(querycursor_resource_type, sizeof(WRQueryCursor));
411 + wrap->data = ts_query_cursor_new();
412 + if (wrap->data) {
413 + ts_query_cursor_exec(wrap->data, query_wrap->data, node_wrap->data);
414 +
415 + return to_ok_term(env, enif_make_resource(env, wrap));
416 + }
417 + return to_error_string(env, "Unable to create cursor");
418 + }
419 +
420 + // bool ts_query_cursor_next_match(TSQueryCursor *, TSQueryMatch *match);
421 + // TODO: Return the cursor?
422 + static ERL_NIF_TERM query_cursor_next_match(ErlNifEnv *env, int argc,
423 + const ERL_NIF_TERM argv[]) {
424 + WRQueryCursor *querycursor_wrap;
425 + if (!enif_get_resource(env, argv[0], querycursor_resource_type,
426 + (void **)&querycursor_wrap)) {
427 + return to_error_string(env, "Unable to unwrap querycursor");
428 + }
429 + TSQueryMatch match;
430 + if (!ts_query_cursor_next_match(querycursor_wrap->data, &match)) {
431 + return to_error_nil(env);
432 + }
433 + // Add them to an list
434 + ERL_NIF_TERM *lst =
435 + (ERL_NIF_TERM *)malloc(match.capture_count * sizeof(ERL_NIF_TERM));
436 + for (int i = 0; i < match.capture_count; ++i) {
437 + lst[i] = enif_make_node_wrap(env, match.captures[i].node);
438 + }
439 + ERL_NIF_TERM arr = enif_make_list_from_array(env, lst, match.capture_count);
440 + free(lst);
441 + // TODO: Do we ever need the match id and pattern_index?
442 + return enif_make_tuple2(
443 + env, enif_make_atom(env, "ok"),
444 + enif_make_tuple3(env, enif_make_int(env, match.id),
445 + enif_make_int(env, match.pattern_index), arr));
446 + }
447 +
448 + static ERL_NIF_TERM query_pattern_byte_range(ErlNifEnv *env, int argc,
449 + const ERL_NIF_TERM argv[]) {
450 + // TODO: Can we make this into a function
451 + WRQuery *query_wrap;
452 + if (!enif_get_resource(env, argv[0], query_resource_type,
453 + (void **)&query_wrap)) {
454 + return to_error_string(env, "Unable to unwrap query");
455 + }
456 +
457 + uint32_t pattern_index;
458 + enif_get_uint(env, argv[1], &pattern_index);
459 +
460 + uint32_t byte_start =
461 + ts_query_start_byte_for_pattern(query_wrap->data, pattern_index);
462 + uint32_t byte_end =
463 + ts_query_end_byte_for_pattern(query_wrap->data, pattern_index);
464 + return enif_make_tuple2(env, enif_make_int(env, byte_start),
465 + enif_make_int(env, byte_end));
466 + }
467 +
468 + /**
469 + * Get the name and length of one of the query's captures, or one of the
470 + * query's string literals. Each capture and string is associated with a
471 + * numeric id based on the order that it appeared in the query's source.
472 + */
473 + // const char *ts_query_capture_name_for_id(
474 + // const TSQuery *self,
475 + // uint32_t index,
476 + // uint32_t *length
477 + // );
478 + static ERL_NIF_TERM query_capture_name_for_id(ErlNifEnv *env, int argc,
479 + const ERL_NIF_TERM argv[]) {
480 + // TODO: Can we make this into a function
481 + WRQuery *query_wrap;
482 + if (!enif_get_resource(env, argv[0], query_resource_type,
483 + (void **)&query_wrap)) {
484 + return to_error_string(env, "Unable to unwrap query");
485 + }
486 +
487 + uint32_t pattern_index;
488 + enif_get_uint(env, argv[1], &pattern_index);
489 + int len;
490 + const char *name =
491 + ts_query_capture_name_for_id(query_wrap->data, pattern_index, &len);
492 + return enif_make_tuple2(env, enif_make_atom(env, "ok"),
493 + enif_make_string(env, name, ERL_NIF_UTF8));
494 + }
495 +
286 496 static ErlNifFunc nif_funcs[] = {
287 497 {"parser_new", 0, parser_new, 0},
288 - {"parser_set_language", 3, parser_set_language, 0},
498 + {"language", 2, language, 0},
499 + {"parser_set_language", 2, parser_set_language, 0},
289 500 {"parse", 2, parse, 0},
290 501 {"tree_root_node", 1, tree_root_node, 0},
291 502 {"node_parent", 1, node_parent, 0},
  @@ -295,6 +506,11 @@ static ErlNifFunc nif_funcs[] = {
295 506 {"node_string", 1, node_string, 0},
296 507 {"node_type", 1, node_type, 0},
297 508 {"node_byte_range", 1, node_byte_range, 0},
298 - {"node_point_range", 1, node_point_range, 0}};
509 + {"node_point_range", 1, node_point_range, 0},
510 + {"query_new", 2, query_new, 0},
511 + {"query_cursor_exec", 2, query_cursor_exec, 0},
512 + {"query_cursor_next_match", 1, query_cursor_next_match, 0},
513 + {"query_pattern_byte_range", 2, query_pattern_byte_range, 0},
514 + {"query_capture_name_for_id", 2, query_capture_name_for_id, 0}};
299 515
300 516 ERL_NIF_INIT(Elixir.TreeSitter.Nif, nif_funcs, load, NULL, NULL, NULL)