johnson 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/CHANGELOG.rdoc +12 -0
  2. data/Manifest.txt +6 -4
  3. data/README.rdoc +2 -8
  4. data/Rakefile +11 -11
  5. data/bin/johnson +1 -3
  6. data/ext/spidermonkey/context.c +3 -2
  7. data/ext/spidermonkey/conversions.c +61 -27
  8. data/ext/spidermonkey/conversions.h +13 -0
  9. data/ext/spidermonkey/debugger.c +13 -5
  10. data/ext/spidermonkey/debugger.h +1 -0
  11. data/ext/spidermonkey/extconf.rb +2 -3
  12. data/ext/spidermonkey/jroot.h +11 -1
  13. data/ext/spidermonkey/js_land_proxy.c +21 -11
  14. data/ext/spidermonkey/ruby_land_proxy.c +116 -41
  15. data/ext/spidermonkey/ruby_land_proxy.h +21 -0
  16. data/ext/spidermonkey/runtime.c +85 -19
  17. data/ext/spidermonkey/runtime.h +2 -0
  18. data/ext/spidermonkey/spidermonkey.c +3 -1
  19. data/lib/johnson.rb +19 -27
  20. data/lib/johnson/cli.rb +2 -1
  21. data/{js/johnson → lib/johnson/js}/cli.js +0 -0
  22. data/lib/johnson/js/core.js +34 -0
  23. data/lib/johnson/js/prelude.js +149 -0
  24. data/lib/johnson/ruby_land_proxy.rb +113 -0
  25. data/lib/johnson/runtime.rb +92 -33
  26. data/lib/johnson/spidermonkey.rb +12 -0
  27. data/lib/johnson/spidermonkey/js_land_proxy.rb +10 -8
  28. data/lib/johnson/spidermonkey/ruby_land_proxy.rb +10 -47
  29. data/lib/johnson/spidermonkey/runtime.rb +11 -31
  30. data/test/johnson/conversions/array_test.rb +41 -3
  31. data/test/johnson/conversions/string_test.rb +12 -0
  32. data/test/johnson/custom_conversions_test.rb +50 -0
  33. data/test/johnson/prelude_test.rb +23 -0
  34. data/test/johnson/runtime_test.rb +82 -2
  35. data/test/johnson/spidermonkey/ruby_land_proxy_test.rb +17 -1
  36. data/test/johnson/spidermonkey/runtime_test.rb +24 -0
  37. data/vendor/spidermonkey/jsprf.c +2 -0
  38. metadata +22 -9
  39. data/js/johnson/prelude.js +0 -80
  40. data/lib/johnson/version.rb +0 -3
  41. data/lib/rails/init.rb +0 -37
@@ -559,17 +559,22 @@ static void finalize(JSContext* js_context, JSObject* obj)
559
559
 
560
560
  JSBool make_js_land_proxy(JohnsonRuntime* runtime, VALUE value, jsval* retval)
561
561
  {
562
- *retval = (jsval)JS_HashTableLookup(runtime->rbids, (void *)value);
563
-
564
- if (*retval)
562
+ jsval base_value = (jsval)JS_HashTableLookup(runtime->rbids, (void *)value);
563
+
564
+ JSContext * context = johnson_get_current_context(runtime);
565
+ PREPARE_JROOTS(context, 2);
566
+
567
+ jsval johnson = JSVAL_NULL;
568
+ JCHECK(evaluate_js_property_expression(runtime, "Johnson", &johnson));
569
+ JROOT(johnson);
570
+
571
+ if (base_value)
565
572
  {
566
- return JS_TRUE;
573
+ JCHECK(JS_CallFunctionName(context, johnson, "applyConversions", 1, &base_value, retval));
574
+ JRETURN;
567
575
  }
568
576
  else
569
577
  {
570
- JSContext * context = johnson_get_current_context(runtime);
571
- PREPARE_JROOTS(context, 1);
572
-
573
578
  JSObject *jsobj;
574
579
 
575
580
  JSClass *klass = &JSLandProxyClass;
@@ -596,15 +601,20 @@ JSBool make_js_land_proxy(JohnsonRuntime* runtime, VALUE value, jsval* retval)
596
601
  JCHECK(JS_DefineFunction(context, jsobj, "toArray", to_array, 0, 0));
597
602
  JCHECK(JS_DefineFunction(context, jsobj, "toString", to_string, 0, 0));
598
603
 
599
- *retval = OBJECT_TO_JSVAL(jsobj);
604
+ base_value = OBJECT_TO_JSVAL(jsobj);
600
605
 
601
- // put the proxy OID in the id map
602
- JCHECK(JS_HashTableAdd(runtime->rbids, (void *)value, (void *)(*retval)));
603
-
604
606
  // root the ruby value for GC
605
607
  VALUE ruby_runtime = (VALUE)JS_GetRuntimePrivate(runtime->js);
606
608
  rb_funcall(ruby_runtime, rb_intern("add_gcthing"), 1, value);
607
609
 
610
+ jsval wrapped_value = JSVAL_NULL;
611
+ JCHECK(JS_CallFunctionName(context, johnson, "applyWrappers", 1, &base_value, &wrapped_value));
612
+
613
+ // put the proxy OID in the id map
614
+ JCHECK(JS_HashTableAdd(runtime->rbids, (void *)value, (void *)(wrapped_value)));
615
+
616
+ JCHECK(JS_CallFunctionName(context, johnson, "applyConversions", 1, &wrapped_value, retval));
617
+
608
618
  JRETURN;
609
619
  }
610
620
  }
