masamune-ast 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2023-04-24
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in masamune.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "minitest", "~> 5.0"
11
+
12
+ gem "pry"
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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/test_*.rb"]
10
+ end
11
+
12
+ task default: :test
@@ -0,0 +1,11 @@
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
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MasamuneAst
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,12 @@
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
data/sig/masamune.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Masamune
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
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: []