Ruby: include/ruby/internal/scan_args.h Source File

Ruby 4.0.5p0 (2026-05-20 revision 64336ffd0ee9e1f4c05891695a3d7b49cb709721)

scan_args.h
Go to the documentation of this file.
1#ifndef RBIMPL_SCAN_ARGS_H /*-*-C++-*-vi:se ft=cpp:*/
2#define RBIMPL_SCAN_ARGS_H
26#include "ruby/assert.h"
27#include "ruby/internal/attr/diagnose_if.h"
28#include "ruby/internal/attr/error.h"
29#include "ruby/internal/attr/forceinline.h"
30#include "ruby/internal/attr/nonnull.h"
31#include "ruby/internal/attr/noreturn.h"
32#include "ruby/internal/config.h"
33#include "ruby/internal/dllexport.h"
34#include "ruby/internal/has/attribute.h"
35#include "ruby/internal/intern/array.h" /* rb_ary_new_from_values */
36#include "ruby/internal/intern/error.h" /* rb_error_arity */
37#include "ruby/internal/intern/hash.h" /* rb_hash_dup */
38#include "ruby/internal/intern/proc.h" /* rb_block_proc */
39#include "ruby/internal/iterator.h" /* rb_block_given_p / rb_keyword_given_p */
40#include "ruby/internal/static_assert.h"
41#include "ruby/internal/stdbool.h"
42#include "ruby/internal/value.h"
43
48
50#define RB_SCAN_ARGS_PASS_CALLED_KEYWORDS 0
51
53#define RB_SCAN_ARGS_KEYWORDS 1
54
59#define RB_SCAN_ARGS_LAST_HASH_KEYWORDS 3
60
62
67
69#define RB_NO_KEYWORDS 0
70
72#define RB_PASS_KEYWORDS 1
73
78#define RB_PASS_CALLED_KEYWORDS !!rb_keyword_given_p()
79
81
89#define HAVE_RB_SCAN_ARGS_OPTIONAL_HASH 1
90
91 RBIMPL_SYMBOL_EXPORT_BEGIN()
92 RBIMPL_ATTR_NONNULL((2, 3))
147int rb_scan_args(int argc, const VALUE *argv, const char *fmt, ...);
148
149 RBIMPL_ATTR_NONNULL((3, 4))
168int rb_scan_args_kw(int kw_splat, int argc, const VALUE *argv, const char *fmt, ...);
169
170 RBIMPL_ATTR_ERROR(("bad scan arg format"))
177void rb_scan_args_bad_format(const char*);
178
179 RBIMPL_ATTR_ERROR(("variable argument length doesn't match"))
186void rb_scan_args_length_mismatch(const char*,int);
187
188 RBIMPL_SYMBOL_EXPORT_END()
189
191
192/* If we could use constexpr the following macros could be inline functions
193 * ... but sadly we cannot. */
194
195#define rb_scan_args_isdigit(c) (RBIMPL_CAST((unsigned char)((c)-'0'))<10)
196
197#define rb_scan_args_count_end(fmt, ofs, vari) \
198 ((fmt)[ofs] ? -1 : (vari))
199
200#define rb_scan_args_count_block(fmt, ofs, vari) \
201 ((fmt)[ofs]!='&' ? \
202 rb_scan_args_count_end(fmt, ofs, vari) : \
203 rb_scan_args_count_end(fmt, (ofs)+1, (vari)+1))
204
205#define rb_scan_args_count_hash(fmt, ofs, vari) \
206 ((fmt)[ofs]!=':' ? \
207 rb_scan_args_count_block(fmt, ofs, vari) : \
208 rb_scan_args_count_block(fmt, (ofs)+1, (vari)+1))
209
210#define rb_scan_args_count_trail(fmt, ofs, vari) \
211 (!rb_scan_args_isdigit((fmt)[ofs]) ? \
212 rb_scan_args_count_hash(fmt, ofs, vari) : \
213 rb_scan_args_count_hash(fmt, (ofs)+1, (vari)+((fmt)[ofs]-'0')))
214
215#define rb_scan_args_count_var(fmt, ofs, vari) \
216 ((fmt)[ofs]!='*' ? \
217 rb_scan_args_count_trail(fmt, ofs, vari) : \
218 rb_scan_args_count_trail(fmt, (ofs)+1, (vari)+1))
219
220#define rb_scan_args_count_opt(fmt, ofs, vari) \
221 (!rb_scan_args_isdigit((fmt)[ofs]) ? \
222 rb_scan_args_count_var(fmt, ofs, vari) : \
223 rb_scan_args_count_var(fmt, (ofs)+1, (vari)+(fmt)[ofs]-'0'))
224
225#define rb_scan_args_count_lead(fmt, ofs, vari) \
226 (!rb_scan_args_isdigit((fmt)[ofs]) ? \
227 rb_scan_args_count_var(fmt, ofs, vari) : \
228 rb_scan_args_count_opt(fmt, (ofs)+1, (vari)+(fmt)[ofs]-'0'))
229
230#define rb_scan_args_count(fmt) rb_scan_args_count_lead(fmt, 0, 0)
231
232#if RBIMPL_HAS_ATTRIBUTE(diagnose_if)
233# /* Assertions done in the attribute. */
234# define rb_scan_args_verify(fmt, varc) RBIMPL_ASSERT_NOTHING
235#else
236# /* At one sight it _seems_ the expressions below could be written using
237# * static assertions. The reality is no, they don't. Because fmt is a
238# * string literal, any operations against fmt cannot produce the "integer
239# * constant expression"s, as defined in ISO/IEC 9899:2018 section 6.6
240# * paragraph #6. Static assertions need such integer constant expressions as
241# * defined in ISO/IEC 9899:2018 section 6.7.10 paragraph #3.
242# *
243# * GCC nonetheless constant-folds this into a no-op, though. */
244# define rb_scan_args_verify(fmt, varc) \
245 (sizeof(char[1-2*(rb_scan_args_count(fmt)<0)])!=1 ? \
246 rb_scan_args_bad_format(fmt) : \
247 sizeof(char[1-2*(rb_scan_args_count(fmt)!=(varc))])!=1 ? \
248 rb_scan_args_length_mismatch(fmt, varc) : \
249 RBIMPL_ASSERT_NOTHING)
250#endif
251
252static inline bool
253rb_scan_args_keyword_p(int kw_flag, VALUE last)
254{
255 switch (kw_flag) {
256 case RB_SCAN_ARGS_PASS_CALLED_KEYWORDS:
257 return !! rb_keyword_given_p();
258 case RB_SCAN_ARGS_KEYWORDS:
259 return true;
260 case RB_SCAN_ARGS_LAST_HASH_KEYWORDS:
261 return RB_TYPE_P(last, T_HASH);
262 default:
263 return false;
264 }
265}
266
267 RBIMPL_ATTR_FORCEINLINE()
268static bool
269rb_scan_args_lead_p(const char *fmt)
270{
271 return rb_scan_args_isdigit(fmt[0]);
272}
273
274 RBIMPL_ATTR_FORCEINLINE()
275static int
276rb_scan_args_n_lead(const char *fmt)
277{
278 return (rb_scan_args_lead_p(fmt) ? fmt[0]-'0' : 0);
279}
280
281 RBIMPL_ATTR_FORCEINLINE()
282static bool
283rb_scan_args_opt_p(const char *fmt)
284{
285 return (rb_scan_args_lead_p(fmt) && rb_scan_args_isdigit(fmt[1]));
286}
287
288 RBIMPL_ATTR_FORCEINLINE()
289static int
290rb_scan_args_n_opt(const char *fmt)
291{
292 return (rb_scan_args_opt_p(fmt) ? fmt[1]-'0' : 0);
293}
294
295 RBIMPL_ATTR_FORCEINLINE()
296static int
297rb_scan_args_var_idx(const char *fmt)
298{
299 return (!rb_scan_args_lead_p(fmt) ? 0 : !rb_scan_args_isdigit(fmt[1]) ? 1 : 2);
300}
301
302 RBIMPL_ATTR_FORCEINLINE()
303static bool
304rb_scan_args_f_var(const char *fmt)
305{
306 return (fmt[rb_scan_args_var_idx(fmt)]=='*');
307}
308
309 RBIMPL_ATTR_FORCEINLINE()
310static int
311rb_scan_args_trail_idx(const char *fmt)
312{
313 const int idx = rb_scan_args_var_idx(fmt);
314 return idx+(fmt[idx]=='*');
315}
316
317 RBIMPL_ATTR_FORCEINLINE()
318static int
319rb_scan_args_n_trail(const char *fmt)
320{
321 const int idx = rb_scan_args_trail_idx(fmt);
322 return (rb_scan_args_isdigit(fmt[idx]) ? fmt[idx]-'0' : 0);
323}
324
325 RBIMPL_ATTR_FORCEINLINE()
326static int
327rb_scan_args_hash_idx(const char *fmt)
328{
329 const int idx = rb_scan_args_trail_idx(fmt);
330 return idx+rb_scan_args_isdigit(fmt[idx]);
331}
332
333 RBIMPL_ATTR_FORCEINLINE()
334static bool
335rb_scan_args_f_hash(const char *fmt)
336{
337 return (fmt[rb_scan_args_hash_idx(fmt)]==':');
338}
339
340 RBIMPL_ATTR_FORCEINLINE()
341static int
342rb_scan_args_block_idx(const char *fmt)
343{
344 const int idx = rb_scan_args_hash_idx(fmt);
345 return idx+(fmt[idx]==':');
346}
347
348 RBIMPL_ATTR_FORCEINLINE()
349static bool
350rb_scan_args_f_block(const char *fmt)
351{
352 return (fmt[rb_scan_args_block_idx(fmt)]=='&');
353}
354
355# if 0
356 RBIMPL_ATTR_FORCEINLINE()
357static int
358rb_scan_args_end_idx(const char *fmt)
359{
360 const int idx = rb_scan_args_block_idx(fmt);
361 return idx+(fmt[idx]=='&');
362}
363# endif
364
365/* NOTE: Use `char *fmt` instead of `const char *fmt` because of clang's bug*/
366/* https://bugs.llvm.org/show_bug.cgi?id=38095 */
367# define rb_scan_args0(argc, argv, fmt, varc, vars) \
368 rb_scan_args_set(RB_SCAN_ARGS_PASS_CALLED_KEYWORDS, argc, argv, \
369 rb_scan_args_n_lead(fmt), \
370 rb_scan_args_n_opt(fmt), \
371 rb_scan_args_n_trail(fmt), \
372 rb_scan_args_f_var(fmt), \
373 rb_scan_args_f_hash(fmt), \
374 rb_scan_args_f_block(fmt), \
375 (rb_scan_args_verify(fmt, varc), vars), (char *)fmt, varc)
376# define rb_scan_args_kw0(kw_flag, argc, argv, fmt, varc, vars) \
377 rb_scan_args_set(kw_flag, argc, argv, \
378 rb_scan_args_n_lead(fmt), \
379 rb_scan_args_n_opt(fmt), \
380 rb_scan_args_n_trail(fmt), \
381 rb_scan_args_f_var(fmt), \
382 rb_scan_args_f_hash(fmt), \
383 rb_scan_args_f_block(fmt), \
384 (rb_scan_args_verify(fmt, varc), vars), (char *)fmt, varc)
385
386 RBIMPL_ATTR_FORCEINLINE()
387static int
388rb_scan_args_set(int kw_flag, int argc, const VALUE *argv,
389 int n_lead, int n_opt, int n_trail,
390 bool f_var, bool f_hash, bool f_block,
391 VALUE *vars[], RB_UNUSED_VAR(const char *fmt), RB_UNUSED_VAR(int varc))
392 RBIMPL_ATTR_DIAGNOSE_IF(rb_scan_args_count(fmt) < 0, "bad scan arg format", "error")
393 RBIMPL_ATTR_DIAGNOSE_IF(rb_scan_args_count(fmt) != varc, "variable argument length doesn't match", "error")
394{
395 int i, argi = 0, vari = 0;
396 VALUE *var, hash = Qnil;
397#define rb_scan_args_next_param() vars[vari++]
398 const int n_mand = n_lead + n_trail;
399
400 /* capture an option hash - phase 1: pop from the argv */
401 if (f_hash && argc > 0) {
402 VALUE last = argv[argc - 1];
403 if (rb_scan_args_keyword_p(kw_flag, last)) {
404 hash = rb_hash_dup(last);
405 argc--;
406 }
407 }
408
409 if (argc < n_mand) {
410 goto argc_error;
411 }
412
413 /* capture leading mandatory arguments */
414 for (i = 0; i < n_lead; i++) {
415 var = rb_scan_args_next_param();
416 if (var) *var = argv[argi];
417 argi++;
418 }
419
420 /* capture optional arguments */
421 for (i = 0; i < n_opt; i++) {
422 var = rb_scan_args_next_param();
423 if (argi < argc - n_trail) {
424 if (var) *var = argv[argi];
425 argi++;
426 }
427 else {
428 if (var) *var = Qnil;
429 }
430 }
431
432 /* capture variable length arguments */
433 if (f_var) {
434 int n_var = argc - argi - n_trail;
435
436 var = rb_scan_args_next_param();
437 if (0 < n_var) {
438 if (var) *var = rb_ary_new_from_values(n_var, &argv[argi]);
439 argi += n_var;
440 }
441 else {
442 if (var) *var = rb_ary_new();
443 }
444 }
445
446 /* capture trailing mandatory arguments */
447 for (i = 0; i < n_trail; i++) {
448 var = rb_scan_args_next_param();
449 if (var) *var = argv[argi];
450 argi++;
451 }
452
453 /* capture an option hash - phase 2: assignment */
454 if (f_hash) {
455 var = rb_scan_args_next_param();
456 if (var) *var = hash;
457 }
458
459 /* capture iterator block */
460 if (f_block) {
461 var = rb_scan_args_next_param();
462 if (rb_block_given_p()) {
463 *var = rb_block_proc();
464 }
465 else {
466 *var = Qnil;
467 }
468 }
469
470 if (argi == argc) {
471 return argc;
472 }
473
474 argc_error:
475 rb_error_arity(argc, n_mand, f_var ? UNLIMITED_ARGUMENTS : n_mand + n_opt);
476 UNREACHABLE_RETURN(-1);
477#undef rb_scan_args_next_param
478}
479
481
482#if defined(__DOXYGEN__)
483# /* don't bother */
484
485#elif ! defined(HAVE_BUILTIN___BUILTIN_CHOOSE_EXPR_CONSTANT_P)
486# /* skip */
487
488#elif ! defined(HAVE_VA_ARGS_MACRO)
489# /* skip */
490
491#elif ! defined(__OPTIMIZE__)
492# /* skip */
493
494#elif defined(HAVE___VA_OPT__)
495# define rb_scan_args(argc, argvp, fmt, ...) \
496 __builtin_choose_expr( \
497 __builtin_constant_p(fmt), \
498 rb_scan_args0( \
499 argc, argvp, fmt, \
500 (sizeof((VALUE*[]){__VA_ARGS__})/sizeof(VALUE*)), \
501 ((VALUE*[]){__VA_ARGS__})), \
502 (rb_scan_args)(argc, argvp, fmt __VA_OPT__(, __VA_ARGS__)))
503# define rb_scan_args_kw(kw_flag, argc, argvp, fmt, ...) \
504 __builtin_choose_expr( \
505 __builtin_constant_p(fmt), \
506 rb_scan_args_kw0( \
507 kw_flag, argc, argvp, fmt, \
508 (sizeof((VALUE*[]){__VA_ARGS__})/sizeof(VALUE*)), \
509 ((VALUE*[]){__VA_ARGS__})), \
510 (rb_scan_args_kw)(kw_flag, argc, argvp, fmt __VA_OPT__(, __VA_ARGS__)))
511
512#elif defined(__STRICT_ANSI__)
513# /* skip */
514
515#elif defined(__GNUC__)
516# define rb_scan_args(argc, argvp, fmt, ...) \
517 __builtin_choose_expr( \
518 __builtin_constant_p(fmt), \
519 rb_scan_args0( \
520 argc, argvp, fmt, \
521 (sizeof((VALUE*[]){__VA_ARGS__})/sizeof(VALUE*)), \
522 ((VALUE*[]){__VA_ARGS__})), \
523 (rb_scan_args)(argc, argvp, fmt, __VA_ARGS__))
524# define rb_scan_args_kw(kw_flag, argc, argvp, fmt, ...) \
525 __builtin_choose_expr( \
526 __builtin_constant_p(fmt), \
527 rb_scan_args_kw0( \
528 kw_flag, argc, argvp, fmt, \
529 (sizeof((VALUE*[]){__VA_ARGS__})/sizeof(VALUE*)), \
530 ((VALUE*[]){__VA_ARGS__})), \
531 (rb_scan_args_kw)(kw_flag, argc, argvp, fmt, __VA_ARGS__ /**/))
532#endif
533
534#endif /* RBIMPL_SCAN_ARGS_H */
assert.h
attribute.h
Defines RBIMPL_HAS_ATTRIBUTE.
diagnose_if.h
Defines RBIMPL_ATTR_DIAGNOSE_IF.
RBIMPL_ATTR_DIAGNOSE_IF
#define RBIMPL_ATTR_DIAGNOSE_IF(_, __, ___)
Wraps (or simulates) __attribute__((diagnose_if)).
Definition diagnose_if.h:39
dllexport.h
Tweaking visibility of C variables/functions.
RBIMPL_SYMBOL_EXPORT_END
#define RBIMPL_SYMBOL_EXPORT_END()
Counterpart of RBIMPL_SYMBOL_EXPORT_BEGIN.
Definition dllexport.h:74
RBIMPL_SYMBOL_EXPORT_BEGIN
#define RBIMPL_SYMBOL_EXPORT_BEGIN()
Shortcut macro equivalent to RUBY_SYMBOL_EXPORT_BEGIN extern "C" {.
Definition dllexport.h:65
forceinline.h
Defines RBIMPL_ATTR_FORCEINLINE.
RBIMPL_ATTR_FORCEINLINE
#define RBIMPL_ATTR_FORCEINLINE()
Wraps (or simulates) __forceinline.
Definition forceinline.h:37
rb_scan_args_kw
int rb_scan_args_kw(int kw_flag, int argc, const VALUE *argv, const char *fmt,...)
Identical to rb_scan_args(), except it also accepts kw_splat.
Definition class.c:3146
rb_scan_args
int rb_scan_args(int argc, const VALUE *argv, const char *fmt,...)
Retrieves argument from argc and argv to given VALUE references according to the format string.
Definition class.c:3133
rb_keyword_given_p
int rb_keyword_given_p(void)
Determines if the current method is given a keyword argument.
Definition eval.c:1023
rb_block_given_p
int rb_block_given_p(void)
Determines if the current method is given a block.
Definition eval.c:1010
UNREACHABLE_RETURN
#define UNREACHABLE_RETURN
Old name of RBIMPL_UNREACHABLE_RETURN.
Definition assume.h:29
T_HASH
#define T_HASH
Old name of RUBY_T_HASH.
Definition value_type.h:65
Qnil
#define Qnil
Old name of RUBY_Qnil.
Definition special_consts.h:60
error.h
Defines RBIMPL_ATTR_ERROR.
RBIMPL_ATTR_ERROR
#define RBIMPL_ATTR_ERROR(msg)
Wraps (or simulates) __attribute__((error)).
Definition error.h:29
array.h
Public APIs related to rb_cArray.
rb_ary_new_from_values
VALUE rb_ary_new_from_values(long n, const VALUE *elts)
Identical to rb_ary_new_from_args(), except how objects are passed.
rb_ary_new
VALUE rb_ary_new(void)
Allocates a new, empty array.
error.h
Public APIs related to rb_eException.
UNLIMITED_ARGUMENTS
#define UNLIMITED_ARGUMENTS
This macro is used in conjunction with rb_check_arity().
Definition error.h:35
hash.h
Public APIs related to rb_cHash.
proc.h
Public APIs related to rb_cProc.
rb_block_proc
VALUE rb_block_proc(void)
Constructs a Proc object from implicitly passed components.
Definition proc.c:983
static_assert.h
Defines RBIMPL_STATIC_ASSERT.
iterator.h
Block related APIs.
nonnull.h
Defines RBIMPL_ATTR_NONNULL.
RBIMPL_ATTR_NONNULL
#define RBIMPL_ATTR_NONNULL(list)
Wraps (or simulates) __attribute__((nonnull)).
Definition nonnull.h:30
noreturn.h
Defines RBIMPL_ATTR_NORETURN.
RB_SCAN_ARGS_PASS_CALLED_KEYWORDS
#define RB_SCAN_ARGS_PASS_CALLED_KEYWORDS
Same behaviour as rb_scan_args().
Definition scan_args.h:50
RB_SCAN_ARGS_KEYWORDS
#define RB_SCAN_ARGS_KEYWORDS
The final argument should be a hash treated as keywords.
Definition scan_args.h:53
RB_SCAN_ARGS_LAST_HASH_KEYWORDS
#define RB_SCAN_ARGS_LAST_HASH_KEYWORDS
Treat a final argument as keywords if it is a hash, and not as keywords otherwise.
Definition scan_args.h:59
stdbool.h
C99 shim for <stdbool.h>.
value.h
Defines VALUE and ID.
VALUE
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40
RB_TYPE_P
static bool RB_TYPE_P(VALUE obj, enum ruby_value_type t)
Queries if the given object is of given type.
Definition value_type.h:376