1 //===- Format.cpp - Utilities for String Format ---------------------------===//
2 //
3 // Copyright 2019 The MLIR Authors.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 //   http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 // =============================================================================
17 //
18 // This file defines utilities for formatting strings. They are specially
19 // tailored to the needs of TableGen'ing op definitions and rewrite rules,
20 // so they are not expected to be used as widely applicable utilities.
21 //
22 //===----------------------------------------------------------------------===//
23 
24 #include "mlir/TableGen/Format.h"
25 #include <cctype>
26 
27 using namespace mlir;
28 using namespace mlir::tblgen;
29 
30 // Marker to indicate an error happened when replacing a placeholder.
31 const char *const kMarkerForNoSubst = "<no-subst-found>";
32 
33 FmtContext &tblgen::FmtContext::addSubst(StringRef placeholder, Twine subst) {
34   customSubstMap[placeholder] = subst.str();
35   return *this;
36 }
37 
38 FmtContext &tblgen::FmtContext::withBuilder(Twine subst) {
39   builtinSubstMap[PHKind::Builder] = subst.str();
40   return *this;
41 }
42 
43 FmtContext &tblgen::FmtContext::withOp(Twine subst) {
44   builtinSubstMap[PHKind::Op] = subst.str();
45   return *this;
46 }
47 
48 FmtContext &tblgen::FmtContext::withSelf(Twine subst) {
49   builtinSubstMap[PHKind::Self] = subst.str();
50   return *this;
51 }
52 
53 Optional<StringRef>
54 tblgen::FmtContext::getSubstFor(FmtContext::PHKind placeholder) const {
55   if (placeholder == FmtContext::PHKind::None ||
56       placeholder == FmtContext::PHKind::Custom)
57     return {};
58   auto it = builtinSubstMap.find(placeholder);
59   if (it == builtinSubstMap.end())
60     return {};
61   return StringRef(it->second);
62 }
63 
64 Optional<StringRef>
65 tblgen::FmtContext::getSubstFor(StringRef placeholder) const {
66   auto it = customSubstMap.find(placeholder);
67   if (it == customSubstMap.end())
68     return {};
69   return StringRef(it->second);
70 }
71 
72 FmtContext::PHKind tblgen::FmtContext::getPlaceHolderKind(StringRef str) {
73   return llvm::StringSwitch<FmtContext::PHKind>(str)
74       .Case("_builder", FmtContext::PHKind::Builder)
75       .Case("_op", FmtContext::PHKind::Op)
76       .Case("_self", FmtContext::PHKind::Self)
77       .Case("", FmtContext::PHKind::None)
78       .Default(FmtContext::PHKind::Custom);
79 }
80 
81 std::pair<FmtReplacement, StringRef>
82 tblgen::FmtObjectBase::splitFmtSegment(StringRef fmt) {
83   size_t begin = fmt.find_first_of('$');
84   if (begin == StringRef::npos) {
85     // No placeholders: the whole format string should be returned as a
86     // literal string.
87     return {FmtReplacement{fmt}, StringRef()};
88   }
89   if (begin != 0) {
90     // The first placeholder is not at the beginning: we can split the format
91     // string into a literal string and the rest.
92     return {FmtReplacement{fmt.substr(0, begin)}, fmt.substr(begin)};
93   }
94 
95   // The first placeholder is at the beginning
96 
97   if (fmt.size() == 1) {
98     // The whole format string just contains '$': treat as literal.
99     return {FmtReplacement{fmt}, StringRef()};
100   }
101 
102   // Allow escaping dollar with '$$'
103   if (fmt[1] == '$') {
104     return {FmtReplacement{fmt.substr(0, 1)}, fmt.substr(2)};
105   }
106 
107   // First try to see if it's a positional placeholder, and then handle special
108   // placeholders.
109 
110   size_t end = fmt.find_if_not([](char c) { return std::isdigit(c); }, 1);
111   if (end != 1) {
112     // We have a positional placeholder. Parse the index.
113     size_t index = 0;
114     if (fmt.substr(1, end - 1).consumeInteger(0, index)) {
115       llvm_unreachable("invalid replacement sequence index");
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 
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 
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         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     assert(repl.type == FmtReplacement::Type::PositionalPH);
178 
179     if (repl.index >= adapters.size()) {
180       s << repl.spec << kMarkerForNoSubst;
181       continue;
182     }
183     adapters[repl.index]->format(s, /*Options=*/"");
184   }
185 }
186