[ruby/prism] Prism.parse_success?(source)

A lot of tools use Ripper/RubyVM::AbstractSyntaxTree to determine
if a source is valid. These tools both create an AST instead of
providing an API that will return a boolean only.

This new API only creates the C structs, but doesn't bother
reifying them into Ruby/the serialization API. Instead it only
returns true/false, which is significantly more efficient.

https://github.com/ruby/prism/commit/7014740118
This commit is contained in:
Kevin Newton 2023-12-01 12:22:01 -05:00 committed by git
parent b77551adee
commit 492c82cb41
6 changed files with 133 additions and 1 deletions

View File

@ -64,6 +64,22 @@ module Prism
def self.load(source, serialized)
Serialize.load(source, serialized)
end
# :call-seq:
# Prism::parse_failure?(source, **options) -> bool
#
# Returns true if the source is invalid Ruby code.
def self.parse_failure?(source, **options)
!parse_success?(source, **options)
end
# :call-seq:
# Prism::parse_file_failure?(filepath, **options) -> bool
#
# Returns true if the file at filepath is invalid Ruby code.
def self.parse_file_failure?(filepath, **options)
!parse_file_success?(filepath, **options)
end
end
require_relative "prism/node"

View File

@ -72,7 +72,8 @@ module Prism
"pm_serialize_parse",
"pm_serialize_parse_comments",
"pm_serialize_lex",
"pm_serialize_parse_lex"
"pm_serialize_parse_lex",
"pm_parse_success_p"
)
load_exported_functions_from(
@ -268,6 +269,18 @@ module Prism
end
end
# Mirror the Prism.parse_success? API by using the serialization API.
def parse_success?(code, **options)
LibRubyParser.pm_parse_success_p(code, code.bytesize, dump_options(options))
end
# Mirror the Prism.parse_file_success? API by using the serialization API.
def parse_file_success?(filepath, **options)
LibRubyParser::PrismString.with(filepath) do |string|
parse_success?(string.read, **options, filepath: filepath)
end
end
private
# Convert the given options into a serialized options string.

View File

@ -798,6 +798,64 @@ parse_lex_file(int argc, VALUE *argv, VALUE self) {
return value;
}
/**
* Parse the given input and return true if it parses without errors or
* warnings.
*/
static VALUE
parse_input_success_p(pm_string_t *input, const pm_options_t *options) {
pm_parser_t parser;
pm_parser_init(&parser, pm_string_source(input), pm_string_length(input), options);
pm_node_t *node = pm_parse(&parser);
pm_node_destroy(&parser, node);
VALUE result = parser.error_list.size == 0 && parser.warning_list.size == 0 ? Qtrue : Qfalse;
pm_parser_free(&parser);
return result;
}
/**
* call-seq:
* Prism::parse_success?(source, **options) -> Array
*
* Parse the given string and return true if it parses without errors or
* warnings. For supported options, see Prism::parse.
*/
static VALUE
parse_success_p(int argc, VALUE *argv, VALUE self) {
pm_string_t input;
pm_options_t options = { 0 };
string_options(argc, argv, &input, &options);
VALUE result = parse_input_success_p(&input, &options);
pm_string_free(&input);
pm_options_free(&options);
return result;
}
/**
* call-seq:
* Prism::parse_file_success?(filepath, **options) -> Array
*
* Parse the given file and return true if it parses without errors or warnings.
* For supported options, see Prism::parse.
*/
static VALUE
parse_file_success_p(int argc, VALUE *argv, VALUE self) {
pm_string_t input;
pm_options_t options = { 0 };
if (!file_options(argc, argv, &input, &options)) return Qnil;
VALUE result = parse_input_success_p(&input, &options);
pm_string_free(&input);
pm_options_free(&options);
return result;
}
/******************************************************************************/
/* Utility functions exposed to make testing easier */
/******************************************************************************/
@ -981,6 +1039,8 @@ Init_prism(void) {
rb_define_singleton_method(rb_cPrism, "parse_file_comments", parse_file_comments, -1);
rb_define_singleton_method(rb_cPrism, "parse_lex", parse_lex, -1);
rb_define_singleton_method(rb_cPrism, "parse_lex_file", parse_lex_file, -1);
rb_define_singleton_method(rb_cPrism, "parse_success?", parse_success_p, -1);
rb_define_singleton_method(rb_cPrism, "parse_file_success?", parse_file_success_p, -1);
// Next, the functions that will be called by the parser to perform various
// internal tasks. We expose these to make them easier to test.

View File

@ -152,6 +152,16 @@ PRISM_EXPORTED_FUNCTION void pm_serialize_lex(pm_buffer_t *buffer, const uint8_t
*/
PRISM_EXPORTED_FUNCTION void pm_serialize_parse_lex(pm_buffer_t *buffer, const uint8_t *source, size_t size, const char *data);
/**
* Parse the source and return true if it parses without errors or warnings.
*
* @param source The source to parse.
* @param size The size of the source.
* @param data The optional data to pass to the parser.
* @return True if the source parses without errors or warnings.
*/
PRISM_EXPORTED_FUNCTION bool pm_parse_success_p(const uint8_t *source, size_t size, const char *data);
/**
* Returns a string representation of the given token type.
*

View File

@ -359,3 +359,24 @@ pm_serialize_parse_lex(pm_buffer_t *buffer, const uint8_t *source, size_t size,
pm_parser_free(&parser);
pm_options_free(&options);
}
/**
* Parse the source and return true if it parses without errors or warnings.
*/
PRISM_EXPORTED_FUNCTION bool
pm_parse_success_p(const uint8_t *source, size_t size, const char *data) {
pm_options_t options = { 0 };
pm_options_read(&options, data);
pm_parser_t parser;
pm_parser_init(&parser, source, size, &options);
pm_node_t *node = pm_parse(&parser);
pm_node_destroy(&parser, node);
bool result = parser.error_list.size == 0 && parser.warning_list.size == 0;
pm_parser_free(&parser);
pm_options_free(&options);
return result;
}

View File

@ -20,6 +20,18 @@ module Prism
assert_equal_nodes ast2, ast3
end
def test_parse_success?
assert Prism.parse_success?("1")
refute Prism.parse_success?("<>")
assert Prism.parse_success?("m //", verbose: false)
refute Prism.parse_success?("m //", verbose: true)
end
def test_parse_file_success?
assert Prism.parse_file_success?(__FILE__)
end
def test_options
assert_equal "", Prism.parse("__FILE__").value.statements.body[0].filepath
assert_equal "foo.rb", Prism.parse("__FILE__", filepath: "foo.rb").value.statements.body[0].filepath