masamune-ast 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +12 -1
- data/README.md +36 -27
- data/lib/masamune/abstract_syntax_tree/assign.rb +11 -0
- data/lib/masamune/abstract_syntax_tree/block_var.rb +22 -0
- data/lib/masamune/abstract_syntax_tree/brace_block.rb +19 -0
- data/lib/masamune/abstract_syntax_tree/call.rb +17 -0
- data/lib/masamune/abstract_syntax_tree/data_node.rb +62 -0
- data/lib/masamune/abstract_syntax_tree/def.rb +17 -0
- data/lib/masamune/abstract_syntax_tree/do_block.rb +19 -0
- data/lib/masamune/abstract_syntax_tree/node.rb +41 -0
- data/lib/masamune/abstract_syntax_tree/params.rb +23 -0
- data/lib/masamune/abstract_syntax_tree/program.rb +16 -0
- data/lib/masamune/abstract_syntax_tree/string_content.rb +17 -0
- data/lib/masamune/abstract_syntax_tree/var_field.rb +17 -0
- data/lib/masamune/abstract_syntax_tree/var_ref.rb +19 -0
- data/lib/masamune/abstract_syntax_tree/vcall.rb +17 -0
- data/lib/masamune/abstract_syntax_tree.rb +120 -0
- data/lib/{masamune-ast → masamune}/lex_node.rb +15 -14
- data/lib/{masamune-ast → masamune}/version.rb +1 -1
- data/lib/masamune.rb +29 -0
- metadata +36 -10
- data/lib/masamune-ast/abstract_syntax_tree/data_node.rb +0 -11
- data/lib/masamune-ast/abstract_syntax_tree/node.rb +0 -16
- data/lib/masamune-ast/abstract_syntax_tree.rb +0 -121
- data/lib/masamune-ast.rb +0 -12
- /data/lib/{masamune-ast → masamune}/slicer.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ee5dca320a96035fcea4f73effbc3fceff2ee531808bf94d99aa21086186ce5
|
4
|
+
data.tar.gz: 3f170766c1e97a15e9c550e61fc5f2048b28b48a5f12e2b4841a31b0dad6ebbb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7650eff09ecb23034c236363ee70a971f003736a967f8efd5ae8f36e68d598f73a4239a38d839f478b4aa6b36662d7114834dedf501acfed6bd2adcb6494890e
|
7
|
+
data.tar.gz: fbd0fc4a505b03dc38304abdfadd9cb31fccf5f746bbb325056c63e9a790d7814bd7292d40c95da7441731cf3ab0613f2e9879c4c27f07f61445370a499665a2
|
data/Gemfile.lock
CHANGED
@@ -1,18 +1,29 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
masamune-ast (1.
|
4
|
+
masamune-ast (1.1.0)
|
5
|
+
activesupport
|
5
6
|
|
6
7
|
GEM
|
7
8
|
remote: https://rubygems.org/
|
8
9
|
specs:
|
10
|
+
activesupport (7.0.4.3)
|
11
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
12
|
+
i18n (>= 1.6, < 2)
|
13
|
+
minitest (>= 5.1)
|
14
|
+
tzinfo (~> 2.0)
|
9
15
|
coderay (1.1.3)
|
16
|
+
concurrent-ruby (1.2.2)
|
17
|
+
i18n (1.13.0)
|
18
|
+
concurrent-ruby (~> 1.0)
|
10
19
|
method_source (1.0.0)
|
11
20
|
minitest (5.18.0)
|
12
21
|
pry (0.14.2)
|
13
22
|
coderay (~> 1.1)
|
14
23
|
method_source (~> 1.0)
|
15
24
|
rake (13.0.6)
|
25
|
+
tzinfo (2.0.6)
|
26
|
+
concurrent-ruby (~> 1.0)
|
16
27
|
|
17
28
|
PLATFORMS
|
18
29
|
x86_64-linux
|
data/README.md
CHANGED
@@ -26,12 +26,22 @@ CODE
|
|
26
26
|
|
27
27
|
msmn = Masamune::AbstractSyntaxTree.new(code)
|
28
28
|
|
29
|
-
# Searching the tree returns the specific node and the line number it's on.
|
30
29
|
msmn.variables
|
31
|
-
#=> [[
|
32
|
-
|
33
|
-
|
34
|
-
#=> [
|
30
|
+
#=> [{:position=>[1, 0], :token=>"java"},
|
31
|
+
#=> {:position=>[2, 0], :token=>"javascript"},
|
32
|
+
#=> {:position=>[2, 13], :token=>"java"},
|
33
|
+
#=> {:position=>[3, 5], :token=>"java"},
|
34
|
+
#=> {:position=>[3, 25], :token=>"javascript"}]
|
35
|
+
|
36
|
+
msmn.strings
|
37
|
+
#=> [{:position=>[1, 8], :token=>"java"},
|
38
|
+
#=> {:position=>[2, 21], :token=>"script"},
|
39
|
+
#=> {:position=>[3, 13], :token=>" is not "}]
|
40
|
+
|
41
|
+
msmn.variables(name: "java")
|
42
|
+
#=> [{position: [1, 0], token: "java"},
|
43
|
+
#=> {position: [2, 13], token: "java"},
|
44
|
+
#=> {position: [3, 5], token: "java"}]
|
35
45
|
|
36
46
|
code = <<CODE
|
37
47
|
ary = [1, 2, 3]
|
@@ -47,43 +57,42 @@ CODE
|
|
47
57
|
|
48
58
|
msmn = Masamune::AbstractSyntaxTree.new(code)
|
49
59
|
|
50
|
-
msmn.
|
51
|
-
#=> [[
|
52
|
-
|
53
|
-
|
54
|
-
#=> [
|
55
|
-
|
56
|
-
|
57
|
-
|
60
|
+
msmn.all_methods
|
61
|
+
#=> [{:position=>[6, 4], :token=>"foo"},
|
62
|
+
#=> {:position=>[2, 4], :token=>"sum"},
|
63
|
+
#=> {:position=>[2, 8], :token=>"times"},
|
64
|
+
#=> {:position=>[8, 0], :token=>"foo"},
|
65
|
+
#=> {:position=>[9, 0], :token=>"foo"}]
|
66
|
+
|
67
|
+
msmn.method_calls
|
68
|
+
#=> [{:position=>[2, 4], :token=>"sum"},
|
69
|
+
#=> {:position=>[2, 8], :token=>"times"},
|
70
|
+
#=> {:position=>[8, 0], :token=>"foo"},
|
71
|
+
#=> {:position=>[9, 0], :token=>"foo"}]
|
72
|
+
|
73
|
+
msmn.method_definitions
|
74
|
+
#=> [{:position=>[6, 4], :token=>"foo"}]
|
58
75
|
```
|
59
76
|
|
60
|
-
In some cases, it can be easier to look at the given lex nodes to analyze your source code:
|
77
|
+
In some cases, it can be easier to look at the given lex nodes to analyze your source code since you can easily see the index and the line position it's on:
|
61
78
|
```ruby
|
62
79
|
msmn.lex_nodes
|
63
80
|
=> [#<Masamune::LexNode:0x00007fd61810cac0 @ast_id=1200, @index=0, @position=[1, 0], @state=CMDARG, @token="java", @type=:ident>,
|
64
81
|
#<Masamune::LexNode:0x00007fd61810c930 @ast_id=1200, @index=1, @position=[1, 4], @state=CMDARG, @token=" ", @type=:sp>,
|
65
82
|
#<Masamune::LexNode:0x00007fd61810c7c8 @ast_id=1200, @index=2, @position=[1, 5], @state=BEG, @token="=", @type=:op>,
|
66
|
-
#<Masamune::LexNode:0x00007fd61810c638 @ast_id=1200, @index=3, @position=[1, 6], @state=BEG, @token=" ", @type=:sp>,
|
67
|
-
#<Masamune::LexNode:0x00007fd61810c480 @ast_id=1200, @index=4, @position=[1, 7], @state=BEG, @token="\"", @type=:tstring_beg>,
|
68
|
-
#<Masamune::LexNode:0x00007fd61810c318 @ast_id=1200, @index=5, @position=[1, 8], @state=BEG, @token="java", @type=:tstring_content>,
|
69
|
-
#<Masamune::LexNode:0x00007fd61810c188 @ast_id=1200, @index=6, @position=[1, 12], @state=END, @token="\"", @type=:tstring_end>,
|
70
|
-
#<Masamune::LexNode:0x00007fd61810c020 @ast_id=1200, @index=7, @position=[1, 13], @state=BEG, @token="\n", @type=:nl>,
|
71
|
-
#<Masamune::LexNode:0x00007fd618113e88 @ast_id=1200, @index=8, @position=[2, 0], @state=CMDARG, @token="javascript", @type=:ident>,
|
72
|
-
#<Masamune::LexNode:0x00007fd618113cf8 @ast_id=1200, @index=9, @position=[2, 10], @state=CMDARG, @token=" ", @type=:sp>,
|
73
|
-
#<Masamune::LexNode:0x00007fd618113b68 @ast_id=1200, @index=10, @position=[2, 11], @state=BEG, @token="=", @type=:op>,
|
74
83
|
…
|
75
84
|
]
|
76
85
|
|
77
|
-
msmn.lex_nodes
|
86
|
+
lex_node = msmn.lex_nodes.first
|
87
|
+
|
88
|
+
lex_node.variable?
|
78
89
|
#=> true
|
79
90
|
|
80
|
-
|
91
|
+
lex_node.string?
|
81
92
|
#=> false
|
82
93
|
|
83
|
-
|
94
|
+
lex_node.method_definition?
|
84
95
|
#=> false
|
85
|
-
|
86
|
-
# etc...
|
87
96
|
```
|
88
97
|
|
89
98
|
## Contributing
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# TODO: Add description.
|
2
|
+
|
3
|
+
module Masamune
|
4
|
+
class AbstractSyntaxTree
|
5
|
+
class BlockVar < Node
|
6
|
+
attr_accessor :ast_id
|
7
|
+
|
8
|
+
def initialize(contents, ast_id)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
# :block_var has a lot of nil values within in.
|
13
|
+
# I'm not sure what this represents, but it would be
|
14
|
+
# nice to find out and implement it document/implement it somewhere.
|
15
|
+
def extract_data_nodes
|
16
|
+
@contents[1][1].map do |content|
|
17
|
+
Masamune::AbstractSyntaxTree::DataNode.new(content, @ast_id)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# TODO: Add description.
|
2
|
+
|
3
|
+
module Masamune
|
4
|
+
class AbstractSyntaxTree
|
5
|
+
class BraceBlock < Node
|
6
|
+
attr_accessor :ast_id
|
7
|
+
|
8
|
+
def initialize(contents, ast_id)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def params
|
13
|
+
# This node should exist already, so we search for it in the ast object.
|
14
|
+
block_var = Masamune::AbstractSyntaxTree::BlockVar.new(contents[1], ast_id)
|
15
|
+
ast.node_list.find {|node| node.contents == block_var.contents}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# TODO: Add description.
|
2
|
+
|
3
|
+
module Masamune
|
4
|
+
class AbstractSyntaxTree
|
5
|
+
class Call < Node
|
6
|
+
def initialize(contents, ast_id)
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def extract_data_nodes
|
11
|
+
[
|
12
|
+
Masamune::AbstractSyntaxTree::DataNode.new(@contents.last, @ast_id)
|
13
|
+
]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# A data node represents an abstraction in the AST which has details about a specific type.
|
2
|
+
# i.e. - [:@ident, "variable_name", [4, 7]]
|
3
|
+
# These values are the `type`, `token`, and `position`, respectively.
|
4
|
+
# It is simliar to what you see in `Ripper.lex(code)` and `Masamune::AbstractSyntaxTree's @lex_nodes`.
|
5
|
+
|
6
|
+
# We break this down into a simpler structure, `position_and_token`,
|
7
|
+
# which looks like this: {position: [4, 7], token: "ruby"}
|
8
|
+
|
9
|
+
module Masamune
|
10
|
+
class AbstractSyntaxTree
|
11
|
+
class DataNode < Node
|
12
|
+
attr_reader :type, :token, :line_position
|
13
|
+
|
14
|
+
def initialize(contents, ast_id)
|
15
|
+
@type, @token, @line_position = contents
|
16
|
+
super(contents, ast_id)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Results here represent the position and token of the
|
20
|
+
# data we're searching in the form of a Hash like the following:
|
21
|
+
# [
|
22
|
+
# {position: [4, 7], token: "ruby"},
|
23
|
+
# {position: [7, 7], token: "rails"}
|
24
|
+
# ]
|
25
|
+
# TODO: Worry about using a faster sorting algorithm later.
|
26
|
+
def self.order_results_by_position(position_and_token_ary)
|
27
|
+
# Extract the line numbers first, i.e - 4 from [4, 7]
|
28
|
+
line_numbers = position_and_token_ary.map do |position_and_token|
|
29
|
+
line_number = position_and_token[:position].first
|
30
|
+
end.uniq.sort
|
31
|
+
|
32
|
+
final_result = []
|
33
|
+
line_numbers.each do |line_number|
|
34
|
+
# Group data together in an array if they're on the same line.
|
35
|
+
shared_line_data = position_and_token_ary.select do |position_and_token|
|
36
|
+
position_and_token[:position].first == line_number
|
37
|
+
end
|
38
|
+
|
39
|
+
# Sort the positions on each line number respectively.
|
40
|
+
positions_on_line = shared_line_data.map do |position_and_token|
|
41
|
+
position_and_token[:position].last
|
42
|
+
end.sort
|
43
|
+
|
44
|
+
# Apply to the final result.
|
45
|
+
positions_on_line.each do |position_on_line|
|
46
|
+
shared_line_data.each do |position_and_token|
|
47
|
+
if position_and_token[:position].last == position_on_line
|
48
|
+
final_result << position_and_token
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
final_result
|
55
|
+
end
|
56
|
+
|
57
|
+
def position_and_token
|
58
|
+
{position: @line_position, token: @token}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# TODO: Add description.
|
2
|
+
|
3
|
+
module Masamune
|
4
|
+
class AbstractSyntaxTree
|
5
|
+
class Def < Node
|
6
|
+
def initialize(contents, ast_id)
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def extract_data_nodes
|
11
|
+
[
|
12
|
+
Masamune::AbstractSyntaxTree::DataNode.new(@contents[1], @ast_id)
|
13
|
+
]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# TODO: Add description.
|
2
|
+
|
3
|
+
module Masamune
|
4
|
+
class AbstractSyntaxTree
|
5
|
+
class DoBlock < Node
|
6
|
+
attr_accessor :ast_id
|
7
|
+
|
8
|
+
def initialize(contents, ast_id)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def params
|
13
|
+
# This node should exist already, so we search for it in the ast object.
|
14
|
+
block_var = Masamune::AbstractSyntaxTree::BlockVar.new(contents[1], ast_id)
|
15
|
+
ast.node_list.find {|node| node.contents == block_var.contents}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# TODO: Add description
|
2
|
+
|
3
|
+
module Masamune
|
4
|
+
class AbstractSyntaxTree
|
5
|
+
class Node
|
6
|
+
attr_reader :ast_id, :contents, :index_stack, :data_nodes
|
7
|
+
|
8
|
+
def initialize(contents, ast_id)
|
9
|
+
@ast_id = ast_id
|
10
|
+
@contents = contents
|
11
|
+
@data_nodes = extract_data_nodes
|
12
|
+
end
|
13
|
+
|
14
|
+
def ast
|
15
|
+
ObjectSpace._id2ref(@ast_id)
|
16
|
+
end
|
17
|
+
|
18
|
+
# TODO: Consider removing the :type and :typeless methods.
|
19
|
+
|
20
|
+
# Ripper's abstract syntax tree nodes are either typed or typeless.
|
21
|
+
# Types consist of values such as :def, :do_block, :var_ref, etc.
|
22
|
+
# Typed nodes house one of these types as its first element,
|
23
|
+
# whereas typeless nodes simply have arrays as top-level elements.
|
24
|
+
# Example of a typed node: [:@tstring_content, "ruby", [4, 7]]
|
25
|
+
def typed?
|
26
|
+
@contents.is_a?(Array) && @contents.first.is_a?(Symbol)
|
27
|
+
end
|
28
|
+
|
29
|
+
def typeless?
|
30
|
+
!typed?
|
31
|
+
end
|
32
|
+
|
33
|
+
# By default, we assume a node has no data nodes to extract.
|
34
|
+
# Because the structure for each node is different, we handle
|
35
|
+
# extraction separately for each node within its respective class.
|
36
|
+
def extract_data_nodes
|
37
|
+
# TODO: Might want to make this [] instead of nil.
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# TODO: Add description.
|
2
|
+
|
3
|
+
module Masamune
|
4
|
+
class AbstractSyntaxTree
|
5
|
+
class Params < Node
|
6
|
+
def initialize(contents, ast_id)
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def extract_data_nodes
|
11
|
+
# TODO: Sometimes the params node looks like this:
|
12
|
+
# [:params, nil, nil, nil, nil, nil, nil, nil]
|
13
|
+
# Add a description for this, and review this portion
|
14
|
+
# to ensure that it's being handled properly.
|
15
|
+
unless @contents[1].nil?
|
16
|
+
@contents[1].map do |content|
|
17
|
+
Masamune::AbstractSyntaxTree::DataNode.new(content, @ast_id)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Program represents the top layer of the AST,
|
2
|
+
# and the second element of the array houses the entire program.
|
3
|
+
|
4
|
+
module Masamune
|
5
|
+
class AbstractSyntaxTree
|
6
|
+
class Program < Node
|
7
|
+
def initialize(contents, ast_id)
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def extract_data_nodes
|
12
|
+
# No data nodes to extract.
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# TODO: Add description.
|
2
|
+
|
3
|
+
module Masamune
|
4
|
+
class AbstractSyntaxTree
|
5
|
+
class StringContent < Node
|
6
|
+
def initialize(contents, ast_id)
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def extract_data_nodes
|
11
|
+
[
|
12
|
+
Masamune::AbstractSyntaxTree::DataNode.new(@contents[1], @ast_id)
|
13
|
+
]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# TODO: Add description.
|
2
|
+
|
3
|
+
module Masamune
|
4
|
+
class AbstractSyntaxTree
|
5
|
+
class VarField < Node
|
6
|
+
def initialize(contents, ast_id)
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def extract_data_nodes
|
11
|
+
[
|
12
|
+
Masamune::AbstractSyntaxTree::DataNode.new(@contents[1], @ast_id)
|
13
|
+
]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# TODO: Add description.
|
2
|
+
|
3
|
+
module Masamune
|
4
|
+
class AbstractSyntaxTree
|
5
|
+
class VarRef < Node
|
6
|
+
attr_accessor :ast_id
|
7
|
+
|
8
|
+
def initialize(contents, ast_id)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def extract_data_nodes
|
13
|
+
[
|
14
|
+
Masamune::AbstractSyntaxTree::DataNode.new(@contents[1], @ast_id)
|
15
|
+
]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# TODO: Add description.
|
2
|
+
|
3
|
+
module Masamune
|
4
|
+
class AbstractSyntaxTree
|
5
|
+
class Vcall < Node
|
6
|
+
def initialize(contents, ast_id)
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def extract_data_nodes
|
11
|
+
[
|
12
|
+
Masamune::AbstractSyntaxTree::DataNode.new(@contents[1], @ast_id)
|
13
|
+
]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Masamune
|
2
|
+
class AbstractSyntaxTree
|
3
|
+
attr_reader :tree
|
4
|
+
attr_accessor :node_list, :data_node_list, :lex_nodes
|
5
|
+
|
6
|
+
def initialize(code)
|
7
|
+
@tree = Ripper.sexp(code)
|
8
|
+
raw_lex_nodes = Ripper.lex(code)
|
9
|
+
@lex_nodes = raw_lex_nodes.map do |lex_node|
|
10
|
+
Masamune::LexNode.new(raw_lex_nodes.index(lex_node), lex_node, self.__id__)
|
11
|
+
end
|
12
|
+
|
13
|
+
@node_list = []
|
14
|
+
@data_node_list = []
|
15
|
+
register_nodes(@tree)
|
16
|
+
end
|
17
|
+
|
18
|
+
def register_nodes(tree_node = self.tree)
|
19
|
+
if tree_node.is_a?(Array)
|
20
|
+
klass = get_node_class(tree_node.first)
|
21
|
+
msmn_node = klass.new(tree_node, self.__id__)
|
22
|
+
else
|
23
|
+
# Create a general node if the node is a single value.
|
24
|
+
msmn_node = Masamune::AbstractSyntaxTree::Node.new(tree_node, self.__id__)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Register nodes and any data nodes housed within it.
|
28
|
+
# See Masamune::AbstractSyntaxTree::DataNode for more details on what a data node is.
|
29
|
+
@node_list << msmn_node
|
30
|
+
msmn_node.data_nodes.each { |dn| @data_node_list << dn } if msmn_node.data_nodes
|
31
|
+
|
32
|
+
# Continue down the tree until base case is reached.
|
33
|
+
if !msmn_node.nil? && msmn_node.contents.is_a?(Array)
|
34
|
+
msmn_node.contents.each { |node| register_nodes(node) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# TODO: Add block_params: true to the arguments.
|
39
|
+
def variables(name: nil)
|
40
|
+
var_classes = [
|
41
|
+
:var_field,
|
42
|
+
:var_ref,
|
43
|
+
:params
|
44
|
+
].map {|type| get_node_class(type)}
|
45
|
+
find_nodes(var_classes, token: name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def strings(content: nil)
|
49
|
+
find_nodes(get_node_class(:string_content), token: content)
|
50
|
+
end
|
51
|
+
|
52
|
+
def method_definitions(name: nil)
|
53
|
+
find_nodes(get_node_class(:def), token: name)
|
54
|
+
end
|
55
|
+
|
56
|
+
def method_calls(name: nil)
|
57
|
+
method_classes = [
|
58
|
+
:vcall,
|
59
|
+
:call
|
60
|
+
].map {|type| get_node_class(type)}
|
61
|
+
find_nodes(method_classes, token: name)
|
62
|
+
end
|
63
|
+
|
64
|
+
# TODO
|
65
|
+
def do_block_params
|
66
|
+
end
|
67
|
+
|
68
|
+
# TODO
|
69
|
+
def brace_block_params
|
70
|
+
end
|
71
|
+
|
72
|
+
def all_methods
|
73
|
+
method_definitions + method_calls
|
74
|
+
end
|
75
|
+
|
76
|
+
def block_params
|
77
|
+
# TODO: do_block_params + brace_block_params
|
78
|
+
find_nodes(get_node_class(:params))
|
79
|
+
end
|
80
|
+
|
81
|
+
# TODO: Create an option to return a list of DataNode class instances.
|
82
|
+
def find_nodes(token_classes, token: nil)
|
83
|
+
# Ensure the classes are in an array
|
84
|
+
token_classes = [token_classes].flatten
|
85
|
+
|
86
|
+
var_nodes = []
|
87
|
+
token_classes.each do |klass|
|
88
|
+
var_nodes << @node_list.select {|node| node.class == klass}
|
89
|
+
end
|
90
|
+
|
91
|
+
# Searching for multiple classes will yield multi-dimensional arrays,
|
92
|
+
# so we ensure everything is flattened out before moving forward.
|
93
|
+
var_nodes.flatten!
|
94
|
+
|
95
|
+
if token
|
96
|
+
var_nodes = var_nodes.select {|node| node.data_nodes.first.token == token}.flatten
|
97
|
+
end
|
98
|
+
|
99
|
+
final_result = []
|
100
|
+
var_nodes.each do |node|
|
101
|
+
node.data_nodes.each {|dn| final_result << dn.position_and_token}
|
102
|
+
end
|
103
|
+
|
104
|
+
Masamune::AbstractSyntaxTree::DataNode.order_results_by_position(final_result)
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def get_node_class(type)
|
110
|
+
begin
|
111
|
+
class_name = "Masamune::AbstractSyntaxTree::#{type.to_s.camelize}"
|
112
|
+
klass = class_name.constantize
|
113
|
+
rescue NameError
|
114
|
+
# For all other nodes that we haven't covered yet, we just make a general class.
|
115
|
+
# We can worry about adding the classes for other nodes as we go.
|
116
|
+
msmn_node = Masamune::AbstractSyntaxTree::Node
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -23,30 +23,31 @@ module Masamune
|
|
23
23
|
ObjectSpace._id2ref(@ast_id)
|
24
24
|
end
|
25
25
|
|
26
|
-
def
|
27
|
-
return false unless
|
28
|
-
ast.
|
26
|
+
def variable?
|
27
|
+
return false unless identifier?
|
28
|
+
ast.variables(name: @token).any?
|
29
29
|
end
|
30
30
|
|
31
|
-
def
|
32
|
-
|
33
|
-
is_method_definition? || is_method_call?
|
31
|
+
def method_definition?
|
32
|
+
ast.method_definitions(name: @token).any?
|
34
33
|
end
|
35
34
|
|
36
|
-
def
|
37
|
-
ast.
|
35
|
+
def method_call?
|
36
|
+
ast.method_calls(name: @token).any?
|
38
37
|
end
|
39
38
|
|
40
|
-
def
|
41
|
-
|
39
|
+
def method?
|
40
|
+
return false unless identifier?
|
41
|
+
method_definition? || method_call?
|
42
42
|
end
|
43
43
|
|
44
|
-
|
45
|
-
|
44
|
+
# TODO: I'm not sure how I feel about checking @type against the symbol directly.
|
45
|
+
def identifier?
|
46
|
+
@type == :ident
|
46
47
|
end
|
47
48
|
|
48
|
-
def
|
49
|
-
type == :tstring_content
|
49
|
+
def string?
|
50
|
+
@type == :tstring_content
|
50
51
|
end
|
51
52
|
end
|
52
53
|
end
|
data/lib/masamune.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "masamune/version"
|
4
|
+
require "ripper"
|
5
|
+
require "active_support/core_ext/string/inflections"
|
6
|
+
require "masamune/lex_node"
|
7
|
+
require "masamune/abstract_syntax_tree"
|
8
|
+
require "masamune/abstract_syntax_tree/node"
|
9
|
+
require "masamune/abstract_syntax_tree/data_node"
|
10
|
+
|
11
|
+
# Node Types
|
12
|
+
require "masamune/abstract_syntax_tree/assign"
|
13
|
+
require "masamune/abstract_syntax_tree/block_var"
|
14
|
+
require "masamune/abstract_syntax_tree/brace_block"
|
15
|
+
require "masamune/abstract_syntax_tree/call"
|
16
|
+
require "masamune/abstract_syntax_tree/def"
|
17
|
+
require "masamune/abstract_syntax_tree/do_block"
|
18
|
+
require "masamune/abstract_syntax_tree/params"
|
19
|
+
require "masamune/abstract_syntax_tree/program"
|
20
|
+
require "masamune/abstract_syntax_tree/string_content"
|
21
|
+
require "masamune/abstract_syntax_tree/var_field"
|
22
|
+
require "masamune/abstract_syntax_tree/var_ref"
|
23
|
+
require "masamune/abstract_syntax_tree/vcall"
|
24
|
+
|
25
|
+
require "pp"
|
26
|
+
require "pry"
|
27
|
+
|
28
|
+
module Masamune
|
29
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: masamune-ast
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gabriel Zayas
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
12
|
-
dependencies:
|
11
|
+
date: 2023-06-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
description: A layer of abstraction on top of Ripper for handling Abstract Syntax
|
14
28
|
Trees in Ruby.
|
15
29
|
email:
|
@@ -24,13 +38,25 @@ files:
|
|
24
38
|
- LICENSE.txt
|
25
39
|
- README.md
|
26
40
|
- Rakefile
|
27
|
-
- lib/masamune
|
28
|
-
- lib/masamune
|
29
|
-
- lib/masamune
|
30
|
-
- lib/masamune
|
31
|
-
- lib/masamune
|
32
|
-
- lib/masamune
|
33
|
-
- lib/masamune
|
41
|
+
- lib/masamune.rb
|
42
|
+
- lib/masamune/abstract_syntax_tree.rb
|
43
|
+
- lib/masamune/abstract_syntax_tree/assign.rb
|
44
|
+
- lib/masamune/abstract_syntax_tree/block_var.rb
|
45
|
+
- lib/masamune/abstract_syntax_tree/brace_block.rb
|
46
|
+
- lib/masamune/abstract_syntax_tree/call.rb
|
47
|
+
- lib/masamune/abstract_syntax_tree/data_node.rb
|
48
|
+
- lib/masamune/abstract_syntax_tree/def.rb
|
49
|
+
- lib/masamune/abstract_syntax_tree/do_block.rb
|
50
|
+
- lib/masamune/abstract_syntax_tree/node.rb
|
51
|
+
- lib/masamune/abstract_syntax_tree/params.rb
|
52
|
+
- lib/masamune/abstract_syntax_tree/program.rb
|
53
|
+
- lib/masamune/abstract_syntax_tree/string_content.rb
|
54
|
+
- lib/masamune/abstract_syntax_tree/var_field.rb
|
55
|
+
- lib/masamune/abstract_syntax_tree/var_ref.rb
|
56
|
+
- lib/masamune/abstract_syntax_tree/vcall.rb
|
57
|
+
- lib/masamune/lex_node.rb
|
58
|
+
- lib/masamune/slicer.rb
|
59
|
+
- lib/masamune/version.rb
|
34
60
|
- sig/masamune.rbs
|
35
61
|
homepage: https://www.github.com/gazayas/masamune-ast
|
36
62
|
licenses:
|
@@ -1,16 +0,0 @@
|
|
1
|
-
# TODO: Not sure if I'll implement this yet.
|
2
|
-
|
3
|
-
module Masamune
|
4
|
-
class AbstractSyntaxTree
|
5
|
-
class Node
|
6
|
-
def initialize(tree_node)
|
7
|
-
@contents = tree_node
|
8
|
-
end
|
9
|
-
|
10
|
-
# i.e. - [:@ident, "variable_name", [4, 7]]
|
11
|
-
def is_data_node?
|
12
|
-
@contents[0].is_a?(Symbol) && @contents[1].is_a?(String) && (@contents[2][0].is_a?(Integer) && @contents[2][1].is_a?(Integer))
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
@@ -1,121 +0,0 @@
|
|
1
|
-
module Masamune
|
2
|
-
class AbstractSyntaxTree
|
3
|
-
attr_reader :data
|
4
|
-
attr_accessor :lex_nodes, :debug
|
5
|
-
|
6
|
-
def initialize(code)
|
7
|
-
@data = Ripper.sexp(code)
|
8
|
-
raw_lex_nodes = Ripper.lex(code)
|
9
|
-
@lex_nodes = raw_lex_nodes.map do |lex_node|
|
10
|
-
Masamune::LexNode.new(raw_lex_nodes.index(lex_node), lex_node, self.__id__)
|
11
|
-
end
|
12
|
-
@debug = false
|
13
|
-
end
|
14
|
-
|
15
|
-
def tree_nodes
|
16
|
-
raise "Set `debug` to `true` to display the tree nodes" unless @debug
|
17
|
-
search # Perform search with no conditions.
|
18
|
-
end
|
19
|
-
|
20
|
-
def variables
|
21
|
-
search(:variable)
|
22
|
-
end
|
23
|
-
|
24
|
-
def all_methods
|
25
|
-
method_definitions + method_calls
|
26
|
-
end
|
27
|
-
|
28
|
-
def method_definitions
|
29
|
-
search(:def)
|
30
|
-
end
|
31
|
-
|
32
|
-
def method_calls
|
33
|
-
search(:method_call)
|
34
|
-
end
|
35
|
-
|
36
|
-
def strings
|
37
|
-
search(:string)
|
38
|
-
end
|
39
|
-
|
40
|
-
def data_nodes
|
41
|
-
search(:data_node)
|
42
|
-
end
|
43
|
-
|
44
|
-
def search(type = nil, token = nil, tree_node = self.data, result = [])
|
45
|
-
return unless tree_node.is_a?(Array) || (tree_node.is_a?(Array) && tree_node.empty?)
|
46
|
-
debug_output(tree_node) if @debug
|
47
|
-
|
48
|
-
# If the first element is an array, then we're getting all arrays so we just continue the search.
|
49
|
-
if tree_node.first.is_a?(Array)
|
50
|
-
tree_node.each { |node| search(type, token, node, result) }
|
51
|
-
elsif tree_node.first.is_a?(Symbol)
|
52
|
-
# TODO: These two if statements bother me a lot.
|
53
|
-
# There should be a more effiecient/smart way to do this.
|
54
|
-
if has_data_node?(tree_node)
|
55
|
-
if (type == :variable && (tree_node.first == :var_field || tree_node.first == :var_ref)) ||
|
56
|
-
(type == :string && tree_node.first == :string_content) ||
|
57
|
-
(type == :def && tree_node.first == :def) ||
|
58
|
-
(type == :method_call && tree_node.first == :vcall)
|
59
|
-
|
60
|
-
# TODO: AbstractSyntaxTree::DataNode.new(tree_node)
|
61
|
-
# For most tree nodes, the data_node is housed in the second element.
|
62
|
-
position, data_node_token = data_node_parts(tree_node[1])
|
63
|
-
|
64
|
-
# Gather all results if token isn't specified.
|
65
|
-
result << [position, data_node_token] if token == data_node_token || token.nil?
|
66
|
-
end
|
67
|
-
|
68
|
-
# Continue search for all necessary elements.
|
69
|
-
case tree_node.first
|
70
|
-
when :def, :command
|
71
|
-
tree_node.each { |node| search(type, token, node, result) }
|
72
|
-
end
|
73
|
-
|
74
|
-
# The data nodes in :call nodes are in a different place within the array, so we handle that here.
|
75
|
-
# These :call nodes represent methods and chained methods like `[1, 2, 3].sum.times`.
|
76
|
-
elsif (type == :method_call && tree_node.first == :call)
|
77
|
-
# The method inside the [:call, ...] data node is the last element in the array.
|
78
|
-
position, data_node_token = data_node_parts(tree_node.last)
|
79
|
-
result << [position, data_node_token] if token == data_node_token || token.nil?
|
80
|
-
# The second element is where more :call nodes are nested, so we search it.
|
81
|
-
search(type, token, tree_node[1], result)
|
82
|
-
else
|
83
|
-
# Simply continue the search for all other nodes.
|
84
|
-
tree_node.each { |node| search(type, token, node, result) }
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
result
|
89
|
-
end
|
90
|
-
|
91
|
-
private
|
92
|
-
|
93
|
-
# A data node represents an abstraction in the AST which has details about a specific command.
|
94
|
-
# i.e. - [:@ident, "variable_name", [4, 7]]
|
95
|
-
# These values are the `type`, `token`, and `position`, respectively.
|
96
|
-
# It is simliar to what you see in `Ripper.lex(code)` and `Masamune::Base's @lex_nodes`.
|
97
|
-
# Data nodes serve as a base case when recursively searching the AST.
|
98
|
-
#
|
99
|
-
# The parent node's first element houses the type of action being performed:
|
100
|
-
# i.e. - [:assign, [:@ident, "variable_name", [4, 7]]]
|
101
|
-
# `has_data_node?` is performed on a parent node.
|
102
|
-
def has_data_node?(node)
|
103
|
-
node[1].is_a?(Array) && node[1][1].is_a?(String)
|
104
|
-
end
|
105
|
-
|
106
|
-
def data_node_parts(tree_node)
|
107
|
-
_, token, position = tree_node
|
108
|
-
[position, token]
|
109
|
-
end
|
110
|
-
|
111
|
-
def is_line_position?(tree_node)
|
112
|
-
tree_node.size == 2 && tree_node.first.is_a?(Integer) && tree_node.last.is_a?(Integer)
|
113
|
-
end
|
114
|
-
|
115
|
-
def debug_output(tree_node)
|
116
|
-
puts "==================================" # TODO: Track the array depth and output the number here.
|
117
|
-
puts "=================================="
|
118
|
-
p tree_node
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
data/lib/masamune-ast.rb
DELETED
File without changes
|