Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions compiler/back_end/cpp/generated_code_templates
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ ${decode_fields}
void WriteToTextStream(
Stream *emboss_reserved_local_stream,
::emboss::TextOutputOptions emboss_reserved_local_options) const {
::emboss::TextOutputOptions emboss_reserved_local_field_options =
auto emboss_reserved_local_field_options =
emboss_reserved_local_options.PlusOneIndent();
if (emboss_reserved_local_options.multiline()) {
emboss_reserved_local_stream->Write("{\n");
Expand All @@ -295,11 +295,18 @@ ${write_fields}
// Avoid unused variable warnings for empty structures:
(void)emboss_reserved_local_wrote_field;
if (emboss_reserved_local_options.multiline()) {
if (emboss_reserved_local_wrote_field &&
emboss_reserved_local_options.json()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can delete the && ...json() here and then lines 348-351 entirely, I believe.

emboss_reserved_local_stream->Write("\n");
}
emboss_reserved_local_stream->Write(
emboss_reserved_local_options.current_indent());
emboss_reserved_local_stream->Write("}");
} else {
emboss_reserved_local_stream->Write(" }");
if (!emboss_reserved_local_options.json()) {
emboss_reserved_local_stream->Write(" ");
}
emboss_reserved_local_stream->Write("}");
}
}

Expand All @@ -323,20 +330,28 @@ ${write_fields}
// they are not `Ok()` overall, since submembers may still be `Ok()`.
if (!emboss_reserved_local_field_options.allow_partial_output() ||
${field_name}().IsAggregate() || ${field_name}().Ok()) {
if (emboss_reserved_local_wrote_field) {
if (emboss_reserved_local_field_options.json() ||
!emboss_reserved_local_field_options.multiline()) {
emboss_reserved_local_stream->Write(",");
}
}
if (emboss_reserved_local_field_options.multiline()) {
emboss_reserved_local_stream->Write(
emboss_reserved_local_field_options.current_indent());
} else {
if (emboss_reserved_local_wrote_field) {
emboss_reserved_local_stream->Write(",");
}
} else if (!emboss_reserved_local_field_options.json()) {
emboss_reserved_local_stream->Write(" ");
}
emboss_reserved_local_stream->Write("${field_name}: ");
if (emboss_reserved_local_field_options.json()) {
emboss_reserved_local_stream->Write("\"${field_name}\":");
} else {
emboss_reserved_local_stream->Write("${field_name}: ");
}
${field_name}().WriteToTextStream(emboss_reserved_local_stream,
emboss_reserved_local_field_options);
emboss_reserved_local_wrote_field = true;
if (emboss_reserved_local_field_options.multiline()) {
if (emboss_reserved_local_field_options.multiline() &&
!emboss_reserved_local_field_options.json()) {
emboss_reserved_local_stream->Write("\n");
}
} else if (emboss_reserved_local_field_options.allow_partial_output() &&
Expand Down
113 changes: 110 additions & 3 deletions compiler/back_end/cpp/testcode/text_format_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
// Tests of generated code for text format.
#include <stdint.h>

#include <type_traits>
#include <utility>
#include <vector>
#include <array>
#include <numeric>

#include "gtest/gtest.h"
#include "runtime/cpp/emboss_text_util.h"
#include "testdata/text_format.emb.h"

namespace emboss {
Expand Down Expand Up @@ -93,6 +93,113 @@ TEST(TextFormat, UpdateFromText) {
EXPECT_EQ(view.b().Read(), 4);
}

TEST(TextFormat, JsonOutput) {
::std::array<char, 57> values = {};
::std::iota(values.begin(), values.end(), 0);

const auto view = MakeJsonTestStructView(&values);
EXPECT_EQ(
"{\"one_byte_enum\":\"ZERO\",\"seven_bit_uint\":1,\"one_bit_flag\":"
"false,\"one_byte_uint\":2,\"two_byte_uint\":1027,"
"\"four_byte_uint\":134678021,\"eight_byte_uint\":"
"1157159078456920585,\"uint8_array\":[17,18,19,20,21,22,23,24,"
"25,26],\"uint16_array\":[7195,7709,8223,8737,9251,9765,10279,"
"10793,11307,11821],\"struct_array\":[{\"element_one\":47,"
"\"element_two\":48,\"element_three\":49,\"element_four\":50},"
"{\"element_one\":51,\"element_two\":52,\"element_three\":53,"
"\"element_four\":54}]}",
::emboss::WriteToString(view, TextOutputOptions().Json(true)));
}

TEST(TextFormat, JsonOutputRobustness) {
::std::array<char, 57> values = {};
::std::iota(values.begin(), values.end(), 0);

const auto view = MakeJsonTestStructView(&values);
auto options = ::emboss::TextOutputOptions()
.Json(true)
.WithComments(true)
.WithDigitGrouping(true)
.WithNumericBase(16);
EXPECT_EQ(
"{\"one_byte_enum\":\"ZERO\",\"seven_bit_uint\":1,\"one_bit_flag\":"
"false,\"one_byte_uint\":2,\"two_byte_uint\":1027,"
"\"four_byte_uint\":134678021,\"eight_byte_uint\":"
"1157159078456920585,\"uint8_array\":[17,18,19,20,21,22,23,24,"
"25,26],\"uint16_array\":[7195,7709,8223,8737,9251,9765,10279,"
"10793,11307,11821],\"struct_array\":[{\"element_one\":47,"
"\"element_two\":48,\"element_three\":49,\"element_four\":50},"
"{\"element_one\":51,\"element_two\":52,\"element_three\":53,"
"\"element_four\":54}]}",
::emboss::WriteToString(view, options));
}

TEST(TextFormat, DigitGroupingAndNumericBase) {
::std::array<char, 57> values = {};
::std::iota(values.begin(), values.end(), 0);

const auto view = MakeJsonTestStructView(&values);
auto options =
::emboss::TextOutputOptions().WithDigitGrouping(true).WithNumericBase(16);
EXPECT_EQ(
"{ one_byte_enum: ZERO, seven_bit_uint: 0x1, one_bit_flag: false, "
"one_byte_uint: 0x2, two_byte_uint: 0x403, four_byte_uint: 0x807_0605, "
"eight_byte_uint: 0x100f_0e0d_0c0b_0a09, uint8_array: { [0x0]: 0x11, "
"0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, [0x8]: 0x19, 0x1a }, "
"uint16_array: { [0x0]: 0x1c1b, 0x1e1d, 0x201f, 0x2221, 0x2423, 0x2625, "
"0x2827, 0x2a29, [0x8]: 0x2c2b, 0x2e2d }, struct_array: { [0x0]: { "
"element_one: 0x2f, element_two: 0x30, element_three: 0x31, "
"element_four: 0x32 }, { element_one: 0x33, element_two: 0x34, "
"element_three: 0x35, element_four: 0x36 } } }",
::emboss::WriteToString(view, options));
}

TEST(TextFormat, MultilineAndPartial) {
::std::array<char, 1> values = {10};
// MakeVanillaView expects a pointer to an array of size 2, so we have to
// construct the view manually.
auto view = VanillaWriter(values.data(), values.size());
auto options =
::emboss::TextOutputOptions().Multiline(true).WithAllowPartialOutput(
true);
EXPECT_EQ(
"{\n"
"a: 10\n"
"}",
::emboss::WriteToString(view, options));
}

TEST(TextFormat, JsonSkippedFieldOutput) {
::std::array<char, 3> values = {1, 2, 3};
const auto view = MakeStructWithSkippedFieldsView(&values);
EXPECT_EQ("{\"a\":1,\"c\":3}",
::emboss::WriteToString(view, TextOutputOptions().Json(true)));
}

TEST(TextFormat, JsonLargeIntegerAsString) {
::std::array<char, 57> values = {};
::std::iota(values.begin(), values.end(), 0);

const auto view = MakeJsonTestStructView(&values);
auto options = ::emboss::TextOutputOptions()
.Json(true)
.WithJsonLargeIntegerHandling(
JsonLargeIntegerHandling::kLargeAsString);
// With kLargeAsString, the eight_byte_uint should be quoted, but smaller
// integers should remain as numbers.
EXPECT_EQ(
"{\"one_byte_enum\":\"ZERO\",\"seven_bit_uint\":1,\"one_bit_flag\":"
"false,\"one_byte_uint\":2,\"two_byte_uint\":1027,"
"\"four_byte_uint\":134678021,\"eight_byte_uint\":"
"\"1157159078456920585\",\"uint8_array\":[17,18,19,20,21,22,23,24,"
"25,26],\"uint16_array\":[7195,7709,8223,8737,9251,9765,10279,"
"10793,11307,11821],\"struct_array\":[{\"element_one\":47,"
"\"element_two\":48,\"element_three\":49,\"element_four\":50},"
"{\"element_one\":51,\"element_two\":52,\"element_three\":53,"
"\"element_four\":54}]}",
::emboss::WriteToString(view, options));
}

} // namespace
} // namespace test
} // namespace emboss
2 changes: 1 addition & 1 deletion compiler/util/parser_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"""Types related to the LR(1) parser.

This module contains types used by the LR(1) parser, which are also used in
other parts of the compiler:
other parts of the compiler:

SourcePosition: a position (zero-width) within a source file.
SourceLocation: a span within a source file.
Expand Down
101 changes: 93 additions & 8 deletions runtime/cpp/emboss_text_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
// 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_
#ifndef THIRD_PARTY_EMBOSS_RUNTIME_CPP_EMBOSS_TEXT_UTIL_H_
#define THIRD_PARTY_EMBOSS_RUNTIME_CPP_EMBOSS_TEXT_UTIL_H_

#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <cstdint>
Expand All @@ -25,12 +26,25 @@
#include <limits>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

#include "runtime/cpp/emboss_defines.h"

namespace emboss {

// Controls how large integers are serialized in JSON output. JSON numbers are
// 64-bit floating-point, so integers outside the range [-2^53, 2^53] may lose
// precision when parsed by standard JSON parsers.
enum class JsonLargeIntegerHandling {
// Serialize all integers as numbers (default, but may lose precision for
// large values).
kAsNumber,
// Serialize integers larger than 32 bits as quoted strings.
kLargeAsString,
};

// TextOutputOptions are used to configure text output. Typically, one can just
// use a default TextOutputOptions() (for compact output) or MultilineText()
// (for reasonable formatted output).
Expand Down Expand Up @@ -80,13 +94,30 @@ class TextOutputOptions final {
return result;
}

TextOutputOptions Json(bool new_value) const {
TextOutputOptions result = *this;
result.json_ = new_value;
return result;
}

TextOutputOptions WithJsonLargeIntegerHandling(
JsonLargeIntegerHandling new_value) const {
TextOutputOptions result = *this;
result.json_large_integer_handling_ = 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 digit_grouping() const { return digit_grouping_ && !json_; }
bool comments() const { return comments_ && !json_; }
::std::uint8_t numeric_base() const { return json_ ? 10 : numeric_base_; }
bool json() const { return json_; }
bool allow_partial_output() const { return allow_partial_output_; }
JsonLargeIntegerHandling json_large_integer_handling() const {
return json_large_integer_handling_;
}

private:
::std::string indent_;
Expand All @@ -95,7 +126,10 @@ class TextOutputOptions final {
bool multiline_ = false;
bool digit_grouping_ = false;
bool allow_partial_output_ = false;
bool json_ = false;
::std::uint8_t numeric_base_ = 10;
JsonLargeIntegerHandling json_large_integer_handling_ =
JsonLargeIntegerHandling::kAsNumber;
};

namespace support {
Expand Down Expand Up @@ -337,8 +371,21 @@ void WriteIntegerToTextStream(IntegralType value, Stream *stream,
template <class Stream, class View>
void WriteIntegerViewToTextStream(View *view, Stream *stream,
const TextOutputOptions &options) {
// In JSON mode with kLargeAsString, serialize integers larger than 32 bits
// as quoted strings to avoid precision loss in JSON parsers.
bool quote_value =
options.json() &&
options.json_large_integer_handling() ==
JsonLargeIntegerHandling::kLargeAsString &&
sizeof(typename View::ValueType) > 4;
if (quote_value) {
stream->Write("\"");
}
WriteIntegerToTextStream(view->Read(), stream, options.numeric_base(),
options.digit_grouping());
if (quote_value) {
stream->Write("\"");
}
if (options.comments()) {
stream->Write(" # ");
WriteIntegerToTextStream(view->Read(), stream,
Expand Down Expand Up @@ -562,6 +609,10 @@ void WriteFloatToTextStream(Float n, Stream *stream,
// currently available.

if (::std::isnan(n)) {
if (options.json()) {
stream->Write("\"NaN\"");
return;
}
// 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;
Expand All @@ -585,6 +636,14 @@ void WriteFloatToTextStream(Float n, Stream *stream,
}

if (::std::isinf(n)) {
if (options.json()) {
if (n < 0.0) {
stream->Write("\"-Infinity\"");
} else {
stream->Write("\"Infinity\"");
}
return;
}
if (n < 0.0) {
stream->Write("-Inf");
} else {
Expand Down Expand Up @@ -636,13 +695,17 @@ void WriteEnumViewToTextStream(View *view, Stream *stream,
const TextOutputOptions &options) {
const char *name = TryToGetNameFromEnum(view->Read());
if (name != nullptr) {
if (options.json()) stream->Write("\"");
stream->Write(name);
if (options.json()) stream->Write("\"");
}
// 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(" # ");
if (name != nullptr) {
stream->Write(" # ");
}
WriteIntegerToTextStream(
static_cast<
typename ::std::underlying_type<typename View::ValueType>::type>(
Expand Down Expand Up @@ -756,7 +819,29 @@ template <class Array, class Stream>
void WriteArrayToTextStream(Array *array, Stream *stream,
const TextOutputOptions &options) {
TextOutputOptions element_options = options.PlusOneIndent();
if (options.multiline()) {
if (options.json()) {
stream->Write("[");
bool first = true;
for (::std::size_t i = 0; i < array->ElementCount(); ++i) {
if (!options.allow_partial_output() || (*array)[i].IsAggregate() ||
(*array)[i].Ok()) {
if (!first) {
stream->Write(",");
}
if (options.multiline()) {
stream->Write("\n");
stream->Write(element_options.current_indent());
}
(*array)[i].WriteToTextStream(stream, element_options);
first = false;
}
}
if (options.multiline()) {
stream->Write("\n");
stream->Write(options.current_indent());
}
stream->Write("]");
} else if (options.multiline()) {
stream->Write("{");
WriteShorthandArrayCommentToTextStream(array, stream, element_options);
for (::std::size_t i = 0; i < array->ElementCount(); ++i) {
Expand Down Expand Up @@ -896,4 +981,4 @@ inline ::std::string WriteToString(const EmbossViewType &view) {

} // namespace emboss

#endif // EMBOSS_RUNTIME_CPP_EMBOSS_TEXT_UTIL_H_
#endif // THIRD_PARTY_EMBOSS_RUNTIME_CPP_EMBOSS_TEXT_UTIL_H_
Loading
Loading