// Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This header contains functionality related to Emboss text output. #ifndef EMBOSS_RUNTIME_CPP_EMBOSS_TEXT_UTIL_H_ #define EMBOSS_RUNTIME_CPP_EMBOSS_TEXT_UTIL_H_ #include #include #include #include #include #include #include #include #include #include #include "runtime/cpp/emboss_defines.h" namespace emboss { // TextOutputOptions are used to configure text output. Typically, one can just // use a default TextOutputOptions() (for compact output) or MultilineText() // (for reasonable formatted output). class TextOutputOptions final { public: TextOutputOptions() = default; TextOutputOptions PlusOneIndent() const { TextOutputOptions result = *this; result.current_indent_ += indent(); return result; } TextOutputOptions Multiline(bool new_value) const { TextOutputOptions result = *this; result.multiline_ = new_value; return result; } TextOutputOptions WithIndent(::std::string new_value) const { TextOutputOptions result = *this; result.indent_ = ::std::move(new_value); return result; } TextOutputOptions WithComments(bool new_value) const { TextOutputOptions result = *this; result.comments_ = new_value; return result; } TextOutputOptions WithDigitGrouping(bool new_value) const { TextOutputOptions result = *this; result.digit_grouping_ = new_value; return result; } TextOutputOptions WithNumericBase(uint8_t new_value) const { TextOutputOptions result = *this; result.numeric_base_ = new_value; return result; } TextOutputOptions WithAllowPartialOutput(bool new_value) const { TextOutputOptions result = *this; result.allow_partial_output_ = new_value; return result; } ::std::string current_indent() const { return current_indent_; } ::std::string indent() const { return indent_; } bool multiline() const { return multiline_; } bool digit_grouping() const { return digit_grouping_; } bool comments() const { return comments_; } ::std::uint8_t numeric_base() const { return numeric_base_; } bool allow_partial_output() const { return allow_partial_output_; } private: ::std::string indent_; ::std::string current_indent_; bool comments_ = false; bool multiline_ = false; bool digit_grouping_ = false; bool allow_partial_output_ = false; ::std::uint8_t numeric_base_ = 10; }; namespace support { // TextOutputStream puts a stream-like interface onto a std::string, for use by // DumpToTextStream. It is used by UpdateFromText(). class TextOutputStream final { public: inline explicit TextOutputStream() = default; inline void Write(const ::std::string &text) { text_.write(text.data(), text.size()); } inline void Write(const char *text) { text_.write(text, strlen(text)); } inline void Write(const char c) { text_.put(c); } inline ::std::string Result() { return text_.str(); } private: ::std::ostringstream text_; }; // DecodeInteger decodes an integer from a string. This is very similar to the // many, many existing integer decode routines in the world, except that a) it // accepts integers in any Emboss format, and b) it can run in environments that // do not support std::istream or Google's number conversion routines. // // Ideally, this would be replaced by someone else's code. template bool DecodeInteger(const ::std::string &text, IntType *result) { IntType accumulator = 0; IntType base = 10; bool negative = false; unsigned offset = 0; if (::std::is_signed::value && text.size() >= 1 + offset && text[offset] == '-') { negative = true; offset += 1; } if (text.size() >= 2 + offset && text[offset] == '0') { if (text[offset + 1] == 'x' || text[offset + 1] == 'X') { base = 16; offset += 2; } else if (text[offset + 1] == 'b' || text[offset + 1] == 'B') { base = 2; offset += 2; } } // "", "0x", "0b", "-", "-0x", and "-0b" are not valid numbers. if (offset == text.size()) return false; for (; offset < text.size(); ++offset) { char c = text[offset]; IntType digit = 0; if (c == '_') { if (offset == 0) { return false; } continue; } else if (c >= '0' && c <= '9') { digit = c - '0'; } else if (c >= 'A' && c <= 'F') { digit = c - 'A' + 10; } else if (c >= 'a' && c <= 'f') { digit = c - 'a' + 10; } else { return false; } if (digit >= base) { return false; } if (negative) { if (accumulator < (::std::numeric_limits::min() + digit) / base) { return false; } accumulator = accumulator * base - digit; } else { if (accumulator > (::std::numeric_limits::max() - digit) / base) { return false; } accumulator = accumulator * base + digit; } } *result = accumulator; return true; } template bool DiscardWhitespace(Stream *stream) { char c; bool in_comment = false; do { if (!stream->Read(&c)) return true; if (c == '#') in_comment = true; if (c == '\r' || c == '\n') in_comment = false; } while (in_comment || c == ' ' || c == '\t' || c == '\n' || c == '\r'); return stream->Unread(c); } template bool ReadToken(Stream *stream, ::std::string *token) { ::std::vector result; char c; if (!DiscardWhitespace(stream)) return false; if (!stream->Read(&c)) { *token = ""; return true; } const char *const punctuation = ":{}[],"; if (strchr(punctuation, c) != nullptr) { *token = ::std::string(1, c); return true; } else { // TODO(bolms): Only allow alphanumeric characters here? do { result.push_back(c); if (!stream->Read(&c)) { *token = ::std::string(&result[0], result.size()); return true; } } while (c != ' ' && c != '\t' && c != '\n' && c != '\r' && c != '#' && strchr(punctuation, c) == nullptr); if (!stream->Unread(c)) return false; *token = ::std::string(&result[0], result.size()); return true; } } template bool ReadIntegerFromTextStream(View *view, Stream *stream) { ::std::string token; if (!::emboss::support::ReadToken(stream, &token)) return false; if (token.empty()) return false; typename View::ValueType value; if (!::emboss::support::DecodeInteger(token, &value)) return false; return view->TryToWrite(value); } // WriteIntegerToTextStream encodes the given value in base 2, 10, or 16, with // or without digit group separators ('_'), and then calls stream->Write() with // a char * argument that is a C-style null-terminated string of the encoded // number. // // As with DecodeInteger, above, it would be nice to be able to replace this // with someone else's code, but I (bolms@) was unable to find anything in // standard C++ that would encode numbers in binary, nothing that would add // digit separators to hex numbers, and nothing that would use '_' for digit // separators. template void WriteIntegerToTextStream(IntegralType value, Stream *stream, ::std::uint8_t base, bool digit_grouping) { static_assert(::std::numeric_limits< typename ::std::remove_cv::type>::is_integer, "WriteIntegerToTextStream only supports integer types."); static_assert( !::std::is_same::type>::value, "WriteIntegerToTextStream only supports integer types."); EMBOSS_CHECK(base == 10 || base == 2 || base == 16); const char *const digits = "0123456789abcdef"; const int grouping = base == 10 ? 3 : base == 16 ? 4 : 8; // The maximum size 32-bit number is -2**31, which is: // // -0b10000000_00000000_00000000_00000000 (38 chars) // -2_147_483_648 (14 chars) // -0x8000_0000 (12 chars) // // Likewise, the maximum size 8-bit number is -128, which is: // -0b10000000 (11 chars) // -128 (4 chars) // -0x80 (5 chars) // // Binary with separators is always the longest value: 9 chars per 8 bits, // minus 1 char for the '_' that does not appear at the front of the number, // plus 2 chars for "0b", plus 1 char for '-', plus 1 extra char for the // trailing '\0', which is (sizeof value) * CHAR_BIT * 9 / 8 - 1 + 2 + 1 + 1. const int buffer_size = (sizeof value) * CHAR_BIT * 9 / 8 + 3; char buffer[buffer_size]; buffer[buffer_size - 1] = '\0'; int next_char = buffer_size - 2; if (value == 0) { EMBOSS_DCHECK_GE(next_char, 0); buffer[next_char] = digits[0]; --next_char; } int sign = value < 0 ? -1 : 1; int digit_count = 0; auto buffer_char = [&](char c) { EMBOSS_DCHECK_GE(next_char, 0); buffer[next_char] = c; --next_char; }; if (value < 0) { if (value == ::std::numeric_limits::lowest()) { // The minimum negative two's-complement value has no corresponding // positive value, so 'value = -value' is not useful in that case. // Instead, we do some trickery to buffer the lowest-order digit here. auto digit = -(value + 1) % base + 1; value = -(value + 1) / base; if (digit == base) { digit = 0; ++value; } buffer_char(digits[digit]); ++digit_count; } else { value = -value; } } while (value > 0) { if (digit_count && digit_count % grouping == 0 && digit_grouping) { buffer_char('_'); } buffer_char(digits[value % base]); value /= base; ++digit_count; } if (base == 16) { buffer_char('x'); buffer_char('0'); } else if (base == 2) { buffer_char('b'); buffer_char('0'); } if (sign < 0) { buffer_char('-'); } stream->Write(buffer + 1 + next_char); } // Writes an integer value in the base given in options, plus an optional // comment with the same value in a second base. This is used for the common // output format of IntView, UIntView, and BcdView. template void WriteIntegerViewToTextStream(View *view, Stream *stream, const TextOutputOptions &options) { WriteIntegerToTextStream(view->Read(), stream, options.numeric_base(), options.digit_grouping()); if (options.comments()) { stream->Write(" # "); WriteIntegerToTextStream(view->Read(), stream, options.numeric_base() == 10 ? 16 : 10, options.digit_grouping()); } } template bool ReadBooleanFromTextStream(View *view, Stream *stream) { ::std::string token; if (!::emboss::support::ReadToken(stream, &token)) return false; if (token == "true") { return view->TryToWrite(true); } else if (token == "false") { return view->TryToWrite(false); } // TODO(bolms): Provide a way to get an error message on parse failure. return false; } // The TextOutputOptions parameter is present so that it can be passed in by // generated code that uses the same form for WriteBooleanViewToTextStream, // WriteIntegerViewToTextStream, and WriteEnumViewToTextStream. template void WriteBooleanViewToTextStream(View *view, Stream *stream, const TextOutputOptions &) { if (view->Read()) { stream->Write("true"); } else { stream->Write("false"); } } // FloatConstants holds various masks for working with IEEE754-compatible // floating-point values at a bit level. These are mostly used here to // implement text format for NaNs, preserving the NaN payload so that the text // format can (in theory) provide a bit-exact round-trip through the text // format. template struct FloatConstants; template <> struct FloatConstants { static_assert(sizeof(float) == 4, "Emboss requires 32-bit float."); using MatchingIntegerType = ::std::uint32_t; static constexpr MatchingIntegerType kMantissaMask() { return 0x7fffffU; } static constexpr MatchingIntegerType kExponentMask() { return 0x7f800000U; } static constexpr MatchingIntegerType kSignMask() { return 0x80000000U; } static constexpr int kPrintfPrecision() { return 9; } static constexpr const char *kScanfFormat() { return "%f%n"; } }; template <> struct FloatConstants { static_assert(sizeof(double) == 8, "Emboss requires 64-bit double."); using MatchingIntegerType = ::std::uint64_t; static constexpr MatchingIntegerType kMantissaMask() { return 0xfffffffffffffUL; } static constexpr MatchingIntegerType kExponentMask() { return 0x7ff0000000000000UL; } static constexpr MatchingIntegerType kSignMask() { return 0x8000000000000000UL; } static constexpr int kPrintfPrecision() { return 17; } static constexpr const char *kScanfFormat() { return "%lf%n"; } }; // Decodes a floating-point number from text. template bool DecodeFloat(const ::std::string &token, Float *result) { // The state of the world for reading floating-point values is somewhat better // than the situation for writing them, but there are still a few bits that // are underspecified. This function is the mirror of WriteFloatToTextStream, // below, so it specifically decodes infinities and NaNs in the formats that // Emboss uses. // // Because of the use of scanf here, this function accepts hex floating-point // values (0xh.hhhhpeee) *on some systems*. TODO(bolms): make hex float // support universal. using UInt = typename FloatConstants::MatchingIntegerType; if (token.empty()) return false; // First, check for negative. bool negative = token[0] == '-'; // Second, check for NaN. ::std::size_t i = token[0] == '-' || token[0] == '+' ? 1 : 0; if (token.size() >= i + 3 && (token[i] == 'N' || token[i] == 'n') && (token[i + 1] == 'A' || token[i + 1] == 'a') && (token[i + 2] == 'N' || token[i + 2] == 'n')) { UInt nan_payload; if (token.size() >= i + 4) { if (token[i + 3] == '(' && token[token.size() - 1] == ')') { if (!DecodeInteger(token.substr(i + 4, token.size() - i - 5), &nan_payload)) { return false; } } else { // NaN may not be followed by trailing characters other than a // ()-enclosed payload. return false; } } else { // If no specific NaN was given, take a default NaN from the C++ standard // library. Technically, a conformant C++ implementation might not have // quiet_NaN(), but any IEEE754-based implementation should. // // It is tempting to just write the default NaN directly into the view and // return success, but "-NaN" should be have its sign bit set, and there // is no direct way to set the sign bit of a NaN, so there are fewer code // paths if we extract the default NaN payload, then use it in the // reconstruction step, below. Float default_nan = ::std::numeric_limits::quiet_NaN(); UInt bits; ::std::memcpy(&bits, &default_nan, sizeof(bits)); nan_payload = bits & FloatConstants::kMantissaMask(); } if (nan_payload == 0) { // "NaN" with a payload of zero is actually the bit pattern for infinity; // "NaN(0)" should not be an alias for "Inf". return false; } if (nan_payload & (FloatConstants::kExponentMask() | FloatConstants::kSignMask())) { // The payload must be small enough to fit in the payload space; it must // not overflow into the exponent or sign bits. // // Note that the DecodeInteger call which decoded the payload will return // false if the payload would overflow the `UInt` type, so cases like // "NaN(0x10000000000000000000000000000)" -- which are so big that they no // longer interfere with the sign or exponent -- are caught above. return false; } UInt bits = FloatConstants::kExponentMask(); bits |= nan_payload; if (negative) { bits |= FloatConstants::kSignMask(); } ::std::memcpy(result, &bits, sizeof(bits)); return true; } // If the value is not NaN, check for infinity. if (token.size() >= i + 3 && (token[i] == 'I' || token[i] == 'i') && (token[i + 1] == 'N' || token[i + 1] == 'n') && (token[i + 2] == 'F' || token[i + 2] == 'f')) { if (token.size() > i + 3) { // Infinity must be exactly "Inf" or "-Inf" (case insensitive). There // must not be trailing characters. return false; } // As with quiet_NaN(), a conforming C++ implementation might not have // infinity(), but an IEEE 754-based implementation should. if (negative) { *result = -::std::numeric_limits::infinity(); return true; } else { *result = ::std::numeric_limits::infinity(); return true; } } // For non-NaN, non-Inf values, use the C scanf function, mirroring the use of // printf for writing the value, below. int chars_used = -1; if (::std::sscanf(token.c_str(), FloatConstants::kScanfFormat(), result, &chars_used) < 1) { return false; } if (chars_used < 0 || static_cast(chars_used) < token.size()) { return false; } return true; } // Decodes a floating-point number from a text stream and writes it to the // specified view. template bool ReadFloatFromTextStream(View *view, Stream *stream) { ::std::string token; if (!ReadToken(stream, &token)) return false; typename View::ValueType value; if (!DecodeFloat(token, &value)) return false; return view->TryToWrite(value); } template void WriteFloatToTextStream(Float n, Stream *stream, const TextOutputOptions &options) { static_assert(::std::is_same::value || ::std::is_same::value, "WriteFloatToTextStream can only write float or double."); // The state of the world w.r.t. rendering floating-points as decimal text is, // ca. 2018, less than ideal. // // In C++ land, there is actually no stable facility in the standard library // until to_chars() in C++17 -- which is not actually implemented yet in // libc++. to_string(), the printf() family, and the iostreams system all // respect the current locale. In most programs, the locale is permanently // left on "C", but this is not guaranteed. to_string() also uses a fixed and // rather unfortunate format. // // For integers, I (bolms@) chose to just implement custom read and write // routines, but those routines are quite small and straightforward compared // to floating point conversion. Even writing correct output is difficult, // and writing correct and minimal output is the subject of a number of // academic papers. // // For the moment, I'm just using snprintf("%.*g", 17, n), which is guaranteed // to be read back as the same number, but can be longer than strictly // necessary. // // TODO(bolms): Import a modified version of the double-to-string conversion // from Swift's standard library, which appears to be best implementation // currently available. if (::std::isnan(n)) { // The printf format for NaN is just "NaN". In the interests of keeping // things bit-exact, Emboss prints the exact NaN. typename FloatConstants::MatchingIntegerType bits; ::std::memcpy(&bits, &n, sizeof(bits)); ::std::uint64_t nan_payload = bits & FloatConstants::kMantissaMask(); ::std::uint64_t nan_sign = bits & FloatConstants::kSignMask(); if (nan_sign) { // NaN still has a sign bit, which is generally treated differently from // the payload. There is no real "standard" text format for NaNs, but // "-NaN" appears to be a common way of indicating a NaN with the sign bit // set. stream->Write("-NaN("); } else { stream->Write("NaN("); } // NaN payloads are always dumped in hex. Note that Emboss is treating the // is_quiet/is_signal bit as just another bit in the payload. WriteIntegerToTextStream(nan_payload, stream, 16, options.digit_grouping()); stream->Write(")"); return; } if (::std::isinf(n)) { if (n < 0.0) { stream->Write("-Inf"); } else { stream->Write("Inf"); } return; } // TODO(bolms): Should the current numeric base be honored here? Should there // be a separate Float numeric base? ::std::array buffer; // TODO(bolms): Figure out how to get ::std::snprintf to work on // microcontroller builds. ::std::size_t snprintf_result = static_cast(::snprintf( &(buffer[0]), buffer.size(), "%.*g", FloatConstants::kPrintfPrecision(), static_cast(n))); (void)snprintf_result; // Unused if EMBOSS_CHECK_LE is compiled out. EMBOSS_CHECK_LE(snprintf_result, buffer.size()); stream->Write(&buffer[0]); // TODO(bolms): Support digit grouping. } template bool ReadEnumViewFromTextStream(View *view, Stream *stream) { ::std::string token; if (!ReadToken(stream, &token)) return false; if (token.empty()) return false; if (::std::isdigit(token[0])) { ::std::uint64_t value; if (!DecodeInteger(token, &value)) return false; // TODO(bolms): Fix the static_cast for signed ValueType. // TODO(bolms): Should values between 2**63 and 2**64-1 actually be // allowed in the text format when ValueType is signed? return view->TryToWrite(static_cast(value)); } else if (token[0] == '-') { ::std::int64_t value; if (!DecodeInteger(token, &value)) return false; return view->TryToWrite(static_cast(value)); } else { typename View::ValueType value; if (!TryToGetEnumFromName(token.c_str(), &value)) return false; return view->TryToWrite(value); } } template void WriteEnumViewToTextStream(View *view, Stream *stream, const TextOutputOptions &options) { const char *name = TryToGetNameFromEnum(view->Read()); if (name != nullptr) { stream->Write(name); } // If the enum value has no known name, then write its numeric value // instead. If it does have a known name, and comments are enabled on the // output, then write the numeric value as a comment. if (name == nullptr || options.comments()) { if (name != nullptr) stream->Write(" # "); WriteIntegerToTextStream( static_cast< typename ::std::underlying_type::type>( view->Read()), stream, options.numeric_base(), options.digit_grouping()); } } // Updates an array from a text stream. For an array of integers, the most // basic form of the text format looks like: // // { 0, 1, 2 } // // However, the following are all acceptable and equivalent: // // { 0, 1, 2, } // {0 1 2} // { [2]: 2, [1]: 1, [0]: 0 } // {[2]:2, [0]:0, 1} // // Formally, the array must be contained within braces ("{}"). Elements are // represented as an optional index surrounded by brackets ("[]") followed by // the text format of the element, followed by a single optional comma (","). // If no index is present for the first element, the index 0 will be used. If // no index is present for any elements after the first, the index one greater // than the previous index will be used. template bool ReadArrayFromTextStream(Array *array, Stream *stream) { // The text format allows any given index to be set more than once. In // theory, this function could track indices and fail if an index were // double-set, but doing so would require quite a bit of overhead, and // O(array->ElementCount()) extra space in the worst case. It does not seem // worth it to impose the runtime cost here. ::std::size_t index = 0; ::std::string brace; // Read out the opening brace. if (!ReadToken(stream, &brace)) return false; if (brace != "{") return false; for (;;) { char c; // Check for a closing brace; if present, success. if (!DiscardWhitespace(stream)) return false; if (!stream->Read(&c)) return false; if (c == '}') return true; // If the element has an index, read it. if (c == '[') { ::std::string index_text; if (!ReadToken(stream, &index_text)) return false; if (!::emboss::support::DecodeInteger(index_text, &index)) return false; ::std::string closing_bracket; if (!ReadToken(stream, &closing_bracket)) return false; if (closing_bracket != "]") return false; ::std::string colon; if (!ReadToken(stream, &colon)) return false; if (colon != ":") return false; } else { if (!stream->Unread(c)) return false; } // Read the element. if (index >= array->ElementCount()) return false; if (!(*array)[index].UpdateFromTextStream(stream)) return false; ++index; // If there is a trailing comma, discard it. if (!DiscardWhitespace(stream)) return false; if (!stream->Read(&c)) return false; if (c != ',') { if (c != '}') return false; if (!stream->Unread(c)) return false; } } } // Prints out the elements of an 8-bit Int or UInt array as characters. template void WriteShorthandAsciiArrayCommentToTextStream( const Array *array, Stream *stream, const TextOutputOptions &options) { if (!options.multiline()) return; if (!options.comments()) return; if (array->ElementCount() == 0) return; static constexpr int kCharsPerBlock = 64; static constexpr char kStandInForNonPrintableChar = '.'; auto start_new_line = [&]() { stream->Write("\n"); stream->Write(options.current_indent()); stream->Write("# "); }; for (int i = 0, n = array->ElementCount(); i < n; ++i) { const int c = (*array)[i].Read(); const bool c_is_printable = (c >= 32 && c <= 126); const bool starting_new_block = ((i % kCharsPerBlock) == 0); if (starting_new_block) start_new_line(); stream->Write(c_is_printable ? static_cast(c) : kStandInForNonPrintableChar); } } // Writes an array to a text stream. This writes the array in a format // compatible with ReadArrayFromTextStream, above. For multiline output, writes // one element per line. // // TODO(bolms): Make the output for arrays of small elements (like bytes) much // more compact. // // This will require several support functions like `MaxTextLength` on every // view type, and will substantially increase the number of tests required for // this function, but will make arrays of small elements much more readable. template void WriteArrayToTextStream(Array *array, Stream *stream, const TextOutputOptions &options) { TextOutputOptions element_options = options.PlusOneIndent(); if (options.multiline()) { stream->Write("{"); WriteShorthandArrayCommentToTextStream(array, stream, element_options); for (::std::size_t i = 0; i < array->ElementCount(); ++i) { if (!options.allow_partial_output() || (*array)[i].IsAggregate() || (*array)[i].Ok()) { stream->Write("\n"); stream->Write(element_options.current_indent()); stream->Write("["); // TODO(bolms): Put padding in here so that array elements start at the // same column. // // TODO(bolms): (Maybe) figure out how to get padding to work so that // elements with comments can have their comments align to the same // column. WriteIntegerToTextStream(i, stream, options.numeric_base(), options.digit_grouping()); stream->Write("]: "); (*array)[i].WriteToTextStream(stream, element_options); } else if (element_options.comments()) { stream->Write("\n"); stream->Write(element_options.current_indent()); stream->Write("# ["); WriteIntegerToTextStream(i, stream, options.numeric_base(), options.digit_grouping()); stream->Write("]: UNREADABLE"); } } stream->Write("\n"); stream->Write(options.current_indent()); stream->Write("}"); } else { stream->Write("{"); bool skipped_unreadable = false; for (::std::size_t i = 0; i < array->ElementCount(); ++i) { if (!options.allow_partial_output() || (*array)[i].IsAggregate() || (*array)[i].Ok()) { stream->Write(" "); if (i % 8 == 0 || skipped_unreadable) { stream->Write("["); WriteIntegerToTextStream(i, stream, options.numeric_base(), options.digit_grouping()); stream->Write("]: "); } (*array)[i].WriteToTextStream(stream, element_options); if (i < array->ElementCount() - 1) { stream->Write(","); } skipped_unreadable = false; } else { if (element_options.comments()) { stream->Write(" # "); if (i % 8 == 0) { stream->Write("["); WriteIntegerToTextStream(i, stream, options.numeric_base(), options.digit_grouping()); stream->Write("]: "); } stream->Write("UNREADABLE\n"); } skipped_unreadable = true; } } stream->Write(" }"); } } // TextStream puts a stream-like interface onto a std::string, for use by // UpdateFromTextStream. It is used by UpdateFromText(). class TextStream final { public: // This template handles std::string, std::string_view, and absl::string_view. template inline explicit TextStream(const String &text) : text_(text.data()), length_(text.size()) {} inline explicit TextStream(const char *text) : text_(text), length_(strlen(text)) {} inline TextStream(const char *text, ::std::size_t length) : text_(text), length_(length) {} inline bool Read(char *result) { if (index_ >= length_) return false; *result = text_[index_]; ++index_; return true; } inline bool Unread(char c) { if (index_ < 1) return false; if (text_[index_ - 1] != c) return false; --index_; return true; } private: // It would be nice to use string_view here, but that's not available until // C++17. const char *text_ = nullptr; ::std::size_t length_ = 0; ::std::size_t index_ = 0; }; } // namespace support // Returns a TextOutputOptions set for reasonable multi-line text output. static inline TextOutputOptions MultilineText() { return TextOutputOptions() .Multiline(true) .WithIndent(" ") .WithComments(true) .WithDigitGrouping(true); } // TODO(bolms): Add corresponding ReadFromText*() verbs which enforce the // constraint that all of a field's dependencies must be present in the text // before the field itself is set. template inline bool UpdateFromText(const EmbossViewType &view, const ::std::string &text) { auto text_stream = support::TextStream{text}; return view.UpdateFromTextStream(&text_stream); } template inline ::std::string WriteToString(const EmbossViewType &view, TextOutputOptions options) { support::TextOutputStream text_stream; view.WriteToTextStream(&text_stream, options); return text_stream.Result(); } template inline ::std::string WriteToString(const EmbossViewType &view) { return WriteToString(view, TextOutputOptions()); } } // namespace emboss #endif // EMBOSS_RUNTIME_CPP_EMBOSS_TEXT_UTIL_H_