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.
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