Skip to content

Commit 0aa7da0

Browse files
authored
[llvm][mustache] Fix failing StandaloneIndentation test (#159192)
When rendering partials, we need to use an indentation stream, but when part of the partial is a unescaped sequence, we cannot indent those. To address this, we build a common MustacheStream interface for all the output streams to use. This allows us to further customize the AddIndentationStream implementation and opt it out of indenting the UnescapeSequence.
1 parent 69b0a47 commit 0aa7da0

File tree

3 files changed

+263
-203
lines changed

3 files changed

+263
-203
lines changed

llvm/lib/Support/Mustache.cpp

Lines changed: 110 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,33 @@ static Accessor splitMustacheString(StringRef Str) {
5656

5757
namespace llvm::mustache {
5858

59+
class MustacheOutputStream : public raw_ostream {
60+
public:
61+
MustacheOutputStream() = default;
62+
~MustacheOutputStream() override = default;
63+
64+
virtual void suspendIndentation() {}
65+
virtual void resumeIndentation() {}
66+
67+
private:
68+
void anchor() override;
69+
};
70+
71+
void MustacheOutputStream::anchor() {}
72+
73+
class RawMustacheOutputStream : public MustacheOutputStream {
74+
public:
75+
RawMustacheOutputStream(raw_ostream &OS) : OS(OS) { SetUnbuffered(); }
76+
77+
private:
78+
raw_ostream &OS;
79+
80+
void write_impl(const char *Ptr, size_t Size) override {
81+
OS.write(Ptr, Size);
82+
}
83+
uint64_t current_pos() const override { return OS.tell(); }
84+
};
85+
5986
class Token {
6087
public:
6188
enum class Type {
@@ -156,29 +183,31 @@ class ASTNode {
156183

157184
void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; };
158185

159-
void render(const llvm::json::Value &Data, llvm::raw_ostream &OS);
186+
void render(const llvm::json::Value &Data, MustacheOutputStream &OS);
160187

161188
private:
162-
void renderLambdas(const llvm::json::Value &Contexts, llvm::raw_ostream &OS,
163-
Lambda &L);
189+
void renderLambdas(const llvm::json::Value &Contexts,
190+
MustacheOutputStream &OS, Lambda &L);
164191

165192
void renderSectionLambdas(const llvm::json::Value &Contexts,
166-
llvm::raw_ostream &OS, SectionLambda &L);
193+
MustacheOutputStream &OS, SectionLambda &L);
167194

168-
void renderPartial(const llvm::json::Value &Contexts, llvm::raw_ostream &OS,
169-
ASTNode *Partial);
195+
void renderPartial(const llvm::json::Value &Contexts,
196+
MustacheOutputStream &OS, ASTNode *Partial);
170197

171-
void renderChild(const llvm::json::Value &Context, llvm::raw_ostream &OS);
198+
void renderChild(const llvm::json::Value &Context, MustacheOutputStream &OS);
172199

173200
const llvm::json::Value *findContext();
174201

175-
void renderRoot(const json::Value &CurrentCtx, raw_ostream &OS);
176-
void renderText(raw_ostream &OS);
177-
void renderPartial(const json::Value &CurrentCtx, raw_ostream &OS);
178-
void renderVariable(const json::Value &CurrentCtx, raw_ostream &OS);
179-
void renderUnescapeVariable(const json::Value &CurrentCtx, raw_ostream &OS);
180-
void renderSection(const json::Value &CurrentCtx, raw_ostream &OS);
181-
void renderInvertSection(const json::Value &CurrentCtx, raw_ostream &OS);
202+
void renderRoot(const json::Value &CurrentCtx, MustacheOutputStream &OS);
203+
void renderText(MustacheOutputStream &OS);
204+
void renderPartial(const json::Value &CurrentCtx, MustacheOutputStream &OS);
205+
void renderVariable(const json::Value &CurrentCtx, MustacheOutputStream &OS);
206+
void renderUnescapeVariable(const json::Value &CurrentCtx,
207+
MustacheOutputStream &OS);
208+
void renderSection(const json::Value &CurrentCtx, MustacheOutputStream &OS);
209+
void renderInvertSection(const json::Value &CurrentCtx,
210+
MustacheOutputStream &OS);
182211

183212
MustacheContext &Ctx;
184213
Type Ty;
@@ -455,7 +484,7 @@ static SmallVector<Token> tokenize(StringRef Template) {
455484
}
456485

457486
// Custom stream to escape strings.
458-
class EscapeStringStream : public raw_ostream {
487+
class EscapeStringStream : public MustacheOutputStream {
459488
public:
460489
explicit EscapeStringStream(llvm::raw_ostream &WrappedStream,
461490
EscapeMap &Escape)
@@ -497,28 +526,34 @@ class EscapeStringStream : public raw_ostream {
497526
};
498527

499528
// Custom stream to add indentation used to for rendering partials.
500-
class AddIndentationStringStream : public raw_ostream {
529+
class AddIndentationStringStream : public MustacheOutputStream {
501530
public:
502-
explicit AddIndentationStringStream(llvm::raw_ostream &WrappedStream,
531+
explicit AddIndentationStringStream(raw_ostream &WrappedStream,
503532
size_t Indentation)
504533
: Indentation(Indentation), WrappedStream(WrappedStream),
505-
NeedsIndent(true) {
534+
NeedsIndent(true), IsSuspended(false) {
506535
SetUnbuffered();
507536
}
508537

538+
void suspendIndentation() override { IsSuspended = true; }
539+
void resumeIndentation() override { IsSuspended = false; }
540+
509541
protected:
510542
void write_impl(const char *Ptr, size_t Size) override {
511543
llvm::StringRef Data(Ptr, Size);
512544
SmallString<0> Indent;
513545
Indent.resize(Indentation, ' ');
514546

515547
for (char C : Data) {
548+
LLVM_DEBUG(dbgs() << "IndentationStream: NeedsIndent=" << NeedsIndent
549+
<< ", C='" << C << "', Indentation=" << Indentation
550+
<< "\n");
516551
if (NeedsIndent && C != '\n') {
517552
WrappedStream << Indent;
518553
NeedsIndent = false;
519554
}
520555
WrappedStream << C;
521-
if (C == '\n')
556+
if (C == '\n' && !IsSuspended)
522557
NeedsIndent = true;
523558
}
524559
}
@@ -527,8 +562,9 @@ class AddIndentationStringStream : public raw_ostream {
527562

528563
private:
529564
size_t Indentation;
530-
llvm::raw_ostream &WrappedStream;
565+
raw_ostream &WrappedStream;
531566
bool NeedsIndent;
567+
bool IsSuspended;
532568
};
533569

534570
class Parser {
@@ -618,6 +654,7 @@ void Parser::parseMustache(ASTNode *Parent) {
618654
}
619655
}
620656
static void toMustacheString(const json::Value &Data, raw_ostream &OS) {
657+
LLVM_DEBUG(dbgs() << "toMustacheString: kind=" << (int)Data.kind() << "\n");
621658
switch (Data.kind()) {
622659
case json::Value::Null:
623660
return;
@@ -630,6 +667,7 @@ static void toMustacheString(const json::Value &Data, raw_ostream &OS) {
630667
}
631668
case json::Value::String: {
632669
auto Str = *Data.getAsString();
670+
LLVM_DEBUG(dbgs() << " --> writing string: \"" << Str << "\"\n");
633671
OS << Str.str();
634672
return;
635673
}
@@ -649,19 +687,24 @@ static void toMustacheString(const json::Value &Data, raw_ostream &OS) {
649687
}
650688
}
651689

652-
void ASTNode::renderRoot(const json::Value &CurrentCtx, raw_ostream &OS) {
690+
void ASTNode::renderRoot(const json::Value &CurrentCtx,
691+
MustacheOutputStream &OS) {
653692
renderChild(CurrentCtx, OS);
654693
}
655694

656-
void ASTNode::renderText(raw_ostream &OS) { OS << Body; }
695+
void ASTNode::renderText(MustacheOutputStream &OS) { OS << Body; }
657696

658-
void ASTNode::renderPartial(const json::Value &CurrentCtx, raw_ostream &OS) {
697+
void ASTNode::renderPartial(const json::Value &CurrentCtx,
698+
MustacheOutputStream &OS) {
699+
LLVM_DEBUG(dbgs() << "renderPartial: Accessor=" << AccessorValue[0]
700+
<< ", Indentation=" << Indentation << "\n");
659701
auto Partial = Ctx.Partials.find(AccessorValue[0]);
660702
if (Partial != Ctx.Partials.end())
661703
renderPartial(CurrentCtx, OS, Partial->getValue().get());
662704
}
663705

664-
void ASTNode::renderVariable(const json::Value &CurrentCtx, raw_ostream &OS) {
706+
void ASTNode::renderVariable(const json::Value &CurrentCtx,
707+
MustacheOutputStream &OS) {
665708
auto Lambda = Ctx.Lambdas.find(AccessorValue[0]);
666709
if (Lambda != Ctx.Lambdas.end()) {
667710
renderLambdas(CurrentCtx, OS, Lambda->getValue());
@@ -672,16 +715,22 @@ void ASTNode::renderVariable(const json::Value &CurrentCtx, raw_ostream &OS) {
672715
}
673716

674717
void ASTNode::renderUnescapeVariable(const json::Value &CurrentCtx,
675-
raw_ostream &OS) {
718+
MustacheOutputStream &OS) {
719+
LLVM_DEBUG(dbgs() << "renderUnescapeVariable: Accessor=" << AccessorValue[0]
720+
<< "\n");
676721
auto Lambda = Ctx.Lambdas.find(AccessorValue[0]);
677722
if (Lambda != Ctx.Lambdas.end()) {
678723
renderLambdas(CurrentCtx, OS, Lambda->getValue());
679724
} else if (const json::Value *ContextPtr = findContext()) {
725+
LLVM_DEBUG(dbgs() << " --> Found context value, writing to stream.\n");
726+
OS.suspendIndentation();
680727
toMustacheString(*ContextPtr, OS);
728+
OS.resumeIndentation();
681729
}
682730
}
683731

684-
void ASTNode::renderSection(const json::Value &CurrentCtx, raw_ostream &OS) {
732+
void ASTNode::renderSection(const json::Value &CurrentCtx,
733+
MustacheOutputStream &OS) {
685734
auto SectionLambda = Ctx.SectionLambdas.find(AccessorValue[0]);
686735
if (SectionLambda != Ctx.SectionLambdas.end()) {
687736
renderSectionLambdas(CurrentCtx, OS, SectionLambda->getValue());
@@ -701,48 +750,50 @@ void ASTNode::renderSection(const json::Value &CurrentCtx, raw_ostream &OS) {
701750
}
702751

703752
void ASTNode::renderInvertSection(const json::Value &CurrentCtx,
704-
raw_ostream &OS) {
753+
MustacheOutputStream &OS) {
705754
bool IsLambda = Ctx.SectionLambdas.contains(AccessorValue[0]);
706755
const json::Value *ContextPtr = findContext();
707756
if (isContextFalsey(ContextPtr) && !IsLambda) {
708757
renderChild(CurrentCtx, OS);
709758
}
710759
}
711760

712-
void ASTNode::render(const json::Value &CurrentCtx, raw_ostream &OS) {
761+
void ASTNode::render(const llvm::json::Value &Data, MustacheOutputStream &OS) {
713762
if (Ty != Root && Ty != Text && AccessorValue.empty())
714763
return;
715764
// Set the parent context to the incoming context so that we
716765
// can walk up the context tree correctly in findContext().
717-
ParentContext = &CurrentCtx;
766+
ParentContext = &Data;
718767

719768
switch (Ty) {
720769
case Root:
721-
renderRoot(CurrentCtx, OS);
770+
renderRoot(Data, OS);
722771
return;
723772
case Text:
724773
renderText(OS);
725774
return;
726775
case Partial:
727-
renderPartial(CurrentCtx, OS);
776+
renderPartial(Data, OS);
728777
return;
729778
case Variable:
730-
renderVariable(CurrentCtx, OS);
779+
renderVariable(Data, OS);
731780
return;
732781
case UnescapeVariable:
733-
renderUnescapeVariable(CurrentCtx, OS);
782+
renderUnescapeVariable(Data, OS);
734783
return;
735784
case Section:
736-
renderSection(CurrentCtx, OS);
785+
renderSection(Data, OS);
737786
return;
738787
case InvertSection:
739-
renderInvertSection(CurrentCtx, OS);
788+
renderInvertSection(Data, OS);
740789
return;
741790
}
742791
llvm_unreachable("Invalid ASTNode type");
743792
}
744793

745794
const json::Value *ASTNode::findContext() {
795+
LLVM_DEBUG(dbgs() << "findContext: AccessorValue[0]=" << AccessorValue[0]
796+
<< "\n");
746797
// The mustache spec allows for dot notation to access nested values
747798
// a single dot refers to the current context.
748799
// We attempt to find the JSON context in the current node, if it is not
@@ -757,12 +808,22 @@ const json::Value *ASTNode::findContext() {
757808
StringRef CurrentAccessor = AccessorValue[0];
758809
ASTNode *CurrentParent = Parent;
759810

811+
LLVM_DEBUG(dbgs() << "findContext: ParentContext: ";
812+
if (ParentContext) ParentContext->print(dbgs());
813+
else dbgs() << "nullptr"; dbgs() << "\n");
814+
760815
while (!CurrentContext || !CurrentContext->get(CurrentAccessor)) {
816+
LLVM_DEBUG(dbgs() << "findContext: climbing parent\n");
761817
if (CurrentParent->Ty != Root) {
762818
CurrentContext = CurrentParent->ParentContext->getAsObject();
763819
CurrentParent = CurrentParent->Parent;
820+
LLVM_DEBUG(dbgs() << "findContext: new ParentContext: ";
821+
if (CurrentParent->ParentContext)
822+
CurrentParent->ParentContext->print(dbgs());
823+
else dbgs() << "nullptr"; dbgs() << "\n");
764824
continue;
765825
}
826+
LLVM_DEBUG(dbgs() << "findContext: reached root, not found\n");
766827
return nullptr;
767828
}
768829
const json::Value *Context = nullptr;
@@ -778,22 +839,28 @@ const json::Value *ASTNode::findContext() {
778839
Context = CurrentValue;
779840
}
780841
}
842+
LLVM_DEBUG(dbgs() << "findContext: found value: ";
843+
if (Context) Context->print(dbgs()); else dbgs() << "nullptr";
844+
dbgs() << "\n");
781845
return Context;
782846
}
783847

784-
void ASTNode::renderChild(const json::Value &Contexts, llvm::raw_ostream &OS) {
848+
void ASTNode::renderChild(const json::Value &Contexts,
849+
MustacheOutputStream &OS) {
785850
for (AstPtr &Child : Children)
786851
Child->render(Contexts, OS);
787852
}
788853

789-
void ASTNode::renderPartial(const json::Value &Contexts, llvm::raw_ostream &OS,
790-
ASTNode *Partial) {
854+
void ASTNode::renderPartial(const json::Value &Contexts,
855+
MustacheOutputStream &OS, ASTNode *Partial) {
856+
LLVM_DEBUG(dbgs() << "renderPartial (helper): Indentation=" << Indentation
857+
<< "\n");
791858
AddIndentationStringStream IS(OS, Indentation);
792859
Partial->render(Contexts, IS);
793860
}
794861

795-
void ASTNode::renderLambdas(const json::Value &Contexts, llvm::raw_ostream &OS,
796-
Lambda &L) {
862+
void ASTNode::renderLambdas(const json::Value &Contexts,
863+
MustacheOutputStream &OS, Lambda &L) {
797864
json::Value LambdaResult = L();
798865
std::string LambdaStr;
799866
raw_string_ostream Output(LambdaStr);
@@ -810,7 +877,7 @@ void ASTNode::renderLambdas(const json::Value &Contexts, llvm::raw_ostream &OS,
810877
}
811878

812879
void ASTNode::renderSectionLambdas(const json::Value &Contexts,
813-
llvm::raw_ostream &OS, SectionLambda &L) {
880+
MustacheOutputStream &OS, SectionLambda &L) {
814881
json::Value Return = L(RawBody);
815882
if (isFalsey(Return))
816883
return;
@@ -823,7 +890,8 @@ void ASTNode::renderSectionLambdas(const json::Value &Contexts,
823890
}
824891

825892
void Template::render(const json::Value &Data, llvm::raw_ostream &OS) {
826-
Tree->render(Data, OS);
893+
RawMustacheOutputStream MOS(OS);
894+
Tree->render(Data, MOS);
827895
}
828896

829897
void Template::registerPartial(std::string Name, std::string Partial) {

0 commit comments

Comments
 (0)