/*
*
* This code is under public domain (CC0)
* .
*
* To the extent possible under law, dearblue has waived all copyright
* and related or neighboring rights to this work.
*
* dearblue
*/
#include "hashargs.h"
struct rbx_scanhash_args
{
struct rbx_scanhash_arg *args;
const struct rbx_scanhash_arg *end;
VALUE rest;
};
static void
rbx_scanhash_error(ID given, struct rbx_scanhash_arg *args, const struct rbx_scanhash_arg *end)
{
// 引数の数が㌧でもない数の場合、よくないことが起きそう。
VALUE names = rb_ary_new();
for (; args < end; args ++) {
rb_ary_push(names, ID2SYM(args->name));
}
size_t namenum = RARRAY_LEN(names);
if (namenum > 2) {
VALUE w = rb_ary_pop(names);
names = rb_ary_join(names, rb_str_new_cstr(", "));
names = rb_ary_new4(1, &names);
rb_ary_push(names, w);
names = rb_ary_join(names, rb_str_new_cstr(" or "));
} else if (namenum > 1) {
names = rb_ary_join(names, rb_str_new_cstr(" or "));
}
{
VALUE key = rb_sym_to_s(ID2SYM(given));
rb_raise(rb_eArgError,
"unknown keyword (%s for %s)",
StringValueCStr(key), StringValueCStr(names));
}
}
static inline ID
rbx_scanhash_intern(VALUE key)
{
if (RB_TYPE_P(key, RUBY_T_SYMBOL)) {
return SYM2ID(key);
} else {
key = rb_String(key);
return rb_intern_str(key);
}
}
static int
rbx_scanhash_foreach(VALUE key, VALUE value, struct rbx_scanhash_args *args)
{
struct rbx_scanhash_arg *p = args->args;
const struct rbx_scanhash_arg *end = args->end;
ID keyid = rbx_scanhash_intern(key);
for (; p < end; p ++) {
if (p->name == keyid) {
if (p->dest) {
*p->dest = value;
}
return 0;
}
}
if (RTEST(args->rest)) {
rb_hash_aset(args->rest, key, value);
} else {
rbx_scanhash_error(keyid, args->args, args->end);
}
return 0;
}
static VALUE
rbx_scanhash_to_hash(VALUE hash)
{
if (NIL_P(hash)) { return Qnil; }
static ID id_to_hash;
if (!id_to_hash) { id_to_hash = rb_intern_const("to_hash"); }
VALUE hash1 = rb_funcall2(hash, id_to_hash, 0, 0);
if (TYPE(hash1) != T_HASH) {
rb_raise(rb_eTypeError,
"converted object is not a hash (<#%s:%p>)",
rb_obj_classname(hash), (void *)hash);
}
return hash1;
}
static inline void
rbx_scanhash_setdefaults(struct rbx_scanhash_arg *args, struct rbx_scanhash_arg *end)
{
for (; args < end; args ++) {
if (args->dest) {
*args->dest = args->initval;
}
}
}
static inline void
rbx_scanhash_check_missingkeys(struct rbx_scanhash_arg *args, struct rbx_scanhash_arg *end)
{
for (; args < end; args ++) {
if (args->dest && *args->dest == Qundef) {
VALUE key = rb_sym_to_s(ID2SYM(args->name));
rb_raise(rb_eArgError,
"missing keyword: `%s'",
StringValueCStr(key));
}
}
}
VALUE
rbx_scanhash(VALUE hash, VALUE rest, struct rbx_scanhash_arg *args, struct rbx_scanhash_arg *end)
{
if (RTEST(rest)) {
if (rest == Qtrue) {
rest = rb_hash_new();
} else if (!rb_obj_is_kind_of(rest, rb_cHash)) {
rb_raise(rb_eArgError,
"`rest' is not a hash");
}
} else {
rest = Qnil;
}
rbx_scanhash_setdefaults(args, end);
hash = rbx_scanhash_to_hash(hash);
if (!NIL_P(hash) && !RHASH_EMPTY_P(hash)) {
struct rbx_scanhash_args argset = { args, end, rest };
rb_hash_foreach(hash, (int (*)(VALUE, VALUE, VALUE))rbx_scanhash_foreach, (VALUE)&argset);
}
rbx_scanhash_check_missingkeys(args, end);
return rest;
}