Unverified Commit ff1ac49a authored by Yukihiro "Matz" Matsumoto's avatar Yukihiro "Matz" Matsumoto Committed by GitHub

Merge pull request #4754 from dearblue/get-keyword-args

Get keyword arguments with `mrb_get_args()`
parents ffaa2968 5f929d69
...@@ -870,11 +870,67 @@ MRB_API struct RClass * mrb_define_module_under(mrb_state *mrb, struct RClass *o ...@@ -870,11 +870,67 @@ 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. | * | `*` | 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. | * | <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. | * | `?` | 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 * @see mrb_get_args
*/ */
typedef const char *mrb_args_format; 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. * Retrieve arguments from mrb_state.
* *
...@@ -883,6 +939,7 @@ typedef const char *mrb_args_format; ...@@ -883,6 +939,7 @@ typedef const char *mrb_args_format;
* @param ... The passing variadic arguments must be a pointer of retrieving type. * @param ... The passing variadic arguments must be a pointer of retrieving type.
* @return the number of arguments retrieved. * @return the number of arguments retrieved.
* @see mrb_args_format * @see mrb_args_format
* @see mrb_kwargs
*/ */
MRB_API mrb_int mrb_get_args(mrb_state *mrb, mrb_args_format format, ...); MRB_API mrb_int mrb_get_args(mrb_state *mrb, mrb_args_format format, ...);
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <stdarg.h> #include <stdarg.h>
#include <mruby.h> #include <mruby.h>
#include <mruby/array.h> #include <mruby/array.h>
#include <mruby/hash.h>
#include <mruby/class.h> #include <mruby/class.h>
#include <mruby/numeric.h> #include <mruby/numeric.h>
#include <mruby/proc.h> #include <mruby/proc.h>
...@@ -549,6 +550,8 @@ mrb_get_argv(mrb_state *mrb) ...@@ -549,6 +550,8 @@ mrb_get_argv(mrb_state *mrb)
return array_argv; return array_argv;
} }
void mrb_hash_check_kdict(mrb_state *mrb, mrb_value self);
/* /*
retrieve arguments from mrb_state. retrieve arguments from mrb_state.
...@@ -578,6 +581,7 @@ mrb_get_argv(mrb_state *mrb) ...@@ -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 *: 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 Following arguments are optional
?: optional given [mrb_bool] true if preceding argument (optional) is given ?: optional given [mrb_bool] true if preceding argument (optional) is given
':': keyword args [mrb_kwargs const] Get keyword arguments
*/ */
MRB_API mrb_int MRB_API mrb_int
mrb_get_args(mrb_state *mrb, const char *format, ...) mrb_get_args(mrb_state *mrb, const char *format, ...)
...@@ -592,6 +596,9 @@ 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 = FALSE;
mrb_bool opt_skip = TRUE; mrb_bool opt_skip = TRUE;
mrb_bool given = TRUE; mrb_bool given = TRUE;
mrb_value kdict;
mrb_bool reqkarg = FALSE;
mrb_int needargc = 0;
va_start(ap, format); va_start(ap, format);
...@@ -605,18 +612,31 @@ mrb_get_args(mrb_state *mrb, const char *format, ...) ...@@ -605,18 +612,31 @@ mrb_get_args(mrb_state *mrb, const char *format, ...)
break; break;
case '*': case '*':
opt_skip = FALSE; opt_skip = FALSE;
if (!reqkarg) reqkarg = strchr(fmt, ':') ? TRUE : FALSE;
goto check_exit; goto check_exit;
case '!': case '!':
break; break;
case '&': case '?': case '&': case '?':
if (opt) opt_skip = FALSE; if (opt) opt_skip = FALSE;
break; break;
case ':':
reqkarg = TRUE;
break;
default: default:
if (!opt) needargc ++;
break; break;
} }
} }
check_exit: 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; opt = FALSE;
i = 0; i = 0;
while ((c = *format++)) { while ((c = *format++)) {
...@@ -624,7 +644,7 @@ mrb_get_args(mrb_state *mrb, const char *format, ...) ...@@ -624,7 +644,7 @@ mrb_get_args(mrb_state *mrb, const char *format, ...)
mrb_bool altmode; mrb_bool altmode;
switch (c) { switch (c) {
case '|': case '*': case '&': case '?': case '|': case '*': case '&': case '?': case ':':
break; break;
default: default:
if (argc <= i) { if (argc <= i) {
...@@ -932,6 +952,62 @@ mrb_get_args(mrb_state *mrb, const char *format, ...) ...@@ -932,6 +952,62 @@ mrb_get_args(mrb_state *mrb, const char *format, ...)
} }
} }
break; 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: default:
mrb_raisef(mrb, E_ARGUMENT_ERROR, "invalid argument specifier %c", c); mrb_raisef(mrb, E_ARGUMENT_ERROR, "invalid argument specifier %c", c);
break; break;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment