OR-1 dataflow CPU sketch
at main 316 lines 9.3 kB view raw
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