Add error_tolerant option to RubyVM::AST

If this option is enabled, SyntaxError is not raised and Node is
returned even if passed script is broken.

[Feature #19013]
This commit is contained in:
yui-knk 2022-09-25 17:53:44 +09:00 committed by Yuichiro Kaneko
parent 7775d14356
commit fbbdbdd891
Notes: git 2022-10-08 17:59:35 +09:00
5 changed files with 50 additions and 20 deletions

29
ast.c
View File

@ -64,8 +64,8 @@ ast_new_internal(rb_ast_t *ast, const NODE *node)
return obj;
}
static VALUE rb_ast_parse_str(VALUE str, VALUE keep_script_lines);
static VALUE rb_ast_parse_file(VALUE path, VALUE keep_script_lines);
static VALUE rb_ast_parse_str(VALUE str, VALUE keep_script_lines, VALUE error_tolerant);
static VALUE rb_ast_parse_file(VALUE path, VALUE keep_script_lines, VALUE error_tolerant);
static VALUE
ast_parse_new(void)
@ -85,31 +85,32 @@ ast_parse_done(rb_ast_t *ast)
}
static VALUE
ast_s_parse(rb_execution_context_t *ec, VALUE module, VALUE str, VALUE keep_script_lines)
ast_s_parse(rb_execution_context_t *ec, VALUE module, VALUE str, VALUE keep_script_lines, VALUE error_tolerant)
{
return rb_ast_parse_str(str, keep_script_lines);
return rb_ast_parse_str(str, keep_script_lines, error_tolerant);
}
static VALUE
rb_ast_parse_str(VALUE str, VALUE keep_script_lines)
rb_ast_parse_str(VALUE str, VALUE keep_script_lines, VALUE error_tolerant)
{
rb_ast_t *ast = 0;
StringValue(str);
VALUE vparser = ast_parse_new();
if (RTEST(keep_script_lines)) rb_parser_keep_script_lines(vparser);
if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser);
ast = rb_parser_compile_string_path(vparser, Qnil, str, 1);
return ast_parse_done(ast);
}
static VALUE
ast_s_parse_file(rb_execution_context_t *ec, VALUE module, VALUE path, VALUE keep_script_lines)
ast_s_parse_file(rb_execution_context_t *ec, VALUE module, VALUE path, VALUE keep_script_lines, VALUE error_tolerant)
{
return rb_ast_parse_file(path, keep_script_lines);
return rb_ast_parse_file(path, keep_script_lines, error_tolerant);
}
static VALUE
rb_ast_parse_file(VALUE path, VALUE keep_script_lines)
rb_ast_parse_file(VALUE path, VALUE keep_script_lines, VALUE error_tolerant)
{
VALUE f;
rb_ast_t *ast = 0;
@ -120,6 +121,7 @@ rb_ast_parse_file(VALUE path, VALUE keep_script_lines)
rb_funcall(f, rb_intern("set_encoding"), 2, rb_enc_from_encoding(enc), rb_str_new_cstr("-"));
VALUE vparser = ast_parse_new();
if (RTEST(keep_script_lines)) rb_parser_keep_script_lines(vparser);
if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser);
ast = rb_parser_compile_file_path(vparser, Qnil, f, 1);
rb_io_close(f);
return ast_parse_done(ast);
@ -139,13 +141,14 @@ lex_array(VALUE array, int index)
}
static VALUE
rb_ast_parse_array(VALUE array, VALUE keep_script_lines)
rb_ast_parse_array(VALUE array, VALUE keep_script_lines, VALUE error_tolerant)
{
rb_ast_t *ast = 0;
array = rb_check_array_type(array);
VALUE vparser = ast_parse_new();
if (RTEST(keep_script_lines)) rb_parser_keep_script_lines(vparser);
if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser);
ast = rb_parser_compile_generic(vparser, lex_array, Qnil, array, 1);
return ast_parse_done(ast);
}
@ -193,7 +196,7 @@ script_lines(VALUE path)
}
static VALUE
ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE keep_script_lines)
ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE keep_script_lines, VALUE error_tolerant)
{
VALUE node, lines = Qnil;
const rb_iseq_t *iseq;
@ -232,13 +235,13 @@ ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE keep_script
}
if (!NIL_P(lines) || !NIL_P(lines = script_lines(path))) {
node = rb_ast_parse_array(lines, keep_script_lines);
node = rb_ast_parse_array(lines, keep_script_lines, error_tolerant);
}
else if (e_option) {
node = rb_ast_parse_str(rb_e_script, keep_script_lines);
node = rb_ast_parse_str(rb_e_script, keep_script_lines, error_tolerant);
}
else {
node = rb_ast_parse_file(path, keep_script_lines);
node = rb_ast_parse_file(path, keep_script_lines, error_tolerant);
}
return node_find(node, node_id);

