masamune-ast 0.1.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: 1131c8f9da2cb862e77f3840c9abc806443a7d4445d788fd3683cec41b1916e8
4
- data.tar.gz: bb9025361d1063fd3f7776070830b4e41b7529d3ea04b11fbe9466890bca8d28
3
+ metadata.gz: 1ee5dca320a96035fcea4f73effbc3fceff2ee531808bf94d99aa21086186ce5
4
+ data.tar.gz: 3f170766c1e97a15e9c550e61fc5f2048b28b48a5f12e2b4841a31b0dad6ebbb
5
5
  SHA512:
6
- metadata.gz: f6e571ac354ab0fae7e39fbf1d0030c1025738732d008b9c3afa9ade11c2cb41b88d67a08ac66d878dd913fae1a9cf08f79d4539f931b7a55fd43f563332d08b
7
- data.tar.gz: 47065f76eebaab82601b9d577a738765e66d588f6ad300931223bd61824e0f290612a6341dd098d8bd78cdb90e5b44ce9513d6beb5280b9ebc5691b4044145ea
6
+ metadata.gz: 7650eff09ecb23034c236363ee70a971f003736a967f8efd5ae8f36e68d598f73a4239a38d839f478b4aa6b36662d7114834dedf501acfed6bd2adcb6494890e
7
+ data.tar.gz: fbd0fc4a505b03dc38304abdfadd9cb31fccf5f746bbb325056c63e9a790d7814bd7292d40c95da7441731cf3ab0613f2e9879c4c27f07f61445370a499665a2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.0.0] - 2023-05-01
4
+
5
+ - Change `MasamuneAst` module to `Masamune`.
6
+
3
7
  ## [0.1.0] - 2023-04-24
4
8
 
5
9
  - Initial release
