Navigation Menu

Skip to content

Commit

Permalink
Metakoans.
Browse files Browse the repository at this point in the history
  • Loading branch information
mattwildig committed Mar 7, 2012
1 parent 2a2d36e commit 60fc76c
Show file tree
Hide file tree
Showing 5 changed files with 450 additions and 0 deletions.
3 changes: 3 additions & 0 deletions 67_metakoans/.gitignore
@@ -0,0 +1,3 @@
*.o
*.bundle
Makefile
6 changes: 6 additions & 0 deletions 67_metakoans/extconf.rb
@@ -0,0 +1,6 @@
require 'mkmf'

$warnflags.gsub! /-Wdeclaration-after-statement/, '' if $warnflags
$CFLAGS << " -std=c99"

create_makefile 'knowledge'
122 changes: 122 additions & 0 deletions 67_metakoans/knowledge.c
@@ -0,0 +1,122 @@
#include "ruby.h"

static ID meth_name_id;
static ID to_s_id;

static VALUE evalulator;
static ID evalualte_id;

static VALUE current_method(){
VALUE sym = rb_funcall(rb_cObject, meth_name_id, 0);
return rb_funcall(sym, to_s_id, 0);
}

static char* ivar_name() {
VALUE name = current_method();
char last = RSTRING_PTR(name)[RSTRING_LEN(name)-1];
char* ivar;
int strip = 0;
size_t len;

if(last == '=' || last == '?'){
strip = 1;
}

len = RSTRING_LEN(name) + 2 - strip;
ivar = malloc(len);
memcpy(ivar + 1, RSTRING_PTR(name), len - 2);
ivar[0] = '@';
ivar[len - 1] = 0;

return ivar;
}

static VALUE do_block(VALUE yielded, VALUE proc, int argc, VALUE argv[]) {
return rb_proc_call(proc, rb_ary_new());
}

static VALUE get(VALUE self){
char* ivar = ivar_name();
VALUE val = Qnil;

if (rb_ivar_defined(self, rb_intern(ivar))) {
val = rb_iv_get(self, ivar);
}
else {
VALUE class = RBASIC(self)->klass; //need to check singletons / metaclasses etc, so rb_obj_class is no good
while (val == Qnil && class != rb_cObject) {
//if it's an ICLASS, we need to check the original
VALUE check_class = (RBASIC(class)->flags & T_ICLASS) ? RBASIC(class)->klass : class;
val = rb_iv_get(check_class, ivar +1);
class = RCLASS_SUPER(class);
}

if (rb_obj_is_proc(val)) {
val = rb_funcall(evalulator, evalualte_id, 2, self, val);
}
}

free(ivar);
return val;
}

static VALUE set(VALUE self, VALUE new_val){
char* ivar = ivar_name();
rb_iv_set(self, ivar, new_val);

free(ivar);
return Qnil;
}

static VALUE attribute(VALUE, VALUE);

static int hash_entry(VALUE key, VALUE value, VALUE mod){
attribute(mod, key);
rb_iv_set(mod, StringValuePtr(key), value);
return ST_CONTINUE;
}

static VALUE attribute(VALUE self, VALUE name){

VALUE tmp = rb_check_hash_type(name);
if (!NIL_P(tmp)) {
rb_hash_foreach(tmp, hash_entry, self);
return Qnil;
}

char* c_name = StringValuePtr(name);
size_t name_len = RSTRING_LEN(name);
char* query = malloc(name_len + 2);
memcpy(query, c_name, name_len);
query[name_len + 1] = 0;

rb_define_method(self, c_name, get, 0);
query[name_len] = '?';
rb_define_method(self, query, get, 0);
query[name_len] = '=';
rb_define_method(self, query, set, 1);

free(query);

//set default
VALUE default_val = Qnil;
if (rb_block_given_p()) {
default_val = rb_block_proc();
}
rb_iv_set(self, c_name, default_val);

return Qnil;
}

void Init_knowledge() {
meth_name_id = rb_intern("__method__");
to_s_id = rb_intern("to_s");

//the only way to evaluate a block in another context is in Ruby...
evalulator = rb_class_new_instance(0, NULL, rb_cObject);
VALUE eval_method = rb_str_new2("def eval_in_context(context, proc); context.instance_eval &proc;end");
rb_obj_instance_eval(1, &eval_method, evalulator);
evalualte_id = rb_intern("eval_in_context");

rb_define_method(rb_cModule, "attribute", attribute, 1);
}
24 changes: 24 additions & 0 deletions 67_metakoans/knowledge.rb
@@ -0,0 +1,24 @@
class Module
def attribute name, &blk

name, default = name.is_a?(Hash) ? name.to_a.first : [name, nil]

ivar = "@#{name}"

define_method name do
if instance_variables.include? ivar.to_sym #symbols in 1.9
instance_variable_get ivar
else
blk ? instance_eval(&blk) : default
end
end

define_method "#{name}?" do
!!__send__(name)
end

define_method "#{name}=" do |val|
instance_variable_set ivar, val
end
end
end

0 comments on commit 60fc76c

Please sign in to comment.