houndstooth 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +11 -0
  6. data/Gemfile.lock +49 -0
  7. data/README.md +99 -0
  8. data/bin/houndstooth.rb +183 -0
  9. data/fuzz/cases/x.rb +8 -0
  10. data/fuzz/cases/y.rb +8 -0
  11. data/fuzz/cases/z.rb +22 -0
  12. data/fuzz/ruby.dict +64 -0
  13. data/fuzz/run +21 -0
  14. data/lib/houndstooth/environment/builder.rb +260 -0
  15. data/lib/houndstooth/environment/type_parser.rb +149 -0
  16. data/lib/houndstooth/environment/types/basic/type.rb +85 -0
  17. data/lib/houndstooth/environment/types/basic/type_instance.rb +54 -0
  18. data/lib/houndstooth/environment/types/compound/union_type.rb +72 -0
  19. data/lib/houndstooth/environment/types/defined/base_defined_type.rb +23 -0
  20. data/lib/houndstooth/environment/types/defined/defined_type.rb +137 -0
  21. data/lib/houndstooth/environment/types/defined/pending_defined_type.rb +14 -0
  22. data/lib/houndstooth/environment/types/method/method.rb +79 -0
  23. data/lib/houndstooth/environment/types/method/method_type.rb +144 -0
  24. data/lib/houndstooth/environment/types/method/parameters.rb +53 -0
  25. data/lib/houndstooth/environment/types/method/special_constructor_method.rb +15 -0
  26. data/lib/houndstooth/environment/types/special/instance_type.rb +9 -0
  27. data/lib/houndstooth/environment/types/special/self_type.rb +9 -0
  28. data/lib/houndstooth/environment/types/special/type_parameter_placeholder.rb +38 -0
  29. data/lib/houndstooth/environment/types/special/untyped_type.rb +11 -0
  30. data/lib/houndstooth/environment/types/special/void_type.rb +12 -0
  31. data/lib/houndstooth/environment/types.rb +3 -0
  32. data/lib/houndstooth/environment.rb +74 -0
  33. data/lib/houndstooth/errors.rb +53 -0
  34. data/lib/houndstooth/instructions.rb +698 -0
  35. data/lib/houndstooth/interpreter/const_internal.rb +148 -0
  36. data/lib/houndstooth/interpreter/objects.rb +142 -0
  37. data/lib/houndstooth/interpreter/runtime.rb +309 -0
  38. data/lib/houndstooth/interpreter.rb +7 -0
  39. data/lib/houndstooth/semantic_node/control_flow.rb +218 -0
  40. data/lib/houndstooth/semantic_node/definitions.rb +253 -0
  41. data/lib/houndstooth/semantic_node/identifiers.rb +308 -0
  42. data/lib/houndstooth/semantic_node/keywords.rb +45 -0
  43. data/lib/houndstooth/semantic_node/literals.rb +226 -0
  44. data/lib/houndstooth/semantic_node/operators.rb +126 -0
  45. data/lib/houndstooth/semantic_node/parameters.rb +108 -0
  46. data/lib/houndstooth/semantic_node/send.rb +349 -0
  47. data/lib/houndstooth/semantic_node/super.rb +12 -0
  48. data/lib/houndstooth/semantic_node.rb +119 -0
  49. data/lib/houndstooth/stdlib.rb +6 -0
  50. data/lib/houndstooth/type_checker.rb +462 -0
  51. data/lib/houndstooth.rb +53 -0
  52. data/spec/ast_to_node_spec.rb +889 -0
  53. data/spec/environment_spec.rb +323 -0
  54. data/spec/instructions_spec.rb +291 -0
  55. data/spec/integration_spec.rb +785 -0
  56. data/spec/interpreter_spec.rb +170 -0
  57. data/spec/self_spec.rb +7 -0
  58. data/spec/spec_helper.rb +50 -0
  59. data/test/ruby_interpreter_test.rb +162 -0
  60. data/types/stdlib.htt +170 -0
  61. metadata +110 -0
