syntax_tree 5.3.0 → 6.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 +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
|