1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

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; return obj;
} }
static VALUE rb_ast_parse_str(VALUE str, 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); static VALUE rb_ast_parse_file(VALUE path, VALUE keep_script_lines, VALUE error_tolerant);
static VALUE static VALUE
ast_parse_new(void) ast_parse_new(void)
@ -85,31 +85,32 @@ ast_parse_done(rb_ast_t *ast)
} }
static VALUE 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 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; rb_ast_t *ast = 0;
StringValue(str); StringValue(str);
VALUE vparser = ast_parse_new(); VALUE vparser = ast_parse_new();
if (RTEST(keep_script_lines)) rb_parser_keep_script_lines(vparser); 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); ast = rb_parser_compile_string_path(vparser, Qnil, str, 1);
return ast_parse_done(ast); return ast_parse_done(ast);
} }
static VALUE 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 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; VALUE f;
rb_ast_t *ast = 0; 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("-")); rb_funcall(f, rb_intern("set_encoding"), 2, rb_enc_from_encoding(enc), rb_str_new_cstr("-"));
VALUE vparser = ast_parse_new(); VALUE vparser = ast_parse_new();
if (RTEST(keep_script_lines)) rb_parser_keep_script_lines(vparser); 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); ast = rb_parser_compile_file_path(vparser, Qnil, f, 1);
rb_io_close(f); rb_io_close(f);
return ast_parse_done(ast); return ast_parse_done(ast);
@ -139,13 +141,14 @@ lex_array(VALUE array, int index)
} }
static VALUE 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; rb_ast_t *ast = 0;
array = rb_check_array_type(array); array = rb_check_array_type(array);
VALUE vparser = ast_parse_new(); VALUE vparser = ast_parse_new();
if (RTEST(keep_script_lines)) rb_parser_keep_script_lines(vparser); 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); ast = rb_parser_compile_generic(vparser, lex_array, Qnil, array, 1);
return ast_parse_done(ast); return ast_parse_done(ast);
} }
@ -193,7 +196,7 @@ script_lines(VALUE path)
} }
static VALUE 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; VALUE node, lines = Qnil;
const rb_iseq_t *iseq; 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))) { 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) { 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 { 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); 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.parse("x = 1 + 2")
# # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-1:9> # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-1:9>
def self.parse string, keep_script_lines: false def self.parse string, keep_script_lines: false, error_tolerant: false
Primitive.ast_s_parse string, keep_script_lines Primitive.ast_s_parse string, keep_script_lines, error_tolerant
end end
# call-seq: # call-seq:
@ -44,8 +44,8 @@ module RubyVM::AbstractSyntaxTree
# #
# RubyVM::AbstractSyntaxTree.parse_file("my-app/app.rb") # RubyVM::AbstractSyntaxTree.parse_file("my-app/app.rb")
# # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-31:3> # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-31:3>
def self.parse_file pathname, keep_script_lines: false def self.parse_file pathname, keep_script_lines: false, error_tolerant: false
Primitive.ast_s_parse_file pathname, keep_script_lines Primitive.ast_s_parse_file pathname, keep_script_lines, error_tolerant
end end
# call-seq: # call-seq:
@ -63,8 +63,8 @@ module RubyVM::AbstractSyntaxTree
# #
# RubyVM::AbstractSyntaxTree.of(method(:hello)) # RubyVM::AbstractSyntaxTree.of(method(:hello))
# # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-3:3> # # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-3:3>
def self.of body, keep_script_lines: false def self.of body, keep_script_lines: false, error_tolerant: false
Primitive.ast_s_of body, keep_script_lines Primitive.ast_s_of body, keep_script_lines, error_tolerant
end end
# RubyVM::AbstractSyntaxTree::Node instances are created by parse methods in # 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); VALUE rb_parser_set_yydebug(VALUE, VALUE);
void *rb_parser_load_file(VALUE parser, VALUE name); void *rb_parser_load_file(VALUE parser, VALUE name);
void rb_parser_keep_script_lines(VALUE vparser); void rb_parser_keep_script_lines(VALUE vparser);
void rb_parser_error_tolerant(VALUE vparser);
RUBY_SYMBOL_EXPORT_BEGIN RUBY_SYMBOL_EXPORT_BEGIN
VALUE rb_parser_set_context(VALUE, const struct rb_iseq_struct *, int); VALUE rb_parser_set_context(VALUE, const struct rb_iseq_struct *, int);

13
parse.y
View file

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

View file

@ -565,4 +565,17 @@ dummy
assert_in_out_err(["-e", "def foo; end; pp RubyVM::AbstractSyntaxTree.of(method(:foo)).type"], assert_in_out_err(["-e", "def foo; end; pp RubyVM::AbstractSyntaxTree.of(method(:foo)).type"],
"", [":SCOPE"], []) "", [":SCOPE"], [])
end 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 end