Skip to content
This repository was archived by the owner on Nov 5, 2024. It is now read-only.

Commit 6bfe0e4

Browse files
committed
implemented custom decoder
1 parent cbf66f6 commit 6bfe0e4

File tree

3 files changed

+304
-81
lines changed

3 files changed

+304
-81
lines changed

convert.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,16 @@ namespace lib_ruby_parser_node
240240
});
241241
}
242242

243+
Value convert(Bytes &bytes, Env env)
244+
{
245+
Napi::Array array = Napi::Array::New(env, bytes.size());
246+
for (size_t i = 0; i < bytes.size(); i++)
247+
{
248+
array.Set(i, Number::New(env, bytes.ptr()[i]));
249+
}
250+
return array;
251+
}
252+
243253
void InitCustomTypes(Env env, Object exports)
244254
{
245255
Napi::Function fn;

node_bindings.cc

Lines changed: 134 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,109 @@
22
#include "lib-ruby-parser.h"
33
#include "convert.h"
44
#include <iostream>
5+
#include <variant>
6+
#include <tuple>
57

68
using namespace lib_ruby_parser;
79
using namespace Napi;
810

911
namespace lib_ruby_parser_node
1012
{
11-
Value parse(const CallbackInfo &info)
13+
14+
class JsCustomDecoder : public CustomDecoder
1215
{
13-
Env env = info.Env();
16+
public:
17+
struct DecodeError
18+
{
19+
bool has_error;
20+
std::string error;
21+
};
1422

15-
if (info.Length() != 2)
23+
Napi::FunctionReference callback;
24+
std::shared_ptr<DecodeError> error;
25+
26+
JsCustomDecoder(const Napi::Function &callback, std::shared_ptr<DecodeError> error)
1627
{
17-
TypeError::New(env, "Wrong number of arguments (expected 2)")
18-
.ThrowAsJavaScriptException();
19-
return env.Null();
28+
this->callback = Napi::Persistent(callback);
29+
this->error = error;
2030
}
2131

22-
Value source_arg = info[0];
23-
if (!source_arg.IsString())
32+
Env env()
2433
{
25-
TypeError::New(env, "The first argument must be a string")
26-
.ThrowAsJavaScriptException();
27-
return env.Null();
34+
return this->callback.Env();
2835
}
29-
std::string source = source_arg.As<String>().Utf8Value();
3036

31-
Value options_arg = info[1];
32-
if (!options_arg.IsObject())
37+
virtual Result rewrite(std::string encoding, Bytes input)
3338
{
34-
TypeError::New(env, "The second argument must be an object")
35-
.ThrowAsJavaScriptException();
36-
return env.Null();
39+
Value raw_response = callback.Call({
40+
String::New(env(), encoding),
41+
convert(input, env()),
42+
});
43+
if (!raw_response.IsObject())
44+
return JsError("response must be an object");
45+
46+
Object response = raw_response.As<Object>();
47+
48+
Value success = response.Get("success");
49+
if (!success.IsBoolean())
50+
return JsError("'success' field must be true/false");
51+
52+
if (success.ToBoolean().Value())
53+
{
54+
// success, consume 'output' field
55+
if (!response.Get("output").IsArray())
56+
return JsError("'output' field must be an array");
57+
58+
Napi::Array output = response.Get("output").As<Napi::Array>();
59+
std::vector<char> bytes;
60+
bytes.reserve(output.Length());
61+
for (size_t i = 0; i < output.Length(); i++)
62+
{
63+
Value byte = output[i];
64+
if (!byte.IsNumber())
65+
{
66+
return JsError("'output' field contains invalid byte");
67+
}
68+
bytes.push_back(byte.ToNumber().Int32Value());
69+
}
70+
return Result::Ok(Bytes(std::move(bytes)));
71+
}
72+
else
73+
{
74+
// error, consume 'error' field
75+
if (!response.Get("error").IsString())
76+
return JsError("'error' field must be a string");
77+
78+
String error = String::New(env(), response.Get("error").ToString().Utf8Value());
79+
return Result::Error(error);
80+
}
3781
}
38-
Object options_obj = options_arg.As<Object>();
82+
virtual ~JsCustomDecoder() {}
3983

84+
Result JsError(std::string message)
85+
{
86+
this->error->has_error = true;
87+
this->error->error = "custom_rewriter: " + message;
88+
return Result::Error(message);
89+
}
90+
};
91+
92+
Value JsThrow(Env env, std::string message)
93+
{
94+
TypeError::New(env, message).ThrowAsJavaScriptException();
95+
return env.Null();
96+
}
97+
98+
using SharedDecodeError = std::shared_ptr<JsCustomDecoder::DecodeError>;
99+
using BuildParserOptionsSuccess = std::tuple<ParserOptions, SharedDecodeError>;
100+
using BuildParserOptionsResult = std::variant<BuildParserOptionsSuccess, std::string>;
101+
102+
BuildParserOptionsResult BuildParserOptions(Napi::Object &object)
103+
{
40104
ParserOptions options;
41-
options.record_tokens = options_obj.Get("record_tokens").ToBoolean().Value();
42-
options.debug = options_obj.Get("debug").ToBoolean().Value();
43-
auto buffer_name = options_obj.Get("buffer_name");
105+
options.record_tokens = object.Get("record_tokens").ToBoolean().Value();
106+
options.debug = object.Get("debug").ToBoolean().Value();
107+
Napi::Value buffer_name = object.Get("buffer_name");
44108
if (buffer_name.IsString())
45109
{
46110
options.buffer_name = buffer_name.As<String>().Utf8Value();
@@ -51,12 +115,58 @@ namespace lib_ruby_parser_node
51115
}
52116
else
53117
{
54-
TypeError::New(env, "buffer_name must be string/undefined")
55-
.ThrowAsJavaScriptException();
56-
return env.Null();
118+
return "buffer_name must be string/undefined";
57119
}
120+
SharedDecodeError decode_error = std::make_shared<JsCustomDecoder::DecodeError>();
121+
Napi::Value custom_decoder = object.Get("custom_decoder");
122+
if (custom_decoder.IsFunction())
123+
{
124+
options.custom_decoder = std::make_unique<JsCustomDecoder>(custom_decoder.As<Function>(), decode_error);
125+
}
126+
else if (custom_decoder.IsUndefined())
127+
{
128+
// ok, default is used
129+
}
130+
else
131+
{
132+
return "custom_decoder must be function/undefined";
133+
}
134+
135+
return std::make_tuple(std::move(options), decode_error);
136+
}
137+
138+
Value parse(const CallbackInfo &info)
139+
{
140+
Env env = info.Env();
141+
142+
if (info.Length() != 2)
143+
return JsThrow(env, "Wrong number of arguments (expected 2)");
144+
145+
if (!info[0].IsString())
146+
return JsThrow(env, "The first argument must be a string");
147+
std::string source = info[0].As<String>().Utf8Value();
148+
149+
if (!info[1].IsObject())
150+
return JsThrow(env, "The second argument must be an object");
151+
Object js_options = info[1].As<Object>();
152+
153+
auto build_result = BuildParserOptions(js_options);
154+
if (auto error = std::get_if<std::string>(&build_result))
155+
{
156+
return JsThrow(env, *error);
157+
}
158+
auto tuple = std::get<BuildParserOptionsSuccess>(std::move(build_result));
159+
auto decode_error = std::get<1>(tuple);
160+
auto options = std::get<0>(std::move(tuple));
58161

59162
auto result = ParserResult::from_source(source, std::move(options));
163+
164+
if (decode_error->has_error)
165+
{
166+
TypeError::New(env, decode_error->error)
167+
.ThrowAsJavaScriptException();
168+
return env.Null();
169+
}
60170
return convert(std::move(result), env);
61171
}
62172

0 commit comments

Comments
 (0)