gogotanaka/Rubype

View on GitHub
ext/rubype/rubype.c

Summary

Maintainability
Test Coverage
#include "rubype.h"

VALUE rb_mRubype, rb_cContract, rb_eRubypeArgumentTypeError, rb_eRubypeReturnTypeError, rb_eInvalidTypesigError;
static ID id_to_s, id_meth, id_owner, id_arg_types, id_rtn_type, id_private_meth_p, id_protected_meth_p, id_private, id_protected, id_define_method;

#define error_fmt "\nfor %"PRIsVALUE"\nExpected: %"PRIsVALUE"\nActual:   %"PRIsVALUE""
#define unmatch_type_p(obj, type_info) !(match_type_p(obj, type_info))

int match_type_p(VALUE obj, VALUE type_info)
{
  switch (TYPE(type_info)) {
    case T_SYMBOL: return rb_respond_to(obj, rb_to_id(type_info));
                   break;

    case T_MODULE:
    case T_CLASS:  return (int)rb_obj_is_kind_of(obj, type_info);
                   break;

    default:       return 0;
                   break;
  }
}

VALUE expected_mes(VALUE expected)
{
  switch (TYPE(expected)) {
    case T_SYMBOL: return rb_sprintf("respond to #%"PRIsVALUE, expected);
                   break;

    case T_MODULE:
    case T_CLASS:  return rb_funcall(expected, id_to_s, 0);
                   break;

    default:       return rb_str_new2("");
                   break;
  }
}

#define assing_ivars VALUE meth_caller, meth, target;\
                     meth_caller = rb_ivar_get(self, id_owner);\
                     meth        = rb_ivar_get(self, id_meth);

static VALUE
rb_rubype_assert_args_type(VALUE self, VALUE args)
{
  assing_ivars
  int i;
  VALUE arg, arg_type;
  VALUE arg_types = rb_ivar_get(self, id_arg_types);

  for (i=0; i<RARRAY_LEN(args); i++) {
    arg      = rb_ary_entry(args, i);
    arg_type = rb_ary_entry(arg_types, i);

    if (unmatch_type_p(arg, arg_type)){
      target = rb_sprintf("%"PRIsVALUE"#%"PRIsVALUE"'s %d argument", meth_caller, meth, i+1);
      rb_raise(rb_eRubypeArgumentTypeError, error_fmt, target, expected_mes(arg_type), arg);
    }
  }
  return Qnil;
}

static VALUE
rb_rubype_assert_rtn_type(VALUE self, VALUE rtn)
{
  assing_ivars
  VALUE rtn_type = rb_ivar_get(self, id_rtn_type);

  if (unmatch_type_p(rtn, rtn_type)){
    target = rb_sprintf("%"PRIsVALUE"#%"PRIsVALUE"'s return", meth_caller, meth);
    rb_raise(rb_eRubypeReturnTypeError, error_fmt, target, expected_mes(rtn_type), rtn);
  }
  return rtn;
}

static VALUE
rb_rubype_initialize(VALUE self, VALUE arg_types, VALUE rtn_type, VALUE owner, VALUE meth)
{
  rb_ivar_set(self, id_owner,     owner);
  rb_ivar_set(self, id_meth,      meth);
  rb_ivar_set(self, id_arg_types, arg_types);
  rb_ivar_set(self, id_rtn_type,  rtn_type);
  return Qnil;
}

static VALUE
rb_rubype_add_typed_method_to_proxy(VALUE self, VALUE owner, VALUE meth, VALUE proxy_mod)
{
  VALUE body;
  body = rb_block_lambda();
  if ((int)rb_funcall(owner, id_private_meth_p, 1, meth)) {
    rb_funcall(proxy_mod, id_define_method, 2, meth, body);
    rb_funcall(proxy_mod, id_private, 1, meth);
  }
  else if ((int)rb_funcall(owner, id_protected_meth_p, 1, meth)) {
    rb_funcall(proxy_mod, id_define_method, 2, meth, body);
    rb_funcall(proxy_mod, id_protected, 1, meth);
  }
  else {
    rb_funcall(proxy_mod, id_define_method, 2, meth, body);
  }
  return Qnil;
}

void
Init_rubype(void)
{
  rb_mRubype  = rb_define_module("Rubype");

  rb_eRubypeArgumentTypeError = rb_define_class_under(rb_mRubype, "ArgumentTypeError",   rb_eTypeError);
  rb_eRubypeReturnTypeError   = rb_define_class_under(rb_mRubype, "ReturnTypeError",     rb_eTypeError);
  rb_eInvalidTypesigError     = rb_define_class_under(rb_mRubype, "InvalidTypesigError", rb_eTypeError);

  rb_cContract = rb_define_class_under(rb_mRubype, "Contract", rb_cObject);
  rb_define_method(rb_cContract, "initialize", rb_rubype_initialize, 4);
  rb_define_method(rb_cContract, "assert_args_type", rb_rubype_assert_args_type, 1);
  rb_define_method(rb_cContract, "assert_rtn_type", rb_rubype_assert_rtn_type, 1);
  rb_define_singleton_method(rb_mRubype, "add_typed_method_to_proxy", rb_rubype_add_typed_method_to_proxy, 3);


  id_meth      = rb_intern("@meth");
  id_owner     = rb_intern("@owner");
  id_arg_types = rb_intern("@arg_types");
  id_rtn_type  = rb_intern("@rtn_type");
  id_to_s      = rb_intern("to_s");

  id_private_meth_p   = rb_intern("private_method_defined?");
  id_protected_meth_p = rb_intern("protected_method_defined?");
  id_private          = rb_intern("private");
  id_protected        = rb_intern("protected");

  id_define_method    = rb_intern("define_method");
}