Skip to content
This repository has been archived by the owner on Jan 2, 2025. It is now read-only.

Commit

Permalink
Merge pull request #3 from euglena1215/refact-parser-2
Browse files Browse the repository at this point in the history
Change parsing from regular expression to Prism AST
  • Loading branch information
euglena1215 authored Jun 17, 2024
2 parents f8d4c3d + d681ba4 commit 0348017
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 34 deletions.
6 changes: 6 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
AllCops:
TargetRubyVersion: 3.3
NewCops: enable

Style/StringLiterals:
EnforcedStyle: double_quotes
Expand All @@ -17,3 +18,8 @@ Metrics/ClassLength:
Metrics/MethodLength:
Exclude:
- 'test/**/*'
Max: 20

Style/Style/Documentation:
Enabled: false

25 changes: 16 additions & 9 deletions lib/rbs_inline_data/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,29 @@ def run(args)
end
end.parse!(args)

targets = Pathname.glob(args[0]).flat_map do |path|
if path.directory?
Pathname.glob(path.join("**/*.rb").to_s)
get_targets(args[0]).each do |target|
result = Prism.parse_file(target.to_s)
definitions = Parser.parse(result)
Writer.write(definitions, output_path ? (output_path + target).sub_ext(".rbs") : nil)
end
end

private

#:: (String) -> Array[Pathname]
def get_targets(path)
targets = Pathname.glob(path).flat_map do |pathname|
if pathname.directory?
Pathname.glob(pathname.join("**/*.rb").to_s)
else
path
pathname
end
end

targets.sort!
targets.uniq!

targets.each do |target|
result = Prism.parse_file(target.to_s)
definitions = Parser.parse(result)
Writer.write(definitions, output_path ? (output_path + target).sub_ext(".rbs") : nil)
end
targets
end
end
end
67 changes: 44 additions & 23 deletions lib/rbs_inline_data/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,37 @@ class Parser < Prism::Visitor
:field_name, #:: String
:type #:: String
)
# @rbs skip
Comments = Data.define(
:comment_lines #:: Hash[Integer, String]
)
class Comments
MARKER = "#::"

#:: (Array[Prism::Comment]) -> RbsInlineData::Parser::Comments
def self.from_prism_comments(comments)
# @type var comment_lines: Hash[Integer, String]
comment_lines = {}
comments.each do |comment|
sliced = comment.slice
next unless sliced.start_with?(MARKER)

comment_lines[comment.location.start_line] = sliced.sub(MARKER, "").strip
end

new(comment_lines:)
end
end

# @rbs @definitions: Array[RbsInlineData::Parser::TypedDefinition]
# @rbs @surronding_class_or_module: Array[Symbol]
# @rbs @comments: RbsInlineData::Parser::Comments

# rubocop:disable Lint/MissingSuper
#:: (Array[RbsInlineData::Parser::TypedDefinition]) -> void
def initialize(definitions)
#:: (Array[RbsInlineData::Parser::TypedDefinition], RbsInlineData::Parser::Comments) -> void
def initialize(definitions, comments)
@definitions = definitions
@comments = comments
@surronding_class_or_module = []
end
# rubocop:enable Lint/MissingSuper
Expand All @@ -30,7 +53,8 @@ def initialize(definitions)
def self.parse(result)
# @type var definitions: Array[RbsInlineData::Parser::TypedDefinition]
definitions = []
instance = new(definitions)
comments = Comments.from_prism_comments(result.comments)
instance = new(definitions, comments)
result.value.accept(instance)
definitions
end
Expand Down Expand Up @@ -80,30 +104,27 @@ def define_data?(node)

#:: (Prism::ConstantWriteNode) -> RbsInlineData::Parser::TypedDefinition?
def extract_definition(node)
source = node.slice
_, class_name, field_text = source.match(/\A([a-zA-Z0-9]+) ?= ?Data\.define\(([\n\s\w\W]+)\)\z/).to_a
return nil if field_text.nil? || class_name.nil?

class_name = "#{@surronding_class_or_module.join("::")}::#{class_name}"

