summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/prism.rb17
-rw-r--r--lib/prism/ffi.rb10
-rw-r--r--lib/prism/prism.gemspec3
-rw-r--r--prism/extension.c30
-rw-r--r--prism/templates/lib/prism/node.rb.erb16
-rw-r--r--prism/templates/lib/prism/reflection.rb.erb151
-rwxr-xr-xprism/templates/template.rb1
-rw-r--r--test/prism/reflection_test.rb22
8 files changed, 232 insertions, 18 deletions
diff --git a/lib/prism.rb b/lib/prism.rb
index 23a72fa49a..c512cb4015 100644
--- a/lib/prism.rb
+++ b/lib/prism.rb
@@ -24,6 +24,7 @@ module Prism
autoload :NodeInspector, "prism/node_inspector"
autoload :Pack, "prism/pack"
autoload :Pattern, "prism/pattern"
+ autoload :Reflection, "prism/reflection"
autoload :Serialize, "prism/serialize"
autoload :Translation, "prism/translation"
autoload :Visitor, "prism/visitor"
@@ -64,22 +65,6 @@ 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 parses with errors.
- 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 parses with errors.
- 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 0a064a5c94..1fd053f902 100644
--- a/lib/prism/ffi.rb
+++ b/lib/prism/ffi.rb
@@ -286,12 +286,22 @@ module Prism
LibRubyParser::PrismString.with_string(code) { |string| parse_file_success_common(string, options) }
end
+ # Mirror the Prism.parse_failure? API by using the serialization API.
+ def parse_failure?(code, **options)
+ !parse_success?(code, **options)
+ end
+
# Mirror the Prism.parse_file_success? API by using the serialization API.
def parse_file_success?(filepath, **options)
options[:filepath] = filepath
LibRubyParser::PrismString.with_file(filepath) { |string| parse_file_success_common(string, options) }
end
+ # Mirror the Prism.parse_file_failure? API by using the serialization API.
+ def parse_file_failure?(filepath, **options)
+ !parse_file_success?(filepath, **options)
+ end
+
private
def dump_common(string, options) # :nodoc:
diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec
index f4be1b36bb..c0f3db5594 100644
--- a/lib/prism/prism.gemspec
+++ b/lib/prism/prism.gemspec
@@ -87,6 +87,7 @@ Gem::Specification.new do |spec|
"lib/prism/parse_result/newlines.rb",
"lib/prism/pattern.rb",
"lib/prism/polyfill/string.rb",
+ "lib/prism/reflection.rb",
"lib/prism/serialize.rb",
"lib/prism/translation.rb",
"lib/prism/translation/parser.rb",
@@ -134,6 +135,7 @@ Gem::Specification.new do |spec|
"sig/prism/pack.rbs",
"sig/prism/parse_result.rbs",
"sig/prism/pattern.rbs",
+ "sig/prism/reflection.rbs",
"sig/prism/serialize.rbs",
"sig/prism/visitor.rbs",
"rbi/prism.rbi",
@@ -143,6 +145,7 @@ Gem::Specification.new do |spec|
"rbi/prism/node_ext.rbi",
"rbi/prism/node.rbi",
"rbi/prism/parse_result.rbi",
+ "rbi/prism/reflection.rbi",
"rbi/prism/translation/parser/compiler.rbi",
"rbi/prism/translation/ripper.rbi",
"rbi/prism/translation/ripper/ripper_compiler.rbi",
diff --git a/prism/extension.c b/prism/extension.c
index 27e799a8da..807c8f69dc 100644
--- a/prism/extension.c
+++ b/prism/extension.c
@@ -969,7 +969,7 @@ parse_input_success_p(pm_string_t *input, const pm_options_t *options) {
/**
* call-seq:
- * Prism::parse_success?(source, **options) -> Array
+ * Prism::parse_success?(source, **options) -> bool
*
* Parse the given string and return true if it parses without errors. For
* supported options, see Prism::parse.
@@ -989,7 +989,19 @@ parse_success_p(int argc, VALUE *argv, VALUE self) {
/**
* call-seq:
- * Prism::parse_file_success?(filepath, **options) -> Array
+ * Prism::parse_failure?(source, **options) -> bool
+ *
+ * Parse the given string and return true if it parses with errors. For
+ * supported options, see Prism::parse.
+ */
+static VALUE
+parse_failure_p(int argc, VALUE *argv, VALUE self) {
+ return RTEST(parse_success_p(argc, argv, self)) ? Qfalse : Qtrue;
+}
+
+/**
+ * call-seq:
+ * Prism::parse_file_success?(filepath, **options) -> bool
*
* Parse the given file and return true if it parses without errors. For
* supported options, see Prism::parse.
@@ -1008,6 +1020,18 @@ parse_file_success_p(int argc, VALUE *argv, VALUE self) {
return result;
}
+/**
+ * call-seq:
+ * Prism::parse_file_failure?(filepath, **options) -> bool
+ *
+ * Parse the given file and return true if it parses with errors. For
+ * supported options, see Prism::parse.
+ */
+static VALUE
+parse_file_failure_p(int argc, VALUE *argv, VALUE self) {
+ return RTEST(parse_file_success_p(argc, argv, self)) ? Qfalse : Qtrue;
+}
+
/******************************************************************************/
/* Utility functions exposed to make testing easier */
/******************************************************************************/
@@ -1366,7 +1390,9 @@ Init_prism(void) {
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_failure?", parse_failure_p, -1);
rb_define_singleton_method(rb_cPrism, "parse_file_success?", parse_file_success_p, -1);
+ rb_define_singleton_method(rb_cPrism, "parse_file_failure?", parse_file_failure_p, -1);
#ifndef PRISM_EXCLUDE_SERIALIZATION
rb_define_singleton_method(rb_cPrism, "dump", dump, -1);
diff --git a/prism/templates/lib/prism/node.rb.erb b/prism/templates/lib/prism/node.rb.erb
index 12a984e5a2..6b5a285315 100644
--- a/prism/templates/lib/prism/node.rb.erb
+++ b/prism/templates/lib/prism/node.rb.erb
@@ -60,6 +60,17 @@ module Prism
DotVisitor.new.tap { |visitor| accept(visitor) }.to_dot
end
+ # Returns a list of the fields that exist for this node class. Fields
+ # describe the structure of the node. This kind of reflection is useful for
+ # things like recursively visiting each node _and_ field in the tree.
+ def self.fields
+ # This method should only be called on subclasses of Node, not Node
+ # itself.
+ raise NoMethodError, "undefined method `fields' for #{inspect}" if self == Node
+
+ Reflection.fields_for(self)
+ end
+
# --------------------------------------------------------------------------
# :section: Node interface
# These methods are effectively abstract methods that must be implemented by
@@ -102,6 +113,11 @@ module Prism
def inspect(inspector = NodeInspector.new)
raise NoMethodError, "undefined method `inspect' for #{inspect}"
end
+
+ # Returns the type of the node as a symbol.
+ def self.type
+ raise NoMethodError, "undefined method `type' for #{inspect}"
+ end
end
<%- nodes.each do |node| -%>
diff --git a/prism/templates/lib/prism/reflection.rb.erb b/prism/templates/lib/prism/reflection.rb.erb
new file mode 100644
index 0000000000..13d1da33e8
--- /dev/null
+++ b/prism/templates/lib/prism/reflection.rb.erb
@@ -0,0 +1,151 @@
+module Prism
+ # The Reflection module provides the ability to reflect on the structure of
+ # the syntax tree itself, as opposed to looking at a single syntax tree. This
+ # is useful in metaprogramming contexts.
+ module Reflection
+ # A field represents a single piece of data on a node. It is the base class
+ # for all other field types.
+ class Field
+ # The name of the field.
+ attr_reader :name
+
+ # Initializes the field with the given name.
+ def initialize(name)
+ @name = name
+ end
+ end
+
+ # A node field represents a single child node in the syntax tree. It
+ # resolves to a Prism::Node in Ruby.
+ class NodeField < Field
+ end
+
+ # An optional node field represents a single child node in the syntax tree
+ # that may or may not be present. It resolves to either a Prism::Node or nil
+ # in Ruby.
+ class OptionalNodeField < Field
+ end
+
+ # A node list field represents a list of child nodes in the syntax tree. It
+ # resolves to an array of Prism::Node instances in Ruby.
+ class NodeListField < Field
+ end
+
+ # A constant field represents a constant value on a node. Effectively, it
+ # represents an identifier found within the source. It resolves to a symbol
+ # in Ruby.
+ class ConstantField < Field
+ end
+
+ # An optional constant field represents a constant value on a node that may
+ # or may not be present. It resolves to either a symbol or nil in Ruby.
+ class OptionalConstantField < Field
+ end
+
+ # A constant list field represents a list of constant values on a node. It
+ # resolves to an array of symbols in Ruby.
+ class ConstantListField < Field
+ end
+
+ # A string field represents a string value on a node. It almost always
+ # represents the unescaped value of a string-like literal. It resolves to a
+ # string in Ruby.
+ class StringField < Field
+ end
+
+ # A location field represents the location of some part of the node in the
+ # source code. For example, the location of a keyword or an operator. It
+ # resolves to a Prism::Location in Ruby.
+ class LocationField < Field
+ end
+
+ # An optional location field represents the location of some part of the
+ # node in the source code that may or may not be present. It resolves to
+ # either a Prism::Location or nil in Ruby.
+ class OptionalLocationField < Field
+ end
+
+ # A uint8 field represents an unsigned 8-bit integer value on a node. It
+ # resolves to an Integer in Ruby.
+ class UInt8Field < Field
+ end
+
+ # A uint32 field represents an unsigned 32-bit integer value on a node. It
+ # resolves to an Integer in Ruby.
+ class UInt32Field < Field
+ end
+
+ # A flags field represents a bitset of flags on a node. It resolves to an
+ # integer in Ruby. Note that the flags cannot be accessed directly on the
+ # node because the integer is kept private. Instead, the various flags in
+ # the bitset should be accessed through their query methods.
+ class FlagsField < Field
+ # The names of the flags in the bitset.
+ attr_reader :flags
+
+ # Initializes the flags field with the given name and flags.
+ def initialize(name, flags)
+ super(name)
+ @flags = flags
+ end
+ end
+
+ # An integer field represents an arbitrarily-sized integer value. It is used
+ # exclusively to represent the value of an integer literal. It resolves to
+ # an Integer in Ruby.
+ class IntegerField < Field
+ end
+
+ # A double field represents a double-precision floating point value. It is
+ # used exclusively to represent the value of a floating point literal. It
+ # resolves to a Float in Ruby.
+ class DoubleField < Field
+ end
+
+ # Returns the fields for the given node.
+ def self.fields_for(node)
+ case node.type
+ <%- nodes.each do |node| -%>
+ when :<%= node.human %>
+ [<%= node.fields.map { |field|
+ case field
+ when Prism::Template::NodeField
+ "NodeField.new(:#{field.name})"
+ when Prism::Template::OptionalNodeField
+ "OptionalNodeField.new(:#{field.name})"
+ when Prism::Template::NodeListField
+ "NodeListField.new(:#{field.name})"
+ when Prism::Template::ConstantField
+ "ConstantField.new(:#{field.name})"
+ when Prism::Template::OptionalConstantField
+ "OptionalConstantField.new(:#{field.name})"
+ when Prism::Template::ConstantListField
+ "ConstantListField.new(:#{field.name})"
+ when Prism::Template::StringField
+ "StringField.new(:#{field.name})"
+ when Prism::Template::LocationField
+ "LocationField.new(:#{field.name})"
+ when Prism::Template::OptionalLocationField
+ "OptionalLocationField.new(:#{field.name})"
+ when Prism::Template::UInt8Field
+ "UInt8Field.new(:#{field.name})"
+ when Prism::Template::UInt32Field
+ "UInt32Field.new(:#{field.name})"
+ when Prism::Template::FlagsField
+ found = flags.find { |flag| flag.name == field.kind }.tap { |found| raise "Expected to find #{field.kind}" unless found }
+ "FlagsField.new(:#{field.name}, [#{found.values.map { |value| ":#{value.name.downcase}?" }.join(", ")}])"
+ when Prism::Template::IntegerField
+ "IntegerField.new(:#{field.name})"
+ when Prism::Template::DoubleField
+ "DoubleField.new(:#{field.name})"
+ else
+ raise field.class.name
+ end
+ }.join(", ") %>]
+ <%- end -%>
+ else
+ raise "Unknown node type: #{node.type.inspect}"
+ end
+ end
+ end
+end
diff --git a/prism/templates/template.rb b/prism/templates/template.rb
index 31257ef1a1..d0ce6c6643 100755
--- a/prism/templates/template.rb
+++ b/prism/templates/template.rb
@@ -631,6 +631,7 @@ module Prism
"lib/prism/dsl.rb",
"lib/prism/mutation_compiler.rb",
"lib/prism/node.rb",
+ "lib/prism/reflection.rb",
"lib/prism/serialize.rb",
"lib/prism/visitor.rb",
"src/diagnostic.c",
diff --git a/test/prism/reflection_test.rb b/test/prism/reflection_test.rb
new file mode 100644
index 0000000000..869b68b1f8
--- /dev/null
+++ b/test/prism/reflection_test.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require_relative "test_helper"
+
+module Prism
+ class ReflectionTest < TestCase
+ def test_fields_for
+ fields = Reflection.fields_for(CallNode)
+ methods = CallNode.instance_methods(false)
+
+ fields.each do |field|
+ if field.is_a?(Reflection::FlagsField)
+ field.flags.each do |flag|
+ assert_includes methods, flag
+ end
+ else
+ assert_includes methods, field.name
+ end
+ end
+ end
+ end
+end