syntax_tree 3.6.3 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +15 -1
- data/Gemfile.lock +5 -5
- data/README.md +32 -10
- data/bin/profile +5 -6
- data/lib/syntax_tree/cli.rb +3 -3
- data/lib/syntax_tree/formatter.rb +67 -8
- data/lib/syntax_tree/node.rb +775 -527
- data/lib/syntax_tree/parser.rb +335 -245
- data/lib/syntax_tree/version.rb +1 -1
- data/lib/syntax_tree/visitor/environment.rb +81 -0
- data/lib/syntax_tree/visitor/with_environment.rb +141 -0
- data/lib/syntax_tree.rb +14 -2
- data/syntax_tree.gemspec +1 -1
- metadata +6 -4
data/lib/syntax_tree/version.rb
CHANGED
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
# The environment class is used to keep track of local variables and arguments
|
5
|
+
# inside a particular scope
|
6
|
+
class Environment
|
7
|
+
# [Array[Local]] The local variables and arguments defined in this
|
8
|
+
# environment
|
9
|
+
attr_reader :locals
|
10
|
+
|
11
|
+
# This class tracks the occurrences of a local variable or argument
|
12
|
+
class Local
|
13
|
+
# [Symbol] The type of the local (e.g. :argument, :variable)
|
14
|
+
attr_reader :type
|
15
|
+
|
16
|
+
# [Array[Location]] The locations of all definitions and assignments of
|
17
|
+
# this local
|
18
|
+
attr_reader :definitions
|
19
|
+
|
20
|
+
# [Array[Location]] The locations of all usages of this local
|
21
|
+
attr_reader :usages
|
22
|
+
|
23
|
+
# initialize: (Symbol type) -> void
|
24
|
+
def initialize(type)
|
25
|
+
@type = type
|
26
|
+
@definitions = []
|
27
|
+
@usages = []
|
28
|
+
end
|
29
|
+
|
30
|
+
# add_definition: (Location location) -> void
|
31
|
+
def add_definition(location)
|
32
|
+
@definitions << location
|
33
|
+
end
|
34
|
+
|
35
|
+
# add_usage: (Location location) -> void
|
36
|
+
def add_usage(location)
|
37
|
+
@usages << location
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# initialize: (Environment | nil parent) -> void
|
42
|
+
def initialize(parent = nil)
|
43
|
+
@locals = {}
|
44
|
+
@parent = parent
|
45
|
+
end
|
46
|
+
|
47
|
+
# Adding a local definition will either insert a new entry in the locals
|
48
|
+
# hash or append a new definition location to an existing local. Notice that
|
49
|
+
# it's not possible to change the type of a local after it has been
|
50
|
+
# registered
|
51
|
+
# add_local_definition: (Ident | Label identifier, Symbol type) -> void
|
52
|
+
def add_local_definition(identifier, type)
|
53
|
+
name = identifier.value.delete_suffix(":")
|
54
|
+
|
55
|
+
@locals[name] ||= Local.new(type)
|
56
|
+
@locals[name].add_definition(identifier.location)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Adding a local usage will either insert a new entry in the locals
|
60
|
+
# hash or append a new usage location to an existing local. Notice that
|
61
|
+
# it's not possible to change the type of a local after it has been
|
62
|
+
# registered
|
63
|
+
# add_local_usage: (Ident | Label identifier, Symbol type) -> void
|
64
|
+
def add_local_usage(identifier, type)
|
65
|
+
name = identifier.value.delete_suffix(":")
|
66
|
+
|
67
|
+
@locals[name] ||= Local.new(type)
|
68
|
+
@locals[name].add_usage(identifier.location)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Try to find the local given its name in this environment or any of its
|
72
|
+
# parents
|
73
|
+
# find_local: (String name) -> Local | nil
|
74
|
+
def find_local(name)
|
75
|
+
local = @locals[name]
|
76
|
+
return local unless local.nil?
|
77
|
+
|
78
|
+
@parent&.find_local(name)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
# WithEnvironment is a module intended to be included in classes inheriting
|
5
|
+
# from Visitor. The module overrides a few visit methods to automatically keep
|
6
|
+
# track of local variables and arguments defined in the current environment.
|
7
|
+
# Example usage:
|
8
|
+
# class MyVisitor < Visitor
|
9
|
+
# include WithEnvironment
|
10
|
+
#
|
11
|
+
# def visit_ident(node)
|
12
|
+
# # Check if we're visiting an identifier for an argument, a local
|
13
|
+
# variable or something else
|
14
|
+
# local = current_environment.find_local(node)
|
15
|
+
#
|
16
|
+
# if local.type == :argument
|
17
|
+
# # handle identifiers for arguments
|
18
|
+
# elsif local.type == :variable
|
19
|
+
# # handle identifiers for variables
|
20
|
+
# else
|
21
|
+
# # handle other identifiers, such as method names
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
module WithEnvironment
|
25
|
+
def current_environment
|
26
|
+
@current_environment ||= Environment.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def with_new_environment
|
30
|
+
previous_environment = @current_environment
|
31
|
+
@current_environment = Environment.new(previous_environment)
|
32
|
+
yield
|
33
|
+
ensure
|
34
|
+
@current_environment = previous_environment
|
35
|
+
end
|
36
|
+
|
37
|
+
# Visits for nodes that create new environments, such as classes, modules
|
38
|
+
# and method definitions
|
39
|
+
def visit_class(node)
|
40
|
+
with_new_environment { super }
|
41
|
+
end
|
42
|
+
|
43
|
+
def visit_module(node)
|
44
|
+
with_new_environment { super }
|
45
|
+
end
|
46
|
+
|
47
|
+
def visit_method_add_block(node)
|
48
|
+
with_new_environment { super }
|
49
|
+
end
|
50
|
+
|
51
|
+
def visit_def(node)
|
52
|
+
with_new_environment { super }
|
53
|
+
end
|
54
|
+
|
55
|
+
def visit_defs(node)
|
56
|
+
with_new_environment { super }
|
57
|
+
end
|
58
|
+
|
59
|
+
def visit_def_endless(node)
|
60
|
+
with_new_environment { super }
|
61
|
+
end
|
62
|
+
|
63
|
+
# Visit for keeping track of local arguments, such as method and block
|
64
|
+
# arguments
|
65
|
+
def visit_params(node)
|
66
|
+
node.requireds.each do |param|
|
67
|
+
@current_environment.add_local_definition(param, :argument)
|
68
|
+
end
|
69
|
+
|
70
|
+
node.posts.each do |param|
|
71
|
+
@current_environment.add_local_definition(param, :argument)
|
72
|
+
end
|
73
|
+
|
74
|
+
node.keywords.each do |param|
|
75
|
+
@current_environment.add_local_definition(param.first, :argument)
|
76
|
+
end
|
77
|
+
|
78
|
+
node.optionals.each do |param|
|
79
|
+
@current_environment.add_local_definition(param.first, :argument)
|
80
|
+
end
|
81
|
+
|
82
|
+
super
|
83
|
+
end
|
84
|
+
|
85
|
+
def visit_rest_param(node)
|
86
|
+
name = node.name
|
87
|
+
@current_environment.add_local_definition(name, :argument) if name
|
88
|
+
|
89
|
+
super
|
90
|
+
end
|
91
|
+
|
92
|
+
def visit_kwrest_param(node)
|
93
|
+
name = node.name
|
94
|
+
@current_environment.add_local_definition(name, :argument) if name
|
95
|
+
|
96
|
+
super
|
97
|
+
end
|
98
|
+
|
99
|
+
def visit_blockarg(node)
|
100
|
+
name = node.name
|
101
|
+
@current_environment.add_local_definition(name, :argument) if name
|
102
|
+
|
103
|
+
super
|
104
|
+
end
|
105
|
+
|
106
|
+
# Visit for keeping track of local variable definitions
|
107
|
+
def visit_var_field(node)
|
108
|
+
value = node.value
|
109
|
+
|
110
|
+
if value.is_a?(SyntaxTree::Ident)
|
111
|
+
@current_environment.add_local_definition(value, :variable)
|
112
|
+
end
|
113
|
+
|
114
|
+
super
|
115
|
+
end
|
116
|
+
|
117
|
+
alias visit_pinned_var_ref visit_var_field
|
118
|
+
|
119
|
+
# Visits for keeping track of variable and argument usages
|
120
|
+
def visit_aref_field(node)
|
121
|
+
name = node.collection.value
|
122
|
+
@current_environment.add_local_usage(name, :variable) if name
|
123
|
+
|
124
|
+
super
|
125
|
+
end
|
126
|
+
|
127
|
+
def visit_var_ref(node)
|
128
|
+
value = node.value
|
129
|
+
|
130
|
+
if value.is_a?(SyntaxTree::Ident)
|
131
|
+
definition = @current_environment.find_local(value.value)
|
132
|
+
|
133
|
+
if definition
|
134
|
+
@current_environment.add_local_usage(value, definition.type)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
super
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/lib/syntax_tree.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "delegate"
|
4
3
|
require "etc"
|
5
4
|
require "json"
|
6
5
|
require "pp"
|
@@ -10,7 +9,6 @@ require "stringio"
|
|
10
9
|
|
11
10
|
require_relative "syntax_tree/formatter"
|
12
11
|
require_relative "syntax_tree/node"
|
13
|
-
require_relative "syntax_tree/parser"
|
14
12
|
require_relative "syntax_tree/version"
|
15
13
|
|
16
14
|
require_relative "syntax_tree/basic_visitor"
|
@@ -19,6 +17,20 @@ require_relative "syntax_tree/visitor/field_visitor"
|
|
19
17
|
require_relative "syntax_tree/visitor/json_visitor"
|
20
18
|
require_relative "syntax_tree/visitor/match_visitor"
|
21
19
|
require_relative "syntax_tree/visitor/pretty_print_visitor"
|
20
|
+
require_relative "syntax_tree/visitor/environment"
|
21
|
+
require_relative "syntax_tree/visitor/with_environment"
|
22
|
+
|
23
|
+
require_relative "syntax_tree/parser"
|
24
|
+
|
25
|
+
# We rely on Symbol#name being available, which is only available in Ruby 3.0+.
|
26
|
+
# In case we're running on an older Ruby version, we polyfill it here.
|
27
|
+
unless :+.respond_to?(:name)
|
28
|
+
class Symbol # rubocop:disable Style/Documentation
|
29
|
+
def name
|
30
|
+
to_s.freeze
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
22
34
|
|
23
35
|
# Syntax Tree is a suite of tools built on top of the internal CRuby parser. It
|
24
36
|
# provides the ability to generate a syntax tree from source, as well as the
|
data/syntax_tree.gemspec
CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
26
|
spec.require_paths = %w[lib]
|
27
27
|
|
28
|
-
spec.add_dependency "prettier_print"
|
28
|
+
spec.add_dependency "prettier_print", ">= 1.0.0"
|
29
29
|
|
30
30
|
spec.add_development_dependency "bundler"
|
31
31
|
spec.add_development_dependency "minitest"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: syntax_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Newton
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-10-
|
11
|
+
date: 2022-10-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: prettier_print
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 1.0.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 1.0.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -137,10 +137,12 @@ files:
|
|
137
137
|
- lib/syntax_tree/rake_tasks.rb
|
138
138
|
- lib/syntax_tree/version.rb
|
139
139
|
- lib/syntax_tree/visitor.rb
|
140
|
+
- lib/syntax_tree/visitor/environment.rb
|
140
141
|
- lib/syntax_tree/visitor/field_visitor.rb
|
141
142
|
- lib/syntax_tree/visitor/json_visitor.rb
|
142
143
|
- lib/syntax_tree/visitor/match_visitor.rb
|
143
144
|
- lib/syntax_tree/visitor/pretty_print_visitor.rb
|
145
|
+
- lib/syntax_tree/visitor/with_environment.rb
|
144
146
|
- syntax_tree.gemspec
|
145
147
|
homepage: https://github.com/kddnewton/syntax_tree
|
146
148
|
licenses:
|