@@ -7,7 +7,16 @@ DEFINE_RUBY_WRAPPER(rb_call_super, rb_call_super, ARGLIST2(argc, argv))
7
7
  DECLARE_RUBY_WRAPPER(rb_yield, VALUE v)
8
8
  DEFINE_RUBY_WRAPPER(rb_yield, rb_yield, ARGLIST1(v))
9
9
 
10
+ DECLARE_RUBY_WRAPPER(rb_check_type, VALUE o; int t)
11
+ DEFINE_VOID_RUBY_WRAPPER(rb_check_type, rb_check_type, ARGLIST2(o, t))
12
+
13
+ DEFINE_RUBY_WRAPPER(rb_string_value, rb_string_value, ARGLIST1(v))
14
+ DEFINE_VOID_RUBY_WRAPPER(rb_string_value_cstr, rb_string_value_cstr, ARGLIST1(v))
15
+
16
+ DEFINE_RUBY_WRAPPER(make_ruby_land_proxy, make_ruby_land_proxy, ARGLIST3(runtime, value, root_name))
17
+
10
18
  static VALUE proxy_class = Qnil;
19
+ static VALUE script_class = Qnil;
11
20
 
12
21
  static inline JSBool get_jsval_for_proxy(RubyLandProxy* proxy, jsval* jv)
