OR-1 dataflow CPU sketch
1#include "./parser.h"
2#include "./conversions.h"
3#include "./language.h"
4#include "./logger.h"
5#include "./tree.h"
6
7#include <cstddef>
8#include <napi.h>
9#include <vector>
10
11using namespace Napi;
12using std::vector;
13
14namespace node_tree_sitter {
15
16class CallbackInput final {
17 public:
18 CallbackInput(Function callback, Napi::Value js_buffer_size) {
19 this->callback.Reset(callback, 1);
20 if (js_buffer_size.IsNumber()) {
21 buffer.resize(js_buffer_size.As<Number>().Uint32Value());
22 } else {
23 buffer.resize(static_cast<uint64_t>(32 * 1024));
24 }
25 }
26
27 ~CallbackInput() {
28 callback.Reset();
29 partial_string.Reset();
30 }
31
32 TSInput Input() {
33 TSInput result;
34 result.payload = static_cast<void *>(this);
35 result.encoding = TSInputEncodingUTF16LE;
36 result.read = Read;
37 return result;
38 }
39
40 private:
41 static String slice(String s, uint32_t offset) {
42 Env env = s.Env();
43 auto *data = env.GetInstanceData<AddonData>();
44 return data->string_slice.Call(s, {Number::New(s.Env(), offset)}).As<String>();
45 }
46
47 static const char * Read(void *payload, uint32_t byte, TSPoint position, uint32_t *bytes_read) {
48 auto *reader = static_cast<CallbackInput *>(payload);
49 Napi::Env env = reader->callback.Env();
50
51 if (byte != reader->byte_offset) {
52 reader->byte_offset = byte;
53 reader->partial_string.Reset();
54 }
55
56 *bytes_read = 0;
57 String result;
58 if (!reader->partial_string.IsEmpty()) {
59 result = reader->partial_string.Get("value").As<String>();
60 } else {
61 Function callback = reader->callback.Value();
62 Napi::Value result_value = callback({
63 ByteCountToJS(env, byte),
64 PointToJS(env, position),
65 });
66 if (env.IsExceptionPending()) {
67 return nullptr;
68 }
69 if (!result_value.IsString()) {
70 return nullptr;
71 }
72 result = result_value.As<String>();
73 }
74
75 size_t length = 0;
76 size_t utf16_units_read = 0;
77 napi_status status;
78 status = napi_get_value_string_utf16(
79 env, result, nullptr, 0, &length
80 );
81 if (status != napi_ok) {
82 return nullptr;
83 }
84 status = napi_get_value_string_utf16(
85 env, result, reinterpret_cast<char16_t *>(reader->buffer.data()), reader->buffer.size(), &utf16_units_read
86 );
87 if (status != napi_ok) {
88 return nullptr;
89 }
90
91 *bytes_read = 2 * utf16_units_read;
92 reader->byte_offset += *bytes_read;
93
94 if (utf16_units_read < length) {
95 if (reader->partial_string.IsEmpty()) {
96 reader->partial_string = Napi::Persistent(Object::New(env));
97 }
98 reader->partial_string.Set("value", slice(result, utf16_units_read));
99 } else {
100 reader->partial_string.Reset();
101 }
102
103 return reinterpret_cast<const char *>(reader->buffer.data());
104 }
105
106 FunctionReference callback;
107 std::vector<uint16_t> buffer;
108 size_t byte_offset {};
109 ObjectReference partial_string;
110};
111
112void Parser::Init(Napi::Env env, Napi::Object exports) {
113 auto *data = env.GetInstanceData<AddonData>();
114
115 Function ctor = DefineClass(env, "Parser", {
116 InstanceMethod("setLanguage", &Parser::SetLanguage, napi_default_method),
117 InstanceMethod("parse", &Parser::Parse, napi_default_method),
118 InstanceMethod("getIncludedRanges", &Parser::IncludedRanges, napi_default_method),
119 InstanceMethod("getTimeoutMicros", &Parser::TimeoutMicros, napi_default_method),
120 InstanceMethod("setTimeoutMicros", &Parser::SetTimeoutMicros, napi_default_method),
121 InstanceMethod("getLogger", &Parser::GetLogger, napi_default_method),
122 InstanceMethod("setLogger", &Parser::SetLogger, napi_default_method),
123 InstanceMethod("printDotGraphs", &Parser::PrintDotGraphs, napi_default_method),
124 InstanceMethod("reset", &Parser::Reset, napi_default_method),
125 });
126
127 data->parser_constructor = Napi::Persistent(ctor);
128 exports["Parser"] = ctor;
129 exports["LANGUAGE_VERSION"] = Number::New(env, TREE_SITTER_LANGUAGE_VERSION);
130
131 String s = String::New(env, "");
132 Napi::Value string_slice_value = s.As<Object>()["slice"];
133 data->string_slice = Napi::Persistent(string_slice_value.As<Function>());
134}
135
136Parser::Parser(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Parser>(info), parser_(ts_parser_new()) {}
137
138Parser::~Parser() {
139 ts_parser_print_dot_graphs(parser_, -1);
140 ts_parser_set_logger(parser_, { nullptr, nullptr });
141 ts_parser_delete(parser_);
142}
143
144namespace {
145
146bool handle_included_ranges(Napi::Env env, TSParser *parser, Napi::Value arg) {
147 uint32_t last_included_range_end = 0;
148 if (arg.IsArray()) {
149 auto js_included_ranges = arg.As<Array>();
150 vector<TSRange> included_ranges;
151 for (unsigned i = 0; i < js_included_ranges.Length(); i++) {
152 Value range_value = js_included_ranges[i];
153 if (!range_value.IsObject()) {
154 return false;
155 }
156 auto maybe_range = RangeFromJS(range_value);
157 if (!maybe_range.IsJust()) {
158 return false;
159 }
160 auto range = maybe_range.Unwrap();
161 if (range.start_byte < last_included_range_end) {
162 throw RangeError::New(env, "Overlapping ranges");
163 }
164 last_included_range_end = range.end_byte;
165 included_ranges.push_back(range);
166 }
167 ts_parser_set_included_ranges(parser, included_ranges.data(), included_ranges.size());
168 } else {
169 ts_parser_set_included_ranges(parser, nullptr, 0);
170 }
171
172 return true;
173}
174
175} // namespace
176
177Napi::Value Parser::SetLanguage(const Napi::CallbackInfo &info) {
178 const TSLanguage *language = language_methods::UnwrapLanguage(info[0]);
179 if (language != nullptr) {
180 ts_parser_set_language(parser_, language);
181 return info.This();
182 }
183 return info.Env().Undefined();
184}
185
186Napi::Value Parser::Parse(const CallbackInfo &info) {
187 Napi::Env env = info.Env();
188
189 if (!info[0].IsFunction()) {
190 throw TypeError::New(env, "Input must be a function");
191 }
192
193 auto callback = info[0].As<Function>();
194
195 Object js_old_tree;
196 const TSTree *old_tree = nullptr;
197 if (info.Length() > 1 && !info[1].IsNull() && !info[1].IsUndefined() && info[1].IsObject()) {
198 js_old_tree = info[1].As<Object>();
199 const Tree *tree = Tree::UnwrapTree(js_old_tree);
200 if (tree == nullptr) {
201 throw TypeError::New(env, "Second argument must be a tree");
202 }
203 old_tree = tree->tree_;
204 }
205
206 Napi::Value buffer_size = env.Null();
207 if (info.Length() > 2) {
208 buffer_size = info[2];
209 }
210
211 if (!handle_included_ranges(env, parser_, info[3])) {
212 return env.Undefined();
213 }
214
215 CallbackInput callback_input(callback, buffer_size);
216 TSTree *tree = ts_parser_parse(parser_, old_tree, callback_input.Input());
217 Napi::Value result = Tree::NewInstance(env, tree);
218 return result;
219}
220
221Napi::Value Parser::IncludedRanges(const Napi::CallbackInfo &info) {
222 Napi::Env env = info.Env();
223 uint32_t count;
224 const TSRange *ranges = ts_parser_included_ranges(parser_, &count);
225
226 Napi::Array result = Napi::Array::New(env, count);
227 for (uint32_t i = 0; i < count; i++) {
228 result[i] = RangeToJS(env, ranges[i]);
229 }
230
231 return result;
232}
233
234Napi::Value Parser::TimeoutMicros(const Napi::CallbackInfo &info) {
235 uint64_t timeout_micros = ts_parser_timeout_micros(parser_);
236 return Number::New(info.Env(), static_cast<double>(timeout_micros));
237}
238
239Napi::Value Parser::SetTimeoutMicros(const Napi::CallbackInfo &info) {
240 uint64_t timeout_micros;
241 if (!info[0].IsNumber()) {
242 throw TypeError::New(info.Env(), "First argument must be a number");
243 }
244 timeout_micros = info[0].As<Number>().Uint32Value();
245 ts_parser_set_timeout_micros(parser_, timeout_micros);
246 return info.This();
247}
248
249Napi::Value Parser::GetLogger(const Napi::CallbackInfo &info) {
250 TSLogger current_logger = ts_parser_logger(parser_);
251 if ((current_logger.payload != nullptr) && current_logger.log == Logger::Log) {
252 auto *logger = static_cast<Logger *>(current_logger.payload);
253 return logger->func.Value();
254 }
255 return info.Env().Null();
256}
257
258Napi::Value Parser::SetLogger(const Napi::CallbackInfo &info) {
259 TSLogger current_logger = ts_parser_logger(parser_);
260
261 if (info[0].IsFunction()) {
262 if (current_logger.payload != nullptr) {
263 delete static_cast<Logger *>(current_logger.payload);
264 }
265 ts_parser_set_logger(parser_, Logger::Make(info[0].As<Function>()));
266 } else if (info[0].IsEmpty() || info[0].IsUndefined() || info[0].IsNull() || (info[0].IsBoolean() && !info[0].As<Boolean>())) {
267 if (current_logger.payload != nullptr) {
268 delete static_cast<Logger *>(current_logger.payload);
269 }
270 ts_parser_set_logger(parser_, { nullptr, nullptr });
271 } else {
272 throw TypeError::New(info.Env(), "Logger callback must either be a function or a falsy value");
273 }
274
275 return info.This();
276}
277
278Napi::Value Parser::PrintDotGraphs(const Napi::CallbackInfo &info) {
279 bool should_print = true;
280 int fd = fileno(stderr);
281
282 if (info.Length() > 0) {
283 if (!info[0].IsBoolean()) {
284 throw TypeError::New(info.Env(), "First argument must be a boolean");
285 }
286 should_print = info[0].As<Boolean>();
287 }
288
289 if (info.Length() > 1) {
290 if (!info[1].IsNumber()) {
291 throw TypeError::New(info.Env(), "Second argument must be a number");
292 }
293 fd = info[1].As<Number>().Int32Value();
294 }
295
296 ts_parser_print_dot_graphs(parser_, should_print ? fd : -1);
297
298 return info.This();
299}
300
301Napi::Value Parser::Reset(const Napi::CallbackInfo & info) {
302 ts_parser_reset(parser_);
303 return info.This();
304}
305
306} // namespace node_tree_sitter