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