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