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(sub.first, 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::withOp(Twine subst) { 42 builtinSubstMap[PHKind::Op] = subst.str(); 43 return *this; 44 } 45 46 FmtContext &FmtContext::withSelf(Twine subst) { 47 builtinSubstMap[PHKind::Self] = subst.str(); 48 return *this; 49 } 50 51 Optional<StringRef> 52 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 62 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 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> 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 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 151 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 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 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