Skip to content

Commit

Permalink
Merge pull request #2709 from ruby/reflection
Browse files Browse the repository at this point in the history
Add a reflection API for determining the fields of a node
  • Loading branch information
kddnewton committed Apr 17, 2024
2 parents b42b9c0 + f3f9950 commit bfb20ab
Show file tree
Hide file tree
Showing 19 changed files with 427 additions and 53 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ out.svg
/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
/sorbet/
Expand Down
1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ A lot of code in prism's repository is templated from a single configuration fil
* `lib/prism/dsl.rb` - for defining the DSL for the nodes in Ruby
* `lib/prism/mutation_compiler.rb` - for defining the mutation compiler for the nodes in Ruby
* `lib/prism/node.rb` - for defining the nodes in Ruby
* `lib/prism/reflection.rb` - for defining the reflection API in Ruby
* `lib/prism/serialize.rb` - for defining how to deserialize the nodes in Ruby
* `lib/prism/visitor.rb` - for defining the visitor interface for the nodes in Ruby
* `src/diagnostic.c` - for defining how to build diagnostics
Expand Down
30 changes: 28 additions & 2 deletions ext/prism/extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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 */
/******************************************************************************/
Expand Down Expand Up @@ -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);
Expand Down
17 changes: 1 addition & 16 deletions lib/prism.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -64,22 +65,6 @@ def self.lex_ripper(source)
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"
Expand Down
10 changes: 10 additions & 0 deletions lib/prism/ffi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -286,12 +286,22 @@ def parse_success?(code, **options)
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:
Expand Down
3 changes: 3 additions & 0 deletions prism.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down
64 changes: 32 additions & 32 deletions rbi/prism.rbi
Original file line number Diff line number Diff line change
@@ -1,57 +1,57 @@
# typed: strict

module Prism
sig { params(source: String, filepath: T.nilable(String), line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(String) }
def self.dump(source, filepath: nil, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(String) }
def self.dump(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(filepath: String, line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(String) }
def self.dump_file(filepath, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(String) }
def self.dump_file(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(source: String, filepath: T.nilable(String), line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(Prism::ParseResult[T::Array[T.untyped]]) }
def self.lex(source, filepath: nil, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseResult[T::Array[T.untyped]]) }
def self.lex(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(filepath: String, line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(Prism::ParseResult[T::Array[T.untyped]]) }
def self.lex_file(filepath, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseResult[T::Array[T.untyped]]) }
def self.lex_file(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(source: String, filepath: T.nilable(String), line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(Prism::ParseResult[T::Array[T.untyped]]) }
def self.lex_compat(source, filepath: nil, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(source: String, options: T::Hash[Symbol, T.untyped]).returns(Prism::ParseResult[T::Array[T.untyped]]) }
def self.lex_compat(source, **options); end

sig { params(source: String).returns(T::Array[T.untyped]) }
def self.lex_ripper(source); end

sig { params(source: String, serialized: String).returns(Prism::ParseResult[Prism::ProgramNode]) }
def self.load(source, serialized); end

sig { params(source: String, filepath: T.nilable(String), line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(Prism::ParseResult[Prism::ProgramNode]) }
def self.parse(source, filepath: nil, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseResult[Prism::ProgramNode]) }
def self.parse(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(filepath: String, line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(Prism::ParseResult[Prism::ProgramNode]) }
def self.parse_file(filepath, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseResult[Prism::ProgramNode]) }
def self.parse_file(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(stream: T.any(IO, StringIO), filepath: T.nilable(String), line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(Prism::ParseResult[Prism::ProgramNode]) }
def self.parse_stream(stream, filepath: nil, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(stream: T.any(IO, StringIO), command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseResult[Prism::ProgramNode]) }
def self.parse_stream(stream, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(source: String, filepath: T.nilable(String), line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(T::Array[Prism::Comment]) }
def self.parse_comments(source, filepath: nil, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(T::Array[Prism::Comment]) }
def self.parse_comments(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(filepath: String, line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(T::Array[Prism::Comment]) }
def self.parse_file_comments(filepath, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(T::Array[Prism::Comment]) }
def self.parse_file_comments(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(source: String, filepath: T.nilable(String), line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(Prism::ParseResult[[Prism::ProgramNode, T::Array[T.untyped]]]) }
def self.parse_lex(source, filepath: nil, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseResult[[Prism::ProgramNode, T::Array[T.untyped]]]) }
def self.parse_lex(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(filepath: String, line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(Prism::ParseResult[[Prism::ProgramNode, T::Array[T.untyped]]]) }
def self.parse_lex_file(filepath, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseResult[[Prism::ProgramNode, T::Array[T.untyped]]]) }
def self.parse_lex_file(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(source: String, filepath: T.nilable(String), line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(T::Boolean) }
def self.parse_success?(source, filepath: nil, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(T::Boolean) }
def self.parse_success?(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(source: String, filepath: T.nilable(String), line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(T::Boolean) }
def self.parse_failure?(source, filepath: nil, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(T::Boolean) }
def self.parse_failure?(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(filepath: String, line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(T::Boolean) }
def self.parse_file_success?(filepath, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(T::Boolean) }
def self.parse_file_success?(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(filepath: String, line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(T::Boolean) }
def self.parse_file_failure?(filepath, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(T::Boolean) }
def self.parse_file_failure?(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end
end

0 comments on commit bfb20ab

Please sign in to comment.