22#include " lib-ruby-parser.h"
33#include " convert.h"
44#include < iostream>
5+ #include < variant>
6+ #include < tuple>
57
68using namespace lib_ruby_parser ;
79using namespace Napi ;
810
911namespace 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