13
22
  {
@@ -45,7 +54,8 @@ static VALUE call_js_function_value(JohnsonRuntime* runtime, jsval target, jsval
45
54
  * call-seq:
46
55
  * [](name)
47
56
  *
48
- * Returns the property with +name+.
57
+ * Retrieves the current value of the +name+ property of this JavaScript
58
+ * object.
49
59
  */
50
60
  static VALUE
51
61
  get(VALUE self, VALUE name)
@@ -67,8 +77,10 @@ get(VALUE self, VALUE name)
67
77
  JCHECK(JS_GetElement(context,
68
78
  JSVAL_TO_OBJECT(proxy_value), (jsint)(NUM2INT(name)), &js_value));
69
79
  break;
80
+ case T_SYMBOL:
81
+ name = RB_FUNCALL_0(name, RB_INTERN("to_s"));
70
82
  default:
71
- Check_Type(name, T_STRING);
83
+ CALL_RUBY_WRAPPER(rb_string_value_cstr, &name);
72
84
  JCHECK(JS_GetProperty(context,
73
85
  JSVAL_TO_OBJECT(proxy_value), StringValueCStr(name), &js_value));
74
86
  break;
@@ -79,9 +91,9 @@ get(VALUE self, VALUE name)
79
91
 
80
92
  /*
81
93
  * call-seq:
82
- * []=(name,value)
94
+ * []=(name, value)
83
95
  *
84
- * Sets the property with +name+ to +value+.
96
+ * Sets this JavaScript object's +name+ property to +value+.
85
97
  */
86
98
  static VALUE
87
99
  set(VALUE self, VALUE name, VALUE value)
@@ -106,8 +118,10 @@ set(VALUE self, VALUE name, VALUE value)
106
118
  JCHECK(JS_SetElement(context,
107
119
  JSVAL_TO_OBJECT(proxy_value), (jsint)(NUM2INT(name)), &js_value));
108
120
  break;
121
+ case T_SYMBOL:
122
+ name = RB_FUNCALL_0(name, RB_INTERN("to_s"));
109
123
  default:
110
- Check_Type(name, T_STRING);
124
+ CALL_RUBY_WRAPPER(rb_string_value_cstr, &name);
111
125
  JCHECK(JS_SetProperty(context,
112
126
  JSVAL_TO_OBJECT(proxy_value), StringValueCStr(name), &js_value));
113
127
  break;
@@ -118,9 +132,9 @@ set(VALUE self, VALUE name, VALUE value)
118
132
 
119
133
  /*
120
134
  * call-seq:
121
- * function?
135
+ * function?()
122
136
  *
123
- * Returns <code>true</code> if this is a function.
137
+ * Returns <code>true</code> if this JavaScript object is a function.
124
138
  */
125
139
  static VALUE
126
140
  function_p(VALUE self)
@@ -135,11 +149,18 @@ function_p(VALUE self)
135
149
  JRETURN_RUBY(JS_TypeOfValue(context, proxy_value) == JSTYPE_FUNCTION ? Qtrue : Qfalse);
136
150
  }
137
151
 
152
+ static VALUE
153
+ callable_test_p(VALUE self, VALUE proxy)
154
+ {
155
+ return function_p(proxy);
156
+ }
157
+
138
158
  /*
139
159
  * call-seq:
140
160
  * respond_to?(symbol)
141
161
  *
142
- * Returns <code>true</code> if _obj_ responds to given method.
162
+ * Returns <code>true</code> if this JavaScript object responds to the
163
+ * named method.
143
164
  */
144
165
  static VALUE
145
166
  respond_to_p(int argc, const VALUE* argv, VALUE self)
@@ -178,9 +199,10 @@ respond_to_p(int argc, const VALUE* argv, VALUE self)
178
199
 
179
200
  /*
180
201
  * call-seq:
181
- * native_call(global, *args)
202
+ * native_call(this, *args)
182
203
  *
183
- * Call as a function with given +global+ using *args.
204
+ * Call this Ruby Object as a function, with the given +this+ object and
205
+ * arguments. Equivalent to the call method in JavaScript.
184
206
  */
185
207
  static VALUE
186
208
  native_call(int argc, VALUE* argv, VALUE self)
@@ -217,9 +239,12 @@ destroy_id_array(JSContext* context, void* data)
217
239
 
218
240
  /*
219
241
  * call-seq:
220
- * each { |obj| block }
242
+ * each {| element | block }
243
+ * each {| name, value | block }
221
244
  *
222
- * Calls <em>block</em> with each item in the collection.
245
+ * Calls <em>block</em> with each item in this JavaScript array, or with
246
+ * each +name+/+value+ pair (like a Hash) for any other JavaScript
247
+ * object.
223
248
  */
224
249
  static VALUE
225
250
  each(VALUE self)
@@ -248,7 +273,7 @@ each(VALUE self)
248
273
  {
249
274
  jsval element;
250
275
  JCHECK(JS_GetElement(context, value, (signed) i, &element));
251
- CALL_RUBY_WRAPPER(rb_yield, convert_to_ruby(proxy->runtime, element));
276
+ CALL_RUBY_WRAPPER(rb_yield, CONVERT_TO_RUBY(proxy->runtime, element));
252
277
  }
253
278
  }
254
279
  else
@@ -296,9 +321,10 @@ each(VALUE self)
296
321
 
297
322
  /*
298
323
  * call-seq:
299
- * length
324
+ * length()
300
325
  *
301
- * Returns the length of the collection.
326
+ * Returns the number of entries in the JavaScript array, or the number
327
+ * of properties on the JavaScript object.
302
328
  */
303
329
  static VALUE
304
330
  length(VALUE self)
@@ -337,9 +363,10 @@ length(VALUE self)
337
363
 
338
364
  /*
339
365
  * call-seq:
340
- * runtime
366
+ * runtime()
341
367
  *
342
- * Returns runtime.
368
+ * Returns the Johnson::SpiderMonkey::Runtime against which this object
369
+ * is registered.
343
370
  */
344
371
  static VALUE
345
372
  runtime(VALUE self)
@@ -353,13 +380,17 @@ runtime(VALUE self)
353
380
  * call-seq:
354
381
  * function_property?(name)
355
382
  *
356
- * Returns <code>true</code> if +name+ is a function property.
383
+ * Returns <code>true</code> if this JavaScript object's +name+ property
384
+ * is a function.
357
385
  */
358
386
  static VALUE
359
387
  function_property_p(VALUE self, VALUE name)
