1 //===- Format.cpp - Utilities for String Format ---------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file defines utilities for formatting strings. They are specially
10 // tailored to the needs of TableGen'ing op definitions and rewrite rules,
11 // so they are not expected to be used as widely applicable utilities.
12 //
13 //===----------------------------------------------------------------------===//
14 
15 #include "mlir/TableGen/Format.h"
16 #include <cctype>
17 
18 using namespace mlir;
19 using namespace mlir::tblgen;
20 
21 // Marker to indicate an error happened when replacing a placeholder.
22 const char *const kMarkerForNoSubst = "<no-subst-found>";
23 
24 FmtContext &FmtContext::addSubst(StringRef placeholder, Twine subst) {
25   customSubstMap[placeholder] = subst.str();
26   return *this;
27 }
28 
29 FmtContext &FmtContext::withBuilder(Twine subst) {
30   builtinSubstMap[PHKind::Builder] = subst.str();
31   return *this;
32 }
33 
34 FmtContext &FmtContext::withOp(Twine subst) {
35   builtinSubstMap[PHKind::Op] = subst.str();
36   return *this;
37 }
38 
39 FmtContext &FmtContext::withSelf(Twine subst) {
40   builtinSubstMap[PHKind::Self] = subst.str();
41   return *this;
42 }
43 
44 Optional<StringRef>
45 FmtContext::getSubstFor(FmtContext::PHKind placeholder) const {
46   if (placeholder == FmtContext::PHKind::None ||
47       placeholder == FmtContext::PHKind::Custom)
48     return {};
49   auto it = builtinSubstMap.find(placeholder);
50   if (it == builtinSubstMap.end())
51     return {};
52   return StringRef(it->second);
53 }
54 
55 Optional<StringRef> FmtContext::getSubstFor(StringRef placeholder) const {
56   auto it = customSubstMap.find(placeholder);
57   if (it == customSubstMap.end())
58     return {};
59   return StringRef(it->second);
60 }
61 
62 FmtContext::PHKind FmtContext::getPlaceHolderKind(StringRef str) {
63   return StringSwitch<FmtContext::PHKind>(str)
64       .Case("_builder", FmtContext::PHKind::Builder)
65       .Case("_op", FmtContext::PHKind::Op)
66       .Case("_self", FmtContext::PHKind::Self)
67       .Case("", FmtContext::PHKind::None)
68       .Default(FmtContext::PHKind::Custom);
69 }
70 
71 std::pair<FmtReplacement, StringRef>
72 FmtObjectBase::splitFmtSegment(StringRef fmt) {
73   size_t begin = fmt.find_first_of('$');
74   if (begin == StringRef::npos) {
75     // No placeholders: the whole format string should be returned as a
76     // literal string.
77     return {FmtReplacement{fmt}, StringRef()};
78   }
79   if (begin != 0) {
80     // The first placeholder is not at the beginning: we can split the format
81     // string into a literal string and the rest.
82     return {FmtReplacement{fmt.substr(0, begin)}, fmt.substr(begin)};
83   }
84 
85   // The first placeholder is at the beginning
86 
87   if (fmt.size() == 1) {
88     // The whole format string just contains '$': treat as literal.
89     return {FmtReplacement{fmt}, StringRef()};
90   }
91 
92   // Allow escaping dollar with '$$'
93   if (fmt[1] == '$') {
94     return {FmtReplacement{fmt.substr(0, 1)}, fmt.substr(2)};
95   }
96 
97   // First try to see if it's a positional placeholder, and then handle special
98   // placeholders.
99 
100   size_t end =
101       fmt.find_if_not([](char c) { return std::isdigit(c); }, /*From=*/1);
102   if (end != 1) {
103     // We have a positional placeholder. Parse the index.
104     size_t index = 0;
105     if (fmt.substr(1, end - 1).consumeInteger(0, index)) {
106       llvm_unreachable("invalid replacement sequence index");
107     }
108 
109     // Check if this is the part of a range specification.
110     if (fmt.substr(end, 3) == "...") {
111       // Currently only ranges without upper bound are supported.
112       return {
113           FmtReplacement{fmt.substr(0, end + 3), index, FmtReplacement::kUnset},
114           fmt.substr(end + 3)};
115     }
116 
117     if (end == StringRef::npos) {
118       // All the remaining characters are part of the positional placeholder.
119       return {FmtReplacement{fmt, index}, StringRef()};
120     }
121     return {FmtReplacement{fmt.substr(0, end), index}, fmt.substr(end)};
122   }
123 
124   end = fmt.find_if_not([](char c) { return std::isalnum(c) || c == '_'; }, 1);
125   auto placeholder = FmtContext::getPlaceHolderKind(fmt.substr(1, end - 1));
126   if (end == StringRef::npos) {
127     // All the remaining characters are part of the special placeholder.
128     return {FmtReplacement{fmt, placeholder}, StringRef()};
129   }
130   return {FmtReplacement{fmt.substr(0, end), placeholder}, fmt.substr(end)};
131 }
132 
133 std::vector<FmtReplacement> FmtObjectBase::parseFormatString(StringRef fmt) {
134   std::vector<FmtReplacement> replacements;
135   FmtReplacement repl;
136   while (!fmt.empty()) {
137     std::tie(repl, fmt) = splitFmtSegment(fmt);
138     if (repl.type != FmtReplacement::Type::Empty)
139       replacements.push_back(repl);
140   }
141   return replacements;
142 }
143 
144 void FmtObjectBase::format(raw_ostream &s) const {
145   for (auto &repl : replacements) {
146     if (repl.type == FmtReplacement::Type::Empty)
147       continue;
148 
149     if (repl.type == FmtReplacement::Type::Literal) {
150       s << repl.spec;
151       continue;
152     }
153 
154     if (repl.type == FmtReplacement::Type::SpecialPH) {
155       if (repl.placeholder == FmtContext::PHKind::None) {
156         s << repl.spec;
157       } else if (!context) {
158         // We need the context to replace special placeholders.
159         s << repl.spec << kMarkerForNoSubst;
160       } else {
161         Optional<StringRef> subst;
162         if (repl.placeholder == FmtContext::PHKind::Custom) {
163           // Skip the leading '$' sign for the custom placeholder
164           subst = context->getSubstFor(repl.spec.substr(1));
165         } else {
166           subst = context->getSubstFor(repl.placeholder);
167         }
168         if (subst)
169           s << *subst;
170         else
171           s << repl.spec << kMarkerForNoSubst;
172       }
173       continue;
174     }
175 
176     if (repl.type == FmtReplacement::Type::PositionalRangePH) {
177       if (repl.index >= adapters.size()) {
178         s << repl.spec << kMarkerForNoSubst;
179         continue;
180       }
181       auto range = llvm::makeArrayRef(adapters);
182       range = range.drop_front(repl.index);
183       if (repl.end != FmtReplacement::kUnset)
184         range = range.drop_back(adapters.size() - repl.end);
185       llvm::interleaveComma(range, s,
186                             [&](auto &x) { x->format(s, /*Options=*/""); });
187       continue;
188     }
189 
190     assert(repl.type == FmtReplacement::Type::PositionalPH);
191 
192     if (repl.index >= adapters.size()) {
193       s << repl.spec << kMarkerForNoSubst;
194       continue;
195     }
196     adapters[repl.index]->format(s, /*Options=*/"");
197   }
198 }
199 
200 FmtStrVecObject::FmtStrVecObject(StringRef fmt, const FmtContext *ctx,
201                                  ArrayRef<std::string> params)
202     : FmtObjectBase(fmt, ctx, params.size()) {
203   parameters.reserve(params.size());
204   for (std::string p : params)
205     parameters.push_back(llvm::detail::build_format_adapter(std::move(p)));
206 
207   adapters.reserve(parameters.size());
208   for (auto &p : parameters)
209     adapters.push_back(&p);
210 }
211 
212 FmtStrVecObject::FmtStrVecObject(FmtStrVecObject &&that)
213     : FmtObjectBase(std::move(that)), parameters(std::move(that.parameters)) {
214   adapters.reserve(parameters.size());
215   for (auto &p : parameters)
216     adapters.push_back(&p);
217 }
218