sonolus.py 0.1.3__py3-none-any.whl → 0.1.5__py3-none-any.whl
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.
Potentially problematic release.
This version of sonolus.py might be problematic. Click here for more details.
- sonolus/backend/blocks.py +756 -756
- sonolus/backend/excepthook.py +37 -37
- sonolus/backend/finalize.py +77 -69
- sonolus/backend/interpret.py +7 -7
- sonolus/backend/ir.py +29 -3
- sonolus/backend/mode.py +24 -24
- sonolus/backend/node.py +40 -40
- sonolus/backend/ops.py +197 -197
- sonolus/backend/optimize/__init__.py +0 -0
- sonolus/backend/optimize/allocate.py +126 -0
- sonolus/backend/optimize/constant_evaluation.py +374 -0
- sonolus/backend/optimize/copy_coalesce.py +85 -0
- sonolus/backend/optimize/dead_code.py +185 -0
- sonolus/backend/optimize/dominance.py +96 -0
- sonolus/backend/{flow.py → optimize/flow.py} +122 -92
- sonolus/backend/optimize/inlining.py +137 -0
- sonolus/backend/optimize/liveness.py +177 -0
- sonolus/backend/optimize/optimize.py +44 -0
- sonolus/backend/optimize/passes.py +52 -0
- sonolus/backend/optimize/simplify.py +191 -0
- sonolus/backend/optimize/ssa.py +200 -0
- sonolus/backend/place.py +17 -25
- sonolus/backend/utils.py +58 -48
- sonolus/backend/visitor.py +1151 -882
- sonolus/build/cli.py +7 -1
- sonolus/build/compile.py +88 -90
- sonolus/build/engine.py +10 -5
- sonolus/build/level.py +24 -23
- sonolus/build/node.py +43 -43
- sonolus/script/archetype.py +438 -139
- sonolus/script/array.py +27 -10
- sonolus/script/array_like.py +297 -0
- sonolus/script/bucket.py +253 -191
- sonolus/script/containers.py +257 -51
- sonolus/script/debug.py +26 -10
- sonolus/script/easing.py +365 -0
- sonolus/script/effect.py +191 -131
- sonolus/script/engine.py +71 -4
- sonolus/script/globals.py +303 -269
- sonolus/script/instruction.py +205 -151
- sonolus/script/internal/__init__.py +5 -5
- sonolus/script/internal/builtin_impls.py +255 -144
- sonolus/script/{callbacks.py → internal/callbacks.py} +127 -127
- sonolus/script/internal/constant.py +139 -0
- sonolus/script/internal/context.py +26 -9
- sonolus/script/internal/descriptor.py +17 -17
- sonolus/script/internal/dict_impl.py +65 -0
- sonolus/script/internal/generic.py +6 -9
- sonolus/script/internal/impl.py +38 -13
- sonolus/script/internal/introspection.py +17 -14
- sonolus/script/internal/math_impls.py +121 -0
- sonolus/script/internal/native.py +40 -38
- sonolus/script/internal/random.py +67 -0
- sonolus/script/internal/range.py +81 -0
- sonolus/script/internal/transient.py +51 -0
- sonolus/script/internal/tuple_impl.py +113 -0
- sonolus/script/internal/value.py +3 -3
- sonolus/script/interval.py +338 -112
- sonolus/script/iterator.py +167 -214
- sonolus/script/level.py +24 -0
- sonolus/script/num.py +80 -48
- sonolus/script/options.py +257 -191
- sonolus/script/particle.py +190 -157
- sonolus/script/pointer.py +30 -30
- sonolus/script/print.py +102 -81
- sonolus/script/project.py +8 -0
- sonolus/script/quad.py +263 -0
- sonolus/script/record.py +47 -16
- sonolus/script/runtime.py +52 -1
- sonolus/script/sprite.py +418 -333
- sonolus/script/text.py +409 -407
- sonolus/script/timing.py +114 -42
- sonolus/script/transform.py +332 -48
- sonolus/script/ui.py +216 -160
- sonolus/script/values.py +6 -13
- sonolus/script/vec.py +196 -78
- {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/METADATA +1 -1
- sonolus_py-0.1.5.dist-info/RECORD +89 -0
- {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/WHEEL +1 -1
- {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +21 -21
- sonolus/backend/allocate.py +0 -51
- sonolus/backend/optimize.py +0 -9
- sonolus/backend/passes.py +0 -6
- sonolus/backend/simplify.py +0 -30
- sonolus/script/comptime.py +0 -160
- sonolus/script/graphics.py +0 -150
- sonolus/script/math.py +0 -92
- sonolus/script/range.py +0 -58
- sonolus_py-0.1.3.dist-info/RECORD +0 -75
- {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/entry_points.txt +0 -0
sonolus/backend/visitor.py
CHANGED
|
@@ -1,882 +1,1151 @@
|
|
|
1
|
-
# ruff: noqa: N802
|
|
2
|
-
import ast
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
from
|
|
7
|
-
from types import FunctionType, MethodType
|
|
8
|
-
from typing import Any, Never
|
|
9
|
-
|
|
10
|
-
from sonolus.backend.excepthook import install_excepthook
|
|
11
|
-
from sonolus.backend.utils import get_function, scan_writes
|
|
12
|
-
from sonolus.script.debug import assert_true
|
|
13
|
-
from sonolus.script.internal.builtin_impls import BUILTIN_IMPLS
|
|
14
|
-
from sonolus.script.internal.
|
|
15
|
-
from sonolus.script.internal.
|
|
16
|
-
from sonolus.script.internal.
|
|
17
|
-
from sonolus.script.internal.
|
|
18
|
-
from sonolus.script.internal.
|
|
19
|
-
from sonolus.script.
|
|
20
|
-
from sonolus.script.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
case
|
|
37
|
-
return
|
|
38
|
-
case
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return fn
|
|
47
|
-
elif callable(fn):
|
|
48
|
-
raise TypeError(f"Unsupported callable {fn!r}")
|
|
49
|
-
else:
|
|
50
|
-
raise TypeError(f"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def eval_fn(fn: Callable, /, *args, **kwargs):
|
|
54
|
-
source_file, node = get_function(fn)
|
|
55
|
-
bound_args = inspect.signature(fn).bind(*args, **kwargs)
|
|
56
|
-
bound_args.apply_defaults()
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
ast.
|
|
73
|
-
ast.
|
|
74
|
-
ast.
|
|
75
|
-
ast.
|
|
76
|
-
ast.
|
|
77
|
-
ast.
|
|
78
|
-
ast.
|
|
79
|
-
ast.
|
|
80
|
-
ast.
|
|
81
|
-
ast.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
ast.
|
|
89
|
-
ast.
|
|
90
|
-
ast.
|
|
91
|
-
ast.
|
|
92
|
-
ast.
|
|
93
|
-
ast.
|
|
94
|
-
ast.
|
|
95
|
-
ast.
|
|
96
|
-
ast.
|
|
97
|
-
ast.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
ast.
|
|
105
|
-
ast.
|
|
106
|
-
ast.
|
|
107
|
-
ast.
|
|
108
|
-
ast.
|
|
109
|
-
ast.
|
|
110
|
-
ast.
|
|
111
|
-
ast.
|
|
112
|
-
ast.
|
|
113
|
-
ast.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
ast.
|
|
121
|
-
ast.
|
|
122
|
-
ast.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
ast.
|
|
130
|
-
ast.
|
|
131
|
-
ast.
|
|
132
|
-
ast.
|
|
133
|
-
ast.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
self.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
value
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
case ast.
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
if
|
|
626
|
-
raise ValueError("
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
self.
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
def
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
return
|
|
835
|
-
|
|
836
|
-
def
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1
|
+
# ruff: noqa: N802
|
|
2
|
+
import ast
|
|
3
|
+
import builtins
|
|
4
|
+
import functools
|
|
5
|
+
import inspect
|
|
6
|
+
from collections.abc import Callable, Sequence
|
|
7
|
+
from types import FunctionType, MethodType, MethodWrapperType
|
|
8
|
+
from typing import Any, Never, Self
|
|
9
|
+
|
|
10
|
+
from sonolus.backend.excepthook import install_excepthook
|
|
11
|
+
from sonolus.backend.utils import get_function, scan_writes
|
|
12
|
+
from sonolus.script.debug import assert_true
|
|
13
|
+
from sonolus.script.internal.builtin_impls import BUILTIN_IMPLS, _bool, _float, _int, _len
|
|
14
|
+
from sonolus.script.internal.constant import ConstantValue
|
|
15
|
+
from sonolus.script.internal.context import Context, EmptyBinding, Scope, ValueBinding, ctx, set_ctx
|
|
16
|
+
from sonolus.script.internal.descriptor import SonolusDescriptor
|
|
17
|
+
from sonolus.script.internal.error import CompilationError
|
|
18
|
+
from sonolus.script.internal.impl import validate_value
|
|
19
|
+
from sonolus.script.internal.tuple_impl import TupleImpl
|
|
20
|
+
from sonolus.script.internal.value import Value
|
|
21
|
+
from sonolus.script.iterator import SonolusIterator
|
|
22
|
+
from sonolus.script.num import Num, _is_num
|
|
23
|
+
|
|
24
|
+
_compiler_internal_ = True
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def compile_and_call[**P, R](fn: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
28
|
+
if not ctx():
|
|
29
|
+
return fn(*args, **kwargs)
|
|
30
|
+
return validate_value(generate_fn_impl(fn)(*args, **kwargs))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def generate_fn_impl(fn: Callable):
|
|
34
|
+
install_excepthook()
|
|
35
|
+
match fn:
|
|
36
|
+
case ConstantValue() as value if value._is_py_():
|
|
37
|
+
return generate_fn_impl(value._as_py_())
|
|
38
|
+
case MethodType() as method:
|
|
39
|
+
return functools.partial(generate_fn_impl(method.__func__), method.__self__)
|
|
40
|
+
case FunctionType() as function:
|
|
41
|
+
if getattr(function, "_meta_fn_", False):
|
|
42
|
+
return function
|
|
43
|
+
return functools.partial(eval_fn, function)
|
|
44
|
+
case _:
|
|
45
|
+
if callable(fn) and isinstance(fn, Value):
|
|
46
|
+
return generate_fn_impl(fn.__call__)
|
|
47
|
+
elif callable(fn):
|
|
48
|
+
raise TypeError(f"Unsupported callable {fn!r}")
|
|
49
|
+
else:
|
|
50
|
+
raise TypeError(f"'{type(fn).__name__}' object is not callable")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def eval_fn(fn: Callable, /, *args, **kwargs):
|
|
54
|
+
source_file, node = get_function(fn)
|
|
55
|
+
bound_args = inspect.signature(fn).bind(*args, **kwargs)
|
|
56
|
+
bound_args.apply_defaults()
|
|
57
|
+
global_vars = {
|
|
58
|
+
**builtins.__dict__,
|
|
59
|
+
**fn.__globals__,
|
|
60
|
+
**inspect.getclosurevars(fn).nonlocals,
|
|
61
|
+
}
|
|
62
|
+
return Visitor(source_file, bound_args, global_vars).run(node)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
unary_ops = {
|
|
66
|
+
ast.Invert: "__invert__",
|
|
67
|
+
ast.UAdd: "__pos__",
|
|
68
|
+
ast.USub: "__neg__",
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
bin_ops = {
|
|
72
|
+
ast.Add: "__add__",
|
|
73
|
+
ast.Sub: "__sub__",
|
|
74
|
+
ast.Mult: "__mul__",
|
|
75
|
+
ast.Div: "__truediv__",
|
|
76
|
+
ast.FloorDiv: "__floordiv__",
|
|
77
|
+
ast.Mod: "__mod__",
|
|
78
|
+
ast.Pow: "__pow__",
|
|
79
|
+
ast.LShift: "__lshift__",
|
|
80
|
+
ast.RShift: "__rshift__",
|
|
81
|
+
ast.BitOr: "__or__",
|
|
82
|
+
ast.BitAnd: "__and__",
|
|
83
|
+
ast.BitXor: "__xor__",
|
|
84
|
+
ast.MatMult: "__matmul__",
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
rbin_ops = {
|
|
88
|
+
ast.Add: "__radd__",
|
|
89
|
+
ast.Sub: "__rsub__",
|
|
90
|
+
ast.Mult: "__rmul__",
|
|
91
|
+
ast.Div: "__rtruediv__",
|
|
92
|
+
ast.FloorDiv: "__rfloordiv__",
|
|
93
|
+
ast.Mod: "__rmod__",
|
|
94
|
+
ast.Pow: "__rpow__",
|
|
95
|
+
ast.LShift: "__rlshift__",
|
|
96
|
+
ast.RShift: "__rrshift__",
|
|
97
|
+
ast.BitOr: "__ror__",
|
|
98
|
+
ast.BitAnd: "__rand__",
|
|
99
|
+
ast.BitXor: "__rxor__",
|
|
100
|
+
ast.MatMult: "__rmatmul__",
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
inplace_ops = {
|
|
104
|
+
ast.Add: "__iadd__",
|
|
105
|
+
ast.Sub: "__isub__",
|
|
106
|
+
ast.Mult: "__imul__",
|
|
107
|
+
ast.Div: "__itruediv__",
|
|
108
|
+
ast.FloorDiv: "__ifloordiv__",
|
|
109
|
+
ast.Mod: "__imod__",
|
|
110
|
+
ast.Pow: "__ipow__",
|
|
111
|
+
ast.LShift: "__ilshift__",
|
|
112
|
+
ast.RShift: "__irshift__",
|
|
113
|
+
ast.BitOr: "__ior__",
|
|
114
|
+
ast.BitXor: "__ixor__",
|
|
115
|
+
ast.BitAnd: "__iand__",
|
|
116
|
+
ast.MatMult: "__imatmul__",
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
comp_ops = {
|
|
120
|
+
ast.Eq: "__eq__",
|
|
121
|
+
ast.NotEq: "__ne__",
|
|
122
|
+
ast.Lt: "__lt__",
|
|
123
|
+
ast.LtE: "__le__",
|
|
124
|
+
ast.Gt: "__gt__",
|
|
125
|
+
ast.GtE: "__ge__",
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
rcomp_ops = {
|
|
129
|
+
ast.Eq: "__eq__",
|
|
130
|
+
ast.NotEq: "__ne__",
|
|
131
|
+
ast.Lt: "__gt__",
|
|
132
|
+
ast.LtE: "__ge__",
|
|
133
|
+
ast.Gt: "__lt__",
|
|
134
|
+
ast.GtE: "__le__",
|
|
135
|
+
ast.In: "__contains__", # Only supported on the right side
|
|
136
|
+
ast.NotIn: "__contains__",
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
op_to_symbol = {
|
|
140
|
+
ast.Add: "+",
|
|
141
|
+
ast.Sub: "-",
|
|
142
|
+
ast.Mult: "*",
|
|
143
|
+
ast.Div: "/",
|
|
144
|
+
ast.FloorDiv: "//",
|
|
145
|
+
ast.Mod: "%",
|
|
146
|
+
ast.Pow: "**",
|
|
147
|
+
ast.Eq: "==",
|
|
148
|
+
ast.NotEq: "!=",
|
|
149
|
+
ast.Lt: "<",
|
|
150
|
+
ast.LtE: "<=",
|
|
151
|
+
ast.Gt: ">",
|
|
152
|
+
ast.GtE: ">=",
|
|
153
|
+
ast.And: "and",
|
|
154
|
+
ast.Or: "or",
|
|
155
|
+
ast.BitAnd: "&",
|
|
156
|
+
ast.BitOr: "|",
|
|
157
|
+
ast.BitXor: "^",
|
|
158
|
+
ast.LShift: "<<",
|
|
159
|
+
ast.RShift: ">>",
|
|
160
|
+
ast.USub: "-",
|
|
161
|
+
ast.UAdd: "+",
|
|
162
|
+
ast.Invert: "~",
|
|
163
|
+
ast.Not: "not",
|
|
164
|
+
ast.In: "in",
|
|
165
|
+
ast.NotIn: "not in",
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class Visitor(ast.NodeVisitor):
|
|
170
|
+
source_file: str
|
|
171
|
+
globals: dict[str, Any]
|
|
172
|
+
bound_args: inspect.BoundArguments
|
|
173
|
+
used_names: dict[str, int]
|
|
174
|
+
return_ctxs: list[Context] # Contexts at return statements, which will branch to the exit
|
|
175
|
+
loop_head_ctxs: list[Context] # Contexts at loop heads, from outer to inner
|
|
176
|
+
break_ctxs: list[list[Context]] # Contexts at break statements, from outer to inner
|
|
177
|
+
active_ctx: Context | None # The active context for use in nested functions=
|
|
178
|
+
parent: Self | None # The parent visitor for use in nested functions
|
|
179
|
+
|
|
180
|
+
def __init__(
|
|
181
|
+
self,
|
|
182
|
+
source_file: str,
|
|
183
|
+
bound_args: inspect.BoundArguments,
|
|
184
|
+
global_vars: dict[str, Any],
|
|
185
|
+
parent: Self | None = None,
|
|
186
|
+
):
|
|
187
|
+
self.source_file = source_file
|
|
188
|
+
self.globals = global_vars
|
|
189
|
+
self.bound_args = bound_args
|
|
190
|
+
self.used_names = {}
|
|
191
|
+
self.return_ctxs = []
|
|
192
|
+
self.loop_head_ctxs = []
|
|
193
|
+
self.break_ctxs = []
|
|
194
|
+
self.active_ctx = None
|
|
195
|
+
self.parent = parent
|
|
196
|
+
|
|
197
|
+
def run(self, node):
|
|
198
|
+
before_ctx = ctx()
|
|
199
|
+
set_ctx(before_ctx.branch_with_scope(None, Scope()))
|
|
200
|
+
for name, value in self.bound_args.arguments.items():
|
|
201
|
+
ctx().scope.set_value(name, validate_value(value))
|
|
202
|
+
match node:
|
|
203
|
+
case ast.FunctionDef(body=body):
|
|
204
|
+
ctx().scope.set_value("$return", validate_value(None))
|
|
205
|
+
for stmt in body:
|
|
206
|
+
self.visit(stmt)
|
|
207
|
+
case ast.Lambda(body=body):
|
|
208
|
+
result = self.visit(body)
|
|
209
|
+
ctx().scope.set_value("$return", result)
|
|
210
|
+
case _:
|
|
211
|
+
raise NotImplementedError("Unsupported syntax")
|
|
212
|
+
after_ctx = Context.meet([*self.return_ctxs, ctx()])
|
|
213
|
+
self.active_ctx = after_ctx
|
|
214
|
+
result_binding = after_ctx.scope.get_binding("$return")
|
|
215
|
+
if not isinstance(result_binding, ValueBinding):
|
|
216
|
+
raise ValueError("Function has conflicting return values")
|
|
217
|
+
set_ctx(after_ctx.branch_with_scope(None, before_ctx.scope.copy()))
|
|
218
|
+
return result_binding.value
|
|
219
|
+
|
|
220
|
+
def visit(self, node):
|
|
221
|
+
"""Visit a node."""
|
|
222
|
+
# We want this here so this is filtered out of tracebacks
|
|
223
|
+
method = "visit_" + node.__class__.__name__
|
|
224
|
+
visitor = getattr(self, method, self.generic_visit)
|
|
225
|
+
with self.reporting_errors_at_node(node):
|
|
226
|
+
return visitor(node)
|
|
227
|
+
|
|
228
|
+
def visit_FunctionDef(self, node):
|
|
229
|
+
name = node.name
|
|
230
|
+
signature = self.arguments_to_signature(node.args)
|
|
231
|
+
|
|
232
|
+
def fn(*args, **kwargs):
|
|
233
|
+
bound = signature.bind(*args, **kwargs)
|
|
234
|
+
bound.apply_defaults()
|
|
235
|
+
return Visitor(
|
|
236
|
+
self.source_file,
|
|
237
|
+
bound,
|
|
238
|
+
self.globals,
|
|
239
|
+
self,
|
|
240
|
+
).run(node)
|
|
241
|
+
|
|
242
|
+
fn._meta_fn_ = True
|
|
243
|
+
fn.__name__ = name
|
|
244
|
+
fn.__qualname__ = name
|
|
245
|
+
|
|
246
|
+
for decorator in reversed(node.decorator_list):
|
|
247
|
+
fn = self.handle_call(decorator, self.visit(decorator), fn)
|
|
248
|
+
|
|
249
|
+
ctx().scope.set_value(name, validate_value(fn))
|
|
250
|
+
|
|
251
|
+
def visit_AsyncFunctionDef(self, node):
|
|
252
|
+
raise NotImplementedError("Async functions are not supported")
|
|
253
|
+
|
|
254
|
+
def visit_ClassDef(self, node):
|
|
255
|
+
raise NotImplementedError("Classes within functions are not supported")
|
|
256
|
+
|
|
257
|
+
def visit_Return(self, node):
|
|
258
|
+
value = self.visit(node.value) if node.value else validate_value(None)
|
|
259
|
+
ctx().scope.set_value("$return", value)
|
|
260
|
+
self.return_ctxs.append(ctx())
|
|
261
|
+
set_ctx(ctx().into_dead())
|
|
262
|
+
|
|
263
|
+
def visit_Delete(self, node):
|
|
264
|
+
for target in node.targets:
|
|
265
|
+
match target:
|
|
266
|
+
case ast.Name():
|
|
267
|
+
raise NotImplementedError("Deleting variables is not supported")
|
|
268
|
+
case ast.Subscript(value=value, slice=slice):
|
|
269
|
+
self.handle_delitem(target, self.visit(value), self.visit(slice))
|
|
270
|
+
case ast.Attribute():
|
|
271
|
+
raise NotImplementedError("Deleting attributes is not supported")
|
|
272
|
+
case _:
|
|
273
|
+
raise NotImplementedError("Unsupported delete target")
|
|
274
|
+
|
|
275
|
+
def visit_Assign(self, node):
|
|
276
|
+
value = self.visit(node.value)
|
|
277
|
+
for target in node.targets:
|
|
278
|
+
self.handle_assign(target, value)
|
|
279
|
+
|
|
280
|
+
def visit_TypeAlias(self, node):
|
|
281
|
+
raise NotImplementedError("Type aliases are not supported")
|
|
282
|
+
|
|
283
|
+
def visit_AugAssign(self, node):
|
|
284
|
+
lhs_value = self.visit(node.target)
|
|
285
|
+
rhs_value = self.visit(node.value)
|
|
286
|
+
inplace_fn_name = inplace_ops[type(node.op)]
|
|
287
|
+
regular_fn_name = bin_ops[type(node.op)]
|
|
288
|
+
right_fn_name = rbin_ops[type(node.op)]
|
|
289
|
+
if hasattr(lhs_value, inplace_fn_name):
|
|
290
|
+
result = self.handle_call(node, getattr(lhs_value, inplace_fn_name), rhs_value)
|
|
291
|
+
if not self.is_not_implemented(result):
|
|
292
|
+
if result is not lhs_value:
|
|
293
|
+
raise ValueError("Inplace operation must return the same object")
|
|
294
|
+
self.handle_assign(node.target, result)
|
|
295
|
+
return
|
|
296
|
+
if hasattr(lhs_value, regular_fn_name):
|
|
297
|
+
result = self.handle_call(node, getattr(lhs_value, regular_fn_name), rhs_value)
|
|
298
|
+
if not self.is_not_implemented(result):
|
|
299
|
+
self.handle_assign(node.target, result)
|
|
300
|
+
return
|
|
301
|
+
if hasattr(rhs_value, right_fn_name) and type(lhs_value) is not type(rhs_value):
|
|
302
|
+
result = self.handle_call(node, getattr(rhs_value, right_fn_name), lhs_value)
|
|
303
|
+
if not self.is_not_implemented(result):
|
|
304
|
+
self.handle_assign(node.target, result)
|
|
305
|
+
return
|
|
306
|
+
raise TypeError(
|
|
307
|
+
f"unsupported operand type(s) for {op_to_symbol[type(node.op)]}=: "
|
|
308
|
+
f"'{type(lhs_value).__name__}' and '{type(rhs_value).__name__}'"
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
def visit_AnnAssign(self, node):
|
|
312
|
+
value = self.visit(node.value)
|
|
313
|
+
self.handle_assign(node.target, value)
|
|
314
|
+
|
|
315
|
+
def visit_For(self, node):
|
|
316
|
+
from sonolus.script.internal.tuple_impl import TupleImpl
|
|
317
|
+
|
|
318
|
+
iterable = self.visit(node.iter)
|
|
319
|
+
if isinstance(iterable, TupleImpl):
|
|
320
|
+
# Unroll the loop
|
|
321
|
+
for value in iterable.value:
|
|
322
|
+
set_ctx(ctx().branch(None))
|
|
323
|
+
self.handle_assign(node.target, validate_value(value))
|
|
324
|
+
for stmt in node.body:
|
|
325
|
+
self.visit(stmt)
|
|
326
|
+
return
|
|
327
|
+
iterator = self.handle_call(node, iterable.__iter__)
|
|
328
|
+
if not isinstance(iterator, SonolusIterator):
|
|
329
|
+
raise ValueError("Unsupported iterator")
|
|
330
|
+
writes = scan_writes(node)
|
|
331
|
+
header_ctx = ctx().prepare_loop_header(writes)
|
|
332
|
+
self.loop_head_ctxs.append(header_ctx)
|
|
333
|
+
self.break_ctxs.append([])
|
|
334
|
+
set_ctx(header_ctx)
|
|
335
|
+
has_next = self.ensure_boolean_num(self.handle_call(node, iterator.has_next))
|
|
336
|
+
if has_next._is_py_() and not has_next._as_py_():
|
|
337
|
+
# The loop will never run, continue after evaluating the condition
|
|
338
|
+
for stmt in node.orelse:
|
|
339
|
+
self.visit(stmt)
|
|
340
|
+
return
|
|
341
|
+
ctx().test = has_next.ir()
|
|
342
|
+
body_ctx = ctx().branch(None)
|
|
343
|
+
else_ctx = ctx().branch(0)
|
|
344
|
+
|
|
345
|
+
set_ctx(body_ctx)
|
|
346
|
+
self.handle_assign(node.target, self.handle_call(node, iterator.next))
|
|
347
|
+
for stmt in node.body:
|
|
348
|
+
self.visit(stmt)
|
|
349
|
+
ctx().branch_to_loop_header(header_ctx)
|
|
350
|
+
|
|
351
|
+
set_ctx(else_ctx)
|
|
352
|
+
for stmt in node.orelse:
|
|
353
|
+
self.visit(stmt)
|
|
354
|
+
else_end_ctx = ctx()
|
|
355
|
+
|
|
356
|
+
self.loop_head_ctxs.pop()
|
|
357
|
+
break_ctxs = self.break_ctxs.pop()
|
|
358
|
+
after_ctx = Context.meet([else_end_ctx, *break_ctxs])
|
|
359
|
+
set_ctx(after_ctx)
|
|
360
|
+
|
|
361
|
+
def visit_While(self, node):
|
|
362
|
+
writes = scan_writes(node)
|
|
363
|
+
header_ctx = ctx().prepare_loop_header(writes)
|
|
364
|
+
self.loop_head_ctxs.append(header_ctx)
|
|
365
|
+
self.break_ctxs.append([])
|
|
366
|
+
set_ctx(header_ctx)
|
|
367
|
+
test = self.ensure_boolean_num(self.visit(node.test))
|
|
368
|
+
if test._is_py_() and not test._as_py_():
|
|
369
|
+
# The loop will never run, continue after evaluating the condition
|
|
370
|
+
for stmt in node.orelse:
|
|
371
|
+
self.visit(stmt)
|
|
372
|
+
return
|
|
373
|
+
ctx().test = test.ir()
|
|
374
|
+
body_ctx = ctx().branch(None)
|
|
375
|
+
else_ctx = ctx().branch(0)
|
|
376
|
+
|
|
377
|
+
set_ctx(body_ctx)
|
|
378
|
+
for stmt in node.body:
|
|
379
|
+
self.visit(stmt)
|
|
380
|
+
ctx().branch_to_loop_header(header_ctx)
|
|
381
|
+
|
|
382
|
+
set_ctx(else_ctx)
|
|
383
|
+
for stmt in node.orelse:
|
|
384
|
+
self.visit(stmt)
|
|
385
|
+
else_end_ctx = ctx()
|
|
386
|
+
|
|
387
|
+
self.loop_head_ctxs.pop()
|
|
388
|
+
break_ctxs = self.break_ctxs.pop()
|
|
389
|
+
after_ctx = Context.meet([else_end_ctx, *break_ctxs])
|
|
390
|
+
set_ctx(after_ctx)
|
|
391
|
+
|
|
392
|
+
def visit_If(self, node):
|
|
393
|
+
test = self.ensure_boolean_num(self.visit(node.test))
|
|
394
|
+
|
|
395
|
+
if test._is_py_():
|
|
396
|
+
if test._as_py_():
|
|
397
|
+
for stmt in node.body:
|
|
398
|
+
self.visit(stmt)
|
|
399
|
+
else:
|
|
400
|
+
for stmt in node.orelse:
|
|
401
|
+
self.visit(stmt)
|
|
402
|
+
return
|
|
403
|
+
|
|
404
|
+
ctx_init = ctx()
|
|
405
|
+
ctx_init.test = test.ir()
|
|
406
|
+
true_ctx = ctx_init.branch(None)
|
|
407
|
+
false_ctx = ctx_init.branch(0)
|
|
408
|
+
|
|
409
|
+
set_ctx(true_ctx)
|
|
410
|
+
for stmt in node.body:
|
|
411
|
+
self.visit(stmt)
|
|
412
|
+
true_end_ctx = ctx()
|
|
413
|
+
|
|
414
|
+
set_ctx(false_ctx)
|
|
415
|
+
for stmt in node.orelse:
|
|
416
|
+
self.visit(stmt)
|
|
417
|
+
false_end_ctx = ctx()
|
|
418
|
+
|
|
419
|
+
set_ctx(Context.meet([true_end_ctx, false_end_ctx]))
|
|
420
|
+
|
|
421
|
+
def visit_With(self, node):
|
|
422
|
+
raise NotImplementedError("With statements are not supported")
|
|
423
|
+
|
|
424
|
+
def visit_AsyncWith(self, node):
|
|
425
|
+
raise NotImplementedError("Async with statements are not supported")
|
|
426
|
+
|
|
427
|
+
def visit_Match(self, node):
|
|
428
|
+
subject = self.visit(node.subject)
|
|
429
|
+
end_ctxs = []
|
|
430
|
+
for case in node.cases:
|
|
431
|
+
if not ctx().live:
|
|
432
|
+
break
|
|
433
|
+
true_ctx, false_ctx = self.handle_match_pattern(subject, case.pattern)
|
|
434
|
+
if not true_ctx.live:
|
|
435
|
+
set_ctx(false_ctx)
|
|
436
|
+
continue
|
|
437
|
+
set_ctx(true_ctx)
|
|
438
|
+
guard = self.ensure_boolean_num(self.visit(case.guard)) if case.guard else validate_value(True)
|
|
439
|
+
if guard._is_py_():
|
|
440
|
+
if guard._as_py_():
|
|
441
|
+
for stmt in case.body:
|
|
442
|
+
self.visit(stmt)
|
|
443
|
+
end_ctxs.append(ctx())
|
|
444
|
+
else:
|
|
445
|
+
# Merge failing before the guard and failing now at the guard (which we know is guaranteed to fail)
|
|
446
|
+
false_ctx = Context.meet([ctx(), false_ctx])
|
|
447
|
+
else:
|
|
448
|
+
ctx().test = guard.ir()
|
|
449
|
+
guard_true_ctx = ctx().branch(None)
|
|
450
|
+
guard_false_ctx = ctx().branch(0)
|
|
451
|
+
set_ctx(guard_true_ctx)
|
|
452
|
+
for stmt in case.body:
|
|
453
|
+
self.visit(stmt)
|
|
454
|
+
end_ctxs.append(ctx())
|
|
455
|
+
false_ctx = Context.meet([false_ctx, guard_false_ctx])
|
|
456
|
+
set_ctx(false_ctx)
|
|
457
|
+
end_ctxs.append(ctx())
|
|
458
|
+
if end_ctxs:
|
|
459
|
+
set_ctx(Context.meet(end_ctxs))
|
|
460
|
+
|
|
461
|
+
def handle_match_pattern(self, subject: Value, pattern: ast.pattern) -> tuple[Context, Context]:
|
|
462
|
+
from sonolus.script.internal.generic import validate_type_spec
|
|
463
|
+
from sonolus.script.internal.tuple_impl import TupleImpl
|
|
464
|
+
|
|
465
|
+
if not ctx().live:
|
|
466
|
+
return ctx().into_dead(), ctx()
|
|
467
|
+
|
|
468
|
+
match pattern:
|
|
469
|
+
case ast.MatchValue(value=value):
|
|
470
|
+
value = self.visit(value)
|
|
471
|
+
test = self.ensure_boolean_num(validate_value(subject == value))
|
|
472
|
+
if test._is_py_():
|
|
473
|
+
if test._as_py_():
|
|
474
|
+
return ctx(), ctx().into_dead()
|
|
475
|
+
else:
|
|
476
|
+
return ctx().into_dead(), ctx()
|
|
477
|
+
ctx_init = ctx()
|
|
478
|
+
ctx_init.test = test.ir()
|
|
479
|
+
true_ctx = ctx_init.branch(None)
|
|
480
|
+
false_ctx = ctx_init.branch(0)
|
|
481
|
+
return true_ctx, false_ctx
|
|
482
|
+
case ast.MatchSingleton(value=value):
|
|
483
|
+
match value:
|
|
484
|
+
case True:
|
|
485
|
+
raise NotImplementedError("Matching against True is not supported, use 1 instead")
|
|
486
|
+
case False:
|
|
487
|
+
raise NotImplementedError("Matching against False is not supported, use 0 instead")
|
|
488
|
+
case None:
|
|
489
|
+
test = validate_value(subject._is_py_() and subject._as_py_() is None)
|
|
490
|
+
case _:
|
|
491
|
+
raise NotImplementedError("Unsupported match singleton")
|
|
492
|
+
ctx_init = ctx()
|
|
493
|
+
ctx_init.test = test.ir()
|
|
494
|
+
true_ctx = ctx_init.branch(None)
|
|
495
|
+
false_ctx = ctx_init.branch(0)
|
|
496
|
+
return true_ctx, false_ctx
|
|
497
|
+
case ast.MatchSequence(patterns=patterns):
|
|
498
|
+
target_len = len(patterns)
|
|
499
|
+
if not (isinstance(subject, Sequence | TupleImpl)):
|
|
500
|
+
return ctx().into_dead(), ctx()
|
|
501
|
+
length_test = self.ensure_boolean_num(validate_value(_len(subject) == target_len))
|
|
502
|
+
ctx_init = ctx()
|
|
503
|
+
if not length_test._is_py_():
|
|
504
|
+
ctx_init.test = length_test.ir()
|
|
505
|
+
true_ctx = ctx_init.branch(None)
|
|
506
|
+
false_ctxs = [ctx_init.branch(0)]
|
|
507
|
+
elif length_test._as_py_():
|
|
508
|
+
true_ctx = ctx_init
|
|
509
|
+
false_ctxs = []
|
|
510
|
+
else:
|
|
511
|
+
return ctx().into_dead(), ctx()
|
|
512
|
+
set_ctx(true_ctx)
|
|
513
|
+
for i, subpattern in enumerate(patterns):
|
|
514
|
+
if not ctx().live:
|
|
515
|
+
break
|
|
516
|
+
value = self.handle_getitem(subpattern, subject, validate_value(i))
|
|
517
|
+
true_ctx, false_ctx = self.handle_match_pattern(value, subpattern)
|
|
518
|
+
false_ctxs.append(false_ctx)
|
|
519
|
+
set_ctx(true_ctx)
|
|
520
|
+
return true_ctx, Context.meet(false_ctxs)
|
|
521
|
+
case ast.MatchMapping():
|
|
522
|
+
raise NotImplementedError("Match mappings are not supported")
|
|
523
|
+
case ast.MatchClass(cls=cls, patterns=patterns, kwd_attrs=kwd_attrs, kwd_patterns=kwd_patterns):
|
|
524
|
+
cls = self.visit(cls)
|
|
525
|
+
if cls._is_py_() and cls._as_py_() in {_int, _float, _bool}:
|
|
526
|
+
raise TypeError("Instance check against int, float, or bool is not supported, use Num instead")
|
|
527
|
+
cls = validate_type_spec(cls)
|
|
528
|
+
if not isinstance(cls, type):
|
|
529
|
+
raise TypeError("Class is not a type")
|
|
530
|
+
if not isinstance(subject, cls):
|
|
531
|
+
return ctx().into_dead(), ctx()
|
|
532
|
+
if patterns:
|
|
533
|
+
if not hasattr(cls, "__match_args__"):
|
|
534
|
+
raise TypeError("Class does not support match patterns")
|
|
535
|
+
if len(cls.__match_args__) < len(patterns):
|
|
536
|
+
raise ValueError("Too many match patterns")
|
|
537
|
+
# kwd_attrs can't be mixed with patterns on the syntax level,
|
|
538
|
+
# so we can just set it like this since it's empty
|
|
539
|
+
kwd_attrs = cls.__match_args__[: len(patterns)]
|
|
540
|
+
kwd_patterns = patterns
|
|
541
|
+
if kwd_attrs:
|
|
542
|
+
true_ctx = ctx()
|
|
543
|
+
false_ctxs = []
|
|
544
|
+
for attr, subpattern in zip(kwd_attrs, kwd_patterns, strict=False):
|
|
545
|
+
if not hasattr(subject, attr):
|
|
546
|
+
raise AttributeError(f"Object has no attribute {attr}")
|
|
547
|
+
value = self.handle_getattr(subpattern, subject, attr)
|
|
548
|
+
true_ctx, false_ctx = self.handle_match_pattern(value, subpattern)
|
|
549
|
+
false_ctxs.append(false_ctx)
|
|
550
|
+
set_ctx(true_ctx)
|
|
551
|
+
return true_ctx, Context.meet(false_ctxs)
|
|
552
|
+
return ctx(), ctx().into_dead()
|
|
553
|
+
case ast.MatchStar():
|
|
554
|
+
raise NotImplementedError("Match stars are not supported")
|
|
555
|
+
case ast.MatchAs(pattern=pattern, name=name):
|
|
556
|
+
if pattern:
|
|
557
|
+
true_ctx, false_ctx = self.handle_match_pattern(subject, pattern)
|
|
558
|
+
if name:
|
|
559
|
+
true_ctx.scope.set_value(name, subject)
|
|
560
|
+
return true_ctx, false_ctx
|
|
561
|
+
else:
|
|
562
|
+
if name:
|
|
563
|
+
ctx().scope.set_value(name, subject)
|
|
564
|
+
return ctx(), ctx().into_dead()
|
|
565
|
+
case ast.MatchOr():
|
|
566
|
+
true_ctxs = []
|
|
567
|
+
assert pattern.patterns
|
|
568
|
+
for subpattern in pattern.patterns:
|
|
569
|
+
if not ctx().live:
|
|
570
|
+
break
|
|
571
|
+
true_ctx, false_ctx = self.handle_match_pattern(subject, subpattern)
|
|
572
|
+
true_ctxs.append(true_ctx)
|
|
573
|
+
set_ctx(false_ctx)
|
|
574
|
+
return Context.meet(true_ctxs), ctx()
|
|
575
|
+
|
|
576
|
+
def visit_Raise(self, node):
|
|
577
|
+
raise NotImplementedError("Raise statements are not supported")
|
|
578
|
+
|
|
579
|
+
def visit_Try(self, node):
|
|
580
|
+
raise NotImplementedError("Try statements are not supported")
|
|
581
|
+
|
|
582
|
+
def visit_TryStar(self, node):
|
|
583
|
+
raise NotImplementedError("Try* statements are not supported")
|
|
584
|
+
|
|
585
|
+
def visit_Assert(self, node):
|
|
586
|
+
self.handle_call(
|
|
587
|
+
node, assert_true, self.visit(node.test), self.visit(node.msg) if node.msg else validate_value(None)
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
def visit_Import(self, node):
|
|
591
|
+
raise NotImplementedError("Import statements are not supported")
|
|
592
|
+
|
|
593
|
+
def visit_ImportFrom(self, node):
|
|
594
|
+
raise NotImplementedError("Import statements are not supported")
|
|
595
|
+
|
|
596
|
+
def visit_Global(self, node):
|
|
597
|
+
raise NotImplementedError("Global statements are not supported")
|
|
598
|
+
|
|
599
|
+
def visit_Nonlocal(self, node):
|
|
600
|
+
raise NotImplementedError("Nonlocal statements are not supported")
|
|
601
|
+
|
|
602
|
+
def visit_Expr(self, node):
|
|
603
|
+
return self.visit(node.value)
|
|
604
|
+
|
|
605
|
+
def visit_Pass(self, node):
|
|
606
|
+
pass
|
|
607
|
+
|
|
608
|
+
def visit_Break(self, node):
|
|
609
|
+
self.break_ctxs[-1].append(ctx())
|
|
610
|
+
set_ctx(ctx().into_dead())
|
|
611
|
+
|
|
612
|
+
def visit_Continue(self, node):
|
|
613
|
+
ctx().branch_to_loop_header(self.loop_head_ctxs[-1])
|
|
614
|
+
set_ctx(ctx().into_dead())
|
|
615
|
+
|
|
616
|
+
def visit_BoolOp(self, node) -> Value:
|
|
617
|
+
match node.op:
|
|
618
|
+
case ast.And():
|
|
619
|
+
handler = self.handle_and
|
|
620
|
+
case ast.Or():
|
|
621
|
+
handler = self.handle_or
|
|
622
|
+
case _:
|
|
623
|
+
raise NotImplementedError(f"Unsupported bool operator {op_to_symbol[type(node.op)]}")
|
|
624
|
+
|
|
625
|
+
if not node.values:
|
|
626
|
+
raise ValueError("Bool operator requires at least one operand")
|
|
627
|
+
if len(node.values) == 1:
|
|
628
|
+
return self.visit(node.values[0])
|
|
629
|
+
initial, *rest = node.values
|
|
630
|
+
return handler(self.visit(initial), ast.copy_location(ast.BoolOp(op=node.op, values=rest), node))
|
|
631
|
+
|
|
632
|
+
def visit_NamedExpr(self, node):
|
|
633
|
+
value = self.visit(node.value)
|
|
634
|
+
self.handle_assign(node.target, value)
|
|
635
|
+
return value
|
|
636
|
+
|
|
637
|
+
def visit_BinOp(self, node):
|
|
638
|
+
lhs = self.visit(node.left)
|
|
639
|
+
rhs = self.visit(node.right)
|
|
640
|
+
op = bin_ops[type(node.op)]
|
|
641
|
+
if lhs._is_py_() and rhs._is_py_():
|
|
642
|
+
lhs_py = lhs._as_py_()
|
|
643
|
+
rhs_py = rhs._as_py_()
|
|
644
|
+
if isinstance(lhs_py, type) and isinstance(rhs_py, type):
|
|
645
|
+
return validate_value(getattr(lhs_py, op)(rhs_py))
|
|
646
|
+
if hasattr(lhs, op):
|
|
647
|
+
result = self.handle_call(node, getattr(lhs, op), rhs)
|
|
648
|
+
if not self.is_not_implemented(result):
|
|
649
|
+
return result
|
|
650
|
+
if hasattr(rhs, rbin_ops[type(node.op)]) and type(lhs) is not type(rhs):
|
|
651
|
+
result = self.handle_call(node, getattr(rhs, rbin_ops[type(node.op)]), lhs)
|
|
652
|
+
if not self.is_not_implemented(result):
|
|
653
|
+
return result
|
|
654
|
+
raise TypeError(
|
|
655
|
+
f"unsupported operand type(s) for {op_to_symbol[type(node.op)]}: "
|
|
656
|
+
f"'{type(lhs).__name__}' and '{type(rhs).__name__}'"
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
def visit_UnaryOp(self, node):
|
|
660
|
+
operand = self.visit(node.operand)
|
|
661
|
+
if isinstance(node.op, ast.Not):
|
|
662
|
+
return self.ensure_boolean_num(operand).not_()
|
|
663
|
+
op = unary_ops[type(node.op)]
|
|
664
|
+
if hasattr(operand, op):
|
|
665
|
+
return self.handle_call(node, getattr(operand, op))
|
|
666
|
+
raise TypeError(f"bad operand type for unary {op_to_symbol[type(node.op)]}: '{type(operand).__name__}'")
|
|
667
|
+
|
|
668
|
+
def visit_Lambda(self, node):
|
|
669
|
+
signature = self.arguments_to_signature(node.args)
|
|
670
|
+
|
|
671
|
+
def fn(*args, **kwargs):
|
|
672
|
+
bound = signature.bind(*args, **kwargs)
|
|
673
|
+
bound.apply_defaults()
|
|
674
|
+
return Visitor(
|
|
675
|
+
self.source_file,
|
|
676
|
+
bound,
|
|
677
|
+
self.globals,
|
|
678
|
+
self,
|
|
679
|
+
).run(node)
|
|
680
|
+
|
|
681
|
+
fn._meta_fn_ = True
|
|
682
|
+
fn.__name__ = "<lambda>"
|
|
683
|
+
|
|
684
|
+
return validate_value(fn)
|
|
685
|
+
|
|
686
|
+
def visit_IfExp(self, node):
|
|
687
|
+
test = self.ensure_boolean_num(self.visit(node.test))
|
|
688
|
+
|
|
689
|
+
if test._is_py_():
|
|
690
|
+
if test._as_py_():
|
|
691
|
+
return self.visit(node.body)
|
|
692
|
+
else:
|
|
693
|
+
return self.visit(node.orelse)
|
|
694
|
+
|
|
695
|
+
res_name = self.new_name("ifexp")
|
|
696
|
+
ctx_init = ctx()
|
|
697
|
+
ctx_init.test = test.ir()
|
|
698
|
+
|
|
699
|
+
set_ctx(ctx_init.branch(None))
|
|
700
|
+
true_value = self.visit(node.body)
|
|
701
|
+
ctx().scope.set_value(res_name, true_value)
|
|
702
|
+
ctx_true = ctx()
|
|
703
|
+
|
|
704
|
+
set_ctx(ctx_init.branch(0))
|
|
705
|
+
false_value = self.visit(node.orelse)
|
|
706
|
+
ctx().scope.set_value(res_name, false_value)
|
|
707
|
+
ctx_false = ctx()
|
|
708
|
+
|
|
709
|
+
set_ctx(Context.meet([ctx_true, ctx_false]))
|
|
710
|
+
return ctx().scope.get_value(res_name)
|
|
711
|
+
|
|
712
|
+
def visit_Dict(self, node):
|
|
713
|
+
return validate_value({self.visit(k): self.visit(v) for k, v in zip(node.keys, node.values, strict=True)})
|
|
714
|
+
|
|
715
|
+
def visit_Set(self, node):
|
|
716
|
+
raise NotImplementedError("Set literals are not supported")
|
|
717
|
+
|
|
718
|
+
def visit_ListComp(self, node):
|
|
719
|
+
raise NotImplementedError("List comprehensions are not supported")
|
|
720
|
+
|
|
721
|
+
def visit_SetComp(self, node):
|
|
722
|
+
raise NotImplementedError("Set comprehensions are not supported")
|
|
723
|
+
|
|
724
|
+
def visit_DictComp(self, node):
|
|
725
|
+
raise NotImplementedError("Dict comprehensions are not supported")
|
|
726
|
+
|
|
727
|
+
def visit_GeneratorExp(self, node):
|
|
728
|
+
raise NotImplementedError("Generator expressions are not supported")
|
|
729
|
+
|
|
730
|
+
def visit_Await(self, node):
|
|
731
|
+
raise NotImplementedError("Await expressions are not supported")
|
|
732
|
+
|
|
733
|
+
def visit_Yield(self, node):
|
|
734
|
+
raise NotImplementedError("Yield expressions are not supported")
|
|
735
|
+
|
|
736
|
+
def visit_YieldFrom(self, node):
|
|
737
|
+
raise NotImplementedError("Yield from expressions are not supported")
|
|
738
|
+
|
|
739
|
+
def _has_real_method(self, obj: Value, method_name: str) -> bool:
|
|
740
|
+
return hasattr(obj, method_name) and not isinstance(getattr(obj, method_name), MethodWrapperType)
|
|
741
|
+
|
|
742
|
+
def visit_Compare(self, node):
|
|
743
|
+
result_name = self.new_name("compare")
|
|
744
|
+
ctx().scope.set_value(result_name, Num._accept_(0))
|
|
745
|
+
l_val = self.visit(node.left)
|
|
746
|
+
false_ctxs = []
|
|
747
|
+
for i, (op, rhs) in enumerate(zip(node.ops, node.comparators, strict=True)):
|
|
748
|
+
r_val = self.visit(rhs)
|
|
749
|
+
inverted = isinstance(op, ast.NotIn)
|
|
750
|
+
result = None
|
|
751
|
+
if isinstance(op, ast.Is | ast.IsNot):
|
|
752
|
+
if not (r_val._is_py_() and r_val._as_py_() is None):
|
|
753
|
+
raise TypeError("The right operand of 'is' must be None")
|
|
754
|
+
if isinstance(op, ast.Is):
|
|
755
|
+
result = Num._accept_(l_val._is_py_() and l_val._as_py_() is None)
|
|
756
|
+
else:
|
|
757
|
+
result = Num._accept_(not (l_val._is_py_() and l_val._as_py_() is None))
|
|
758
|
+
elif type(op) in comp_ops and self._has_real_method(l_val, comp_ops[type(op)]):
|
|
759
|
+
result = self.handle_call(node, getattr(l_val, comp_ops[type(op)]), r_val)
|
|
760
|
+
if (
|
|
761
|
+
(result is None or self.is_not_implemented(result))
|
|
762
|
+
and type(op) in rcomp_ops
|
|
763
|
+
and self._has_real_method(r_val, rcomp_ops[type(op)])
|
|
764
|
+
):
|
|
765
|
+
result = self.handle_call(node, getattr(r_val, rcomp_ops[type(op)]), l_val)
|
|
766
|
+
if result is None or self.is_not_implemented(result):
|
|
767
|
+
if type(op) is ast.Eq:
|
|
768
|
+
result = Num._accept_(l_val is r_val)
|
|
769
|
+
elif type(op) is ast.NotEq:
|
|
770
|
+
result = Num._accept_(l_val is not r_val)
|
|
771
|
+
else:
|
|
772
|
+
raise TypeError(
|
|
773
|
+
f"'{op_to_symbol[type(op)]}' not supported between instances of '{type(l_val).__name__}' and "
|
|
774
|
+
f"'{type(r_val).__name__}'"
|
|
775
|
+
)
|
|
776
|
+
result = self.ensure_boolean_num(result)
|
|
777
|
+
if inverted:
|
|
778
|
+
result = result.not_()
|
|
779
|
+
curr_ctx = ctx()
|
|
780
|
+
if i == len(node.ops) - 1:
|
|
781
|
+
curr_ctx.scope.set_value(result_name, result)
|
|
782
|
+
elif result._is_py_():
|
|
783
|
+
if result._as_py_():
|
|
784
|
+
l_val = r_val
|
|
785
|
+
else:
|
|
786
|
+
false_ctxs.append(curr_ctx)
|
|
787
|
+
set_ctx(curr_ctx.into_dead())
|
|
788
|
+
break
|
|
789
|
+
else:
|
|
790
|
+
curr_ctx.test = result.ir()
|
|
791
|
+
true_ctx = curr_ctx.branch(None)
|
|
792
|
+
false_ctx = curr_ctx.branch(0)
|
|
793
|
+
false_ctxs.append(false_ctx)
|
|
794
|
+
set_ctx(true_ctx)
|
|
795
|
+
l_val = r_val
|
|
796
|
+
last_ctx = ctx() # This is the result of the last comparison returning true
|
|
797
|
+
set_ctx(Context.meet([last_ctx, *false_ctxs]))
|
|
798
|
+
return ctx().scope.get_value(result_name)
|
|
799
|
+
|
|
800
|
+
def visit_Call(self, node):
|
|
801
|
+
from sonolus.script.internal.dict_impl import DictImpl
|
|
802
|
+
|
|
803
|
+
fn = self.visit(node.func)
|
|
804
|
+
args = []
|
|
805
|
+
kwargs = {}
|
|
806
|
+
for arg in node.args:
|
|
807
|
+
if isinstance(arg, ast.Starred):
|
|
808
|
+
args.extend(self.handle_starred(self.visit(arg.value)))
|
|
809
|
+
else:
|
|
810
|
+
args.append(self.visit(arg))
|
|
811
|
+
for keyword in node.keywords:
|
|
812
|
+
if keyword.arg:
|
|
813
|
+
kwargs[keyword.arg] = self.visit(keyword.value)
|
|
814
|
+
else:
|
|
815
|
+
value = self.visit(keyword.value)
|
|
816
|
+
if isinstance(value, DictImpl):
|
|
817
|
+
if not all(isinstance(k, str) for k in value.value):
|
|
818
|
+
raise ValueError("Keyword arguments must be strings")
|
|
819
|
+
kwargs.update(value.value)
|
|
820
|
+
else:
|
|
821
|
+
raise ValueError("Starred keyword arguments (**kwargs) must be dictionaries")
|
|
822
|
+
return self.handle_call(node, fn, *args, **kwargs)
|
|
823
|
+
|
|
824
|
+
def visit_FormattedValue(self, node):
|
|
825
|
+
raise NotImplementedError("F-strings are not supported")
|
|
826
|
+
|
|
827
|
+
def visit_JoinedStr(self, node):
|
|
828
|
+
raise NotImplementedError("F-strings are not supported")
|
|
829
|
+
|
|
830
|
+
def visit_Constant(self, node):
|
|
831
|
+
return validate_value(node.value)
|
|
832
|
+
|
|
833
|
+
def visit_Attribute(self, node):
|
|
834
|
+
return self.handle_getattr(node, self.visit(node.value), node.attr)
|
|
835
|
+
|
|
836
|
+
def visit_Subscript(self, node):
|
|
837
|
+
value = self.visit(node.value)
|
|
838
|
+
slice_value = self.visit(node.slice)
|
|
839
|
+
return self.handle_getitem(node, value, slice_value)
|
|
840
|
+
|
|
841
|
+
def visit_Starred(self, node):
|
|
842
|
+
raise NotImplementedError("Starred expressions are not supported")
|
|
843
|
+
|
|
844
|
+
def visit_Name(self, node):
|
|
845
|
+
self.active_ctx = ctx()
|
|
846
|
+
v = self
|
|
847
|
+
while v:
|
|
848
|
+
if not isinstance(v.active_ctx.scope.get_binding(node.id), EmptyBinding):
|
|
849
|
+
return v.active_ctx.scope.get_value(node.id)
|
|
850
|
+
v = v.parent
|
|
851
|
+
if node.id in self.globals:
|
|
852
|
+
value = self.globals[node.id]
|
|
853
|
+
if value is ctx:
|
|
854
|
+
raise ValueError("Unexpected use of ctx in non meta-function")
|
|
855
|
+
return validate_value(BUILTIN_IMPLS.get(id(value), value))
|
|
856
|
+
raise NameError(f"Name {node.id} is not defined")
|
|
857
|
+
|
|
858
|
+
def visit_List(self, node):
|
|
859
|
+
raise NotImplementedError("List literals are not supported")
|
|
860
|
+
|
|
861
|
+
def visit_Tuple(self, node):
|
|
862
|
+
values = []
|
|
863
|
+
for elt in node.elts:
|
|
864
|
+
if isinstance(elt, ast.Starred):
|
|
865
|
+
values.extend(self.handle_starred(self.visit(elt.value)))
|
|
866
|
+
else:
|
|
867
|
+
values.append(self.visit(elt))
|
|
868
|
+
return validate_value(tuple(values))
|
|
869
|
+
|
|
870
|
+
def visit_Slice(self, node):
|
|
871
|
+
raise NotImplementedError("Slices are not supported")
|
|
872
|
+
|
|
873
|
+
def handle_assign(self, target: ast.stmt | ast.expr, value: Value):
|
|
874
|
+
match target:
|
|
875
|
+
case ast.Name(id=name):
|
|
876
|
+
ctx().scope.set_value(name, value)
|
|
877
|
+
case ast.Attribute(value=attr_value, attr=attr):
|
|
878
|
+
attr_value = self.visit(attr_value)
|
|
879
|
+
self.handle_setattr(target, attr_value, attr, value)
|
|
880
|
+
case ast.Subscript(value=sub_value, slice=slice_expr):
|
|
881
|
+
sub_value = self.visit(sub_value)
|
|
882
|
+
slice_value = self.visit(slice_expr)
|
|
883
|
+
self.handle_setitem(target, sub_value, slice_value, value)
|
|
884
|
+
case ast.Tuple(elts=elts) | ast.List(elts=elts):
|
|
885
|
+
values = self.handle_starred(value)
|
|
886
|
+
if len(elts) != len(values):
|
|
887
|
+
raise ValueError("Unpacking assignment requires the same number of elements")
|
|
888
|
+
for elt, v in zip(elts, values, strict=False):
|
|
889
|
+
self.handle_assign(elt, validate_value(v))
|
|
890
|
+
case ast.Starred():
|
|
891
|
+
raise NotImplementedError("Starred assignment is not supported")
|
|
892
|
+
case _:
|
|
893
|
+
raise NotImplementedError("Unsupported assignment target")
|
|
894
|
+
|
|
895
|
+
def handle_and(self, l_val: Value, r_expr: ast.expr) -> Value:
|
|
896
|
+
ctx_init = ctx()
|
|
897
|
+
l_val = self.ensure_boolean_num(l_val)
|
|
898
|
+
|
|
899
|
+
if l_val._is_py_():
|
|
900
|
+
if l_val._as_py_():
|
|
901
|
+
# The rhs is definitely evaluated, so we can return it directly
|
|
902
|
+
return self.ensure_boolean_num(self.visit(r_expr))
|
|
903
|
+
else:
|
|
904
|
+
return l_val
|
|
905
|
+
|
|
906
|
+
ctx_init.test = l_val.ir()
|
|
907
|
+
res_name = self.new_name("and")
|
|
908
|
+
|
|
909
|
+
set_ctx(ctx_init.branch(None))
|
|
910
|
+
r_val = self.ensure_boolean_num(self.visit(r_expr))
|
|
911
|
+
ctx().scope.set_value(res_name, r_val)
|
|
912
|
+
ctx_true = ctx()
|
|
913
|
+
|
|
914
|
+
set_ctx(ctx_init.branch(0))
|
|
915
|
+
ctx().scope.set_value(res_name, Num._accept_(0))
|
|
916
|
+
ctx_false = ctx()
|
|
917
|
+
|
|
918
|
+
set_ctx(Context.meet([ctx_true, ctx_false]))
|
|
919
|
+
if l_val._is_py_() and r_val._is_py_():
|
|
920
|
+
return Num._accept_(l_val._as_py_() and r_val._as_py_())
|
|
921
|
+
return ctx().scope.get_value(res_name)
|
|
922
|
+
|
|
923
|
+
def handle_or(self, l_val: Value, r_expr: ast.expr) -> Value:
|
|
924
|
+
ctx_init = ctx()
|
|
925
|
+
l_val = self.ensure_boolean_num(l_val)
|
|
926
|
+
|
|
927
|
+
if l_val._is_py_():
|
|
928
|
+
if l_val._as_py_():
|
|
929
|
+
return l_val
|
|
930
|
+
else:
|
|
931
|
+
# The rhs is definitely evaluated, so we can return it directly
|
|
932
|
+
return self.ensure_boolean_num(self.visit(r_expr))
|
|
933
|
+
|
|
934
|
+
ctx_init.test = l_val.ir()
|
|
935
|
+
res_name = self.new_name("or")
|
|
936
|
+
|
|
937
|
+
set_ctx(ctx_init.branch(None))
|
|
938
|
+
ctx().scope.set_value(res_name, l_val)
|
|
939
|
+
ctx_true = ctx()
|
|
940
|
+
|
|
941
|
+
set_ctx(ctx_init.branch(0))
|
|
942
|
+
r_val = self.ensure_boolean_num(self.visit(r_expr))
|
|
943
|
+
ctx().scope.set_value(res_name, r_val)
|
|
944
|
+
ctx_false = ctx()
|
|
945
|
+
|
|
946
|
+
set_ctx(Context.meet([ctx_true, ctx_false]))
|
|
947
|
+
if l_val._is_py_() and r_val._is_py_():
|
|
948
|
+
return Num._accept_(l_val._as_py_() or r_val._as_py_())
|
|
949
|
+
return ctx().scope.get_value(res_name)
|
|
950
|
+
|
|
951
|
+
def generic_visit(self, node):
|
|
952
|
+
if isinstance(node, ast.stmt | ast.expr):
|
|
953
|
+
with self.reporting_errors_at_node(node):
|
|
954
|
+
raise NotImplementedError(f"Unsupported syntax: {type(node).__name__}")
|
|
955
|
+
raise NotImplementedError(f"Unsupported syntax: {type(node).__name__}")
|
|
956
|
+
|
|
957
|
+
def handle_getattr(self, node: ast.stmt | ast.expr, target: Value, key: str) -> Value:
|
|
958
|
+
with self.reporting_errors_at_node(node):
|
|
959
|
+
if isinstance(target, ConstantValue):
|
|
960
|
+
# Unwrap so we can access fields
|
|
961
|
+
target = target._as_py_()
|
|
962
|
+
descriptor = type(target).__dict__.get(key)
|
|
963
|
+
match descriptor:
|
|
964
|
+
case property(fget=getter):
|
|
965
|
+
return self.handle_call(node, getter, target)
|
|
966
|
+
case SonolusDescriptor() | FunctionType() | classmethod() | staticmethod() | None:
|
|
967
|
+
return validate_value(getattr(target, key))
|
|
968
|
+
case non_descriptor if not hasattr(non_descriptor, "__get__"):
|
|
969
|
+
return validate_value(getattr(target, key))
|
|
970
|
+
case _:
|
|
971
|
+
raise TypeError(f"Unsupported field or descriptor {key}")
|
|
972
|
+
|
|
973
|
+
def handle_setattr(self, node: ast.stmt | ast.expr, target: Value, key: str, value: Value):
|
|
974
|
+
with self.reporting_errors_at_node(node):
|
|
975
|
+
if target._is_py_():
|
|
976
|
+
target = target._as_py_()
|
|
977
|
+
descriptor = getattr(type(target), key, None)
|
|
978
|
+
match descriptor:
|
|
979
|
+
case property(fset=setter):
|
|
980
|
+
if setter is None:
|
|
981
|
+
raise AttributeError(f"Cannot set attribute {key} because property has no setter")
|
|
982
|
+
self.handle_call(node, setter, target, value)
|
|
983
|
+
case SonolusDescriptor():
|
|
984
|
+
setattr(target, key, value)
|
|
985
|
+
case _:
|
|
986
|
+
raise TypeError(f"Unsupported field or descriptor {key}")
|
|
987
|
+
|
|
988
|
+
def handle_call[**P, R](
|
|
989
|
+
self, node: ast.stmt | ast.expr, fn: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs
|
|
990
|
+
) -> R:
|
|
991
|
+
"""Handles a call to the given callable."""
|
|
992
|
+
self.active_ctx = ctx()
|
|
993
|
+
if (
|
|
994
|
+
isinstance(fn, Value)
|
|
995
|
+
and fn._is_py_()
|
|
996
|
+
and isinstance(fn._as_py_(), type)
|
|
997
|
+
and issubclass(fn._as_py_(), Value)
|
|
998
|
+
):
|
|
999
|
+
return validate_value(self.execute_at_node(node, fn._as_py_(), *args, **kwargs))
|
|
1000
|
+
else:
|
|
1001
|
+
return self.execute_at_node(node, lambda: validate_value(compile_and_call(fn, *args, **kwargs)))
|
|
1002
|
+
|
|
1003
|
+
def handle_getitem(self, node: ast.stmt | ast.expr, target: Value, key: Value) -> Value:
|
|
1004
|
+
with self.reporting_errors_at_node(node):
|
|
1005
|
+
if target._is_py_() and isinstance(target._as_py_(), type):
|
|
1006
|
+
if not key._is_py_():
|
|
1007
|
+
raise ValueError("Type parameters must be compile-time constants")
|
|
1008
|
+
return validate_value(target._as_py_()[key._as_py_()])
|
|
1009
|
+
else:
|
|
1010
|
+
if isinstance(target, Value) and hasattr(target, "__getitem__"):
|
|
1011
|
+
return self.handle_call(node, target.__getitem__, key)
|
|
1012
|
+
raise TypeError(f"Cannot get items on {type(target).__name__}")
|
|
1013
|
+
|
|
1014
|
+
def handle_setitem(self, node: ast.stmt | ast.expr, target: Value, key: Value, value: Value):
|
|
1015
|
+
with self.reporting_errors_at_node(node):
|
|
1016
|
+
if isinstance(target, Value) and hasattr(target, "__setitem__"):
|
|
1017
|
+
return self.handle_call(node, target.__setitem__, key, value)
|
|
1018
|
+
raise TypeError(f"Cannot set items on {type(target).__name__}")
|
|
1019
|
+
|
|
1020
|
+
def handle_delitem(self, node: ast.stmt | ast.expr, target: Value, key: Value):
|
|
1021
|
+
with self.reporting_errors_at_node(node):
|
|
1022
|
+
if isinstance(target, Value) and hasattr(target, "__delitem__"):
|
|
1023
|
+
return self.handle_call(node, target.__delitem__, key)
|
|
1024
|
+
raise TypeError(f"Cannot delete items on {type(target).__name__}")
|
|
1025
|
+
|
|
1026
|
+
def handle_starred(self, value: Value) -> tuple[Value, ...]:
|
|
1027
|
+
if isinstance(value, TupleImpl):
|
|
1028
|
+
return value.value
|
|
1029
|
+
raise ValueError("Unsupported starred expression")
|
|
1030
|
+
|
|
1031
|
+
def is_not_implemented(self, value):
|
|
1032
|
+
value = validate_value(value)
|
|
1033
|
+
return value._is_py_() and value._as_py_() is NotImplemented
|
|
1034
|
+
|
|
1035
|
+
def ensure_boolean_num(self, value) -> Num:
|
|
1036
|
+
# This just checks the type for now, although we could support custom __bool__ implementations in the future
|
|
1037
|
+
if not _is_num(value):
|
|
1038
|
+
raise TypeError(f"Invalid type where a bool (Num) was expected: {type(value).__name__}")
|
|
1039
|
+
return value
|
|
1040
|
+
|
|
1041
|
+
def arguments_to_signature(self, arguments: ast.arguments) -> inspect.Signature:
|
|
1042
|
+
parameters: list[inspect.Parameter] = []
|
|
1043
|
+
pos_only_count = len(arguments.posonlyargs)
|
|
1044
|
+
for i, arg in enumerate(arguments.posonlyargs):
|
|
1045
|
+
default_idx = i - pos_only_count + len(arguments.defaults)
|
|
1046
|
+
default = self.visit(arguments.defaults[default_idx]) if default_idx >= 0 else None
|
|
1047
|
+
param = inspect.Parameter(
|
|
1048
|
+
name=arg.arg,
|
|
1049
|
+
kind=inspect.Parameter.POSITIONAL_ONLY,
|
|
1050
|
+
default=default if default_idx >= 0 else inspect.Parameter.empty,
|
|
1051
|
+
annotation=inspect.Parameter.empty,
|
|
1052
|
+
)
|
|
1053
|
+
parameters.append(param)
|
|
1054
|
+
|
|
1055
|
+
pos_kw_count = len(arguments.args)
|
|
1056
|
+
for i, arg in enumerate(arguments.args):
|
|
1057
|
+
default_idx = i - pos_kw_count + len(arguments.defaults)
|
|
1058
|
+
default = self.visit(arguments.defaults[default_idx]) if default_idx >= 0 else None
|
|
1059
|
+
param = inspect.Parameter(
|
|
1060
|
+
name=arg.arg,
|
|
1061
|
+
kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
1062
|
+
default=default if default_idx >= 0 else inspect.Parameter.empty,
|
|
1063
|
+
annotation=inspect.Parameter.empty,
|
|
1064
|
+
)
|
|
1065
|
+
parameters.append(param)
|
|
1066
|
+
|
|
1067
|
+
if arguments.vararg:
|
|
1068
|
+
param = inspect.Parameter(
|
|
1069
|
+
name=arguments.vararg.arg,
|
|
1070
|
+
kind=inspect.Parameter.VAR_POSITIONAL,
|
|
1071
|
+
default=inspect.Parameter.empty,
|
|
1072
|
+
annotation=inspect.Parameter.empty,
|
|
1073
|
+
)
|
|
1074
|
+
parameters.append(param)
|
|
1075
|
+
|
|
1076
|
+
for i, arg in enumerate(arguments.kwonlyargs):
|
|
1077
|
+
default = self.visit(arguments.kw_defaults[i]) if arguments.kw_defaults[i] is not None else None
|
|
1078
|
+
param = inspect.Parameter(
|
|
1079
|
+
name=arg.arg,
|
|
1080
|
+
kind=inspect.Parameter.KEYWORD_ONLY,
|
|
1081
|
+
default=default if default is not None else inspect.Parameter.empty,
|
|
1082
|
+
annotation=inspect.Parameter.empty,
|
|
1083
|
+
)
|
|
1084
|
+
parameters.append(param)
|
|
1085
|
+
|
|
1086
|
+
if arguments.kwarg:
|
|
1087
|
+
param = inspect.Parameter(
|
|
1088
|
+
name=arguments.kwarg.arg,
|
|
1089
|
+
kind=inspect.Parameter.VAR_KEYWORD,
|
|
1090
|
+
default=inspect.Parameter.empty,
|
|
1091
|
+
annotation=inspect.Parameter.empty,
|
|
1092
|
+
)
|
|
1093
|
+
parameters.append(param)
|
|
1094
|
+
|
|
1095
|
+
return inspect.Signature(parameters)
|
|
1096
|
+
|
|
1097
|
+
def raise_exception_at_node(self, node: ast.stmt | ast.expr, cause: Exception) -> Never:
|
|
1098
|
+
"""Throws a compilation error at the given node."""
|
|
1099
|
+
|
|
1100
|
+
def thrower() -> Never:
|
|
1101
|
+
raise CompilationError(str(cause)) from cause
|
|
1102
|
+
|
|
1103
|
+
self.execute_at_node(node, thrower)
|
|
1104
|
+
|
|
1105
|
+
def execute_at_node[**P, R](
|
|
1106
|
+
self, node: ast.stmt | ast.expr, fn: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs
|
|
1107
|
+
) -> R:
|
|
1108
|
+
"""Executes the given function at the given node for a better traceback."""
|
|
1109
|
+
expr = ast.Expression(
|
|
1110
|
+
body=ast.Call(
|
|
1111
|
+
func=ast.Name(id="fn", ctx=ast.Load()),
|
|
1112
|
+
args=[ast.Starred(value=ast.Name(id="args", ctx=ast.Load()), ctx=ast.Load())],
|
|
1113
|
+
keywords=[ast.keyword(value=ast.Name(id="kwargs", ctx=ast.Load()), arg=None)],
|
|
1114
|
+
lineno=node.lineno,
|
|
1115
|
+
col_offset=node.col_offset,
|
|
1116
|
+
end_lineno=node.end_lineno,
|
|
1117
|
+
end_col_offset=node.end_col_offset,
|
|
1118
|
+
),
|
|
1119
|
+
)
|
|
1120
|
+
expr = ast.fix_missing_locations(expr)
|
|
1121
|
+
return eval(
|
|
1122
|
+
compile(expr, filename=self.source_file, mode="eval"),
|
|
1123
|
+
{"fn": fn, "args": args, "kwargs": kwargs, "_filter_traceback_": True},
|
|
1124
|
+
)
|
|
1125
|
+
|
|
1126
|
+
def reporting_errors_at_node(self, node: ast.stmt | ast.expr):
|
|
1127
|
+
return ReportingErrorsAtNode(self, node)
|
|
1128
|
+
|
|
1129
|
+
def new_name(self, name: str):
|
|
1130
|
+
self.used_names[name] = self.used_names.get(name, 0) + 1
|
|
1131
|
+
return f"${name}_{self.used_names[name]}"
|
|
1132
|
+
|
|
1133
|
+
|
|
1134
|
+
# Not using @contextmanager so it doesn't end up in tracebacks
|
|
1135
|
+
class ReportingErrorsAtNode:
|
|
1136
|
+
def __init__(self, compiler, node: ast.stmt | ast.expr):
|
|
1137
|
+
self.compiler = compiler
|
|
1138
|
+
self.node = node
|
|
1139
|
+
|
|
1140
|
+
def __enter__(self):
|
|
1141
|
+
return self
|
|
1142
|
+
|
|
1143
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
1144
|
+
if exc_type is None:
|
|
1145
|
+
return
|
|
1146
|
+
|
|
1147
|
+
if issubclass(exc_type, CompilationError):
|
|
1148
|
+
raise exc_value from exc_value.__cause__
|
|
1149
|
+
|
|
1150
|
+
if exc_value is not None:
|
|
1151
|
+
self.compiler.raise_exception_at_node(self.node, exc_value)
|