360
388
  {
361
- Check_Type(name, T_STRING);
362
-
389
+ if (TYPE(name) == T_SYMBOL)
390
+ name = rb_funcall(name, rb_intern("to_s"), 0);
391
+
392
+ rb_string_value_cstr(&name);
393
+
363
394
  RubyLandProxy* proxy;
364
395
  Data_Get_Struct(self, RubyLandProxy, proxy);
365
396
  JSContext * context = johnson_get_current_context(proxy->runtime);
@@ -386,7 +417,11 @@ function_property_p(VALUE self, VALUE name)
386
417
  * call-seq:
387
418
  * call_function_property(name, arguments)
388
419
  *
389
- * Calls function +name+ with +arguments+.
420
+ * Calls this JavaScript object's +name+ method, passing the given
421
+ * arguments.
422
+ *
423
+ * Equivalent to:
424
+ * proxy[name].native_call(proxy, *arguments)
390
425
  */
391
426
  static VALUE
392
427
  call_function_property(int argc, VALUE* argv, VALUE self)
@@ -405,26 +440,30 @@ call_function_property(int argc, VALUE* argv, VALUE self)
405
440
  JROOT(proxy_value);
406
441
 
407
442
  jsval function;
443
+
444
+ VALUE name = argv[0];
445
+ CALL_RUBY_WRAPPER(rb_string_value_cstr, &name);
408
446
 
409
447
  JCHECK(JS_GetProperty(context,
410
- JSVAL_TO_OBJECT(proxy_value), StringValueCStr(argv[0]), &function));
448
+ JSVAL_TO_OBJECT(proxy_value), StringValueCStr(name), &function));
411
449
 
412
450
  JROOT(function);
413
451
 
414
- JSType funtype = JS_TypeOfValue(context, function);
415
-
416
452
  // should never be anything but a function
417
- if (funtype != JSTYPE_FUNCTION)
418
- JERROR("Specified property \"%s\" isn't a function.", StringValueCStr(argv[0]));
453
+ if (!JS_ObjectIsFunction(context, function))
454
+ JERROR("Specified property \"%s\" isn't a function.", StringValueCStr(name));
455
+
456
+ REMOVE_JROOTS;
419
457
 
420
- JRETURN_RUBY(call_js_function_value(proxy->runtime, proxy_value, function, argc - 1, &(argv[1])));
458
+ return call_js_function_value(proxy->runtime, proxy_value, function, argc - 1, &(argv[1]));
421
459
  }
422
460
 
423
461
  /*
424
462
  * call-seq:
425
- * to_s
463
+ * to_s()
426
464
  *
427
- * Converts object to a string.
465
+ * Converts the JavaScript object to a string, using its toString method
466
+ * if available.
428
467
  */
429
468
  static VALUE to_s(VALUE self)
430
469
  {
@@ -439,7 +478,7 @@ static VALUE to_s(VALUE self)
439
478
  JROOT(proxy_value);
440
479
 
441
480
  JSString* str = JS_ValueToString(context, proxy_value);
442
- JRETURN_RUBY(convert_js_string_to_ruby(proxy->runtime, str));
481
+ JRETURN_RUBY(CONVERT_JS_STRING_TO_RUBY(proxy->runtime, str));
443
482
  }
444
483
 
445
484
  ///////////////////////////////////////////////////////////////////////////
@@ -468,7 +507,26 @@ static void finalize(RubyLandProxy* proxy)
468
507
 
469
508
  bool ruby_value_is_proxy(VALUE maybe_proxy)
470
509
  {
471
- return proxy_class == CLASS_OF(maybe_proxy);
510
+ return rb_obj_is_kind_of(maybe_proxy, proxy_class);
511
+ }
512
+
513
+ bool ruby_value_is_script_proxy(VALUE maybe_proxy)
514
+ {
515
+ return rb_obj_is_kind_of(maybe_proxy, script_class);
516
+ }
517
+
518
+ VALUE apply_wrappers(VALUE proxy)
519
+ {
520
+ VALUE johnson = rb_const_get(rb_mKernel, rb_intern("Johnson"));
521
+ VALUE johnson_proxy = rb_const_get(johnson, rb_intern("RubyLandProxy"));
522
+ return rb_funcall(johnson_proxy, rb_intern("apply_wrappers"), 1, proxy);
523
+ }
524
+
525
+ VALUE apply_conversions(VALUE proxy)
526
+ {
527
+ VALUE johnson = rb_const_get(rb_mKernel, rb_intern("Johnson"));
528
+ VALUE johnson_proxy = rb_const_get(johnson, rb_intern("RubyLandProxy"));
529
+ return rb_funcall(johnson_proxy, rb_intern("apply_conversions"), 1, proxy);
472
530
  }
473
531
 
474
532
  JSBool unwrap_ruby_land_proxy(JohnsonRuntime* runtime, VALUE wrapped, jsval* retval)
