From 67f3f0063a8cb99e4ea06882f4c69288cf4b77d7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 4 Nov 2019 12:38:13 -0800 Subject: [PATCH] Convert module to use NAPI instead of Nan --- binding.gyp | 12 +- index.js | 1 + package.json | 1 + src/binding.cc | 18 +- src/conversions.cc | 165 +++++----- src/conversions.h | 23 +- src/language.cc | 101 +++--- src/language.h | 10 +- src/logger.cc | 57 ++-- src/logger.h | 7 +- src/node.cc | 704 +++++++++++++++++++++-------------------- src/node.h | 24 +- src/optional.h | 29 ++ src/parser.cc | 739 +++++++++++++++++++++++--------------------- src/parser.h | 27 +- src/tree.cc | 180 +++++------ src/tree.h | 35 +-- src/tree_cursor.cc | 265 ++++++++-------- src/tree_cursor.h | 35 +-- src/util.h | 58 +++- test/parser_test.js | 4 +- test/tree_test.js | 2 +- vendor/tree-sitter | 2 +- 23 files changed, 1253 insertions(+), 1246 deletions(-) create mode 100644 src/optional.h diff --git a/binding.gyp b/binding.gyp index 4dc4cfd0..bb43e646 100644 --- a/binding.gyp +++ b/binding.gyp @@ -2,7 +2,13 @@ "targets": [ { "target_name": "tree_sitter_runtime_binding", - "dependencies": ["tree_sitter"], + "dependencies": [ + "tree_sitter", + " -#include +#include #include "./language.h" #include "./node.h" #include "./parser.h" @@ -9,17 +8,18 @@ namespace node_tree_sitter { -using namespace v8; +using namespace Napi; -void InitAll(Local exports) { +Object Init(Env env, Object exports) { InitConversions(exports); - node_methods::Init(exports); - language_methods::Init(exports); - Parser::Init(exports); + InitNode(exports); + InitLanguage(exports); + InitParser(exports); + InitTreeCursor(exports); Tree::Init(exports); - TreeCursor::Init(exports); + return exports; } -NODE_MODULE(tree_sitter_runtime_binding, InitAll) +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) } // namespace node_tree_sitter diff --git a/src/conversions.cc b/src/conversions.cc index 92c92c67..4b0e1c85 100644 --- a/src/conversions.cc +++ b/src/conversions.cc @@ -1,35 +1,30 @@ -#include "./node.h" -#include +#include #include -#include +#include "./node.h" #include "./conversions.h" #include namespace node_tree_sitter { -using namespace v8; - -Nan::Persistent row_key; -Nan::Persistent column_key; -Nan::Persistent start_index_key; -Nan::Persistent start_position_key; -Nan::Persistent end_index_key; -Nan::Persistent end_position_key; +using namespace Napi; static unsigned BYTES_PER_CHARACTER = 2; static uint32_t *point_transfer_buffer; -void InitConversions(Local exports) { - row_key.Reset(Nan::Persistent(Nan::New("row").ToLocalChecked())); - column_key.Reset(Nan::Persistent(Nan::New("column").ToLocalChecked())); - start_index_key.Reset(Nan::Persistent(Nan::New("startIndex").ToLocalChecked())); - start_position_key.Reset(Nan::Persistent(Nan::New("startPosition").ToLocalChecked())); - end_index_key.Reset(Nan::Persistent(Nan::New("endIndex").ToLocalChecked())); - end_position_key.Reset(Nan::Persistent(Nan::New("endPosition").ToLocalChecked())); - +void InitConversions(Object &exports) { + auto env = exports.Env(); point_transfer_buffer = static_cast(malloc(2 * sizeof(uint32_t))); - auto js_point_transfer_buffer = ArrayBuffer::New(Isolate::GetCurrent(), point_transfer_buffer, 2 * sizeof(uint32_t)); - Nan::Set(exports, Nan::New("pointTransferArray").ToLocalChecked(), Uint32Array::New(js_point_transfer_buffer, 0, 2)); + auto js_point_transfer_buffer = ArrayBuffer::New( + env, + static_cast(point_transfer_buffer), + 2 * sizeof(uint32_t) + ); + exports.Set("pointTransferArray", Uint32Array::New( + env, + 2, + js_point_transfer_buffer, + 0 + )); } void TransferPoint(const TSPoint &point) { @@ -37,110 +32,116 @@ void TransferPoint(const TSPoint &point) { point_transfer_buffer[1] = point.column / 2; } -Local RangeToJS(const TSRange &range) { - Local result = Nan::New(); - Nan::Set(result, Nan::New(start_position_key), PointToJS(range.start_point)); - Nan::Set(result, Nan::New(start_index_key), ByteCountToJS(range.start_byte)); - Nan::Set(result, Nan::New(end_position_key), PointToJS(range.end_point)); - Nan::Set(result, Nan::New(end_index_key), ByteCountToJS(range.end_byte)); +Object RangeToJS(Env env, const TSRange &range) { + Object result = Object::New(env); + result.Set("startPosition", PointToJS(env, range.start_point)); + result.Set("startIndex", ByteCountToJS(env, range.start_byte)); + result.Set("endPosition", PointToJS(env, range.end_point)); + result.Set("endIndex", ByteCountToJS(env, range.end_byte)); return result; } -Nan::Maybe RangeFromJS(const Local &arg) { - if (!arg->IsObject()) { - Nan::ThrowTypeError("Range must be a {startPosition, endPosition, startIndex, endIndex} object"); - return Nan::Nothing(); +optional RangeFromJS(const Value &arg) { + Env env = arg.Env(); + + if (!arg.IsObject()) { + TypeError::New(env, "Range must be a {startPosition, endPosition, startIndex, endIndex} object").ThrowAsJavaScriptException(); + return optional(); } TSRange result; - - Local js_range = Local::Cast(arg); + Object js_range = arg.ToObject(); #define INIT(field, key, Convert) { \ - auto value = Nan::Get(js_range, Nan::New(key)); \ + auto value = js_range.Get(key); \ if (value.IsEmpty()) { \ - Nan::ThrowTypeError("Range must be a {startPosition, endPosition, startIndex, endIndex} object"); \ - return Nan::Nothing(); \ + TypeError::New(env, "Range must be a {startPosition, endPosition, startIndex, endIndex} object").ThrowAsJavaScriptException(); \ + return optional(); \ } \ - auto field = Convert(value.ToLocalChecked()); \ - if (field.IsJust()) { \ - result.field = field.FromJust(); \ + auto field = Convert(value); \ + if (field) { \ + result.field = *field; \ } else { \ - return Nan::Nothing(); \ + return optional(); \ } \ } - INIT(start_point, start_position_key, PointFromJS); - INIT(end_point, end_position_key, PointFromJS); - INIT(start_byte, start_index_key, ByteCountFromJS); - INIT(end_byte, end_index_key, ByteCountFromJS); + INIT(start_point, "startPosition", PointFromJS); + INIT(end_point, "endPosition", PointFromJS); + INIT(start_byte, "startIndex", ByteCountFromJS); + INIT(end_byte, "endIndex", ByteCountFromJS); #undef INIT - return Nan::Just(result); + return result; } -Local PointToJS(const TSPoint &point) { - Local result = Nan::New(); - Nan::Set(result, Nan::New(row_key), Nan::New(point.row)); - Nan::Set(result, Nan::New(column_key), ByteCountToJS(point.column)); +Object PointToJS(Env env, const TSPoint &point) { + Object result = Object::New(env); + result["row"] = Number::New(env, point.row); + result["column"] = ByteCountToJS(env, point.column); return result; } -Nan::Maybe PointFromJS(const Local &arg) { - Local js_point; - if (!arg->IsObject() || !Nan::To(arg).ToLocal(&js_point)) { - Nan::ThrowTypeError("Point must be a {row, column} object"); - return Nan::Nothing(); +Number ByteCountToJS(Env env, uint32_t byte_count) { + return Number::New(env, byte_count / BYTES_PER_CHARACTER); +} + +optional PointFromJS(const Value &arg) { + Env env = arg.Env(); + + if (!arg.IsObject()) { + TypeError::New(env, "Point must be a {row, column} object").ThrowAsJavaScriptException(); + return optional(); } - Local js_row; - if (!Nan::Get(js_point, Nan::New(row_key)).ToLocal(&js_row)) { - Nan::ThrowTypeError("Point must be a {row, column} object"); - return Nan::Nothing(); + Object js_point = arg.ToObject(); + + Number js_row = js_point.Get("row").As(); + if (!js_row.IsNumber()) { + TypeError::New(env, "Point must be a {row, column} object").ThrowAsJavaScriptException(); + return optional(); } - Local js_column; - if (!Nan::Get(js_point, Nan::New(column_key)).ToLocal(&js_column)) { - Nan::ThrowTypeError("Point must be a {row, column} object"); - return Nan::Nothing(); + Number js_column = js_point.Get("column").As(); + if (!js_column.IsNumber()) { + TypeError::New(env, "Point must be a {row, column} object").ThrowAsJavaScriptException(); + return optional(); } uint32_t row; - if (!std::isfinite(Nan::To(js_row).FromMaybe(0))) { + if (!std::isfinite(js_row.DoubleValue())) { row = UINT32_MAX; - } else if (js_row->IsNumber()) { - row = Nan::To(js_row).FromJust(); } else { - Nan::ThrowTypeError("Point.row must be a number"); - return Nan::Nothing(); + row = js_row.Uint32Value(); } uint32_t column; - if (!std::isfinite(Nan::To(js_column).FromMaybe(0))) { + if (!std::isfinite(js_column.DoubleValue())) { column = UINT32_MAX; - } else if (js_column->IsNumber()) { - column = Nan::To(js_column).FromMaybe(0) * BYTES_PER_CHARACTER; } else { - Nan::ThrowTypeError("Point.column must be a number"); - return Nan::Nothing(); + column = js_column.Uint32Value() * BYTES_PER_CHARACTER; } - return Nan::Just({row, column}); + return TSPoint{row, column}; } -Local ByteCountToJS(uint32_t byte_count) { - return Nan::New(byte_count / BYTES_PER_CHARACTER); -} +optional ByteCountFromJS(const Value &arg) { + Env env = arg.Env(); -Nan::Maybe ByteCountFromJS(const v8::Local &arg) { - auto result = Nan::To(arg); - if (!arg->IsNumber()) { - Nan::ThrowTypeError("Character index must be a number"); - return Nan::Nothing(); + if (!arg.IsNumber()) { + if (!env.IsExceptionPending()) { + TypeError::New(env, "Character index must be a number").ThrowAsJavaScriptException(); + } + return optional(); } - return Nan::Just(result.FromJust() * BYTES_PER_CHARACTER); + Number js_number = arg.ToNumber(); + if (!std::isfinite(js_number.DoubleValue())) { + return UINT32_MAX; + } else { + return js_number.Uint32Value() * BYTES_PER_CHARACTER; + } } } // namespace node_tree_sitter diff --git a/src/conversions.h b/src/conversions.h index 69bcfb51..649561b5 100644 --- a/src/conversions.h +++ b/src/conversions.h @@ -1,25 +1,22 @@ #ifndef NODE_TREE_SITTER_CONVERSIONS_H_ #define NODE_TREE_SITTER_CONVERSIONS_H_ -#include -#include +#include #include +#include "./optional.h" namespace node_tree_sitter { -void InitConversions(v8::Local exports); -v8::Local RangeToJS(const TSRange &); -v8::Local PointToJS(const TSPoint &); +void InitConversions(Napi::Object &); void TransferPoint(const TSPoint &); -v8::Local ByteCountToJS(uint32_t); -Nan::Maybe PointFromJS(const v8::Local &); -Nan::Maybe ByteCountFromJS(const v8::Local &); -Nan::Maybe RangeFromJS(const v8::Local &); -extern Nan::Persistent row_key; -extern Nan::Persistent column_key; -extern Nan::Persistent start_key; -extern Nan::Persistent end_key; +Napi::Object RangeToJS(Napi::Env, const TSRange &); +Napi::Object PointToJS(Napi::Env, const TSPoint &); +Napi::Number ByteCountToJS(Napi::Env, uint32_t); + +optional PointFromJS(const Napi::Value &); +optional ByteCountFromJS(const Napi::Value &); +optional RangeFromJS(const Napi::Value &); } // namespace node_tree_sitter diff --git a/src/language.cc b/src/language.cc index bdfb90f1..208bd8d6 100644 --- a/src/language.cc +++ b/src/language.cc @@ -1,92 +1,89 @@ #include "./language.h" -#include +#include #include #include #include -#include +#include "./util.h" namespace node_tree_sitter { -namespace language_methods { using std::vector; -using namespace v8; - -const TSLanguage *UnwrapLanguage(const v8::Local &value) { - if (value->IsObject()) { - Local arg = Local::Cast(value); - if (arg->InternalFieldCount() == 1) { - const TSLanguage *language = (const TSLanguage *)Nan::GetInternalFieldPointer(arg, 0); - if (language) { - uint16_t version = ts_language_version(language); - if ( - version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION || - version > TREE_SITTER_LANGUAGE_VERSION - ) { - std::string message = - "Incompatible language version. Compatible range: " + - std::to_string(TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION) + " - " + - std::to_string(TREE_SITTER_LANGUAGE_VERSION) + ". Got: " + - std::to_string(ts_language_version(language)); - Nan::ThrowError(Nan::RangeError(message.c_str())); - return nullptr; - } - return language; - } +using namespace Napi; + + +const TSLanguage *UnwrapLanguage(const Napi::Value &value) { + Env env = value.Env(); + + const TSLanguage *language = static_cast( + GetInternalFieldPointer(value) + ); + + if (language) { + uint16_t version = ts_language_version(language); + if ( + version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION || + version > TREE_SITTER_LANGUAGE_VERSION + ) { + std::string message = + "Incompatible language version. Compatible range: " + + std::to_string(TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION) + " - " + + std::to_string(TREE_SITTER_LANGUAGE_VERSION) + ". Got: " + + std::to_string(ts_language_version(language)); + RangeError::New(env, message.c_str()).ThrowAsJavaScriptException(); + return nullptr; } + return language; } - Nan::ThrowTypeError("Invalid language object"); + + TypeError::New(env, "Invalid language object").ThrowAsJavaScriptException(); return nullptr; } -static void GetNodeTypeNamesById(const Nan::FunctionCallbackInfo &info) { +static Value GetNodeTypeNamesById(const CallbackInfo &info) { + Env env = info.Env(); + const TSLanguage *language = UnwrapLanguage(info[0]); - if (!language) return; + if (!language) return env.Null(); - auto result = Nan::New(); + Array result = Array::New(env); uint32_t length = ts_language_symbol_count(language); for (uint32_t i = 0; i < length; i++) { const char *name = ts_language_symbol_name(language, i); TSSymbolType type = ts_language_symbol_type(language, i); if (type == TSSymbolTypeRegular) { - Nan::Set(result, i, Nan::New(name).ToLocalChecked()); + result[i] = String::New(env, name); } else { - Nan::Set(result, i, Nan::Null()); + result[i] = env.Null(); } } - info.GetReturnValue().Set(result); + return result; } -static void GetNodeFieldNamesById(const Nan::FunctionCallbackInfo &info) { +static Value GetNodeFieldNamesById(const CallbackInfo &info) { + Env env = info.Env(); + const TSLanguage *language = UnwrapLanguage(info[0]); - if (!language) return; + if (!language) return env.Null(); - auto result = Nan::New(); + Array result = Array::New(env); uint32_t length = ts_language_field_count(language); for (uint32_t i = 0; i < length + 1; i++) { const char *name = ts_language_field_name_for_id(language, i); if (name) { - Nan::Set(result, i, Nan::New(name).ToLocalChecked()); + result[i] = String::New(env, name); } else { - Nan::Set(result, i, Nan::Null()); + result[i] = env.Null(); } } - info.GetReturnValue().Set(result); -} -void Init(Local exports) { - Nan::Set( - exports, - Nan::New("getNodeTypeNamesById").ToLocalChecked(), - Nan::GetFunction(Nan::New(GetNodeTypeNamesById)).ToLocalChecked() - ); + return result; +} - Nan::Set( - exports, - Nan::New("getNodeFieldNamesById").ToLocalChecked(), - Nan::GetFunction(Nan::New(GetNodeFieldNamesById)).ToLocalChecked() - ); +void InitLanguage(Object &exports) { + Env env = exports.Env(); + exports["getNodeTypeNamesById"] = Function::New(env, GetNodeTypeNamesById); + exports["getNodeFieldNamesById"] = Function::New(env, GetNodeFieldNamesById); } -} // namespace language_methods } // namespace node_tree_sitter diff --git a/src/language.h b/src/language.h index 76f7ac28..cde411fd 100644 --- a/src/language.h +++ b/src/language.h @@ -1,20 +1,16 @@ #ifndef NODE_TREE_SITTER_LANGUAGE_H_ #define NODE_TREE_SITTER_LANGUAGE_H_ -#include -#include -#include +#include #include #include "./tree.h" namespace node_tree_sitter { -namespace language_methods { -void Init(v8::Local); +void InitLanguage(Napi::Object &); -const TSLanguage *UnwrapLanguage(const v8::Local &); +const TSLanguage *UnwrapLanguage(const Napi::Value &); -} // namespace language_methods } // namespace node_tree_sitter #endif // NODE_TREE_SITTER_LANGUAGE_H_ diff --git a/src/logger.cc b/src/logger.cc index 5f0cacd1..2d1148b1 100644 --- a/src/logger.cc +++ b/src/logger.cc @@ -1,34 +1,34 @@ #include "./logger.h" #include -#include -#include +#include #include namespace node_tree_sitter { -using namespace v8; +using namespace Napi; using std::string; void Logger::Log(void *payload, TSLogType type, const char *message_str) { Logger *debugger = (Logger *)payload; - Local fn = Nan::New(debugger->func); - if (!fn->IsFunction()) - return; + Function fn = debugger->func.Value(); + if (!fn.IsFunction()) return; + Env env = fn.Env(); string message(message_str); string param_sep = " "; size_t param_sep_pos = message.find(param_sep, 0); - Local type_name = Nan::New((type == TSLogTypeParse) ? "parse" : "lex").ToLocalChecked(); - Local name = Nan::New(message.substr(0, param_sep_pos)).ToLocalChecked(); - Local params = Nan::New(); + String type_name = String::New( + env, + type == TSLogTypeParse ? "parse" : "lex" + ); + String name = String::New(env, message.substr(0, param_sep_pos)); + Object params = Object::New(env); while (param_sep_pos != string::npos) { size_t key_pos = param_sep_pos + param_sep.size(); size_t value_sep_pos = message.find(":", key_pos); - - if (value_sep_pos == string::npos) - break; + if (value_sep_pos == string::npos) break; size_t val_pos = value_sep_pos + 1; param_sep = ", "; @@ -36,29 +36,30 @@ void Logger::Log(void *payload, TSLogType type, const char *message_str) { string key = message.substr(key_pos, (value_sep_pos - key_pos)); string value = message.substr(val_pos, (param_sep_pos - val_pos)); - Nan::Set(params, Nan::New(key).ToLocalChecked(), Nan::New(value).ToLocalChecked()); + params[key] = String::New(env, value); } - Local argv[3] = { name, params, type_name }; - TryCatch try_catch(Isolate::GetCurrent()); - Nan::Call(fn, fn->CreationContext()->Global(), 3, argv); - if (try_catch.HasCaught()) { - Local log_argv[2] = { - Nan::New("Error in debug callback:").ToLocalChecked(), - try_catch.Exception() - }; - - Local console = Local::Cast(Nan::Get(fn->CreationContext()->Global(), Nan::New("console").ToLocalChecked()).ToLocalChecked()); - Local error_fn = Local::Cast(Nan::Get(console, Nan::New("error").ToLocalChecked()).ToLocalChecked()); - Nan::Call(error_fn, console, 2, log_argv); + fn({ name, params, type_name }); + if (env.IsExceptionPending()) { + Error error = env.GetAndClearPendingException(); + Value console = env.Global()["console"]; + if (console.IsObject()) { + Value console_error_fn = console.ToObject()["error"]; + if (console_error_fn.IsFunction()) { + console_error_fn.As()({ + String::New(env, "Error in debug callback:"), + error.Value() + }); + } + } } } -TSLogger Logger::Make(Local func) { +TSLogger Logger::Make(Function func) { TSLogger result; Logger *logger = new Logger(); - logger->func.Reset(Nan::Persistent(func)); - result.payload = (void *)logger; + logger->func.Reset(func, 1); + result.payload = static_cast(logger); result.log = Log; return result; } diff --git a/src/logger.h b/src/logger.h index af68413f..105b4659 100644 --- a/src/logger.h +++ b/src/logger.h @@ -1,16 +1,15 @@ #ifndef NODE_TREE_SITTER_LOGGER_H_ #define NODE_TREE_SITTER_LOGGER_H_ -#include -#include +#include #include namespace node_tree_sitter { class Logger { public: - static TSLogger Make(v8::Local); - Nan::Persistent func; + static TSLogger Make(Napi::Function); + Napi::FunctionReference func; static void Log(void *, TSLogType, const char *); }; diff --git a/src/node.cc b/src/node.cc index 14054544..e225bac9 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1,36 +1,39 @@ #include "./node.h" -#include #include +#include #include -#include #include "./util.h" #include "./conversions.h" #include "./tree.h" #include "./tree_cursor.h" namespace node_tree_sitter { -namespace node_methods { using std::vector; -using namespace v8; +using namespace Napi; static const uint32_t FIELD_COUNT_PER_NODE = 6; static uint32_t *transfer_buffer = nullptr; static uint32_t transfer_buffer_length = 0; -static Nan::Persistent module_exports; +static ObjectReference module_exports; static TSTreeCursor scratch_cursor = {nullptr, nullptr, {0, 0}}; -static inline void setup_transfer_buffer(uint32_t node_count) { +static inline void setup_transfer_buffer(Env env, uint32_t node_count) { uint32_t new_length = node_count * FIELD_COUNT_PER_NODE; if (new_length > transfer_buffer_length) { transfer_buffer_length = new_length; transfer_buffer = static_cast(malloc(transfer_buffer_length * sizeof(uint32_t))); - auto js_transfer_buffer = ArrayBuffer::New(Isolate::GetCurrent(), transfer_buffer, transfer_buffer_length * sizeof(uint32_t)); - Nan::Set( - Nan::New(module_exports), - Nan::New("nodeTransferArray").ToLocalChecked(), - Uint32Array::New(js_transfer_buffer, 0, transfer_buffer_length) + auto js_transfer_buffer = ArrayBuffer::New( + env, + transfer_buffer, + transfer_buffer_length * sizeof(uint32_t) + ); + module_exports.Value()["nodeTransferArray"] = Uint32Array::New( + env, + transfer_buffer_length, + js_transfer_buffer, + 0 ); } } @@ -41,65 +44,76 @@ static inline bool operator<=(const TSPoint &left, const TSPoint &right) { return left.column <= right.column; } -static void MarshalNodes(const Nan::FunctionCallbackInfo &info, - const Tree *tree, const TSNode *nodes, uint32_t node_count) { - auto result = Nan::New(); - setup_transfer_buffer(node_count); +static Value MarshalNodes( + Env env, + const Tree *tree, + const TSNode *nodes, + uint32_t node_count +) { + Array result = Array::New(env); + setup_transfer_buffer(env, node_count); uint32_t *p = transfer_buffer; for (unsigned i = 0; i < node_count; i++) { TSNode node = nodes[i]; const auto &cache_entry = tree->cached_nodes_.find(node.id); if (cache_entry == tree->cached_nodes_.end()) { - MarshalNodeId(node.id, p); + MarshalPointer(node.id, p); p += 2; *(p++) = node.context[0]; *(p++) = node.context[1]; *(p++) = node.context[2]; *(p++) = node.context[3]; if (node.id) { - Nan::Set(result, i, Nan::New(ts_node_symbol(node))); + result[i] = Number::New(env, ts_node_symbol(node)); } else { - Nan::Set(result, i, Nan::Null()); + result[i] = env.Null(); } } else { - Nan::Set(result, i, Nan::New(cache_entry->second->node)); + result[i] = cache_entry->second->node.Value(); } } - info.GetReturnValue().Set(result); + return result; } -void MarshalNode(const Nan::FunctionCallbackInfo &info, const Tree *tree, TSNode node) { +Value MarshalNode( + Env env, + const Tree *tree, + TSNode node +) { const auto &cache_entry = tree->cached_nodes_.find(node.id); if (cache_entry == tree->cached_nodes_.end()) { - setup_transfer_buffer(1); + setup_transfer_buffer(env, 1); uint32_t *p = transfer_buffer; - MarshalNodeId(node.id, p); + MarshalPointer(node.id, p); p += 2; *(p++) = node.context[0]; *(p++) = node.context[1]; *(p++) = node.context[2]; *(p++) = node.context[3]; if (node.id) { - info.GetReturnValue().Set(Nan::New(ts_node_symbol(node))); + return Number::New(env, ts_node_symbol(node)); + } else { + return env.Null(); } } else { - info.GetReturnValue().Set(Nan::New(cache_entry->second->node)); + return cache_entry->second->node.Value(); } } -void MarshalNullNode() { +Value MarshalNullNode(Env env) { memset(transfer_buffer, 0, FIELD_COUNT_PER_NODE * sizeof(transfer_buffer[0])); + return env.Null(); } -TSNode UnmarshalNode(const Tree *tree) { +TSNode UnmarshalNode(Env env, const Tree *tree) { TSNode result = {{0, 0, 0, 0}, nullptr, nullptr}; - result.tree = tree->tree_; - if (!result.tree) { - Nan::ThrowTypeError("Argument must be a tree"); + if (!tree) { + TypeError::New(env, "Argument must be a tree").ThrowAsJavaScriptException(); return result; } - result.id = UnmarshalNodeId(&transfer_buffer[0]); + result.tree = tree->tree_; + result.id = UnmarshalPointer(&transfer_buffer[0]); result.context[0] = transfer_buffer[2]; result.context[1] = transfer_buffer[3]; result.context[2] = transfer_buffer[4]; @@ -107,350 +121,375 @@ TSNode UnmarshalNode(const Tree *tree) { return result; } -static void ToString(const Nan::FunctionCallbackInfo &info) { +static Value ToString(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - const char *string = ts_node_string(node); - info.GetReturnValue().Set(Nan::New(string).ToLocalChecked()); - free((char *)string); + char *string = ts_node_string(node); + String result = String::New(env, string); + free(string); + return result; } + return env.Undefined(); } -static void IsMissing(const Nan::FunctionCallbackInfo &info) { +static Value IsMissing(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { bool result = ts_node_is_missing(node); - info.GetReturnValue().Set(Nan::New(result)); + return Boolean::New(env, result); } + return env.Undefined(); } -static void HasChanges(const Nan::FunctionCallbackInfo &info) { +static Value HasChanges(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { bool result = ts_node_has_changes(node); - info.GetReturnValue().Set(Nan::New(result)); + return Boolean::New(env, result); } + return env.Undefined(); } -static void HasError(const Nan::FunctionCallbackInfo &info) { +static Value HasError(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { bool result = ts_node_has_error(node); - info.GetReturnValue().Set(Nan::New(result)); + return Boolean::New(env, result); } + return env.Undefined(); } -static void FirstNamedChildForIndex(const Nan::FunctionCallbackInfo &info) { +static Value FirstNamedChildForIndex(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - Nan::Maybe byte = ByteCountFromJS(info[1]); - if (byte.IsJust()) { - MarshalNode(info, tree, ts_node_first_named_child_for_byte(node, byte.FromJust())); - return; + auto byte = ByteCountFromJS(info[1]); + if (byte) { + return MarshalNode(env, tree, ts_node_first_named_child_for_byte(node, *byte)); } } - MarshalNullNode(); + return MarshalNullNode(env); } -static void FirstChildForIndex(const Nan::FunctionCallbackInfo &info) { +static Value FirstChildForIndex(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id && info.Length() > 1) { - Nan::Maybe byte = ByteCountFromJS(info[1]); - if (byte.IsJust()) { - MarshalNode(info, tree, ts_node_first_child_for_byte(node, byte.FromJust())); - return; + optional byte = ByteCountFromJS(info[1]); + if (byte) { + return MarshalNode(env, tree, ts_node_first_child_for_byte(node, *byte)); } } - MarshalNullNode(); + return MarshalNullNode(env); } -static void NamedDescendantForIndex(const Nan::FunctionCallbackInfo &info) { +static Value NamedDescendantForIndex(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - Nan::Maybe maybe_min = ByteCountFromJS(info[1]); - Nan::Maybe maybe_max = ByteCountFromJS(info[2]); - if (maybe_min.IsJust() && maybe_max.IsJust()) { - uint32_t min = maybe_min.FromJust(); - uint32_t max = maybe_max.FromJust(); - MarshalNode(info, tree, ts_node_named_descendant_for_byte_range(node, min, max)); - return; + optional maybe_min = ByteCountFromJS(info[1]); + if (maybe_min) { + optional maybe_max = ByteCountFromJS(info[2]); + if (maybe_max) { + uint32_t min = *maybe_min; + uint32_t max = *maybe_max; + return MarshalNode(env, tree, ts_node_named_descendant_for_byte_range(node, min, max)); + } } } - MarshalNullNode(); + return MarshalNullNode(env); } -static void DescendantForIndex(const Nan::FunctionCallbackInfo &info) { +static Value DescendantForIndex(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - Nan::Maybe maybe_min = ByteCountFromJS(info[1]); - Nan::Maybe maybe_max = ByteCountFromJS(info[2]); - if (maybe_min.IsJust() && maybe_max.IsJust()) { - uint32_t min = maybe_min.FromJust(); - uint32_t max = maybe_max.FromJust(); - MarshalNode(info, tree, ts_node_descendant_for_byte_range(node, min, max)); - return; + optional maybe_min = ByteCountFromJS(info[1]); + if (maybe_min) { + optional maybe_max = ByteCountFromJS(info[2]); + if (maybe_max) { + uint32_t min = *maybe_min; + uint32_t max = *maybe_max; + return MarshalNode(env, tree, ts_node_descendant_for_byte_range(node, min, max)); + } } } - MarshalNullNode(); + return MarshalNullNode(env); } -static void NamedDescendantForPosition(const Nan::FunctionCallbackInfo &info) { +static Value NamedDescendantForPosition(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - Nan::Maybe maybe_min = PointFromJS(info[1]); - Nan::Maybe maybe_max = PointFromJS(info[2]); - if (maybe_min.IsJust() && maybe_max.IsJust()) { - TSPoint min = maybe_min.FromJust(); - TSPoint max = maybe_max.FromJust(); - MarshalNode(info, tree, ts_node_named_descendant_for_point_range(node, min, max)); - return; + optional maybe_min = PointFromJS(info[1]); + optional maybe_max = PointFromJS(info[2]); + if (maybe_min && maybe_max) { + TSPoint min = *maybe_min; + TSPoint max = *maybe_max; + return MarshalNode(env, tree, ts_node_named_descendant_for_point_range(node, min, max)); } } - MarshalNullNode(); + return MarshalNullNode(env); } -static void DescendantForPosition(const Nan::FunctionCallbackInfo &info) { +static Value DescendantForPosition(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - Nan::Maybe maybe_min = PointFromJS(info[1]); - Nan::Maybe maybe_max = PointFromJS(info[2]); - if (maybe_min.IsJust() && maybe_max.IsJust()) { - TSPoint min = maybe_min.FromJust(); - TSPoint max = maybe_max.FromJust(); - MarshalNode(info, tree, ts_node_descendant_for_point_range(node, min, max)); - return; + optional maybe_min = PointFromJS(info[1]); + if (maybe_min) { + optional maybe_max = PointFromJS(info[2]); + if (maybe_max) { + TSPoint min = *maybe_min; + TSPoint max = *maybe_max; + return MarshalNode(env, tree, ts_node_descendant_for_point_range(node, min, max)); + } } } - MarshalNullNode(); + return MarshalNullNode(env); } -static void Type(const Nan::FunctionCallbackInfo &info) { +static Value Type(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { const char *result = ts_node_type(node); - info.GetReturnValue().Set(Nan::New(result).ToLocalChecked()); + return String::New(env, result); } + return env.Undefined(); } -static void TypeId(const Nan::FunctionCallbackInfo &info) { +static Value TypeId(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { TSSymbol result = ts_node_symbol(node); - info.GetReturnValue().Set(Nan::New(result)); + return Number::New(env, result); } + return env.Undefined(); } -static void IsNamed(const Nan::FunctionCallbackInfo &info) { +static Value IsNamed(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { bool result = ts_node_is_named(node); - info.GetReturnValue().Set(Nan::New(result)); + return Boolean::New(env, result); } + return env.Undefined(); } -static void StartIndex(const Nan::FunctionCallbackInfo &info) { +static Value StartIndex(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - int32_t result = ts_node_start_byte(node) / 2; - info.GetReturnValue().Set(Nan::New(result)); + uint32_t result = ts_node_start_byte(node) / 2; + return Number::New(env, result); } + return env.Undefined(); } -static void EndIndex(const Nan::FunctionCallbackInfo &info) { +static Value EndIndex(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - int32_t result = ts_node_end_byte(node) / 2; - info.GetReturnValue().Set(Nan::New(result)); + uint32_t result = ts_node_end_byte(node) / 2; + return Number::New(env, result); } + return env.Undefined(); } -static void StartPosition(const Nan::FunctionCallbackInfo &info) { +static Value StartPosition(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); - + TSNode node = UnmarshalNode(env, tree); if (node.id) { TransferPoint(ts_node_start_point(node)); } + return env.Undefined(); } -static void EndPosition(const Nan::FunctionCallbackInfo &info) { +static Value EndPosition(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); - + TSNode node = UnmarshalNode(env, tree); if (node.id) { TransferPoint(ts_node_end_point(node)); } + return env.Undefined(); } -static void Child(const Nan::FunctionCallbackInfo &info) { +static Value Child(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); - + TSNode node = UnmarshalNode(env, tree); if (node.id) { - if (!info[1]->IsUint32()) { - Nan::ThrowTypeError("Second argument must be an integer"); - return; + if (info[1].IsNumber()) { + uint32_t index = info[1].As().Uint32Value(); + return MarshalNode(env, tree, ts_node_child(node, index)); } - uint32_t index = Nan::To(info[1]).FromJust(); - MarshalNode(info, tree, ts_node_child(node, index)); - return; + TypeError::New(env, "Second argument must be an integer").ThrowAsJavaScriptException(); } - MarshalNullNode(); + return MarshalNullNode(env); } -static void NamedChild(const Nan::FunctionCallbackInfo &info) { +static Value NamedChild(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); - + TSNode node = UnmarshalNode(env, tree); if (node.id) { - if (!info[1]->IsUint32()) { - Nan::ThrowTypeError("Second argument must be an integer"); - return; + if (info[1].IsNumber()) { + uint32_t index = info[1].As().Uint32Value(); + return MarshalNode(env, tree, ts_node_named_child(node, index)); } - uint32_t index = Nan::To(info[1]).FromJust(); - MarshalNode(info, tree, ts_node_named_child(node, index)); - return; + TypeError::New(env, "Second argument must be an integer").ThrowAsJavaScriptException(); } - MarshalNullNode(); + return MarshalNullNode(env); } -static void ChildCount(const Nan::FunctionCallbackInfo &info) { +static Value ChildCount(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); - + TSNode node = UnmarshalNode(env, tree); if (node.id) { - info.GetReturnValue().Set(Nan::New(ts_node_child_count(node))); + return Number::New(env, ts_node_child_count(node)); } + return env.Undefined(); } -static void NamedChildCount(const Nan::FunctionCallbackInfo &info) { +static Value NamedChildCount(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); - + TSNode node = UnmarshalNode(env, tree); if (node.id) { - info.GetReturnValue().Set(Nan::New(ts_node_named_child_count(node))); + return Number::New(env, ts_node_named_child_count(node)); } + return env.Undefined(); } -static void FirstChild(const Nan::FunctionCallbackInfo &info) { +static Value FirstChild(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - MarshalNode(info, tree, ts_node_child(node, 0)); - return; + return MarshalNode(env, tree, ts_node_child(node, 0)); } - MarshalNullNode(); + return MarshalNullNode(env); } -static void FirstNamedChild(const Nan::FunctionCallbackInfo &info) { +static Value FirstNamedChild(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - MarshalNode(info, tree, ts_node_named_child(node, 0)); - return; + return MarshalNode(env, tree, ts_node_named_child(node, 0)); } - MarshalNullNode(); + return MarshalNullNode(env); } -static void LastChild(const Nan::FunctionCallbackInfo &info) { +static Value LastChild(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { uint32_t child_count = ts_node_child_count(node); if (child_count > 0) { - MarshalNode(info, tree, ts_node_child(node, child_count - 1)); - return; + return MarshalNode(env, tree, ts_node_child(node, child_count - 1)); } } - MarshalNullNode(); + return MarshalNullNode(env); } -static void LastNamedChild(const Nan::FunctionCallbackInfo &info) { +static Value LastNamedChild(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { uint32_t child_count = ts_node_named_child_count(node); if (child_count > 0) { - MarshalNode(info, tree, ts_node_named_child(node, child_count - 1)); - return; + return MarshalNode(env, tree, ts_node_named_child(node, child_count - 1)); } } - MarshalNullNode(); + return MarshalNullNode(env); } -static void Parent(const Nan::FunctionCallbackInfo &info) { +static Value Parent(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - MarshalNode(info, tree, ts_node_parent(node)); - return; + return MarshalNode(env, tree, ts_node_parent(node)); } - MarshalNullNode(); + return MarshalNullNode(env); } -static void NextSibling(const Nan::FunctionCallbackInfo &info) { +static Value NextSibling(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - MarshalNode(info, tree, ts_node_next_sibling(node)); - return; + return MarshalNode(env, tree, ts_node_next_sibling(node)); } - MarshalNullNode(); + return MarshalNullNode(env); } -static void NextNamedSibling(const Nan::FunctionCallbackInfo &info) { +static Value NextNamedSibling(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - MarshalNode(info, tree, ts_node_next_named_sibling(node)); - return; + return MarshalNode(env, tree, ts_node_next_named_sibling(node)); } - MarshalNullNode(); + return MarshalNullNode(env); } -static void PreviousSibling(const Nan::FunctionCallbackInfo &info) { +static Value PreviousSibling(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - MarshalNode(info, tree, ts_node_prev_sibling(node)); - return; + return MarshalNode(env, tree, ts_node_prev_sibling(node)); } - MarshalNullNode(); + return MarshalNullNode(env); } -static void PreviousNamedSibling(const Nan::FunctionCallbackInfo &info) { +static Value PreviousNamedSibling(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - MarshalNode(info, tree, ts_node_prev_named_sibling(node)); - return; + return MarshalNode(env, tree, ts_node_prev_named_sibling(node)); } - MarshalNullNode(); + return MarshalNullNode(env); } struct SymbolSet { @@ -459,62 +498,41 @@ struct SymbolSet { bool contains(TSSymbol symbol) { return symbols.find(symbol) != symbols.npos; } }; -bool symbol_set_from_js(SymbolSet *symbols, const Local &value, const TSLanguage *language) { - if (!value->IsArray()) { - Nan::ThrowTypeError("Argument must be a string or array of strings"); +bool symbol_set_from_js(SymbolSet *symbols, const Value &value, const TSLanguage *language) { + Env env = value.Env(); + if (!value.IsArray()) { + TypeError::New(env, "Argument must be a string or array of strings").ThrowAsJavaScriptException(); return false; } - + Array js_types = value.As(); unsigned symbol_count = ts_language_symbol_count(language); - - Local js_types = Local::Cast(value); - for (unsigned i = 0, n = js_types->Length(); i < n; i++) { - Local js_node_type_value; - if (Nan::Get(js_types, i).ToLocal(&js_node_type_value)) { - Local js_node_type; - if (Nan::To(js_node_type_value).ToLocal(&js_node_type)) { - auto length = js_node_type->Utf8Length( - #if NODE_MAJOR_VERSION >= 12 - Isolate::GetCurrent() - #endif - ); - - std::string node_type(length, '\0'); - js_node_type->WriteUtf8( - - // Nan doesn't wrap this functionality - #if NODE_MAJOR_VERSION >= 12 - Isolate::GetCurrent(), - #endif - - &node_type[0] - ); - - if (node_type == "ERROR") { - symbols->add(static_cast(-1)); - } else { - for (TSSymbol j = 0; j < symbol_count; j++) { - if (node_type == ts_language_symbol_name(language, j)) { - symbols->add(j); - } + for (uint32_t i = 0, n = js_types.Length(); i < n; i++) { + Value js_node_type_value = js_types[i]; + if (js_node_type_value.IsString()) { + String js_node_type = js_node_type_value.As(); + std::string node_type = js_node_type.Utf8Value(); + if (node_type == "ERROR") { + symbols->add(static_cast(-1)); + } else { + for (TSSymbol j = 0; j < symbol_count; j++) { + if (node_type == ts_language_symbol_name(language, j)) { + symbols->add(j); } } - - continue; } + } else { + TypeError::New(env, "Argument must be a string or array of strings").ThrowAsJavaScriptException(); + return false; } - - Nan::ThrowTypeError("Argument must be a string or array of strings"); - return false; } - return true; } -static void Children(const Nan::FunctionCallbackInfo &info) { +static Value Children(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); - if (!node.id) return; + TSNode node = UnmarshalNode(env, tree); + if (!node.id) return env.Undefined(); vector result; ts_tree_cursor_reset(&scratch_cursor, node); @@ -525,13 +543,14 @@ static void Children(const Nan::FunctionCallbackInfo &info) { } while (ts_tree_cursor_goto_next_sibling(&scratch_cursor)); } - MarshalNodes(info, tree, result.data(), result.size()); + return MarshalNodes(env, tree, result.data(), result.size()); } -static void NamedChildren(const Nan::FunctionCallbackInfo &info) { +static Value NamedChildren(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); - if (!node.id) return; + TSNode node = UnmarshalNode(env, tree); + if (!node.id) return env.Undefined(); vector result; ts_tree_cursor_reset(&scratch_cursor, node); @@ -544,30 +563,32 @@ static void NamedChildren(const Nan::FunctionCallbackInfo &info) { } while (ts_tree_cursor_goto_next_sibling(&scratch_cursor)); } - MarshalNodes(info, tree, result.data(), result.size()); + return MarshalNodes(env, tree, result.data(), result.size()); } -static void DescendantsOfType(const Nan::FunctionCallbackInfo &info) { +static Value DescendantsOfType(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); - if (!node.id) return; + TSNode node = UnmarshalNode(env, tree); + if (!node.id) return env.Undefined(); SymbolSet symbols; - if (!symbol_set_from_js(&symbols, info[1], ts_tree_language(node.tree))) return; + if (!symbol_set_from_js(&symbols, info[1], ts_tree_language(node.tree))) { + return env.Undefined(); + } TSPoint start_point = {0, 0}; TSPoint end_point = {UINT32_MAX, UINT32_MAX}; - - if (info.Length() > 2 && info[2]->IsObject()) { + if (info.Length() > 2 && info[2].IsObject()) { auto maybe_start_point = PointFromJS(info[2]); - if (maybe_start_point.IsNothing()) return; - start_point = maybe_start_point.FromJust(); + if (!maybe_start_point) return env.Undefined(); + start_point = *maybe_start_point; } - if (info.Length() > 3 && info[3]->IsObject()) { + if (info.Length() > 3 && info[3].IsObject()) { auto maybe_end_point = PointFromJS(info[3]); - if (maybe_end_point.IsNothing()) return; - end_point = maybe_end_point.FromJust(); + if (!maybe_end_point) return env.Undefined(); + end_point = *maybe_end_point; } vector found; @@ -610,20 +631,20 @@ static void DescendantsOfType(const Nan::FunctionCallbackInfo &info) { } } - MarshalNodes(info, tree, found.data(), found.size()); + return MarshalNodes(env, tree, found.data(), found.size()); } -static void ChildNodesForFieldId(const Nan::FunctionCallbackInfo &info) { +static Value ChildNodesForFieldId(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); - if (!node.id) return; + TSNode node = UnmarshalNode(env, tree); + if (!node.id) return env.Undefined(); - auto maybe_field_id = Nan::To(info[1]); - if (!maybe_field_id.IsJust()) { - Nan::ThrowTypeError("Second argument must be an integer"); - return; + if (!info[1].IsNumber()) { + TypeError::New(env, "Second argument must be an integer").ThrowAsJavaScriptException(); + return env.Undefined(); } - uint32_t field_id = maybe_field_id.FromJust(); + uint32_t field_id = info[1].As().Uint32Value(); vector result; ts_tree_cursor_reset(&scratch_cursor, node); @@ -636,110 +657,111 @@ static void ChildNodesForFieldId(const Nan::FunctionCallbackInfo &info) { } while (ts_tree_cursor_goto_next_sibling(&scratch_cursor)); } - MarshalNodes(info, tree, result.data(), result.size()); + return MarshalNodes(env, tree, result.data(), result.size()); } -static void ChildNodeForFieldId(const Nan::FunctionCallbackInfo &info) { +static Value ChildNodeForFieldId(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); if (node.id) { - auto maybe_field_id = Nan::To(info[1]); - if (!maybe_field_id.IsJust()) { - Nan::ThrowTypeError("Second argument must be an integer"); - return; + if (!info[1].IsNumber()) { + TypeError::New(env, "Second argument must be an integer").ThrowAsJavaScriptException(); + return env.Undefined(); } - uint32_t field_id = maybe_field_id.FromJust(); - MarshalNode(info, tree, ts_node_child_by_field_id(node, field_id)); - return; + uint32_t field_id = info[1].As().Uint32Value(); + return MarshalNode(env, tree, ts_node_child_by_field_id(node, field_id)); } - MarshalNullNode(); + return MarshalNullNode(env); } -static void Closest(const Nan::FunctionCallbackInfo &info) { +static Value Closest(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); - if (!node.id) return; + TSNode node = UnmarshalNode(env, tree); + if (!node.id) return env.Undefined(); SymbolSet symbols; - if (!symbol_set_from_js(&symbols, info[1], ts_tree_language(node.tree))) return; + if (!symbol_set_from_js(&symbols, info[1], ts_tree_language(node.tree))) { + return env.Undefined(); + } for (;;) { TSNode parent = ts_node_parent(node); if (!parent.id) break; if (symbols.contains(ts_node_symbol(parent))) { - MarshalNode(info, tree, parent); - return; + return MarshalNode(env, tree, parent); } node = parent; } - MarshalNullNode(); + return MarshalNullNode(env); } -static void Walk(const Nan::FunctionCallbackInfo &info) { +static Value Walk(const CallbackInfo &info) { + Env env = info.Env(); const Tree *tree = Tree::UnwrapTree(info[0]); - TSNode node = UnmarshalNode(tree); + TSNode node = UnmarshalNode(env, tree); TSTreeCursor cursor = ts_tree_cursor_new(node); - info.GetReturnValue().Set(TreeCursor::NewInstance(cursor)); -} - -void Init(Local exports) { - Local result = Nan::New(); - - FunctionPair methods[] = { - {"startIndex", StartIndex}, - {"endIndex", EndIndex}, - {"type", Type}, - {"typeId", TypeId}, - {"isNamed", IsNamed}, - {"parent", Parent}, - {"child", Child}, - {"namedChild", NamedChild}, - {"children", Children}, - {"namedChildren", NamedChildren}, - {"childCount", ChildCount}, - {"namedChildCount", NamedChildCount}, - {"firstChild", FirstChild}, - {"lastChild", LastChild}, - {"firstNamedChild", FirstNamedChild}, - {"lastNamedChild", LastNamedChild}, - {"nextSibling", NextSibling}, - {"nextNamedSibling", NextNamedSibling}, - {"previousSibling", PreviousSibling}, - {"previousNamedSibling", PreviousNamedSibling}, - {"startPosition", StartPosition}, - {"endPosition", EndPosition}, - {"isMissing", IsMissing}, - {"toString", ToString}, - {"firstChildForIndex", FirstChildForIndex}, - {"firstNamedChildForIndex", FirstNamedChildForIndex}, - {"descendantForIndex", DescendantForIndex}, - {"namedDescendantForIndex", NamedDescendantForIndex}, - {"descendantForPosition", DescendantForPosition}, - {"namedDescendantForPosition", NamedDescendantForPosition}, - {"hasChanges", HasChanges}, - {"hasError", HasError}, - {"descendantsOfType", DescendantsOfType}, - {"walk", Walk}, - {"closest", Closest}, - {"childNodeForFieldId", ChildNodeForFieldId}, - {"childNodesForFieldId", ChildNodesForFieldId}, - }; - - for (size_t i = 0; i < length_of_array(methods); i++) { - Nan::Set( - result, - Nan::New(methods[i].name).ToLocalChecked(), - Nan::GetFunction(Nan::New(methods[i].callback)).ToLocalChecked() - ); - } + return NewTreeCursor(cursor); +} + +class NodeMethods : public ObjectWrap { + public: + NodeMethods(const Napi::CallbackInfo &info) + : Napi::ObjectWrap(info) + {} + + static void Init(Napi::Env env, Object &exports) { + exports["NodeMethods"] = DefineClass(env, "NodeMethods", { + StaticMethod("startIndex", StartIndex, napi_writable), + StaticMethod("endIndex", EndIndex, napi_writable), + StaticMethod("type", Type, napi_writable), + StaticMethod("typeId", TypeId, napi_writable), + StaticMethod("isNamed", IsNamed, napi_writable), + StaticMethod("parent", Parent, napi_writable), + StaticMethod("child", Child, napi_writable), + StaticMethod("namedChild", NamedChild, napi_writable), + StaticMethod("children", Children, napi_writable), + StaticMethod("namedChildren", NamedChildren, napi_writable), + StaticMethod("childCount", ChildCount, napi_writable), + StaticMethod("namedChildCount", NamedChildCount, napi_writable), + StaticMethod("firstChild", FirstChild, napi_writable), + StaticMethod("lastChild", LastChild, napi_writable), + StaticMethod("firstNamedChild", FirstNamedChild, napi_writable), + StaticMethod("lastNamedChild", LastNamedChild, napi_writable), + StaticMethod("nextSibling", NextSibling, napi_writable), + StaticMethod("nextNamedSibling", NextNamedSibling, napi_writable), + StaticMethod("previousSibling", PreviousSibling, napi_writable), + StaticMethod("previousNamedSibling", PreviousNamedSibling, napi_writable), + StaticMethod("startPosition", StartPosition, napi_writable), + StaticMethod("endPosition", EndPosition, napi_writable), + StaticMethod("isMissing", IsMissing, napi_writable), + StaticMethod("toString", ToString, napi_writable), + StaticMethod("firstChildForIndex", FirstChildForIndex, napi_writable), + StaticMethod("firstNamedChildForIndex", FirstNamedChildForIndex, napi_writable), + StaticMethod("descendantForIndex", DescendantForIndex, napi_writable), + StaticMethod("namedDescendantForIndex", NamedDescendantForIndex, napi_writable), + StaticMethod("descendantForPosition", DescendantForPosition, napi_writable), + StaticMethod("namedDescendantForPosition", NamedDescendantForPosition, napi_writable), + StaticMethod("hasChanges", HasChanges, napi_writable), + StaticMethod("hasError", HasError, napi_writable), + StaticMethod("descendantsOfType", DescendantsOfType, napi_writable), + StaticMethod("walk", Walk, napi_writable), + StaticMethod("closest", Closest, napi_writable), + StaticMethod("childNodeForFieldId", ChildNodeForFieldId, napi_writable), + StaticMethod("childNodesForFieldId", ChildNodesForFieldId, napi_writable), + }); - module_exports.Reset(exports); - setup_transfer_buffer(1); + } +}; - Nan::Set(exports, Nan::New("NodeMethods").ToLocalChecked(), result); +void InitNode(Object &exports) { + Env env = exports.Env(); + NodeMethods::Init(env, exports); + module_exports.Reset(exports, 1); + setup_transfer_buffer(env, 1); } -} // namespace node_methods } // namespace node_tree_sitter diff --git a/src/node.h b/src/node.h index 6dcc34f0..1bb95859 100644 --- a/src/node.h +++ b/src/node.h @@ -1,31 +1,17 @@ #ifndef NODE_TREE_SITTER_NODE_H_ #define NODE_TREE_SITTER_NODE_H_ -#include -#include -#include +#include #include #include "./tree.h" +#include "./util.h" namespace node_tree_sitter { -namespace node_methods { -void Init(v8::Local); -void MarshalNode(const Nan::FunctionCallbackInfo &info, const Tree *, TSNode); -TSNode UnmarshalNode(const Tree *tree); +void InitNode(Napi::Object &exports); +Napi::Value MarshalNode(Napi::Env, const Tree *, TSNode); +TSNode UnmarshalNode(Napi::Env env, const Tree *tree); -static inline const void *UnmarshalNodeId(const uint32_t *buffer) { - const void *result; - memcpy(&result, buffer, sizeof(result)); - return result; -} - -static inline void MarshalNodeId(const void *id, uint32_t *buffer) { - memset(buffer, 0, sizeof(uint64_t)); - memcpy(buffer, &id, sizeof(id)); -} - -} // namespace node_methods } // namespace node_tree_sitter #endif // NODE_TREE_SITTER_NODE_H_ diff --git a/src/optional.h b/src/optional.h new file mode 100644 index 00000000..31366f2a --- /dev/null +++ b/src/optional.h @@ -0,0 +1,29 @@ +#ifndef NODE_TREE_SITTER_OPTIONAL_H +#define NODE_TREE_SITTER_OPTIONAL_H + +#include + +template class optional { + T value; + bool is_some; + +public: + optional(T &&value) : value(std::move(value)), is_some(true) {} + optional(const T &value) : value(value), is_some(true) {} + optional() : value(T()), is_some(false) {} + + T &operator*() { return value; } + const T &operator*() const { return value; } + const T *operator->() const { return &value; } + T *operator->() { return &value; } + operator bool() const { return is_some; } + bool operator==(const optional &other) { + if (is_some) { + return other.is_some && value == other.value; + } else { + return !other.is_some; + } + } +}; + +#endif // NODE_TREE_SITTER_OPTIONAL_H diff --git a/src/parser.cc b/src/parser.cc index 52e1beac..533f92ee 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -2,11 +2,11 @@ #include #include #include -#include -#include +#include #include "./conversions.h" #include "./language.h" #include "./logger.h" +#include "./node.h" #include "./tree.h" #include "./util.h" #include "text-buffer-snapshot-wrapper.h" @@ -14,445 +14,466 @@ namespace node_tree_sitter { -using namespace v8; +using namespace Napi; using std::vector; using std::pair; -Nan::Persistent Parser::constructor; - -class CallbackInput { +class Parser : public ObjectWrap { public: - CallbackInput(v8::Local callback, v8::Local js_buffer_size) - : callback(callback), - byte_offset(0), - partial_string_offset(0) { - uint32_t buffer_size = Nan::To(js_buffer_size).FromMaybe(0); - if (buffer_size == 0) buffer_size = 32 * 1024; - buffer.resize(buffer_size); - } + static void Init(Object &exports) { + Napi::Env env = exports.Env(); + + Function ctor = DefineClass(env, "Parser", { + InstanceMethod("getLogger", &Parser::GetLogger, napi_writable), + InstanceMethod("setLogger", &Parser::SetLogger, napi_writable), + InstanceMethod("setLanguage", &Parser::SetLanguage, napi_writable), + InstanceMethod("printDotGraphs", &Parser::PrintDotGraphs, napi_writable), + InstanceMethod("parse", &Parser::Parse, napi_writable), + InstanceMethod("parseTextBuffer", &Parser::ParseTextBuffer, napi_writable), + InstanceMethod("parseTextBufferSync", &Parser::ParseTextBufferSync, napi_writable), + }); + + String s = String::New(env, ""); + if (env.IsExceptionPending()) { + return; + } - TSInput Input() { - TSInput result; - result.payload = (void *)this; - result.encoding = TSInputEncodingUTF16; - result.read = Read; - return result; + napi_value value; + napi_valuetype type; + napi_status status = napi_get_property( + env, + s, + String::New(env, "slice"), + &value + ); + assert(status == napi_ok); + status = napi_typeof(env, value, &type); + assert(status == napi_ok); + + constructor.Reset(ctor, 1); + // string_slice.Reset(string_slice_fn.As(), 1); + exports["Parser"] = ctor; + exports["LANGUAGE_VERSION"] = Number::New(env, TREE_SITTER_LANGUAGE_VERSION); } + TSParser *parser_; + bool is_parsing_async_; + + Parser(const CallbackInfo &info) + : Napi::ObjectWrap(info), + parser_(ts_parser_new()), + is_parsing_async_(false) + {} + + ~Parser() { ts_parser_delete(parser_); } + private: - static const char * Read(void *payload, uint32_t byte, TSPoint position, uint32_t *bytes_read) { - CallbackInput *reader = (CallbackInput *)payload; + class CallbackInput { + public: + CallbackInput(Function callback, Napi::Value js_buffer_size) + : byte_offset(0) { + this->callback.Reset(callback, 1); + if (js_buffer_size.IsNumber()) { + buffer.resize(js_buffer_size.As().Uint32Value()); + } else { + buffer.resize(32 * 1024); + } + } - if (byte != reader->byte_offset) { - reader->byte_offset = byte; - reader->partial_string_offset = 0; - reader->partial_string.Reset(); + TSInput Input() { + TSInput result; + result.payload = (void *)this; + result.encoding = TSInputEncodingUTF16; + result.read = Read; + return result; } - *bytes_read = 0; - Local result; - uint32_t start = 0; - if (reader->partial_string_offset) { - result = Nan::New(reader->partial_string); - start = reader->partial_string_offset; - } else { - Local callback = Nan::New(reader->callback); - uint32_t utf16_unit = byte / 2; - Local argv[2] = { Nan::New(utf16_unit), PointToJS(position) }; - TryCatch try_catch(Isolate::GetCurrent()); - auto maybe_result_value = Nan::Call(callback, callback->CreationContext()->Global(), 2, argv); - if (try_catch.HasCaught()) return nullptr; - - Local result_value; - if (!maybe_result_value.ToLocal(&result_value)) return nullptr; - if (!result_value->IsString()) return nullptr; - if (!Nan::To(result_value).ToLocal(&result)) return nullptr; + private: + static String slice(String s, uint32_t offset) { + return string_slice.Call(s, {Number::New(s.Env(), offset)}).As(); } - int utf16_units_read = result->Write( + static const char * Read(void *payload, uint32_t byte, TSPoint position, uint32_t *bytes_read) { + CallbackInput *reader = (CallbackInput *)payload; + Napi::Env env = reader->callback.Env(); - // Nan doesn't wrap this functionality - #if NODE_MAJOR_VERSION >= 12 - Isolate::GetCurrent(), - #endif + if (byte != reader->byte_offset) { + reader->byte_offset = byte; + reader->partial_string.Reset(); + } - reader->buffer.data(), - start, - reader->buffer.size(), - String::NO_NULL_TERMINATION - ); - int end = start + utf16_units_read; - *bytes_read = 2 * utf16_units_read; + *bytes_read = 0; + String result; + if (!reader->partial_string.IsEmpty()) { + result = reader->partial_string.Value().As(); + } else { + Function callback = reader->callback.Value(); + Napi::Value result_value = callback({ + ByteCountToJS(env, byte), + PointToJS(env, position), + }); + if (env.IsExceptionPending()) return nullptr; + if (!result_value.IsString()) return nullptr; + result = result_value.As(); + } - reader->byte_offset += *bytes_read; + size_t length = 0; + size_t utf16_units_read = 0; + napi_status status; + status = napi_get_value_string_utf16( + env, result, nullptr, 0, &length + ); + if (status != napi_ok) return nullptr; + status = napi_get_value_string_utf16( + env, result, (char16_t *)&reader->buffer[0], reader->buffer.size(), &utf16_units_read + ); + if (status != napi_ok) return nullptr; + + *bytes_read = 2 * utf16_units_read; + reader->byte_offset += *bytes_read; + + if (utf16_units_read < length) { + reader->partial_string.Reset(slice(result, utf16_units_read)); + } else { + reader->partial_string.Reset(); + } - if (end < result->Length()) { - reader->partial_string_offset = end; - reader->partial_string.Reset(result); - } else { - reader->partial_string_offset = 0; - reader->partial_string.Reset(); + return (const char *)reader->buffer.data(); } - return (const char *)reader->buffer.data(); - } - - Nan::Persistent callback; - std::vector buffer; - size_t byte_offset; - Nan::Persistent partial_string; - size_t partial_string_offset; -}; + FunctionReference callback; + std::vector buffer; + size_t byte_offset; + Reference partial_string; + }; -class TextBufferInput { -public: - TextBufferInput(const vector> *slices) - : slices_(slices), - byte_offset(0), - slice_index_(0), - slice_offset_(0) {} + class TextBufferInput { + public: + TextBufferInput(const vector> *slices) + : slices_(slices), + byte_offset(0), + slice_index_(0), + slice_offset_(0) {} - TSInput input() { - return TSInput{this, Read, TSInputEncodingUTF16}; - } + TSInput input() { + return TSInput{this, Read, TSInputEncodingUTF16}; + } -private: - void seek(uint32_t byte_offset) { - this->byte_offset = byte_offset; - - uint32_t total_length = 0; - uint32_t goal_index = byte_offset / 2; - for (unsigned i = 0, n = this->slices_->size(); i < n; i++) { - uint32_t next_total_length = total_length + this->slices_->at(i).second; - if (next_total_length > goal_index) { - this->slice_index_ = i; - this->slice_offset_ = goal_index - total_length; - return; + private: + void seek(uint32_t byte_offset) { + this->byte_offset = byte_offset; + + uint32_t total_length = 0; + uint32_t goal_index = byte_offset / 2; + for (unsigned i = 0, n = this->slices_->size(); i < n; i++) { + uint32_t next_total_length = total_length + this->slices_->at(i).second; + if (next_total_length > goal_index) { + this->slice_index_ = i; + this->slice_offset_ = goal_index - total_length; + return; + } + total_length = next_total_length; } - total_length = next_total_length; + + this->slice_index_ = this->slices_->size(); + this->slice_offset_ = 0; } - this->slice_index_ = this->slices_->size(); - this->slice_offset_ = 0; - } + static const char *Read(void *payload, uint32_t byte, TSPoint position, uint32_t *length) { + auto self = static_cast(payload); - static const char *Read(void *payload, uint32_t byte, TSPoint position, uint32_t *length) { - auto self = static_cast(payload); + if (byte != self->byte_offset) self->seek(byte); - if (byte != self->byte_offset) self->seek(byte); + if (self->slice_index_ == self->slices_->size()) { + *length = 0; + return ""; + } - if (self->slice_index_ == self->slices_->size()) { - *length = 0; - return ""; + auto &slice = self->slices_->at(self->slice_index_); + const char16_t *result = slice.first + self->slice_offset_; + *length = 2 * (slice.second - self->slice_offset_); + self->byte_offset += *length; + self->slice_index_++; + self->slice_offset_ = 0; + return reinterpret_cast(result); } - auto &slice = self->slices_->at(self->slice_index_); - const char16_t *result = slice.first + self->slice_offset_; - *length = 2 * (slice.second - self->slice_offset_); - self->byte_offset += *length; - self->slice_index_++; - self->slice_offset_ = 0; - return reinterpret_cast(result); - } - - const vector> *slices_; - uint32_t byte_offset; - uint32_t slice_index_; - uint32_t slice_offset_; -}; - -void Parser::Init(Local exports) { - Local tpl = Nan::New(New); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - Local class_name = Nan::New("Parser").ToLocalChecked(); - tpl->SetClassName(class_name); - - FunctionPair methods[] = { - {"getLogger", GetLogger}, - {"setLogger", SetLogger}, - {"setLanguage", SetLanguage}, - {"printDotGraphs", PrintDotGraphs}, - {"parse", Parse}, - {"parseTextBuffer", ParseTextBuffer}, - {"parseTextBufferSync", ParseTextBufferSync}, + const vector> *slices_; + uint32_t byte_offset; + uint32_t slice_index_; + uint32_t slice_offset_; }; - for (size_t i = 0; i < length_of_array(methods); i++) { - Nan::SetPrototypeMethod(tpl, methods[i].name, methods[i].callback); - } - - constructor.Reset(Nan::Persistent(Nan::GetFunction(tpl).ToLocalChecked())); - Nan::Set(exports, class_name, Nan::New(constructor)); - Nan::Set(exports, Nan::New("LANGUAGE_VERSION").ToLocalChecked(), Nan::New(TREE_SITTER_LANGUAGE_VERSION)); -} - -Parser::Parser() : parser_(ts_parser_new()), is_parsing_async_(false) {} - -Parser::~Parser() { ts_parser_delete(parser_); } - -static bool handle_included_ranges(TSParser *parser, Local arg) { - uint32_t last_included_range_end = 0; - if (arg->IsArray()) { - auto js_included_ranges = Local::Cast(arg); - vector included_ranges; - for (unsigned i = 0; i < js_included_ranges->Length(); i++) { - Local range_value; - if (!Nan::Get(js_included_ranges, i).ToLocal(&range_value)) return false; - auto maybe_range = RangeFromJS(range_value); - if (!maybe_range.IsJust()) return false; - auto range = maybe_range.FromJust(); - if (range.start_byte < last_included_range_end) { - Nan::ThrowRangeError("Overlapping ranges"); - return false; + bool handle_included_ranges(class Value arg) { + Napi::Env env = arg.Env(); + uint32_t last_included_range_end = 0; + if (arg.IsArray()) { + Array js_included_ranges = arg.As(); + vector included_ranges; + for (unsigned i = 0; i < js_included_ranges.Length(); i++) { + Napi::Value range_value = js_included_ranges[i]; + if (!range_value.IsObject()) return false; + optional range = RangeFromJS(range_value); + if (!range) return false; + if (range->start_byte < last_included_range_end) { + RangeError::New(env, "Overlapping ranges").ThrowAsJavaScriptException(); + return false; + } + last_included_range_end = range->end_byte; + included_ranges.push_back(*range); } - last_included_range_end = range.end_byte; - included_ranges.push_back(range); + ts_parser_set_included_ranges(parser_, included_ranges.data(), included_ranges.size()); + } else { + ts_parser_set_included_ranges(parser_, nullptr, 0); } - ts_parser_set_included_ranges(parser, included_ranges.data(), included_ranges.size()); - } else { - ts_parser_set_included_ranges(parser, nullptr, 0); + return true; } - return true; -} - -void Parser::New(const Nan::FunctionCallbackInfo &info) { - if (info.IsConstructCall()) { - Parser *parser = new Parser(); - parser->Wrap(info.This()); - info.GetReturnValue().Set(info.This()); - } else { - Local self; - MaybeLocal maybe_self = Nan::New(constructor)->NewInstance(Nan::GetCurrentContext()); - if (maybe_self.ToLocal(&self)) { - info.GetReturnValue().Set(self); - } else { - info.GetReturnValue().Set(Nan::Null()); + Napi::Value SetLanguage(const CallbackInfo &info) { + auto env = info.Env(); + if (is_parsing_async_) { + Error::New(env, "Parser is in use").ThrowAsJavaScriptException(); + return env.Undefined(); } - } -} -void Parser::SetLanguage(const Nan::FunctionCallbackInfo &info) { - Parser *parser = ObjectWrap::Unwrap(info.This()); - if (parser->is_parsing_async_) { - Nan::ThrowError("Parser is in use"); - return; + const TSLanguage *language = UnwrapLanguage(info[0]); + if (language) ts_parser_set_language(parser_, language); + return info.This(); } - const TSLanguage *language = language_methods::UnwrapLanguage(info[0]); - if (language) { - ts_parser_set_language(parser->parser_, language); - info.GetReturnValue().Set(info.This()); - } -} + Napi::Value Parse(const CallbackInfo &info) { + auto env = info.Env(); -void Parser::Parse(const Nan::FunctionCallbackInfo &info) { - Parser *parser = ObjectWrap::Unwrap(info.This()); - if (parser->is_parsing_async_) { - Nan::ThrowError("Parser is in use"); - return; - } + if (is_parsing_async_) { + Error::New(env, "Parser is in use").ThrowAsJavaScriptException(); + return env.Undefined(); + } - if (!info[0]->IsFunction()) { - Nan::ThrowTypeError("Input must be a function"); - return; - } + if (!info[0].IsFunction()) { + TypeError::New(env, "Input must be a function").ThrowAsJavaScriptException(); + return env.Undefined(); + } - Local callback = Local::Cast(info[0]); + Function callback = info[0].As(); - Local js_old_tree; - const TSTree *old_tree = nullptr; - if (info.Length() > 1 && !info[1]->IsNull() && !info[1]->IsUndefined() && Nan::To(info[1]).ToLocal(&js_old_tree)) { - const Tree *tree = Tree::UnwrapTree(js_old_tree); - if (!tree) { - Nan::ThrowTypeError("Second argument must be a tree"); - return; + const TSTree *old_tree = nullptr; + if (info.Length() > 1 && info[1].IsObject()) { + Object js_old_tree = info[1].As(); + const Tree *tree = Tree::UnwrapTree(js_old_tree); + if (!tree) { + TypeError::New(env, "Second argument must be a tree").ThrowAsJavaScriptException(); + return env.Undefined(); + } + old_tree = tree->tree_; } - old_tree = tree->tree_; + + auto buffer_size = env.Null(); + if (info.Length() > 2) buffer_size = info[2]; + if (!handle_included_ranges(info[3])) return env.Undefined(); + + CallbackInput callback_input(callback, buffer_size); + TSTree *tree = ts_parser_parse(parser_, old_tree, callback_input.Input()); + auto result = Tree::NewInstance(env, tree); + return result; } - Local buffer_size = Nan::Null(); - if (info.Length() > 2) buffer_size = info[2]; + class ParseWorker : public Napi::AsyncWorker { + Parser *parser_; + TSTree *new_tree_; + TextBufferInput *input_; + + public: + ParseWorker(Napi::Function &callback, Parser *parser, TextBufferInput *input) : + Napi::AsyncWorker(callback, "tree-sitter.parseTextBuffer"), + parser_(parser), + new_tree_(nullptr), + input_(input) {} + + void Execute() { + TSLogger logger = ts_parser_logger(parser_->parser_); + ts_parser_set_logger(parser_->parser_, TSLogger{0, 0}); + new_tree_ = ts_parser_parse(parser_->parser_, nullptr, input_->input()); + ts_parser_set_logger(parser_->parser_, logger); + } - if (!handle_included_ranges(parser->parser_, info[3])) return; + void OnOK() { + parser_->is_parsing_async_ = false; + delete input_; + Callback()({Tree::NewInstance(Env(), new_tree_)}); + } + }; - CallbackInput callback_input(callback, buffer_size); - TSTree *tree = ts_parser_parse(parser->parser_, old_tree, callback_input.Input()); - Local result = Tree::NewInstance(tree); - info.GetReturnValue().Set(result); -} + const std::vector> * + TextBufferSnapshotFromJS(Napi::Value value) { + auto env = value.Env(); + if (!value.IsObject()) { + TypeError::New(env, "Expected a snapshot wrapper").ThrowAsJavaScriptException(); + return nullptr; + } -class ParseWorker : public Nan::AsyncWorker { - Parser *parser_; - TSTree *new_tree_; - TextBufferInput *input_; - -public: - ParseWorker(Nan::Callback *callback, Parser *parser, TextBufferInput *input) : - AsyncWorker(callback, "tree-sitter.parseTextBuffer"), - parser_(parser), - new_tree_(nullptr), - input_(input) {} - - void Execute() { - TSLogger logger = ts_parser_logger(parser_->parser_); - ts_parser_set_logger(parser_->parser_, TSLogger{0, 0}); - new_tree_ = ts_parser_parse(parser_->parser_, nullptr, input_->input()); - ts_parser_set_logger(parser_->parser_, logger); + void *internal = GetInternalFieldPointer(value); + if (internal) { + return reinterpret_cast(internal)->slices(); + } else { + TypeError::New(env, "Expected a snapshot wrapper").ThrowAsJavaScriptException(); + return nullptr; + } } - void HandleOKCallback() { - parser_->is_parsing_async_ = false; - delete input_; - Local argv[] = {Tree::NewInstance(new_tree_)}; - callback->Call(1, argv, async_resource); - } -}; + Napi::Value ParseTextBuffer(const CallbackInfo &info) { + auto env = info.Env(); -void Parser::ParseTextBuffer(const Nan::FunctionCallbackInfo &info) { - Parser *parser = ObjectWrap::Unwrap(info.This()); - if (parser->is_parsing_async_) { - Nan::ThrowError("Parser is in use"); - return; - } + if (is_parsing_async_) { + Error::New(env, "Parser is in use").ThrowAsJavaScriptException(); + return env.Undefined(); + } - Local js_old_tree; - const TSTree *old_tree = nullptr; - if (info.Length() > 2 && info[2]->IsObject() && Nan::To(info[2]).ToLocal(&js_old_tree)) { - const Tree *tree = Tree::UnwrapTree(js_old_tree); - if (!tree) { - Nan::ThrowTypeError("Second argument must be a tree"); - return; + auto callback = info[0].As(); + + const TSTree *old_tree = nullptr; + if (info.Length() > 2 && info[2].IsObject()) { + Object js_old_tree = info[2].As(); + const Tree *tree = Tree::UnwrapTree(js_old_tree); + if (!tree) { + TypeError::New(env, "Second argument must be a tree").ThrowAsJavaScriptException(); + return env.Undefined(); + } + old_tree = tree->tree_; } - old_tree = tree->tree_; - } - if (!handle_included_ranges(parser->parser_, info[3])) return; + if (!handle_included_ranges(info[3])) return env.Undefined(); - auto snapshot = Nan::ObjectWrap::Unwrap(info[1].As()); - auto input = new TextBufferInput(snapshot->slices()); + auto snapshot = TextBufferSnapshotFromJS(info[1]); + if (!snapshot) return env.Undefined(); - // If a `syncTimeoutMicros` option is passed, parse synchronously - // for the given amount of time before queuing an async task. - double js_sync_timeout = Nan::To(info[4]).FromMaybe(-1); - if (js_sync_timeout > 0) { - size_t sync_timeout; + auto input = new TextBufferInput(snapshot); - // If the timeout is `Infinity`, then parse synchronously with no timeout. - if (js_sync_timeout && !std::isfinite(js_sync_timeout)) { - sync_timeout = 0; - } else { - auto maybe_sync_timeout = Nan::To(info[4]); - if (maybe_sync_timeout.IsJust()) { - sync_timeout = maybe_sync_timeout.FromJust(); + // If a `syncTimeoutMicros` option is passed, parse synchronously + // for the given amount of time before queuing an async task. + auto js_sync_timeout = info[4]; + if (!js_sync_timeout.IsEmpty() && !js_sync_timeout.IsNull() && !js_sync_timeout.IsUndefined()) { + size_t sync_timeout; + + // If the timeout is `Infinity`, then parse synchronously with no timeout. + if (!std::isfinite(js_sync_timeout.ToNumber().DoubleValue())) { + sync_timeout = 0; + } else if (js_sync_timeout.IsNumber()) { + sync_timeout = js_sync_timeout.As().Uint32Value(); } else { - Nan::ThrowTypeError("The `syncTimeoutMicros` option must be a positive integer."); - return; + TypeError::New(env, "The `syncTimeoutMicros` option must be a positive integer.").ThrowAsJavaScriptException(); + return env.Undefined(); } - } - // Logging is disabled for this method, because we can't call the - // logging callback from an async worker. - TSLogger logger = ts_parser_logger(parser->parser_); - ts_parser_set_timeout_micros(parser->parser_, sync_timeout); - ts_parser_set_logger(parser->parser_, TSLogger{0, 0}); - TSTree *result = ts_parser_parse(parser->parser_, old_tree, input->input()); - ts_parser_set_timeout_micros(parser->parser_, 0); - ts_parser_set_logger(parser->parser_, logger); - - if (result) { - delete input; - Local argv[] = {Tree::NewInstance(result)}; - auto callback = info[0].As(); - Nan::Call(callback, callback->CreationContext()->Global(), 1, argv); - return; + // Logging is disabled for this method, because we can't call the + // logging callback from an async worker. + TSLogger logger = ts_parser_logger(parser_); + ts_parser_set_timeout_micros(parser_, sync_timeout); + ts_parser_set_logger(parser_, TSLogger{0, 0}); + TSTree *result = ts_parser_parse(parser_, old_tree, input->input()); + ts_parser_set_timeout_micros(parser_, 0); + ts_parser_set_logger(parser_, logger); + + if (result) { + delete input; + callback({Tree::NewInstance(env, result)}); + return env.Undefined(); + } } + + is_parsing_async_ = true; + auto worker = new ParseWorker(callback, this, input); + worker->Queue(); + return env.Undefined(); } - auto callback = new Nan::Callback(info[0].As()); - parser->is_parsing_async_ = true; - Nan::AsyncQueueWorker(new ParseWorker( - callback, - parser, - input - )); -} + Napi::Value ParseTextBufferSync(const CallbackInfo &info) { + auto env = info.Env(); + + if (is_parsing_async_) { + Error::New(env, "Parser is in use").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + const TSTree *old_tree = nullptr; + if (info.Length() > 1 && info[1].IsObject()) { + const Tree *tree = Tree::UnwrapTree(info[1].As()); + if (!tree) { + TypeError::New(env, "Second argument must be a tree").ThrowAsJavaScriptException(); + return env.Undefined(); + } + old_tree = ts_tree_copy(tree->tree_); + } + + if (!handle_included_ranges(info[2])) return env.Undefined(); -void Parser::ParseTextBufferSync(const Nan::FunctionCallbackInfo &info) { - Parser *parser = ObjectWrap::Unwrap(info.This()); - if (parser->is_parsing_async_) { - Nan::ThrowError("Parser is in use"); - return; + auto snapshot = TextBufferSnapshotFromJS(info[0]); + if (!snapshot) return env.Undefined(); + + TextBufferInput input(snapshot); + TSTree *result = ts_parser_parse(parser_, old_tree, input.input()); + return Tree::NewInstance(env, result); } - Local js_old_tree; - const TSTree *old_tree = nullptr; - if (info.Length() > 1 && info[1]->IsObject() && Nan::To(info[1]).ToLocal(&js_old_tree)) { - const Tree *tree = Tree::UnwrapTree(js_old_tree); - if (!tree) { - Nan::ThrowTypeError("Second argument must be a tree"); - return; + Napi::Value GetLogger(const CallbackInfo &info) { + auto env = info.Env(); + TSLogger current_logger = ts_parser_logger(parser_); + if (current_logger.payload && current_logger.log == Logger::Log) { + Logger *logger = (Logger *)current_logger.payload; + return logger->func.Value(); + } else { + return env.Null(); } - old_tree = ts_tree_copy(tree->tree_); } - if (!handle_included_ranges(parser->parser_, info[2])) return; + Napi::Value SetLogger(const CallbackInfo &info) { + auto env = info.Env(); - auto snapshot = Nan::ObjectWrap::Unwrap(info[0].As()); - TextBufferInput input(snapshot->slices()); - TSTree *result = ts_parser_parse(parser->parser_, old_tree, input.input()); - info.GetReturnValue().Set(Tree::NewInstance(result)); -} + if (is_parsing_async_) { + Error::New(env, "Parser is in use").ThrowAsJavaScriptException(); + return env.Undefined(); + } -void Parser::GetLogger(const Nan::FunctionCallbackInfo &info) { - Parser *parser = ObjectWrap::Unwrap(info.This()); + TSLogger current_logger = ts_parser_logger(parser_); - TSLogger current_logger = ts_parser_logger(parser->parser_); - if (current_logger.payload && current_logger.log == Logger::Log) { - Logger *logger = (Logger *)current_logger.payload; - info.GetReturnValue().Set(Nan::New(logger->func)); - } else { - info.GetReturnValue().Set(Nan::Null()); - } -} + if (info[0].IsFunction()) { + if (current_logger.payload) delete (Logger *)current_logger.payload; + ts_parser_set_logger(parser_, Logger::Make(info[0].As())); + } else if (info[0].IsEmpty() || info[0].IsNull() || (info[0].IsBoolean() && !info[0].As())) { + if (current_logger.payload) delete (Logger *)current_logger.payload; + ts_parser_set_logger(parser_, { 0, 0 }); + } else { + TypeError::New(env, "Logger callback must either be a function or a falsy value").ThrowAsJavaScriptException(); + } -void Parser::SetLogger(const Nan::FunctionCallbackInfo &info) { - Parser *parser = ObjectWrap::Unwrap(info.This()); - if (parser->is_parsing_async_) { - Nan::ThrowError("Parser is in use"); - return; + return info.This(); } - TSLogger current_logger = ts_parser_logger(parser->parser_); - - if (info[0]->IsFunction()) { - if (current_logger.payload) delete (Logger *)current_logger.payload; - ts_parser_set_logger(parser->parser_, Logger::Make(Local::Cast(info[0]))); - } else if (!Nan::To(info[0]).FromMaybe(true)) { - if (current_logger.payload) delete (Logger *)current_logger.payload; - ts_parser_set_logger(parser->parser_, { 0, 0 }); - } else { - Nan::ThrowTypeError("Logger callback must either be a function or a falsy value"); - return; - } + Napi::Value PrintDotGraphs(const CallbackInfo &info) { + auto env = info.Env(); - info.GetReturnValue().Set(info.This()); -} + if (is_parsing_async_) { + Error::New(env, "Parser is in use").ThrowAsJavaScriptException(); + return env.Undefined(); + } -void Parser::PrintDotGraphs(const Nan::FunctionCallbackInfo &info) { - Parser *parser = ObjectWrap::Unwrap(info.This()); - if (parser->is_parsing_async_) { - Nan::ThrowError("Parser is in use"); - return; - } + if (info[0].IsBoolean() && info[0].As()) { + ts_parser_print_dot_graphs(parser_, 2); + } else { + ts_parser_print_dot_graphs(parser_, -1); + } - if (Nan::To(info[0]).FromMaybe(false)) { - ts_parser_print_dot_graphs(parser->parser_, 2); - } else { - ts_parser_print_dot_graphs(parser->parser_, -1); + return info.This(); } - info.GetReturnValue().Set(info.This()); + static Napi::FunctionReference constructor; + static Napi::FunctionReference string_slice; +}; + +void InitParser(Object &exports) { + Parser::Init(exports); } +Napi::FunctionReference Parser::constructor; +Napi::FunctionReference Parser::string_slice; + } // namespace node_tree_sitter diff --git a/src/parser.h b/src/parser.h index e01e1f11..ac838ead 100644 --- a/src/parser.h +++ b/src/parser.h @@ -1,35 +1,12 @@ #ifndef NODE_TREE_SITTER_PARSER_H_ #define NODE_TREE_SITTER_PARSER_H_ -#include -#include -#include +#include #include namespace node_tree_sitter { -class Parser : public Nan::ObjectWrap { - public: - static void Init(v8::Local exports); - - TSParser *parser_; - bool is_parsing_async_; - - private: - explicit Parser(); - ~Parser(); - - static void New(const Nan::FunctionCallbackInfo &); - static void SetLanguage(const Nan::FunctionCallbackInfo &); - static void GetLogger(const Nan::FunctionCallbackInfo &); - static void SetLogger(const Nan::FunctionCallbackInfo &); - static void Parse(const Nan::FunctionCallbackInfo &); - static void ParseTextBuffer(const Nan::FunctionCallbackInfo &); - static void ParseTextBufferSync(const Nan::FunctionCallbackInfo &); - static void PrintDotGraphs(const Nan::FunctionCallbackInfo &); - - static Nan::Persistent constructor; -}; +void InitParser(Napi::Object &exports); } // namespace node_tree_sitter diff --git a/src/tree.cc b/src/tree.cc index bac04a2c..f949755a 100644 --- a/src/tree.cc +++ b/src/tree.cc @@ -1,6 +1,5 @@ #include "./tree.h" #include -#include #include #include "./node.h" #include "./logger.h" @@ -9,85 +8,65 @@ namespace node_tree_sitter { -using namespace v8; -using node_methods::UnmarshalNodeId; - -Nan::Persistent Tree::constructor; -Nan::Persistent Tree::constructor_template; - -void Tree::Init(Local exports) { - Local tpl = Nan::New(New); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - Local class_name = Nan::New("Tree").ToLocalChecked(); - tpl->SetClassName(class_name); - - FunctionPair methods[] = { - {"edit", Edit}, - {"rootNode", RootNode}, - {"printDotGraph", PrintDotGraph}, - {"getChangedRanges", GetChangedRanges}, - {"getEditedRange", GetEditedRange}, - {"_cacheNode", CacheNode}, - }; +using namespace Napi; - for (size_t i = 0; i < length_of_array(methods); i++) { - Nan::SetPrototypeMethod(tpl, methods[i].name, methods[i].callback); - } +FunctionReference Tree::constructor; - Local ctor = Nan::GetFunction(tpl).ToLocalChecked(); +void Tree::Init(Object &exports) { + Napi::Env env = exports.Env(); - constructor_template.Reset(tpl); - constructor.Reset(ctor); - Nan::Set(exports, class_name, ctor); + Function ctor = DefineClass(env, "Tree", { + InstanceMethod("edit", &Tree::Edit, napi_writable), + InstanceMethod("rootNode", &Tree::RootNode, napi_configurable), + InstanceMethod("printDotGraph", &Tree::PrintDotGraph), + InstanceMethod("getChangedRanges", &Tree::GetChangedRanges), + InstanceMethod("getEditedRange", &Tree::GetEditedRange), + InstanceMethod("_cacheNode", &Tree::CacheNode), + }); + + constructor.Reset(ctor, 1); + exports["Tree"] = ctor; } -Tree::Tree(TSTree *tree) : tree_(tree) {} +Tree::Tree(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info), + tree_(nullptr) {} Tree::~Tree() { - ts_tree_delete(tree_); + if (tree_) ts_tree_delete(tree_); for (auto &entry : cached_nodes_) { entry.second->tree = nullptr; } } -Local Tree::NewInstance(TSTree *tree) { +Napi::Value Tree::NewInstance(Napi::Env env, TSTree *tree) { if (tree) { - Local self; - MaybeLocal maybe_self = Nan::NewInstance(Nan::New(constructor)); - if (maybe_self.ToLocal(&self)) { - (new Tree(tree))->Wrap(self); - return self; - } + Object js_tree = constructor.Value().New({}); + Tree::Unwrap(js_tree)->tree_ = tree; + return js_tree; } - return Nan::Null(); + return env.Null(); } -const Tree *Tree::UnwrapTree(const Local &value) { - if (!value->IsObject()) return nullptr; - Local js_tree = Local::Cast(value); - if (!Nan::New(constructor_template)->HasInstance(js_tree)) return nullptr; - return ObjectWrap::Unwrap(js_tree); +const Tree *Tree::UnwrapTree(const Napi::Value &value) { + return Tree::Unwrap(value.As()); } -void Tree::New(const Nan::FunctionCallbackInfo &info) {} - -#define read_number_from_js(out, value, name) \ - maybe_number = Nan::To(value); \ - if (maybe_number.IsNothing()) { \ - Nan::ThrowTypeError(name " must be an integer"); \ - return; \ - } \ - *(out) = maybe_number.FromJust(); +#define read_number_from_js(out, value, name) \ + if (!value.IsNumber()) { \ + TypeError::New(env, name " must be an integer").ThrowAsJavaScriptException(); \ + return env.Undefined(); \ + } \ + *(out) = value.As().Uint32Value(); #define read_byte_count_from_js(out, value, name) \ read_number_from_js(out, value, name); \ (*out) *= 2 -void Tree::Edit(const Nan::FunctionCallbackInfo &info) { - Tree *tree = ObjectWrap::Unwrap(info.This()); +Napi::Value Tree::Edit(const CallbackInfo &info) { + auto env = info.Env(); TSInputEdit edit; - Nan::Maybe maybe_number = Nan::Nothing(); read_number_from_js(&edit.start_point.row, info[0], "startPosition.row"); read_byte_count_from_js(&edit.start_point.column, info[1], "startPosition.column"); read_number_from_js(&edit.old_end_point.row, info[2], "oldEndPosition.row"); @@ -98,57 +77,53 @@ void Tree::Edit(const Nan::FunctionCallbackInfo &info) { read_byte_count_from_js(&edit.old_end_byte, info[7], "oldEndIndex"); read_byte_count_from_js(&edit.new_end_byte, info[8], "newEndIndex"); - ts_tree_edit(tree->tree_, &edit); + ts_tree_edit(tree_, &edit); - for (auto &entry : tree->cached_nodes_) { - Local js_node = Nan::New(entry.second->node); + for (auto &entry : cached_nodes_) { + Object js_node = entry.second->node.Value(); TSNode node; node.id = entry.first; for (unsigned i = 0; i < 4; i++) { - Local node_field; - if (Nan::Get(js_node, i + 2).ToLocal(&node_field)) { - node.context[i] = Nan::To(node_field).FromMaybe(0); + Napi::Value node_field = js_node[i + 2u]; + if (node_field.IsNumber()) { + node.context[i] = node_field.As().Uint32Value(); } } ts_node_edit(&node, &edit); for (unsigned i = 0; i < 4; i++) { - Nan::Set(js_node, i + 2, Nan::New(node.context[i])); + js_node[i + 2u] = Number::New(env, node.context[i]); } } - info.GetReturnValue().Set(info.This()); + return info.This(); } -void Tree::RootNode(const Nan::FunctionCallbackInfo &info) { - Tree *tree = ObjectWrap::Unwrap(info.This()); - node_methods::MarshalNode(info, tree, ts_tree_root_node(tree->tree_)); +Napi::Value Tree::RootNode(const CallbackInfo &info) { + return MarshalNode(info.Env(), this, ts_tree_root_node(tree_)); } -void Tree::GetChangedRanges(const Nan::FunctionCallbackInfo &info) { - const Tree *tree = ObjectWrap::Unwrap(info.This()); +Napi::Value Tree::GetChangedRanges(const CallbackInfo &info) { + auto env = info.Env(); const Tree *other_tree = UnwrapTree(info[0]); - if (!other_tree) { - Nan::ThrowTypeError("Argument must be a tree"); - return; - } + if (!other_tree) return env.Undefined(); uint32_t range_count; - TSRange *ranges = ts_tree_get_changed_ranges(tree->tree_, other_tree->tree_, &range_count); + TSRange *ranges = ts_tree_get_changed_ranges(tree_, other_tree->tree_, &range_count); - Local result = Nan::New(); - for (size_t i = 0; i < range_count; i++) { - Nan::Set(result, i, RangeToJS(ranges[i])); + Array result = Array::New(env); + for (unsigned i = 0; i < range_count; i++) { + result[i] = RangeToJS(env, ranges[i]); } - info.GetReturnValue().Set(result); + return result; } -void Tree::GetEditedRange(const Nan::FunctionCallbackInfo &info) { - Tree *tree = ObjectWrap::Unwrap(info.This()); - TSNode root = ts_tree_root_node(tree->tree_); - if (!ts_node_has_changes(root)) return; +Napi::Value Tree::GetEditedRange(const CallbackInfo &info) { + auto env = info.Env(); + TSNode root = ts_tree_root_node(tree_); + if (!ts_node_has_changes(root)) return env.Undefined(); TSRange result = { ts_node_start_point(root), ts_node_end_point(root), @@ -190,17 +165,15 @@ void Tree::GetEditedRange(const Nan::FunctionCallbackInfo &info) { } ts_tree_cursor_delete(&cursor); - info.GetReturnValue().Set(RangeToJS(result)); + return RangeToJS(env, result); } -void Tree::PrintDotGraph(const Nan::FunctionCallbackInfo &info) { - Tree *tree = ObjectWrap::Unwrap(info.This()); - ts_tree_print_dot_graph(tree->tree_, stderr); - info.GetReturnValue().Set(info.This()); +Napi::Value Tree::PrintDotGraph(const CallbackInfo &info) { + ts_tree_print_dot_graph(tree_, stderr); + return info.This(); } -static void FinalizeNode(const v8::WeakCallbackInfo &info) { - Tree::NodeCacheEntry *cache_entry = info.GetParameter(); +static void FinalizeNode(Env env, Tree::NodeCacheEntry *cache_entry) { assert(!cache_entry->node.IsEmpty()); cache_entry->node.Reset(); if (cache_entry->tree) { @@ -210,26 +183,29 @@ static void FinalizeNode(const v8::WeakCallbackInfo &info) delete cache_entry; } -void Tree::CacheNode(const Nan::FunctionCallbackInfo &info) { - Tree *tree = ObjectWrap::Unwrap(info.This()); - Local js_node = Local::Cast(info[0]); +Napi::Value Tree::CacheNode(const CallbackInfo &info) { + auto env = info.Env(); + Object js_node = info[0].As(); - Local js_node_field1, js_node_field2; - if (!Nan::Get(js_node, 0).ToLocal(&js_node_field1)) return; - if (!Nan::Get(js_node, 1).ToLocal(&js_node_field2)) return; + Napi::Value js_node_field1 = js_node[0u]; + Napi::Value js_node_field2 = js_node[1u]; + if (!js_node_field1.IsNumber() || !js_node_field2.IsNumber()) { + return env.Undefined(); + } uint32_t key_parts[2] = { - Nan::To(js_node_field1).FromMaybe(0), - Nan::To(js_node_field2).FromMaybe(0) + js_node_field1.As().Uint32Value(), + js_node_field2.As().Uint32Value(), }; - const void *key = UnmarshalNodeId(key_parts); + const void *key = UnmarshalPointer(key_parts); - auto cache_entry = new NodeCacheEntry{tree, key, {}}; - cache_entry->node.Reset(info.GetIsolate(), js_node); - cache_entry->node.SetWeak(cache_entry, &FinalizeNode, Nan::WeakCallbackType::kParameter); + auto cache_entry = new NodeCacheEntry{this, key, {}}; + cache_entry->node.Reset(js_node, 0); + js_node.AddFinalizer(&FinalizeNode, cache_entry); - assert(!tree->cached_nodes_.count(key)); + assert(!cached_nodes_.count(key)); - tree->cached_nodes_[key] = cache_entry; + cached_nodes_[key] = cache_entry; + return env.Undefined(); } } // namespace node_tree_sitter diff --git a/src/tree.h b/src/tree.h index 1d5962e2..98e4c5eb 100644 --- a/src/tree.h +++ b/src/tree.h @@ -1,43 +1,40 @@ #ifndef NODE_TREE_SITTER_TREE_H_ #define NODE_TREE_SITTER_TREE_H_ -#include -#include -#include +#include #include #include namespace node_tree_sitter { -class Tree : public Nan::ObjectWrap { +class Tree : public Napi::ObjectWrap { public: - static void Init(v8::Local exports); - static v8::Local NewInstance(TSTree *); - static const Tree *UnwrapTree(const v8::Local &); + static void Init(Napi::Object &); + static Napi::Value NewInstance(Napi::Env, TSTree *); + static const Tree *UnwrapTree(const Napi::Value &); + Tree(const Napi::CallbackInfo& info); + ~Tree(); struct NodeCacheEntry { Tree *tree; const void *key; - v8::Persistent node; + Napi::ObjectReference node; }; TSTree *tree_; std::unordered_map cached_nodes_; private: - explicit Tree(TSTree *); - ~Tree(); - static void New(const Nan::FunctionCallbackInfo &); - static void Edit(const Nan::FunctionCallbackInfo &); - static void RootNode(const Nan::FunctionCallbackInfo &); - static void PrintDotGraph(const Nan::FunctionCallbackInfo &); - static void GetEditedRange(const Nan::FunctionCallbackInfo &); - static void GetChangedRanges(const Nan::FunctionCallbackInfo &); - static void CacheNode(const Nan::FunctionCallbackInfo &); + Napi::Value New(const Napi::CallbackInfo &); + Napi::Value Edit(const Napi::CallbackInfo &); + Napi::Value RootNode(const Napi::CallbackInfo &); + Napi::Value PrintDotGraph(const Napi::CallbackInfo &); + Napi::Value GetEditedRange(const Napi::CallbackInfo &); + Napi::Value GetChangedRanges(const Napi::CallbackInfo &); + Napi::Value CacheNode(const Napi::CallbackInfo &); - static Nan::Persistent constructor; - static Nan::Persistent constructor_template; + static Napi::FunctionReference constructor; }; } // namespace node_tree_sitter diff --git a/src/tree_cursor.cc b/src/tree_cursor.cc index a1bb741c..c3f5679c 100644 --- a/src/tree_cursor.cc +++ b/src/tree_cursor.cc @@ -1,7 +1,6 @@ #include "./tree_cursor.h" -#include #include -#include +#include #include "./util.h" #include "./conversions.h" #include "./node.h" @@ -9,163 +8,155 @@ namespace node_tree_sitter { -using namespace v8; - -Nan::Persistent TreeCursor::constructor; - -void TreeCursor::Init(v8::Local exports) { - Local tpl = Nan::New(New); - Local class_name = Nan::New("TreeCursor").ToLocalChecked(); - tpl->SetClassName(class_name); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - - GetterPair getters[] = { - {"startIndex", StartIndex}, - {"endIndex", EndIndex}, - {"nodeType", NodeType}, - {"nodeIsNamed", NodeIsNamed}, - {"currentFieldName", CurrentFieldName}, - }; - - FunctionPair methods[] = { - {"startPosition", StartPosition}, - {"endPosition", EndPosition}, - {"gotoParent", GotoParent}, - {"gotoFirstChild", GotoFirstChild}, - {"gotoFirstChildForIndex", GotoFirstChildForIndex}, - {"gotoNextSibling", GotoNextSibling}, - {"currentNode", CurrentNode}, - {"reset", Reset}, - }; - - for (size_t i = 0; i < length_of_array(getters); i++) { - Nan::SetAccessor( - tpl->InstanceTemplate(), - Nan::New(getters[i].name).ToLocalChecked(), - getters[i].callback); +using namespace Napi; + +class TreeCursor : public Napi::ObjectWrap { + public: + static void Init(Napi::Object &exports) { + Napi::Env env = exports.Env(); + + Function ctor = DefineClass(env, "TreeCursor", { + InstanceAccessor("startIndex", &TreeCursor::StartIndex, nullptr), + InstanceAccessor("endIndex", &TreeCursor::EndIndex, nullptr), + InstanceAccessor("nodeType", &TreeCursor::NodeType, nullptr), + InstanceAccessor("nodeIsNamed", &TreeCursor::NodeIsNamed, nullptr), + InstanceAccessor("currentFieldName", &TreeCursor::CurrentFieldName, nullptr), + + InstanceMethod("startPosition", &TreeCursor::StartPosition, napi_configurable), + InstanceMethod("endPosition", &TreeCursor::EndPosition, napi_configurable), + InstanceMethod("gotoParent", &TreeCursor::GotoParent), + InstanceMethod("gotoFirstChild", &TreeCursor::GotoFirstChild), + InstanceMethod("gotoFirstChildForIndex", &TreeCursor::GotoFirstChildForIndex), + InstanceMethod("gotoNextSibling", &TreeCursor::GotoNextSibling), + InstanceMethod("currentNode", &TreeCursor::CurrentNode, napi_configurable), + InstanceMethod("reset", &TreeCursor::Reset), + }); + + constructor.Reset(ctor, 1); + exports.Set("TreeCursor", ctor); } - for (size_t i = 0; i < length_of_array(methods); i++) { - Nan::SetPrototypeMethod(tpl, methods[i].name, methods[i].callback); - } + TreeCursor(const CallbackInfo &info) + : Napi::ObjectWrap(info), + cursor_({0, 0, {0, 0}}) + {} - Local constructor_local = Nan::GetFunction(tpl).ToLocalChecked(); - Nan::Set(exports, class_name, constructor_local); - constructor.Reset(Nan::Persistent(constructor_local)); -} + ~TreeCursor() { ts_tree_cursor_delete(&cursor_); } -Local TreeCursor::NewInstance(TSTreeCursor cursor) { - Local self; - MaybeLocal maybe_self = Nan::New(constructor)->NewInstance(Nan::GetCurrentContext()); - if (maybe_self.ToLocal(&self)) { - (new TreeCursor(cursor))->Wrap(self); - return self; - } else { - return Nan::Null(); + Napi::Value GotoParent(const CallbackInfo &info) { + auto env = info.Env(); + bool result = ts_tree_cursor_goto_parent(&cursor_); + return Boolean::New(env, result); } -} - -TreeCursor::TreeCursor(TSTreeCursor cursor) : cursor_(cursor) {} -TreeCursor::~TreeCursor() { ts_tree_cursor_delete(&cursor_); } - -void TreeCursor::New(const Nan::FunctionCallbackInfo &info) { - info.GetReturnValue().Set(Nan::Null()); -} + Napi::Value GotoFirstChild(const CallbackInfo &info) { + auto env = info.Env(); + bool result = ts_tree_cursor_goto_first_child(&cursor_); + return Boolean::New(env, result); + } -void TreeCursor::GotoParent(const Nan::FunctionCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - bool result = ts_tree_cursor_goto_parent(&cursor->cursor_); - info.GetReturnValue().Set(Nan::New(result)); -} + Napi::Value GotoFirstChildForIndex(const CallbackInfo &info) { + auto env = info.Env(); + auto js_index = info[0].As(); + if (!js_index.IsNumber()) { + TypeError::New(env, "Argument must be an integer").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + uint32_t goal_byte = js_index.Uint32Value() * 2; + int64_t child_index = ts_tree_cursor_goto_first_child_for_byte(&cursor_, goal_byte); + if (child_index < 0) { + return env.Null(); + } else { + return Number::New(env, child_index); + } + } -void TreeCursor::GotoFirstChild(const Nan::FunctionCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - bool result = ts_tree_cursor_goto_first_child(&cursor->cursor_); - info.GetReturnValue().Set(Nan::New(result)); -} + Napi::Value GotoNextSibling(const CallbackInfo &info) { + auto env = info.Env(); + bool result = ts_tree_cursor_goto_next_sibling(&cursor_); + return Boolean::New(env, result); + } -void TreeCursor::GotoFirstChildForIndex(const Nan::FunctionCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - auto maybe_index = Nan::To(info[0]); - if (maybe_index.IsNothing()) { - Nan::ThrowTypeError("Argument must be an integer"); - return; + Napi::Value StartPosition(const CallbackInfo &info) { + auto env = info.Env(); + TSNode node = ts_tree_cursor_current_node(&cursor_); + TransferPoint(ts_node_start_point(node)); + return env.Undefined(); } - uint32_t goal_byte = maybe_index.FromJust() * 2; - int64_t child_index = ts_tree_cursor_goto_first_child_for_byte(&cursor->cursor_, goal_byte); - if (child_index < 0) { - info.GetReturnValue().Set(Nan::Null()); - } else { - info.GetReturnValue().Set(Nan::New(static_cast(child_index))); + + Napi::Value EndPosition(const CallbackInfo &info) { + auto env = info.Env(); + TSNode node = ts_tree_cursor_current_node(&cursor_); + TransferPoint(ts_node_end_point(node)); + return env.Undefined(); } -} -void TreeCursor::GotoNextSibling(const Nan::FunctionCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - bool result = ts_tree_cursor_goto_next_sibling(&cursor->cursor_); - info.GetReturnValue().Set(Nan::New(result)); -} + Napi::Value CurrentNode(const CallbackInfo &info) { + auto env = info.Env(); + Napi::Value js_tree = info.This().As()["tree"]; + const Tree *tree = Tree::UnwrapTree(js_tree.As()); + TSNode node = ts_tree_cursor_current_node(&cursor_); + return MarshalNode(env, tree, node); + } -void TreeCursor::StartPosition(const Nan::FunctionCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - TSNode node = ts_tree_cursor_current_node(&cursor->cursor_); - TransferPoint(ts_node_start_point(node)); -} + Napi::Value Reset(const CallbackInfo &info) { + auto env = info.Env(); + Napi::Value js_tree = info.This().As()["tree"]; + const Tree *tree = Tree::UnwrapTree(js_tree.As()); + TSNode node = UnmarshalNode(env, tree); + ts_tree_cursor_reset(&cursor_, node); + return env.Undefined(); + } -void TreeCursor::EndPosition(const Nan::FunctionCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - TSNode node = ts_tree_cursor_current_node(&cursor->cursor_); - TransferPoint(ts_node_end_point(node)); -} + Napi::Value NodeType(const CallbackInfo &info) { + auto env = info.Env(); + TSNode node = ts_tree_cursor_current_node(&cursor_); + return String::New(env, ts_node_type(node)); + } -void TreeCursor::CurrentNode(const Nan::FunctionCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - Local key = Nan::New("tree").ToLocalChecked(); - const Tree *tree = Tree::UnwrapTree(Nan::Get(info.This(), key).ToLocalChecked()); - TSNode node = ts_tree_cursor_current_node(&cursor->cursor_); - node_methods::MarshalNode(info, tree, node); -} + Napi::Value NodeIsNamed(const CallbackInfo &info) { + auto env = info.Env(); + TSNode node = ts_tree_cursor_current_node(&cursor_); + return Boolean::New(env, ts_node_is_named(node)); + } -void TreeCursor::Reset(const Nan::FunctionCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - Local key = Nan::New("tree").ToLocalChecked(); - const Tree *tree = Tree::UnwrapTree(Nan::Get(info.This(), key).ToLocalChecked()); - TSNode node = node_methods::UnmarshalNode(tree); - ts_tree_cursor_reset(&cursor->cursor_, node); -} + Napi::Value CurrentFieldName(const CallbackInfo &info) { + auto env = info.Env(); + const char *field_name = ts_tree_cursor_current_field_name(&cursor_); + if (field_name) { + return String::New(env, field_name); + } else { + return env.Undefined(); + } + } -void TreeCursor::NodeType(v8::Local prop, const Nan::PropertyCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - TSNode node = ts_tree_cursor_current_node(&cursor->cursor_); - info.GetReturnValue().Set(Nan::New(ts_node_type(node)).ToLocalChecked()); -} + Napi::Value StartIndex(const CallbackInfo &info) { + auto env = info.Env(); + TSNode node = ts_tree_cursor_current_node(&cursor_); + return ByteCountToJS(env, ts_node_start_byte(node)); + } -void TreeCursor::NodeIsNamed(v8::Local prop, const Nan::PropertyCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - TSNode node = ts_tree_cursor_current_node(&cursor->cursor_); + Napi::Value EndIndex(const CallbackInfo &info) { + auto env = info.Env(); + TSNode node = ts_tree_cursor_current_node(&cursor_); + return ByteCountToJS(env, ts_node_end_byte(node)); + } - info.GetReturnValue().Set(Nan::New(ts_node_is_named(node))); -} + TSTreeCursor cursor_; + static Napi::FunctionReference constructor; +}; -void TreeCursor::CurrentFieldName(v8::Local prop, const Nan::PropertyCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - const char *field_name = ts_tree_cursor_current_field_name(&cursor->cursor_); - if (field_name) { - info.GetReturnValue().Set(Nan::New(field_name).ToLocalChecked()); - } +void InitTreeCursor(Object &exports) { + TreeCursor::Init(exports); } -void TreeCursor::StartIndex(v8::Local prop, const Nan::PropertyCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - TSNode node = ts_tree_cursor_current_node(&cursor->cursor_); - info.GetReturnValue().Set(ByteCountToJS(ts_node_start_byte(node))); +Napi::Value NewTreeCursor(TSTreeCursor cursor) { + Object js_cursor = TreeCursor::constructor.Value().New({}); + TreeCursor::Unwrap(js_cursor)->cursor_ = cursor; + return js_cursor; } -void TreeCursor::EndIndex(v8::Local prop, const Nan::PropertyCallbackInfo &info) { - TreeCursor *cursor = Nan::ObjectWrap::Unwrap(info.This()); - TSNode node = ts_tree_cursor_current_node(&cursor->cursor_); - info.GetReturnValue().Set(ByteCountToJS(ts_node_end_byte(node))); -} +FunctionReference TreeCursor::constructor; -} +} // namespace node_tree_sitter diff --git a/src/tree_cursor.h b/src/tree_cursor.h index cd54e4bb..1bff0660 100644 --- a/src/tree_cursor.h +++ b/src/tree_cursor.h @@ -1,42 +1,13 @@ #ifndef NODE_TREE_SITTER_TREE_CURSOR_H_ #define NODE_TREE_SITTER_TREE_CURSOR_H_ -#include -#include -#include +#include #include namespace node_tree_sitter { -class TreeCursor : public Nan::ObjectWrap { - public: - static void Init(v8::Local exports); - static v8::Local NewInstance(TSTreeCursor); - - private: - explicit TreeCursor(TSTreeCursor); - ~TreeCursor(); - - static void New(const Nan::FunctionCallbackInfo &); - static void GotoParent(const Nan::FunctionCallbackInfo &); - static void GotoFirstChild(const Nan::FunctionCallbackInfo &); - static void GotoFirstChildForIndex(const Nan::FunctionCallbackInfo &); - static void GotoNextSibling(const Nan::FunctionCallbackInfo &); - static void StartPosition(const Nan::FunctionCallbackInfo &); - static void EndPosition(const Nan::FunctionCallbackInfo &); - static void CurrentNode(const Nan::FunctionCallbackInfo &); - static void Reset(const Nan::FunctionCallbackInfo &); - - static void NodeType(v8::Local, const Nan::PropertyCallbackInfo &); - static void NodeIsNamed(v8::Local, const Nan::PropertyCallbackInfo &); - static void CurrentFieldName(v8::Local, const Nan::PropertyCallbackInfo &); - static void StartIndex(v8::Local, const Nan::PropertyCallbackInfo &); - static void EndIndex(v8::Local, const Nan::PropertyCallbackInfo &); - - TSTreeCursor cursor_; - static Nan::Persistent constructor; - static Nan::Persistent constructor_template; -}; +void InitTreeCursor(Napi::Object &); +Napi::Value NewTreeCursor(TSTreeCursor); } // namespace node_tree_sitter diff --git a/src/util.h b/src/util.h index 3e2ebbba..6c1ebd4d 100644 --- a/src/util.h +++ b/src/util.h @@ -1,17 +1,55 @@ -#include +#ifndef NODE_TREE_SITTER_UTIL_H_ +#define NODE_TREE_SITTER_UTIL_H_ + +#include +#include namespace node_tree_sitter { -#define length_of_array(a) (sizeof(a) / sizeof(a[0])) +static inline const void *UnmarshalPointer(const uint32_t *buffer) { + const void *result; + memcpy(&result, buffer, sizeof(result)); + return result; +} + +static inline void MarshalPointer(const void *id, uint32_t *buffer) { + memset(buffer, 0, sizeof(uint64_t)); + memcpy(buffer, &id, sizeof(id)); +} + +//========================================================================= +// This library must be able to load parsers that were generated +// using older versions of Tree-sitter, which did not use `napi`. +// So we need to use the V8 APIs directly here. +// +// The following assertion and function were taken from Node's `napi` implementation: +// github.com/nodejs/node/blob/53ca0b9ae145c430842bf78e553e3b6cbd2823aa/src/js_native_api_v8.h -struct GetterPair { - const char *name; - Nan::GetterCallback callback; -}; +static_assert( + sizeof(v8::Local) == sizeof(napi_value), + "Cannot convert between v8::Local and napi_value" +); -struct FunctionPair { - const char *name; - Nan::FunctionCallback callback; -}; +static inline v8::Local V8LocalValueFromJsValue(napi_value v) { + v8::Local local; + memcpy(static_cast(&local), &v, sizeof(v)); + return local; +} + +//========================================================================= + +static inline void *GetInternalFieldPointer(Napi::Value value) { + if (value.IsObject()) { + v8::Local object = v8::Local::Cast( + V8LocalValueFromJsValue(value) + ); + if (object->InternalFieldCount() == 1) { + return object->GetAlignedPointerFromInternalField(0); + } + } + return nullptr; +} } // namespace node_tree_sitter + +#endif // NODE_TREE_SITTER_UTIL_H_ diff --git a/test/parser_test.js b/test/parser_test.js index a1f6d179..b45f1dfa 100644 --- a/test/parser_test.js +++ b/test/parser_test.js @@ -178,7 +178,9 @@ describe("Parser", () => { const sourceCode = "[" + "0,".repeat(elementCount) + "]"; const buffer = new TextBuffer(sourceCode) - const tree = await parser.parseTextBuffer(buffer); + const promise = parser.parseTextBuffer(buffer); + assert.equal(promise.constructor, Promise); + const tree = await promise; const arrayNode = tree.rootNode.firstChild.firstChild; assert.equal(arrayNode.type, "array"); assert.equal(arrayNode.namedChildCount, elementCount); diff --git a/test/tree_test.js b/test/tree_test.js index fe4729ad..660c4d3b 100644 --- a/test/tree_test.js +++ b/test/tree_test.js @@ -147,7 +147,7 @@ describe("Tree", () => { assert.throws(() => { tree1.getChangedRanges({}); - }, /Argument must be a tree/); + }, /Invalid argument/); }) }); diff --git a/vendor/tree-sitter b/vendor/tree-sitter index 80008b0b..3214de55 160000 --- a/vendor/tree-sitter +++ b/vendor/tree-sitter @@ -1 +1 @@ -Subproject commit 80008b0bccbddffc8e68f66a5f173ef71fd125e3 +Subproject commit 3214de55f06d292babfdc932d2ffbb66c7d16a33