| 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(placeholder: sub.first, subst: 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::withSelf(Twine subst) { |
| 42 | builtinSubstMap[PHKind::Self] = subst.str(); |
| 43 | return *this; |
| 44 | } |
| 45 | |
| 46 | std::optional<StringRef> |
| 47 | FmtContext::getSubstFor(FmtContext::PHKind placeholder) const { |
| 48 | if (placeholder == FmtContext::PHKind::None || |
| 49 | placeholder == FmtContext::PHKind::Custom) |
| 50 | return {}; |
| 51 | auto it = builtinSubstMap.find(Val: placeholder); |
| 52 | if (it == builtinSubstMap.end()) |
| 53 | return {}; |
| 54 | return StringRef(it->second); |
| 55 | } |
| 56 | |
| 57 | std::optional<StringRef> FmtContext::getSubstFor(StringRef placeholder) const { |
| 58 | auto it = customSubstMap.find(Key: placeholder); |
| 59 | if (it == customSubstMap.end()) |
| 60 | return {}; |
| 61 | return StringRef(it->second); |
| 62 | } |
| 63 | |
| 64 | FmtContext::PHKind FmtContext::getPlaceHolderKind(StringRef str) { |
| 65 | return StringSwitch<FmtContext::PHKind>(str) |
| 66 | .Case(S: "_builder" , Value: FmtContext::PHKind::Builder) |
| 67 | .Case(S: "_self" , Value: FmtContext::PHKind::Self) |
| 68 | .Case(S: "" , Value: FmtContext::PHKind::None) |
| 69 | .Default(Value: FmtContext::PHKind::Custom); |
| 70 | } |
| 71 | |
| 72 | std::pair<FmtReplacement, StringRef> |
| 73 | FmtObjectBase::splitFmtSegment(StringRef fmt) { |
| 74 | size_t begin = fmt.find_first_of(C: '$'); |
| 75 | if (begin == StringRef::npos) { |
| 76 | // No placeholders: the whole format string should be returned as a |
| 77 | // literal string. |
| 78 | return {FmtReplacement{fmt}, StringRef()}; |
| 79 | } |
| 80 | if (begin != 0) { |
| 81 | // The first placeholder is not at the beginning: we can split the format |
| 82 | // string into a literal string and the rest. |
| 83 | return {FmtReplacement{fmt.substr(Start: 0, N: begin)}, fmt.substr(Start: begin)}; |
| 84 | } |
| 85 | |
| 86 | // The first placeholder is at the beginning |
| 87 | |
| 88 | if (fmt.size() == 1) { |
| 89 | // The whole format string just contains '$': treat as literal. |
| 90 | return {FmtReplacement{fmt}, StringRef()}; |
| 91 | } |
| 92 | |
| 93 | // Allow escaping dollar with '$$' |
| 94 | if (fmt[1] == '$') { |
| 95 | return {FmtReplacement{fmt.substr(Start: 0, N: 1)}, fmt.substr(Start: 2)}; |
| 96 | } |
| 97 | |
| 98 | // First try to see if it's a positional placeholder, and then handle special |
| 99 | // placeholders. |
| 100 | |
| 101 | size_t end = |
| 102 | fmt.find_if_not(F: [](char c) { return std::isdigit(c); }, /*From=*/1); |
| 103 | if (end != 1) { |
| 104 | // We have a positional placeholder. Parse the index. |
| 105 | size_t index = 0; |
| 106 | if (fmt.substr(Start: 1, N: end - 1).consumeInteger(Radix: 0, Result&: index)) { |
| 107 | llvm_unreachable("invalid replacement sequence index" ); |
| 108 | } |
| 109 | |
| 110 | // Check if this is the part of a range specification. |
| 111 | if (fmt.substr(Start: end, N: 3) == "..." ) { |
| 112 | // Currently only ranges without upper bound are supported. |
| 113 | return { |
| 114 | FmtReplacement{fmt.substr(Start: 0, N: end + 3), index, FmtReplacement::kUnset}, |
| 115 | fmt.substr(Start: end + 3)}; |
| 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(Start: 0, N: end), index}, fmt.substr(Start: end)}; |
| 123 | } |
| 124 | |
| 125 | end = fmt.find_if_not(F: [](char c) { return std::isalnum(c) || c == '_'; }, From: 1); |
| 126 | auto placeholder = FmtContext::getPlaceHolderKind(str: fmt.substr(Start: 1, N: 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(Start: 0, N: end), placeholder}, fmt.substr(Start: 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(args&: repl, args&: fmt) = splitFmtSegment(fmt); |
| 139 | if (repl.type != FmtReplacement::Type::Empty) |
| 140 | replacements.push_back(x: 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 | std::optional<StringRef> subst; |
| 163 | if (repl.placeholder == FmtContext::PHKind::Custom) { |
| 164 | // Skip the leading '$' sign for the custom placeholder |
| 165 | subst = context->getSubstFor(placeholder: repl.spec.substr(Start: 1)); |
| 166 | } else { |
| 167 | subst = context->getSubstFor(placeholder: repl.placeholder); |
| 168 | } |
| 169 | if (subst) |
| 170 | s << *subst; |
| 171 | else |
| 172 | s << repl.spec << kMarkerForNoSubst; |
| 173 | } |
| 174 | continue; |
| 175 | } |
| 176 | |
| 177 | if (repl.type == FmtReplacement::Type::PositionalRangePH) { |
| 178 | if (repl.index >= adapters.size()) { |
| 179 | s << repl.spec << kMarkerForNoSubst; |
| 180 | continue; |
| 181 | } |
| 182 | auto range = llvm::ArrayRef(adapters); |
| 183 | range = range.drop_front(N: repl.index); |
| 184 | if (repl.end != FmtReplacement::kUnset) |
| 185 | range = range.drop_back(N: adapters.size() - repl.end); |
| 186 | llvm::interleaveComma(c: range, os&: s, |
| 187 | each_fn: [&](auto &x) { x->format(s, /*Options=*/"" ); }); |
| 188 | continue; |
| 189 | } |
| 190 | |
| 191 | assert(repl.type == FmtReplacement::Type::PositionalPH); |
| 192 | |
| 193 | if (repl.index >= adapters.size()) { |
| 194 | s << repl.spec << kMarkerForNoSubst; |
| 195 | continue; |
| 196 | } |
| 197 | adapters[repl.index]->format(S&: s, /*Options=*/"" ); |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | FmtStrVecObject::FmtStrVecObject(StringRef fmt, const FmtContext *ctx, |
| 202 | ArrayRef<std::string> params) |
| 203 | : FmtObjectBase(fmt, ctx, params.size()) { |
| 204 | parameters.reserve(N: params.size()); |
| 205 | for (std::string p : params) |
| 206 | parameters.push_back( |
| 207 | Elt: llvm::support::detail::build_format_adapter(Item: std::move(p))); |
| 208 | |
| 209 | adapters.reserve(n: parameters.size()); |
| 210 | for (auto &p : parameters) |
| 211 | adapters.push_back(x: &p); |
| 212 | } |
| 213 | |
| 214 | FmtStrVecObject::FmtStrVecObject(FmtStrVecObject &&that) |
| 215 | : FmtObjectBase(std::move(that)), parameters(std::move(that.parameters)) { |
| 216 | adapters.reserve(n: parameters.size()); |
| 217 | for (auto &p : parameters) |
| 218 | adapters.push_back(x: &p); |
| 219 | } |
| 220 | |