Skip to content

Commit

Permalink
Add error_tolerant option to RubyVM::AST
Browse files Browse the repository at this point in the history
If this option is enabled, SyntaxError is not raised and Node is
returned even if passed script is broken.

[Feature #19013]
  • Loading branch information
yui-knk committed Oct 8, 2022
1 parent 7775d14 commit fbbdbdd
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 20 deletions.
29 changes: 16 additions & 13 deletions ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -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);
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
12 changes: 6 additions & 6 deletions ast.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -44,8 +44,8 @@ def self.parse string, keep_script_lines: false
#
# 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:
Expand All @@ -63,8 +63,8 @@ def self.parse_file pathname, keep_script_lines: false
#
# 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
Expand Down
1 change: 1 addition & 0 deletions internal/parse.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
15 changes: 14 additions & 1 deletion parse.y
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions test/ruby/test_ast.rb
Original file line number Diff line number Diff line change
Expand Up @@ -565,4 +565,17 @@ def test_e_option
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

0 comments on commit fbbdbdd

Please sign in to comment.