[Feature #20329] Separate additional flags from main dump options

Additional flags are comma separated list preceeded by `-` or `+`.

Before:
```sh
$ ruby --dump=insns+without_opt
```

After:
```sh
$ ruby --dump=insns-opt,-optimize
```

At the same time, `parsetree_with_comment` is split to `parsetree`
option and additional `comment` flag.

Before:
```sh
$ ruby --dump=parsetree_with_comment
```

After:
```sh
$ ruby --dump=parsetree,+comment
```

Also flags can be separate `--dump`.
```sh
$ ruby --dump=parsetree --dump=+comment --dump=+error_tolerant
```

Ineffective flags are ignored silently.
```sh
$ ruby --dump=parsetree --dump=+comment --dump=+error_tolerant
```
This commit is contained in:
Nobuyoshi Nakada 2024-03-18 23:22:30 +09:00
parent 9b5d4274a2
commit df8f1f78f0
No known key found for this signature in database
GPG Key ID: 3582D74E1FEE4465
2 changed files with 72 additions and 44 deletions

101
ruby.c
View File

@ -155,20 +155,15 @@ enum feature_flag_bits {
/* END OF DUMPS */ /* END OF DUMPS */
enum dump_flag_bits { enum dump_flag_bits {
dump_version_v, dump_version_v,
dump_error_tolerant, dump_opt_error_tolerant,
dump_with_comment, dump_opt_comment,
dump_without_opt, dump_opt_optimize,
EACH_DUMPS(DEFINE_DUMP, COMMA), EACH_DUMPS(DEFINE_DUMP, COMMA),
dump_error_tolerant_bits = (DUMP_BIT(yydebug) |
DUMP_BIT(parsetree)),
dump_with_comment_bits = (DUMP_BIT(yydebug) |
DUMP_BIT(parsetree)),
dump_without_opt_bits = (DUMP_BIT(insns)),
dump_exit_bits = (DUMP_BIT(yydebug) | DUMP_BIT(syntax) | dump_exit_bits = (DUMP_BIT(yydebug) | DUMP_BIT(syntax) |
DUMP_BIT(parsetree) | DUMP_BIT(insns)), DUMP_BIT(parsetree) | DUMP_BIT(insns)),
dump_optional_bits = (DUMP_BIT(error_tolerant) | dump_optional_bits = (DUMP_BIT(opt_error_tolerant) |
DUMP_BIT(with_comment) | DUMP_BIT(opt_comment) |
DUMP_BIT(without_opt)) DUMP_BIT(opt_optimize))
}; };
static inline void static inline void
@ -224,6 +219,7 @@ cmdline_options_init(ruby_cmdline_options_t *opt)
#elif defined(YJIT_FORCE_ENABLE) #elif defined(YJIT_FORCE_ENABLE)
opt->features.set |= FEATURE_BIT(yjit); opt->features.set |= FEATURE_BIT(yjit);
#endif #endif
opt->dump |= DUMP_BIT(opt_optimize);
opt->backtrace_length_limit = LONG_MIN; opt->backtrace_length_limit = LONG_MIN;
return opt; return opt;
@ -374,9 +370,12 @@ usage(const char *name, int help, int highlight, int columns)
M("-y", ", --yydebug", "Print parser log; backward compatibility not guaranteed."), M("-y", ", --yydebug", "Print parser log; backward compatibility not guaranteed."),
}; };
static const struct ruby_opt_message dumps[] = { static const struct ruby_opt_message dumps[] = {
M("insns(+without-opt)", "", "Instruction sequences."), M("insns", "", "Instruction sequences."),
M("yydebug(+error-tolerant)", "", "yydebug of yacc parser generator."), M("yydebug", "", "yydebug of yacc parser generator."),
M("parsetree(+error-tolerant, +with_comment)", "", "Abstract syntax tree (AST)."), M("parsetree", "", "Abstract syntax tree (AST)."),
M("-optimize", "", "Disable optimization (affects insns)."),
M("+error-tolerant", "", "Error-tolerant parsing (affects yydebug, parsetree)."),
M("+comment", "", "Add comments to AST (affects parsetree)."),
}; };
static const struct ruby_opt_message features[] = { static const struct ruby_opt_message features[] = {
M("gems", "", "Rubygems (only for debugging, default: "DEFAULT_RUBYGEMS_ENABLED")."), M("gems", "", "Rubygems (only for debugging, default: "DEFAULT_RUBYGEMS_ENABLED")."),
@ -978,7 +977,7 @@ name_match_p(const char *name, const char *str, size_t len)
if (*str != '-' && *str != '_') return 0; if (*str != '-' && *str != '_') return 0;
while (ISALNUM(*name)) name++; while (ISALNUM(*name)) name++;
if (*name != '-' && *name != '_') return 0; if (*name != '-' && *name != '_') return 0;
++name; if (!*++name) return 1;
++str; ++str;
if (--len == 0) return 1; if (--len == 0) return 1;
} }
@ -1098,31 +1097,45 @@ memtermspn(const char *str, char term, int len)
static const char additional_opt_sep = '+'; static const char additional_opt_sep = '+';
static unsigned int static unsigned int
dump_additional_option(const char *str, int len, unsigned int bits, const char *name) dump_additional_option_flag(const char *str, int len, unsigned int bits, bool set)
{
#define SET_DUMP_OPT(bit) if (NAME_MATCH_P(#bit, str, len)) { \
return set ? (bits | DUMP_BIT(opt_ ## bit)) : (bits & ~DUMP_BIT(opt_ ## bit)); \
}
SET_DUMP_OPT(error_tolerant);
SET_DUMP_OPT(comment);
SET_DUMP_OPT(optimize);
#undef SET_DUMP_OPT
rb_warn("don't know how to dump with%s '%.*s'", set ? "" : "out", len, str);
return bits;
}
static unsigned int
dump_additional_option(const char *str, int len, unsigned int bits)
{ {
int w; int w;
for (; len-- > 0 && *str++ == additional_opt_sep; len -= w, str += w) { for (; len-- > 0 && *str++ == additional_opt_sep; len -= w, str += w) {
w = memtermspn(str, additional_opt_sep, len); w = memtermspn(str, additional_opt_sep, len);
#define SET_ADDITIONAL_BIT(bit) { \ bool set = true;
if (bits & DUMP_BIT(bit)) \ if (*str == '-' || *str == '+') {
rb_warn("duplicate option to dump %s: '%.*s'", name, w, str); \ set = *str++ == '+';
bits |= DUMP_BIT(bit); \ --w;
continue; \
} }
#define SET_ADDITIONAL(bit) if (NAME_MATCH_P(#bit, str, w)) SET_ADDITIONAL_BIT(bit) else {
#define SET_ADDITIONAL_2(bit, alias) \ int n = memtermspn(str, '-', w);
if (NAME_MATCH_P(#bit, str, w) || NAME_MATCH_P(#alias, str, w)) SET_ADDITIONAL_BIT(bit) if (str[n] == '-') {
if (NAME_MATCH_P("with", str, n)) {
if (dump_error_tolerant_bits & bits) { str += n;
SET_ADDITIONAL(error_tolerant); w -= n;
}
else if (NAME_MATCH_P("without", str, n)) {
set = false;
str += n;
w -= n;
}
}
} }
if (dump_with_comment_bits & bits) { bits = dump_additional_option_flag(str, w, bits, set);
SET_ADDITIONAL_2(with_comment, comment);
}
if (dump_without_opt_bits & bits) {
SET_ADDITIONAL(without_opt);
}
rb_warn("don't know how to dump %s with '%.*s'", name, w, str);
} }
return bits; return bits;
} }
@ -1131,12 +1144,17 @@ static void
dump_option(const char *str, int len, void *arg) dump_option(const char *str, int len, void *arg)
{ {
static const char list[] = EACH_DUMPS(LITERAL_NAME_ELEMENT, ", "); static const char list[] = EACH_DUMPS(LITERAL_NAME_ELEMENT, ", ");
unsigned int *bits_ptr = (unsigned int *)arg;
if (*str == '+' || *str == '-') {
bool set = *str++ == '+';
*bits_ptr = dump_additional_option_flag(str, --len, *bits_ptr, set);
return;
}
int w = memtermspn(str, additional_opt_sep, len); int w = memtermspn(str, additional_opt_sep, len);
#define SET_WHEN_DUMP(bit) \ #define SET_WHEN_DUMP(bit) \
if (NAME_MATCH_P(#bit, (str), (w))) { \ if (NAME_MATCH_P(#bit "-", (str), (w))) { \
*(unsigned int *)arg |= \ *bits_ptr = dump_additional_option(str + w, len - w, *bits_ptr | DUMP_BIT(bit)); \
dump_additional_option(str + w, len - w, DUMP_BIT(bit), #bit); \
return; \ return; \
} }
EACH_DUMPS(SET_WHEN_DUMP, ;); EACH_DUMPS(SET_WHEN_DUMP, ;);
@ -2059,12 +2077,13 @@ process_script(ruby_cmdline_options_t *opt)
{ {
rb_ast_t *ast; rb_ast_t *ast;
VALUE parser = rb_parser_new(); VALUE parser = rb_parser_new();
const unsigned int dump = opt->dump;
if (opt->dump & DUMP_BIT(yydebug)) { if (dump & DUMP_BIT(yydebug)) {
rb_parser_set_yydebug(parser, Qtrue); rb_parser_set_yydebug(parser, Qtrue);
} }
if (opt->dump & DUMP_BIT(error_tolerant)) { if ((dump & dump_exit_bits) && (dump & DUMP_BIT(opt_error_tolerant))) {
rb_parser_error_tolerant(parser); rb_parser_error_tolerant(parser);
} }
@ -2510,7 +2529,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt)
if (dump & DUMP_BIT(parsetree)) { if (dump & DUMP_BIT(parsetree)) {
VALUE tree; VALUE tree;
if (result.ast) { if (result.ast) {
int comment = opt->dump & DUMP_BIT(with_comment); int comment = opt->dump & DUMP_BIT(opt_comment);
tree = rb_parser_dump_tree(result.ast->body.root, comment); tree = rb_parser_dump_tree(result.ast->body.root, comment);
} }
else { else {
@ -2543,7 +2562,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt)
GetBindingPtr(rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING")), toplevel_binding); GetBindingPtr(rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING")), toplevel_binding);
const struct rb_block *base_block = toplevel_context(toplevel_binding); const struct rb_block *base_block = toplevel_context(toplevel_binding);
const rb_iseq_t *parent = vm_block_iseq(base_block); const rb_iseq_t *parent = vm_block_iseq(base_block);
bool optimize = !(opt->dump & DUMP_BIT(without_opt)); bool optimize = (opt->dump & DUMP_BIT(opt_optimize)) != 0;
if (!result.ast) { if (!result.ast) {
pm_parse_result_t *pm = &result.prism; pm_parse_result_t *pm = &result.prism;

View File

@ -1138,17 +1138,17 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(['-p', '-e', 'sub(/t.*/){"TEST"}'], %[test], %w[TEST], [], bug7157) assert_in_out_err(['-p', '-e', 'sub(/t.*/){"TEST"}'], %[test], %w[TEST], [], bug7157)
end end
def assert_norun_with_rflag(*opt) def assert_norun_with_rflag(*opt, test_stderr: [])
bug10435 = "[ruby-dev:48712] [Bug #10435]: should not run with #{opt} option" bug10435 = "[ruby-dev:48712] [Bug #10435]: should not run with #{opt} option"
stderr = [] stderr = []
Tempfile.create(%w"bug10435- .rb") do |script| Tempfile.create(%w"bug10435- .rb") do |script|
dir, base = File.split(script.path) dir, base = File.split(script.path)
File.write(script, "abort ':run'\n") File.write(script, "abort ':run'\n")
opts = ['-C', dir, '-r', "./#{base}", *opt] opts = ['-C', dir, '-r', "./#{base}", *opt]
_, e = assert_in_out_err([*opts, '-ep'], "", //) _, e = assert_in_out_err([*opts, '-ep'], "", //, test_stderr)
stderr.concat(e) if e stderr.concat(e) if e
stderr << "---" stderr << "---"
_, e = assert_in_out_err([*opts, base], "", //) _, e = assert_in_out_err([*opts, base], "", //, test_stderr)
stderr.concat(e) if e stderr.concat(e) if e
end end
assert_not_include(stderr, ":run", bug10435) assert_not_include(stderr, ":run", bug10435)
@ -1171,6 +1171,15 @@ class TestRubyOptions < Test::Unit::TestCase
assert_norun_with_rflag('--dump=parse+error_tolerant') assert_norun_with_rflag('--dump=parse+error_tolerant')
end end
def test_dump_parsetree_error_tolerant
assert_in_out_err(['--dump=parse', '-e', 'begin'],
"", [], /unexpected end-of-input/, success: false)
assert_in_out_err(['--dump=parse', '--dump=+error_tolerant', '-e', 'begin'],
"", /^# @/, /unexpected end-of-input/, success: true)
assert_in_out_err(['--dump=+error_tolerant', '-e', 'begin p :run'],
"", [], /unexpected end-of-input/, success: false)
end
def test_dump_insns_with_rflag def test_dump_insns_with_rflag
assert_norun_with_rflag('--dump=insns') assert_norun_with_rflag('--dump=insns')
end end