masamune-ast 1.0.0 → 1.1.1

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: a00f60e535cf2096a37cd6efd639aa7c132c70e58c3b422a23538128368b5c85
4
+ data.tar.gz: 5d548fc4b9d5cf25c1099a2b446542f57342694d7b414052df004dcd2cf311c2
5
5
  SHA512:
6
- metadata.gz: 165fb67094e4e12730106dd7262d4d8be67db9d7994ca96c5d94a46d2334c0c62d1aa9fab64637e9290e3aff6f7a8c8b913580b4ae3e2ed4584b52f40a82d55a
7
- data.tar.gz: '006920de6a9409d6a08f6cfe955f918e8411c267665559aec9847fddffb6c514ca9283db8efbe73d8098f687c274d06439ffd681a817172ac5e7216fd61ef774'
6
+ metadata.gz: c80bcefac510bfd4103d56fd13f39a46d1e4531eceb678d620709b97fe0c941f20f1e7ffddc6058e77aab46ab093d1d4ba768b6860649c5a57197e36f89bf109
7
+ data.tar.gz: 731a46f94d655a820acdd949d7d7fa03f3fc37277858b5b5daea3c0c13444720b3b1ca829d0f304d5aecb3f390e3055168503ce6655653268cb89fcf19ab28e3
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.1)
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
@@ -13,8 +13,38 @@ Or add the following to your Gemfile and run `bundle install`
13
13
  gem "masamune-ast"
14
14
  ```
15
15
 
16
+ Then require the gem in a file like this:
17
+ ```ruby
18
+ require "masamune"
19
+ ```
20
+
16
21
  ## Usage
17
22
 
23
+ Isolate and replace variables, methods, or strings in your Ruby source code according to the specific tokens allotted when the code is intially parsed:
24
+ ```ruby
25
+ code = <<~CODE
26
+ 10.times do |n|
27
+ puts n
28
+ end
29
+
30
+ def n
31
+ "n"
32
+ end
33
+ CODE
34
+
35
+ msmn = Masamune::AbstractSyntaxTree.new(code)
36
+ msmn.replace(type: :variables, old_token: "n", new_token: "foo")
37
+
38
+ # This will produce the following code in string form.
39
+ 10.times do |foo|
40
+ puts foo
41
+ end
42
+
43
+ def n
44
+ "n"
45
+ end
46
+ ```
47
+
18
48
  Pinpoint variables and methods in your source code even when other tokens have the same or similar spelling:
19
49
  ```ruby
20
50
  code = <<CODE
@@ -26,12 +56,22 @@ CODE
26
56
 
27
57
  msmn = Masamune::AbstractSyntaxTree.new(code)
28
58
 
29
- # Searching the tree returns the specific node and the line number it's on.
30
59
  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"]]
60
+ #=> [{:position=>[1, 0], :token=>"java"},
61
+ #=> {:position=>[2, 0], :token=>"javascript"},
62
+ #=> {:position=>[2, 13], :token=>"java"},
63
+ #=> {:position=>[3, 5], :token=>"java"},
64
+ #=> {:position=>[3, 25], :token=>"javascript"}]
65
+
66
+ msmn.strings
67
+ #=> [{:position=>[1, 8], :token=>"java"},
68
+ #=> {:position=>[2, 21], :token=>"script"},
69
+ #=> {:position=>[3, 13], :token=>" is not "}]
70
+
71
+ msmn.variables(name: "java")
72
+ #=> [{position: [1, 0], token: "java"},
73
+ #=> {position: [2, 13], token: "java"},
74
+ #=> {position: [3, 5], token: "java"}]
35
75
 
36
76
  code = <<CODE
37
77
  ary = [1, 2, 3]
@@ -47,43 +87,42 @@ CODE
47
87
 
48
88
  msmn = Masamune::AbstractSyntaxTree.new(code)
49
89
 
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"]]
90
+ msmn.all_methods
91
+ #=> [{:position=>[6, 4], :token=>"foo"},
92
+ #=> {:position=>[2, 4], :token=>"sum"},
93
+ #=> {:position=>[2, 8], :token=>"times"},
94
+ #=> {:position=>[8, 0], :token=>"foo"},
95
+ #=> {:position=>[9, 0], :token=>"foo"}]
96
+
97
+ msmn.method_calls
98
+ #=> [{:position=>[2, 4], :token=>"sum"},
99
+ #=> {:position=>[2, 8], :token=>"times"},
100
+ #=> {:position=>[8, 0], :token=>"foo"},
101
+ #=> {:position=>[9, 0], :token=>"foo"}]
102
+
103
+ msmn.method_definitions
104
+ #=> [{:position=>[6, 4], :token=>"foo"}]
58
105
  ```
59
106
 
60
- In some cases, it can be easier to look at the given lex nodes to analyze your source code:
107
+ 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
108
  ```ruby
