1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/include/ruby/internal/scan_args.h
2022-09-23 08:52:15 +09:00

534 lines
18 KiB
C++

#ifndef RBIMPL_SCAN_ARGS_H /*-*-C++-*-vi:se ft=cpp:*/
#define RBIMPL_SCAN_ARGS_H
/**
* @file
* @author Ruby developers <ruby-core@ruby-lang.org>
* @copyright This file is a part of the programming language Ruby.
* Permission is hereby granted, to either redistribute and/or
* modify this file, provided that the conditions mentioned in the
* file COPYING are met. Consult the file for details.
* @warning Symbols prefixed with either `RBIMPL` or `rbimpl` are
* implementation details. Don't take them as canon. They could
* rapidly appear then vanish. The name (path) of this header file
* is also an implementation detail. Do not expect it to persist
* at the place it is now. Developers are free to move it anywhere
* anytime at will.
* @note To ruby-core: remember that this header can be possibly
* recursively included from extension libraries written in C++.
* Do not expect for instance `__VA_ARGS__` is always available.
* We assume C99 for ruby itself but we don't assume languages of
* extension libraries. They could be written in C++98.
* @brief Compile-time static implementation of ::rb_scan_args().
*
* This is a beast. It statically analyses the argument spec string, and
* expands the assignment of variables into dedicated codes.
*/
#include "ruby/assert.h"
#include "ruby/internal/attr/diagnose_if.h"
#include "ruby/internal/attr/error.h"
#include "ruby/internal/attr/forceinline.h"
#include "ruby/internal/attr/nonnull.h"
#include "ruby/internal/attr/noreturn.h"
#include "ruby/internal/config.h"
#include "ruby/internal/dllexport.h"
#include "ruby/internal/has/attribute.h"
#include "ruby/internal/intern/array.h" /* rb_ary_new_from_values */
#include "ruby/internal/intern/error.h" /* rb_error_arity */
#include "ruby/internal/intern/hash.h" /* rb_hash_dup */
#include "ruby/internal/intern/proc.h" /* rb_block_proc */
#include "ruby/internal/iterator.h" /* rb_block_given_p / rb_keyword_given_p */
#include "ruby/internal/static_assert.h"
#include "ruby/internal/stdbool.h"
#include "ruby/internal/value.h"
/**
* @name Possible values that you should pass to rb_scan_args_kw().
* @{
*/
/** Same behaviour as rb_scan_args(). */
#define RB_SCAN_ARGS_PASS_CALLED_KEYWORDS 0
/** The final argument should be a hash treated as keywords.*/
#define RB_SCAN_ARGS_KEYWORDS 1
/**
* Treat a final argument as keywords if it is a hash, and not as keywords
* otherwise.
*/
#define RB_SCAN_ARGS_LAST_HASH_KEYWORDS 3
/** @} */
/**
* @name Possible values that you should pass to rb_funcallv_kw().
* @{
*/
/** Do not pass keywords. */
#define RB_NO_KEYWORDS 0
/** Pass keywords, final argument should be a hash of keywords. */
#define RB_PASS_KEYWORDS 1
/**
* Pass keywords if current method is called with keywords, useful for argument
* delegation
*/
#define RB_PASS_CALLED_KEYWORDS rb_keyword_given_p()
/** @} */
/**
* @private
*
* @deprecated This macro once was a thing in the old days, but makes no sense
* any longer today. Exists here for backwards compatibility
* only. You can safely forget about it.
*/
#define HAVE_RB_SCAN_ARGS_OPTIONAL_HASH 1
RBIMPL_SYMBOL_EXPORT_BEGIN()
RBIMPL_ATTR_NONNULL((2, 3))
/**
* Retrieves argument from argc and argv to given ::VALUE references according
* to the format string. The format can be described in ABNF as follows:
*
* ```
* scan-arg-spec := param-arg-spec [keyword-arg-spec] [block-arg-spec]
*
* param-arg-spec := pre-arg-spec [post-arg-spec] / post-arg-spec /
* pre-opt-post-arg-spec
* pre-arg-spec := num-of-leading-mandatory-args
* [num-of-optional-args]
* post-arg-spec := sym-for-variable-length-args
* [num-of-trailing-mandatory-args]
* pre-opt-post-arg-spec := num-of-leading-mandatory-args num-of-optional-args
* num-of-trailing-mandatory-args
* keyword-arg-spec := sym-for-keyword-arg
* block-arg-spec := sym-for-block-arg
*
* num-of-leading-mandatory-args := DIGIT ; The number of leading mandatory
* ; arguments
* num-of-optional-args := DIGIT ; The number of optional arguments
* sym-for-variable-length-args := "*" ; Indicates that variable length
* ; arguments are captured as a ruby
* ; array
* num-of-trailing-mandatory-args := DIGIT ; The number of trailing mandatory
* ; arguments
* sym-for-keyword-arg := ":" ; Indicates that keyword argument
* ; captured as a hash.
* ; If keyword arguments are not
* ; provided, returns nil.
* sym-for-block-arg := "&" ; Indicates that an iterator block
* ; should be captured if given
* ```
*
* For example, "12" means that the method requires at least one argument, and
* at most receives three (1+2) arguments. So, the format string must be
* followed by three variable references, which are to be assigned to captured
* arguments. For omitted arguments, variables are set to ::RUBY_Qnil. `NULL`
* can be put in place of a variable reference, which means the corresponding
* captured argument(s) should be just dropped.
*
* The number of given arguments, excluding an option hash or iterator block,
* is returned.
*
* @param[in] argc Length of `argv`.
* @param[in] argv Pointer to the arguments to parse.
* @param[in] fmt Format, in the language described above.
* @param[out] ... Variables to fill in.
* @exception rb_eFatal Malformed `fmt`.
* @exception rb_eArgError Arity mismatch.
* @return Actually parsed number of given arguments.
* @post Each values passed to `argv` is filled into the variadic
* arguments, according to the format.
*/
int rb_scan_args(int argc, const VALUE *argv, const char *fmt, ...);
RBIMPL_ATTR_NONNULL((3, 4))
/**
* Identical to rb_scan_args(), except it also accepts `kw_splat`.
*
* @param[in] kw_splat How to understand the keyword arguments.
* - RB_SCAN_ARGS_PASS_CALLED_KEYWORDS: Same behaviour as rb_scan_args().
* - RB_SCAN_ARGS_KEYWORDS: The final argument is a kwarg.
* - RB_SCAN_ARGS_LAST_HASH_KEYWORDS: The final argument is a kwarg, iff it
* is a hash.
* @param[in] argc Length of `argv`.
* @param[in] argv Pointer to the arguments to parse.
* @param[in] fmt Format, in the language described above.
* @param[out] ... Variables to fill in.
* @exception rb_eFatal Malformed `fmt`.
* @exception rb_eArgError Arity mismatch.
* @return Actually parsed number of given arguments.
* @post Each values passed to `argv` is filled into the variadic
* arguments, according to the format.
*/
int rb_scan_args_kw(int kw_splat, int argc, const VALUE *argv, const char *fmt, ...);
RBIMPL_ATTR_ERROR(("bad scan arg format"))
/**
* @private
*
* This is an implementation detail of rb_scan_args(). People don't use it
* directly.
*/
void rb_scan_args_bad_format(const char*);
RBIMPL_ATTR_ERROR(("variable argument length doesn't match"))
/**
* @private
*
* This is an implementation detail of rb_scan_args(). People don't use it
* directly.
*/
void rb_scan_args_length_mismatch(const char*,int);
RBIMPL_SYMBOL_EXPORT_END()
/** @cond INTERNAL_MACRO */
/* If we could use constexpr the following macros could be inline functions
* ... but sadly we cannot. */
#define rb_scan_args_isdigit(c) (RBIMPL_CAST((unsigned char)((c)-'0'))<10)
#define rb_scan_args_count_end(fmt, ofs, vari) \
((fmt)[ofs] ? -1 : (vari))
#define rb_scan_args_count_block(fmt, ofs, vari) \
((fmt)[ofs]!='&' ? \
rb_scan_args_count_end(fmt, ofs, vari) : \
rb_scan_args_count_end(fmt, (ofs)+1, (vari)+1))
#define rb_scan_args_count_hash(fmt, ofs, vari) \
((fmt)[ofs]!=':' ? \
rb_scan_args_count_block(fmt, ofs, vari) : \
rb_scan_args_count_block(fmt, (ofs)+1, (vari)+1))
#define rb_scan_args_count_trail(fmt, ofs, vari) \
(!rb_scan_args_isdigit((fmt)[ofs]) ? \
rb_scan_args_count_hash(fmt, ofs, vari) : \
rb_scan_args_count_hash(fmt, (ofs)+1, (vari)+((fmt)[ofs]-'0')))
#define rb_scan_args_count_var(fmt, ofs, vari) \
((fmt)[ofs]!='*' ? \
rb_scan_args_count_trail(fmt, ofs, vari) : \
rb_scan_args_count_trail(fmt, (ofs)+1, (vari)+1))
#define rb_scan_args_count_opt(fmt, ofs, vari) \
(!rb_scan_args_isdigit((fmt)[ofs]) ? \
rb_scan_args_count_var(fmt, ofs, vari) : \
rb_scan_args_count_var(fmt, (ofs)+1, (vari)+(fmt)[ofs]-'0'))
#define rb_scan_args_count_lead(fmt, ofs, vari) \
(!rb_scan_args_isdigit((fmt)[ofs]) ? \
rb_scan_args_count_var(fmt, ofs, vari) : \
rb_scan_args_count_opt(fmt, (ofs)+1, (vari)+(fmt)[ofs]-'0'))
#define rb_scan_args_count(fmt) rb_scan_args_count_lead(fmt, 0, 0)
#if RBIMPL_HAS_ATTRIBUTE(diagnose_if)
# /* Assertions done in the attribute. */
# define rb_scan_args_verify(fmt, varc) RBIMPL_ASSERT_NOTHING
#else
# /* At one sight it _seems_ the expressions below could be written using
# * static assertions. The reality is no, they don't. Because fmt is a
# * string literal, any operations against fmt cannot produce the "integer
# * constant expression"s, as defined in ISO/IEC 9899:2018 section 6.6
# * paragraph #6. Static assertions need such integer constant expressions as
# * defined in ISO/IEC 9899:2018 section 6.7.10 paragraph #3.
# *
# * GCC nonetheless constant-folds this into a no-op, though. */
# define rb_scan_args_verify(fmt, varc) \
(sizeof(char[1-2*(rb_scan_args_count(fmt)<0)])!=1 ? \
rb_scan_args_bad_format(fmt) : \
sizeof(char[1-2*(rb_scan_args_count(fmt)!=(varc))])!=1 ? \
rb_scan_args_length_mismatch(fmt, varc) : \
RBIMPL_ASSERT_NOTHING)
#endif
static inline bool
rb_scan_args_keyword_p(int kw_flag, VALUE last)
{
switch (kw_flag) {
case RB_SCAN_ARGS_PASS_CALLED_KEYWORDS:
return !! rb_keyword_given_p();
case RB_SCAN_ARGS_KEYWORDS:
return true;
case RB_SCAN_ARGS_LAST_HASH_KEYWORDS:
return RB_TYPE_P(last, T_HASH);
default:
return false;
}
}
RBIMPL_ATTR_FORCEINLINE()
static bool
rb_scan_args_lead_p(const char *fmt)
{
return rb_scan_args_isdigit(fmt[0]);
}
RBIMPL_ATTR_FORCEINLINE()
static int
rb_scan_args_n_lead(const char *fmt)
{
return (rb_scan_args_lead_p(fmt) ? fmt[0]-'0' : 0);
}
RBIMPL_ATTR_FORCEINLINE()
static bool
rb_scan_args_opt_p(const char *fmt)
{
return (rb_scan_args_lead_p(fmt) && rb_scan_args_isdigit(fmt[1]));
}
RBIMPL_ATTR_FORCEINLINE()
static int
rb_scan_args_n_opt(const char *fmt)
{
return (rb_scan_args_opt_p(fmt) ? fmt[1]-'0' : 0);
}
RBIMPL_ATTR_FORCEINLINE()
static int
rb_scan_args_var_idx(const char *fmt)
{
return (!rb_scan_args_lead_p(fmt) ? 0 : !rb_scan_args_isdigit(fmt[1]) ? 1 : 2);
}
RBIMPL_ATTR_FORCEINLINE()
static bool
rb_scan_args_f_var(const char *fmt)
{
return (fmt[rb_scan_args_var_idx(fmt)]=='*');
}
RBIMPL_ATTR_FORCEINLINE()
static int
rb_scan_args_trail_idx(const char *fmt)
{
const int idx = rb_scan_args_var_idx(fmt);
return idx+(fmt[idx]=='*');
}
RBIMPL_ATTR_FORCEINLINE()
static int
rb_scan_args_n_trail(const char *fmt)
{
const int idx = rb_scan_args_trail_idx(fmt);
return (rb_scan_args_isdigit(fmt[idx]) ? fmt[idx]-'0' : 0);
}
RBIMPL_ATTR_FORCEINLINE()
static int
rb_scan_args_hash_idx(const char *fmt)
{
const int idx = rb_scan_args_trail_idx(fmt);
return idx+rb_scan_args_isdigit(fmt[idx]);
}
RBIMPL_ATTR_FORCEINLINE()
static bool
rb_scan_args_f_hash(const char *fmt)
{
return (fmt[rb_scan_args_hash_idx(fmt)]==':');
}
RBIMPL_ATTR_FORCEINLINE()
static int
rb_scan_args_block_idx(const char *fmt)
{
const int idx = rb_scan_args_hash_idx(fmt);
return idx+(fmt[idx]==':');
}
RBIMPL_ATTR_FORCEINLINE()
static bool
rb_scan_args_f_block(const char *fmt)
{
return (fmt[rb_scan_args_block_idx(fmt)]=='&');
}
# if 0
RBIMPL_ATTR_FORCEINLINE()
static int
rb_scan_args_end_idx(const char *fmt)
{
const int idx = rb_scan_args_block_idx(fmt);
return idx+(fmt[idx]=='&');
}
# endif
/* NOTE: Use `char *fmt` instead of `const char *fmt` because of clang's bug*/
/* https://bugs.llvm.org/show_bug.cgi?id=38095 */
# define rb_scan_args0(argc, argv, fmt, varc, vars) \
rb_scan_args_set(RB_SCAN_ARGS_PASS_CALLED_KEYWORDS, argc, argv, \
rb_scan_args_n_lead(fmt), \
rb_scan_args_n_opt(fmt), \
rb_scan_args_n_trail(fmt), \
rb_scan_args_f_var(fmt), \
rb_scan_args_f_hash(fmt), \
rb_scan_args_f_block(fmt), \
(rb_scan_args_verify(fmt, varc), vars), (char *)fmt, varc)
# define rb_scan_args_kw0(kw_flag, argc, argv, fmt, varc, vars) \
rb_scan_args_set(kw_flag, argc, argv, \
rb_scan_args_n_lead(fmt), \
rb_scan_args_n_opt(fmt), \
rb_scan_args_n_trail(fmt), \
rb_scan_args_f_var(fmt), \
rb_scan_args_f_hash(fmt), \
rb_scan_args_f_block(fmt), \
(rb_scan_args_verify(fmt, varc), vars), (char *)fmt, varc)
RBIMPL_ATTR_FORCEINLINE()
static int
rb_scan_args_set(int kw_flag, int argc, const VALUE *argv,
int n_lead, int n_opt, int n_trail,
bool f_var, bool f_hash, bool f_block,
VALUE *vars[], RB_UNUSED_VAR(const char *fmt), RB_UNUSED_VAR(int varc))
RBIMPL_ATTR_DIAGNOSE_IF(rb_scan_args_count(fmt) < 0, "bad scan arg format", "error")
RBIMPL_ATTR_DIAGNOSE_IF(rb_scan_args_count(fmt) != varc, "variable argument length doesn't match", "error")
{
int i, argi = 0, vari = 0;
VALUE *var, hash = Qnil;
#define rb_scan_args_next_param() vars[vari++]
const int n_mand = n_lead + n_trail;
/* capture an option hash - phase 1: pop from the argv */
if (f_hash && argc > 0) {
VALUE last = argv[argc - 1];
if (rb_scan_args_keyword_p(kw_flag, last)) {
hash = rb_hash_dup(last);
argc--;
}
}
if (argc < n_mand) {
goto argc_error;
}
/* capture leading mandatory arguments */
for (i = 0; i < n_lead; i++) {
var = rb_scan_args_next_param();
if (var) *var = argv[argi];
argi++;
}
/* capture optional arguments */
for (i = 0; i < n_opt; i++) {
var = rb_scan_args_next_param();
if (argi < argc - n_trail) {
if (var) *var = argv[argi];
argi++;
}
else {
if (var) *var = Qnil;
}
}
/* capture variable length arguments */
if (f_var) {
int n_var = argc - argi - n_trail;
var = rb_scan_args_next_param();
if (0 < n_var) {
if (var) *var = rb_ary_new_from_values(n_var, &argv[argi]);
argi += n_var;
}
else {
if (var) *var = rb_ary_new();
}
}
/* capture trailing mandatory arguments */
for (i = 0; i < n_trail; i++) {
var = rb_scan_args_next_param();
if (var) *var = argv[argi];
argi++;
}
/* capture an option hash - phase 2: assignment */
if (f_hash) {
var = rb_scan_args_next_param();
if (var) *var = hash;
}
/* capture iterator block */
if (f_block) {
var = rb_scan_args_next_param();
if (rb_block_given_p()) {
*var = rb_block_proc();
}
else {
*var = Qnil;
}
}
if (argi == argc) {
return argc;
}
argc_error:
rb_error_arity(argc, n_mand, f_var ? UNLIMITED_ARGUMENTS : n_mand + n_opt);
UNREACHABLE_RETURN(-1);
#undef rb_scan_args_next_param
}
/** @endcond */
#if defined(__DOXYGEN__)
# /* don't bother */
#elif ! defined(HAVE_BUILTIN___BUILTIN_CHOOSE_EXPR_CONSTANT_P)
# /* skip */
#elif ! defined(HAVE_VA_ARGS_MACRO)
# /* skip */
#elif ! defined(__OPTIMIZE__)
# /* skip */
#elif defined(HAVE___VA_OPT__)
# define rb_scan_args(argc, argvp, fmt, ...) \
__builtin_choose_expr( \
__builtin_constant_p(fmt), \
rb_scan_args0( \
argc, argvp, fmt, \
(sizeof((VALUE*[]){__VA_ARGS__})/sizeof(VALUE*)), \
((VALUE*[]){__VA_ARGS__})), \
(rb_scan_args)(argc, argvp, fmt __VA_OPT__(, __VA_ARGS__)))
# define rb_scan_args_kw(kw_flag, argc, argvp, fmt, ...) \
__builtin_choose_expr( \
__builtin_constant_p(fmt), \
rb_scan_args_kw0( \
kw_flag, argc, argvp, fmt, \
(sizeof((VALUE*[]){__VA_ARGS__})/sizeof(VALUE*)), \
((VALUE*[]){__VA_ARGS__})), \
(rb_scan_args_kw)(kw_flag, argc, argvp, fmt __VA_OPT__(, __VA_ARGS__)))
#elif defined(__STRICT_ANSI__)
# /* skip */
#elif defined(__GNUC__)
# define rb_scan_args(argc, argvp, fmt, ...) \
__builtin_choose_expr( \
__builtin_constant_p(fmt), \
rb_scan_args0( \
argc, argvp, fmt, \
(sizeof((VALUE*[]){__VA_ARGS__})/sizeof(VALUE*)), \
((VALUE*[]){__VA_ARGS__})), \
(rb_scan_args)(argc, argvp, fmt, __VA_ARGS__))
# define rb_scan_args_kw(kw_flag, argc, argvp, fmt, ...) \
__builtin_choose_expr( \
__builtin_constant_p(fmt), \
rb_scan_args_kw0( \
kw_flag, argc, argvp, fmt, \
(sizeof((VALUE*[]){__VA_ARGS__})/sizeof(VALUE*)), \
((VALUE*[]){__VA_ARGS__})), \
(rb_scan_args_kw)(kw_flag, argc, argvp, fmt, __VA_ARGS__ /**/))
#endif
#endif /* RBIMPL_SCAN_ARGS_H */