fields = field_text.split("\n").map(&:strip).reject(&:empty?).map do |str|
case str
when /:(\w+),? #:: ([\w:\[\], ]+)/
[::Regexp.last_match(1), ::Regexp.last_match(2)]
when /:(\w+),?/
[::Regexp.last_match(1), "untyped"]
end
end.compact.map do |field_name, type|
TypedField.new(
field_name:,
type:
)
arguments_node = node.value.arguments
if arguments_node
typed_fields = arguments_node.arguments.map do |sym_node|
return nil unless sym_node.is_a?(Prism::SymbolNode)

TypedField.new(
field_name: sym_node.unescaped,
type: type_of(sym_node)
)
end.compact
end

TypedDefinition.new(
class_name:,
fields:
class_name: "#{@surronding_class_or_module.join("::")}::#{node.name}",
fields: typed_fields || []
)
end

#:: (Prism::SymbolNode) -> String
def type_of(node)
@comments.comment_lines[node.location.start_line] || "untyped"
end
end
end
1 change: 1 addition & 0 deletions rbs_inline_data.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ Gem::Specification.new do |spec|

spec.add_dependency "prism"
spec.add_dependency "rbs"
spec.metadata["rubygems_mfa_required"] = "true"
end
8 changes: 8 additions & 0 deletions sig/generated/data/lib/rbs_inline_data/parser.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ class RbsInlineData::Parser::TypedField
| (**untyped) -> ::RbsInlineData::Parser::TypedField
| ...
end

class RbsInlineData::Parser::Comments
extend Data::_DataClass
attr_reader comment_lines: Hash[Integer, String]
def self.new: (*untyped) -> ::RbsInlineData::Parser::Comments
| (**untyped) -> ::RbsInlineData::Parser::Comments
| ...
end
6 changes: 6 additions & 0 deletions sig/generated/rbs_inline_data/cli.rbs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# Generated from lib/rbs_inline_data/cli.rb with RBS::Inline

module RbsInlineData
# Process executed when running the rbs-inline-data command.
class CLI
# :: (Array[String]) -> void
def run: (Array[String]) -> void

private

# :: (String) -> Array[Pathname]
def get_targets: (String) -> Array[Pathname]
end
end
17 changes: 15 additions & 2 deletions sig/generated/rbs_inline_data/parser.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@

module RbsInlineData
class Parser < Prism::Visitor
class Comments
MARKER: ::String

# :: (Array[Prism::Comment]) -> RbsInlineData::Parser::Comments
def self.from_prism_comments: (Array[Prism::Comment]) -> RbsInlineData::Parser::Comments
end

@definitions: Array[RbsInlineData::Parser::TypedDefinition]

@surronding_class_or_module: Array[Symbol]

# :: (Array[RbsInlineData::Parser::TypedDefinition]) -> void
def initialize: (Array[RbsInlineData::Parser::TypedDefinition]) -> void
@comments: RbsInlineData::Parser::Comments

# rubocop:disable Lint/MissingSuper
# :: (Array[RbsInlineData::Parser::TypedDefinition], RbsInlineData::Parser::Comments) -> void
def initialize: (Array[RbsInlineData::Parser::TypedDefinition], RbsInlineData::Parser::Comments) -> void

# :: (Prism::ParseResult) -> Array[RbsInlineData::Parser::TypedDefinition]
def self.parse: (Prism::ParseResult) -> Array[RbsInlineData::Parser::TypedDefinition]
Expand All @@ -31,5 +41,8 @@ module RbsInlineData

# :: (Prism::ConstantWriteNode) -> RbsInlineData::Parser::TypedDefinition?
def extract_definition: (Prism::ConstantWriteNode) -> RbsInlineData::Parser::TypedDefinition?

# :: (Prism::SymbolNode) -> String
def type_of: (Prism::SymbolNode) -> String
end
end
9 changes: 9 additions & 0 deletions sig/patch.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,13 @@ module Prism
def constant_path: () -> Prism::ConstantReadNode
| ...
end

class Comment
def slice: () -> String
end

class ConstantWriteNode
def value: () -> CallNode
| ...
end
end
13 changes: 13 additions & 0 deletions test/rbs_inline_data/parser_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,18 @@ class A
]
)
end

def test_no_args
definitions = Parser.parse(parse_ruby(<<~RUBY))
class A
B = Data.define
end
RUBY

assert_equal definitions[0], Parser::TypedDefinition.new(
class_name: "A::B",
fields: []
)
end
end
end

0 comments on commit 0348017

Please sign in to comment.