ast_utils 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/README.md +80 -0
- data/Rakefile +10 -0
- data/ast_utils.gemspec +29 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/ast_utils +7 -0
- data/lib/ast_utils/cli.rb +16 -0
- data/lib/ast_utils/labeling.rb +178 -0
- data/lib/ast_utils/navigation.rb +36 -0
- data/lib/ast_utils/node_helper.rb +30 -0
- data/lib/ast_utils/node_set.rb +55 -0
- data/lib/ast_utils/partial_map.rb +53 -0
- data/lib/ast_utils/scope.rb +187 -0
- data/lib/ast_utils/version.rb +3 -0
- data/lib/ast_utils.rb +15 -0
- metadata +132 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e1090f235aaa912ae5a85844b1e10463faaf584d
|
4
|
+
data.tar.gz: 14426ab5c982587d9e8a2dcc910addf95af061df
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d699fb6c01dd21bdb32a4c95d4786167aa4a8581ef49c1facab6ee2e1fbd885f2e36dcdb6e71e8d8e20e686d75fa9bb7265c96c4896541a43e3e28a770c7fb7b
|
7
|
+
data.tar.gz: f8b6a0131c03bb38afcaa03cca9724afba47f6af3e0127aa8b8dac838d884eb6a9d0172b65fe98908cf916c8e0f384f3a6d676edc4df4a98d04b4228c785c0b9
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# ASTUtils
|
2
|
+
|
3
|
+
ASTUtils provides some utility over parser gem AST, which aims to help analyzing Ruby programs.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'ast_utils'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install ast_utils
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### ASTUtils::Labeling
|
24
|
+
|
25
|
+
`ASTUTils::Labeling` implements local variables labeling.
|
26
|
+
|
27
|
+
In Ruby, local variable scope can be nested, and identification on local variables is not trivial.
|
28
|
+
This utility give labels to identify local variables.
|
29
|
+
|
30
|
+
```rb
|
31
|
+
require "ast_utils"
|
32
|
+
|
33
|
+
node = Parser::CurrentRuby.parse(source)
|
34
|
+
labeled = ASTUtils::Labeling.translate(node: node)
|
35
|
+
```
|
36
|
+
|
37
|
+
### ASTUTils::Navigation
|
38
|
+
|
39
|
+
`AST` has `children` but no pointer to its parent.
|
40
|
+
|
41
|
+
```rb
|
42
|
+
require "ast_utils"
|
43
|
+
|
44
|
+
node = Parser::CurrentRuby.parse(source)
|
45
|
+
navigation = ASTUtils::Navigation.from(node: node)
|
46
|
+
|
47
|
+
parent = navigation.parent(child_node)
|
48
|
+
```
|
49
|
+
|
50
|
+
### ASTUtils::Scope
|
51
|
+
|
52
|
+
`Scope` is about *scope* in Ruby.
|
53
|
+
|
54
|
+
It associates outer scope and its inner scope.
|
55
|
+
|
56
|
+
```rb
|
57
|
+
require "ast_utils"
|
58
|
+
|
59
|
+
node = Parser::CurrentRuby.parse(source)
|
60
|
+
labeled = ASTUtils::Labeling.translate(node: node)
|
61
|
+
scope = ASTUtils::Scope.from(node: labeled)
|
62
|
+
|
63
|
+
scope.root
|
64
|
+
scope.children(root)
|
65
|
+
scope.subs(root)
|
66
|
+
```
|
67
|
+
|
68
|
+
`children` includes `def` and iterator block, but `subs` only includes blocks.
|
69
|
+
|
70
|
+
It also defines `assignments` and `references` to refere assignments and references for local variables in the scope.
|
71
|
+
|
72
|
+
`assignments` returns pair of:
|
73
|
+
|
74
|
+
* Node which implements assignment
|
75
|
+
* Labeled local variable name which is assigned
|
76
|
+
|
77
|
+
## Contributing
|
78
|
+
|
79
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/soutaro/ast_utils.
|
80
|
+
|
data/Rakefile
ADDED
data/ast_utils.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ast_utils/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ast_utils"
|
8
|
+
spec.version = ASTUtils::VERSION
|
9
|
+
spec.authors = ["Soutaro Matsumoto"]
|
10
|
+
spec.email = ["matsumoto@soutaro.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Ruby AST Utility}
|
13
|
+
spec.description = %q{Ruby AST Utility}
|
14
|
+
spec.homepage = "https://github.com/soutaro/ast_utils"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
26
|
+
|
27
|
+
spec.add_runtime_dependency "parser", "~> 2.4"
|
28
|
+
spec.add_runtime_dependency "thor", "~> 0.19.4"
|
29
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "ast_utils"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/exe/ast_utils
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "thor"
|
2
|
+
|
3
|
+
module ASTUtils
|
4
|
+
class CLI < Thor
|
5
|
+
desc "label SCRIPTS...", "labeling Ruby scripts given as SCRIPTS..."
|
6
|
+
def label(*scripts)
|
7
|
+
scripts.map {|script| Pathname(script) }.each do |path|
|
8
|
+
puts "Parsing #{path}..."
|
9
|
+
node = Parser::CurrentRuby.parse(path.read, path.to_s)
|
10
|
+
puts "Translating node..."
|
11
|
+
labeled = Labeling.translate(node: node)
|
12
|
+
puts "#{labeled.inspect}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
module ASTUtils
|
2
|
+
class Labeling
|
3
|
+
class LabeledName
|
4
|
+
attr_reader :name
|
5
|
+
attr_reader :label
|
6
|
+
|
7
|
+
def initialize(name:, label:)
|
8
|
+
@name = name
|
9
|
+
@label = label
|
10
|
+
end
|
11
|
+
|
12
|
+
def inspect
|
13
|
+
"#{name}@#{label}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def hash
|
17
|
+
name.hash ^ label.hash
|
18
|
+
end
|
19
|
+
|
20
|
+
def ==(other)
|
21
|
+
other.is_a?(LabeledName) && other.name == name && other.label == label
|
22
|
+
end
|
23
|
+
|
24
|
+
def equal?(other)
|
25
|
+
self == other
|
26
|
+
end
|
27
|
+
|
28
|
+
def eql?(other)
|
29
|
+
self == other
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
include NodeHelper
|
34
|
+
|
35
|
+
attr_accessor :counter
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
self.counter = 0
|
39
|
+
end
|
40
|
+
|
41
|
+
def next_label!
|
42
|
+
self.counter += 1
|
43
|
+
|
44
|
+
new_label = self.counter
|
45
|
+
|
46
|
+
if block_given?
|
47
|
+
yield new_label
|
48
|
+
else
|
49
|
+
new_label
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def translate(node, env)
|
54
|
+
case node.type
|
55
|
+
when :lvasgn
|
56
|
+
children = PartialMap.apply(node.children) do |map|
|
57
|
+
map.on!(0) {|name| lookup_env(name: name, env: env) }
|
58
|
+
map.on?(1) {|child| translate(child, env) }
|
59
|
+
end
|
60
|
+
|
61
|
+
node.updated(nil, children, nil)
|
62
|
+
|
63
|
+
when :lvar
|
64
|
+
children = replace(node.children, 0) {|name| lookup_env(name: name, env: env) }
|
65
|
+
node.updated(nil, children, nil)
|
66
|
+
|
67
|
+
when :arg, :restarg, :kwarg, :kwrestarg, :blockarg
|
68
|
+
name = node.children[0]
|
69
|
+
|
70
|
+
labeled_name = LabeledName.new(name: name, label: next_label!)
|
71
|
+
env[name] = labeled_name
|
72
|
+
|
73
|
+
children = replace(node.children, 0) {|_| labeled_name }
|
74
|
+
node.updated(nil, children, nil)
|
75
|
+
|
76
|
+
when :procarg0
|
77
|
+
case node.children[0]
|
78
|
+
when AST::Node
|
79
|
+
children = map_child_node(node) {|child| translate(child, env) }
|
80
|
+
node.updated(nil, children, nil)
|
81
|
+
when Symbol
|
82
|
+
name = node.children[0]
|
83
|
+
|
84
|
+
labeled_name = LabeledName.new(name: name, label: next_label!)
|
85
|
+
env[name] = labeled_name
|
86
|
+
|
87
|
+
children = replace(node.children, 0) {|_| labeled_name }
|
88
|
+
node.updated(nil, children, nil)
|
89
|
+
else
|
90
|
+
raise "Unexpected node structure: #{node}"
|
91
|
+
end
|
92
|
+
when :optarg, :kwoptarg
|
93
|
+
children = PartialMap.apply(node.children) do |map|
|
94
|
+
map.on!(0) {|name| lookup_env(name: name, env: env) }
|
95
|
+
map.on?(1) {|child| translate(child, env) }
|
96
|
+
end
|
97
|
+
|
98
|
+
node.updated(nil, children, nil)
|
99
|
+
|
100
|
+
when :def
|
101
|
+
env_ = {}
|
102
|
+
children = map_child_node(node) {|child| translate(child, env_) }
|
103
|
+
node.updated(nil, children, nil)
|
104
|
+
|
105
|
+
when :block
|
106
|
+
children = node.children.dup
|
107
|
+
translate_child!(children, 0, env)
|
108
|
+
|
109
|
+
block_env = env.dup
|
110
|
+
translate_child!(children, 1, block_env)
|
111
|
+
translate_child!(children, 2, block_env)
|
112
|
+
|
113
|
+
node.updated(nil, children, nil)
|
114
|
+
|
115
|
+
when :class
|
116
|
+
children = PartialMap.apply(node.children) do |map|
|
117
|
+
map.on!(0) {|child| translate(child, env) }
|
118
|
+
map.on?(1) {|child| translate(child, env) }
|
119
|
+
map.on?(2) {|child| translate(child, {}) }
|
120
|
+
end
|
121
|
+
|
122
|
+
node.updated(nil, children, nil)
|
123
|
+
|
124
|
+
when :module
|
125
|
+
children = PartialMap.apply(node.children) do |map|
|
126
|
+
map.on!(0) {|child| translate(child, env) }
|
127
|
+
map.on?(1) {|child| translate(child, {}) }
|
128
|
+
end
|
129
|
+
|
130
|
+
node.updated(nil, children, nil)
|
131
|
+
|
132
|
+
when :match_with_lvasgn
|
133
|
+
names = self.class.extract_variables(node.children[0].children[0].children[0])
|
134
|
+
vars = names.map {|name| lookup_env(name: name, env: env) }
|
135
|
+
|
136
|
+
children = map_child_node(node) {|child| translate(child, env) }
|
137
|
+
node.updated(nil, children + [vars], nil)
|
138
|
+
|
139
|
+
else
|
140
|
+
children = map_child_node(node) {|child| translate(child, env) }
|
141
|
+
node.updated(nil, children, nil)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def lookup_env(name:, env:)
|
146
|
+
labeled_name = env[name]
|
147
|
+
|
148
|
+
unless labeled_name
|
149
|
+
labeled_name = LabeledName.new(name: name, label: next_label!)
|
150
|
+
env[name] = labeled_name
|
151
|
+
end
|
152
|
+
|
153
|
+
labeled_name
|
154
|
+
end
|
155
|
+
|
156
|
+
def translate_child!(children, index, env)
|
157
|
+
if children[index]
|
158
|
+
children[index] = translate(children[index], env)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def replace(array, index)
|
163
|
+
array = array.dup
|
164
|
+
array[index] = yield(array[index])
|
165
|
+
array
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.extract_variables(string)
|
169
|
+
string.scan(/\(\?(\<([\w_]+)\>)|(\'([\w_]+)\')/).map do |_, b, _, d|
|
170
|
+
(b || d).to_sym
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.translate(node:)
|
175
|
+
self.new.translate(node, {})
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module ASTUtils
|
2
|
+
class Navigation
|
3
|
+
include NodeHelper
|
4
|
+
|
5
|
+
attr_reader :nodes
|
6
|
+
attr_reader :parents
|
7
|
+
attr_reader :root
|
8
|
+
|
9
|
+
def initialize(node:)
|
10
|
+
@root = node
|
11
|
+
@nodes = NodeSet.new
|
12
|
+
@parents = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def construct
|
16
|
+
set_parent(root)
|
17
|
+
end
|
18
|
+
|
19
|
+
def set_parent(node)
|
20
|
+
nodes << node
|
21
|
+
|
22
|
+
each_child_node(node) do |child|
|
23
|
+
parents[child.__id__] = node
|
24
|
+
set_parent(child)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def parent(node)
|
29
|
+
parents[node.__id__]
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.from(node:)
|
33
|
+
new(node: node).tap {|nav| nav.construct }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ASTUtils
|
2
|
+
module NodeHelper
|
3
|
+
def each_child_node(node)
|
4
|
+
if block_given?
|
5
|
+
node.children.each do |child|
|
6
|
+
if child.is_a?(AST::Node)
|
7
|
+
yield child
|
8
|
+
end
|
9
|
+
end
|
10
|
+
else
|
11
|
+
enum_for :each_child_node, node
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# order preserved
|
16
|
+
def map_child_node(node)
|
17
|
+
if block_given?
|
18
|
+
node.children.each.with_object([]) do |child, array|
|
19
|
+
if child.is_a?(AST::Node)
|
20
|
+
array << yield(child)
|
21
|
+
else
|
22
|
+
array << child
|
23
|
+
end
|
24
|
+
end
|
25
|
+
else
|
26
|
+
enum_for :map_child_node, node
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module ASTUtils
|
2
|
+
class NodeSet
|
3
|
+
class Item
|
4
|
+
attr_reader :object
|
5
|
+
|
6
|
+
def initialize(object)
|
7
|
+
@object = object
|
8
|
+
end
|
9
|
+
|
10
|
+
def hash
|
11
|
+
object.__id__
|
12
|
+
end
|
13
|
+
|
14
|
+
def eql?(other)
|
15
|
+
other.is_a?(Item) && other.object.__id__ == object.__id__
|
16
|
+
end
|
17
|
+
|
18
|
+
def ==(other)
|
19
|
+
eql?(other)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :set
|
24
|
+
|
25
|
+
def initialize(objects = [])
|
26
|
+
@set = Set.new(objects.map {|object| Item.new(object) })
|
27
|
+
end
|
28
|
+
|
29
|
+
def <<(node)
|
30
|
+
set << Item.new(node)
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete(node)
|
34
|
+
set.delete Item.new(node)
|
35
|
+
end
|
36
|
+
|
37
|
+
def each(&block)
|
38
|
+
set.map(&:object).each(&block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def empty?
|
42
|
+
size == 0
|
43
|
+
end
|
44
|
+
|
45
|
+
def size
|
46
|
+
set.size
|
47
|
+
end
|
48
|
+
|
49
|
+
include Enumerable
|
50
|
+
|
51
|
+
def +(other)
|
52
|
+
self.class.new(self.set + other.set)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module ASTUtils
|
2
|
+
class PartialMap
|
3
|
+
attr_reader :enumerable
|
4
|
+
attr_reader :updaters
|
5
|
+
|
6
|
+
def initialize(enumerable)
|
7
|
+
@enumerable = enumerable
|
8
|
+
@updaters = Array.new(enumerable.count)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.apply(enumerable)
|
12
|
+
map = new(enumerable)
|
13
|
+
yield map
|
14
|
+
map.apply
|
15
|
+
end
|
16
|
+
|
17
|
+
def on(index, &block)
|
18
|
+
updaters[index] = [:on, block]
|
19
|
+
end
|
20
|
+
|
21
|
+
def on!(index, &block)
|
22
|
+
updaters[index] = [:on!, block]
|
23
|
+
end
|
24
|
+
|
25
|
+
def on?(index, &block)
|
26
|
+
updaters[index] = [:on?, block]
|
27
|
+
end
|
28
|
+
|
29
|
+
def apply
|
30
|
+
enumerable.map.with_index do |value, index|
|
31
|
+
updater = updaters[index]
|
32
|
+
|
33
|
+
if updater
|
34
|
+
case updater&.first
|
35
|
+
when :on
|
36
|
+
updater.last[value]
|
37
|
+
when :on!
|
38
|
+
raise if value.nil?
|
39
|
+
updater.last[value]
|
40
|
+
when :on?
|
41
|
+
unless value.nil?
|
42
|
+
updater.last[value]
|
43
|
+
else
|
44
|
+
value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
else
|
48
|
+
value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
module ASTUtils
|
2
|
+
class Scope
|
3
|
+
class Assignment
|
4
|
+
attr_reader :node, :variable
|
5
|
+
|
6
|
+
def initialize(node:, variable:)
|
7
|
+
@node = node
|
8
|
+
@variable = variable
|
9
|
+
end
|
10
|
+
|
11
|
+
def hash
|
12
|
+
node.__id__ ^ variable.hash
|
13
|
+
end
|
14
|
+
|
15
|
+
def eql?(other)
|
16
|
+
other.is_a?(Assignment) && other.node.__id__ == node.__id__ && other.variable == variable
|
17
|
+
end
|
18
|
+
|
19
|
+
def ==(other)
|
20
|
+
eql?(other)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
include NodeHelper
|
25
|
+
|
26
|
+
attr_reader :root
|
27
|
+
attr_reader :all_scopes
|
28
|
+
attr_reader :child_scopes
|
29
|
+
attr_reader :parent_scopes
|
30
|
+
attr_reader :sub_scopes
|
31
|
+
attr_reader :super_scopes
|
32
|
+
attr_reader :assignment_nodes
|
33
|
+
attr_reader :reference_nodes
|
34
|
+
|
35
|
+
def initialize(root:)
|
36
|
+
@root = root
|
37
|
+
@all_scopes = NodeSet.new
|
38
|
+
@child_scopes = {}
|
39
|
+
@parent_scopes = {}
|
40
|
+
@sub_scopes = {}
|
41
|
+
@super_scopes = {}
|
42
|
+
@assignment_nodes = {}
|
43
|
+
@reference_nodes = {}
|
44
|
+
end
|
45
|
+
|
46
|
+
def children(scope)
|
47
|
+
child_scopes[scope.__id__]
|
48
|
+
end
|
49
|
+
|
50
|
+
def parent(scope)
|
51
|
+
parent_scopes[scope.__id__]
|
52
|
+
end
|
53
|
+
|
54
|
+
def subs(scope)
|
55
|
+
sub_scopes[scope.__id__]
|
56
|
+
end
|
57
|
+
|
58
|
+
def sup(scope)
|
59
|
+
super_scopes[scope.__id__]
|
60
|
+
end
|
61
|
+
|
62
|
+
def assignments(scope, include_subs: false)
|
63
|
+
if include_subs
|
64
|
+
subs(scope).inject(assignment_nodes[scope.__id__]) {|assignments, scope_|
|
65
|
+
assignments + assignments(scope_, include_subst: true)
|
66
|
+
}
|
67
|
+
else
|
68
|
+
assignment_nodes[scope.__id__]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def references(scope, include_subs: false)
|
73
|
+
if include_subs
|
74
|
+
subs(scope).inject(reference_nodes[scope.__id__]) {|references, scope_|
|
75
|
+
references + references(scope_, include_subs: true)
|
76
|
+
}
|
77
|
+
else
|
78
|
+
reference_nodes[scope.__id__]
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
def each(&block)
|
84
|
+
all_scopes.each(&block)
|
85
|
+
end
|
86
|
+
|
87
|
+
def construct
|
88
|
+
if Scope.scope_node?(root)
|
89
|
+
child_scope!(root, nil)
|
90
|
+
else
|
91
|
+
add_scope(root)
|
92
|
+
construct_node(root, root)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def add_scope(scope)
|
97
|
+
all_scopes << scope
|
98
|
+
child_scopes[scope.__id__] = NodeSet.new
|
99
|
+
sub_scopes[scope.__id__] = NodeSet.new
|
100
|
+
assignment_nodes[scope.__id__] = Set.new
|
101
|
+
reference_nodes[scope.__id__] = Set.new
|
102
|
+
end
|
103
|
+
|
104
|
+
def child_scope!(scope, parent_scope)
|
105
|
+
add_scope(scope)
|
106
|
+
|
107
|
+
if parent_scope
|
108
|
+
parent_scopes[scope.__id__] = parent_scope
|
109
|
+
child_scopes[parent_scope.__id__] << scope
|
110
|
+
end
|
111
|
+
|
112
|
+
each_child_node(scope) do |child|
|
113
|
+
construct_node(child, scope)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def nested_scope!(scope, super_scope)
|
118
|
+
add_scope(scope)
|
119
|
+
|
120
|
+
if super_scope
|
121
|
+
parent_scopes[scope.__id__] = super_scope
|
122
|
+
child_scopes[super_scope.__id__] << scope
|
123
|
+
super_scopes[scope.__id__] = super_scope
|
124
|
+
sub_scopes[super_scope.__id__] << scope
|
125
|
+
end
|
126
|
+
|
127
|
+
each_child_node(scope) do |child|
|
128
|
+
construct_node(child, scope)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def construct_node(node, current_scope)
|
133
|
+
case node.type
|
134
|
+
when :class, :module, :def
|
135
|
+
child_scope!(node, current_scope)
|
136
|
+
when :block
|
137
|
+
nested_scope!(node, current_scope)
|
138
|
+
when :lvar
|
139
|
+
reference_nodes[current_scope.__id__] << node
|
140
|
+
when :lvasgn, :arg, :optarg, :restarg, :kwarg, :kwoptarg, :kwrestarg, :blockarg
|
141
|
+
assignment_nodes[current_scope.__id__] << Assignment.new(node: node, variable: node.children[0])
|
142
|
+
construct_children(node, current_scope)
|
143
|
+
when :procarg0
|
144
|
+
case node.children[0]
|
145
|
+
when AST::Node
|
146
|
+
construct_children(node, current_scope)
|
147
|
+
else
|
148
|
+
assignment_nodes[current_scope.__id__] << Assignment.new(node: node, variable: node.children[0])
|
149
|
+
construct_children(node, current_scope)
|
150
|
+
end
|
151
|
+
when :match_with_lvasgn
|
152
|
+
node.children[2].each do |var|
|
153
|
+
assignment_nodes[current_scope.__id__] << Assignment.new(node: node, variable: var)
|
154
|
+
end
|
155
|
+
|
156
|
+
construct_children(node, current_scope)
|
157
|
+
else
|
158
|
+
construct_children(node, current_scope)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def construct_children(node, current_scope)
|
163
|
+
each_child_node(node) do |child|
|
164
|
+
construct_node(child, current_scope)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def valid_scope!(node)
|
169
|
+
unless root.equal?(node) || Scope.scope_node?(node)
|
170
|
+
raise "Invalid scope node given: #{node}"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.from(node:)
|
175
|
+
new(root: node).tap(&:construct)
|
176
|
+
end
|
177
|
+
|
178
|
+
def self.scope_node?(node)
|
179
|
+
case node.type
|
180
|
+
when :class, :module, :def, :block
|
181
|
+
true
|
182
|
+
else
|
183
|
+
false
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
data/lib/ast_utils.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "ast_utils/version"
|
2
|
+
|
3
|
+
require "parser/current"
|
4
|
+
require "pathname"
|
5
|
+
require "set"
|
6
|
+
|
7
|
+
require "ast_utils/node_set"
|
8
|
+
require "ast_utils/node_helper"
|
9
|
+
require "ast_utils/partial_map"
|
10
|
+
require "ast_utils/labeling"
|
11
|
+
require "ast_utils/navigation"
|
12
|
+
require "ast_utils/scope"
|
13
|
+
|
14
|
+
Parser::Builders::Default.emit_lambda = true
|
15
|
+
Parser::Builders::Default.emit_procarg0 = true
|
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ast_utils
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Soutaro Matsumoto
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.13'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.13'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: parser
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.4'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.4'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: thor
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.19.4
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.19.4
|
83
|
+
description: Ruby AST Utility
|
84
|
+
email:
|
85
|
+
- matsumoto@soutaro.com
|
86
|
+
executables:
|
87
|
+
- ast_utils
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- ".travis.yml"
|
93
|
+
- Gemfile
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- ast_utils.gemspec
|
97
|
+
- bin/console
|
98
|
+
- bin/setup
|
99
|
+
- exe/ast_utils
|
100
|
+
- lib/ast_utils.rb
|
101
|
+
- lib/ast_utils/cli.rb
|
102
|
+
- lib/ast_utils/labeling.rb
|
103
|
+
- lib/ast_utils/navigation.rb
|
104
|
+
- lib/ast_utils/node_helper.rb
|
105
|
+
- lib/ast_utils/node_set.rb
|
106
|
+
- lib/ast_utils/partial_map.rb
|
107
|
+
- lib/ast_utils/scope.rb
|
108
|
+
- lib/ast_utils/version.rb
|
109
|
+
homepage: https://github.com/soutaro/ast_utils
|
110
|
+
licenses: []
|
111
|
+
metadata: {}
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 2.6.8
|
129
|
+
signing_key:
|
130
|
+
specification_version: 4
|
131
|
+
summary: Ruby AST Utility
|
132
|
+
test_files: []
|