mrb_get_args(mrb_state *mrb, const char *format, ...)
{
  const char *fmt = format;
  char c;
  int i = 0;
  va_list ap;
  mrb_callinfo *ci = mrb->c->ci;
  int argc = ci->n;
  const mrb_value *argv = ci->stack+1;
  mrb_bool argv_on_stack;
  mrb_bool opt = FALSE;
  mrb_bool opt_skip = TRUE;
  const mrb_value *pickarg = NULL; /* arguments currently being processed */
  mrb_value kdict = mrb_nil_value();
  mrb_bool reqkarg = FALSE;
  int argc_min = 0, argc_max = 0;

  va_start(ap, format);

  while ((c = *fmt++)) {
    switch (c) {
    case '|':
      opt = TRUE;
      break;
    case '*':
      opt_skip = FALSE;
      argc_max = -1;
      if (!reqkarg) reqkarg = strchr(fmt, ':') ? TRUE : FALSE;
      goto check_exit;
    case '!':
    case '+':
      break;
    case ':':
      reqkarg = TRUE;
      /* fall through */
    case '&': case '?':
      if (opt) opt_skip = FALSE;
      break;
    default:
      if (!opt) argc_min++;
      argc_max++;
      break;
    }
  }

 check_exit:
  if (!reqkarg && ci->nk > 0) {
    mrb_assert(ci->nk == 15);
    kdict = ci->stack[mrb_ci_bidx(ci)-1];
    if (mrb_hash_p(kdict) && mrb_hash_size(mrb, kdict) > 0) {
      if (argc < 14) {
        ci->n++;
        argc++;    /* include kdict in normal arguments */
      }
      else {
        /* 14+1 == 15 so pack first */
        if (argc == 14) {
          /* pack arguments and kdict */
          ci->stack[1] = mrb_ary_new_from_values(mrb, argc+1, &ci->stack[1]);
          argc = ci->n = 15;
        }
        else {
          /* push kdict to packed arguments */
          mrb_ary_push(mrb, ci->stack[1], kdict);
        }
        ci->stack[2] = ci->stack[mrb_ci_bidx(ci)];
      }
      ci->nk = 0;
    }
  }
  if (reqkarg && ci->nk > 0) {
    kdict = ci->stack[mrb_ci_bidx(ci)-1];
    mrb_assert(ci->nk == 15);
    mrb_assert(mrb_hash_p(kdict));
  }

  argv_on_stack = argc < 15;
  if (!argv_on_stack) {
    struct RArray *a = mrb_ary_ptr(*argv);
    argv = ARY_PTR(a);
    argc = ARY_LEN(a);
  }

  opt = FALSE;
  i = 0;
  while ((c = *format++)) {
    mrb_bool altmode = FALSE;
    mrb_bool needmodify = FALSE;

    for (; *format; format++) {
      switch (*format) {
      case '!':
        if (altmode) goto modifier_exit; /* not accept for multiple '!' */
        altmode = TRUE;
        break;
      case '+':
        if (needmodify) goto modifier_exit; /* not accept for multiple '+' */
        needmodify = TRUE;
        break;
      default:
        goto modifier_exit;
      }
    }

  modifier_exit:
    switch (c) {
    case '|': case '*': case '&': case '?': case ':':
      if (needmodify) {
      bad_needmodify:
        mrb_raisef(mrb, E_ARGUMENT_ERROR, "wrong `%c+` modified specifier`", c);
      }
      break;
    default:
      if (i < argc) {
        pickarg = &argv[i++];
        if (needmodify && !mrb_nil_p(*pickarg)) {
          if (mrb_immediate_p(*pickarg)) {
            mrb_raisef(mrb, E_FROZEN_ERROR, "can't modify frozen %t", *pickarg);
          }
          mrb_check_frozen(mrb, mrb_obj_ptr(*pickarg));
        }
      }
      else {
        if (opt) {
          pickarg = NULL;
        }
        else {
          mrb_argnum_error(mrb, argc, argc_min, argc_max);
        }
      }
      break;
    }

    switch (c) {
    case 'o':
    case 'C':
    case 'S':
    case 'A':
    case 'H':
      {
        mrb_value *p;

        p = va_arg(ap, mrb_value*);
        if (pickarg) {
          if (!(altmode && mrb_nil_p(*pickarg))) {
            switch (c) {
            case 'C': ensure_class_type(mrb, *pickarg); break;
            case 'S': mrb_ensure_string_type(mrb, *pickarg); break;
            case 'A': mrb_ensure_array_type(mrb, *pickarg); break;
            case 'H': mrb_ensure_hash_type(mrb, *pickarg); break;
            }
          }
          *p = *pickarg;
        }
      }
      break;
    case 'c':
      {
        struct RClass **p;

        p = va_arg(ap, struct RClass**);
        if (pickarg) {
          if (altmode && mrb_nil_p(*pickarg)) {
            *p = NULL;
          }
          else {
            ensure_class_type(mrb, *pickarg);
            *p = mrb_class_ptr(*pickarg);
          }
        }
      }
      break;
    case 's':
      {
        const char **ps = 0;
        mrb_int *pl = 0;

        ps = va_arg(ap, const char**);
        pl = va_arg(ap, mrb_int*);
        if (needmodify) goto bad_needmodify;
        if (pickarg) {
          if (altmode && mrb_nil_p(*pickarg)) {
            *ps = NULL;
            *pl = 0;
          }
          else {
            mrb_ensure_string_type(mrb, *pickarg);
            *ps = RSTRING_PTR(*pickarg);
            *pl = RSTRING_LEN(*pickarg);
          }
        }
      }
      break;
    case 'z':
      {
        const char **ps;

        ps = va_arg(ap, const char**);
        if (needmodify) goto bad_needmodify;
        if (pickarg) {
          if (altmode && mrb_nil_p(*pickarg)) {
            *ps = NULL;
          }
          else {
            mrb_ensure_string_type(mrb, *pickarg);
            *ps = RSTRING_CSTR(mrb, *pickarg);
          }
        }
      }
      break;
    case 'a':
      {
        struct RArray *a;
        const mrb_value **pb;
        mrb_int *pl;

        pb = va_arg(ap, const mrb_value**);
        pl = va_arg(ap, mrb_int*);
        if (needmodify) goto bad_needmodify;
        if (pickarg) {
          if (altmode && mrb_nil_p(*pickarg)) {
            *pb = 0;
            *pl = 0;
          }
          else {
            mrb_ensure_array_type(mrb, *pickarg);
            a = mrb_ary_ptr(*pickarg);
            *pb = ARY_PTR(a);
            *pl = ARY_LEN(a);
          }
        }
      }
      break;
    case 'I':
      {
        void* *p;
        struct RClass *klass;

        p = va_arg(ap, void**);
        klass = va_arg(ap, struct RClass*);
        if (pickarg) {
          if (altmode && mrb_nil_p(*pickarg)) {
            *p = NULL;
          }
          else {
            if (!mrb_obj_is_kind_of(mrb, *pickarg, klass)) {
              mrb_raisef(mrb, E_TYPE_ERROR, "%v is not a %C", *pickarg, klass);
            }
            if (!mrb_istruct_p(*pickarg)) {
              mrb_raisef(mrb, E_TYPE_ERROR, "%v is not inline struct", *pickarg);
            }
            *p = mrb_istruct_ptr(*pickarg);
          }
        }
      }
      break;
#ifndef MRB_NO_FLOAT
    case 'f':
      {
        mrb_float *p;

        p = va_arg(ap, mrb_float*);
        if (pickarg) {
          *p = mrb_as_float(mrb, *pickarg);
        }
      }
      break;
#endif
    case 'i':
      {
        mrb_int *p;

        p = va_arg(ap, mrb_int*);
        if (pickarg) {
          *p = mrb_as_int(mrb, *pickarg);
        }
      }
      break;
    case 'b':
      {
        mrb_bool *boolp = va_arg(ap, mrb_bool*);

        if (pickarg) {
          *boolp = mrb_test(*pickarg);
        }
      }
      break;
    case 'n':
      {
        mrb_sym *symp;

        symp = va_arg(ap, mrb_sym*);
        if (pickarg) {
          *symp = to_sym(mrb, *pickarg);
        }
      }
      break;
    case 'd':
      {
        void** datap;
        struct mrb_data_type const* type;

        datap = va_arg(ap, void**);
        type = va_arg(ap, struct mrb_data_type const*);
        if (pickarg) {
          if (altmode && mrb_nil_p(*pickarg)) {
            *datap = 0;
          }
          else {
            *datap = mrb_data_get_ptr(mrb, *pickarg, type);
          }
        }
      }
      break;

    case '&':
      {
        mrb_value *p, *bp;

        p = va_arg(ap, mrb_value*);
        bp = ci->stack + mrb_ci_bidx(ci);
        if (altmode && mrb_nil_p(*bp)) {
          mrb_raise(mrb, E_ARGUMENT_ERROR, "no block given");
        }
        *p = *bp;
      }
      break;
    case '|':
      if (opt_skip && i == argc) goto finish;
      opt = TRUE;
      break;
    case '?':
      {
        mrb_bool *p;

        p = va_arg(ap, mrb_bool*);
        *p = pickarg ? TRUE : FALSE;
      }
      break;

    case '*':
      {
        const mrb_value **var;
        mrb_int *pl;
        mrb_bool nocopy = (altmode || !argv_on_stack) ? TRUE : FALSE;

        var = va_arg(ap, const mrb_value**);
        pl = va_arg(ap, mrb_int*);
        if (argc > i) {
          *pl = argc-i;
          if (*pl > 0) {
            if (nocopy) {
              *var = argv+i;
            }
            else {
              mrb_value args = mrb_ary_new_from_values(mrb, *pl, argv+i);
              RARRAY(args)->c = NULL;
              *var = RARRAY_PTR(args);
            }
          }
          i = argc;
        }
        else {
          *pl = 0;
          *var = NULL;
        }
      }
      break;

    case ':':
      {
        mrb_value ksrc = mrb_hash_p(kdict) ? mrb_hash_dup(mrb, kdict) : mrb_hash_new(mrb);
        const mrb_kwargs *kwargs = va_arg(ap, const mrb_kwargs*);
        mrb_value *rest;

        if (kwargs == NULL) {
          rest = NULL;
        }
        else {
          uint32_t kwnum = kwargs->num;
          uint32_t required = kwargs->required;
          const mrb_sym *kname = kwargs->table;
          mrb_value *values = kwargs->values;
          uint32_t j;
          const uint32_t keyword_max = 40;

          if (kwnum > keyword_max || required > kwnum) {
            mrb_raise(mrb, E_ARGUMENT_ERROR, "keyword number is too large");
          }

          for (j = required; j > 0; j--, kname++, values++) {
            mrb_value k = mrb_symbol_value(*kname);
            if (!mrb_hash_key_p(mrb, ksrc, k)) {
              mrb_raisef(mrb, E_ARGUMENT_ERROR, "missing keyword: %n", *kname);
            }
            *values = mrb_hash_delete_key(mrb, ksrc, k);
            mrb_gc_protect(mrb, *values);
          }

          for (j = kwnum - required; j > 0; j--, kname++, values++) {
            mrb_value k = mrb_symbol_value(*kname);
            if (mrb_hash_key_p(mrb, ksrc, k)) {
              *values = mrb_hash_delete_key(mrb, ksrc, k);
              mrb_gc_protect(mrb, *values);
            }
            else {
              *values = mrb_undef_value();
            }
          }

          rest = kwargs->rest;
        }

        if (rest) {
          *rest = ksrc;
        }
        else if (!mrb_hash_empty_p(mrb, ksrc)) {
          ksrc = mrb_hash_keys(mrb, ksrc);
          ksrc = RARRAY_PTR(ksrc)[0];
          mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown keyword: %v", ksrc);
        }
      }
      break;

    default:
      mrb_raisef(mrb, E_ARGUMENT_ERROR, "invalid argument specifier %c", c);
      break;
    }
  }

  if (!c && argc > i) {
    mrb_argnum_error(mrb, argc, argc_min, argc_max);
  }

finish:
  va_end(ap);
  return i;
}