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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -1
  3. data/CHANGELOG.md +64 -1
  4. data/Gemfile.lock +2 -2
  5. data/README.md +28 -9
  6. data/Rakefile +12 -8
  7. data/bin/console +1 -0
  8. data/bin/whitequark +79 -0
  9. data/doc/changing_structure.md +16 -0
  10. data/lib/syntax_tree/basic_visitor.rb +44 -5
  11. data/lib/syntax_tree/cli.rb +2 -2
  12. data/lib/syntax_tree/dsl.rb +23 -11
  13. data/lib/syntax_tree/{visitor/field_visitor.rb → field_visitor.rb} +54 -55
  14. data/lib/syntax_tree/formatter.rb +1 -1
  15. data/lib/syntax_tree/index.rb +56 -54
  16. data/lib/syntax_tree/json_visitor.rb +55 -0
  17. data/lib/syntax_tree/language_server.rb +157 -2
  18. data/lib/syntax_tree/match_visitor.rb +120 -0
  19. data/lib/syntax_tree/mermaid.rb +177 -0
  20. data/lib/syntax_tree/mermaid_visitor.rb +69 -0
  21. data/lib/syntax_tree/{visitor/mutation_visitor.rb → mutation_visitor.rb} +27 -27
  22. data/lib/syntax_tree/node.rb +198 -107
  23. data/lib/syntax_tree/parser.rb +322 -118
  24. data/lib/syntax_tree/pretty_print_visitor.rb +83 -0
  25. data/lib/syntax_tree/reflection.rb +241 -0
  26. data/lib/syntax_tree/translation/parser.rb +3019 -0
  27. data/lib/syntax_tree/translation/rubocop_ast.rb +21 -0
  28. data/lib/syntax_tree/translation.rb +28 -0
  29. data/lib/syntax_tree/version.rb +1 -1
  30. data/lib/syntax_tree/with_scope.rb +244 -0
  31. data/lib/syntax_tree/yarv/basic_block.rb +53 -0
  32. data/lib/syntax_tree/yarv/calldata.rb +91 -0
  33. data/lib/syntax_tree/yarv/compiler.rb +110 -100
  34. data/lib/syntax_tree/yarv/control_flow_graph.rb +257 -0
  35. data/lib/syntax_tree/yarv/data_flow_graph.rb +338 -0
  36. data/lib/syntax_tree/yarv/decompiler.rb +1 -1
  37. data/lib/syntax_tree/yarv/disassembler.rb +104 -80
  38. data/lib/syntax_tree/yarv/instruction_sequence.rb +43 -18
  39. data/lib/syntax_tree/yarv/instructions.rb +203 -649
  40. data/lib/syntax_tree/yarv/legacy.rb +12 -24
  41. data/lib/syntax_tree/yarv/sea_of_nodes.rb +534 -0
  42. data/lib/syntax_tree/yarv.rb +18 -0
  43. data/lib/syntax_tree.rb +88 -56
  44. data/tasks/sorbet.rake +277 -0
  45. data/tasks/whitequark.rake +87 -0
  46. metadata +23 -11
  47. data/.gitmodules +0 -9
  48. data/lib/syntax_tree/language_server/inlay_hints.rb +0 -159
  49. data/lib/syntax_tree/visitor/environment.rb +0 -84
  50. data/lib/syntax_tree/visitor/json_visitor.rb +0 -55
  51. data/lib/syntax_tree/visitor/match_visitor.rb +0 -122
  52. data/lib/syntax_tree/visitor/pretty_print_visitor.rb +0 -85
  53. 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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SyntaxTree
4
- VERSION = "5.3.0"
4
+ VERSION = "6.0.0"
5
5
  end
@@ -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::Visitor::Compiler::InstructionSequence object.
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
- def visit_array(node)
128
- node.contents ? visit_all(node.contents.parts) : []
129
- end
127
+ visit_methods do
128
+ def visit_array(node)
129
+ node.contents ? visit_all(node.contents.parts) : []
130
+ end
130
131
 
131
- def visit_bare_assoc_hash(node)
132
- node.assocs.to_h do |assoc|
133
- # We can only convert regular key-value pairs. A double splat **
134
- # operator means it has to be converted at run-time.
135
- raise CompilationError unless assoc.is_a?(Assoc)
136
- [visit(assoc.key), visit(assoc.value)]
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
- def visit_float(node)
141
- node.value.to_f
142
- end
141
+ def visit_float(node)
142
+ node.value.to_f
143
+ end
143
144
 
144
- alias visit_hash visit_bare_assoc_hash
145
+ alias visit_hash visit_bare_assoc_hash
145
146
 
146
- def visit_imaginary(node)
147
- node.value.to_c
148
- end
147
+ def visit_imaginary(node)
148
+ node.value.to_c
149
+ end
149
150
 
150
- def visit_int(node)
151
- case (value = node.value)
152
- when /^0b/
153
- value[2..].to_i(2)
154
- when /^0o/
155
- value[2..].to_i(8)
156
- when /^0d/
157
- value[2..].to_i
158
- when /^0x/
159
- value[2..].to_i(16)
160
- else
161
- value.to_i
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
- def visit_label(node)
166
- node.value.chomp(":").to_sym
167
- end
166
+ def visit_label(node)
167
+ node.value.chomp(":").to_sym
168
+ end
168
169
 
169
- def visit_mrhs(node)
170
- visit_all(node.parts)
171
- end
170
+ def visit_mrhs(node)
171
+ visit_all(node.parts)
172
+ end
172
173
 
173
- def visit_qsymbols(node)
174
- node.elements.map { |element| visit(element).to_sym }
175
- end
174
+ def visit_qsymbols(node)
175
+ node.elements.map { |element| visit(element).to_sym }
176
+ end
176
177
 
177
- def visit_qwords(node)
178
- visit_all(node.elements)
179
- end
178
+ def visit_qwords(node)
179
+ visit_all(node.elements)
180
+ end
180
181
 
181
- def visit_range(node)
182
- left, right = [visit(node.left), visit(node.right)]
183
- node.operator.value === ".." ? left..right : left...right
184
- end
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
- def visit_rational(node)
187
- node.value.to_r
188
- end
187
+ def visit_rational(node)
188
+ node.value.to_r
189
+ end
189
190
 
190
- def visit_regexp_literal(node)
191
- if node.parts.length == 1 && node.parts.first.is_a?(TStringContent)
192
- Regexp.new(node.parts.first.value, visit_regexp_literal_flags(node))
193
- else
194
- # Any interpolation of expressions or variables will result in the
195
- # regular expression being constructed at run-time.
196
- raise CompilationError
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: node.truthy,
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: node.falsy,
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