#include "ruby_land_proxy.h"
#include "conversions.h"
DECLARE_RUBY_WRAPPER(rb_call_super, int argc; const VALUE* argv)
DEFINE_RUBY_WRAPPER(rb_call_super, rb_call_super, ARGLIST2(argc, argv))
DECLARE_RUBY_WRAPPER(rb_yield, VALUE v)
DEFINE_RUBY_WRAPPER(rb_yield, rb_yield, ARGLIST1(v))
DECLARE_RUBY_WRAPPER(rb_check_type, VALUE o; int t)
DEFINE_VOID_RUBY_WRAPPER(rb_check_type, rb_check_type, ARGLIST2(o, t))
DEFINE_RUBY_WRAPPER(rb_string_value, rb_string_value, ARGLIST1T(volatile VALUE*,v))
DEFINE_VOID_RUBY_WRAPPER(rb_string_value_cstr, rb_string_value_cstr, ARGLIST1T(volatile VALUE*,v))
DEFINE_RUBY_WRAPPER(make_ruby_land_proxy, make_ruby_land_proxy, ARGLIST3(runtime, value, root_name))
static VALUE proxy_class = Qnil;
static VALUE script_class = Qnil;
static inline JSBool get_jsval_for_proxy(RubyLandProxy* proxy, jsval* jv)
{
*jv = (jsval)(proxy->key);
return JS_TRUE;
}
static VALUE call_js_function_value(JohnsonRuntime* runtime, jsval target, jsval function, int argc, VALUE* argv)
{
JSContext * context = johnson_get_current_context(runtime);
PREPARE_RUBY_JROOTS(context, argc + 2);
JROOT(target);
JROOT(function);
assert(JSVAL_IS_OBJECT(target));
jsval args[argc];
jsval result;
int i;
for(i = 0; i < argc; ++i)
{
JCHECK(convert_to_js(runtime, argv[i], &(args[i])));
JROOT(args[i]);
}
JCHECK(JS_CallFunctionValue(context,
JSVAL_TO_OBJECT(target), function, (unsigned) argc, args, &result));
JRETURN_RUBY(CONVERT_TO_RUBY(runtime, result));
}
/*
* call-seq:
* [](name)
*
* Retrieves the current value of the +name+ property of this JavaScript
* object.
*/
static VALUE
get(VALUE self, VALUE name)
{
RubyLandProxy* proxy;
Data_Get_Struct(self, RubyLandProxy, proxy);
JSContext * context = johnson_get_current_context(proxy->runtime);
PREPARE_RUBY_JROOTS(context, 1);
jsval proxy_value;
JCHECK(get_jsval_for_proxy(proxy, &proxy_value));
JROOT(proxy_value);
jsval js_value;
switch(TYPE(name)) {
case T_FIXNUM:
JCHECK(JS_GetElement(context,
JSVAL_TO_OBJECT(proxy_value), (jsint)(NUM2INT(name)), &js_value));
break;
case T_SYMBOL:
name = RB_FUNCALL_0(name, RB_INTERN("to_s"));
default:
CALL_RUBY_WRAPPER(rb_string_value_cstr, (VALUE)&name);
JCHECK(JS_GetProperty(context,
JSVAL_TO_OBJECT(proxy_value), StringValueCStr(name), &js_value));
break;
}
JRETURN_RUBY(CONVERT_TO_RUBY(proxy->runtime, js_value));
}
/*
* call-seq:
* []=(name, value)
*
* Sets this JavaScript object's +name+ property to +value+.
*/
static VALUE
set(VALUE self, VALUE name, VALUE value)
{
RubyLandProxy* proxy;
Data_Get_Struct(self, RubyLandProxy, proxy);
JSContext * context = johnson_get_current_context(proxy->runtime);
PREPARE_RUBY_JROOTS(context, 2);
jsval proxy_value;
JCHECK(get_jsval_for_proxy(proxy, &proxy_value));
JROOT(proxy_value);
jsval js_value;
JCHECK(convert_to_js(proxy->runtime, value, &js_value));
JROOT(js_value);
switch(TYPE(name)) {
case T_FIXNUM:
JCHECK(JS_SetElement(context,
JSVAL_TO_OBJECT(proxy_value), (jsint)(NUM2INT(name)), &js_value));
break;
case T_SYMBOL:
name = RB_FUNCALL_0(name, RB_INTERN("to_s"));
default:
CALL_RUBY_WRAPPER(rb_string_value_cstr, (VALUE)&name);
JCHECK(JS_SetProperty(context,
JSVAL_TO_OBJECT(proxy_value), StringValueCStr(name), &js_value));
break;
}
JRETURN_RUBY(value);
}
/*
* call-seq:
* function?()
*
* Returns true
if this JavaScript object is a function.
*/
static VALUE
function_p(VALUE self)
{
RubyLandProxy* proxy;
Data_Get_Struct(self, RubyLandProxy, proxy);
JSContext * context = johnson_get_current_context(proxy->runtime);
PREPARE_RUBY_JROOTS(context, 1);
jsval proxy_value;
JCHECK(get_jsval_for_proxy(proxy, &proxy_value));
JROOT(proxy_value);
JRETURN_RUBY(JS_TypeOfValue(context, proxy_value) == JSTYPE_FUNCTION ? Qtrue : Qfalse);
}
static VALUE
callable_test_p(VALUE /* self */, VALUE proxy)
{
return function_p(proxy);
}
/*
* call-seq:
* respond_to?(symbol)
*
* Returns true
if this JavaScript object responds to the
* named method.
*/
static VALUE
respond_to_p(int argc, const VALUE* argv, VALUE self)
{
VALUE sym, priv;
rb_scan_args(argc, argv, "11", &sym, &priv);
RubyLandProxy* proxy;
Data_Get_Struct(self, RubyLandProxy, proxy);
JSContext * context = johnson_get_current_context(proxy->runtime);
PREPARE_RUBY_JROOTS(context, 2);
VALUE stringval = rb_funcall(sym, rb_intern("to_s"), 0);
char* name = StringValuePtr(stringval);
// assignment is always okay
if (name[strlen(name) - 1] == '=')
JRETURN_RUBY(Qtrue);
jsval proxy_value;
JCHECK(get_jsval_for_proxy(proxy, &proxy_value));
JROOT(proxy_value);
JSObject *obj;
JSBool found;
JCHECK(JS_ValueToObject(context, proxy_value, &obj));
JROOT(obj);
JCHECK(JS_HasProperty(context, obj, name, &found));
JRETURN_RUBY(found ? Qtrue : CALL_RUBY_WRAPPER(rb_call_super, argc, argv));
}
/*
* call-seq:
* native_call(this, *args)
*
* Call this Ruby Object as a function, with the given +this+ object and
* arguments. Equivalent to the call method in JavaScript.
*/
static VALUE
native_call(int argc, VALUE* argv, VALUE self)
{
if (!function_p(self))
rb_raise(rb_eRuntimeError,
"This Johnson::TraceMonkey::RubyLandProxy isn't a function.");
if (argc < 1)
rb_raise(rb_eArgError, "Target object required");
RubyLandProxy* proxy;
Data_Get_Struct(self, RubyLandProxy, proxy);
jsval proxy_value;
if (!get_jsval_for_proxy(proxy, &proxy_value))
raise_js_error_in_ruby(proxy->runtime);
jsval global;
if (!convert_to_js(proxy->runtime, argv[0], &global))
raise_js_error_in_ruby(proxy->runtime);
return call_js_function_value(proxy->runtime, global, proxy_value,
argc - 1, &(argv[1]));
}
static void
destroy_id_array(JSContext* context, void* data)
{
JS_DestroyIdArray(context, (JSIdArray*)data);
}
/*
* call-seq:
* each {| element | block }
* each {| name, value | block }
*
* Calls block with each item in this JavaScript array, or with
* each +name+/+value+ pair (like a Hash) for any other JavaScript
* object.
*/
static VALUE
each(VALUE self)
{
RubyLandProxy* proxy;
Data_Get_Struct(self, RubyLandProxy, proxy);
JSContext * context = johnson_get_current_context(proxy->runtime);
PREPARE_RUBY_JROOTS(context, 5);
jsval proxy_value;
JCHECK(get_jsval_for_proxy(proxy, &proxy_value));
JROOT(proxy_value);
JSObject* value = JSVAL_TO_OBJECT(proxy_value);
JROOT(value);
// arrays behave like you'd expect, indexes in order
if (JS_IsArrayObject(context, value))
{
jsuint length;
JCHECK(JS_GetArrayLength(context, value, &length));
jsuint i = 0;
for (i = 0; i < length; ++i)
{
jsval element;
JCHECK(JS_GetElement(context, value, (signed) i, &element));
CALL_RUBY_WRAPPER(rb_yield, CONVERT_TO_RUBY(proxy->runtime, element));
}
}
else
{
// not an array? behave like each on Hash; yield [key, value]
JSIdArray* ids = JS_Enumerate(context, value);
JCHECK(ids);
JCLEANUP(destroy_id_array, ids);
int i;
for (i = 0; i < ids->length; ++i)
{
jsval js_key, js_value;
JCHECK(JS_IdToValue(context, ids->vector[i], &js_key));
JROOT(js_key);
if (JSVAL_IS_STRING(js_key))
{
// regular properties have string keys
JCHECK(JS_GetProperty(context, value,
JS_GetStringBytes(JSVAL_TO_STRING(js_key)), &js_value));
}
else
{
// it's a numeric property, use array access
JCHECK(JS_GetElement(context, value,
JSVAL_TO_INT(js_key), &js_value));
}
JROOT(js_value);
VALUE key = CONVERT_TO_RUBY(proxy->runtime, js_key);
VALUE value = CONVERT_TO_RUBY(proxy->runtime, js_value);
CALL_RUBY_WRAPPER(rb_yield, rb_ary_new3(2L, key, value));
JUNROOT(js_value);
JUNROOT(js_key);
}
}
JRETURN_RUBY(self);
}
/*
* call-seq:
* length()
*
* Returns the number of entries in the JavaScript array, or the number
* of properties on the JavaScript object.
*/
static VALUE
length(VALUE self)
{
RubyLandProxy* proxy;
Data_Get_Struct(self, RubyLandProxy, proxy);
JSContext * context = johnson_get_current_context(proxy->runtime);
PREPARE_RUBY_JROOTS(context, 2);
jsval proxy_value;
JCHECK(get_jsval_for_proxy(proxy, &proxy_value));
JROOT(proxy_value);
JSObject* value = JSVAL_TO_OBJECT(proxy_value);
JROOT(value);
if (JS_IsArrayObject(context, value))
{
jsuint length;
JCHECK(JS_GetArrayLength(context, value, &length));
JRETURN_RUBY(INT2FIX(length));
}
else
{
JSIdArray* ids = JS_Enumerate(context, value);
JCHECK(ids);
VALUE length = INT2FIX(ids->length);
JS_DestroyIdArray(context, ids);
JRETURN_RUBY(length);
}
}
/*
* call-seq:
* runtime()
*
* Returns the Johnson::TraceMonkey::Runtime against which this object
* is registered.
*/
static VALUE
runtime(VALUE self)
{
RubyLandProxy* proxy;
Data_Get_Struct(self, RubyLandProxy, proxy);
return (VALUE)JS_GetRuntimePrivate(proxy->runtime->js);
}
/*
* call-seq:
* function_property?(name)
*
* Returns true
if this JavaScript object's +name+ property
* is a function.
*/
static VALUE
function_property_p(VALUE self, VALUE name)
{
if (TYPE(name) == T_SYMBOL)
name = rb_funcall(name, rb_intern("to_s"), 0);
rb_string_value_cstr(&name);
RubyLandProxy* proxy;
Data_Get_Struct(self, RubyLandProxy, proxy);
JSContext * context = johnson_get_current_context(proxy->runtime);
PREPARE_RUBY_JROOTS(context, 2);
jsval proxy_value;
JCHECK(get_jsval_for_proxy(proxy, &proxy_value));
JROOT(proxy_value);
jsval js_value;
JCHECK(JS_GetProperty(context,
JSVAL_TO_OBJECT(proxy_value), StringValueCStr(name), &js_value));
JROOT(js_value);
JSType type = JS_TypeOfValue(context, js_value);
JRETURN_RUBY(type == JSTYPE_FUNCTION ? Qtrue : Qfalse);
}
/*
* call-seq:
* call_function_property(name, arguments)
*
* Calls this JavaScript object's +name+ method, passing the given
* arguments.
*
* Equivalent to:
* proxy[name].native_call(proxy, *arguments)
*/
static VALUE
call_function_property(int argc, VALUE* argv, VALUE self)
{
RubyLandProxy* proxy;
Data_Get_Struct(self, RubyLandProxy, proxy);
JSContext * context = johnson_get_current_context(proxy->runtime);
if (argc < 1)
rb_raise(rb_eArgError, "Function name required");
PREPARE_RUBY_JROOTS(context, 2);
jsval proxy_value;
JCHECK(get_jsval_for_proxy(proxy, &proxy_value));
JROOT(proxy_value);
jsval function;
VALUE name = argv[0];
CALL_RUBY_WRAPPER(rb_string_value_cstr, (VALUE)&name);
JCHECK(JS_GetProperty(context,
JSVAL_TO_OBJECT(proxy_value), StringValueCStr(name), &function));
JROOT(function);
// should never be anything but a function
if (!JS_ObjectIsFunction(context, JSVAL_TO_OBJECT(function)))
JERROR("Specified property \"%s\" isn't a function.", StringValueCStr(name));
REMOVE_JROOTS;
return call_js_function_value(proxy->runtime, proxy_value, function, argc - 1, &(argv[1]));
}
/*
* call-seq:
* to_s()
*
* Converts the JavaScript object to a string, using its toString method
* if available.
*/
static VALUE to_s(VALUE self)
{
RubyLandProxy* proxy;
Data_Get_Struct(self, RubyLandProxy, proxy);
JSContext * context = johnson_get_current_context(proxy->runtime);
PREPARE_RUBY_JROOTS(context, 1);
jsval proxy_value;
JCHECK(get_jsval_for_proxy(proxy, &proxy_value));
JROOT(proxy_value);
JSString* str = JS_ValueToString(context, proxy_value);
JRETURN_RUBY(CONVERT_JS_STRING_TO_RUBY(proxy->runtime, str));
}
///////////////////////////////////////////////////////////////////////////
//// INFRASTRUCTURE BELOW HERE ////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
static void finalize(RubyLandProxy* proxy)
{
// could get finalized after the context has been freed
if (proxy->runtime && proxy->runtime->jsids)
{
// remove this proxy from the OID map
jsval proxy_value;
get_jsval_for_proxy(proxy, &proxy_value);
JS_HashTableRemove(proxy->runtime->jsids, (void *)proxy_value);
}
if (proxy->runtime)
{
// remove our GC handle on the JS value
JS_RemoveRootRT(proxy->runtime->js, &(proxy->key));
}
free(proxy);
}
bool ruby_value_is_proxy(VALUE maybe_proxy)
{
return rb_obj_is_kind_of(maybe_proxy, proxy_class);
}
bool ruby_value_is_script_proxy(VALUE maybe_proxy)
{
return rb_obj_is_kind_of(maybe_proxy, script_class);
}
VALUE apply_wrappers(VALUE proxy)
{
VALUE johnson = rb_const_get(rb_mKernel, rb_intern("Johnson"));
VALUE johnson_proxy = rb_const_get(johnson, rb_intern("RubyLandProxy"));
return rb_funcall(johnson_proxy, rb_intern("apply_wrappers"), 1, proxy);
}
JSBool unwrap_ruby_land_proxy(JohnsonRuntime* runtime, VALUE wrapped, jsval* retval)
{
JSContext * context = johnson_get_current_context(runtime);
assert(ruby_value_is_proxy(wrapped));
PREPARE_JROOTS(context, 0);
RubyLandProxy* proxy;
Data_Get_Struct(wrapped, RubyLandProxy, proxy);
JCHECK(get_jsval_for_proxy(proxy, retval));
JRETURN;
}
VALUE make_ruby_land_proxy(JohnsonRuntime* runtime, jsval value, const char* root_name)
{
RubyLandProxy * our_proxy = (RubyLandProxy *)JS_HashTableLookup(runtime->jsids, (void *)value);
if (our_proxy)
{
// if we already have a proxy, return it
return our_proxy->self;
}
else
{
// otherwise make one and cache it
VALUE proxy = Data_Make_Struct((strncmp(root_name, "JSScriptProxy", strlen("JSScriptProxy")) ? proxy_class : script_class), RubyLandProxy, 0, finalize, our_proxy);
JSContext * context = johnson_get_current_context(runtime);
PREPARE_RUBY_JROOTS(context, 1);
JROOT(value);
VALUE rb_runtime = (VALUE)JS_GetRuntimePrivate(runtime->js);
rb_iv_set(proxy, "@runtime", rb_runtime);
our_proxy->runtime = runtime;
our_proxy->key = (void *)value;
our_proxy->self = proxy;
// root the value for JS GC and lookups
JCHECK(JS_AddNamedRootRT(runtime->js, &(our_proxy->key), root_name));
// put the proxy OID in the id map
JCHECK(JS_HashTableAdd(runtime->jsids, (void *)value, (void *)our_proxy));
VALUE final_proxy = JPROTECT(apply_wrappers, proxy);
our_proxy->self = final_proxy;
JRETURN_RUBY(final_proxy);
}
}
void init_Johnson_TraceMonkey_Proxy(VALUE tracemonkey)
{
/* HACK: These comments are *only* to make RDoc happy.
VALUE johnson = rb_define_module("Johnson");
VALUE tracemonkey = rb_define_module_under(johnson, "TraceMonkey");
*/
VALUE johnson = rb_const_get(rb_mKernel, rb_intern("Johnson"));
VALUE johnson_proxy = rb_const_get(johnson, rb_intern("RubyLandProxy"));
/* RubyLandProxy class. */
proxy_class = rb_define_class_under(tracemonkey, "RubyLandProxy", johnson_proxy);
rb_define_method(proxy_class, "[]", (ruby_callback)get, 1);
rb_define_method(proxy_class, "[]=", (ruby_callback)set, 2);
rb_define_method(proxy_class, "function?", (ruby_callback)function_p, 0);
rb_define_method(proxy_class, "respond_to?", (ruby_callback)respond_to_p, -1);
rb_define_method(proxy_class, "each", (ruby_callback)each, 0);
rb_define_method(proxy_class, "length", (ruby_callback)length, 0);
rb_define_method(proxy_class, "to_s", (ruby_callback)to_s, 0);
rb_define_private_method(proxy_class, "runtime", (ruby_callback)runtime, 0);
rb_define_private_method(proxy_class, "function_property?", (ruby_callback)function_property_p, 1);
rb_define_private_method(proxy_class, "call_function_property", (ruby_callback)call_function_property, -1);
VALUE callable = rb_define_module_under(proxy_class, "Callable");
rb_define_singleton_method(callable, "test?", (ruby_callback)callable_test_p, 1);
rb_define_private_method(callable, "native_call", (ruby_callback)native_call, -1);
rb_funcall(johnson_proxy, rb_intern("insert_wrapper"), 1, callable);
script_class = rb_define_class_under(tracemonkey, "RubyLandScript", proxy_class);
}