diff options
| -rw-r--r-- | include/mruby.h | 57 | ||||
| -rw-r--r-- | src/class.c | 78 |
2 files changed, 134 insertions, 1 deletions
diff --git a/include/mruby.h b/include/mruby.h index 5d12cf84e..04e0c0df7 100644 --- a/include/mruby.h +++ b/include/mruby.h @@ -870,12 +870,68 @@ MRB_API struct RClass * mrb_define_module_under(mrb_state *mrb, struct RClass *o * | `*` | rest arguments | {mrb_value} *, {mrb_int} | Receive the rest of arguments as an array; `*!` avoid copy of the stack. | * | <code>\|</code> | optional | | After this spec following specs would be optional. | * | `?` | optional given | {mrb_bool} | `TRUE` if preceding argument is given. Used to check optional argument is given. | + * | `:` | keyword args | {mrb_kwargs} const | Get keyword arguments. @see mrb_kwargs | * * @see mrb_get_args */ typedef const char *mrb_args_format; /** + * Get keyword arguments by `mrb_get_args()` with `:` specifier. + * + * `mrb_kwargs::num` indicates that the number of keyword values. + * + * `mrb_kwargs::values` is an object array, and the keyword argument corresponding to the string array is assigned. + * Note that `undef` is assigned if there is no keyword argument corresponding to `mrb_kwargs::optional`. + * + * `mrb_kwargs::table` accepts a string array. + * + * `mrb_kwargs::required` indicates that the specified number of keywords starting from the beginning of the string array are required. + * + * `mrb_kwargs::rest` is the remaining keyword argument that can be accepted as `**rest` in Ruby. + * If `NULL` is specified, `ArgumentError` is raised when there is an undefined keyword. + * + * Examples: + * + * // def method(a: 1, b: 2) + * + * uint32_t kw_num = 2; + * const char *kw_names[kw_num] = { "a", "b" }; + * uint32_t kw_required = 0; + * mrb_value kw_values[kw_num]; + * const mrb_kwargs kwargs = { kw_num, kw_values, kw_names, kw_required, NULL }; + * + * mrb_get_args(mrb, ":", &kwargs); + * if (mrb_undef_p(kw_values[0])) { kw_values[0] = mrb_fixnum_value(1); } + * if (mrb_undef_p(kw_values[1])) { kw_values[1] = mrb_fixnum_value(2); } + * + * + * // def method(str, x:, y: 2, z: "default string", **opts) + * + * mrb_value str, kw_rest; + * uint32_t kw_num = 3; + * const char *kw_names[kw_num] = { "x", "y", "z" }; + * uint32_t kw_required = 1; + * mrb_value kw_values[kw_num]; + * const mrb_kwargs kwargs = { kw_num, kw_values, kw_names, kw_required, &kw_rest }; + * + * mrb_get_args(mrb, "S:", &str, &kwargs); + * // or: mrb_get_args(mrb, ":S", &kwargs, &str); + * if (mrb_undef_p(kw_values[1])) { kw_values[1] = mrb_fixnum_value(2); } + * if (mrb_undef_p(kw_values[2])) { kw_values[2] = mrb_str_new_cstr(mrb, "default string"); } + */ +typedef struct mrb_kwargs mrb_kwargs; + +struct mrb_kwargs +{ + uint32_t num; + mrb_value *values; + const char *const *table; + uint32_t required; + mrb_value *rest; +}; + +/** * Retrieve arguments from mrb_state. * * @param mrb The current MRuby state. @@ -883,6 +939,7 @@ typedef const char *mrb_args_format; * @param ... The passing variadic arguments must be a pointer of retrieving type. * @return the number of arguments retrieved. * @see mrb_args_format + * @see mrb_kwargs */ MRB_API mrb_int mrb_get_args(mrb_state *mrb, mrb_args_format format, ...); diff --git a/src/class.c b/src/class.c index 9803e7662..74ff5e11a 100644 --- a/src/class.c +++ b/src/class.c @@ -7,6 +7,7 @@ #include <stdarg.h> #include <mruby.h> #include <mruby/array.h> +#include <mruby/hash.h> #include <mruby/class.h> #include <mruby/numeric.h> #include <mruby/proc.h> @@ -549,6 +550,8 @@ mrb_get_argv(mrb_state *mrb) return array_argv; } +void mrb_hash_check_kdict(mrb_state *mrb, mrb_value self); + /* retrieve arguments from mrb_state. @@ -578,6 +581,7 @@ mrb_get_argv(mrb_state *mrb) *: rest argument [mrb_value*,mrb_int] The rest of the arguments as an array; *! avoid copy of the stack |: optional Following arguments are optional ?: optional given [mrb_bool] true if preceding argument (optional) is given + ':': keyword args [mrb_kwargs const] Get keyword arguments */ MRB_API mrb_int mrb_get_args(mrb_state *mrb, const char *format, ...) @@ -592,6 +596,9 @@ mrb_get_args(mrb_state *mrb, const char *format, ...) mrb_bool opt = FALSE; mrb_bool opt_skip = TRUE; mrb_bool given = TRUE; + mrb_value kdict; + mrb_bool reqkarg = FALSE; + mrb_int needargc = 0; va_start(ap, format); @@ -605,18 +612,31 @@ mrb_get_args(mrb_state *mrb, const char *format, ...) break; case '*': opt_skip = FALSE; + if (!reqkarg) reqkarg = strchr(fmt, ':') ? TRUE : FALSE; goto check_exit; case '!': break; case '&': case '?': if (opt) opt_skip = FALSE; break; + case ':': + reqkarg = TRUE; + break; default: + if (!opt) needargc ++; break; } } check_exit: + if (reqkarg && argc > needargc && mrb_hash_p(kdict = ARGV[argc - 1])) { + mrb_hash_check_kdict(mrb, kdict); + argc --; + } + else { + kdict = mrb_nil_value(); + } + opt = FALSE; i = 0; while ((c = *format++)) { @@ -624,7 +644,7 @@ mrb_get_args(mrb_state *mrb, const char *format, ...) mrb_bool altmode; switch (c) { - case '|': case '*': case '&': case '?': + case '|': case '*': case '&': case '?': case ':': break; default: if (argc <= i) { @@ -932,6 +952,62 @@ mrb_get_args(mrb_state *mrb, const char *format, ...) } } 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 char *const *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(mrb_intern_cstr(mrb, *kname)); + if (!mrb_hash_key_p(mrb, ksrc, k)) { + mrb_raisef(mrb, E_ARGUMENT_ERROR, "missing keyword: %s", *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(mrb_intern_cstr(mrb, *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; |