62
109
  msmn.lex_nodes
63
110
  => [#<Masamune::LexNode:0x00007fd61810cac0 @ast_id=1200, @index=0, @position=[1, 0], @state=CMDARG, @token="java", @type=:ident>,
64
111
  #<Masamune::LexNode:0x00007fd61810c930 @ast_id=1200, @index=1, @position=[1, 4], @state=CMDARG, @token=" ", @type=:sp>,
65
112
  #<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
113
 
75
114
  ]
76
115
 
77
- msmn.lex_nodes[8].is_variable?
116
+ lex_node = msmn.lex_nodes.first
117
+
118
+ lex_node.variable?
78
119
  #=> true
79
120
 
80
- msmn.lex_nodes[8].is_string?
121
+ lex_node.string?
81
122
  #=> false
82
123
 
83
- msmn.lex_nodes[8].is_method_definition?
124
+ lex_node.method_definition?
84
125
  #=> false
85
-
86
- # etc...
87
126
  ```
88
127
 
89
128
  ## 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
+ 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 = 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
+ 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
+ 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 = 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
+ 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
+ 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
+ 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
+ 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
+ DataNode.new(@contents[1], @ast_id)
13
+ ]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,132 @@
1
+ module Masamune
2
+ class AbstractSyntaxTree
3
+ attr_reader :code, :tree
4
+ attr_accessor :node_list, :data_node_list, :lex_nodes
5
+
6
+ def initialize(code)
7
+ @code = code
8
+ @tree = Ripper.sexp(code)
9
+ raw_lex_nodes = Ripper.lex(code)
10
+ @lex_nodes = raw_lex_nodes.map do |lex_node|
11
+ LexNode.new(raw_lex_nodes.index(lex_node), lex_node, self.__id__)
12
+ end
13
+
14
+ @node_list = []
15
+ @data_node_list = []
16
+ register_nodes(@tree)
17
+ end
18
+
19
+ def register_nodes(tree_node = self.tree)
20
+ if tree_node.is_a?(Array)
21
+ klass = get_node_class(tree_node.first)
22
+ msmn_node = klass.new(tree_node, self.__id__)
23
+ else
24
+ # Create a general node if the node is a single value.
25
+ msmn_node = Node.new(tree_node, self.__id__)
26
+ end
27
+
28
+ # Register nodes and any data nodes housed within it.
29
+ # See Masamune::AbstractSyntaxTree::DataNode for more details on what a data node is.
30
+ @node_list << msmn_node
31
+ msmn_node.data_nodes.each { |dn| @data_node_list << dn } if msmn_node.data_nodes
32
+
33
+ # Continue down the tree until base case is reached.
34
+ if !msmn_node.nil? && msmn_node.contents.is_a?(Array)
35
+ msmn_node.contents.each { |node| register_nodes(node) }
36
+ end
37
+ end
38
+
39
+ # TODO: Add block_params: true to the arguments.
40
+ def variables(name: nil)
41
+ var_classes = [
42
+ :var_field,
43
+ :var_ref,
44
+ :params
45
+ ].map {|type| get_node_class(type)}
46
+ find_nodes(var_classes, token: name)
47
+ end
48
+
49
+ def strings(content: nil)
50
+ find_nodes(get_node_class(:string_content), token: content)
51
+ end
52
+
53
+ def method_definitions(name: nil)
54
+ find_nodes(get_node_class(:def), token: name)
55
+ end
56
+
57
+ def method_calls(name: nil)
58
+ method_classes = [
59
+ :vcall,
60
+ :call
61
+ ].map {|type| get_node_class(type)}
62
+ find_nodes(method_classes, token: name)
63
+ end
64
+
65
+ # TODO
66
+ def do_block_params
67
+ end
68
+
69
+ # TODO
70
+ def brace_block_params
71
+ end
72
+
73
+ def all_methods
74
+ method_definitions + method_calls
75
+ end
76
+
77
+ def block_params
78
+ # TODO: do_block_params + brace_block_params
79
+ find_nodes(get_node_class(:params))
80
+ end
81
+
82
+ # TODO: Create an option to return a list of DataNode class instances.
83
+ def find_nodes(token_classes, token: nil)
84
+ # Ensure the classes are in an array
85
+ token_classes = [token_classes].flatten
86
+
87
+ nodes = []
88
+ token_classes.each do |klass|
89
+ nodes << @node_list.select {|node| node.class == klass}
90
+ end
91
+
92
+ # Searching for multiple classes will yield multi-dimensional arrays,
93
+ # so we ensure everything is flattened out before moving forward.
94
+ nodes.flatten!
95
+
96
+ if token
97
+ # TODO: This most likely shouldn't be `node.data_nodes.first`.
98
+ # There are probably more data_nodes we need to check depending on the node class.
99
+ nodes = nodes.select {|node| node.data_nodes.first.token == token}.flatten
100
+ end
101
+
102
+ final_result = []
103
+ nodes.each do |node|
104
+ node.data_nodes.each {|dn| final_result << dn.position_and_token} if node.data_nodes
105
+ end
106
+
107
+ DataNode.order_results_by_position(final_result)
108
+ end
109
+
110
+ def replace(type:, old_token:, new_token:)
111
+ Slasher.replace(
112
+ type: type,
113
+ old_token: old_token,
114
+ new_token: new_token,
115
+ code: @code,
116
+ ast: self
117
+ )
118
+ end
119
+
120
+ private
121
+
122
+ def get_node_class(type)
123
+ begin
124
+ "Masamune::AbstractSyntaxTree::#{type.to_s.camelize}".constantize
125
+ rescue NameError
126
+ # For all other nodes that we haven't covered yet, we just make a general class.
127
+ # We can worry about adding the classes for other nodes as we go.
128
+ Node
129
+ end
130
+ end
131
+ end
132
+ 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
@@ -0,0 +1,32 @@
1
+ module Masamune
2
+ module Slasher
3
+ def self.replace(type:, old_token:, new_token:, code:, ast:)
4
+ # `type` can either be a method from the ast like `method_definitions`,
5
+ # or it can be a list of Masamune::AbstractSyntaxTree node classes.
6
+ position_and_token_ary = if type.is_a?(Symbol) && ast.respond_to?(type)
7
+ ast.send(type)
8
+ elsif type.is_a?(Array)
9
+ type.map {|klass| ast.find_nodes(klass)}.flatten
10
+ end
11
+
12
+ tokens_to_replace = position_and_token_ary.select do |pos_and_tok|
13
+ pos_and_tok[:token] == old_token
14
+ end
15
+
16
+ # Build from lex nodes
17
+ result = ast.lex_nodes.map do |lex_node|
18
+ match_found = false
19
+ tokens_to_replace.each do |position_and_token|
20
+ if position_and_token[:position] == lex_node.position
21
+ match_found = true
22
+ break
23
+ end
24
+ end
25
+
26
+ match_found ? new_token : lex_node.token
27
+ end
28
+
29
+ result.join
30
+ end
31
+ end
32
+ 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.1"
5
5
  end
data/lib/masamune.rb ADDED
@@ -0,0 +1,30 @@
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/slasher"
8
+ require "masamune/abstract_syntax_tree"
9
+ require "masamune/abstract_syntax_tree/node"
10
+ require "masamune/abstract_syntax_tree/data_node"
11
+
12
+ # Node Types
13
+ require "masamune/abstract_syntax_tree/assign"
14
+ require "masamune/abstract_syntax_tree/block_var"
15
+ require "masamune/abstract_syntax_tree/brace_block"
16
+ require "masamune/abstract_syntax_tree/call"
17
+ require "masamune/abstract_syntax_tree/def"
18
+ require "masamune/abstract_syntax_tree/do_block"
19
+ require "masamune/abstract_syntax_tree/params"
20
+ require "masamune/abstract_syntax_tree/program"
21
+ require "masamune/abstract_syntax_tree/string_content"
22
+ require "masamune/abstract_syntax_tree/var_field"
23
+ require "masamune/abstract_syntax_tree/var_ref"
24
+ require "masamune/abstract_syntax_tree/vcall"
25
+
26
+ require "pp"
27
+ require "pry"
28
+
29
+ module Masamune
30
+ 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.1
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-03 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/slasher.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
@@ -1,19 +0,0 @@
1
- module Masamune
2
- module Slicer
3
- def initialize(ast)
4
- @ast = ast
5
- end
6
-
7
- # TODO
8
-
9
- # def replace(type, token)
10
- # @ast.search(type, token)
11
- # end
12
-
13
- # def insert_inside_block
14
- # end
15
-
16
- # def append_to_block
17
- # end
18
- end
19
- 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