syntax_tree 5.3.0 → 6.0.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 +4 -4
- data/.rubocop.yml +12 -1
- data/CHANGELOG.md +64 -1
- data/Gemfile.lock +2 -2
- data/README.md +28 -9
- data/Rakefile +12 -8
- data/bin/console +1 -0
- data/bin/whitequark +79 -0
- data/doc/changing_structure.md +16 -0
- data/lib/syntax_tree/basic_visitor.rb +44 -5
- data/lib/syntax_tree/cli.rb +2 -2
- data/lib/syntax_tree/dsl.rb +23 -11
- data/lib/syntax_tree/{visitor/field_visitor.rb → field_visitor.rb} +54 -55
- data/lib/syntax_tree/formatter.rb +1 -1
- data/lib/syntax_tree/index.rb +56 -54
- data/lib/syntax_tree/json_visitor.rb +55 -0
- data/lib/syntax_tree/language_server.rb +157 -2
- data/lib/syntax_tree/match_visitor.rb +120 -0
- data/lib/syntax_tree/mermaid.rb +177 -0
- data/lib/syntax_tree/mermaid_visitor.rb +69 -0
- data/lib/syntax_tree/{visitor/mutation_visitor.rb → mutation_visitor.rb} +27 -27
- data/lib/syntax_tree/node.rb +198 -107
- data/lib/syntax_tree/parser.rb +322 -118
- data/lib/syntax_tree/pretty_print_visitor.rb +83 -0
- data/lib/syntax_tree/reflection.rb +241 -0
- data/lib/syntax_tree/translation/parser.rb +3019 -0
- data/lib/syntax_tree/translation/rubocop_ast.rb +21 -0
- data/lib/syntax_tree/translation.rb +28 -0
- data/lib/syntax_tree/version.rb +1 -1
- data/lib/syntax_tree/with_scope.rb +244 -0
- data/lib/syntax_tree/yarv/basic_block.rb +53 -0
- data/lib/syntax_tree/yarv/calldata.rb +91 -0
- data/lib/syntax_tree/yarv/compiler.rb +110 -100
- data/lib/syntax_tree/yarv/control_flow_graph.rb +257 -0
- data/lib/syntax_tree/yarv/data_flow_graph.rb +338 -0
- data/lib/syntax_tree/yarv/decompiler.rb +1 -1
- data/lib/syntax_tree/yarv/disassembler.rb +104 -80
- data/lib/syntax_tree/yarv/instruction_sequence.rb +43 -18
- data/lib/syntax_tree/yarv/instructions.rb +203 -649
- data/lib/syntax_tree/yarv/legacy.rb +12 -24
- data/lib/syntax_tree/yarv/sea_of_nodes.rb +534 -0
- data/lib/syntax_tree/yarv.rb +18 -0
- data/lib/syntax_tree.rb +88 -56
- data/tasks/sorbet.rake +277 -0
- data/tasks/whitequark.rake +87 -0
- metadata +23 -11
- data/.gitmodules +0 -9
- data/lib/syntax_tree/language_server/inlay_hints.rb +0 -159
- data/lib/syntax_tree/visitor/environment.rb +0 -84
- data/lib/syntax_tree/visitor/json_visitor.rb +0 -55
- data/lib/syntax_tree/visitor/match_visitor.rb +0 -122
- data/lib/syntax_tree/visitor/pretty_print_visitor.rb +0 -85
- data/lib/syntax_tree/visitor/with_environment.rb +0 -140
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
module Translation
|
5
|
+
# This visitor is responsible for converting the syntax tree produced by
|
6
|
+
# Syntax Tree into the syntax tree produced by the rubocop/rubocop-ast gem.
|
7
|
+
class RuboCopAST < Parser
|
8
|
+
private
|
9
|
+
|
10
|
+
# This method is effectively the same thing as the parser gem except that
|
11
|
+
# it uses the rubocop-ast specializations of the nodes.
|
12
|
+
def s(type, children, location)
|
13
|
+
::RuboCop::AST::Builder::NODE_MAP.fetch(type, ::RuboCop::AST::Node).new(
|
14
|
+
type,
|
15
|
+
children,
|
16
|
+
location: location
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
# This module is responsible for translating the Syntax Tree syntax tree into
|
5
|
+
# other representations.
|
6
|
+
module Translation
|
7
|
+
# This method translates the given node into the representation defined by
|
8
|
+
# the whitequark/parser gem. We don't explicitly list it as a dependency
|
9
|
+
# because it's not required for the core functionality of Syntax Tree.
|
10
|
+
def self.to_parser(node, buffer)
|
11
|
+
require "parser"
|
12
|
+
require_relative "translation/parser"
|
13
|
+
|
14
|
+
node.accept(Parser.new(buffer))
|
15
|
+
end
|
16
|
+
|
17
|
+
# This method translates the given node into the representation defined by
|
18
|
+
# the rubocop/rubocop-ast gem. We don't explicitly list it as a dependency
|
19
|
+
# because it's not required for the core functionality of Syntax Tree.
|
20
|
+
def self.to_rubocop_ast(node, buffer)
|
21
|
+
require "rubocop/ast"
|
22
|
+
require_relative "translation/parser"
|
23
|
+
require_relative "translation/rubocop_ast"
|
24
|
+
|
25
|
+
node.accept(RuboCopAST.new(buffer))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/syntax_tree/version.rb
CHANGED
@@ -0,0 +1,244 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
# WithScope is a module intended to be included in classes inheriting from
|
5
|
+
# Visitor. The module overrides a few visit methods to automatically keep
|
6
|
+
# track of local variables and arguments defined in the current scope.
|
7
|
+
# Example usage:
|
8
|
+
#
|
9
|
+
# class MyVisitor < Visitor
|
10
|
+
# include WithScope
|
11
|
+
#
|
12
|
+
# def visit_ident(node)
|
13
|
+
# # Check if we're visiting an identifier for an argument, a local
|
14
|
+
# # variable or something else
|
15
|
+
# local = current_scope.find_local(node)
|
16
|
+
#
|
17
|
+
# if local.type == :argument
|
18
|
+
# # handle identifiers for arguments
|
19
|
+
# elsif local.type == :variable
|
20
|
+
# # handle identifiers for variables
|
21
|
+
# else
|
22
|
+
# # handle other identifiers, such as method names
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
module WithScope
|
28
|
+
# The scope class is used to keep track of local variables and arguments
|
29
|
+
# inside a particular scope.
|
30
|
+
class Scope
|
31
|
+
# This class tracks the occurrences of a local variable or argument.
|
32
|
+
class Local
|
33
|
+
# [Symbol] The type of the local (e.g. :argument, :variable)
|
34
|
+
attr_reader :type
|
35
|
+
|
36
|
+
# [Array[Location]] The locations of all definitions and assignments of
|
37
|
+
# this local
|
38
|
+
attr_reader :definitions
|
39
|
+
|
40
|
+
# [Array[Location]] The locations of all usages of this local
|
41
|
+
attr_reader :usages
|
42
|
+
|
43
|
+
def initialize(type)
|
44
|
+
@type = type
|
45
|
+
@definitions = []
|
46
|
+
@usages = []
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_definition(location)
|
50
|
+
@definitions << location
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_usage(location)
|
54
|
+
@usages << location
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# [Integer] a unique identifier for this scope
|
59
|
+
attr_reader :id
|
60
|
+
|
61
|
+
# [scope | nil] The parent scope
|
62
|
+
attr_reader :parent
|
63
|
+
|
64
|
+
# [Hash[String, Local]] The local variables and arguments defined in this
|
65
|
+
# scope
|
66
|
+
attr_reader :locals
|
67
|
+
|
68
|
+
def initialize(id, parent = nil)
|
69
|
+
@id = id
|
70
|
+
@parent = parent
|
71
|
+
@locals = {}
|
72
|
+
end
|
73
|
+
|
74
|
+
# Adding a local definition will either insert a new entry in the locals
|
75
|
+
# hash or append a new definition location to an existing local. Notice
|
76
|
+
# that it's not possible to change the type of a local after it has been
|
77
|
+
# registered.
|
78
|
+
def add_local_definition(identifier, type)
|
79
|
+
name = identifier.value.delete_suffix(":")
|
80
|
+
|
81
|
+
local =
|
82
|
+
if type == :argument
|
83
|
+
locals[name] ||= Local.new(type)
|
84
|
+
else
|
85
|
+
resolve_local(name, type)
|
86
|
+
end
|
87
|
+
|
88
|
+
local.add_definition(identifier.location)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Adding a local usage will either insert a new entry in the locals
|
92
|
+
# hash or append a new usage location to an existing local. Notice that
|
93
|
+
# it's not possible to change the type of a local after it has been
|
94
|
+
# registered.
|
95
|
+
def add_local_usage(identifier, type)
|
96
|
+
name = identifier.value.delete_suffix(":")
|
97
|
+
resolve_local(name, type).add_usage(identifier.location)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Try to find the local given its name in this scope or any of its
|
101
|
+
# parents.
|
102
|
+
def find_local(name)
|
103
|
+
locals[name] || parent&.find_local(name)
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def resolve_local(name, type)
|
109
|
+
local = find_local(name)
|
110
|
+
|
111
|
+
unless local
|
112
|
+
local = Local.new(type)
|
113
|
+
locals[name] = local
|
114
|
+
end
|
115
|
+
|
116
|
+
local
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
attr_reader :current_scope
|
121
|
+
|
122
|
+
def initialize(*args, **kwargs, &block)
|
123
|
+
super
|
124
|
+
|
125
|
+
@current_scope = Scope.new(0)
|
126
|
+
@next_scope_id = 0
|
127
|
+
end
|
128
|
+
|
129
|
+
# Visits for nodes that create new scopes, such as classes, modules
|
130
|
+
# and method definitions.
|
131
|
+
def visit_class(node)
|
132
|
+
with_scope { super }
|
133
|
+
end
|
134
|
+
|
135
|
+
def visit_module(node)
|
136
|
+
with_scope { super }
|
137
|
+
end
|
138
|
+
|
139
|
+
# When we find a method invocation with a block, only the code that happens
|
140
|
+
# inside of the block needs a fresh scope. The method invocation
|
141
|
+
# itself happens in the same scope.
|
142
|
+
def visit_method_add_block(node)
|
143
|
+
visit(node.call)
|
144
|
+
with_scope(current_scope) { visit(node.block) }
|
145
|
+
end
|
146
|
+
|
147
|
+
def visit_def(node)
|
148
|
+
with_scope { super }
|
149
|
+
end
|
150
|
+
|
151
|
+
# Visit for keeping track of local arguments, such as method and block
|
152
|
+
# arguments.
|
153
|
+
def visit_params(node)
|
154
|
+
add_argument_definitions(node.requireds)
|
155
|
+
|
156
|
+
node.posts.each do |param|
|
157
|
+
current_scope.add_local_definition(param, :argument)
|
158
|
+
end
|
159
|
+
|
160
|
+
node.keywords.each do |param|
|
161
|
+
current_scope.add_local_definition(param.first, :argument)
|
162
|
+
end
|
163
|
+
|
164
|
+
node.optionals.each do |param|
|
165
|
+
current_scope.add_local_definition(param.first, :argument)
|
166
|
+
end
|
167
|
+
|
168
|
+
super
|
169
|
+
end
|
170
|
+
|
171
|
+
def visit_rest_param(node)
|
172
|
+
name = node.name
|
173
|
+
current_scope.add_local_definition(name, :argument) if name
|
174
|
+
|
175
|
+
super
|
176
|
+
end
|
177
|
+
|
178
|
+
def visit_kwrest_param(node)
|
179
|
+
name = node.name
|
180
|
+
current_scope.add_local_definition(name, :argument) if name
|
181
|
+
|
182
|
+
super
|
183
|
+
end
|
184
|
+
|
185
|
+
def visit_blockarg(node)
|
186
|
+
name = node.name
|
187
|
+
current_scope.add_local_definition(name, :argument) if name
|
188
|
+
|
189
|
+
super
|
190
|
+
end
|
191
|
+
|
192
|
+
# Visit for keeping track of local variable definitions
|
193
|
+
def visit_var_field(node)
|
194
|
+
value = node.value
|
195
|
+
current_scope.add_local_definition(value, :variable) if value.is_a?(Ident)
|
196
|
+
|
197
|
+
super
|
198
|
+
end
|
199
|
+
|
200
|
+
# Visit for keeping track of local variable definitions
|
201
|
+
def visit_pinned_var_ref(node)
|
202
|
+
value = node.value
|
203
|
+
current_scope.add_local_usage(value, :variable) if value.is_a?(Ident)
|
204
|
+
|
205
|
+
super
|
206
|
+
end
|
207
|
+
|
208
|
+
# Visits for keeping track of variable and argument usages
|
209
|
+
def visit_var_ref(node)
|
210
|
+
value = node.value
|
211
|
+
|
212
|
+
if value.is_a?(Ident)
|
213
|
+
definition = current_scope.find_local(value.value)
|
214
|
+
current_scope.add_local_usage(value, definition.type) if definition
|
215
|
+
end
|
216
|
+
|
217
|
+
super
|
218
|
+
end
|
219
|
+
|
220
|
+
private
|
221
|
+
|
222
|
+
def add_argument_definitions(list)
|
223
|
+
list.each do |param|
|
224
|
+
if param.is_a?(SyntaxTree::MLHSParen)
|
225
|
+
add_argument_definitions(param.contents.parts)
|
226
|
+
else
|
227
|
+
current_scope.add_local_definition(param, :argument)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def next_scope_id
|
233
|
+
@next_scope_id += 1
|
234
|
+
end
|
235
|
+
|
236
|
+
def with_scope(parent_scope = nil)
|
237
|
+
previous_scope = @current_scope
|
238
|
+
@current_scope = Scope.new(next_scope_id, parent_scope)
|
239
|
+
yield
|
240
|
+
ensure
|
241
|
+
@current_scope = previous_scope
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
module YARV
|
5
|
+
# This object represents a single basic block, wherein all contained
|
6
|
+
# instructions do not branch except for the last one.
|
7
|
+
class BasicBlock
|
8
|
+
# This is the unique identifier for this basic block.
|
9
|
+
attr_reader :id
|
10
|
+
|
11
|
+
# This is the index into the list of instructions where this block starts.
|
12
|
+
attr_reader :block_start
|
13
|
+
|
14
|
+
# This is the set of instructions that this block contains.
|
15
|
+
attr_reader :insns
|
16
|
+
|
17
|
+
# This is an array of basic blocks that lead into this block.
|
18
|
+
attr_reader :incoming_blocks
|
19
|
+
|
20
|
+
# This is an array of basic blocks that this block leads into.
|
21
|
+
attr_reader :outgoing_blocks
|
22
|
+
|
23
|
+
def initialize(block_start, insns)
|
24
|
+
@id = "block_#{block_start}"
|
25
|
+
|
26
|
+
@block_start = block_start
|
27
|
+
@insns = insns
|
28
|
+
|
29
|
+
@incoming_blocks = []
|
30
|
+
@outgoing_blocks = []
|
31
|
+
end
|
32
|
+
|
33
|
+
# Yield each instruction in this basic block along with its index from the
|
34
|
+
# original instruction sequence.
|
35
|
+
def each_with_length
|
36
|
+
return enum_for(:each_with_length) unless block_given?
|
37
|
+
|
38
|
+
length = block_start
|
39
|
+
insns.each do |insn|
|
40
|
+
yield insn, length
|
41
|
+
length += insn.length
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# This method is used to verify that the basic block is well formed. It
|
46
|
+
# checks that the only instruction in this basic block that branches is
|
47
|
+
# the last instruction.
|
48
|
+
def verify
|
49
|
+
insns[0...-1].each { |insn| raise unless insn.branch_targets.empty? }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
module YARV
|
5
|
+
# This is an operand to various YARV instructions that represents the
|
6
|
+
# information about a specific call site.
|
7
|
+
class CallData
|
8
|
+
CALL_ARGS_SPLAT = 1 << 0
|
9
|
+
CALL_ARGS_BLOCKARG = 1 << 1
|
10
|
+
CALL_FCALL = 1 << 2
|
11
|
+
CALL_VCALL = 1 << 3
|
12
|
+
CALL_ARGS_SIMPLE = 1 << 4
|
13
|
+
CALL_BLOCKISEQ = 1 << 5
|
14
|
+
CALL_KWARG = 1 << 6
|
15
|
+
CALL_KW_SPLAT = 1 << 7
|
16
|
+
CALL_TAILCALL = 1 << 8
|
17
|
+
CALL_SUPER = 1 << 9
|
18
|
+
CALL_ZSUPER = 1 << 10
|
19
|
+
CALL_OPT_SEND = 1 << 11
|
20
|
+
CALL_KW_SPLAT_MUT = 1 << 12
|
21
|
+
|
22
|
+
attr_reader :method, :argc, :flags, :kw_arg
|
23
|
+
|
24
|
+
def initialize(
|
25
|
+
method,
|
26
|
+
argc = 0,
|
27
|
+
flags = CallData::CALL_ARGS_SIMPLE,
|
28
|
+
kw_arg = nil
|
29
|
+
)
|
30
|
+
@method = method
|
31
|
+
@argc = argc
|
32
|
+
@flags = flags
|
33
|
+
@kw_arg = kw_arg
|
34
|
+
end
|
35
|
+
|
36
|
+
def flag?(mask)
|
37
|
+
(flags & mask) > 0
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_h
|
41
|
+
result = { mid: method, flag: flags, orig_argc: argc }
|
42
|
+
result[:kw_arg] = kw_arg if kw_arg
|
43
|
+
result
|
44
|
+
end
|
45
|
+
|
46
|
+
def inspect
|
47
|
+
names = []
|
48
|
+
names << :ARGS_SPLAT if flag?(CALL_ARGS_SPLAT)
|
49
|
+
names << :ARGS_BLOCKARG if flag?(CALL_ARGS_BLOCKARG)
|
50
|
+
names << :FCALL if flag?(CALL_FCALL)
|
51
|
+
names << :VCALL if flag?(CALL_VCALL)
|
52
|
+
names << :ARGS_SIMPLE if flag?(CALL_ARGS_SIMPLE)
|
53
|
+
names << :BLOCKISEQ if flag?(CALL_BLOCKISEQ)
|
54
|
+
names << :KWARG if flag?(CALL_KWARG)
|
55
|
+
names << :KW_SPLAT if flag?(CALL_KW_SPLAT)
|
56
|
+
names << :TAILCALL if flag?(CALL_TAILCALL)
|
57
|
+
names << :SUPER if flag?(CALL_SUPER)
|
58
|
+
names << :ZSUPER if flag?(CALL_ZSUPER)
|
59
|
+
names << :OPT_SEND if flag?(CALL_OPT_SEND)
|
60
|
+
names << :KW_SPLAT_MUT if flag?(CALL_KW_SPLAT_MUT)
|
61
|
+
|
62
|
+
parts = []
|
63
|
+
parts << "mid:#{method}" if method
|
64
|
+
parts << "argc:#{argc}"
|
65
|
+
parts << "kw:[#{kw_arg.join(", ")}]" if kw_arg
|
66
|
+
parts << names.join("|") if names.any?
|
67
|
+
|
68
|
+
"<calldata!#{parts.join(", ")}>"
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.from(serialized)
|
72
|
+
new(
|
73
|
+
serialized[:mid],
|
74
|
+
serialized[:orig_argc],
|
75
|
+
serialized[:flag],
|
76
|
+
serialized[:kw_arg]
|
77
|
+
)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# A convenience method for creating a CallData object.
|
82
|
+
def self.calldata(
|
83
|
+
method,
|
84
|
+
argc = 0,
|
85
|
+
flags = CallData::CALL_ARGS_SIMPLE,
|
86
|
+
kw_arg = nil
|
87
|
+
)
|
88
|
+
CallData.new(method, argc, flags, kw_arg)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -8,7 +8,7 @@ module SyntaxTree
|
|
8
8
|
#
|
9
9
|
# You use this as with any other visitor. First you parse code into a tree,
|
10
10
|
# then you visit it with this compiler. Visiting the root node of the tree
|
11
|
-
# will return a SyntaxTree::
|
11
|
+
# will return a SyntaxTree::YARV::Compiler::InstructionSequence object.
|
12
12
|
# With that object you can call #to_a on it, which will return a serialized
|
13
13
|
# form of the instruction sequence as an array. This array _should_ mirror
|
14
14
|
# the array given by RubyVM::InstructionSequence#to_a.
|
@@ -124,76 +124,122 @@ module SyntaxTree
|
|
124
124
|
rescue CompilationError
|
125
125
|
end
|
126
126
|
|
127
|
-
|
128
|
-
|
129
|
-
|
127
|
+
visit_methods do
|
128
|
+
def visit_array(node)
|
129
|
+
node.contents ? visit_all(node.contents.parts) : []
|
130
|
+
end
|
130
131
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
132
|
+
def visit_bare_assoc_hash(node)
|
133
|
+
node.assocs.to_h do |assoc|
|
134
|
+
# We can only convert regular key-value pairs. A double splat **
|
135
|
+
# operator means it has to be converted at run-time.
|
136
|
+
raise CompilationError unless assoc.is_a?(Assoc)
|
137
|
+
[visit(assoc.key), visit(assoc.value)]
|
138
|
+
end
|
137
139
|
end
|
138
|
-
end
|
139
140
|
|
140
|
-
|
141
|
-
|
142
|
-
|
141
|
+
def visit_float(node)
|
142
|
+
node.value.to_f
|
143
|
+
end
|
143
144
|
|
144
|
-
|
145
|
+
alias visit_hash visit_bare_assoc_hash
|
145
146
|
|
146
|
-
|
147
|
-
|
148
|
-
|
147
|
+
def visit_imaginary(node)
|
148
|
+
node.value.to_c
|
149
|
+
end
|
149
150
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
151
|
+
def visit_int(node)
|
152
|
+
case (value = node.value)
|
153
|
+
when /^0b/
|
154
|
+
value[2..].to_i(2)
|
155
|
+
when /^0o/
|
156
|
+
value[2..].to_i(8)
|
157
|
+
when /^0d/
|
158
|
+
value[2..].to_i
|
159
|
+
when /^0x/
|
160
|
+
value[2..].to_i(16)
|
161
|
+
else
|
162
|
+
value.to_i
|
163
|
+
end
|
162
164
|
end
|
163
|
-
end
|
164
165
|
|
165
|
-
|
166
|
-
|
167
|
-
|
166
|
+
def visit_label(node)
|
167
|
+
node.value.chomp(":").to_sym
|
168
|
+
end
|
168
169
|
|
169
|
-
|
170
|
-
|
171
|
-
|
170
|
+
def visit_mrhs(node)
|
171
|
+
visit_all(node.parts)
|
172
|
+
end
|
172
173
|
|
173
|
-
|
174
|
-
|
175
|
-
|
174
|
+
def visit_qsymbols(node)
|
175
|
+
node.elements.map { |element| visit(element).to_sym }
|
176
|
+
end
|
176
177
|
|
177
|
-
|
178
|
-
|
179
|
-
|
178
|
+
def visit_qwords(node)
|
179
|
+
visit_all(node.elements)
|
180
|
+
end
|
180
181
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
182
|
+
def visit_range(node)
|
183
|
+
left, right = [visit(node.left), visit(node.right)]
|
184
|
+
node.operator.value === ".." ? left..right : left...right
|
185
|
+
end
|
185
186
|
|
186
|
-
|
187
|
-
|
188
|
-
|
187
|
+
def visit_rational(node)
|
188
|
+
node.value.to_r
|
189
|
+
end
|
189
190
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
191
|
+
def visit_regexp_literal(node)
|
192
|
+
if node.parts.length == 1 && node.parts.first.is_a?(TStringContent)
|
193
|
+
Regexp.new(
|
194
|
+
node.parts.first.value,
|
195
|
+
visit_regexp_literal_flags(node)
|
196
|
+
)
|
197
|
+
else
|
198
|
+
# Any interpolation of expressions or variables will result in the
|
199
|
+
# regular expression being constructed at run-time.
|
200
|
+
raise CompilationError
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def visit_symbol_literal(node)
|
205
|
+
node.value.value.to_sym
|
206
|
+
end
|
207
|
+
|
208
|
+
def visit_symbols(node)
|
209
|
+
node.elements.map { |element| visit(element).to_sym }
|
210
|
+
end
|
211
|
+
|
212
|
+
def visit_tstring_content(node)
|
213
|
+
node.value
|
214
|
+
end
|
215
|
+
|
216
|
+
def visit_var_ref(node)
|
217
|
+
raise CompilationError unless node.value.is_a?(Kw)
|
218
|
+
|
219
|
+
case node.value.value
|
220
|
+
when "nil"
|
221
|
+
nil
|
222
|
+
when "true"
|
223
|
+
true
|
224
|
+
when "false"
|
225
|
+
false
|
226
|
+
else
|
227
|
+
raise CompilationError
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def visit_word(node)
|
232
|
+
if node.parts.length == 1 && node.parts.first.is_a?(TStringContent)
|
233
|
+
node.parts.first.value
|
234
|
+
else
|
235
|
+
# Any interpolation of expressions or variables will result in the
|
236
|
+
# string being constructed at run-time.
|
237
|
+
raise CompilationError
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def visit_words(node)
|
242
|
+
visit_all(node.elements)
|
197
243
|
end
|
198
244
|
end
|
199
245
|
|
@@ -219,47 +265,6 @@ module SyntaxTree
|
|
219
265
|
end
|
220
266
|
end
|
221
267
|
|
222
|
-
def visit_symbol_literal(node)
|
223
|
-
node.value.value.to_sym
|
224
|
-
end
|
225
|
-
|
226
|
-
def visit_symbols(node)
|
227
|
-
node.elements.map { |element| visit(element).to_sym }
|
228
|
-
end
|
229
|
-
|
230
|
-
def visit_tstring_content(node)
|
231
|
-
node.value
|
232
|
-
end
|
233
|
-
|
234
|
-
def visit_var_ref(node)
|
235
|
-
raise CompilationError unless node.value.is_a?(Kw)
|
236
|
-
|
237
|
-
case node.value.value
|
238
|
-
when "nil"
|
239
|
-
nil
|
240
|
-
when "true"
|
241
|
-
true
|
242
|
-
when "false"
|
243
|
-
false
|
244
|
-
else
|
245
|
-
raise CompilationError
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
def visit_word(node)
|
250
|
-
if node.parts.length == 1 && node.parts.first.is_a?(TStringContent)
|
251
|
-
node.parts.first.value
|
252
|
-
else
|
253
|
-
# Any interpolation of expressions or variables will result in the
|
254
|
-
# string being constructed at run-time.
|
255
|
-
raise CompilationError
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
def visit_words(node)
|
260
|
-
visit_all(node.elements)
|
261
|
-
end
|
262
|
-
|
263
268
|
def visit_unsupported(_node)
|
264
269
|
raise CompilationError
|
265
270
|
end
|
@@ -1050,11 +1055,16 @@ module SyntaxTree
|
|
1050
1055
|
visit_if(
|
1051
1056
|
IfNode.new(
|
1052
1057
|
predicate: node.predicate,
|
1053
|
-
statements:
|
1058
|
+
statements:
|
1059
|
+
Statements.new(body: [node.truthy], location: Location.default),
|
1054
1060
|
consequent:
|
1055
1061
|
Else.new(
|
1056
1062
|
keyword: Kw.new(value: "else", location: Location.default),
|
1057
|
-
statements:
|
1063
|
+
statements:
|
1064
|
+
Statements.new(
|
1065
|
+
body: [node.falsy],
|
1066
|
+
location: Location.default
|
1067
|
+
),
|
1058
1068
|
location: Location.default
|
1059
1069
|
),
|
1060
1070
|
location: Location.default
|