@@ -0,0 +1,889 @@
1
+ include Houndstooth::SemanticNode
2
+
3
+ RSpec.describe 'AST to SemanticNode' do
4
+ it 'translates literals' do
5
+ expect(code_to_semantic_node('1')).to m(IntegerLiteral, value: 1)
6
+
7
+ expect(code_to_semantic_node('3.0')).to m(FloatLiteral, value: 3.0)
8
+ expect(code_to_semantic_node('3.14')).to m(FloatLiteral, value: 3.14)
9
+ expect(code_to_semantic_node('3e2')).to m(FloatLiteral, value: 300.0)
10
+
11
+ expect(code_to_semantic_node('"hello"')).to m(StringLiteral, components: ['hello'])
12
+
13
+ expect(code_to_semantic_node('"My name is: #{name}!"')).to m(StringLiteral, components: [
14
+ 'My name is: ',
15
+ m(Send, target: nil, method: :name),
16
+ '!',
17
+ ])
18
+
19
+ expect(code_to_semantic_node(':foo')).to m(SymbolLiteral, components: ['foo'])
20
+
21
+ expect(code_to_semantic_node(':"#{name}="')).to m(SymbolLiteral, components: [
22
+ m(Send, target: nil, method: :name),
23
+ '=',
24
+ ])
25
+
26
+ expect(code_to_semantic_node('1..3')).to m(RangeLiteral,
27
+ first: m(IntegerLiteral, value: 1),
28
+ last: m(IntegerLiteral, value: 3),
29
+ inclusive: true,
30
+ )
31
+
32
+ expect(code_to_semantic_node('1...3')).to m(RangeLiteral,
33
+ first: m(IntegerLiteral, value: 1),
34
+ last: m(IntegerLiteral, value: 3),
35
+ inclusive: false,
36
+ )
37
+
38
+ expect(code_to_semantic_node('1..')).to m(RangeLiteral,
39
+ first: m(IntegerLiteral, value: 1),
40
+ last: be(nil),
41
+ inclusive: true,
42
+ )
43
+ end
44
+
45
+ it 'translates compound literals' do
46
+ # TODO: splats
47
+
48
+ expect(code_to_semantic_node('[]')).to m(ArrayLiteral, nodes: [])
49
+
50
+ expect(code_to_semantic_node('[1, 2, 3]')).to m(ArrayLiteral, nodes: [
51
+ m(IntegerLiteral, value: 1),
52
+ m(IntegerLiteral, value: 2),
53
+ m(IntegerLiteral, value: 3),
54
+ ])
55
+
56
+ expect(code_to_semantic_node('{}')).to m(HashLiteral, pairs: [])
57
+
58
+ expect(code_to_semantic_node('{a: 3, "b" => 4}')).to m(HashLiteral, pairs: [
59
+ [
60
+ m(SymbolLiteral, components: ['a']),
61
+ m(IntegerLiteral, value: 3),
62
+ ],
63
+ [
64
+ m(StringLiteral, components: ['b']),
65
+ m(IntegerLiteral, value: 4),
66
+ ],
67
+ ])
68
+ end
69
+
70
+ it 'translates sends' do
71
+ expect(code_to_semantic_node('foo')).to m(Send,
72
+ target: nil,
73
+ method: :foo,
74
+ arguments: [],
75
+ block: nil,
76
+ )
77
+
78
+ expect(code_to_semantic_node('Math.add(1, 2, 3)')).to m(Send,
79
+ target: m(Constant, target: nil, name: :Math),
80
+ method: :add,
81
+ arguments: [
82
+ m(PositionalArgument, node: m(IntegerLiteral, value: 1)),
83
+ m(PositionalArgument, node: m(IntegerLiteral, value: 2)),
84
+ m(PositionalArgument, node: m(IntegerLiteral, value: 3)),
85
+ ],
86
+ block: nil,
87
+ )
88
+
89
+ expect(code_to_semantic_node('Factory.new(:Person, name: "Aaron", age: 21)')).to m(Send,
90
+ target: m(Constant, target: nil, name: :Factory),
91
+ method: :new,
92
+ arguments: [
93
+ m(PositionalArgument, node: m(SymbolLiteral, components: ['Person'])),
94
+ m(KeywordArgument,
95
+ name: m(SymbolLiteral, components: ['name']),
96
+ node: m(StringLiteral, components: ['Aaron']),
97
+ ),
98
+ m(KeywordArgument,
99
+ name: m(SymbolLiteral, components: ['age']),
100
+ node: m(IntegerLiteral, value: 21),
101
+ ),
102
+ ],
103
+ block: nil,
104
+ )
105
+
106
+ expect(code_to_semantic_node('array.filter { |x| x.even? }')).to m(Send,
107
+ target: m(Send, target: nil, method: :array),
108
+ method: :filter,
109
+ arguments: [],
110
+
111
+ block: m(Block,
112
+ parameters: m(Parameters,
113
+ only_proc_parameter: true,
114
+ positional_parameters: [],
115
+ optional_parameters: [],
116
+ keyword_parameters: [],
117
+ optional_keyword_parameters: [],
118
+ ),
119
+
120
+ body: m(Send)
121
+ )
122
+ )
123
+
124
+ expect(code_to_semantic_node('array.filter { _1.even? }')).to m(Send,
125
+ target: m(Send, target: nil, method: :array),
126
+ method: :filter,
127
+ arguments: [],
128
+
129
+ block: m(Block,
130
+ parameters: m(Parameters,
131
+ only_proc_parameter: true,
132
+ positional_parameters: [],
133
+ optional_parameters: [],
134
+ keyword_parameters: [],
135
+ optional_keyword_parameters: [],
136
+ ),
137
+
138
+ body: be_a(Send)
139
+ )
140
+ )
141
+
142
+ expect(code_to_semantic_node('array.each_cons(2) { |a, b| a + b }')).to m(Send,
143
+ target: m(Send, target: nil, method: :array),
144
+ method: :each_cons,
145
+ arguments: [
146
+ m(PositionalArgument, node: m(IntegerLiteral, value: 2)),
147
+ ],
148
+
149
+ block: m(Block,
150
+ parameters: m(Parameters,
151
+ positional_parameters: [:a, :b],
152
+ optional_parameters: [],
153
+ keyword_parameters: [],
154
+ optional_keyword_parameters: [],
155
+ ),
156
+
157
+ body: m(Send,
158
+ target: m(LocalVariable, name: :a),
159
+ method: :+,
160
+ arguments: [
161
+ m(PositionalArgument, node: m(LocalVariable, name: :b)),
162
+ ],
163
+ )
164
+ )
165
+ )
166
+
167
+ expect(code_to_semantic_node('array.each_cons(2) { _1 + _2 }')).to m(Send,
168
+ target: m(Send, target: nil, method: :array),
169
+ method: :each_cons,
170
+ arguments: [
171
+ m(PositionalArgument, node: m(IntegerLiteral, value: 2)),
172
+ ],
173
+
174
+ block: m(Block,
175
+ parameters: m(Parameters,
176
+ positional_parameters: [:_1, :_2],
177
+ optional_parameters: [],
178
+ keyword_parameters: [],
179
+ optional_keyword_parameters: [],
180
+ ),
181
+
182
+ body: m(Send,
183
+ target: m(LocalVariable, name: :_1),
184
+ method: :+,
185
+ arguments: [
186
+ m(PositionalArgument, node: m(LocalVariable, name: :_2)),
187
+ ],
188
+ )
189
+ )
190
+ )
191
+
192
+ expect(code_to_semantic_node('x { |a, b = 3, *e, c:, d: 4, **f| a + b + c + d }')).to m(Send,
193
+ target: nil,
194
+ method: :x,
195
+ arguments: [],
196
+
197
+ block: m(Block,
198
+ parameters: m(Parameters,
199
+ positional_parameters: [:a],
200
+ optional_parameters: [[:b, m(IntegerLiteral, value: 3)]],
201
+ keyword_parameters: [:c],
202
+ optional_keyword_parameters: [[:d, m(IntegerLiteral, value: 4)]],
203
+ rest_parameter: :e,
204
+ rest_keyword_parameter: :f,
205
+ ),
206
+
207
+ body: m(Send,
208
+ target: m(Send,
209
+ target: m(Send,
210
+ target: m(LocalVariable, name: :a),
211
+ method: :+,
212
+ arguments: [
213
+ m(PositionalArgument, node: m(LocalVariable, name: :b)),
214
+ ],
215
+ ),
216
+ method: :+,
217
+ arguments: [
218
+ m(PositionalArgument, node: m(LocalVariable, name: :c)),
219
+ ],
220
+ ),
221
+ method: :+,
222
+ arguments: [
223
+ m(PositionalArgument, node: m(LocalVariable, name: :d)),
224
+ ],
225
+ )
226
+ )
227
+ )
228
+
229
+ expect(code_to_semantic_node('x&.y')).to m(Send,
230
+ target: m(Send, target: nil, method: :x),
231
+ method: :y,
232
+ safe_navigation: true,
233
+ )
234
+ end
235
+
236
+ it 'translates local variables' do
237
+ expect(code_to_semantic_node('x = 3; x')).to m(Body,
238
+ nodes: [
239
+ m(VariableAssignment,
240
+ target: m(LocalVariable, name: :x),
241
+ value: m(IntegerLiteral, value: 3),
242
+ ),
243
+ m(LocalVariable, name: :x),
244
+ ]
245
+ )
246
+ end
247
+
248
+ it 'translates instance, class, and global variables' do
249
+ expect(code_to_semantic_node('@x')).to m(InstanceVariable, name: :@x)
250
+ expect(code_to_semantic_node('@x = 3')).to m(VariableAssignment,
251
+ target: m(InstanceVariable, name: :@x),
252
+ value: m(IntegerLiteral, value: 3),
253
+ )
254
+
255
+ expect(code_to_semantic_node('@@x')).to m(ClassVariable, name: :@@x)
256
+ expect(code_to_semantic_node('@@x = 3')).to m(VariableAssignment,
257
+ target: m(ClassVariable, name: :@@x),
258
+ value: m(IntegerLiteral, value: 3),
259
+ )
260
+
261
+ expect(code_to_semantic_node('$x')).to m(GlobalVariable, name: :$x)
262
+ expect(code_to_semantic_node('$x = 3')).to m(VariableAssignment,
263
+ target: m(GlobalVariable, name: :$x),
264
+ value: m(IntegerLiteral, value: 3),
265
+ )
266
+ end
267
+
268
+ it 'translates multiple assignments' do
269
+ expect(code_to_semantic_node('a, @b = 1, 2')).to m(MultipleAssignment,
270
+ targets: [
271
+ m(LocalVariable, name: :a),
272
+ m(InstanceVariable, name: :@b),
273
+ ],
274
+ value: m(ArrayLiteral, nodes: [
275
+ m(IntegerLiteral, value: 1),
276
+ m(IntegerLiteral, value: 2),
277
+ ]),
278
+ )
279
+
280
+ expect(code_to_semantic_node('self.a, self.b = 1, 2')).to m(MultipleAssignment,
281
+ targets: [
282
+ m(Send,
283
+ target: be_a(SelfKeyword),
284
+ method: :a=,
285
+ arguments: [m(PositionalArgument, node: m(MagicPlaceholder))]
286
+ ),
287
+ m(Send,
288
+ target: be_a(SelfKeyword),
289
+ method: :b=,
290
+ arguments: [m(PositionalArgument, node: m(MagicPlaceholder))]
291
+ ),
292
+ ],
293
+ value: m(ArrayLiteral, nodes: [
294
+ m(IntegerLiteral, value: 1),
295
+ m(IntegerLiteral, value: 2),
296
+ ]),
297
+ )
298
+ end
299
+
300
+ it 'translates op-assignments' do
301
+ expect(code_to_semantic_node('x = 1; x += 3')).to m(Body,
302
+ nodes: [
303
+ m(VariableAssignment),
304
+ m(VariableAssignment,
305
+ target: m(LocalVariable, name: :x),
306
+ value: m(Send,
307
+ target: m(LocalVariable, name: :x),
308
+ method: :+,
309
+
310
+ arguments: [m(PositionalArgument, node: m(IntegerLiteral, value: 3))]
311
+ )
312
+ )
313
+ ]
314
+ )
315
+ end
316
+
317
+ it 'translates constants' do
318
+ expect(code_to_semantic_node('X')).to m(Constant, name: :X)
319
+
320
+ expect(code_to_semantic_node('X::Y::Z')).to m(Constant,
321
+ target: m(Constant,
322
+ target: m(Constant, name: :X),
323
+ name: :Y,
324
+ ),
325
+ name: :Z,
326
+ )
327
+
328
+ expect(code_to_semantic_node('lookup_class(:Math)::PI')).to m(Constant,
329
+ target: m(Send,
330
+ target: nil,
331
+ method: :lookup_class,
332
+ arguments: [m(PositionalArgument, node: m(SymbolLiteral, components: ['Math']))],
333
+ ),
334
+ name: :PI,
335
+ )
336
+
337
+ expect(code_to_semantic_node('::X::Y::Z')).to m(Constant,
338
+ target: m(Constant,
339
+ target: m(Constant,
340
+ target: be_a(ConstantBase),
341
+ name: :X,
342
+ ),
343
+ name: :Y,
344
+ ),
345
+ name: :Z,
346
+ )
347
+
348
+ expect(code_to_semantic_node('X = 3')).to m(ConstantAssignment,
349
+ target: nil,
350
+ name: :X,
351
+ value: m(IntegerLiteral, value: 3),
352
+ )
353
+
354
+ expect(code_to_semantic_node('X::Y = 3')).to m(ConstantAssignment,
355
+ target: m(Constant, name: :X),
356
+ name: :Y,
357
+ value: m(IntegerLiteral, value: 3),
358
+ )
359
+ end
360
+
361
+ it 'translates keywords' do
362
+ expect(code_to_semantic_node('true')).to be_a(TrueKeyword)
363
+ expect(code_to_semantic_node('false')).to be_a(FalseKeyword)
364
+ expect(code_to_semantic_node('self')).to be_a(SelfKeyword)
365
+ expect(code_to_semantic_node('nil')).to be_a(NilKeyword)
366
+ end
367
+
368
+ it 'translates control flow statements' do
369
+ expect(code_to_semantic_node('if foo; bar; end')).to m(Conditional,
370
+ condition: m(Send,
371
+ target: nil,
372
+ method: :foo,
373
+ ),
374
+ true_branch: m(Send,
375
+ target: nil,
376
+ method: :bar,
377
+ ),
378
+ false_branch: nil,
379
+ )
380
+
381
+ expect(code_to_semantic_node('if foo; bar; else; baz; end')).to m(Conditional,
382
+ condition: m(Send,
383
+ target: nil,
384
+ method: :foo,
385
+ ),
386
+ true_branch: m(Send,
387
+ target: nil,
388
+ method: :bar,
389
+ ),
390
+ false_branch: m(Send,
391
+ target: nil,
392
+ method: :baz,
393
+ ),
394
+ )
395
+
396
+ expect(code_to_semantic_node('foo ? bar : baz')).to m(Conditional,
397
+ condition: m(Send,
398
+ target: nil,
399
+ method: :foo,
400
+ ),
401
+ true_branch: m(Send,
402
+ target: nil,
403
+ method: :bar,
404
+ ),
405
+ false_branch: m(Send,
406
+ target: nil,
407
+ method: :baz,
408
+ ),
409
+ )
410
+
411
+ expect(code_to_semantic_node("
412
+ x = foo(3)
413
+ case x
414
+ when String
415
+ a
416
+ when 3
417
+ b
418
+ else
419
+ c
420
+ end
421
+ ")).to m(Body,
422
+ nodes: [
423
+ # x = foo(3)
424
+ m(VariableAssignment,
425
+ target: m(LocalVariable, name: :x),
426
+ value: m(Send, method: :foo),
427
+ ),
428
+
429
+ # ___fabricated = x
430
+ m(VariableAssignment,
431
+ target: m(LocalVariable, fabricated: true),
432
+ value: m(LocalVariable, name: :x),
433
+ ),
434
+
435
+ # when String
436
+ m(Conditional,
437
+ condition: m(Send,
438
+ target: m(Constant, name: :String),
439
+ method: :===,
440
+ arguments: [
441
+ m(PositionalArgument, node: m(LocalVariable, fabricated: true)),
442
+ ],
443
+ ),
444
+ true_branch: m(Send, method: :a),
445
+
446
+ # when 3
447
+ false_branch: m(Conditional,
448
+ condition: m(Send,
449
+ target: m(IntegerLiteral, value: 3),
450
+ method: :===,
451
+ arguments: [
452
+ m(PositionalArgument, node: m(LocalVariable, fabricated: true)),
453
+ ],
454
+ ),
455
+ true_branch: m(Send, method: :b),
456
+
457
+ # else
458
+ false_branch: m(Send, method: :c),
459
+ )
460
+ )
461
+ ]
462
+ )
463
+
464
+ expect(code_to_semantic_node("while x; y; end")).to m(While,
465
+ condition: m(Send,
466
+ target: nil,
467
+ method: :x,
468
+ ),
469
+ body: m(Send,
470
+ target: nil,
471
+ method: :y,
472
+ )
473
+ )
474
+ end
475
+
476
+ it 'translates boolean operators' do
477
+ expect(code_to_semantic_node('a && b')).to m(BooleanAnd,
478
+ left: m(Send, target: nil, method: :a),
479
+ right: m(Send, target: nil, method: :b),
480
+ )
481
+
482
+ expect(code_to_semantic_node('a || b')).to m(BooleanOr,
483
+ left: m(Send, target: nil, method: :a),
484
+ right: m(Send, target: nil, method: :b),
485
+ )
486
+
487
+ expect(code_to_semantic_node('a &&= b')).to m(BooleanAndAssignment,
488
+ target: m(LocalVariable, name: :a),
489
+ value: m(Send, target: nil, method: :b),
490
+ )
491
+
492
+ expect(code_to_semantic_node('a ||= b')).to m(BooleanOrAssignment,
493
+ target: m(LocalVariable, name: :a),
494
+ value: m(Send, target: nil, method: :b),
495
+ )
496
+ end
497
+
498
+ it 'translates method definitions' do
499
+ expect(code_to_semantic_node('def x; end')).to m(MethodDefinition,
500
+ name: :x,
501
+ parameters: m(Parameters,
502
+ positional_parameters: [],
503
+ optional_parameters: [],
504
+ ),
505
+ target: nil,
506
+ body: nil,
507
+ )
508
+
509
+ expect(code_to_semantic_node('def add(x, y = 1); x + y; end')).to m(MethodDefinition,
510
+ name: :add,
511
+ parameters: m(Parameters,
512
+ positional_parameters: [:x],
513
+ optional_parameters: [[:y, be_a(IntegerLiteral)]],
514
+ ),
515
+ target: nil,
516
+ body: m(Send,
517
+ target: m(LocalVariable, name: :x),
518
+ method: :+,
519
+ arguments: [
520
+ m(PositionalArgument, node: m(LocalVariable, name: :y)),
521
+ ]
522
+ ),
523
+ )
524
+
525
+ expect(code_to_semantic_node('def x(*x, **y, &blk); end')).to m(MethodDefinition,
526
+ name: :x,
527
+ parameters: m(Parameters,
528
+ positional_parameters: [],
529
+ optional_parameters: [],
530
+ rest_parameter: :x,
531
+ rest_keyword_parameter: :y,
532
+ block_parameter: :blk,
533
+ ),
534
+ target: nil,
535
+ )
536
+
537
+ expect(code_to_semantic_node('def x(x, ...); y(...); end')).to m(MethodDefinition,
538
+ name: :x,
539
+ parameters: m(Parameters,
540
+ positional_parameters: [:x],
541
+ optional_parameters: [],
542
+ has_forward_parameter: true,
543
+ ),
544
+ target: nil,
545
+ body: m(Send,
546
+ target: nil,
547
+ method: :y,
548
+ arguments: [
549
+ m(PositionalArgument, node: m(ForwardedArguments)),
550
+ ]
551
+ )
552
+ )
553
+
554
+ expect(code_to_semantic_node('def self.magic; 42; end')).to m(MethodDefinition,
555
+ name: :magic,
556
+ target: be_a(SelfKeyword),
557
+ body: m(IntegerLiteral, value: 42),
558
+ )
559
+
560
+ expect(code_to_semantic_node('def x.double; end')).to m(MethodDefinition,
561
+ name: :double,
562
+ target: m(Send, target: nil, method: :x),
563
+ body: nil,
564
+ )
565
+ end
566
+
567
+ it 'translates aliases' do
568
+ expect(code_to_semantic_node('alias x y')).to m(Alias,
569
+ from: m(SymbolLiteral, components: ['y']),
570
+ to: m(SymbolLiteral, components: ['x']),
571
+ )
572
+ end
573
+
574
+ it 'translates class definitions and singleton class accesses' do
575
+ expect(code_to_semantic_node('class X; end')).to m(ClassDefinition,
576
+ name: m(Constant,
577
+ target: nil,
578
+ name: :X,
579
+ ),
580
+ superclass: nil,
581
+ body: nil,
582
+ )
583
+
584
+ expect(code_to_semantic_node("
585
+ class X < Y
586
+ def foo
587
+ end
588
+
589
+ def bar
590
+ end
591
+ end
592
+ ")).to m(ClassDefinition,
593
+ name: m(Constant,
594
+ target: nil,
595
+ name: :X,
596
+ ),
597
+ superclass: m(Constant,
598
+ target: nil,
599
+ name: :Y,
600
+ ),
601
+ body: m(Body,
602
+ nodes: [
603
+ m(MethodDefinition, target: nil, name: :foo, body: nil),
604
+ m(MethodDefinition, target: nil, name: :bar, body: nil),
605
+ ]
606
+ ),
607
+ )
608
+
609
+ expect(code_to_semantic_node("
610
+ class X
611
+ def foo
612
+ end
613
+
614
+ class << self
615
+ def bar
616
+ end
617
+ end
618
+ end
619
+ ")).to m(ClassDefinition,
620
+ name: m(Constant,
621
+ target: nil,
622
+ name: :X,
623
+ ),
624
+ superclass: nil,
625
+ body: m(Body,
626
+ nodes: [
627
+ m(MethodDefinition, target: nil, name: :foo, body: nil),
628
+ m(SingletonClass,
629
+ target: be_a(SelfKeyword),
630
+ body: m(MethodDefinition, target: nil, name: :bar, body: nil),
631
+ )
632
+ ]
633
+ ),
634
+ )
635
+
636
+ expect(code_to_semantic_node("
637
+ x = Object.new
638
+ class << x
639
+ end
640
+ ")).to m(Body,
641
+ nodes: [
642
+ m(VariableAssignment, target: be_a(LocalVariable)),
643
+ m(SingletonClass,
644
+ target: m(LocalVariable, name: :x),
645
+ body: nil,
646
+ )
647
+ ]
648
+ )
649
+ end
650
+
651
+ it 'translates module definitions' do
652
+ expect(code_to_semantic_node("
653
+ module X
654
+ def foo
655
+ end
656
+
657
+ def bar
658
+ end
659
+ end
660
+ ")).to m(ModuleDefinition,
661
+ name: m(Constant,
662
+ target: nil,
663
+ name: :X,
664
+ ),
665
+ body: m(Body,
666
+ nodes: [
667
+ m(MethodDefinition, target: nil, name: :foo, body: nil),
668
+ m(MethodDefinition, target: nil, name: :bar, body: nil),
669
+ ]
670
+ ),
671
+ )
672
+ end
673
+
674
+ it 'matches comments to translated nodes' do
675
+ expect(code_to_semantic_node("
676
+ # Hello
677
+ puts 'something'
678
+
679
+ # Hello again
680
+ puts 'something else'
681
+
682
+ # Outer
683
+ func(
684
+ # Inner 1
685
+ a(b, c, d),
686
+ # Inner 2
687
+ e(f, g),
688
+ )
689
+ ")).to m(Body,
690
+ nodes: [
691
+ m(Send,
692
+ target: nil,
693
+ method: :puts,
694
+
695
+ comments: [
696
+ be_a(Parser::Source::Comment) & have_attributes(text: '# Hello')
697
+ ]
698
+ ),
699
+
700
+ m(Send,
701
+ target: nil,
702
+ method: :puts,
703
+
704
+ comments: [
705
+ be_a(Parser::Source::Comment) & have_attributes(text: '# Hello again')
706
+ ]
707
+ ),
708
+
709
+ m(Send,
710
+ target: nil,
711
+ method: :func,
712
+
713
+ comments: [
714
+ be_a(Parser::Source::Comment) & have_attributes(text: '# Outer')
715
+ ],
716
+
717
+ arguments: [
718
+ m(PositionalArgument,
719
+ node: m(Send,
720
+ target: nil,
721
+ method: :a,
722
+
723
+ comments: [
724
+ be_a(Parser::Source::Comment) & have_attributes(text: '# Inner 1')
725
+ ]
726
+ )
727
+ ),
728
+ m(PositionalArgument,
729
+ node: m(Send,
730
+ target: nil,
731
+ method: :e,
732
+
733
+ comments: [
734
+ be_a(Parser::Source::Comment) & have_attributes(text: '# Inner 2')
735
+ ]
736
+ ),
737
+ ),
738
+ ]
739
+ ),
740
+ ]
741
+ )
742
+
743
+ expect(code_to_semantic_node("
744
+ # Comment
745
+ a.b.c
746
+ ")).to m(Send,
747
+ target: m(Send,
748
+ target: m(Send,
749
+ target: nil,
750
+ method: :a,
751
+
752
+ comments: [
753
+ have_attributes(text: '# Comment')
754
+ ]
755
+ ),
756
+ method: :b,
757
+ ),
758
+ method: :c,
759
+ )
760
+
761
+ expect(code_to_semantic_node("
762
+ # A
763
+ a
764
+ # B
765
+ .b
766
+ # C
767
+ .c
768
+ ")).to m(Send,
769
+ target: m(Send,
770
+ target: m(Send,
771
+ target: nil,
772
+ method: :a,
773
+ comments: [have_attributes(text: '# A')],
774
+ ),
775
+ method: :b,
776
+ comments: [have_attributes(text: '# B')],
777
+ ),
778
+ method: :c,
779
+ comments: [have_attributes(text: '# C')],
780
+ )
781
+
782
+ expect(code_to_semantic_node("
783
+ # A module.
784
+ module M
785
+ # A class.
786
+ class C1
787
+ # A method.
788
+ # Returns a string.
789
+ def x
790
+ 'hello'
791
+ end
792
+
793
+ # Another method.
794
+ def y(a, b)
795
+ a * b
796
+ end
797
+ end
798
+
799
+ # Another class.
800
+ class C2
801
+ # A class method.
802
+ def self.z
803
+ true
804
+ end
805
+ end
806
+ end
807
+ ")).to m(ModuleDefinition,
808
+ name: have_attributes(name: :M),
809
+ comments: [have_attributes(text: "# A module.")],
810
+
811
+ body: m(Body,
812
+ nodes: [
813
+ m(ClassDefinition,
814
+ name: have_attributes(name: :C1),
815
+ comments: [have_attributes(text: "# A class.")],
816
+
817
+ body: m(Body,
818
+ nodes: [
819
+ m(MethodDefinition,
820
+ name: :x,
821
+ comments: [
822
+ have_attributes(text: "# A method."),
823
+ have_attributes(text: "# Returns a string."),
824
+ ],
825
+ ),
826
+ m(MethodDefinition,
827
+ name: :y,
828
+ comments: [
829
+ have_attributes(text: "# Another method."),
830
+ ],
831
+ ),
832
+ ],
833
+ ),
834
+ ),
835
+ m(ClassDefinition,
836
+ name: have_attributes(name: :C2),
837
+ comments: [have_attributes(text: "# Another class.")],
838
+
839
+ body: m(MethodDefinition,
840
+ name: :z,
841
+ comments: [
842
+ have_attributes(text: "# A class method."),
843
+ ],
844
+ ),
845
+ ),
846
+ ]
847
+ ),
848
+ )
849
+ end
850
+
851
+ it 'translates splats' do
852
+ expect(code_to_semantic_node("a = *b")).to m(VariableAssignment,
853
+ target: m(LocalVariable, name: :a),
854
+ value: m(ArrayLiteral,
855
+ nodes: [
856
+ m(Splat,
857
+ value: m(Send, method: :b)
858
+ )
859
+ ]
860
+ )
861
+ )
862
+ end
863
+
864
+ it 'translates defined? checks' do
865
+ expect(code_to_semantic_node("defined? a")).to m(IsDefined,
866
+ value: m(Send, method: :a),
867
+ )
868
+ end
869
+
870
+ it 'translates control-flow expressions such as return' do
871
+ expect(code_to_semantic_node("return")).to m(Return, value: nil)
872
+ expect(code_to_semantic_node("return 3")).to m(Return,
873
+ value: m(IntegerLiteral, value: 3),
874
+ )
875
+ expect(code_to_semantic_node("return 3, 4")).to m(Return,
876
+ value: m(ArrayLiteral,
877
+ nodes: [
878
+ m(IntegerLiteral, value: 3),
879
+ m(IntegerLiteral, value: 4),
880
+ ],
881
+ )
882
+ )
883
+
884
+ # These need minimal tests because they'll all behave the same, they derive from the same
885
+ # mixin
886
+ expect(code_to_semantic_node("break")).to m(Break, value: nil)
887
+ expect(code_to_semantic_node("next")).to m(Next, value: nil)
888
+ end
889
+ end