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 llvm::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 = fmt.find_if_not([](char c) { return std::isdigit(c); }, 1); 101 if (end != 1) { 102 // We have a positional placeholder. Parse the index. 103 size_t index = 0; 104 if (fmt.substr(1, end - 1).consumeInteger(0, index)) { 105 llvm_unreachable("invalid replacement sequence index"); 106 } 107 108 if (end == StringRef::npos) { 109 // All the remaining characters are part of the positional placeholder. 110 return {FmtReplacement{fmt, index}, StringRef()}; 111 } 112 return {FmtReplacement{fmt.substr(0, end), index}, fmt.substr(end)}; 113 } 114 115 end = fmt.find_if_not([](char c) { return std::isalnum(c) || c == '_'; }, 1); 116 auto placeholder = FmtContext::getPlaceHolderKind(fmt.substr(1, end - 1)); 117 if (end == StringRef::npos) { 118 // All the remaining characters are part of the special placeholder. 119 return {FmtReplacement{fmt, placeholder}, StringRef()}; 120 } 121 return {FmtReplacement{fmt.substr(0, end), placeholder}, fmt.substr(end)}; 122 } 123 124 std::vector<FmtReplacement> FmtObjectBase::parseFormatString(StringRef fmt) { 125 std::vector<FmtReplacement> replacements; 126 FmtReplacement repl; 127 while (!fmt.empty()) { 128 std::tie(repl, fmt) = splitFmtSegment(fmt); 129 if (repl.type != FmtReplacement::Type::Empty) 130 replacements.push_back(repl); 131 } 132 return replacements; 133 } 134 135 void FmtObjectBase::format(raw_ostream &s) const { 136 for (auto &repl : replacements) { 137 if (repl.type == FmtReplacement::Type::Empty) 138 continue; 139 140 if (repl.type == FmtReplacement::Type::Literal) { 141 s << repl.spec; 142 continue; 143 } 144 145 if (repl.type == FmtReplacement::Type::SpecialPH) { 146 if (repl.placeholder == FmtContext::PHKind::None) { 147 s << repl.spec; 148 } else if (!context) { 149 // We need the context to replace special placeholders. 150 s << repl.spec << kMarkerForNoSubst; 151 } else { 152 Optional<StringRef> subst; 153 if (repl.placeholder == FmtContext::PHKind::Custom) { 154 // Skip the leading '$' sign for the custom placeholder 155 subst = context->getSubstFor(repl.spec.substr(1)); 156 } else { 157 subst = context->getSubstFor(repl.placeholder); 158 } 159 if (subst) 160 s << *subst; 161 else 162 s << repl.spec << kMarkerForNoSubst; 163 } 164 continue; 165 } 166 167 assert(repl.type == FmtReplacement::Type::PositionalPH); 168 169 if (repl.index >= adapters.size()) { 170 s << repl.spec << kMarkerForNoSubst; 171 continue; 172 } 173 adapters[repl.index]->format(s, /*Options=*/""); 174 } 175 } 176