masamune-ast 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +27 -0
- data/LICENSE.txt +21 -0
- data/README.md +66 -0
- data/Rakefile +12 -0
- data/lib/masamune-ast/abstract_syntax_tree/data_node.rb +11 -0
- data/lib/masamune-ast/abstract_syntax_tree/node.rb +16 -0
- data/lib/masamune-ast/abstract_syntax_tree.rb +121 -0
- data/lib/masamune-ast/lex_node.rb +52 -0
- data/lib/masamune-ast/slicer.rb +19 -0
- data/lib/masamune-ast/version.rb +5 -0
- data/lib/masamune-ast.rb +12 -0
- data/sig/masamune.rbs +4 -0
- metadata +61 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1131c8f9da2cb862e77f3840c9abc806443a7d4445d788fd3683cec41b1916e8
|
4
|
+
data.tar.gz: bb9025361d1063fd3f7776070830b4e41b7529d3ea04b11fbe9466890bca8d28
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f6e571ac354ab0fae7e39fbf1d0030c1025738732d008b9c3afa9ade11c2cb41b88d67a08ac66d878dd913fae1a9cf08f79d4539f931b7a55fd43f563332d08b
|
7
|
+
data.tar.gz: 47065f76eebaab82601b9d577a738765e66d588f6ad300931223bd61824e0f290612a6341dd098d8bd78cdb90e5b44ce9513d6beb5280b9ebc5691b4044145ea
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
masamune-ast (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
coderay (1.1.3)
|
10
|
+
method_source (1.0.0)
|
11
|
+
minitest (5.18.0)
|
12
|
+
pry (0.14.2)
|
13
|
+
coderay (~> 1.1)
|
14
|
+
method_source (~> 1.0)
|
15
|
+
rake (13.0.6)
|
16
|
+
|
17
|
+
PLATFORMS
|
18
|
+
x86_64-linux
|
19
|
+
|
20
|
+
DEPENDENCIES
|
21
|
+
masamune-ast!
|
22
|
+
minitest (~> 5.0)
|
23
|
+
pry
|
24
|
+
rake (~> 13.0)
|
25
|
+
|
26
|
+
BUNDLED WITH
|
27
|
+
2.4.1
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2023 Gabriel Zayas
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# MasamuneAst
|
2
|
+
|
3
|
+
## A Ruby source code analyzer based on Ripper’s Abstract Syntax Tree generator (`Ripper#sexp`).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem "masamune-ast"
|
9
|
+
```
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Pinpoint variables and methods in your source code even when other tokens have the same or similar spelling:
|
14
|
+
```ruby
|
15
|
+
code = <<CODE
|
16
|
+
java = "java"
|
17
|
+
javascript = java + "script"
|
18
|
+
puts java + " is not " + javascript
|
19
|
+
# java
|
20
|
+
CODE
|
21
|
+
|
22
|
+
msmn = MasamuneAst::AbstractSyntaxTree.new(code)
|
23
|
+
|
24
|
+
msmn.variables
|
25
|
+
[[[1, 0], "java"], [[2, 0], "javascript"], [[2, 13], "java"]]
|
26
|
+
|
27
|
+
msmn.search(:variable, "java")
|
28
|
+
#=> [[[1, 0], "java"], [[2, 13], "java"]]
|
29
|
+
```
|
30
|
+
|
31
|
+
In some cases, it can be easier to look at the given lex nodes to analyze your source code:
|
32
|
+
```ruby
|
33
|
+
msmn.lex_nodes
|
34
|
+
=> [#<Masamune::LexNode:0x00007fd61810cac0 @ast_id=1200, @index=0, @position=[1, 0], @state=CMDARG, @token="java", @type=:ident>,
|
35
|
+
#<Masamune::LexNode:0x00007fd61810c930 @ast_id=1200, @index=1, @position=[1, 4], @state=CMDARG, @token=" ", @type=:sp>,
|
36
|
+
#<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
|
+
…
|
46
|
+
]
|
47
|
+
|
48
|
+
msmn.lex_nodes[8].is_variable?
|
49
|
+
#=> true
|
50
|
+
|
51
|
+
msmn.lex_nodes[8].is_string?
|
52
|
+
#=> false
|
53
|
+
|
54
|
+
msmn.lex_nodes[8].is_method_definition?
|
55
|
+
#=> false
|
56
|
+
|
57
|
+
# etc...
|
58
|
+
```
|
59
|
+
|
60
|
+
## Contributing
|
61
|
+
|
62
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/gazayas/masamune.
|
63
|
+
|
64
|
+
## License
|
65
|
+
|
66
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
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
|
@@ -0,0 +1,121 @@
|
|
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
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module MasamuneAst
|
2
|
+
# https://docs.ruby-lang.org/en/3.0/Ripper.html#method-c-lex
|
3
|
+
#
|
4
|
+
# @position: Line number and starting point on the line. i.e. - [4, 7].
|
5
|
+
# @type: The type of token. i.e. - :kw (is a keyword).
|
6
|
+
# @token: The raw string which represents the actual ruby code. i.e. - "do".
|
7
|
+
# @state: Ripper::Lexer::State. i.e. - CMDARG.
|
8
|
+
class LexNode
|
9
|
+
attr_accessor :position, :type, :token, :state, :ast_id
|
10
|
+
attr_reader :index
|
11
|
+
|
12
|
+
def initialize(index, raw_lex_node, ast_id)
|
13
|
+
@index = index
|
14
|
+
@position, @type, @token, @state = raw_lex_node
|
15
|
+
@type = @type.to_s.gsub(/^[a-z]*_/, "").to_sym
|
16
|
+
|
17
|
+
# Since the Abstract Syntax Tree can get very large,
|
18
|
+
# We just save the id and reference it with `ast` below.
|
19
|
+
@ast_id = ast_id
|
20
|
+
end
|
21
|
+
|
22
|
+
def ast
|
23
|
+
ObjectSpace._id2ref(@ast_id)
|
24
|
+
end
|
25
|
+
|
26
|
+
def is_variable?
|
27
|
+
return false unless is_identifier?
|
28
|
+
ast.search(:variable, @token).any?
|
29
|
+
end
|
30
|
+
|
31
|
+
def is_method?
|
32
|
+
return false unless is_identifier?
|
33
|
+
is_method_definition? || is_method_call?
|
34
|
+
end
|
35
|
+
|
36
|
+
def is_method_definition?
|
37
|
+
ast.search(:def, @token).any?
|
38
|
+
end
|
39
|
+
|
40
|
+
def is_method_call?
|
41
|
+
ast.search(:method_call, @token).any?
|
42
|
+
end
|
43
|
+
|
44
|
+
def is_identifier?
|
45
|
+
type == :ident
|
46
|
+
end
|
47
|
+
|
48
|
+
def is_string?
|
49
|
+
type == :tstring_content
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module MasamuneAst
|
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
ADDED
data/sig/masamune.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: masamune-ast
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gabriel Zayas
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-04-29 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A layer of abstraction on top of Ripper for handling Abstract Syntax
|
14
|
+
Trees in Ruby.
|
15
|
+
email:
|
16
|
+
- g-zayas@hotmail.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- CHANGELOG.md
|
22
|
+
- Gemfile
|
23
|
+
- Gemfile.lock
|
24
|
+
- LICENSE.txt
|
25
|
+
- README.md
|
26
|
+
- 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
|
34
|
+
- sig/masamune.rbs
|
35
|
+
homepage: https://www.github.com/gazayas/masamune
|
36
|
+
licenses:
|
37
|
+
- MIT
|
38
|
+
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
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 2.6.0
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
requirements: []
|
57
|
+
rubygems_version: 3.3.7
|
58
|
+
signing_key:
|
59
|
+
specification_version: 4
|
60
|
+
summary: MasamuneAst
|
61
|
+
test_files: []
|