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::addSubst(StringRef placeholder, Twine subst) { 25 customSubstMap[placeholder] = subst.str(); 26 return *this; 27 } 28 29 FmtContext &FmtContext::withBuilder(Twine subst) { 30 builtinSubstMap[PHKind::Builder] = subst.str(); 31 return *this; 32 } 33 34 FmtContext &FmtContext::withOp(Twine subst) { 35 builtinSubstMap[PHKind::Op] = subst.str(); 36 return *this; 37 } 38 39 FmtContext &FmtContext::withSelf(Twine subst) { 40 builtinSubstMap[PHKind::Self] = subst.str(); 41 return *this; 42 } 43 44 Optional<StringRef> 45 FmtContext::getSubstFor(FmtContext::PHKind placeholder) const { 46 if (placeholder == FmtContext::PHKind::None || 47 placeholder == FmtContext::PHKind::Custom) 48 return {}; 49 auto it = builtinSubstMap.find(placeholder); 50 if (it == builtinSubstMap.end()) 51 return {}; 52 return StringRef(it->second); 53 } 54 55 Optional<StringRef> FmtContext::getSubstFor(StringRef placeholder) const { 56 auto it = customSubstMap.find(placeholder); 57 if (it == customSubstMap.end()) 58 return {}; 59 return StringRef(it->second); 60 } 61 62 FmtContext::PHKind FmtContext::getPlaceHolderKind(StringRef str) { 63 return StringSwitch<FmtContext::PHKind>(str) 64 .Case("_builder", FmtContext::PHKind::Builder) 65 .Case("_op", FmtContext::PHKind::Op) 66 .Case("_self", FmtContext::PHKind::Self) 67 .Case("", FmtContext::PHKind::None) 68 .Default(FmtContext::PHKind::Custom); 69 } 70 71 std::pair<FmtReplacement, StringRef> 72 FmtObjectBase::splitFmtSegment(StringRef fmt) { 73 size_t begin = fmt.find_first_of('$'); 74 if (begin == StringRef::npos) { 75 // No placeholders: the whole format string should be returned as a 76 // literal string. 77 return {FmtReplacement{fmt}, StringRef()}; 78 } 79 if (begin != 0) { 80 // The first placeholder is not at the beginning: we can split the format 81 // string into a literal string and the rest. 82 return {FmtReplacement{fmt.substr(0, begin)}, fmt.substr(begin)}; 83 } 84 85 // The first placeholder is at the beginning 86 87 if (fmt.size() == 1) { 88 // The whole format string just contains '$': treat as literal. 89 return {FmtReplacement{fmt}, StringRef()}; 90 } 91 92 // Allow escaping dollar with '$$' 93 if (fmt[1] == '$') { 94 return {FmtReplacement{fmt.substr(0, 1)}, fmt.substr(2)}; 95 } 96 97 // First try to see if it's a positional placeholder, and then handle special 98 // placeholders. 99 100 size_t end = 101 fmt.find_if_not([](char c) { return std::isdigit(c); }, /*From=*/1); 102 if (end != 1) { 103 // We have a positional placeholder. Parse the index. 104 size_t index = 0; 105 if (fmt.substr(1, end - 1).consumeInteger(0, index)) { 106 llvm_unreachable("invalid replacement sequence index"); 107 } 108 109 // Check if this is the part of a range specification. 110 if (fmt.substr(end, 3) == "...") { 111 // Currently only ranges without upper bound are supported. 112 return { 113 FmtReplacement{fmt.substr(0, end + 3), index, FmtReplacement::kUnset}, 114 fmt.substr(end + 3)}; 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 if (repl.type == FmtReplacement::Type::PositionalRangePH) { 177 if (repl.index >= adapters.size()) { 178 s << repl.spec << kMarkerForNoSubst; 179 continue; 180 } 181 auto range = llvm::makeArrayRef(adapters); 182 range = range.drop_front(repl.index); 183 if (repl.end != FmtReplacement::kUnset) 184 range = range.drop_back(adapters.size() - repl.end); 185 llvm::interleaveComma(range, s, 186 [&](auto &x) { x->format(s, /*Options=*/""); }); 187 continue; 188 } 189 190 assert(repl.type == FmtReplacement::Type::PositionalPH); 191 192 if (repl.index >= adapters.size()) { 193 s << repl.spec << kMarkerForNoSubst; 194 continue; 195 } 196 adapters[repl.index]->format(s, /*Options=*/""); 197 } 198 } 199 200 FmtStrVecObject::FmtStrVecObject(StringRef fmt, const FmtContext *ctx, 201 ArrayRef<std::string> params) 202 : FmtObjectBase(fmt, ctx, params.size()) { 203 parameters.reserve(params.size()); 204 for (std::string p : params) 205 parameters.push_back(llvm::detail::build_format_adapter(std::move(p))); 206 207 adapters.reserve(parameters.size()); 208 for (auto &p : parameters) 209 adapters.push_back(&p); 210 } 211 212 FmtStrVecObject::FmtStrVecObject(FmtStrVecObject &&that) 213 : FmtObjectBase(std::move(that)), parameters(std::move(that.parameters)) { 214 adapters.reserve(parameters.size()); 215 for (auto &p : parameters) 216 adapters.push_back(&p); 217 } 218