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 | |
26 | FmtContext::FmtContext(ArrayRef<std::pair<StringRef, StringRef>> subs) { |
27 | for (auto &sub : subs) |
28 | addSubst(placeholder: sub.first, subst: sub.second); |
29 | } |
30 | |
31 | FmtContext &FmtContext::addSubst(StringRef placeholder, const Twine &subst) { |
32 | customSubstMap[placeholder] = subst.str(); |
33 | return *this; |
34 | } |
35 | |
36 | FmtContext &FmtContext::withBuilder(Twine subst) { |
37 | builtinSubstMap[PHKind::Builder] = subst.str(); |
38 | return *this; |
39 | } |
40 | |
41 | FmtContext &FmtContext::withSelf(Twine subst) { |
42 | builtinSubstMap[PHKind::Self] = subst.str(); |
43 | return *this; |
44 | } |
45 | |
46 | std::optional<StringRef> |
47 | FmtContext::getSubstFor(FmtContext::PHKind placeholder) const { |
48 | if (placeholder == FmtContext::PHKind::None || |
49 | placeholder == FmtContext::PHKind::Custom) |
50 | return {}; |
51 | auto it = builtinSubstMap.find(Val: placeholder); |
52 | if (it == builtinSubstMap.end()) |
53 | return {}; |
54 | return StringRef(it->second); |
55 | } |
56 | |
57 | std::optional<StringRef> FmtContext::getSubstFor(StringRef placeholder) const { |
58 | auto it = customSubstMap.find(Key: placeholder); |
59 | if (it == customSubstMap.end()) |
60 | return {}; |
61 | return StringRef(it->second); |
62 | } |
63 | |
64 | FmtContext::PHKind FmtContext::getPlaceHolderKind(StringRef str) { |
65 | return StringSwitch<FmtContext::PHKind>(str) |
66 | .Case(S: "_builder" , Value: FmtContext::PHKind::Builder) |
67 | .Case(S: "_self" , Value: FmtContext::PHKind::Self) |
68 | .Case(S: "" , Value: FmtContext::PHKind::None) |
69 | .Default(Value: FmtContext::PHKind::Custom); |
70 | } |
71 | |
72 | std::pair<FmtReplacement, StringRef> |
73 | FmtObjectBase::splitFmtSegment(StringRef fmt) { |
74 | size_t begin = fmt.find_first_of(C: '$'); |
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(Start: 0, N: begin)}, fmt.substr(Start: 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(Start: 0, N: 1)}, fmt.substr(Start: 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(F: [](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(Start: 1, N: end - 1).consumeInteger(Radix: 0, Result&: 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(Start: end, N: 3) == "..." ) { |
112 | // Currently only ranges without upper bound are supported. |
113 | return { |
114 | FmtReplacement{fmt.substr(Start: 0, N: end + 3), index, FmtReplacement::kUnset}, |
115 | fmt.substr(Start: 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(Start: 0, N: end), index}, fmt.substr(Start: end)}; |
123 | } |
124 | |
125 | end = fmt.find_if_not(F: [](char c) { return std::isalnum(c) || c == '_'; }, From: 1); |
126 | auto placeholder = FmtContext::getPlaceHolderKind(str: fmt.substr(Start: 1, N: 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(Start: 0, N: end), placeholder}, fmt.substr(Start: end)}; |
132 | } |
133 | |
134 | std::vector<FmtReplacement> FmtObjectBase::parseFormatString(StringRef fmt) { |
135 | std::vector<FmtReplacement> replacements; |
136 | FmtReplacement repl; |
137 | while (!fmt.empty()) { |
138 | std::tie(args&: repl, args&: fmt) = splitFmtSegment(fmt); |
139 | if (repl.type != FmtReplacement::Type::Empty) |
140 | replacements.push_back(x: repl); |
141 | } |
142 | return replacements; |
143 | } |
144 | |
145 | 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(placeholder: repl.spec.substr(Start: 1)); |
166 | } else { |
167 | subst = context->getSubstFor(placeholder: 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(N: repl.index); |
184 | if (repl.end != FmtReplacement::kUnset) |
185 | range = range.drop_back(N: adapters.size() - repl.end); |
186 | llvm::interleaveComma(c: range, os&: s, |
187 | each_fn: [&](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&: s, /*Options=*/"" ); |
198 | } |
199 | } |
200 | |
201 | FmtStrVecObject::FmtStrVecObject(StringRef fmt, const FmtContext *ctx, |
202 | ArrayRef<std::string> params) |
203 | : FmtObjectBase(fmt, ctx, params.size()) { |
204 | parameters.reserve(N: params.size()); |
205 | for (std::string p : params) |
206 | parameters.push_back( |
207 | Elt: llvm::support::detail::build_format_adapter(Item: std::move(p))); |
208 | |
209 | adapters.reserve(n: parameters.size()); |
210 | for (auto &p : parameters) |
211 | adapters.push_back(x: &p); |
212 | } |
213 | |
214 | FmtStrVecObject::FmtStrVecObject(FmtStrVecObject &&that) |
215 | : FmtObjectBase(std::move(that)), parameters(std::move(that.parameters)) { |
216 | adapters.reserve(n: parameters.size()); |
217 | for (auto &p : parameters) |
218 | adapters.push_back(x: &p); |
219 | } |
220 | |