Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/Ast/src/PrettyPrinter.cpp
2725 views
1
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2
#include "Luau/PrettyPrinter.h"
3
4
#include "Luau/Parser.h"
5
#include "Luau/StringUtils.h"
6
#include "Luau/Common.h"
7
8
#include <algorithm>
9
#include <limits>
10
#include <math.h>
11
12
namespace
13
{
14
bool isIdentifierStartChar(char c)
15
{
16
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_';
17
}
18
19
bool isDigit(char c)
20
{
21
return c >= '0' && c <= '9';
22
}
23
24
bool isIdentifierChar(char c)
25
{
26
return isIdentifierStartChar(c) || isDigit(c);
27
}
28
29
const std::vector<std::string> keywords = {"and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in",
30
"local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while"};
31
32
} // namespace
33
34
namespace Luau
35
{
36
37
struct Writer
38
{
39
virtual ~Writer() {}
40
41
virtual void advance(const Position&) = 0;
42
virtual void newline() = 0;
43
virtual void space() = 0;
44
virtual void maybeSpace(const Position& newPos, int reserve) = 0;
45
virtual void write(std::string_view) = 0;
46
virtual void writeMultiline(std::string_view) = 0;
47
virtual void identifier(std::string_view name) = 0;
48
virtual void keyword(std::string_view) = 0;
49
virtual void symbol(std::string_view) = 0;
50
virtual void literal(std::string_view) = 0;
51
virtual void string(std::string_view) = 0;
52
virtual void sourceString(std::string_view, CstExprConstantString::QuoteStyle quoteStyle, unsigned int blockDepth) = 0;
53
};
54
55
struct StringWriter : Writer
56
{
57
std::string ss;
58
Position pos{0, 0};
59
char lastChar = '\0'; // used to determine whether we need to inject an extra space to preserve grammatical correctness.
60
61
const std::string& str() const
62
{
63
return ss;
64
}
65
66
void advance(const Position& newPos) override
67
{
68
while (pos.line < newPos.line)
69
newline();
70
71
if (pos.column < newPos.column)
72
write(std::string(newPos.column - pos.column, ' '));
73
}
74
75
void maybeSpace(const Position& newPos, int reserve) override
76
{
77
if (pos.column + reserve < newPos.column)
78
space();
79
}
80
81
void newline() override
82
{
83
ss += '\n';
84
pos.column = 0;
85
++pos.line;
86
lastChar = '\n';
87
}
88
89
void space() override
90
{
91
ss += ' ';
92
++pos.column;
93
lastChar = ' ';
94
}
95
96
void writeMultiline(std::string_view s) override
97
{
98
if (s.empty())
99
return;
100
101
ss.append(s.data(), s.size());
102
lastChar = s[s.size() - 1];
103
104
size_t index = 0;
105
size_t numLines = 0;
106
while (true)
107
{
108
auto newlinePos = s.find('\n', index);
109
if (newlinePos == std::string::npos)
110
break;
111
numLines++;
112
index = newlinePos + 1;
113
}
114
115
pos.line += unsigned(numLines);
116
if (numLines > 0)
117
pos.column = unsigned(s.size()) - unsigned(index);
118
else
119
pos.column += unsigned(s.size());
120
}
121
122
void write(std::string_view s) override
123
{
124
if (s.empty())
125
return;
126
127
ss.append(s.data(), s.size());
128
pos.column += unsigned(s.size());
129
lastChar = s[s.size() - 1];
130
}
131
132
void write(char c)
133
{
134
ss += c;
135
pos.column += 1;
136
lastChar = c;
137
}
138
139
void identifier(std::string_view s) override
140
{
141
if (s.empty())
142
return;
143
144
if (isIdentifierChar(lastChar))
145
space();
146
147
write(s);
148
}
149
150
void keyword(std::string_view s) override
151
{
152
if (s.empty())
153
return;
154
155
if (isIdentifierChar(lastChar))
156
space();
157
158
write(s);
159
}
160
161
void symbol(std::string_view s) override
162
{
163
write(s);
164
}
165
166
void literal(std::string_view s) override
167
{
168
if (s.empty())
169
return;
170
171
else if (isIdentifierChar(lastChar) && isDigit(s[0]))
172
space();
173
174
write(s);
175
}
176
177
void string(std::string_view s) override
178
{
179
char quote = '\'';
180
if (std::string::npos != s.find(quote))
181
quote = '\"';
182
183
write(quote);
184
write(escape(s));
185
write(quote);
186
}
187
188
void sourceString(std::string_view s, CstExprConstantString::QuoteStyle quoteStyle, unsigned int blockDepth) override
189
{
190
if (quoteStyle == CstExprConstantString::QuotedRaw)
191
{
192
auto blocks = std::string(blockDepth, '=');
193
write('[');
194
write(blocks);
195
write('[');
196
writeMultiline(s);
197
write(']');
198
write(blocks);
199
write(']');
200
}
201
else
202
{
203
LUAU_ASSERT(blockDepth == 0);
204
205
char quote = '"';
206
switch (quoteStyle)
207
{
208
case CstExprConstantString::QuotedDouble:
209
quote = '"';
210
break;
211
case CstExprConstantString::QuotedSingle:
212
quote = '\'';
213
break;
214
case CstExprConstantString::QuotedInterp:
215
quote = '`';
216
break;
217
default:
218
LUAU_ASSERT(!"Unhandled quote type");
219
}
220
221
write(quote);
222
writeMultiline(s);
223
write(quote);
224
}
225
}
226
};
227
228
class CommaSeparatorInserter
229
{
230
public:
231
explicit CommaSeparatorInserter(Writer& w, const Position* commaPosition = nullptr)
232
: first(true)
233
, writer(w)
234
, commaPosition(commaPosition)
235
{
236
}
237
void operator()()
238
{
239
if (first)
240
first = !first;
241
else
242
{
243
if (commaPosition)
244
{
245
writer.advance(*commaPosition);
246
commaPosition++;
247
}
248
writer.symbol(",");
249
}
250
}
251
252
private:
253
bool first;
254
Writer& writer;
255
const Position* commaPosition;
256
};
257
258
class ArgNameInserter
259
{
260
public:
261
ArgNameInserter(Writer& w, AstArray<std::optional<AstArgumentName>> names, AstArray<std::optional<Position>> colonPositions)
262
: writer(w)
263
, names(names)
264
, colonPositions(colonPositions)
265
{
266
}
267
268
void operator()()
269
{
270
if (idx < names.size)
271
{
272
const auto name = names.data[idx];
273
if (name.has_value())
274
{
275
writer.advance(name->second.begin);
276
writer.identifier(name->first.value);
277
if (idx < colonPositions.size)
278
{
279
LUAU_ASSERT(colonPositions.data[idx].has_value());
280
writer.advance(*colonPositions.data[idx]);
281
}
282
writer.symbol(":");
283
}
284
}
285
idx++;
286
}
287
288
private:
289
Writer& writer;
290
AstArray<std::optional<AstArgumentName>> names;
291
AstArray<std::optional<Position>> colonPositions;
292
size_t idx = 0;
293
};
294
295
struct Printer
296
{
297
explicit Printer(Writer& writer, CstNodeMap cstNodeMap)
298
: writer(writer)
299
, cstNodeMap(std::move(cstNodeMap))
300
{
301
}
302
303
bool writeTypes = false;
304
Writer& writer;
305
CstNodeMap cstNodeMap;
306
307
template<typename T>
308
T* lookupCstNode(AstNode* astNode)
309
{
310
if (const auto cstNode = cstNodeMap[astNode])
311
return cstNode->as<T>();
312
return nullptr;
313
}
314
315
void visualize(const AstLocal& local, Position colonPosition)
316
{
317
advance(local.location.begin);
318
319
writer.identifier(local.name.value);
320
if (writeTypes && local.annotation)
321
{
322
advance(colonPosition);
323
writer.symbol(":");
324
visualizeTypeAnnotation(*local.annotation);
325
}
326
}
327
328
void visualizeTypePackAnnotation(AstTypePack& annotation, bool forVarArg, bool unconditionallyParenthesize = true)
329
{
330
advance(annotation.location.begin);
331
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
332
{
333
if (!forVarArg)
334
writer.symbol("...");
335
336
visualizeTypeAnnotation(*variadicTp->variadicType);
337
}
338
else if (AstTypePackGeneric* genericTp = annotation.as<AstTypePackGeneric>())
339
{
340
writer.symbol(genericTp->genericName.value);
341
if (const auto cstNode = lookupCstNode<CstTypePackGeneric>(genericTp))
342
advance(cstNode->ellipsisPosition);
343
writer.symbol("...");
344
}
345
else if (AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
346
{
347
LUAU_ASSERT(!forVarArg);
348
if (const auto cstNode = lookupCstNode<CstTypePackExplicit>(explicitTp))
349
visualizeTypeList(
350
explicitTp->typeList,
351
cstNode->hasParentheses,
352
cstNode->openParenthesesPosition,
353
cstNode->closeParenthesesPosition,
354
cstNode->commaPositions
355
);
356
else
357
visualizeTypeList(explicitTp->typeList, unconditionallyParenthesize);
358
}
359
else
360
{
361
LUAU_ASSERT(!"Unknown TypePackAnnotation kind");
362
}
363
}
364
365
void visualizeNamedTypeList(
366
const AstTypeList& list,
367
bool unconditionallyParenthesize,
368
std::optional<Position> openParenthesesPosition,
369
std::optional<Position> closeParenthesesPosition,
370
AstArray<Position> commaPositions,
371
AstArray<std::optional<AstArgumentName>> argNames,
372
AstArray<std::optional<Position>> argNamesColonPositions
373
)
374
{
375
size_t typeCount = list.types.size + (list.tailType != nullptr ? 1 : 0);
376
if (typeCount == 0)
377
{
378
if (openParenthesesPosition)
379
advance(*openParenthesesPosition);
380
writer.symbol("(");
381
if (closeParenthesesPosition)
382
advance(*closeParenthesesPosition);
383
writer.symbol(")");
384
}
385
else if (typeCount == 1)
386
{
387
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
388
if (shouldParenthesize)
389
{
390
if (openParenthesesPosition)
391
advance(*openParenthesesPosition);
392
writer.symbol("(");
393
}
394
395
ArgNameInserter(writer, argNames, argNamesColonPositions)();
396
397
// Only variadic tail
398
if (list.types.size == 0)
399
{
400
visualizeTypePackAnnotation(*list.tailType, false);
401
}
402
else
403
{
404
visualizeTypeAnnotation(*list.types.data[0]);
405
}
406
407
if (shouldParenthesize)
408
{
409
if (closeParenthesesPosition)
410
advance(*closeParenthesesPosition);
411
writer.symbol(")");
412
}
413
}
414
else
415
{
416
if (openParenthesesPosition)
417
advance(*openParenthesesPosition);
418
writer.symbol("(");
419
420
CommaSeparatorInserter comma(writer, commaPositions.size > 0 ? commaPositions.begin() : nullptr);
421
ArgNameInserter argName(writer, argNames, argNamesColonPositions);
422
for (const auto& el : list.types)
423
{
424
comma();
425
argName();
426
visualizeTypeAnnotation(*el);
427
}
428
429
if (list.tailType)
430
{
431
comma();
432
visualizeTypePackAnnotation(*list.tailType, false);
433
}
434
435
if (closeParenthesesPosition)
436
advance(*closeParenthesesPosition);
437
writer.symbol(")");
438
}
439
}
440
441
void visualizeTypeList(
442
const AstTypeList& list,
443
bool unconditionallyParenthesize,
444
std::optional<Position> openParenthesesPosition = std::nullopt,
445
std::optional<Position> closeParenthesesPosition = std::nullopt,
446
AstArray<Position> commaPositions = {}
447
)
448
{
449
visualizeNamedTypeList(list, unconditionallyParenthesize, openParenthesesPosition, closeParenthesesPosition, commaPositions, {}, {});
450
}
451
452
bool isIntegerish(double d)
453
{
454
if (d <= std::numeric_limits<int>::max() && d >= std::numeric_limits<int>::min())
455
return double(int(d)) == d && !(d == 0.0 && signbit(d));
456
else
457
return false;
458
}
459
460
void visualize(AstExpr& expr)
461
{
462
advance(expr.location.begin);
463
464
if (const auto& a = expr.as<AstExprGroup>())
465
{
466
writer.symbol("(");
467
visualize(*a->expr);
468
advanceBefore(a->location.end, 1);
469
writer.symbol(")");
470
}
471
else if (expr.is<AstExprConstantNil>())
472
{
473
writer.keyword("nil");
474
}
475
else if (const auto& a = expr.as<AstExprConstantBool>())
476
{
477
if (a->value)
478
writer.keyword("true");
479
else
480
writer.keyword("false");
481
}
482
else if (const auto& a = expr.as<AstExprConstantNumber>())
483
{
484
if (const auto cstNode = lookupCstNode<CstExprConstantNumber>(a))
485
{
486
writer.literal(std::string_view(cstNode->value.data, cstNode->value.size));
487
}
488
else
489
{
490
if (isinf(a->value))
491
{
492
if (a->value > 0)
493
writer.literal("1e500");
494
else
495
writer.literal("-1e500");
496
}
497
else if (isnan(a->value))
498
writer.literal("0/0");
499
else
500
{
501
if (isIntegerish(a->value))
502
writer.literal(std::to_string(int(a->value)));
503
else
504
{
505
char buffer[100];
506
size_t len = snprintf(buffer, sizeof(buffer), "%.17g", a->value);
507
writer.literal(std::string_view{buffer, len});
508
}
509
}
510
}
511
}
512
else if (const auto& a = expr.as<AstExprConstantInteger>())
513
{
514
if (const auto cstNode = lookupCstNode<CstExprConstantInteger>(a))
515
{
516
writer.literal(std::string_view(cstNode->value.data, cstNode->value.size));
517
}
518
else
519
{
520
if (a->value >= 0)
521
{
522
char buffer[100];
523
size_t len = snprintf(buffer, sizeof(buffer), "%lldi", (long long)a->value);
524
writer.literal(std::string_view{buffer, len});
525
}
526
else
527
{
528
char buffer[100];
529
size_t len = snprintf(buffer, sizeof(buffer), "0x%llxi", (unsigned long long)a->value);
530
writer.literal(std::string_view{buffer, len});
531
}
532
}
533
}
534
else if (const auto& a = expr.as<AstExprConstantString>())
535
{
536
if (const auto cstNode = lookupCstNode<CstExprConstantString>(a))
537
{
538
writer.sourceString(
539
std::string_view(cstNode->sourceString.data, cstNode->sourceString.size), cstNode->quoteStyle, cstNode->blockDepth
540
);
541
}
542
else
543
writer.string(std::string_view(a->value.data, a->value.size));
544
}
545
else if (const auto& a = expr.as<AstExprLocal>())
546
{
547
writer.identifier(a->local->name.value);
548
}
549
else if (const auto& a = expr.as<AstExprGlobal>())
550
{
551
writer.identifier(a->name.value);
552
}
553
else if (expr.is<AstExprVarargs>())
554
{
555
writer.symbol("...");
556
}
557
else if (const auto& a = expr.as<AstExprCall>())
558
{
559
visualize(*a->func);
560
561
const auto cstNode = lookupCstNode<CstExprCall>(a);
562
563
if (writeTypes && (a->typeArguments.size > 0 || (cstNode && cstNode->explicitTypes)))
564
{
565
visualizeExplicitTypeInstantiation(a->typeArguments, cstNode && cstNode->explicitTypes ? cstNode->explicitTypes : nullptr);
566
}
567
568
if (cstNode)
569
{
570
if (cstNode->openParens)
571
{
572
advance(*cstNode->openParens);
573
writer.symbol("(");
574
}
575
}
576
else
577
{
578
writer.symbol("(");
579
}
580
581
CommaSeparatorInserter comma(writer, cstNode ? cstNode->commaPositions.begin() : nullptr);
582
for (const auto& arg : a->args)
583
{
584
comma();
585
visualize(*arg);
586
}
587
588
if (cstNode)
589
{
590
if (cstNode->closeParens)
591
{
592
advance(*cstNode->closeParens);
593
writer.symbol(")");
594
}
595
}
596
else
597
{
598
writer.symbol(")");
599
}
600
}
601
else if (const auto& a = expr.as<AstExprIndexName>())
602
{
603
visualize(*a->expr);
604
advance(a->opPosition);
605
writer.symbol(std::string(1, a->op));
606
advance(a->indexLocation.begin);
607
writer.write(a->index.value);
608
}
609
else if (const auto& a = expr.as<AstExprIndexExpr>())
610
{
611
const auto cstNode = lookupCstNode<CstExprIndexExpr>(a);
612
visualize(*a->expr);
613
if (cstNode)
614
advance(cstNode->openBracketPosition);
615
writer.symbol("[");
616
visualize(*a->index);
617
if (cstNode)
618
advance(cstNode->closeBracketPosition);
619
writer.symbol("]");
620
}
621
else if (const auto& a = expr.as<AstExprFunction>())
622
{
623
for (const auto& attribute : a->attributes)
624
visualizeAttribute(*attribute);
625
if (const auto cstNode = lookupCstNode<CstExprFunction>(a))
626
advance(cstNode->functionKeywordPosition);
627
writer.keyword("function");
628
visualizeFunctionBody(*a);
629
}
630
else if (const auto& a = expr.as<AstExprTable>())
631
{
632
writer.symbol("{");
633
634
const CstExprTable::Item* cstItem = nullptr;
635
if (const auto cstNode = lookupCstNode<CstExprTable>(a))
636
{
637
LUAU_ASSERT(cstNode->items.size == a->items.size);
638
cstItem = cstNode->items.begin();
639
}
640
641
bool first = true;
642
643
for (const auto& item : a->items)
644
{
645
if (!cstItem)
646
{
647
if (first)
648
first = false;
649
else
650
writer.symbol(",");
651
}
652
653
switch (item.kind)
654
{
655
case AstExprTable::Item::List:
656
break;
657
658
case AstExprTable::Item::Record:
659
{
660
const auto& value = item.key->as<AstExprConstantString>()->value;
661
advance(item.key->location.begin);
662
writer.identifier(std::string_view(value.data, value.size));
663
if (cstItem)
664
advance(*cstItem->equalsPosition);
665
else
666
writer.maybeSpace(item.value->location.begin, 1);
667
writer.symbol("=");
668
}
669
break;
670
671
case AstExprTable::Item::General:
672
{
673
if (cstItem)
674
advance(*cstItem->indexerOpenPosition);
675
writer.symbol("[");
676
visualize(*item.key);
677
if (cstItem)
678
advance(*cstItem->indexerClosePosition);
679
writer.symbol("]");
680
if (cstItem)
681
advance(*cstItem->equalsPosition);
682
else
683
writer.maybeSpace(item.value->location.begin, 1);
684
writer.symbol("=");
685
}
686
break;
687
688
default:
689
LUAU_ASSERT(!"Unknown table item kind");
690
}
691
692
advance(item.value->location.begin);
693
visualize(*item.value);
694
695
if (cstItem)
696
{
697
if (cstItem->separator)
698
{
699
LUAU_ASSERT(cstItem->separatorPosition);
700
advance(*cstItem->separatorPosition);
701
if (cstItem->separator == CstExprTable::Comma)
702
writer.symbol(",");
703
else if (cstItem->separator == CstExprTable::Semicolon)
704
writer.symbol(";");
705
}
706
cstItem++;
707
}
708
}
709
710
Position endPos = expr.location.end;
711
if (endPos.column > 0)
712
--endPos.column;
713
714
advance(endPos);
715
716
writer.symbol("}");
717
advance(expr.location.end);
718
}
719
else if (const auto& a = expr.as<AstExprUnary>())
720
{
721
if (const auto cstNode = lookupCstNode<CstExprOp>(a))
722
advance(cstNode->opPosition);
723
724
switch (a->op)
725
{
726
case AstExprUnary::Not:
727
writer.keyword("not");
728
break;
729
case AstExprUnary::Minus:
730
writer.symbol("-");
731
break;
732
case AstExprUnary::Len:
733
writer.symbol("#");
734
break;
735
}
736
visualize(*a->expr);
737
}
738
else if (const auto& a = expr.as<AstExprBinary>())
739
{
740
visualize(*a->left);
741
742
if (const auto cstNode = lookupCstNode<CstExprOp>(a))
743
advance(cstNode->opPosition);
744
else
745
{
746
switch (a->op)
747
{
748
case AstExprBinary::Add:
749
case AstExprBinary::Sub:
750
case AstExprBinary::Mul:
751
case AstExprBinary::Div:
752
case AstExprBinary::FloorDiv:
753
case AstExprBinary::Mod:
754
case AstExprBinary::Pow:
755
case AstExprBinary::CompareLt:
756
case AstExprBinary::CompareGt:
757
writer.maybeSpace(a->right->location.begin, 2);
758
break;
759
case AstExprBinary::Concat:
760
case AstExprBinary::CompareNe:
761
case AstExprBinary::CompareEq:
762
case AstExprBinary::CompareLe:
763
case AstExprBinary::CompareGe:
764
case AstExprBinary::Or:
765
writer.maybeSpace(a->right->location.begin, 3);
766
break;
767
case AstExprBinary::And:
768
writer.maybeSpace(a->right->location.begin, 4);
769
break;
770
default:
771
LUAU_ASSERT(!"Unknown Op");
772
}
773
}
774
775
writer.symbol(toString(a->op));
776
777
visualize(*a->right);
778
}
779
else if (const auto& a = expr.as<AstExprTypeAssertion>())
780
{
781
visualize(*a->expr);
782
783
if (writeTypes)
784
{
785
if (const auto* cstNode = lookupCstNode<CstExprTypeAssertion>(a))
786
advance(cstNode->opPosition);
787
else
788
writer.maybeSpace(a->annotation->location.begin, 2);
789
writer.symbol("::");
790
visualizeTypeAnnotation(*a->annotation);
791
}
792
}
793
else if (const auto& a = expr.as<AstExprIfElse>())
794
{
795
writer.keyword("if");
796
visualizeElseIfExpr(*a);
797
}
798
else if (const auto& a = expr.as<AstExprInterpString>())
799
{
800
const auto* cstNode = lookupCstNode<CstExprInterpString>(a);
801
802
writer.symbol("`");
803
804
size_t index = 0;
805
806
for (const auto& string : a->strings)
807
{
808
if (cstNode)
809
{
810
if (index > 0)
811
{
812
advance(cstNode->stringPositions.data[index]);
813
writer.symbol("}");
814
}
815
const AstArray<char> sourceString = cstNode->sourceStrings.data[index];
816
writer.writeMultiline(std::string_view(sourceString.data, sourceString.size));
817
}
818
else
819
{
820
writer.write(escape(std::string_view(string.data, string.size), /* escapeForInterpString = */ true));
821
}
822
823
if (index < a->expressions.size)
824
{
825
writer.symbol("{");
826
visualize(*a->expressions.data[index]);
827
if (!cstNode)
828
writer.symbol("}");
829
}
830
831
index++;
832
}
833
834
writer.symbol("`");
835
}
836
else if (const auto& a = expr.as<AstExprError>())
837
{
838
writer.symbol("(error-expr");
839
840
for (size_t i = 0; i < a->expressions.size; i++)
841
{
842
writer.symbol(i == 0 ? ": " : ", ");
843
visualize(*a->expressions.data[i]);
844
}
845
846
writer.symbol(")");
847
}
848
else if (const auto& a = expr.as<AstExprInstantiate>())
849
{
850
visualize(*a->expr);
851
852
if (writeTypes)
853
{
854
const CstExprExplicitTypeInstantiation* cstExprNode = lookupCstNode<CstExprExplicitTypeInstantiation>(a);
855
856
visualizeExplicitTypeInstantiation(a->typeArguments, cstExprNode ? &cstExprNode->instantiation : nullptr);
857
}
858
}
859
else
860
{
861
LUAU_ASSERT(!"Unknown AstExpr");
862
}
863
}
864
865
void writeEnd(const Location& loc)
866
{
867
Position endPos = loc.end;
868
if (endPos.column >= 3)
869
endPos.column -= 3;
870
advance(endPos);
871
writer.keyword("end");
872
}
873
874
void advance(const Position& newPos)
875
{
876
writer.advance(newPos);
877
}
878
879
void advanceBefore(const Position& newPos, unsigned int tokenLength)
880
{
881
if (newPos.column >= tokenLength)
882
advance(Position{newPos.line, newPos.column - tokenLength});
883
else
884
advance(newPos);
885
}
886
887
void visualize(AstStat& program)
888
{
889
advance(program.location.begin);
890
891
if (const auto& block = program.as<AstStatBlock>())
892
{
893
if (const auto cstNode = lookupCstNode<CstStatDo>(block))
894
{
895
writer.keyword("do");
896
897
advance(cstNode->statsStartPosition);
898
899
for (const auto& s : block->body)
900
visualize(*s);
901
902
advance(cstNode->endPosition);
903
writer.keyword("end");
904
}
905
else
906
{
907
for (const auto& s : block->body)
908
visualize(*s);
909
910
writer.advance(block->location.end);
911
writeEnd(program.location);
912
}
913
}
914
else if (const auto& a = program.as<AstStatIf>())
915
{
916
writer.keyword("if");
917
visualizeElseIf(*a);
918
}
919
else if (const auto& a = program.as<AstStatWhile>())
920
{
921
writer.keyword("while");
922
visualize(*a->condition);
923
// TODO: what if 'hasDo = false'?
924
advance(a->doLocation.begin);
925
writer.keyword("do");
926
visualizeBlock(*a->body);
927
advance(a->body->location.end);
928
writer.keyword("end");
929
}
930
else if (const auto& a = program.as<AstStatRepeat>())
931
{
932
writer.keyword("repeat");
933
visualizeBlock(*a->body);
934
if (const auto cstNode = lookupCstNode<CstStatRepeat>(a))
935
writer.advance(cstNode->untilPosition);
936
else
937
advanceBefore(a->condition->location.begin, 6);
938
writer.keyword("until");
939
visualize(*a->condition);
940
}
941
else if (program.is<AstStatBreak>())
942
writer.keyword("break");
943
else if (program.is<AstStatContinue>())
944
writer.keyword("continue");
945
else if (const auto& a = program.as<AstStatReturn>())
946
{
947
const auto cstNode = lookupCstNode<CstStatReturn>(a);
948
949
writer.keyword("return");
950
951
CommaSeparatorInserter comma(writer, cstNode ? cstNode->commaPositions.begin() : nullptr);
952
for (const auto& expr : a->list)
953
{
954
comma();
955
visualize(*expr);
956
}
957
}
958
else if (const auto& a = program.as<AstStatExpr>())
959
{
960
visualize(*a->expr);
961
}
962
else if (const auto& a = program.as<AstStatLocal>())
963
{
964
const auto cstNode = lookupCstNode<CstStatLocal>(a);
965
966
writer.keyword("local");
967
968
CommaSeparatorInserter varComma(writer, cstNode ? cstNode->varsCommaPositions.begin() : nullptr);
969
for (size_t i = 0; i < a->vars.size; i++)
970
{
971
varComma();
972
if (cstNode)
973
{
974
LUAU_ASSERT(cstNode->varsAnnotationColonPositions.size > i);
975
visualize(*a->vars.data[i], cstNode->varsAnnotationColonPositions.data[i]);
976
}
977
else
978
visualize(*a->vars.data[i], Position{0, 0});
979
}
980
981
if (a->equalsSignLocation)
982
{
983
advance(a->equalsSignLocation->begin);
984
writer.symbol("=");
985
}
986
987
988
CommaSeparatorInserter valueComma(writer, cstNode ? cstNode->valuesCommaPositions.begin() : nullptr);
989
for (const auto& value : a->values)
990
{
991
valueComma();
992
visualize(*value);
993
}
994
}
995
else if (const auto& a = program.as<AstStatFor>())
996
{
997
const auto cstNode = lookupCstNode<CstStatFor>(a);
998
999
writer.keyword("for");
1000
1001
visualize(*a->var, cstNode ? cstNode->annotationColonPosition : Position{0, 0});
1002
1003
if (cstNode)
1004
advance(cstNode->equalsPosition);
1005
writer.symbol("=");
1006
visualize(*a->from);
1007
if (cstNode)
1008
advance(cstNode->endCommaPosition);
1009
writer.symbol(",");
1010
visualize(*a->to);
1011
if (a->step)
1012
{
1013
if (cstNode && cstNode->stepCommaPosition)
1014
advance(*cstNode->stepCommaPosition);
1015
writer.symbol(",");
1016
visualize(*a->step);
1017
}
1018
advance(a->doLocation.begin);
1019
writer.keyword("do");
1020
visualizeBlock(*a->body);
1021
1022
advance(a->body->location.end);
1023
writer.keyword("end");
1024
}
1025
else if (const auto& a = program.as<AstStatForIn>())
1026
{
1027
const auto cstNode = lookupCstNode<CstStatForIn>(a);
1028
1029
writer.keyword("for");
1030
1031
CommaSeparatorInserter varComma(writer, cstNode ? cstNode->varsCommaPositions.begin() : nullptr);
1032
for (size_t i = 0; i < a->vars.size; i++)
1033
{
1034
varComma();
1035
if (cstNode)
1036
{
1037
LUAU_ASSERT(cstNode->varsAnnotationColonPositions.size > i);
1038
visualize(*a->vars.data[i], cstNode->varsAnnotationColonPositions.data[i]);
1039
}
1040
else
1041
visualize(*a->vars.data[i], Position{0, 0});
1042
}
1043
1044
advance(a->inLocation.begin);
1045
writer.keyword("in");
1046
1047
CommaSeparatorInserter valComma(writer, cstNode ? cstNode->valuesCommaPositions.begin() : nullptr);
1048
1049
for (const auto& val : a->values)
1050
{
1051
valComma();
1052
visualize(*val);
1053
}
1054
1055
advance(a->doLocation.begin);
1056
writer.keyword("do");
1057
1058
visualizeBlock(*a->body);
1059
1060
advance(a->body->location.end);
1061
writer.keyword("end");
1062
}
1063
else if (const auto& a = program.as<AstStatAssign>())
1064
{
1065
const auto cstNode = lookupCstNode<CstStatAssign>(a);
1066
1067
CommaSeparatorInserter varComma(writer, cstNode ? cstNode->varsCommaPositions.begin() : nullptr);
1068
for (const auto& var : a->vars)
1069
{
1070
varComma();
1071
visualize(*var);
1072
}
1073
1074
if (cstNode)
1075
advance(cstNode->equalsPosition);
1076
else
1077
writer.space();
1078
writer.symbol("=");
1079
1080
CommaSeparatorInserter valueComma(writer, cstNode ? cstNode->valuesCommaPositions.begin() : nullptr);
1081
for (const auto& value : a->values)
1082
{
1083
valueComma();
1084
visualize(*value);
1085
}
1086
}
1087
else if (const auto& a = program.as<AstStatCompoundAssign>())
1088
{
1089
const auto cstNode = lookupCstNode<CstStatCompoundAssign>(a);
1090
1091
visualize(*a->var);
1092
1093
if (cstNode)
1094
advance(cstNode->opPosition);
1095
1096
switch (a->op)
1097
{
1098
case AstExprBinary::Add:
1099
if (!cstNode)
1100
writer.maybeSpace(a->value->location.begin, 2);
1101
writer.symbol("+=");
1102
break;
1103
case AstExprBinary::Sub:
1104
if (!cstNode)
1105
writer.maybeSpace(a->value->location.begin, 2);
1106
writer.symbol("-=");
1107
break;
1108
case AstExprBinary::Mul:
1109
if (!cstNode)
1110
writer.maybeSpace(a->value->location.begin, 2);
1111
writer.symbol("*=");
1112
break;
1113
case AstExprBinary::Div:
1114
if (!cstNode)
1115
writer.maybeSpace(a->value->location.begin, 2);
1116
writer.symbol("/=");
1117
break;
1118
case AstExprBinary::FloorDiv:
1119
if (!cstNode)
1120
writer.maybeSpace(a->value->location.begin, 3);
1121
writer.symbol("//=");
1122
break;
1123
case AstExprBinary::Mod:
1124
if (!cstNode)
1125
writer.maybeSpace(a->value->location.begin, 2);
1126
writer.symbol("%=");
1127
break;
1128
case AstExprBinary::Pow:
1129
if (!cstNode)
1130
writer.maybeSpace(a->value->location.begin, 2);
1131
writer.symbol("^=");
1132
break;
1133
case AstExprBinary::Concat:
1134
if (!cstNode)
1135
writer.maybeSpace(a->value->location.begin, 3);
1136
writer.symbol("..=");
1137
break;
1138
default:
1139
LUAU_ASSERT(!"Unexpected compound assignment op");
1140
}
1141
1142
visualize(*a->value);
1143
}
1144
else if (const auto& a = program.as<AstStatFunction>())
1145
{
1146
for (const auto& attribute : a->func->attributes)
1147
visualizeAttribute(*attribute);
1148
if (const auto cstNode = lookupCstNode<CstStatFunction>(a))
1149
advance(cstNode->functionKeywordPosition);
1150
writer.keyword("function");
1151
visualize(*a->name);
1152
visualizeFunctionBody(*a->func);
1153
}
1154
else if (const auto& a = program.as<AstStatLocalFunction>())
1155
{
1156
for (const auto& attribute : a->func->attributes)
1157
visualizeAttribute(*attribute);
1158
1159
const auto cstNode = lookupCstNode<CstStatLocalFunction>(a);
1160
1161
if (cstNode)
1162
advance(cstNode->localKeywordPosition);
1163
1164
writer.keyword("local");
1165
1166
if (cstNode)
1167
advance(cstNode->functionKeywordPosition);
1168
else
1169
writer.space();
1170
1171
writer.keyword("function");
1172
advance(a->name->location.begin);
1173
writer.identifier(a->name->name.value);
1174
visualizeFunctionBody(*a->func);
1175
}
1176
else if (const auto& a = program.as<AstStatTypeAlias>())
1177
{
1178
if (writeTypes)
1179
{
1180
const auto* cstNode = lookupCstNode<CstStatTypeAlias>(a);
1181
1182
if (a->exported)
1183
writer.keyword("export");
1184
1185
if (cstNode)
1186
advance(cstNode->typeKeywordPosition);
1187
1188
writer.keyword("type");
1189
advance(a->nameLocation.begin);
1190
writer.identifier(a->name.value);
1191
if (a->generics.size > 0 || a->genericPacks.size > 0)
1192
{
1193
if (cstNode)
1194
advance(cstNode->genericsOpenPosition);
1195
writer.symbol("<");
1196
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
1197
1198
for (auto o : a->generics)
1199
{
1200
comma();
1201
1202
writer.advance(o->location.begin);
1203
writer.identifier(o->name.value);
1204
1205
if (o->defaultValue)
1206
{
1207
const auto* genericTypeCstNode = lookupCstNode<CstGenericType>(o);
1208
1209
if (genericTypeCstNode)
1210
{
1211
LUAU_ASSERT(genericTypeCstNode->defaultEqualsPosition.has_value());
1212
advance(*genericTypeCstNode->defaultEqualsPosition);
1213
}
1214
else
1215
writer.maybeSpace(o->defaultValue->location.begin, 2);
1216
writer.symbol("=");
1217
visualizeTypeAnnotation(*o->defaultValue);
1218
}
1219
}
1220
1221
for (auto o : a->genericPacks)
1222
{
1223
comma();
1224
1225
const auto* genericTypePackCstNode = lookupCstNode<CstGenericTypePack>(o);
1226
1227
writer.advance(o->location.begin);
1228
writer.identifier(o->name.value);
1229
if (genericTypePackCstNode)
1230
advance(genericTypePackCstNode->ellipsisPosition);
1231
writer.symbol("...");
1232
1233
if (o->defaultValue)
1234
{
1235
if (cstNode)
1236
{
1237
LUAU_ASSERT(genericTypePackCstNode->defaultEqualsPosition.has_value());
1238
advance(*genericTypePackCstNode->defaultEqualsPosition);
1239
}
1240
else
1241
writer.maybeSpace(o->defaultValue->location.begin, 2);
1242
writer.symbol("=");
1243
visualizeTypePackAnnotation(*o->defaultValue, false);
1244
}
1245
}
1246
1247
if (cstNode)
1248
advance(cstNode->genericsClosePosition);
1249
writer.symbol(">");
1250
}
1251
if (cstNode)
1252
advance(cstNode->equalsPosition);
1253
else
1254
writer.maybeSpace(a->type->location.begin, 2);
1255
writer.symbol("=");
1256
visualizeTypeAnnotation(*a->type);
1257
}
1258
}
1259
else if (const auto& t = program.as<AstStatTypeFunction>())
1260
{
1261
if (writeTypes)
1262
{
1263
const auto cstNode = lookupCstNode<CstStatTypeFunction>(t);
1264
if (t->exported)
1265
writer.keyword("export");
1266
if (cstNode)
1267
advance(cstNode->typeKeywordPosition);
1268
else
1269
writer.space();
1270
writer.keyword("type");
1271
if (cstNode)
1272
advance(cstNode->functionKeywordPosition);
1273
else
1274
writer.space();
1275
writer.keyword("function");
1276
advance(t->nameLocation.begin);
1277
writer.identifier(t->name.value);
1278
visualizeFunctionBody(*t->body);
1279
}
1280
}
1281
else if (const auto& a = program.as<AstStatError>())
1282
{
1283
writer.symbol("(error-stat");
1284
1285
for (size_t i = 0; i < a->expressions.size; i++)
1286
{
1287
writer.symbol(i == 0 ? ": " : ", ");
1288
visualize(*a->expressions.data[i]);
1289
}
1290
1291
for (size_t i = 0; i < a->statements.size; i++)
1292
{
1293
writer.symbol(i == 0 && a->expressions.size == 0 ? ": " : ", ");
1294
visualize(*a->statements.data[i]);
1295
}
1296
1297
writer.symbol(")");
1298
}
1299
else if (const auto& a = program.as<AstStatDeclareGlobal>())
1300
{
1301
writer.keyword("declare");
1302
writer.advance(a->nameLocation.begin);
1303
writer.identifier(a->name.value);
1304
1305
writer.symbol(":");
1306
visualizeTypeAnnotation(*a->type);
1307
}
1308
else
1309
{
1310
LUAU_ASSERT(!"Unknown AstStat");
1311
}
1312
1313
if (program.hasSemicolon)
1314
{
1315
advanceBefore(program.location.end, 1);
1316
writer.symbol(";");
1317
}
1318
}
1319
1320
void visualizeFunctionBody(AstExprFunction& func)
1321
{
1322
const auto cstNode = lookupCstNode<CstExprFunction>(&func);
1323
1324
if (func.generics.size > 0 || func.genericPacks.size > 0)
1325
{
1326
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
1327
if (cstNode)
1328
advance(cstNode->openGenericsPosition);
1329
writer.symbol("<");
1330
for (const auto& o : func.generics)
1331
{
1332
comma();
1333
1334
writer.advance(o->location.begin);
1335
writer.identifier(o->name.value);
1336
}
1337
for (const auto& o : func.genericPacks)
1338
{
1339
comma();
1340
1341
writer.advance(o->location.begin);
1342
writer.identifier(o->name.value);
1343
if (const auto* genericTypePackCstNode = lookupCstNode<CstGenericTypePack>(o))
1344
advance(genericTypePackCstNode->ellipsisPosition);
1345
writer.symbol("...");
1346
}
1347
if (cstNode)
1348
advance(cstNode->closeGenericsPosition);
1349
writer.symbol(">");
1350
}
1351
1352
if (func.argLocation)
1353
advance(func.argLocation->begin);
1354
writer.symbol("(");
1355
CommaSeparatorInserter comma(writer, cstNode ? cstNode->argsCommaPositions.begin() : nullptr);
1356
1357
for (size_t i = 0; i < func.args.size; ++i)
1358
{
1359
AstLocal* local = func.args.data[i];
1360
1361
comma();
1362
1363
advance(local->location.begin);
1364
writer.identifier(local->name.value);
1365
if (writeTypes && local->annotation)
1366
{
1367
if (cstNode)
1368
{
1369
LUAU_ASSERT(cstNode->argsAnnotationColonPositions.size > i);
1370
advance(cstNode->argsAnnotationColonPositions.data[i]);
1371
}
1372
writer.symbol(":");
1373
visualizeTypeAnnotation(*local->annotation);
1374
}
1375
}
1376
1377
if (func.vararg)
1378
{
1379
comma();
1380
advance(func.varargLocation.begin);
1381
writer.symbol("...");
1382
1383
if (func.varargAnnotation)
1384
{
1385
if (cstNode)
1386
{
1387
LUAU_ASSERT(cstNode->varargAnnotationColonPosition != Position({0, 0}));
1388
advance(cstNode->varargAnnotationColonPosition);
1389
}
1390
writer.symbol(":");
1391
visualizeTypePackAnnotation(*func.varargAnnotation, true);
1392
}
1393
}
1394
1395
if (func.argLocation)
1396
advanceBefore(func.argLocation->end, 1);
1397
writer.symbol(")");
1398
1399
if (writeTypes && func.returnAnnotation != nullptr)
1400
{
1401
if (cstNode)
1402
advance(cstNode->returnSpecifierPosition);
1403
writer.symbol(":");
1404
1405
if (!cstNode)
1406
writer.space();
1407
visualizeTypePackAnnotation(*func.returnAnnotation, false, false);
1408
}
1409
1410
visualizeBlock(*func.body);
1411
advance(func.body->location.end);
1412
writer.keyword("end");
1413
}
1414
1415
void visualizeBlock(AstStatBlock& block)
1416
{
1417
for (const auto& s : block.body)
1418
visualize(*s);
1419
writer.advance(block.location.end);
1420
}
1421
1422
void visualizeBlock(AstStat& stat)
1423
{
1424
if (AstStatBlock* block = stat.as<AstStatBlock>())
1425
visualizeBlock(*block);
1426
else
1427
LUAU_ASSERT(!"visualizeBlock was expecting an AstStatBlock");
1428
}
1429
1430
void visualizeElseIf(AstStatIf& elseif)
1431
{
1432
visualize(*elseif.condition);
1433
if (elseif.thenLocation)
1434
advance(elseif.thenLocation->begin);
1435
writer.keyword("then");
1436
visualizeBlock(*elseif.thenbody);
1437
1438
if (elseif.elsebody == nullptr)
1439
{
1440
advance(elseif.thenbody->location.end);
1441
writer.keyword("end");
1442
}
1443
else if (auto elseifelseif = elseif.elsebody->as<AstStatIf>())
1444
{
1445
if (elseif.elseLocation)
1446
advance(elseif.elseLocation->begin);
1447
writer.keyword("elseif");
1448
visualizeElseIf(*elseifelseif);
1449
}
1450
else
1451
{
1452
if (elseif.elseLocation)
1453
advance(elseif.elseLocation->begin);
1454
writer.keyword("else");
1455
1456
visualizeBlock(*elseif.elsebody);
1457
advance(elseif.elsebody->location.end);
1458
writer.keyword("end");
1459
}
1460
}
1461
1462
void visualizeElseIfExpr(AstExprIfElse& elseif)
1463
{
1464
const auto cstNode = lookupCstNode<CstExprIfElse>(&elseif);
1465
1466
visualize(*elseif.condition);
1467
if (cstNode)
1468
advance(cstNode->thenPosition);
1469
writer.keyword("then");
1470
visualize(*elseif.trueExpr);
1471
1472
if (elseif.falseExpr)
1473
{
1474
if (cstNode)
1475
advance(cstNode->elsePosition);
1476
if (auto elseifelseif = elseif.falseExpr->as<AstExprIfElse>(); elseifelseif && (!cstNode || cstNode->isElseIf))
1477
{
1478
writer.keyword("elseif");
1479
visualizeElseIfExpr(*elseifelseif);
1480
}
1481
else
1482
{
1483
writer.keyword("else");
1484
visualize(*elseif.falseExpr);
1485
}
1486
}
1487
}
1488
1489
void visualizeAttribute(AstAttr& attribute)
1490
{
1491
advance(attribute.location.begin);
1492
writer.symbol("@");
1493
writer.identifier(attribute.name.value);
1494
}
1495
1496
void visualizeTypeAnnotation(AstType& typeAnnotation)
1497
{
1498
advance(typeAnnotation.location.begin);
1499
if (const auto& a = typeAnnotation.as<AstTypeReference>())
1500
{
1501
const auto cstNode = lookupCstNode<CstTypeReference>(a);
1502
1503
if (a->prefix)
1504
{
1505
writer.write(a->prefix->value);
1506
if (cstNode)
1507
advance(*cstNode->prefixPointPosition);
1508
writer.symbol(".");
1509
}
1510
1511
advance(a->nameLocation.begin);
1512
writer.write(a->name.value);
1513
if (a->parameters.size > 0 || a->hasParameterList)
1514
{
1515
CommaSeparatorInserter comma(writer, cstNode ? cstNode->parametersCommaPositions.begin() : nullptr);
1516
if (cstNode)
1517
advance(cstNode->openParametersPosition);
1518
writer.symbol("<");
1519
for (auto o : a->parameters)
1520
{
1521
comma();
1522
1523
if (o.type)
1524
visualizeTypeAnnotation(*o.type);
1525
else
1526
visualizeTypePackAnnotation(*o.typePack, false);
1527
}
1528
if (cstNode)
1529
advance(cstNode->closeParametersPosition);
1530
writer.symbol(">");
1531
}
1532
}
1533
else if (const auto& a = typeAnnotation.as<AstTypeFunction>())
1534
{
1535
const auto cstNode = lookupCstNode<CstTypeFunction>(a);
1536
1537
if (a->generics.size > 0 || a->genericPacks.size > 0)
1538
{
1539
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
1540
if (cstNode)
1541
advance(cstNode->openGenericsPosition);
1542
writer.symbol("<");
1543
for (const auto& o : a->generics)
1544
{
1545
comma();
1546
1547
writer.advance(o->location.begin);
1548
writer.identifier(o->name.value);
1549
}
1550
for (const auto& o : a->genericPacks)
1551
{
1552
comma();
1553
1554
writer.advance(o->location.begin);
1555
writer.identifier(o->name.value);
1556
if (const auto* genericTypePackCstNode = lookupCstNode<CstGenericTypePack>(o))
1557
advance(genericTypePackCstNode->ellipsisPosition);
1558
writer.symbol("...");
1559
}
1560
if (cstNode)
1561
advance(cstNode->closeGenericsPosition);
1562
writer.symbol(">");
1563
}
1564
1565
{
1566
visualizeNamedTypeList(
1567
a->argTypes,
1568
true,
1569
cstNode ? std::make_optional(cstNode->openArgsPosition) : std::nullopt,
1570
cstNode ? std::make_optional(cstNode->closeArgsPosition) : std::nullopt,
1571
cstNode ? cstNode->argumentsCommaPositions : Luau::AstArray<Position>{},
1572
a->argNames,
1573
cstNode ? cstNode->argumentNameColonPositions : Luau::AstArray<std::optional<Position>>{}
1574
);
1575
}
1576
1577
if (cstNode)
1578
advance(cstNode->returnArrowPosition);
1579
writer.symbol("->");
1580
visualizeTypePackAnnotation(*a->returnTypes, false);
1581
}
1582
else if (const auto& a = typeAnnotation.as<AstTypeTable>())
1583
{
1584
AstTypeReference* indexType = a->indexer ? a->indexer->indexType->as<AstTypeReference>() : nullptr;
1585
1586
writer.symbol("{");
1587
1588
const auto cstNode = lookupCstNode<CstTypeTable>(a);
1589
if (cstNode)
1590
{
1591
if (cstNode->isArray)
1592
{
1593
LUAU_ASSERT(a->props.size == 0 && indexType && indexType->name == "number");
1594
if (a->indexer->accessLocation)
1595
{
1596
LUAU_ASSERT(a->indexer->access != AstTableAccess::ReadWrite);
1597
advance(a->indexer->accessLocation->begin);
1598
writer.keyword(a->indexer->access == AstTableAccess::Read ? "read" : "write");
1599
}
1600
visualizeTypeAnnotation(*a->indexer->resultType);
1601
}
1602
else
1603
{
1604
const AstTableProp* prop = a->props.begin();
1605
1606
for (size_t i = 0; i < cstNode->items.size; ++i)
1607
{
1608
CstTypeTable::Item item = cstNode->items.data[i];
1609
// we store indexer as part of items to preserve property ordering
1610
if (item.kind == CstTypeTable::Item::Kind::Indexer)
1611
{
1612
LUAU_ASSERT(a->indexer);
1613
1614
if (a->indexer->accessLocation)
1615
{
1616
LUAU_ASSERT(a->indexer->access != AstTableAccess::ReadWrite);
1617
advance(a->indexer->accessLocation->begin);
1618
writer.keyword(a->indexer->access == AstTableAccess::Read ? "read" : "write");
1619
}
1620
1621
advance(item.indexerOpenPosition);
1622
writer.symbol("[");
1623
visualizeTypeAnnotation(*a->indexer->indexType);
1624
advance(item.indexerClosePosition);
1625
writer.symbol("]");
1626
advance(item.colonPosition);
1627
writer.symbol(":");
1628
visualizeTypeAnnotation(*a->indexer->resultType);
1629
1630
if (item.separator)
1631
{
1632
LUAU_ASSERT(item.separatorPosition);
1633
advance(*item.separatorPosition);
1634
if (item.separator == CstExprTable::Comma)
1635
writer.symbol(",");
1636
else if (item.separator == CstExprTable::Semicolon)
1637
writer.symbol(";");
1638
}
1639
}
1640
else
1641
{
1642
if (prop->accessLocation)
1643
{
1644
LUAU_ASSERT(prop->access != AstTableAccess::ReadWrite);
1645
advance(prop->accessLocation->begin);
1646
writer.keyword(prop->access == AstTableAccess::Read ? "read" : "write");
1647
}
1648
1649
if (item.kind == CstTypeTable::Item::Kind::StringProperty)
1650
{
1651
advance(item.indexerOpenPosition);
1652
writer.symbol("[");
1653
advance(item.stringPosition);
1654
writer.sourceString(
1655
std::string_view(item.stringInfo->sourceString.data, item.stringInfo->sourceString.size),
1656
item.stringInfo->quoteStyle,
1657
item.stringInfo->blockDepth
1658
);
1659
advance(item.indexerClosePosition);
1660
writer.symbol("]");
1661
}
1662
else
1663
{
1664
advance(prop->location.begin);
1665
writer.identifier(prop->name.value);
1666
}
1667
1668
advance(item.colonPosition);
1669
writer.symbol(":");
1670
visualizeTypeAnnotation(*prop->type);
1671
1672
if (item.separator)
1673
{
1674
LUAU_ASSERT(item.separatorPosition);
1675
advance(*item.separatorPosition);
1676
if (item.separator == CstExprTable::Comma)
1677
writer.symbol(",");
1678
else if (item.separator == CstExprTable::Semicolon)
1679
writer.symbol(";");
1680
}
1681
1682
++prop;
1683
}
1684
}
1685
}
1686
}
1687
else
1688
{
1689
if (a->props.size == 0 && indexType && indexType->name == "number")
1690
{
1691
visualizeTypeAnnotation(*a->indexer->resultType);
1692
}
1693
else
1694
{
1695
CommaSeparatorInserter comma(writer);
1696
1697
for (size_t i = 0; i < a->props.size; ++i)
1698
{
1699
comma();
1700
advance(a->props.data[i].location.begin);
1701
writer.identifier(a->props.data[i].name.value);
1702
if (a->props.data[i].type)
1703
{
1704
writer.symbol(":");
1705
visualizeTypeAnnotation(*a->props.data[i].type);
1706
}
1707
}
1708
if (a->indexer)
1709
{
1710
comma();
1711
writer.symbol("[");
1712
visualizeTypeAnnotation(*a->indexer->indexType);
1713
writer.symbol("]");
1714
writer.symbol(":");
1715
visualizeTypeAnnotation(*a->indexer->resultType);
1716
}
1717
}
1718
}
1719
1720
Position endPos = a->location.end;
1721
if (endPos.column > 0)
1722
--endPos.column;
1723
advance(endPos);
1724
1725
writer.symbol("}");
1726
}
1727
else if (auto a = typeAnnotation.as<AstTypeTypeof>())
1728
{
1729
const auto cstNode = lookupCstNode<CstTypeTypeof>(a);
1730
writer.keyword("typeof");
1731
if (cstNode)
1732
advance(cstNode->openPosition);
1733
writer.symbol("(");
1734
visualize(*a->expr);
1735
if (cstNode)
1736
advance(cstNode->closePosition);
1737
writer.symbol(")");
1738
}
1739
else if (const auto& a = typeAnnotation.as<AstTypeUnion>())
1740
{
1741
const auto cstNode = lookupCstNode<CstTypeUnion>(a);
1742
1743
if (!cstNode && a->types.size == 2)
1744
{
1745
AstType* l = a->types.data[0];
1746
AstType* r = a->types.data[1];
1747
1748
auto lta = l->as<AstTypeReference>();
1749
if (lta && lta->name == "nil" && !r->is<AstTypeOptional>())
1750
std::swap(l, r);
1751
1752
// it's still possible that we had a (T | U) or (T | nil) and not (nil | T)
1753
auto rta = r->as<AstTypeReference>();
1754
if (rta && rta->name == "nil")
1755
{
1756
bool wrap = l->as<AstTypeIntersection>() || l->as<AstTypeFunction>();
1757
1758
if (wrap)
1759
writer.symbol("(");
1760
1761
visualizeTypeAnnotation(*l);
1762
1763
if (wrap)
1764
writer.symbol(")");
1765
1766
writer.symbol("?");
1767
return;
1768
}
1769
}
1770
1771
if (cstNode && cstNode->leadingPosition)
1772
{
1773
advance(*cstNode->leadingPosition);
1774
writer.symbol("|");
1775
}
1776
1777
size_t separatorIndex = 0;
1778
for (size_t i = 0; i < a->types.size; ++i)
1779
{
1780
if (const auto optional = a->types.data[i]->as<AstTypeOptional>())
1781
{
1782
advance(optional->location.begin);
1783
writer.symbol("?");
1784
continue;
1785
}
1786
1787
if (i > 0)
1788
{
1789
if (cstNode)
1790
{
1791
advance(cstNode->separatorPositions.data[separatorIndex]);
1792
separatorIndex++;
1793
}
1794
else
1795
writer.maybeSpace(a->types.data[i]->location.begin, 2);
1796
writer.symbol("|");
1797
}
1798
1799
bool wrap = !cstNode && (a->types.data[i]->as<AstTypeIntersection>() || a->types.data[i]->as<AstTypeFunction>());
1800
1801
if (wrap)
1802
writer.symbol("(");
1803
1804
visualizeTypeAnnotation(*a->types.data[i]);
1805
1806
if (wrap)
1807
writer.symbol(")");
1808
}
1809
}
1810
else if (const auto& a = typeAnnotation.as<AstTypeIntersection>())
1811
{
1812
const auto cstNode = lookupCstNode<CstTypeIntersection>(a);
1813
1814
// If the sizes are equal, we know there is a leading & token
1815
if (cstNode && cstNode->leadingPosition)
1816
{
1817
advance(*cstNode->leadingPosition);
1818
writer.symbol("&");
1819
}
1820
1821
for (size_t i = 0; i < a->types.size; ++i)
1822
{
1823
if (i > 0)
1824
{
1825
if (cstNode)
1826
advance(cstNode->separatorPositions.data[i - 1]);
1827
else
1828
writer.maybeSpace(a->types.data[i]->location.begin, 2);
1829
writer.symbol("&");
1830
}
1831
1832
bool wrap = !cstNode && (a->types.data[i]->as<AstTypeUnion>() || a->types.data[i]->as<AstTypeFunction>());
1833
1834
if (wrap)
1835
writer.symbol("(");
1836
1837
visualizeTypeAnnotation(*a->types.data[i]);
1838
1839
if (wrap)
1840
writer.symbol(")");
1841
}
1842
}
1843
else if (const auto& a = typeAnnotation.as<AstTypeGroup>())
1844
{
1845
writer.symbol("(");
1846
visualizeTypeAnnotation(*a->type);
1847
advanceBefore(a->location.end, 1);
1848
writer.symbol(")");
1849
}
1850
else if (const auto& a = typeAnnotation.as<AstTypeSingletonBool>())
1851
{
1852
writer.keyword(a->value ? "true" : "false");
1853
}
1854
else if (const auto& a = typeAnnotation.as<AstTypeSingletonString>())
1855
{
1856
if (const auto cstNode = lookupCstNode<CstTypeSingletonString>(a))
1857
{
1858
writer.sourceString(
1859
std::string_view(cstNode->sourceString.data, cstNode->sourceString.size), cstNode->quoteStyle, cstNode->blockDepth
1860
);
1861
}
1862
else
1863
writer.string(std::string_view(a->value.data, a->value.size));
1864
}
1865
else if (typeAnnotation.is<AstTypeError>())
1866
{
1867
writer.symbol("%error-type%");
1868
}
1869
else
1870
{
1871
LUAU_ASSERT(!"Unknown AstType");
1872
}
1873
}
1874
1875
void visualizeExplicitTypeInstantiation(const AstArray<AstTypeOrPack>& typeArguments, const CstTypeInstantiation* cstNode)
1876
{
1877
if (cstNode)
1878
{
1879
advance(cstNode->leftArrow1Position);
1880
}
1881
writer.symbol("<");
1882
1883
if (cstNode)
1884
{
1885
advance(cstNode->leftArrow2Position);
1886
}
1887
writer.symbol("<");
1888
1889
CommaSeparatorInserter comma(writer, cstNode ? cstNode->commaPositions.begin() : nullptr);
1890
for (const auto& typeOrPack : typeArguments)
1891
{
1892
if (typeOrPack.type)
1893
{
1894
comma();
1895
visualizeTypeAnnotation(*typeOrPack.type);
1896
}
1897
else
1898
{
1899
LUAU_ASSERT(typeOrPack.typePack);
1900
comma();
1901
visualizeTypePackAnnotation(*typeOrPack.typePack, /* forVarArg = */ false);
1902
}
1903
}
1904
1905
if (cstNode)
1906
{
1907
advance(cstNode->rightArrow1Position);
1908
}
1909
writer.symbol(">");
1910
1911
if (cstNode)
1912
{
1913
advance(cstNode->rightArrow2Position);
1914
}
1915
writer.symbol(">");
1916
}
1917
};
1918
1919
std::string toString(AstNode* node)
1920
{
1921
StringWriter writer;
1922
writer.pos = node->location.begin;
1923
1924
Printer printer(writer, CstNodeMap{nullptr});
1925
printer.writeTypes = true;
1926
1927
if (auto statNode = node->asStat())
1928
printer.visualize(*statNode);
1929
else if (auto exprNode = node->asExpr())
1930
printer.visualize(*exprNode);
1931
else if (auto typeNode = node->asType())
1932
printer.visualizeTypeAnnotation(*typeNode);
1933
1934
return writer.str();
1935
}
1936
1937
void dump(AstNode* node)
1938
{
1939
printf("%s\n", toString(node).c_str());
1940
}
1941
1942
std::string prettyPrint(AstStatBlock& block, const CstNodeMap& cstNodeMap)
1943
{
1944
StringWriter writer;
1945
Printer(writer, cstNodeMap).visualizeBlock(block);
1946
return writer.str();
1947
}
1948
1949
std::string prettyPrintWithTypes(AstStatBlock& block, const CstNodeMap& cstNodeMap)
1950
{
1951
StringWriter writer;
1952
Printer printer(writer, cstNodeMap);
1953
printer.writeTypes = true;
1954
printer.visualizeBlock(block);
1955
return writer.str();
1956
}
1957
1958
std::string prettyPrintWithTypes(AstStatBlock& block)
1959
{
1960
return prettyPrintWithTypes(block, CstNodeMap{nullptr});
1961
}
1962
1963
PrettyPrintResult prettyPrint(std::string_view source, ParseOptions options, bool withTypes)
1964
{
1965
options.storeCstData = true;
1966
1967
auto allocator = Allocator{};
1968
auto names = AstNameTable{allocator};
1969
ParseResult parseResult = Parser::parse(source.data(), source.size(), names, allocator, std::move(options));
1970
1971
if (!parseResult.errors.empty())
1972
{
1973
// PrettyPrintResult keeps track of only a single error
1974
const ParseError& error = parseResult.errors.front();
1975
1976
return PrettyPrintResult{"", error.getLocation(), error.what()};
1977
}
1978
1979
LUAU_ASSERT(parseResult.root);
1980
if (!parseResult.root)
1981
return PrettyPrintResult{"", {}, "Internal error: Parser yielded empty parse tree"};
1982
1983
if (withTypes)
1984
return PrettyPrintResult{prettyPrintWithTypes(*parseResult.root, parseResult.cstNodeMap)};
1985
1986
return PrettyPrintResult{prettyPrint(*parseResult.root, parseResult.cstNodeMap)};
1987
}
1988
1989
} // namespace Luau
1990
1991