OR-1 dataflow CPU sketch
1#include "./tree.h"
2#include "./addon_data.h"
3#include "./conversions.h"
4#include "./node.h"
5
6#include <napi.h>
7
8using namespace Napi;
9
10namespace node_tree_sitter {
11
12/*
13 tstag() {
14 b2sum -l64 <(printf tree-sitter) <(printf "$1") | \
15 awk '{printf "0x" toupper($1) (NR == 1 ? ", " : "\n")}'
16 }
17 tstag tree # => 0x8AF2E5212AD58ABF, 0x7FA28BFC1966AC2D
18*/
19const napi_type_tag TREE_TYPE_TAG = {
20 0x8AF2E5212AD58ABF, 0x7FA28BFC1966AC2D
21};
22
23using node_methods::UnmarshalNodeId;
24
25void Tree::Init(Napi::Env env, Napi::Object exports) {
26 auto *data = env.GetInstanceData<AddonData>();
27
28 Function ctor = DefineClass(env, "Tree", {
29 InstanceMethod("edit", &Tree::Edit, napi_default_method),
30 InstanceMethod("rootNode", &Tree::RootNode, napi_default_method),
31 InstanceMethod("rootNodeWithOffset", &Tree::RootNodeWithOffset, napi_default_method),
32 InstanceMethod("printDotGraph", &Tree::PrintDotGraph, napi_default_method),
33 InstanceMethod("getChangedRanges", &Tree::GetChangedRanges, napi_default_method),
34 InstanceMethod("getIncludedRanges", &Tree::GetIncludedRanges, napi_default_method),
35 InstanceMethod("getEditedRange", &Tree::GetEditedRange, napi_default_method),
36 InstanceMethod("_cacheNode", &Tree::CacheNode, napi_default_method),
37 InstanceMethod("_cacheNodes", &Tree::CacheNodes, napi_default_method),
38 });
39
40 data->tree_constructor = Napi::Persistent(ctor);
41 exports["Tree"] = ctor;
42}
43
44Tree::Tree(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Tree>(info), tree_(nullptr) {
45 Value().TypeTag(&TREE_TYPE_TAG);
46}
47
48Tree::~Tree() {
49 ts_tree_delete(tree_);
50 for (auto &entry : cached_nodes_) {
51 entry.second->tree = nullptr;
52 }
53}
54
55Napi::Value Tree::NewInstance(Napi::Env env, TSTree *tree) {
56 auto *data = env.GetInstanceData<AddonData>();
57 if (tree != nullptr) {
58 Object self = data->tree_constructor.New({});
59 Tree::Unwrap(self)->tree_ = tree;
60 return self;
61 }
62
63 return env.Null();
64}
65
66const Tree *Tree::UnwrapTree(const Napi::Value &value) {
67 if (!value.IsObject()) {
68 return nullptr;
69 }
70 auto js_tree = value.As<Object>();
71 if (!js_tree.CheckTypeTag(&TREE_TYPE_TAG)) {
72 return nullptr;
73 }
74 return Tree::Unwrap(js_tree);
75}
76
77#define read_number_from_js(out, value, name) \
78 if (!(value).IsNumber()) { \
79 throw TypeError::New(env, name " must be an integer"); \
80 return env.Undefined(); \
81 } \
82 *(out) = (value).As<Number>().Uint32Value();
83
84#define read_byte_count_from_js(out, value, name) \
85 read_number_from_js(out, value, name); \
86 (*(out)) *= 2
87
88Napi::Value Tree::Edit(const Napi::CallbackInfo &info) {
89 Napi::Env env = info.Env();
90 TSInputEdit edit;
91 read_number_from_js(&edit.start_point.row, info[0], "startPosition.row");
92 read_byte_count_from_js(&edit.start_point.column, info[1], "startPosition.column");
93 read_number_from_js(&edit.old_end_point.row, info[2], "oldEndPosition.row");
94 read_byte_count_from_js(&edit.old_end_point.column, info[3], "oldEndPosition.column");
95 read_number_from_js(&edit.new_end_point.row, info[4], "newEndPosition.row");
96 read_byte_count_from_js(&edit.new_end_point.column, info[5], "newEndPosition.column");
97 read_byte_count_from_js(&edit.start_byte, info[6], "startIndex");
98 read_byte_count_from_js(&edit.old_end_byte, info[7], "oldEndIndex");
99 read_byte_count_from_js(&edit.new_end_byte, info[8], "newEndIndex");
100
101 ts_tree_edit(tree_, &edit);
102
103 for (auto &entry : cached_nodes_) {
104 Object js_node = entry.second->node.Value();
105 if (js_node.IsEmpty()) {
106 continue;
107 }
108 TSNode node;
109 node.id = entry.first;
110 for (unsigned i = 0; i < 4; i++) {
111 Napi::Value node_field = js_node[i + 2];
112 if (node_field.IsNumber()) {
113 node.context[i] = node_field.As<Number>().Uint32Value();
114 }
115 }
116
117 ts_node_edit(&node, &edit);
118
119 for (unsigned i = 0; i < 4; i++) {
120 js_node[i + 2U] = Number::New(env, node.context[i]);
121 }
122 }
123
124 return info.This();
125}
126
127Napi::Value Tree::RootNode(const Napi::CallbackInfo &info) {
128 return node_methods::MarshalNode(info, this, ts_tree_root_node(tree_));
129}
130
131Napi::Value Tree::RootNodeWithOffset(const Napi::CallbackInfo &info) {
132 Napi::Env env = info.Env();
133 uint32_t offset_bytes = 0;
134 TSPoint offset_extent = {0, UINT32_MAX};
135
136 read_byte_count_from_js(&offset_bytes, info[0], "offsetBytes");
137 read_number_from_js(&offset_extent.row, info[1], "offsetExtent.row");
138 read_byte_count_from_js(&offset_extent.column, info[2], "offsetExtent.column");
139
140 return node_methods::MarshalNode(info, this, ts_tree_root_node_with_offset(tree_, offset_bytes, offset_extent));
141}
142
143Napi::Value Tree::GetChangedRanges(const Napi::CallbackInfo &info) {
144 Napi::Env env = info.Env();
145 const Tree *other_tree = UnwrapTree(info[0]);
146 if (other_tree == nullptr) {
147 throw TypeError::New(env, "Argument must be a tree");
148 }
149
150 uint32_t range_count;
151 TSRange *ranges = ts_tree_get_changed_ranges(tree_, other_tree->tree_, &range_count);
152
153 Array result = Array::New(env);
154 for (size_t i = 0; i < range_count; i++) {
155 result[i] = RangeToJS(env, ranges[i]);
156 }
157
158 free(ranges);
159
160 return result;
161}
162
163Napi::Value Tree::GetIncludedRanges(const Napi::CallbackInfo &info) {
164 Napi::Env env = info.Env();
165 uint32_t range_count;
166 TSRange *ranges = ts_tree_included_ranges(tree_, &range_count);
167
168 Array result = Array::New(env);
169 for (size_t i = 0; i < range_count; i++) {
170 result[i] = RangeToJS(env, ranges[i]);
171 }
172
173 free(ranges);
174
175 return result;
176}
177
178Napi::Value Tree::GetEditedRange(const Napi::CallbackInfo &info) {
179 Napi::Env env = info.Env();
180 TSNode root = ts_tree_root_node(tree_);
181 if (!ts_node_has_changes(root)) {
182 return env.Undefined();
183 }
184 TSRange result = {
185 ts_node_start_point(root),
186 ts_node_end_point(root),
187 ts_node_start_byte(root),
188 ts_node_end_byte(root),
189 };
190
191 TSTreeCursor cursor = ts_tree_cursor_new(root);
192
193 while (true) {
194 if (!ts_tree_cursor_goto_first_child(&cursor)) {
195 break;
196 }
197 while (true) {
198 TSNode node = ts_tree_cursor_current_node(&cursor);
199 if (ts_node_has_changes(node)) {
200 result.start_byte = ts_node_start_byte(node);
201 result.start_point = ts_node_start_point(node);
202 break;
203 }
204 if (!ts_tree_cursor_goto_next_sibling(&cursor)) {
205 break;
206 }
207 }
208 }
209
210 while (ts_tree_cursor_goto_parent(&cursor)) {}
211
212 while (true) {
213 if (!ts_tree_cursor_goto_first_child(&cursor)) {
214 break;
215 }
216 while (true) {
217 TSNode node = ts_tree_cursor_current_node(&cursor);
218 if (ts_node_has_changes(node)) {
219 result.end_byte = ts_node_end_byte(node);
220 result.end_point = ts_node_end_point(node);
221 }
222
223 if (!ts_tree_cursor_goto_next_sibling(&cursor)) {
224 break;
225 }
226 }
227 }
228
229 ts_tree_cursor_delete(&cursor);
230 return RangeToJS(env, result);
231}
232
233Napi::Value Tree::PrintDotGraph(const Napi::CallbackInfo &info) {
234 int fd = fileno(stderr);
235
236 if (info.Length() > 0) {
237 if (!info[0].IsNumber()) {
238 throw TypeError::New(info.Env(), "First argument must be a number");
239 }
240 fd = info[0].As<Number>().Int32Value();
241 }
242
243 ts_tree_print_dot_graph(tree_, fd);
244 return info.This();
245}
246
247namespace {
248
249void FinalizeNode(Napi::Env _env, Tree::NodeCacheEntry *cache_entry) {
250 assert(!cache_entry->node.IsEmpty());
251 cache_entry->node.Reset();
252 if (cache_entry->tree != nullptr) {
253 assert(cache_entry->tree->cached_nodes_.count(cache_entry->key));
254 cache_entry->tree->cached_nodes_.erase(cache_entry->key);
255 }
256 delete cache_entry;
257}
258
259void CacheNodeForTree(Tree *tree, Napi::Env _env, Object js_node) {
260 Value js_node_field1 = js_node[0U];
261 Value js_node_field2 = js_node[1U];
262 if (!js_node_field1.IsNumber() || !js_node_field2.IsNumber()) {
263 return;
264 }
265 uint32_t key_parts[2] = {
266 js_node_field1.As<Number>().Uint32Value(),
267 js_node_field2.As<Number>().Uint32Value(),
268 };
269 const void *key = UnmarshalNodeId(key_parts);
270
271 // A Node could have been garbage collected but the finalizer has not yet run to remove its cache entry
272 if (tree->cached_nodes_.count(key)) {
273 return;
274 }
275
276 auto *cache_entry = new Tree::NodeCacheEntry{tree, key, {}};
277 cache_entry->node = Napi::Weak(js_node);
278 js_node.AddFinalizer(&FinalizeNode, cache_entry);
279
280 tree->cached_nodes_[key] = cache_entry;
281}
282
283} // namespace
284
285Napi::Value Tree::CacheNode(const Napi::CallbackInfo &info) {
286 Napi::Env env = info.Env();
287
288 if (!info[0].IsObject()) {
289 throw TypeError::New(env, "not an object");
290 }
291
292 CacheNodeForTree(this, env, info[0].As<Object>());
293 return env.Undefined();
294}
295
296Napi::Value Tree::CacheNodes(const Napi::CallbackInfo &info) {
297 Napi::Env env = info.Env();
298
299 if (!info[0].IsArray()) {
300 throw TypeError::New(env, "not an array");
301 }
302 auto js_nodes = info[0].As<Array>();
303 uint32_t length = js_nodes.Length();
304
305 for (uint32_t i = 0; i < length; ++i) {
306 Napi::Value js_node = js_nodes[i];
307 if (!js_node.IsObject()) {
308 throw TypeError::New(env, "not an object");
309 }
310 CacheNodeForTree(this, env, js_node.As<Object>());
311 }
312
313 return env.Undefined();
314}
315
316} // namespace node_tree_sitter