struct plrb_attr { VALUE recv; ID getter; ID setter; }; static int plrb_mg_attr_set(pTHX_ SV* sv, MAGIC* mg) { struct plrb_attr* attr = (struct plrb_attr*)mg->mg_ptr; VALUE val; if(!attr->setter){ /* first call */ const char* getter = rb_id2name(attr->getter); STRLEN len = strlen(getter)+1; char smallbuf[128]; char* buf; volatile VALUE strbuf; if(len < sizeof(smallbuf)){ buf = smallbuf; } else{ strbuf = rb_str_buf_new((long)len); buf = RSTRING_PTR(strbuf); } /* "attr" + "=" + "\0" */ memcpy(buf, getter, len-1); buf[len-1] = '='; buf[len] = '\0'; attr->setter = rb_intern(buf); } val = SV2VALUE(sv); do_funcall_protect(aTHX_ attr->recv, attr->setter, 1, &val, FALSE); return 0; } MGVTBL plrb_attr_vtbl = { NULL, /* mg_get */ plrb_mg_attr_set, NULL, /* mg_len */ NULL, /* mg_clear */ NULL, /* mg_free */ NULL, /* mg_copy */ NULL, /* mg_dup */ NULL /* mg_local */ }; XS(XS_Ruby_method_dispatcher); XS(XS_Ruby_method_dispatcher) { dXSARGS; VALUE self; VALUE result; ID method = (ID)XSANY.any_iv; if(items == 0) croak("Not enough arguments for %s", rb_id2name(method)); self = ruby_self(ST(0)); result = plrb_funcall_protect(self, method, (items - 1), &ST(1)); if(GIMME_V != G_VOID){ VALUE ret_tab; ID retval_id = method; VALUE v; SV* sv; struct plrb_attr attr = { self, method, (ID)0 }; /* obj[ret_tab][result] = result */ if(OBJ_FROZEN(self) || items != 1){ sv = VALUE2SV(result); } else{ ret_tab = rb_ivar_get_defaultf(self, id_ret_tab, obj_new); if(NIL_P(v = rb_attr_get(ret_tab, retval_id))){ sv = newSVvalue(result); rb_ivar_set(ret_tab, retval_id, v = any_new_noinc(sv)); sv_magicext(sv, NULL, PERL_MAGIC_ext, &plrb_attr_vtbl, (char*)&attr, sizeof(attr)); } else{ sv = valueSV(v); sv_set_value2sv(sv, result); } } ST(0) = sv; XSRETURN(1); } else{ XSRETURN_EMPTY; } }