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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 418c2c9ca6e229ff03320d7057ff12f19cdbb60d3da8b9f1d03db901b6971b43
4
- data.tar.gz: 3dda8f6d319d4ce961a8f9b3421d73bea5a8462c8c740b0c1f6e37e25e3020a1
3
+ metadata.gz: 1ee5dca320a96035fcea4f73effbc3fceff2ee531808bf94d99aa21086186ce5
4
+ data.tar.gz: 3f170766c1e97a15e9c550e61fc5f2048b28b48a5f12e2b4841a31b0dad6ebbb
5
5
  SHA512:
6
- metadata.gz: 165fb67094e4e12730106dd7262d4d8be67db9d7994ca96c5d94a46d2334c0c62d1aa9fab64637e9290e3aff6f7a8c8b913580b4ae3e2ed4584b52f40a82d55a
7
- data.tar.gz: '006920de6a9409d6a08f6cfe955f918e8411c267665559aec9847fddffb6c514ca9283db8efbe73d8098f687c274d06439ffd681a817172ac5e7216fd61ef774'
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.0.0)
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
- #=> [[[1, 0], "java"], [[2, 0], "javascript"], [[2, 13], "java"]]
32
-
33
- msmn.search(:variable, "java")
34
- #=> [[[1, 0], "java"], [[2, 13], "java"]]
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.search(:method_call, "sum")
51
- #=> [[[2, 4], "sum"]]
52
-
53
- msmn.search(:def, "foo")
54
- #=> [[[6, 4], "foo"]]
55
-
56
- msmn.search(:method_call, "foo")
57
- #=> [[[8, 0], "foo"], [[9, 0], "foo"]]
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[8].is_variable?
86
+ lex_node = msmn.lex_nodes.first
87
+
88
+ lex_node.variable?
78
89
  #=> true
79
90
 
80
- msmn.lex_nodes[8].is_string?
91
+ lex_node.string?
81
92
  #=> false
82
93
 
83
- msmn.lex_nodes[8].is_method_definition?
94
+ lex_node.method_definition?
84
95
  #=> false
85
-
86
- # etc...
87
96
  ```
88
97
 
89
98
  ## Contributing
@@ -0,0 +1,11 @@
1
+ # TODO: Add description.
2
+
3
+ module Masamune
4
+ class AbstractSyntaxTree
5
+ class Assign < Node
6
+ def initialize(contents, ast_id)
7
+ super
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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 is_variable?
27
- return false unless is_identifier?
28
- ast.search(:variable, @token).any?
26
+ def variable?
27
+ return false unless identifier?
28
+ ast.variables(name: @token).any?
29
29
  end
30
30
 
31
- def is_method?
32
- return false unless is_identifier?
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 is_method_definition?
37
- ast.search(:def, @token).any?
35
+ def method_call?
36
+ ast.method_calls(name: @token).any?
38
37
  end
39
38
 
40
- def is_method_call?
41
- ast.search(:method_call, @token).any?
39
+ def method?
40
+ return false unless identifier?
41
+ method_definition? || method_call?
42
42
  end
43
43
 
44
- def is_identifier?
45
- type == :ident
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 is_string?
49
- type == :tstring_content
49
+ def string?
50
+ @type == :tstring_content
50
51
  end
51
52
  end
52
53
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Masamune
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.0"
5
5
  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.0.0
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-05-01 00:00:00.000000000 Z
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-ast.rb
28
- - lib/masamune-ast/abstract_syntax_tree.rb
29
- - lib/masamune-ast/abstract_syntax_tree/data_node.rb
30
- - lib/masamune-ast/abstract_syntax_tree/node.rb
31
- - lib/masamune-ast/lex_node.rb
32
- - lib/masamune-ast/slicer.rb
33
- - lib/masamune-ast/version.rb
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,11 +0,0 @@
1
- # TODO: Not sure if I'll implement this yet.
2
-
3
- module Masamune
4
- class AbstractSyntaxTree
5
- class DataNode << Masamune::AbstractSyntaxTree::Node
6
- def initialize(tree_node)
7
- @parent, @contents, _ = tree_node
8
- end
9
- end
10
- end
11
- end
@@ -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
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "masamune-ast/version"
4
- require "ripper"
5
- require "masamune-ast/lex_node"
6
- require "masamune-ast/abstract_syntax_tree"
7
-
8
- require "pp"
9
- require "pry"
10
-
11
- module Masamune
12
- end
File without changes