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