Skip to main content
Glama

CTS MCP Server

by EricA1019
parser.ccโ€ข9.17 kB
#include "./parser.h" #include "./conversions.h" #include "./language.h" #include "./logger.h" #include "./tree.h" #include <cstddef> #include <napi.h> #include <vector> using namespace Napi; using std::vector; namespace node_tree_sitter { class CallbackInput final { public: CallbackInput(Function callback, Napi::Value js_buffer_size) { this->callback.Reset(callback, 1); if (js_buffer_size.IsNumber()) { buffer.resize(js_buffer_size.As<Number>().Uint32Value()); } else { buffer.resize(static_cast<uint64_t>(32 * 1024)); } } TSInput Input() { TSInput result; result.payload = static_cast<void *>(this); result.encoding = TSInputEncodingUTF16; result.read = Read; return result; } private: static String slice(String s, uint32_t offset) { Env env = s.Env(); auto *data = env.GetInstanceData<AddonData>(); return data->string_slice.Call(s, {Number::New(s.Env(), offset)}).As<String>(); } static const char * Read(void *payload, uint32_t byte, TSPoint position, uint32_t *bytes_read) { auto *reader = static_cast<CallbackInput *>(payload); Napi::Env env = reader->callback.Env(); if (byte != reader->byte_offset) { reader->byte_offset = byte; reader->partial_string.Reset(); } *bytes_read = 0; String result; if (!reader->partial_string.IsEmpty()) { result = reader->partial_string.Value().As<String>(); } 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<String>(); } 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, reinterpret_cast<char16_t *>(reader->buffer.data()), 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(); } return reinterpret_cast<const char *>(reader->buffer.data()); } FunctionReference callback; std::vector<uint16_t> buffer; size_t byte_offset {}; Reference<String> partial_string; }; void Parser::Init(Napi::Env env, Napi::Object exports) { auto *data = env.GetInstanceData<AddonData>(); Function ctor = DefineClass(env, "Parser", { InstanceMethod("setLanguage", &Parser::SetLanguage, napi_default_method), InstanceMethod("parse", &Parser::Parse, napi_default_method), InstanceMethod("getIncludedRanges", &Parser::IncludedRanges, napi_default_method), InstanceMethod("getTimeoutMicros", &Parser::TimeoutMicros, napi_default_method), InstanceMethod("setTimeoutMicros", &Parser::SetTimeoutMicros, napi_default_method), InstanceMethod("getLogger", &Parser::GetLogger, napi_default_method), InstanceMethod("setLogger", &Parser::SetLogger, napi_default_method), InstanceMethod("printDotGraphs", &Parser::PrintDotGraphs, napi_default_method), InstanceMethod("reset", &Parser::Reset, napi_default_method), }); data->parser_constructor = Napi::Persistent(ctor); exports["Parser"] = ctor; exports["LANGUAGE_VERSION"] = Number::New(env, TREE_SITTER_LANGUAGE_VERSION); String s = String::New(env, ""); Napi::Value string_slice_value = s.As<Object>()["slice"]; data->string_slice = Napi::Persistent(string_slice_value.As<Function>()); } Parser::Parser(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Parser>(info), parser_(ts_parser_new()) {} Parser::~Parser() { ts_parser_print_dot_graphs(parser_, -1); ts_parser_set_logger(parser_, { nullptr, nullptr }); ts_parser_delete(parser_); } namespace { bool handle_included_ranges(Napi::Env env, TSParser *parser, Napi::Value arg) { uint32_t last_included_range_end = 0; if (arg.IsArray()) { auto js_included_ranges = arg.As<Array>(); vector<TSRange> included_ranges; for (unsigned i = 0; i < js_included_ranges.Length(); i++) { Value range_value = js_included_ranges[i]; if (!range_value.IsObject()) { return false; } auto maybe_range = RangeFromJS(range_value); if (!maybe_range.IsJust()) { return false; } auto range = maybe_range.Unwrap(); if (range.start_byte < last_included_range_end) { throw RangeError::New(env, "Overlapping ranges"); } 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); } return true; } } // namespace Napi::Value Parser::SetLanguage(const Napi::CallbackInfo &info) { const TSLanguage *language = language_methods::UnwrapLanguage(info[0]); if (language != nullptr) { ts_parser_set_language(parser_, language); return info.This(); } return info.Env().Undefined(); } Napi::Value Parser::Parse(const CallbackInfo &info) { Napi::Env env = info.Env(); if (!info[0].IsFunction()) { throw TypeError::New(env, "Input must be a function"); } auto callback = info[0].As<Function>(); Object js_old_tree; const TSTree *old_tree = nullptr; if (info.Length() > 1 && !info[1].IsNull() && !info[1].IsUndefined() && info[1].IsObject()) { js_old_tree = info[1].As<Object>(); const Tree *tree = Tree::UnwrapTree(js_old_tree); if (tree == nullptr) { throw TypeError::New(env, "Second argument must be a tree"); } old_tree = tree->tree_; } Napi::Value buffer_size = env.Null(); if (info.Length() > 2) { buffer_size = info[2]; } if (!handle_included_ranges(env, parser_, info[3])) { return env.Undefined(); } CallbackInput callback_input(callback, buffer_size); TSTree *tree = ts_parser_parse(parser_, old_tree, callback_input.Input()); Napi::Value result = Tree::NewInstance(env, tree); return result; } Napi::Value Parser::IncludedRanges(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); uint32_t count; const TSRange *ranges = ts_parser_included_ranges(parser_, &count); Napi::Array result = Napi::Array::New(env, count); for (uint32_t i = 0; i < count; i++) { result[i] = RangeToJS(env, ranges[i]); } return result; } Napi::Value Parser::TimeoutMicros(const Napi::CallbackInfo &info) { uint64_t timeout_micros = ts_parser_timeout_micros(parser_); return Number::New(info.Env(), static_cast<double>(timeout_micros)); } Napi::Value Parser::SetTimeoutMicros(const Napi::CallbackInfo &info) { uint64_t timeout_micros; if (!info[0].IsNumber()) { throw TypeError::New(info.Env(), "First argument must be a number"); } timeout_micros = info[0].As<Number>().Uint32Value(); ts_parser_set_timeout_micros(parser_, timeout_micros); return info.This(); } Napi::Value Parser::GetLogger(const Napi::CallbackInfo &info) { TSLogger current_logger = ts_parser_logger(parser_); if ((current_logger.payload != nullptr) && current_logger.log == Logger::Log) { auto *logger = static_cast<Logger *>(current_logger.payload); return logger->func.Value(); } return info.Env().Null(); } Napi::Value Parser::SetLogger(const Napi::CallbackInfo &info) { TSLogger current_logger = ts_parser_logger(parser_); if (info[0].IsFunction()) { if (current_logger.payload != nullptr) { delete static_cast<Logger *>(current_logger.payload); } ts_parser_set_logger(parser_, Logger::Make(info[0].As<Function>())); } else if (info[0].IsEmpty() || info[0].IsUndefined() || info[0].IsNull() || (info[0].IsBoolean() && !info[0].As<Boolean>())) { if (current_logger.payload != nullptr) { delete static_cast<Logger *>(current_logger.payload); } ts_parser_set_logger(parser_, { nullptr, nullptr }); } else { throw TypeError::New(info.Env(), "Logger callback must either be a function or a falsy value"); } return info.This(); } Napi::Value Parser::PrintDotGraphs(const Napi::CallbackInfo &info) { bool should_print = true; int32_t fd = fileno(stderr); if (info.Length() > 0) { if (!info[0].IsBoolean()) { throw TypeError::New(info.Env(), "First argument must be a boolean"); } should_print = info[0].As<Boolean>(); } if (info.Length() > 1) { if (!info[1].IsNumber()) { throw TypeError::New(info.Env(), "Second argument must be a number"); } fd = info[1].As<Number>().Int32Value(); } ts_parser_print_dot_graphs(parser_, should_print ? fd : -1); return info.This(); } Napi::Value Parser::Reset(const Napi::CallbackInfo & info) { ts_parser_reset(parser_); return info.This(); } } // namespace node_tree_sitter

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/EricA1019/CTS_MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server