blob: 9cfe03d342cc8f1ba63960b9d6cb6b8e0ca511d1 [file] [log] [blame]
// 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 <array>
#include <climits>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <limits>
#include <sstream>
#include <string>
#include <vector>
#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 <class IntType>
bool DecodeInteger(const ::std::string &text, IntType *result) {
IntType accumulator = 0;
IntType base = 10;
bool negative = false;
unsigned offset = 0;
if (::std::is_signed<IntType>::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<IntType>::min() + digit) / base) {
return false;
}
accumulator = accumulator * base - digit;
} else {
if (accumulator >
(::std::numeric_limits<IntType>::max() - digit) / base) {
return false;
}
accumulator = accumulator * base + digit;
}
}
*result = accumulator;
return true;
}
template <class Stream>
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 <class Stream>
bool ReadToken(Stream *stream, ::std::string *token) {
::std::vector<char> 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 <class Stream, class View>
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 <class Stream, typename IntegralType>
void WriteIntegerToTextStream(IntegralType value, Stream *stream,
::std::uint8_t base, bool digit_grouping) {
static_assert(::std::numeric_limits<
typename ::std::remove_cv<IntegralType>::type>::is_integer,
"WriteIntegerToTextStream only supports integer types.");
static_assert(
!::std::is_same<bool,
typename ::std::remove_cv<IntegralType>::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<decltype(value)>::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 <class Stream, class View>
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 <class Stream, class View>
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 <class Stream, class View>
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 <class Float>
struct FloatConstants;
template <>
struct FloatConstants<float> {
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<double> {
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 <class Float>
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<Float>::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<Float>::quiet_NaN();
UInt bits;
::std::memcpy(&bits, &default_nan, sizeof(bits));
nan_payload = bits & FloatConstants<Float>::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<Float>::kExponentMask() |
FloatConstants<Float>::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<Float>::kExponentMask();
bits |= nan_payload;
if (negative) {
bits |= FloatConstants<Float>::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<Float>::infinity();
return true;
} else {
*result = ::std::numeric_limits<Float>::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<Float>::kScanfFormat(),
result, &chars_used) < 1) {
return false;
}
if (chars_used < 0 ||
static_cast</**/ ::std::size_t>(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 <class Stream, class View>
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 <class Stream, class Float>
void WriteFloatToTextStream(Float n, Stream *stream,
const TextOutputOptions &options) {
static_assert(::std::is_same<Float, float>::value ||
::std::is_same<Float, double>::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<Float>::MatchingIntegerType bits;
::std::memcpy(&bits, &n, sizeof(bits));
::std::uint64_t nan_payload = bits & FloatConstants<Float>::kMantissaMask();
::std::uint64_t nan_sign = bits & FloatConstants<Float>::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<char, 30> buffer;
// TODO(bolms): Figure out how to get ::std::snprintf to work on
// microcontroller builds.
::std::size_t snprintf_result = static_cast</**/ ::std::size_t>(::snprintf(
&(buffer[0]), buffer.size(), "%.*g",
FloatConstants<Float>::kPrintfPrecision(), static_cast<double>(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 <class Stream, class View>
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<ValueType> 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<typename View::ValueType>(value));
} else if (token[0] == '-') {
::std::int64_t value;
if (!DecodeInteger(token, &value)) return false;
return view->TryToWrite(static_cast<typename View::ValueType>(value));
} else {
typename View::ValueType value;
if (!TryToGetEnumFromName(token.c_str(), &value)) return false;
return view->TryToWrite(value);
}
}
template <class Stream, class View>
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<typename View::ValueType>::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 <class Array, class Stream>
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 <class Array, class Stream>
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<char>(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 <class Array, class Stream>
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 <class String>
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 <typename EmbossViewType>
inline bool UpdateFromText(const EmbossViewType &view,
const ::std::string &text) {
auto text_stream = support::TextStream{text};
return view.UpdateFromTextStream(&text_stream);
}
template <typename EmbossViewType>
inline ::std::string WriteToString(const EmbossViewType &view,
TextOutputOptions options) {
support::TextOutputStream text_stream;
view.WriteToTextStream(&text_stream, options);
return text_stream.Result();
}
template <typename EmbossViewType>
inline ::std::string WriteToString(const EmbossViewType &view) {
return WriteToString(view, TextOutputOptions());
}
} // namespace emboss
#endif // EMBOSS_RUNTIME_CPP_EMBOSS_TEXT_UTIL_H_