@@ -23,10 +23,12 @@ export class RubyVM {
23
23
guest : RbAbi . RbAbiGuest ;
24
24
private instance : WebAssembly . Instance | null = null ;
25
25
private exporter : JsValueExporter ;
26
+ private exceptionFormatter : RbExceptionFormatter ;
26
27
27
28
constructor ( ) {
28
29
this . guest = new RbAbi . RbAbiGuest ( ) ;
29
30
this . exporter = new JsValueExporter ( ) ;
31
+ this . exceptionFormatter = new RbExceptionFormatter ( ) ;
30
32
}
31
33
32
34
/**
@@ -168,7 +170,7 @@ export class RubyVM {
168
170
*
169
171
*/
170
172
eval ( code : string ) : RbValue {
171
- return evalRbCode ( this , this . exporter , code ) ;
173
+ return evalRbCode ( this , { exporter : this . exporter , exceptionFormatter : this . exceptionFormatter } , code ) ;
172
174
}
173
175
}
174
176
@@ -189,7 +191,7 @@ export class RbValue {
189
191
constructor (
190
192
private inner : RbAbi . RbAbiValue ,
191
193
private vm : RubyVM ,
192
- private exporter : JsValueExporter
194
+ private privateObject : RubyVMPrivate
193
195
) { }
194
196
195
197
/**
@@ -207,9 +209,9 @@ export class RbValue {
207
209
call ( callee : string , ...args : RbValue [ ] ) : RbValue {
208
210
const innerArgs = args . map ( ( arg ) => arg . inner ) ;
209
211
return new RbValue (
210
- callRbMethod ( this . vm , this . exporter , this . inner , callee , innerArgs ) ,
212
+ callRbMethod ( this . vm , this . privateObject , this . inner , callee , innerArgs ) ,
211
213
this . vm ,
212
- this . exporter
214
+ this . privateObject
213
215
) ;
214
216
}
215
217
@@ -231,7 +233,7 @@ export class RbValue {
231
233
toString ( ) : string {
232
234
const rbString = callRbMethod (
233
235
this . vm ,
234
- this . exporter ,
236
+ this . privateObject ,
235
237
this . inner ,
236
238
"to_s" ,
237
239
[ ]
@@ -252,7 +254,7 @@ export class RbValue {
252
254
return null ;
253
255
}
254
256
jsValue . call ( "__export_to_js" ) ;
255
- return this . exporter . exportJsValue ( ) ;
257
+ return this . privateObject . exporter . exportJsValue ( ) ;
256
258
}
257
259
}
258
260
@@ -269,18 +271,52 @@ enum ruby_tag_type {
269
271
Mask = 0xf ,
270
272
}
271
273
272
- const formatException = (
273
- klass : string ,
274
- message : string ,
275
- backtrace : [ string , string ]
276
- ) => {
277
- return `${ backtrace [ 0 ] } : ${ message } (${ klass } )\n${ backtrace [ 1 ] } ` ;
274
+ type RubyVMPrivate = {
275
+ exporter : JsValueExporter ,
276
+ exceptionFormatter : RbExceptionFormatter ,
278
277
} ;
279
278
279
+
280
+ class RbExceptionFormatter {
281
+ private literalsCache : [ RbValue , RbValue , RbValue ] | null = null ;
282
+
283
+ format ( error : RbValue , vm : RubyVM , privateObject : RubyVMPrivate ) : string {
284
+ const [ zeroLiteral , oneLiteral , newLineLiteral ] = ( ( ) => {
285
+ if ( this . literalsCache == null ) {
286
+ const zeroOneNewLine : [ RbValue , RbValue , RbValue ] = [
287
+ evalRbCode ( vm , privateObject , "0" ) ,
288
+ evalRbCode ( vm , privateObject , "1" ) ,
289
+ evalRbCode ( vm , privateObject , `"\n"` )
290
+ ] ;
291
+ this . literalsCache = zeroOneNewLine ;
292
+ return zeroOneNewLine ;
293
+ } else {
294
+ return this . literalsCache ;
295
+ }
296
+ } ) ( ) ;
297
+
298
+ const backtrace = error . call ( "backtrace" ) ;
299
+ const firstLine = backtrace . call ( "at" , zeroLiteral ) ;
300
+ const restLines = backtrace . call ( "drop" , oneLiteral ) . call ( "join" , newLineLiteral ) ;
301
+ return this . formatString ( error . call ( "class" ) . toString ( ) , error . toString ( ) , [
302
+ firstLine . toString ( ) ,
303
+ restLines . toString ( ) ,
304
+ ] )
305
+ }
306
+
307
+ formatString (
308
+ klass : string ,
309
+ message : string ,
310
+ backtrace : [ string , string ]
311
+ ) : string {
312
+ return `${ backtrace [ 0 ] } : ${ message } (${ klass } )\n${ backtrace [ 1 ] } ` ;
313
+ } ;
314
+ }
315
+
280
316
const checkStatusTag = (
281
317
rawTag : number ,
282
318
vm : RubyVM ,
283
- exporter : JsValueExporter
319
+ privateObject : RubyVMPrivate
284
320
) => {
285
321
switch ( rawTag & ruby_tag_type . Mask ) {
286
322
case ruby_tag_type . None :
@@ -299,40 +335,34 @@ const checkStatusTag = (
299
335
throw new RbError ( "unexpected throw" ) ;
300
336
case ruby_tag_type . Raise :
301
337
case ruby_tag_type . Fatal :
302
- const error = new RbValue ( vm . guest . rbErrinfo ( ) , vm , exporter ) ;
303
- const newLine = evalRbCode ( vm , exporter , `"\n"` ) ;
304
- const backtrace = error . call ( "backtrace" ) ;
305
- const firstLine = backtrace . call ( "at" , evalRbCode ( vm , exporter , "0" ) ) ;
306
- const restLines = backtrace
307
- . call ( "drop" , evalRbCode ( vm , exporter , "1" ) )
308
- . call ( "join" , newLine ) ;
309
- throw new RbError (
310
- formatException ( error . call ( "class" ) . toString ( ) , error . toString ( ) , [
311
- firstLine . toString ( ) ,
312
- restLines . toString ( ) ,
313
- ] )
314
- ) ;
338
+ const error = new RbValue ( vm . guest . rbErrinfo ( ) , vm , privateObject ) ;
339
+ if ( error . call ( "nil?" ) . toString ( ) === "true" ) {
340
+ throw new RbError ( "no exception object" ) ;
341
+ }
342
+ // clear errinfo if got exception due to no rb_jump_tag
343
+ vm . guest . rbClearErrinfo ( ) ;
344
+ throw new RbError ( privateObject . exceptionFormatter . format ( error , vm , privateObject ) ) ;
315
345
default :
316
346
throw new RbError ( `unknown error tag: ${ rawTag } ` ) ;
317
347
}
318
348
} ;
319
349
320
350
const callRbMethod = (
321
351
vm : RubyVM ,
322
- exporter : JsValueExporter ,
352
+ privateObject : RubyVMPrivate ,
323
353
recv : RbAbi . RbAbiValue ,
324
354
callee : string ,
325
355
args : RbAbi . RbAbiValue [ ]
326
356
) => {
327
357
const mid = vm . guest . rbIntern ( callee + "\0" ) ;
328
358
const [ value , status ] = vm . guest . rbFuncallvProtect ( recv , mid , args ) ;
329
- checkStatusTag ( status , vm , exporter ) ;
359
+ checkStatusTag ( status , vm , privateObject ) ;
330
360
return value ;
331
361
} ;
332
- const evalRbCode = ( vm : RubyVM , exporter : JsValueExporter , code : string ) => {
362
+ const evalRbCode = ( vm : RubyVM , privateObject : RubyVMPrivate , code : string ) => {
333
363
const [ value , status ] = vm . guest . rbEvalStringProtect ( code + "\0" ) ;
334
- checkStatusTag ( status , vm , exporter ) ;
335
- return new RbValue ( value , vm , exporter ) ;
364
+ checkStatusTag ( status , vm , privateObject ) ;
365
+ return new RbValue ( value , vm , privateObject ) ;
336
366
} ;
337
367
338
368
/**
0 commit comments