@@ -487,34 +545,41 @@ JSBool unwrap_ruby_land_proxy(JohnsonRuntime* runtime, VALUE wrapped, jsval* ret
487
545
 
488
546
  VALUE make_ruby_land_proxy(JohnsonRuntime* runtime, jsval value, const char const* root_name)
489
547
  {
490
- VALUE id = (VALUE)JS_HashTableLookup(runtime->jsids, (void *)value);
548
+ RubyLandProxy * our_proxy = (RubyLandProxy *)JS_HashTableLookup(runtime->jsids, (void *)value);
491
549
 
492
- if (id)
550
+ if (our_proxy)
493
551
  {
494
552
  // if we already have a proxy, return it
495
- return id;
553
+ return apply_conversions(our_proxy->self);
496
554
  }
497
555
  else
498
556
  {
499
557
  // otherwise make one and cache it
500
- RubyLandProxy* our_proxy;
501
- VALUE proxy = Data_Make_Struct(proxy_class, RubyLandProxy, 0, finalize, our_proxy);
558
+ VALUE proxy = Data_Make_Struct((strncmp(root_name, "JSScriptProxy", strlen("JSScriptProxy")) ? proxy_class : script_class), RubyLandProxy, 0, finalize, our_proxy);
502
559
 
503
560
  JSContext * context = johnson_get_current_context(runtime);
504
561
 
505
562
  PREPARE_RUBY_JROOTS(context, 1);
506
563
  JROOT(value);
507
564
 
565
+ VALUE rb_runtime = (VALUE)JS_GetRuntimePrivate(runtime->js);
566
+ rb_iv_set(proxy, "@runtime", rb_runtime);
567
+
508
568
  our_proxy->runtime = runtime;
509
569
  our_proxy->key = (void *)value;
570
+ our_proxy->self = proxy;
510
571
 
511
572
  // root the value for JS GC and lookups
512
573
  JCHECK(JS_AddNamedRootRT(runtime->js, &(our_proxy->key), root_name));
513
574
 
514
575
  // put the proxy OID in the id map
515
- JCHECK(JS_HashTableAdd(runtime->jsids, (void *)value, (void *)proxy));
516
-
517
- JRETURN_RUBY(proxy);
576
+ JCHECK(JS_HashTableAdd(runtime->jsids, (void *)value, (void *)our_proxy));
577
+
578
+ VALUE final_proxy = JPROTECT(apply_wrappers, proxy);
579
+
580
+ our_proxy->self = final_proxy;
581
+
582
+ JRETURN_RUBY(JPROTECT(apply_conversions, final_proxy));
518
583
  }
519
584
  }
520
585
 
@@ -525,8 +590,11 @@ void init_Johnson_SpiderMonkey_Proxy(VALUE spidermonkey)
525
590
  VALUE spidermonkey = rb_define_module_under(johnson, "SpiderMonkey");
526
591
  */
527
592
 
593
+ VALUE johnson = rb_const_get(rb_mKernel, rb_intern("Johnson"));
594
+ VALUE johnson_proxy = rb_const_get(johnson, rb_intern("RubyLandProxy"));
595
+
528
596
  /* RubyLandProxy class. */
529
- proxy_class = rb_define_class_under(spidermonkey, "RubyLandProxy", rb_cObject);
597
+ proxy_class = rb_define_class_under(spidermonkey, "RubyLandProxy", johnson_proxy);
530
598
 
531
599
  rb_define_method(proxy_class, "[]", get, 1);
532
600
  rb_define_method(proxy_class, "[]=", set, 2);
@@ -536,8 +604,15 @@ void init_Johnson_SpiderMonkey_Proxy(VALUE spidermonkey)
536
604
  rb_define_method(proxy_class, "length", length, 0);
537
605
  rb_define_method(proxy_class, "to_s", to_s, 0);
538
606
 
539
- rb_define_private_method(proxy_class, "native_call", native_call, -1);
540
607
  rb_define_private_method(proxy_class, "runtime", runtime, 0);
541
608
  rb_define_private_method(proxy_class, "function_property?", function_property_p, 1);
542
609
  rb_define_private_method(proxy_class, "call_function_property", call_function_property, -1);
610
+
611
+ VALUE callable = rb_define_module_under(proxy_class, "Callable");
612
+ rb_define_singleton_method(callable, "test?", callable_test_p, 1);
613
+ rb_define_private_method(callable, "native_call", native_call, -1);
614
+
615
+ rb_funcall(johnson_proxy, rb_intern("insert_wrapper"), 1, callable);
616
+
617
+ script_class = rb_define_class_under(spidermonkey, "RubyLandScript", proxy_class);
543
618
  }
