Permalink
Browse files

JS-land proxies for Ruby objects. No support for GC or roundtripping.

git-svn-id: svn+ssh://rubyforge.org/var/svn/johnson/trunk@100 54575175-8111-4fdf-a583-07ff49f40e23
  • Loading branch information...
jbarnette
jbarnette committed Mar 31, 2008
1 parent e08d36d commit 69ace8723e565bd340e65a90e0433be69af0882d
@@ -1,4 +1,5 @@
#include "conversions.h"
+#include "js_proxy.h"
#include "ruby_proxy.h"
static jsval convert_float_or_bignum_to_js(OurContext* context, VALUE float_or_bignum)
@@ -13,7 +14,7 @@ static jsval convert_symbol_to_js(OurContext* context, VALUE symbol)
VALUE to_s = rb_funcall(symbol, rb_intern("to_s"), 0);
jsval name = STRING_TO_JSVAL(JS_NewStringCopyZ(context->js, StringValuePtr(to_s)));
- // calls Ruby.symbolize(name) in JS-land. See prelude.js.
+ // calls Ruby.symbolize(name) in JS-land. See lib/prelude.js
jsval nsRuby;
assert(JS_GetProperty(context->js, context->global, "Ruby", &nsRuby) || JSVAL_VOID == nsRuby);
@@ -24,6 +25,11 @@ static jsval convert_symbol_to_js(OurContext* context, VALUE symbol)
return js;
}
+static jsval convert_object_to_js(OurContext* context, VALUE object)
+{
+ return make_js_proxy(context, object);
+}
+
jsval convert_to_js(OurContext* context, VALUE ruby)
{
switch(TYPE(ruby))
@@ -50,14 +56,17 @@ jsval convert_to_js(OurContext* context, VALUE ruby)
case T_SYMBOL:
return convert_symbol_to_js(context, ruby);
- case T_DATA:
+ case T_CLASS:
+ case T_OBJECT:
+ // FIXME: if it's a wrapped JS object, return it
+ return convert_object_to_js(context, ruby);
+
+ case T_DATA: // keep T_DATA last for fall-through
if (ruby_value_is_proxy(ruby))
return unwrap_ruby_proxy(context, ruby);
// UNIMPLEMENTED BELOW THIS LINE
- case T_OBJECT:
- case T_CLASS:
case T_FILE:
case T_STRUCT:
case T_MODULE:
@@ -77,7 +86,7 @@ static JSBool jsval_is_a_symbol(OurContext* context, jsval maybe_symbol)
jsval nsRuby, cSymbol;
assert(JS_GetProperty(context->js, context->global, "Ruby", &nsRuby));
- if(JSVAL_VOID == nsRuby) Johnson_Error_raise("aaaaaaaugh!");
+ assert(JSVAL_VOID != nsRuby);
assert(JS_GetProperty(context->js, JSVAL_TO_OBJECT(nsRuby), "Symbol", &cSymbol));
assert(JSVAL_VOID != cSymbol);
@@ -103,6 +112,8 @@ VALUE convert_to_ruby(OurContext* context, jsval js)
if (jsval_is_a_symbol(context, js))
return ID2SYM(rb_intern(JS_GetStringBytes(JS_ValueToString(context->js, js))));
+ // FIXME: if it's wrapping a Ruby object, unwrap and return it
+
VALUE id = (VALUE)JS_HashTableLookup(context->ids, (void *)js);
if (id)
View
@@ -0,0 +1,197 @@
+#include "js_proxy.h"
+
+static JSBool get(JSContext* js_context, JSObject* obj, jsval id, jsval* retval);
+static void finalize(JSContext* context, JSObject* obj);
+static JSBool set(JSContext* context, JSObject* obj, jsval id, jsval* retval);
+
+static JSClass JSProxyClass = {
+ "JSProxy", JSCLASS_HAS_PRIVATE,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ get,
+ set,
+ JS_EnumerateStub,
+ JS_ResolveStub,
+ JS_ConvertStub,
+ finalize
+};
+
+static void finalize(JSContext* context, JSObject* obj)
+{
+ // FIXME
+}
+// void Johnson_RubyProxy_finalize(JSContext *js_context, JSObject *obj)
+// {
+// VALUE ruby;
+// VALUE self = (VALUE)JS_GetContextPrivate(js_context);
+//
+// ruby = (VALUE)JS_GetInstancePrivate(js_context, obj, &gRubyProxyClass, NULL);
+// VALUE c_name = rb_funcall(
+// rb_funcall(self, rb_intern("class"), 0),
+// rb_intern("to_s"), 0);
+//
+// if(rb_ivar_defined(self, rb_intern("@converted_objects"))) {
+// rb_hash_delete( rb_iv_get(self, "@converted_objects"),
+// rb_funcall(ruby, rb_intern("object_id"), 0));
+// }
+// }
+//
+
+static JSBool get(JSContext* js_context, JSObject* obj, jsval id, jsval* retval)
+{
+ // pull out our Ruby object, which is embedded in js_context
+
+ VALUE ruby_context;
+ assert(ruby_context = (VALUE)JS_GetContextPrivate(js_context));
+
+ // get our struct, which is embedded in ruby_context
+
+ OurContext* context;
+ Data_Get_Struct(ruby_context, OurContext, context);
+
+ // get the Ruby object that backs this proxy
+
+ VALUE self;
+ assert(self = (VALUE)JS_GetInstancePrivate(context->js, obj, &JSProxyClass, NULL));
+
+ char* key = JS_GetStringBytes(JSVAL_TO_STRING(id));
+ VALUE ruby_id = rb_intern(key);
+
+ VALUE is_method = rb_funcall(self, rb_intern("respond_to?"), 1, ID2SYM(ruby_id));
+
+ if (is_method)
+ {
+ VALUE method = rb_funcall(self, rb_intern("method"), 1, ID2SYM(ruby_id));
+ int arity = NUM2INT(rb_funcall(method, rb_intern("arity"), 0));
+
+ // if the Ruby object has a 0-arity method named the same as the property
+ // we're trying to get, call it and return the converted result
+
+ if (arity == 0)
+ *retval = convert_to_js(context, rb_funcall(self, ruby_id, 0));
+ }
+ else
+ {
+ // otherwise, if the Ruby object quacks sorta like a hash (it responds to
+ // "[]" and "key?"), index it by key and return the converted result
+
+ VALUE is_indexable = rb_funcall(self, rb_intern("respond_to?"), 1, ID2SYM(rb_intern("[]")));
+ VALUE has_key_p = rb_funcall(self, rb_intern("respond_to?"), 1, ID2SYM(rb_intern("key?")));
+
+ if (is_indexable && has_key_p)
+ *retval = convert_to_js(context, rb_funcall(self, rb_intern("[]"), 1, rb_str_new2(key)));
+ }
+
+ return JS_TRUE;
+}
+
+static JSBool set(JSContext* js_context, JSObject* obj, jsval id, jsval* value)
+{
+ VALUE ruby_context;
+ assert(ruby_context = (VALUE)JS_GetContextPrivate(js_context));
+
+ OurContext* context;
+ Data_Get_Struct(ruby_context, OurContext, context);
+
+ VALUE self;
+ assert(self = (VALUE)JS_GetInstancePrivate(context->js, obj, &JSProxyClass, NULL));
+
+ char* key = JS_GetStringBytes(JSVAL_TO_STRING(id));
+ VALUE ruby_key = rb_str_new2(key);
+
+ VALUE setter = rb_str_append(rb_str_new3(ruby_key), rb_str_new2("="));
+ VALUE setter_id = rb_intern(StringValuePtr(setter));
+
+ VALUE has_setter = rb_funcall(self, rb_intern("respond_to?"), 1, ID2SYM(setter_id));
+
+ if (has_setter)
+ {
+ VALUE method = rb_funcall(self, rb_intern("method"), 1, ID2SYM(setter_id));
+ int arity = NUM2INT(rb_funcall(method, rb_intern("arity"), 0));
+
+ // if the Ruby object has a 1-arity method named "property=",
+ // call it with the converted value
+
+ if (arity == 1)
+ rb_funcall(self, setter_id, 1, convert_to_ruby(context, *value));
+ }
+ else
+ {
+ // otherwise, if the Ruby object quacks sorta like a hash for assignment
+ // (it responds to "[]="), assign it by key
+
+ VALUE is_index_assignable =
+ rb_funcall(self, rb_intern("respond_to?"), 1, ID2SYM(rb_intern("[]=")));
+
+ if (is_index_assignable)
+ rb_funcall(self, rb_intern("[]="), 2, ruby_key, convert_to_ruby(context, *value));
+ }
+
+ return JS_TRUE;
+}
+
+static JSBool method_missing(JSContext* js_context, JSObject* obj, uintN argc, jsval* argv, jsval* retval)
+{
+ VALUE ruby_context;
+ assert(ruby_context = (VALUE)JS_GetContextPrivate(js_context));
+
+ OurContext* context;
+ Data_Get_Struct(ruby_context, OurContext, context);
+
+ VALUE self;
+ assert(self = (VALUE)JS_GetInstancePrivate(context->js, obj, &JSProxyClass, NULL));
+
+ char* key = JS_GetStringBytes(JSVAL_TO_STRING(argv[0]));
+
+ VALUE ruby_id = rb_intern(key);
+
+ // FIXME: this could probably be a lot faster, to_a comes from enumerable on proxy
+ VALUE args = rb_funcall(convert_to_ruby(context, argv[1]), rb_intern("to_a"), 0);
+
+ // Context#jsend: if the last arg is a function, it'll get passed along as a &block
+
+ *retval = convert_to_js(context,
+ rb_funcall(ruby_context, rb_intern("jsend"), 3, self, ID2SYM(ruby_id), args));
+
+ return JS_TRUE;
+}
+
+JSBool js_value_is_proxy(jsval maybe_proxy)
+{
+ return JS_FALSE;
+}
+
+VALUE unwrap_js_proxy(OurContext* context, jsval proxy)
+{
+ return Qnil;
+}
+
+// static jsval convert_ruby_object_to_jsval(CombinedContext* context, VALUE ruby)
+// {
+// VALUE self = (VALUE)JS_GetContextPrivate(context->js);
+// JSObject * js = JS_NewObject(context->js, &gRubyProxyClass, NULL, NULL);
+// if(!js) Johnson_Error_raise("failed JS_NewObject");
+//
+// rb_hash_aset( rb_iv_get(self, "@converted_objects"),
+// rb_funcall(ruby, rb_intern("object_id"), 0),
+// ruby );
+//
+// if(JS_SetPrivate(context->js, js, (void*)ruby) == JS_FALSE)
+// Johnson_Error_raise("failed JS_SetPrivate");
+//
+// JS_DefineFunction(context->js, js, "__noSuchMethod__",
+// Johnson_RubyProxy_method_missing, 2, 0);
+//
+// return OBJECT_TO_JSVAL(js);
+// }
+
+jsval make_js_proxy(OurContext* context, VALUE value)
+{
+ JSObject *js;
+
+ assert(js = JS_NewObject(context->js, &JSProxyClass, NULL, NULL));
+ assert(JS_SetPrivate(context->js, js, (void*)value));
+ assert(JS_DefineFunction(context->js, js, "__noSuchMethod__", method_missing, 2, 0));
+
+ return OBJECT_TO_JSVAL(js);
+}
@@ -0,0 +1,11 @@
+#ifndef JOHNSON_SPIDERMONKEY_JS_PROXY_H
+#define JOHNSON_SPIDERMONKEY_JS_PROXY_H
+
+#include "spidermonkey.h"
+#include "context.h"
+
+JSBool js_value_is_proxy(jsval maybe_proxy);
+VALUE unwrap_js_proxy(OurContext* context, jsval proxy);
+jsval make_js_proxy(OurContext* context, VALUE value);
+
+#endif
@@ -268,7 +268,7 @@ jsval unwrap_ruby_proxy(OurContext* context, VALUE wrapped)
void init_Johnson_SpiderMonkey_Proxy(VALUE spidermonkey)
{
- proxy_class = rb_define_class_under(spidermonkey, "Proxy", rb_cObject);
+ proxy_class = rb_define_class_under(spidermonkey, "RubyProxy", rb_cObject);
rb_define_method(proxy_class, "[]", get, 1);
rb_define_method(proxy_class, "[]=", set, 2);
View
@@ -12,7 +12,7 @@
# the SpiderMonkey bits written in Ruby
require "johnson/spidermonkey/context"
-require "johnson/spidermonkey/proxy"
+require "johnson/spidermonkey/ruby_proxy"
require "johnson/spidermonkey/mutable_tree_visitor"
require "johnson/spidermonkey/immutable_node"
@@ -1,6 +1,18 @@
module Johnson #:nodoc:
module SpiderMonkey #:nodoc:
class Context # native
+ def initialize
+ @gcthings = {}
+ end
+
+ def jsend(target, symbol, args)
+ if args.last && args.last.is_a?(RubyProxy) && args.last.function?
+ block = args.pop
+ target.__send__(symbol, *args, &block)
+ else
+ target.__send__(symbol, *args)
+ end
+ end
end
end
end
@@ -1,6 +1,6 @@
module Johnson #:nodoc:
module SpiderMonkey #:nodoc:
- class Proxy # native
+ class RubyProxy # native
include Enumerable
def initialize
View
@@ -13,7 +13,7 @@
module Johnson
class TestCase < Test::Unit::TestCase
- def default_test; end
+ undef :default_test
def assert_js(expression, options={})
context = options[:context] || @context
@@ -30,6 +30,7 @@ class NodeTestCase < Test::Unit::TestCase
include Johnson::Nodes
undef :default_test
+
def setup
@parser = Johnson::Parser
end
@@ -5,13 +5,34 @@ module SpiderMonkey
class ContextTest < Johnson::TestCase
def setup
@context = Johnson::SpiderMonkey::Context.new
+ @context.evaluate(Johnson::PRELUDE)
end
def test_provides_basic_context_interface
assert(@context.respond_to?(:evaluate))
assert(@context.respond_to?(:[]))
assert(@context.respond_to?(:[]=))
end
+
+ class Foo
+ def gets_an_unspecified_block
+ block_given?
+ end
+
+ def runs_block(arg, &block)
+ yield(arg)
+ end
+ end
+
+ def test_jsend_deals_with_blocks
+ func = @context.evaluate("function() {}")
+ assert(@context.jsend(Foo.new, :gets_an_unspecified_block, [func]))
+ end
+
+ def test_jsend_deals_with_specified_blocks
+ func = @context.evaluate("function(x) { return x * 2 }")
+ assert_equal(4, @context.jsend(Foo.new, :runs_block, [2, func]))
+ end
end
end
end
Oops, something went wrong.

0 comments on commit 69ace87

Please sign in to comment.