summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Newton <kddnewton@gmail.com>2023-12-01 12:22:01 -0500
committergit <svn-admin@ruby-lang.org>2023-12-01 20:53:34 +0000
commit492c82cb417a92d1941f10b52e77ec0c4b2cc8a6 (patch)
tree3358d814456f46c6adb80921e968d1fb5258ce04
parentb77551adee831302f22dc7d9fdccd597923511c4 (diff)
[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
-rw-r--r--lib/prism.rb16
-rw-r--r--lib/prism/ffi.rb15
-rw-r--r--prism/extension.c60
-rw-r--r--prism/prism.h10
-rw-r--r--prism/templates/src/serialize.c.erb21
-rw-r--r--test/prism/ruby_api_test.rb12
6 files changed, 133 insertions, 1 deletions
diff --git a/lib/prism.rb b/lib/prism.rb
index 909b71d66d..b9e615df6c 100644
--- a/lib/prism.rb
+++ b/lib/prism.rb
@@ -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"
diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb
index 36f1c398de..8324f722a7 100644
--- a/lib/prism/ffi.rb
+++ b/lib/prism/ffi.rb
@@ -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.
diff --git a/prism/extension.c b/prism/extension.c
index cf3f62f6e6..b1b0d52dea 100644
--- a/prism/extension.c
+++ b/prism/extension.c
@@ -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.
diff --git a/prism/prism.h b/prism/prism.h
index ab5811f9ac..590cd74016 100644
--- a/prism/prism.h
+++ b/prism/prism.h
@@ -153,6 +153,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.
*
* @param token_type The token type to convert to a string.
diff --git a/prism/templates/src/serialize.c.erb b/prism/templates/src/serialize.c.erb
index 60cb9ecb3d..8fa70ffb55 100644
--- a/prism/templates/src/serialize.c.erb
+++ b/prism/templates/src/serialize.c.erb
@@ -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;
+}
diff --git a/test/prism/ruby_api_test.rb b/test/prism/ruby_api_test.rb
index 54c5fd28e9..b934c26ff4 100644
--- a/test/prism/ruby_api_test.rb
+++ b/test/prism/ruby_api_test.rb
@@ -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