12
ast.rb
View File

@ -29,8 +29,8 @@ module RubyVM::AbstractSyntaxTree
#
# RubyVM::AbstractSyntaxTree.parse("x = 1 + 2")
# # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-1:9>
def self.parse string, keep_script_lines: false
Primitive.ast_s_parse string, keep_script_lines
def self.parse string, keep_script_lines: false, error_tolerant: false
Primitive.ast_s_parse string, keep_script_lines, error_tolerant
end
# call-seq:
@ -44,8 +44,8 @@ module RubyVM::AbstractSyntaxTree
#
# RubyVM::AbstractSyntaxTree.parse_file("my-app/app.rb")
# # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-31:3>
def self.parse_file pathname, keep_script_lines: false
Primitive.ast_s_parse_file pathname, keep_script_lines
def self.parse_file pathname, keep_script_lines: false, error_tolerant: false
Primitive.ast_s_parse_file pathname, keep_script_lines, error_tolerant
end
# call-seq:
@ -63,8 +63,8 @@ module RubyVM::AbstractSyntaxTree
#
# RubyVM::AbstractSyntaxTree.of(method(:hello))
# # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-3:3>
def self.of body, keep_script_lines: false
Primitive.ast_s_of body, keep_script_lines
def self.of body, keep_script_lines: false, error_tolerant: false
Primitive.ast_s_of body, keep_script_lines, error_tolerant
end
# RubyVM::AbstractSyntaxTree::Node instances are created by parse methods in

View File

@ -15,6 +15,7 @@ struct rb_iseq_struct; /* in vm_core.h */
VALUE rb_parser_set_yydebug(VALUE, VALUE);
void *rb_parser_load_file(VALUE parser, VALUE name);
void rb_parser_keep_script_lines(VALUE vparser);
void rb_parser_error_tolerant(VALUE vparser);
RUBY_SYMBOL_EXPORT_BEGIN
VALUE rb_parser_set_context(VALUE, const struct rb_iseq_struct *, int);

15
parse.y
View File

@ -348,6 +348,7 @@ struct parser_params {
unsigned int do_chomp: 1;
unsigned int do_split: 1;
unsigned int keep_script_lines: 1;
unsigned int error_tolerant: 1;
NODE *eval_tree_begin;
NODE *eval_tree;
@ -6384,7 +6385,9 @@ yycompile0(VALUE arg)
mesg = rb_class_new_instance(0, 0, rb_eSyntaxError);
}
rb_set_errinfo(mesg);
return FALSE;
if (!p->error_tolerant) {
return FALSE;
}
}
tree = p->eval_tree;
if (!tree) {
@ -13313,6 +13316,16 @@ rb_parser_keep_script_lines(VALUE vparser)
TypedData_Get_Struct(vparser, struct parser_params, &parser_data_type, p);
p->keep_script_lines = 1;
}
void
rb_parser_error_tolerant(VALUE vparser)
{
struct parser_params *p;
TypedData_Get_Struct(vparser, struct parser_params, &parser_data_type, p);
p->error_tolerant = 1;
}
#endif
#ifdef RIPPER

View File

@ -565,4 +565,17 @@ dummy
assert_in_out_err(["-e", "def foo; end; pp RubyVM::AbstractSyntaxTree.of(method(:foo)).type"],
"", [":SCOPE"], [])
end
def test_error_tolerant
node = RubyVM::AbstractSyntaxTree.parse(<<~STR, error_tolerant: true)
class A
def m
if;
a = 10
end
end
STR
assert_equal(:SCOPE, node.type)
end
end