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