@@ -4,12 +4,33 @@
4
4
  #include "spidermonkey.h"
5
5
  #include "runtime.h"
6
6
 
7
+ #ifdef LEAK_ROOT_NAMES
8
+ #define LEAKY_ROOT_NAME(static_string, dynamic_detail) \
9
+ ({\
10
+ const char * const _leaky_root__detail = (dynamic_detail);\
11
+ char * _leaky_root__leaked = malloc(strlen(static_string) + strlen(_leaky_root__detail) + 2);\
12
+ strcpy(_leaky_root__leaked, static_string);\
13
+ _leaky_root__leaked[strlen(static_string)] = ':';\
14
+ strcpy(_leaky_root__leaked + strlen(static_string) + 1, _leaky_root__detail);\
15
+ _leaky_root__leaked;\
16
+ })
17
+ #else
18
+ #define LEAKY_ROOT_NAME(static_string, dynamic_detail) (static_string)
19
+ #endif
20
+
21
+ DECLARE_RUBY_WRAPPER(make_ruby_land_proxy, JohnsonRuntime* runtime; jsval value; const char const* root_name)
22
+
23
+ DECLARE_RUBY_WRAPPER(rb_string_value, VALUE v)
24
+ DECLARE_RUBY_WRAPPER(rb_string_value_cstr, VALUE v)
25
+
7
26
  typedef struct {
8
27
  void* key;
9
28
  JohnsonRuntime* runtime;
29
+ VALUE self;
10
30
  } RubyLandProxy;
11
31
 
12
32
  bool ruby_value_is_proxy(VALUE maybe_proxy);
33
+ bool ruby_value_is_script_proxy(VALUE maybe_proxy);
13
34
  JSBool unwrap_ruby_land_proxy(JohnsonRuntime* runtime, VALUE proxy, jsval* retval);
14
35
  VALUE make_ruby_land_proxy(JohnsonRuntime* runtime, jsval value, const char const* root_name);
15
36
  void init_Johnson_SpiderMonkey_Proxy(VALUE spidermonkey);
@@ -2,13 +2,13 @@
2
2
  #include "global.h"
3
3
  #include "idhash.h"
4
4
  #include "conversions.h"
5
- #include "jsdbgapi.h"
5
+ #include "debugger.h"
6
6
  #include "jroot.h"
7
7
  #include "ruby_land_proxy.h"
8
8
 
9
9
  /*
10
10
  * call-seq:
11
- * global
11
+ * global()
12
12
  *
13
13
  * Returns the global object used for this context.
14
14
  */
@@ -19,14 +19,15 @@ static VALUE global(VALUE self)
19
19
  return convert_to_ruby(runtime, OBJECT_TO_JSVAL(runtime->global));
20
20
  }
21
21
 
