//===-- LocalizationFormat.cpp - Format for Diagnostic Messages -*- C++ -*-===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // This file implements the format for localized diagnostic messages. // //===----------------------------------------------------------------------===// #include "swift/Basic/Range.h" #include "swift/Localization/LocalizationFormat.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/Bitstream/BitstreamReader.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/YAMLParser.h" #include "llvm/Support/YAMLTraits.h" #include #include #include #include namespace { enum LocalDiagID : uint32_t { #define DIAG(KIND, ID, Options, Text, Signature) ID, #include "swift/AST/DiagnosticsAll.def" NumDiags }; static constexpr const char *const diagnosticNameStrings[] = { #define DIAG(KIND, ID, Options, Text, Signature) " [" #ID "]", #include "swift/AST/DiagnosticsAll.def" "", }; } // namespace namespace llvm { namespace yaml { template <> struct ScalarEnumerationTraits { static void enumeration(IO &io, LocalDiagID &value) { #define DIAG(KIND, ID, Options, Text, Signature) \ io.enumCase(value, #ID, LocalDiagID::ID); #include "swift/AST/DiagnosticsAll.def" // Ignore diagnostic IDs that are available in the YAML file and not // available in the `.def` file. if (io.matchEnumFallback()) value = LocalDiagID::NumDiags; } }; } // namespace yaml } // namespace llvm namespace swift { namespace diag { void SerializedLocalizationWriter::insert(swift::DiagID id, llvm::StringRef translation) { generator.insert(static_cast(id), translation); } bool SerializedLocalizationWriter::emit(llvm::StringRef filePath) { assert(llvm::sys::path::extension(filePath) == ".db"); std::error_code error; llvm::raw_fd_ostream OS(filePath, error, llvm::sys::fs::F_None); if (OS.has_error()) { return true; } offset_type offset; { llvm::support::endian::write(OS, 0, llvm::support::little); offset = generator.Emit(OS); } OS.seek(0); llvm::support::endian::write(OS, offset, llvm::support::little); OS.close(); return OS.has_error(); } llvm::StringRef LocalizationProducer::getMessageOr(swift::DiagID id, llvm::StringRef defaultMessage) { auto localizedMessage = getMessage(id); if (localizedMessage.empty()) return defaultMessage; llvm::StringRef diagnosticName(diagnosticNameStrings[(unsigned)id]); if (printDiagnosticName) { assert(localizationSaver); auto localizedDebugDiagnosticMessage = localizationSaver->save(localizedMessage.str() + diagnosticName.str()); return localizedDebugDiagnosticMessage; } return localizedMessage; } SerializedLocalizationProducer::SerializedLocalizationProducer( std::unique_ptr buffer, llvm::Optional localizationSaver, bool printDiagnosticName) : LocalizationProducer(localizationSaver, printDiagnosticName), Buffer(std::move(buffer)) { auto base = reinterpret_cast(Buffer.get()->getBufferStart()); auto tableOffset = endian::read(base, little); SerializedTable.reset(SerializedLocalizationTable::Create( base + tableOffset, base + sizeof(offset_type), base)); } llvm::StringRef SerializedLocalizationProducer::getMessage(swift::DiagID id) const { auto value = SerializedTable.get()->find(id); if (value.getDataLen() == 0) return llvm::StringRef(); return {(const char *)value.getDataPtr(), value.getDataLen()}; } YAMLLocalizationProducer::YAMLLocalizationProducer( llvm::StringRef filePath, llvm::Optional localizationSaver, bool printDiagnosticName) : LocalizationProducer(localizationSaver, printDiagnosticName) { auto FileBufOrErr = llvm::MemoryBuffer::getFileOrSTDIN(filePath); llvm::MemoryBuffer *document = FileBufOrErr->get(); diag::LocalizationInput yin(document->getBuffer()); yin >> diagnostics; unknownIDs = std::move(yin.unknownIDs); } llvm::StringRef YAMLLocalizationProducer::getMessage(swift::DiagID id) const { return diagnostics[(unsigned)id]; } void YAMLLocalizationProducer::forEachAvailable( llvm::function_ref callback) const { for (uint32_t i = 0, n = diagnostics.size(); i != n; ++i) { auto translation = diagnostics[i]; if (!translation.empty()) callback(static_cast(i), translation); } } llvm::Optional LocalizationInput::readID(llvm::yaml::IO &io) { LocalDiagID diagID; io.mapRequired("id", diagID); if (diagID == LocalDiagID::NumDiags) return llvm::None; return static_cast(diagID); } template typename std::enable_if::value, void>::type readYAML(llvm::yaml::IO &io, T &Seq, T &unknownIDs, bool, Context &Ctx) { unsigned count = io.beginSequence(); if (count) { Seq.resize(LocalDiagID::NumDiags); } for (unsigned i = 0; i < count; ++i) { void *SaveInfo; if (io.preflightElement(i, SaveInfo)) { io.beginMapping(); // If the current diagnostic ID is available in YAML and in `.def`, add it // to the diagnostics array. Otherwise, re-parse the current diagnnostic // id as a string and store it in `unknownIDs` array. if (auto id = LocalizationInput::readID(io)) { // YAML file isn't guaranteed to have diagnostics in order of their // declaration in `.def` files, to accommodate that we need to leave // holes in diagnostic array for diagnostics which haven't yet been // localized and for the ones that have `id` indicates their position. io.mapRequired("msg", Seq[*id]); } else { std::string unknownID, message; // Read "raw" id since it doesn't exist in `.def` file. io.mapRequired("id", unknownID); io.mapRequired("msg", message); unknownIDs.push_back(unknownID); } io.endMapping(); io.postflightElement(SaveInfo); } } io.endSequence(); } template typename std::enable_if::value, LocalizationInput &>::type operator>>(LocalizationInput &yin, T &diagnostics) { llvm::yaml::EmptyContext Ctx; if (yin.setCurrentDocument()) { // If YAML file's format doesn't match the current format in // DiagnosticMessageFormat, will throw an error. readYAML(yin, diagnostics, yin.unknownIDs, true, Ctx); } return yin; } void DefToYAMLConverter::convert(llvm::raw_ostream &out) { for (auto i : swift::indices(IDs)) { out << "- id: " << IDs[i] << "\n"; const std::string &msg = Messages[i]; out << " msg: \""; // Add an escape character before a double quote `"` or a backslash `\`. for (unsigned j = 0; j < msg.length(); ++j) { if (msg[j] == '"') { out << '\\'; out << '"'; } else if (msg[j] == '\\') { out << '\\'; out << '\\'; } else { out << msg[j]; } } out << "\"\r\n"; } } } // namespace diag } // namespace swift