[ruby/prism] Add a reflection API for determining the fields of a node
https://github.com/ruby/prism/commit/f3f9950a74
This commit is contained in:
parent
ee6e591b6a
commit
d186eb36a4
17
lib/prism.rb
17
lib/prism.rb
@ -24,6 +24,7 @@ module Prism
|
|||||||
autoload :NodeInspector, "prism/node_inspector"
|
autoload :NodeInspector, "prism/node_inspector"
|
||||||
autoload :Pack, "prism/pack"
|
autoload :Pack, "prism/pack"
|
||||||
autoload :Pattern, "prism/pattern"
|
autoload :Pattern, "prism/pattern"
|
||||||
|
autoload :Reflection, "prism/reflection"
|
||||||
autoload :Serialize, "prism/serialize"
|
autoload :Serialize, "prism/serialize"
|
||||||
autoload :Translation, "prism/translation"
|
autoload :Translation, "prism/translation"
|
||||||
autoload :Visitor, "prism/visitor"
|
autoload :Visitor, "prism/visitor"
|
||||||
@ -64,22 +65,6 @@ module Prism
|
|||||||
def self.load(source, serialized)
|
def self.load(source, serialized)
|
||||||
Serialize.load(source, serialized)
|
Serialize.load(source, serialized)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
require_relative "prism/node"
|
require_relative "prism/node"
|
||||||
|
@ -286,12 +286,22 @@ module Prism
|
|||||||
LibRubyParser::PrismString.with_string(code) { |string| parse_file_success_common(string, options) }
|
LibRubyParser::PrismString.with_string(code) { |string| parse_file_success_common(string, options) }
|
||||||
end
|
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.
|
# Mirror the Prism.parse_file_success? API by using the serialization API.
|
||||||
def parse_file_success?(filepath, **options)
|
def parse_file_success?(filepath, **options)
|
||||||
options[:filepath] = filepath
|
options[:filepath] = filepath
|
||||||
LibRubyParser::PrismString.with_file(filepath) { |string| parse_file_success_common(string, options) }
|
LibRubyParser::PrismString.with_file(filepath) { |string| parse_file_success_common(string, options) }
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def dump_common(string, options) # :nodoc:
|
def dump_common(string, options) # :nodoc:
|
||||||
|
@ -87,6 +87,7 @@ Gem::Specification.new do |spec|
|
|||||||
"lib/prism/parse_result/newlines.rb",
|
"lib/prism/parse_result/newlines.rb",
|
||||||
"lib/prism/pattern.rb",
|
"lib/prism/pattern.rb",
|
||||||
"lib/prism/polyfill/string.rb",
|
"lib/prism/polyfill/string.rb",
|
||||||
|
"lib/prism/reflection.rb",
|
||||||
"lib/prism/serialize.rb",
|
"lib/prism/serialize.rb",
|
||||||
"lib/prism/translation.rb",
|
"lib/prism/translation.rb",
|
||||||
"lib/prism/translation/parser.rb",
|
"lib/prism/translation/parser.rb",
|
||||||
@ -134,6 +135,7 @@ Gem::Specification.new do |spec|
|
|||||||
"sig/prism/pack.rbs",
|
"sig/prism/pack.rbs",
|
||||||
"sig/prism/parse_result.rbs",
|
"sig/prism/parse_result.rbs",
|
||||||
"sig/prism/pattern.rbs",
|
"sig/prism/pattern.rbs",
|
||||||
|
"sig/prism/reflection.rbs",
|
||||||
"sig/prism/serialize.rbs",
|
"sig/prism/serialize.rbs",
|
||||||
"sig/prism/visitor.rbs",
|
"sig/prism/visitor.rbs",
|
||||||
"rbi/prism.rbi",
|
"rbi/prism.rbi",
|
||||||
@ -143,6 +145,7 @@ Gem::Specification.new do |spec|
|
|||||||
"rbi/prism/node_ext.rbi",
|
"rbi/prism/node_ext.rbi",
|
||||||
"rbi/prism/node.rbi",
|
"rbi/prism/node.rbi",
|
||||||
"rbi/prism/parse_result.rbi",
|
"rbi/prism/parse_result.rbi",
|
||||||
|
"rbi/prism/reflection.rbi",
|
||||||
"rbi/prism/translation/parser/compiler.rbi",
|
"rbi/prism/translation/parser/compiler.rbi",
|
||||||
"rbi/prism/translation/ripper.rbi",
|
"rbi/prism/translation/ripper.rbi",
|
||||||
"rbi/prism/translation/ripper/ripper_compiler.rbi",
|
"rbi/prism/translation/ripper/ripper_compiler.rbi",
|
||||||
|
@ -969,7 +969,7 @@ parse_input_success_p(pm_string_t *input, const pm_options_t *options) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* call-seq:
|
* 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
|
* Parse the given string and return true if it parses without errors. For
|
||||||
* supported options, see Prism::parse.
|
* supported options, see Prism::parse.
|
||||||
@ -989,7 +989,19 @@ parse_success_p(int argc, VALUE *argv, VALUE self) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* call-seq:
|
* 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
|
* Parse the given file and return true if it parses without errors. For
|
||||||
* supported options, see Prism::parse.
|
* supported options, see Prism::parse.
|
||||||
@ -1008,6 +1020,18 @@ parse_file_success_p(int argc, VALUE *argv, VALUE self) {
|
|||||||
return result;
|
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 */
|
/* 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", parse_lex, -1);
|
||||||
rb_define_singleton_method(rb_cPrism, "parse_lex_file", parse_lex_file, -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_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_success?", parse_file_success_p, -1);
|
||||||
|
rb_define_singleton_method(rb_cPrism, "parse_file_failure?", parse_file_failure_p, -1);
|
||||||
|
|
||||||
#ifndef PRISM_EXCLUDE_SERIALIZATION
|
#ifndef PRISM_EXCLUDE_SERIALIZATION
|
||||||
rb_define_singleton_method(rb_cPrism, "dump", dump, -1);
|
rb_define_singleton_method(rb_cPrism, "dump", dump, -1);
|
||||||
|
@ -60,6 +60,17 @@ module Prism
|
|||||||
DotVisitor.new.tap { |visitor| accept(visitor) }.to_dot
|
DotVisitor.new.tap { |visitor| accept(visitor) }.to_dot
|
||||||
end
|
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
|
# :section: Node interface
|
||||||
# These methods are effectively abstract methods that must be implemented by
|
# These methods are effectively abstract methods that must be implemented by
|
||||||
@ -102,6 +113,11 @@ module Prism
|
|||||||
def inspect(inspector = NodeInspector.new)
|
def inspect(inspector = NodeInspector.new)
|
||||||
raise NoMethodError, "undefined method `inspect' for #{inspect}"
|
raise NoMethodError, "undefined method `inspect' for #{inspect}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns the type of the node as a symbol.
|
||||||
|
def self.type
|
||||||
|
raise NoMethodError, "undefined method `type' for #{inspect}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
<%- nodes.each do |node| -%>
|
<%- nodes.each do |node| -%>
|
||||||
|
|
||||||
|
151
prism/templates/lib/prism/reflection.rb.erb
Normal file
151
prism/templates/lib/prism/reflection.rb.erb
Normal file
@ -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
|
@ -631,6 +631,7 @@ module Prism
|
|||||||
"lib/prism/dsl.rb",
|
"lib/prism/dsl.rb",
|
||||||
"lib/prism/mutation_compiler.rb",
|
"lib/prism/mutation_compiler.rb",
|
||||||
"lib/prism/node.rb",
|
"lib/prism/node.rb",
|
||||||
|
"lib/prism/reflection.rb",
|
||||||
"lib/prism/serialize.rb",
|
"lib/prism/serialize.rb",
|
||||||
"lib/prism/visitor.rb",
|
"lib/prism/visitor.rb",
|
||||||
"src/diagnostic.c",
|
"src/diagnostic.c",
|
||||||
|
22
test/prism/reflection_test.rb
Normal file
22
test/prism/reflection_test.rb
Normal file
@ -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
|
Loading…
x
Reference in New Issue
Block a user