22
- static JSTrapStatus trap_handler( JSContext *UNUSED(context),
22
+ static JSTrapStatus trap_handler( JSContext *context,
23
23
  JSScript *UNUSED(script),
24
24
  jsbytecode *UNUSED(pc),
25
25
  jsval *UNUSED(rval),
26
26
  void *block_closure )
27
27
  {
28
+ PREPARE_JROOTS(context, 0);
28
29
  VALUE block = (VALUE)block_closure;
29
- rb_funcall(block, rb_intern("call"), 0);
30
+ RB_FUNCALL_0(block, rb_intern("call"));
30
31
  return JSTRAP_CONTINUE;
31
32
  }
32
33
 
@@ -34,7 +35,7 @@ static JSTrapStatus trap_handler( JSContext *UNUSED(context),
34
35
  * call-seq:
35
36
  * clear_trap(script, line_num)
36
37
  *
37
- * Set the trap at +script+ and +line_num+ to +block+
38
+ * Clear the trap previously set at +line_num+ of +script+.
38
39
  */
39
40
  static VALUE clear_trap(VALUE self, VALUE script, VALUE linenum)
40
41
  {
@@ -56,9 +57,10 @@ static VALUE clear_trap(VALUE self, VALUE script, VALUE linenum)
56
57
 
57
58
  /*
58
59
  * call-seq:
59
- * set_trap(script, parsecode, block)
60
+ * set_trap(script, line_num, block)
60
61
  *
61
- * Set the trap at +script+ and +parsecode+ to +block+
62
+ * Set a trap to invoke +block+ when execution of +script+ reaches
63
+ * +line_num+.
62
64
  */
63
65
  static VALUE set_trap(VALUE self, VALUE script, VALUE linenum, VALUE block)
64
66
  {
@@ -77,9 +79,10 @@ static VALUE set_trap(VALUE self, VALUE script, VALUE linenum, VALUE block)
77
79
 
78
80
  /*
79
81
  * call-seq:
80
- * native_compile(script, filename, linenum)
82
+ * native_compile(script_string, filename, linenum)
81
83
  *
82
- * Compile +script+ with +filename+ using +linenum+
84
+ * Compile the JavaScript code in +script_string+, marked as originating
85
+ * from +filename+ starting at +linenum+.
83
86
  */
84
87
  static VALUE native_compile(VALUE self, VALUE script, VALUE filename, VALUE linenum)
85
88
  {
@@ -113,20 +116,23 @@ static VALUE native_compile(VALUE self, VALUE script, VALUE filename, VALUE line
113
116
 
114
117
  JSObject * script_object = JS_NewScriptObject(context, compiled_js);
115
118
 
116
- PREPARE_RUBY_JROOTS(context, 1);
117
- JROOT(script_object);
118
- JRETURN_RUBY(make_ruby_land_proxy(runtime, OBJECT_TO_JSVAL(script_object), "JSScriptProxy"));
119
+ return make_ruby_land_proxy(runtime, OBJECT_TO_JSVAL(script_object), LEAKY_ROOT_NAME("JSScriptProxy", RTEST(filename) ? RSTRING(rb_inspect(filename))->ptr : "(?)"));
119
120
  }
120
121
 
121
122
  /*
122
123
  * call-seq:
123
- * evaluate_compiled_script(script)
124
+ * evaluate_compiled_script(script_proxy)
124
125
  *
125
- * Evaluate +script+
126
+ * Evaluate previously compiled +script_proxy+, returning the final
127
+ * result from that script.
126
128
  */
127
129
  static VALUE evaluate_compiled_script(VALUE self, VALUE compiled_script)
128
130
  {
129
131
  JohnsonRuntime* runtime;
132
+
133
+ if (!ruby_value_is_script_proxy(compiled_script))
134
+ rb_raise(rb_eArgError, "Compiled JS Script expected");
135
+
130
136
  Data_Get_Struct(self, JohnsonRuntime, runtime);
131
137
 
132
138
  JSContext * context = johnson_get_current_context(runtime);
@@ -163,12 +169,16 @@ static VALUE evaluate_compiled_script(VALUE self, VALUE compiled_script)
163
169
  return convert_to_ruby(runtime, js);
164
170
  }
165
171
 
172
+ #ifdef JS_GC_ZEAL
166
173
  /*
167
174
  * call-seq:
168
175
  * gc_zeal=(level)
169
176
  *
170
177
  * Sets the GC zeal.
171
- * 0 = normal, 1 = Very Frequent, 2 = Extremely Frequent
178
+ *
179
+ * 0:: Normal
180
+ * 1:: Very Frequent
181
+ * 2:: Extremely Frequent
172
182
  */
173
183
  static VALUE
174
184
  set_gc_zeal(VALUE self, VALUE zeal)
@@ -182,12 +192,33 @@ set_gc_zeal(VALUE self, VALUE zeal)
182
192
 
183
193
  return zeal;
184
194
  }
195
+ #endif
196
+
197
+ /*
198
+ * call-seq:
199
+ * gc()
200
+ *
201
+ * Manually initiates a SpiderMonkey Garbage Collection run.
202
+ */
203
+ static VALUE
204
+ gc(VALUE self)
205
+ {
206
+ JohnsonRuntime* runtime;
207
+ Data_Get_Struct(self, JohnsonRuntime, runtime);
208
+
209
+ JSContext* context = johnson_get_current_context(runtime);
210
+
211
+ JS_GC(context);
212
+
213
+ return Qnil;
214
+ }
185
215
 
186
216
  /*
187
217
  * call-seq:
188
218
  * debugger=(debugger)
189
219
  *
190
- * Sets a debugger object
220
+ * Directs the runtime to install a full set of debug hooks, using the
221
+ * given +debugger+, which must be a Johnson::SpiderMonkey::Debugger.
191
222
  */
192
223
  static VALUE
193
224
  set_debugger(VALUE self, VALUE debugger)
@@ -195,6 +226,9 @@ set_debugger(VALUE self, VALUE debugger)
195
226
  JohnsonRuntime* runtime;
196
227
  JSDebugHooks* debug_hooks;
197
228
 
229
+ if (!ruby_value_is_debugger(debugger))
230
+ rb_raise(rb_eTypeError, "Expected Johnson::SpiderMonkey::Debugger instance");
231
+
198
232
  rb_iv_set(self, "@debugger", debugger);
199
233
  Data_Get_Struct(self, JohnsonRuntime, runtime);
200
234
  Data_Get_Struct(debugger, JSDebugHooks, debug_hooks);
@@ -247,6 +281,13 @@ JSBool gc_callback(JSContext *context, JSGCStatus status)
247
281
  return JS_FALSE;
248
282
  }
249
283
 
284
+ /**
285
+ * call-seq:
286
+ * initialize_native(options)
287
+ *
288
+ * Create the underlying SpiderMonkey runtime. This must be called
289
+ * first, and only once. Called by +initialize+ by default.
290
+ */
250
291
  static VALUE
251
292
  initialize_native(VALUE self, VALUE UNUSED(options))
252
293
  {
@@ -262,7 +303,7 @@ initialize_native(VALUE self, VALUE UNUSED(options))
262
303
 
263
304
  JSContext* context = johnson_get_current_context(runtime);
264
305
 
265
- if (runtime->global = JS_GetGlobalObject(context))
306
+ if ((runtime->global = JS_GetGlobalObject(context)))
266
307
  return self;
267
308
  }
268
309
 
@@ -289,6 +330,16 @@ JSContext* johnson_get_current_context(JohnsonRuntime * runtime)
289
330
  return context->js;
290
331
  }
291
332
 
333
+ static int proxy_cleanup_enumerator(JSHashEntry *entry, int i, void* arg)
334
+ {
335
+ JohnsonRuntime *runtime = (JohnsonRuntime*)(arg);
336
+ // entry->key is jsval; entry->value is RubyLandProxy*
337
+ RubyLandProxy * proxy = (RubyLandProxy *)(entry->value);
338
+ JS_RemoveRootRT(runtime->js, &(proxy->key));
339
+ proxy->runtime = NULL;
340
+ return 0;
341
+ }
342
+
292
343
  static void deallocate(JohnsonRuntime* runtime)
293
344
  {
294
345
  // our gc callback can create ruby objects, so disable it
@@ -303,6 +354,10 @@ static void deallocate(JohnsonRuntime* runtime)
303
354
  iterator = NULL;
304
355
  }
305
356
 
357
+ JSContext* cleanup = JS_NewContext(runtime->js, 8192L);
358
+ JS_HashTableEnumerateEntries(runtime->jsids, proxy_cleanup_enumerator, runtime);
359
+ JS_DestroyContext(cleanup);
360
+
306
361
  JS_DestroyRuntime(runtime->js);
307
362
  free(runtime);
308
363
  }
@@ -315,16 +370,27 @@ static VALUE allocate(VALUE klass)
315
370
 
316
371
  void init_Johnson_SpiderMonkey_Runtime(VALUE spidermonkey)
317
372
  {
318
- VALUE klass = rb_define_class_under(spidermonkey, "Runtime", rb_cObject);
373
+ /* HACK: These comments are *only* to make RDoc happy.
374
+ VALUE johnson = rb_define_module("Johnson");
375
+ VALUE spidermonkey = rb_define_module_under(johnson, "SpiderMonkey");
376
+ */
377
+
378
+ VALUE johnson = rb_const_get(rb_mKernel, rb_intern("Johnson"));
379
+ VALUE johnson_runtime = rb_const_get(johnson, rb_intern("Runtime"));
380
+
381
+ VALUE klass = rb_define_class_under(spidermonkey, "Runtime", johnson_runtime);
319
382
 
320
383
  rb_define_alloc_func(klass, allocate);
321
384
  rb_define_private_method(klass, "initialize_native", initialize_native, 1);
322
385
 
323
386
  rb_define_method(klass, "global", global, 0);
324
387
  rb_define_method(klass, "debugger=", set_debugger, 1);
388
+ rb_define_method(klass, "gc", gc, 0);
389
+ #ifdef JS_GC_ZEAL
325
390
  rb_define_method(klass, "gc_zeal=", set_gc_zeal, 1);
391
+ #endif
326
392
  rb_define_method(klass, "evaluate_compiled_script", evaluate_compiled_script, 1);
327
393
  rb_define_private_method(klass, "native_compile", native_compile, 3);
328
- rb_define_private_method(klass, "set_trap", set_trap, 3);
394
+ rb_define_method(klass, "set_trap", set_trap, 3);
329
395
  rb_define_private_method(klass, "clear_trap", clear_trap, 2);
330
396
  }