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 
26 using namespace mlir;
27 using namespace mlir::tblgen;
28 
29 // Marker to indicate an error happened when replacing a placeholder.
30 const char *const kMarkerForNoSubst = "<no-subst-found>";
31 
32 FmtContext &tblgen::FmtContext::addSubst(StringRef placeholder, Twine subst) {
33   customSubstMap[placeholder] = subst.str();
34   return *this;
35 }
36 
37 FmtContext &tblgen::FmtContext::withBuilder(Twine subst) {
38   builtinSubstMap[PHKind::Builder] = subst.str();
39   return *this;
40 }
41 
42 FmtContext &tblgen::FmtContext::withOp(Twine subst) {
43   builtinSubstMap[PHKind::Op] = subst.str();
44   return *this;
45 }
46 
47 FmtContext &tblgen::FmtContext::withSelf(Twine subst) {
48   builtinSubstMap[PHKind::Self] = subst.str();
49   return *this;
50 }
51 
52 Optional<StringRef>
53 tblgen::FmtContext::getSubstFor(FmtContext::PHKind placeholder) const {
54   if (placeholder == FmtContext::PHKind::None ||
55       placeholder == FmtContext::PHKind::Custom)
56     return {};
57   auto it = builtinSubstMap.find(placeholder);
58   if (it == builtinSubstMap.end())
59     return {};
60   return StringRef(it->second);
61 }
62 
63 Optional<StringRef>
64 tblgen::FmtContext::getSubstFor(StringRef placeholder) const {
65   auto it = customSubstMap.find(placeholder);
66   if (it == customSubstMap.end())
67     return {};
68   return StringRef(it->second);
69 }
70 
71 FmtContext::PHKind tblgen::FmtContext::getPlaceHolderKind(StringRef str) {
72   return llvm::StringSwitch<FmtContext::PHKind>(str)
73       .Case("_builder", FmtContext::PHKind::Builder)
74       .Case("_op", FmtContext::PHKind::Op)
75       .Case("_self", FmtContext::PHKind::Self)
76       .Case("", FmtContext::PHKind::None)
77       .Default(FmtContext::PHKind::Custom);
78 }
79 
80 std::pair<FmtReplacement, StringRef>
81 tblgen::FmtObjectBase::splitFmtSegment(StringRef fmt) {
82   size_t begin = fmt.find_first_of('$');
83   if (begin == StringRef::npos) {
84     // No placeholders: the whole format string should be returned as a
85     // literal string.
86     return {FmtReplacement{fmt}, StringRef()};
87   }
88   if (begin != 0) {
89     // The first placeholder is not at the beginning: we can split the format
90     // string into a literal string and the rest.
91     return {FmtReplacement{fmt.substr(0, begin)}, fmt.substr(begin)};
92   }
93 
94   // The first placeholder is at the beginning
95 
96   if (fmt.size() == 1) {
97     // The whole format string just contains '$': treat as literal.
98     return {FmtReplacement{fmt}, StringRef()};
99   }
100 
101   // Allow escaping dollar with '$$'
102   if (fmt[1] == '$') {
103     return {FmtReplacement{fmt.substr(0, 1)}, fmt.substr(2)};
104   }
105 
106   // First try to see if it's a positional placeholder, and then handle special
107   // placeholders.
108 
109   size_t end = fmt.find_if_not([](char c) { return std::isdigit(c); }, 1);
110   if (end != 1) {
111     // We have a positional placeholder. Parse the index.
112     size_t index = 0;
113     if (fmt.substr(1, end - 1).consumeInteger(0, index)) {
114       llvm_unreachable("invalid replacement sequence index");
115     }
116 
117     if (end == StringRef::npos) {
118       // All the remaining characters are part of the positional placeholder.
119       return {FmtReplacement{fmt, index}, StringRef()};
120     }
121     return {FmtReplacement{fmt.substr(0, end), index}, fmt.substr(end)};
122   }
123 
124   end = fmt.find_if_not([](char c) { return std::isalnum(c) || c == '_'; }, 1);
125   auto placeholder = FmtContext::getPlaceHolderKind(fmt.substr(1, end - 1));
126   if (end == StringRef::npos) {
127     // All the remaining characters are part of the special placeholder.
128     return {FmtReplacement{fmt, placeholder}, StringRef()};
129   }
130   return {FmtReplacement{fmt.substr(0, end), placeholder}, fmt.substr(end)};
131 }
132 
133 std::vector<FmtReplacement> FmtObjectBase::parseFormatString(StringRef fmt) {
134   std::vector<FmtReplacement> replacements;
135   FmtReplacement repl;
136   while (!fmt.empty()) {
137     std::tie(repl, fmt) = splitFmtSegment(fmt);
138     if (repl.type != FmtReplacement::Type::Empty)
139       replacements.push_back(repl);
140   }
141   return replacements;
142 }
143 
144 void FmtObjectBase::format(raw_ostream &s) const {
145   for (auto &repl : replacements) {
146     if (repl.type == FmtReplacement::Type::Empty)
147       continue;
148 
149     if (repl.type == FmtReplacement::Type::Literal) {
150       s << repl.spec;
151       continue;
152     }
153 
154     if (repl.type == FmtReplacement::Type::SpecialPH) {
155       if (repl.placeholder == FmtContext::PHKind::None) {
156         s << repl.spec;
157       } else if (!context) {
158         // We need the context to replace special placeholders.
159         s << repl.spec << kMarkerForNoSubst;
160       } else {
161         Optional<StringRef> subst;
162         if (repl.placeholder == FmtContext::PHKind::Custom) {
163           // Skip the leading '$' sign for the custom placeholder
164           subst = context->getSubstFor(repl.spec.substr(1));
165         } else {
166           subst = context->getSubstFor(repl.placeholder);
167         }
168         if (subst)
169           s << *subst;
170         else
171           s << repl.spec << kMarkerForNoSubst;
172       }
173       continue;
174     }
175 
176     assert(repl.type == FmtReplacement::Type::PositionalPH);
177 
178     if (repl.index >= adapters.size()) {
179       s << repl.spec << kMarkerForNoSubst;
180       continue;
181     }
182     adapters[repl.index]->format(s, /*Options=*/"");
183   }
184 }
185