Skip to content

Commit 465b332

Browse files
committed
Documentation for search
1 parent bfa8c39 commit 465b332

File tree

3 files changed

+50
-18
lines changed

3 files changed

+50
-18
lines changed

README.md

+24
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ It is built with only standard library dependencies. It additionally ships with
1515
- [CLI](#cli)
1616
- [ast](#ast)
1717
- [check](#check)
18+
- [expr](#expr)
1819
- [format](#format)
1920
- [json](#json)
2021
- [match](#match)
@@ -26,6 +27,7 @@ It is built with only standard library dependencies. It additionally ships with
2627
- [SyntaxTree.read(filepath)](#syntaxtreereadfilepath)
2728
- [SyntaxTree.parse(source)](#syntaxtreeparsesource)
2829
- [SyntaxTree.format(source)](#syntaxtreeformatsource)
30+
- [SyntaxTree.search(source, query, &block)](#syntaxtreesearchsource-query-block)
2931
- [Nodes](#nodes)
3032
- [child_nodes](#child_nodes)
3133
- [Pattern matching](#pattern-matching)
@@ -129,6 +131,24 @@ To change the print width that you are checking against, specify the `--print-wi
129131
stree check --print-width=100 path/to/file.rb
130132
```
131133

134+
### expr
135+
136+
This command will output a Ruby case-match expression that would match correctly against the first expression of the input.
137+
138+
```sh
139+
stree expr path/to/file.rb
140+
```
141+
142+
For a file that contains `1 + 1`, you will receive:
143+
144+
```ruby
145+
SyntaxTree::Binary[
146+
left: SyntaxTree::Int[value: "1"],
147+
operator: :+,
148+
right: SyntaxTree::Int[value: "1"]
149+
]
150+
```
151+
132152
### format
133153

134154
This command will output the formatted version of each of the listed files. Importantly, it will not write that content back to the source files. It is meant to display the formatted version only.
@@ -312,6 +332,10 @@ This function takes an input string containing Ruby code and returns the syntax
312332

313333
This function takes an input string containing Ruby code, parses it into its underlying syntax tree, and formats it back out to a string. You can optionally pass a second argument to this method as well that is the maximum width to print. It defaults to `80`.
314334

335+
### SyntaxTree.search(source, query, &block)
336+
337+
This function takes an input string containing Ruby code, an input string containing a valid Ruby `in` clause expression that can be used to match against nodes in the tree (can be generated using `stree expr`, `stree match`, or `Node#construct_keys`), and a block. Each node that matches the given query will be yielded to the block. The block will receive the node as its only argument.
338+
315339
## Nodes
316340

317341
There are many different node types in the syntax tree. They are meant to be treated as immutable structs containing links to child nodes with minimal logic contained within their implementation. However, for the most part they all respond to a certain set of APIs, listed below.

lib/syntax_tree.rb

+6
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,10 @@ def self.read(filepath)
7575

7676
File.read(filepath, encoding: encoding)
7777
end
78+
79+
# Searches through the given source using the given pattern and yields each
80+
# node in the tree that matches the pattern to the given block.
81+
def self.search(source, query, &block)
82+
Search.new(Pattern.new(query).compile).scan(parse(source), &block)
83+
end
7884
end

lib/syntax_tree/pattern.rb

+20-18
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def compile
7676
def combine_and(left, right)
7777
->(node) { left.call(node) && right.call(node) }
7878
end
79-
79+
8080
def combine_or(left, right)
8181
->(node) { left.call(node) || right.call(node) }
8282
end
@@ -85,18 +85,18 @@ def compile_node(node)
8585
case node
8686
in AryPtn[constant:, requireds:, rest: nil, posts: []]
8787
compiled_constant = compile_node(constant) if constant
88-
88+
8989
preprocessed = requireds.map { |required| compile_node(required) }
90-
90+
9191
compiled_requireds = ->(node) do
9292
deconstructed = node.deconstruct
93-
93+
9494
deconstructed.length == preprocessed.length &&
95-
preprocessed.zip(deconstructed).all? do |(matcher, value)|
96-
matcher.call(value)
97-
end
95+
preprocessed
96+
.zip(deconstructed)
97+
.all? { |(matcher, value)| matcher.call(value) }
9898
end
99-
99+
100100
if compiled_constant
101101
combine_and(compiled_constant, compiled_requireds)
102102
else
@@ -106,55 +106,57 @@ def compile_node(node)
106106
combine_or(compile_node(left), compile_node(right))
107107
in Const[value:] if SyntaxTree.const_defined?(value)
108108
clazz = SyntaxTree.const_get(value)
109-
109+
110110
->(node) { node.is_a?(clazz) }
111111
in Const[value:] if Object.const_defined?(value)
112112
clazz = Object.const_get(value)
113-
113+
114114
->(node) { node.is_a?(clazz) }
115-
in ConstPathRef[parent: VarRef[value: Const[value: "SyntaxTree"]], constant:]
115+
in ConstPathRef[
116+
parent: VarRef[value: Const[value: "SyntaxTree"]], constant:
117+
]
116118
compile_node(constant)
117119
in DynaSymbol[parts: []]
118120
symbol = "".to_sym
119121

120122
->(node) { node == symbol }
121123
in DynaSymbol[parts: [TStringContent[value:]]]
122124
symbol = value.to_sym
123-
125+
124126
->(attribute) { attribute == value }
125127
in HshPtn[constant:, keywords:, keyword_rest: nil]
126128
compiled_constant = compile_node(constant)
127-
129+
128130
preprocessed =
129131
keywords.to_h do |keyword, value|
130132
raise NoMatchingPatternError unless keyword.is_a?(Label)
131133
[keyword.value.chomp(":").to_sym, compile_node(value)]
132134
end
133-
135+
134136
compiled_keywords = ->(node) do
135137
deconstructed = node.deconstruct_keys(preprocessed.keys)
136-
138+
137139
preprocessed.all? do |keyword, matcher|
138140
matcher.call(deconstructed[keyword])
139141
end
140142
end
141-
143+
142144
if compiled_constant
143145
combine_and(compiled_constant, compiled_keywords)
144146
else
145147
compiled_keywords
146148
end
147149
in RegexpLiteral[parts: [TStringContent[value:]]]
148150
regexp = /#{value}/
149-
151+
150152
->(attribute) { regexp.match?(attribute) }
151153
in StringLiteral[parts: []]
152154
->(attribute) { attribute == "" }
153155
in StringLiteral[parts: [TStringContent[value:]]]
154156
->(attribute) { attribute == value }
155157
in SymbolLiteral[value:]
156158
symbol = value.value.to_sym
157-
159+
158160
->(attribute) { attribute == symbol }
159161
in VarRef[value: Const => value]
160162
compile_node(value)

0 commit comments

Comments
 (0)