data/Gemfile.lock CHANGED
@@ -1,18 +1,29 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- masamune-ast (0.1.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
@@ -1,9 +1,14 @@
1
- # MasamuneAst
1
+ # Masamune
2
2
 
3
3
  ## A Ruby source code analyzer based on Ripper’s Abstract Syntax Tree generator (`Ripper#sexp`).
4
4
 
5
5
  ## Installation
6
6
 
7
+ ```ruby
8
+ sudo gem install "masamune-ast"
9
+ ```
10
+
11
+ Or add the following to your Gemfile and run `bundle install`
7
12
  ```ruby
8
13
  gem "masamune-ast"
9
14
  ```
@@ -19,47 +24,80 @@ puts java + " is not " + javascript
19
24
  # java
20
25
  CODE
21
26
 
22
- msmn = MasamuneAst::AbstractSyntaxTree.new(code)
27
+ msmn = Masamune::AbstractSyntaxTree.new(code)
23
28
 
24
29
  msmn.variables
25
- [[[1, 0], "java"], [[2, 0], "javascript"], [[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"}]
45
+
46
+ code = <<CODE
47
+ ary = [1, 2, 3]
48
+ ary.sum.times do |n|
49
+ puts n
50
+ end
51
+
52
+ def foo
53
+ end
54
+ foo
55
+ foo # Call again
56
+ CODE
26
57
 
27
- msmn.search(:variable, "java")
28
- #=> [[[1, 0], "java"], [[2, 13], "java"]]
58
+ msmn = Masamune::AbstractSyntaxTree.new(code)
59
+
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"}]
29
75
  ```
30
76
 
31
- 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:
32
78
  ```ruby
33
79
  msmn.lex_nodes
34
80
  => [#<Masamune::LexNode:0x00007fd61810cac0 @ast_id=1200, @index=0, @position=[1, 0], @state=CMDARG, @token="java", @type=:ident>,
35
81
  #<Masamune::LexNode:0x00007fd61810c930 @ast_id=1200, @index=1, @position=[1, 4], @state=CMDARG, @token=" ", @type=:sp>,
36
82
  #<Masamune::LexNode:0x00007fd61810c7c8 @ast_id=1200, @index=2, @position=[1, 5], @state=BEG, @token="=", @type=:op>,
37
- #<Masamune::LexNode:0x00007fd61810c638 @ast_id=1200, @index=3, @position=[1, 6], @state=BEG, @token=" ", @type=:sp>,
38
- #<Masamune::LexNode:0x00007fd61810c480 @ast_id=1200, @index=4, @position=[1, 7], @state=BEG, @token="\"", @type=:tstring_beg>,
39
- #<Masamune::LexNode:0x00007fd61810c318 @ast_id=1200, @index=5, @position=[1, 8], @state=BEG, @token="java", @type=:tstring_content>,
40
- #<Masamune::LexNode:0x00007fd61810c188 @ast_id=1200, @index=6, @position=[1, 12], @state=END, @token="\"", @type=:tstring_end>,
41
- #<Masamune::LexNode:0x00007fd61810c020 @ast_id=1200, @index=7, @position=[1, 13], @state=BEG, @token="\n", @type=:nl>,
42
- #<Masamune::LexNode:0x00007fd618113e88 @ast_id=1200, @index=8, @position=[2, 0], @state=CMDARG, @token="javascript", @type=:ident>,
43
- #<Masamune::LexNode:0x00007fd618113cf8 @ast_id=1200, @index=9, @position=[2, 10], @state=CMDARG, @token=" ", @type=:sp>,
44
- #<Masamune::LexNode:0x00007fd618113b68 @ast_id=1200, @index=10, @position=[2, 11], @state=BEG, @token="=", @type=:op>,
45
83
 
46
84
  ]
47
85
 
48
- msmn.lex_nodes[8].is_variable?
86
+ lex_node = msmn.lex_nodes.first
87
+
88
+ lex_node.variable?
49
89
  #=> true
50
90
 
51
- msmn.lex_nodes[8].is_string?
91
+ lex_node.string?
52
92
  #=> false
53
93
 
54
- msmn.lex_nodes[8].is_method_definition?
94
+ lex_node.method_definition?
55
95
  #=> false
56
-
57
- # etc...
58
96
  ```
59
97
 
60
98
  ## Contributing
61
99
 
62
- Bug reports and pull requests are welcome on GitHub at https://github.com/gazayas/masamune.
100
+ Bug reports and pull requests are welcome on GitHub at https://github.com/gazayas/masamune-ast.
63
101
 
64
102
  ## License
65
103
 
@@ -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
@@ -1,4 +1,4 @@
1
- module MasamuneAst
1
+ module Masamune
2
2
  # https://docs.ruby-lang.org/en/3.0/Ripper.html#method-c-lex
3
3
  #
4
4
  # @position: Line number and starting point on the line. i.e. - [4, 7].
@@ -23,30 +23,31 @@ module MasamuneAst
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,4 +1,4 @@
1
- module MasamuneAst
1
+ module Masamune
2
2
  module Slicer
3
3
  def initialize(ast)
4
4
  @ast = ast
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Masamune
4
+ VERSION = "1.1.0"
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: 0.1.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-04-29 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,21 +38,33 @@ 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
- homepage: https://www.github.com/gazayas/masamune
61
+ homepage: https://www.github.com/gazayas/masamune-ast
36
62
  licenses:
37
63
  - MIT
38
64
  metadata:
39
- homepage_uri: https://www.github.com/gazayas/masamune
40
- source_code_uri: https://www.github.com/gazayas/masamune
41
- changelog_uri: https://www.github.com/gazayas/masamune
65
+ homepage_uri: https://www.github.com/gazayas/masamune-ast
66
+ source_code_uri: https://www.github.com/gazayas/masamune-ast
67
+ changelog_uri: https://www.github.com/gazayas/masamune-ast
42
68
  post_install_message:
43
69
  rdoc_options: []
44
70
  require_paths:
@@ -57,5 +83,5 @@ requirements: []
57
83
  rubygems_version: 3.3.7
58
84
  signing_key:
59
85
  specification_version: 4
60
- summary: MasamuneAst
86
+ summary: Masamune
61
87
  test_files: []
@@ -1,11 +0,0 @@
1
- # TODO: Not sure if I'll implement this yet.
2
-
3
- module MasamuneAst
4
- class AbstractSyntaxTree
5
- class DataNode << MasamuneAst::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 MasamuneAst
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 MasamuneAst
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
- MasamuneAst::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
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module MasamuneAst
4
- VERSION = "0.1.0"
5
- 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 MasamuneAst
12
- end