static JSBool method_missing(JSContext* js_context, JSObject* obj, uintN argc, jsval* argv, jsval* retval) { VALUE ruby_context = (VALUE)JS_GetContextPrivate(js_context); JohnsonContext* context; JohnsonRuntime* runtime; Data_Get_Struct(ruby_context, JohnsonContext, context); VALUE ruby_runtime = (VALUE)JS_GetRuntimePrivate(JS_GetRuntime(js_context)); Data_Get_Struct(ruby_runtime, JohnsonRuntime, runtime); PREPARE_JROOTS(js_context, 0); VALUE self = (VALUE)JS_GetInstancePrivate(context->js, obj, JS_GET_CLASS(context->js, obj), NULL); assert(argc >= 2); char* key = JS_GetStringBytes(JSVAL_TO_STRING(argv[0])); VALUE ruby_id = rb_intern(key); // FIXME: this is horrible and lazy, to_a comes from enumerable on proxy (argv[1] is a JSArray) VALUE args; JCHECK(call_ruby_from_js2(runtime, &args, CONVERT_TO_RUBY(runtime, argv[1]), rb_intern("to_a"), 0)); JCHECK(call_ruby_from_js(runtime, retval, Johnson_SpiderMonkey_JSLandProxy(), rb_intern("send_with_possible_block"), 3, self, ID2SYM(ruby_id), args)); JRETURN; }
/* * 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, &name); JCHECK(JS_SetProperty(context, JSVAL_TO_OBJECT(proxy_value), StringValueCStr(name), &js_value)); break; } JRETURN_RUBY(value); }
/* * call-seq: * function_property?(name) * * Returns <code>true</code> 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, &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, 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: * 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); } }
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: * respond_to?(symbol) * * Returns <code>true</code> 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)); }
// called for lazily resolved properties, which should go away static JSBool get_and_destroy_resolved_property( JSContext* js_context, JSObject* obj, jsval id, jsval* retval) { PREPARE_JROOTS(js_context, 1); JROOT(id); char* name = JS_GetStringBytes(JSVAL_TO_STRING(id)); JCHECK(JS_DeleteProperty(js_context, obj, name)); JCHECK(get(js_context, obj, id, retval)); JRETURN; }
static JSBool call(JSContext* js_context, JSObject* UNUSED(obj), uintN argc, jsval* argv, jsval* retval) { VALUE ruby_context = (VALUE)JS_GetContextPrivate(js_context); JohnsonContext* context; JohnsonRuntime* runtime; Data_Get_Struct(ruby_context, JohnsonContext, context); VALUE ruby_runtime = (VALUE)JS_GetRuntimePrivate(JS_GetRuntime(js_context)); Data_Get_Struct(ruby_runtime, JohnsonRuntime, runtime); PREPARE_JROOTS(js_context, 0); VALUE self = (VALUE)JS_GetInstancePrivate(context->js, JSVAL_TO_OBJECT(JS_ARGV_CALLEE(argv)), &JSLandCallableProxyClass, NULL); VALUE args = rb_ary_new(); uintN i; for (i = 0; i < argc; ++i) rb_ary_push(args, CONVERT_TO_RUBY(runtime, argv[i])); JCHECK(call_ruby_from_js(runtime, retval, Johnson_SpiderMonkey_JSLandProxy(), rb_intern("send_with_possible_block"), 3, self, ID2SYM(rb_intern("call")), args)); JRETURN; }
VALUE convert_js_string_to_ruby(JohnsonRuntime* runtime, JSString* str) { JSContext * context = johnson_get_current_context(runtime); PREPARE_RUBY_JROOTS(context, 1); JROOT(str); char* bytes = JS_GetStringBytes(str); JCHECK(bytes); JRETURN_RUBY(rb_str_new(bytes, (signed long)JS_GetStringLength(str))); }
static JSBool convert_symbol_to_js(JohnsonRuntime* runtime, VALUE symbol, jsval* retval) { JSContext * context = johnson_get_current_context(runtime); PREPARE_JROOTS(context, 2); VALUE to_s = CALL_RUBY_WRAPPER(rb_funcall_0, symbol, rb_intern("to_s"), 0); jsval name = STRING_TO_JSVAL(JS_NewStringCopyN(context, StringValuePtr(to_s), (size_t) StringValueLen(to_s))); JROOT(name); // calls Johnson.symbolize(name) in JS-land. See lib/prelude.js jsval nsJohnson; JCHECK(JS_GetProperty(context, runtime->global, "Johnson", &nsJohnson)); JROOT(nsJohnson); JCHECK(JS_CallFunctionName(context, JSVAL_TO_OBJECT(nsJohnson), "symbolize", 1, &name, retval)); JRETURN; }
/* * call-seq: * function?() * * Returns <code>true</code> 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); }
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 const* root_name) { RubyLandProxy * our_proxy = (RubyLandProxy *)JS_HashTableLookup(runtime->jsids, (void *)value); if (our_proxy) { // if we already have a proxy, return it return apply_conversions(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(JPROTECT(apply_conversions, final_proxy)); } }
static bool js_value_is_symbol(JohnsonRuntime* runtime, jsval maybe_symbol) { jsval nsJohnson, cSymbol; JSContext * context = johnson_get_current_context(runtime); PREPARE_RUBY_JROOTS(context, 3); JROOT(maybe_symbol); JCHECK(JS_GetProperty(context, runtime->global, "Johnson", &nsJohnson)); if (!JSVAL_IS_OBJECT(nsJohnson)) JERROR("Unable to retrieve Johnson from JSLand"); JROOT(nsJohnson); JCHECK(JS_GetProperty(context, JSVAL_TO_OBJECT(nsJohnson), "Symbol", &cSymbol)); if (!JSVAL_IS_OBJECT(cSymbol)) JERROR("Unable to retrieve Johnson.Symbol from JSLand"); JROOT(cSymbol); JSBool is_a_symbol; JCHECK(JS_HasInstance(context, JSVAL_TO_OBJECT(cSymbol), maybe_symbol, &is_a_symbol)); JRETURN_RUBY(is_a_symbol != JS_FALSE); }
/* * 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)); }
static JSBool convert_regexp_to_js(JohnsonRuntime* runtime, VALUE regexp, jsval* retval) { JSContext * context = johnson_get_current_context(runtime); PREPARE_JROOTS(context, 0); VALUE source = rb_funcall(regexp, rb_intern("source"), 0); jsint options = (jsint)(NUM2INT(rb_funcall(regexp, rb_intern("options"), 0))); JSObject* obj = JS_NewRegExpObject(context, StringValuePtr(source), (size_t) StringValueLen(source), (unsigned) options); JCHECK(obj); *retval = OBJECT_TO_JSVAL(obj); JRETURN; }
static JSBool to_array(JSContext* js_context, JSObject* obj, uintN UNUSED(argc), jsval* UNUSED(argv), jsval* retval) { VALUE ruby_context = (VALUE)JS_GetContextPrivate(js_context); JohnsonContext* context; JohnsonRuntime* runtime; Data_Get_Struct(ruby_context, JohnsonContext, context); VALUE ruby_runtime = (VALUE)JS_GetRuntimePrivate(JS_GetRuntime(js_context)); Data_Get_Struct(ruby_runtime, JohnsonRuntime, runtime); PREPARE_JROOTS(js_context, 0); VALUE self = (VALUE)JS_GetInstancePrivate(context->js, obj, JS_GET_CLASS(context->js, obj), NULL); JCHECK(call_ruby_from_js(runtime, retval, self, rb_intern("to_a"), 0)); JRETURN; }
static JSBool resolve(JSContext *js_context, JSObject *obj, jsval id, uintN UNUSED(flags), JSObject **objp) { VALUE ruby_context = (VALUE)JS_GetContextPrivate(js_context); JohnsonContext* context; Data_Get_Struct(ruby_context, JohnsonContext, context); PREPARE_JROOTS(js_context, 1); JROOT(id); char* name = JS_GetStringBytes(JS_ValueToString(js_context, id)); if (respond_to_p(js_context, obj, name)) { JCHECK(JS_DefineProperty(js_context, obj, name, JSVAL_VOID, get_and_destroy_resolved_property, set, JSPROP_ENUMERATE)); *objp = obj; } JRETURN; }
JSBool convert_to_js(JohnsonRuntime* runtime, VALUE ruby, jsval* retval) { JSContext * context = johnson_get_current_context(runtime); PREPARE_JROOTS(context, 0); switch(TYPE(ruby)) { case T_NONE: JERROR("I don't know how to handle T_NONE."); JRETURN; case T_ICLASS: JERROR("I don't know how to handle T_ICLASS."); JRETURN; case T_MATCH: JERROR("I don't know how to handle T_MATCH."); JRETURN; case T_BLKTAG: JERROR("I don't know how to handle T_BLKTAG."); JRETURN; case T_NODE: JERROR("I don't know how to handle T_NODE | T_MASK."); JRETURN; case T_UNDEF: JERROR("I don't know how to handle T_UNDEF."); JRETURN; case T_VARMAP: JERROR("I don't know how to handle T_VARMAP."); JRETURN; case T_NIL: *retval = JSVAL_NULL; JRETURN; case T_TRUE: *retval = JSVAL_TRUE; JRETURN; case T_FALSE: *retval = JSVAL_FALSE; JRETURN; case T_STRING: { JSString* str = JS_NewStringCopyN(context, StringValuePtr(ruby), (size_t) StringValueLen(ruby)); JCHECK(str); *retval = STRING_TO_JSVAL(str); JRETURN; } case T_FIXNUM: { long val = NUM2LONG(ruby); if (val >= JSVAL_INT_MIN && val <= JSVAL_INT_MAX) { *retval = INT_TO_JSVAL((jsint)val); JRETURN; } } case T_FLOAT: case T_BIGNUM: JCHECK(convert_float_or_bignum_to_js(runtime, ruby, retval)); JRETURN; case T_SYMBOL: JCHECK(convert_symbol_to_js(runtime, ruby, retval)); JRETURN; case T_CLASS: case T_ARRAY: case T_HASH: case T_MODULE: case T_FILE: case T_STRUCT: case T_OBJECT: JCHECK(make_js_land_proxy(runtime, ruby, retval)); JRETURN; case T_REGEXP: JCHECK(convert_regexp_to_js(runtime, ruby, retval)); JRETURN; case T_DATA: // HEY! keep T_DATA last for fall-through if (ruby_value_is_proxy(ruby)) JCHECK(unwrap_ruby_land_proxy(runtime, ruby, retval)); else // If we can't identify the object, just wrap it JCHECK(make_js_land_proxy(runtime, ruby, retval)); JRETURN; default: JERROR("unknown ruby type in switch"); } *retval = JSVAL_NULL; JRETURN; }
/* * call-seq: * each {| element | block } * each {| name, value | block } * * Calls <em>block</em> 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); }
JSBool make_js_land_proxy(JohnsonRuntime* runtime, VALUE value, jsval* retval) { jsval base_value = (jsval)JS_HashTableLookup(runtime->rbids, (void *)value); JSContext * context = johnson_get_current_context(runtime); PREPARE_JROOTS(context, 2); jsval johnson = JSVAL_NULL; JCHECK(evaluate_js_property_expression(runtime, "Johnson", &johnson)); JROOT(johnson); if (base_value) { JCHECK(JS_CallFunctionName(context, johnson, "applyConversions", 1, &base_value, retval)); JRETURN; } else { JSObject *jsobj; JSClass *klass = &JSLandProxyClass; if (T_CLASS == TYPE(value)) klass = &JSLandClassProxyClass; // FIXME: hack; should happen in Rubyland if (T_STRUCT == TYPE(value)) rb_funcall(Johnson_SpiderMonkey_JSLandProxy(), rb_intern("treat_all_properties_as_methods"), 1, value); bool callable_p = Qtrue == rb_funcall(value, rb_intern("respond_to?"), 1, rb_str_new2("call")); if (callable_p) klass = &JSLandCallableProxyClass; JCHECK((jsobj = JS_NewObject(context, klass, NULL, NULL))); JROOT(jsobj); JCHECK(JS_SetPrivate(context, jsobj, (void*)value)); JCHECK(JS_DefineFunction(context, jsobj, "__noSuchMethod__", method_missing, 2, 0)); JCHECK(JS_DefineFunction(context, jsobj, "toArray", to_array, 0, 0)); JCHECK(JS_DefineFunction(context, jsobj, "toString", to_string, 0, 0)); base_value = OBJECT_TO_JSVAL(jsobj); // root the ruby value for GC VALUE ruby_runtime = (VALUE)JS_GetRuntimePrivate(runtime->js); rb_funcall(ruby_runtime, rb_intern("add_gcthing"), 1, value); jsval wrapped_value = JSVAL_NULL; JCHECK(JS_CallFunctionName(context, johnson, "applyWrappers", 1, &base_value, &wrapped_value)); // put the proxy OID in the id map JCHECK(JS_HashTableAdd(runtime->rbids, (void *)value, (void *)(wrapped_value))); JCHECK(JS_CallFunctionName(context, johnson, "applyConversions", 1, &wrapped_value, retval)); JRETURN; } }
static JSBool set(JSContext* js_context, JSObject* obj, jsval id, jsval* value) { VALUE ruby_context = (VALUE)JS_GetContextPrivate(js_context); JohnsonContext* context; JohnsonRuntime* runtime; Data_Get_Struct(ruby_context, JohnsonContext, context); VALUE ruby_runtime = (VALUE)JS_GetRuntimePrivate(JS_GetRuntime(js_context)); Data_Get_Struct(ruby_runtime, JohnsonRuntime, runtime); PREPARE_JROOTS(js_context, 2); JROOT(id); JROOT_PTR(value); VALUE self = (VALUE)JS_GetInstancePrivate(context->js, obj, JS_GET_CLASS(context->js, obj), NULL); // Short-circuit for numeric indexes if (JSVAL_IS_INT(id)) { if (indexable_p(self)) { VALUE idx = INT2FIX(JSVAL_TO_INT(id)); VALUE val = CONVERT_TO_RUBY(runtime, *value); JCHECK(call_ruby_from_js(runtime, NULL, self, rb_intern("[]="), 2, idx, val)); } JRETURN; } VALUE ruby_key = CONVERT_TO_RUBY(runtime, id); VALUE ruby_value = CONVERT_TO_RUBY(runtime, *value); VALUE setter = rb_str_append(rb_str_new3(ruby_key), rb_str_new2("=")); VALUE setter_id = rb_intern(StringValueCStr(setter)); VALUE settable_p, indexable_p; JCHECK(call_ruby_from_js2(runtime, &settable_p, self, rb_intern("respond_to?"), 1, ID2SYM(setter_id))); JCHECK(call_ruby_from_js2(runtime, &indexable_p, self, rb_intern("respond_to?"), 1, ID2SYM(rb_intern("[]=")))); if (settable_p) { VALUE method, arity; JCHECK(call_ruby_from_js2(runtime, &method, self, rb_intern("method"), 1, ID2SYM(setter_id))); JCHECK(call_ruby_from_js2(runtime, &arity, method, rb_intern("arity"), 0)); // if the Ruby object has a 1-arity method named "property=", // call it with the converted value if (NUM2INT(arity) == 1) JCHECK(call_ruby_from_js(runtime, NULL, self, setter_id, 1, ruby_value)); } else if(indexable_p) { // otherwise, if the Ruby object quacks sorta like a hash for assignment // (it responds to "[]="), assign it by key JCHECK(call_ruby_from_js(runtime, NULL, self, rb_intern("[]="), 2, ruby_key, ruby_value)); } else { JCHECK(call_ruby_from_js(runtime, NULL, Johnson_SpiderMonkey_JSLandProxy(), rb_intern("autovivify"), 3, self, ruby_key, ruby_value)); } JRETURN; }
static JSBool get(JSContext* js_context, JSObject* obj, jsval id, jsval* retval) { // pull out our Ruby context, which is embedded in js_context VALUE ruby_context = (VALUE)JS_GetContextPrivate(js_context); // get our struct, which is embedded in ruby_context JohnsonContext* context; JohnsonRuntime* runtime; Data_Get_Struct(ruby_context, JohnsonContext, context); VALUE ruby_runtime = (VALUE)JS_GetRuntimePrivate(JS_GetRuntime(js_context)); Data_Get_Struct(ruby_runtime, JohnsonRuntime, runtime); PREPARE_JROOTS(js_context, 1); JROOT(id); // get the Ruby object that backs this proxy VALUE self = (VALUE)JS_GetInstancePrivate(context->js, obj, JS_GET_CLASS(context->js, obj), NULL); // Short-circuit for numeric indexes if (JSVAL_IS_INT(id)) { if (indexable_p(self)) { VALUE idx = INT2FIX(JSVAL_TO_INT(id)); JCHECK(call_ruby_from_js(runtime, retval, self, rb_intern("[]"), 1, idx)); } JRETURN; } char* name = JS_GetStringBytes(JSVAL_TO_STRING(id)); VALUE ruby_id = rb_intern(name); // FIXME: we should probably just JS_DefineProperty this, and it shouldn't be enumerable if (!strcasecmp("__iterator__", name)) { JCHECK(evaluate_js_property_expression(runtime, "Johnson.Generator.create", retval)); } // if the Ruby object has a dynamic js property with a key // matching the property we're looking for, pull the value out of // that map. else if (autovivified_p(ruby_context, self, name)) { JCHECK(call_ruby_from_js(runtime, retval, Johnson_SpiderMonkey_JSLandProxy(), rb_intern("autovivified"), 2, self, rb_str_new2(name))); } // if the Ruby object is a Module or Class and has a matching // const defined, return the converted result of const_get else if (const_p(self, name)) { JCHECK(call_ruby_from_js(runtime, retval, self, rb_intern("const_get"), 1, ID2SYM(ruby_id))); } // otherwise, if it's a global, return the global else if (global_p(name)) { JCHECK(convert_to_js(runtime, rb_gv_get(name), retval)); } // otherwise, if the Ruby object has a an attribute method matching // the property we're trying to get, call it and return the converted result else if (attribute_p(self, name)) { JCHECK(call_ruby_from_js(runtime, retval, self, ruby_id, 0)); } // otherwise, if the Ruby object quacks sorta like a hash (it responds to // "[]" and "key?"), index it by key and return the converted result else if (has_key_p(self, name)) { JCHECK(call_ruby_from_js(runtime, retval, self, rb_intern("[]"), 1, rb_str_new2(name))); } // otherwise, it's a method being accessed as a property, which means // we need to return a lambda // FIXME: this should really wrap the Method for 'name' in a JS class // rather than generating a wrapper Proc else if (method_p(self, name)) { JCHECK(call_ruby_from_js(runtime, retval, self, rb_intern("method"), 1, rb_str_new2(name))); } // else it's undefined (JS_VOID) by default JRETURN; }