Add JSON output support to Emboss text format.
diff --git a/compiler/back_end/cpp/generated_code_templates b/compiler/back_end/cpp/generated_code_templates
index e8f7c46..1810573 100644
--- a/compiler/back_end/cpp/generated_code_templates
+++ b/compiler/back_end/cpp/generated_code_templates
@@ -1,17 +1,3 @@
-// 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.
-
// -*- mode: C++ -*-
// vim: set filetype=cpp:
@@ -29,14 +15,14 @@
*/
#ifndef ${header_guard}
#define ${header_guard}
-#include <stdint.h>
-#include <string.h>
+// #include <stdint.h>
+// #include <string.h>
-#include <algorithm>
-#include <type_traits>
-#include <utility>
+// #include <algorithm>
+// #include <type_traits>
+// #include <utility>
-#include "runtime/cpp/emboss_cpp_util.h"
+#include "third_party/emboss/runtime/cpp/emboss_cpp_util.h"
${includes}
@@ -283,8 +269,14 @@
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.json()) {
+ emboss_reserved_local_field_options =
+ emboss_reserved_local_field_options.WithComments(false)
+ .WithDigitGrouping(false)
+ .WithNumericBase(10);
+ }
if (emboss_reserved_local_options.multiline()) {
emboss_reserved_local_stream->Write("{\n");
} else {
@@ -295,11 +287,18 @@
// 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()) {
+ 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("}");
}
}
@@ -323,20 +322,31 @@
// 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(",");
+ if (!emboss_reserved_local_field_options.json() ||
+ emboss_reserved_local_wrote_field) {
+ emboss_reserved_local_stream->Write(" ");
}
- 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() &&
diff --git a/compiler/back_end/cpp/testcode/text_format_test.cc b/compiler/back_end/cpp/testcode/text_format_test.cc
index 82559cd..19f7ebd 100644
--- a/compiler/back_end/cpp/testcode/text_format_test.cc
+++ b/compiler/back_end/cpp/testcode/text_format_test.cc
@@ -15,12 +15,12 @@
// 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 "testdata/text_format.emb.h"
+#include "testing/base/public/gunit.h"
+#include "third_party/emboss/runtime/cpp/emboss_text_util.h"
+#include "third_party/emboss/testdata/text_format.emb.h"
namespace emboss {
namespace test {
@@ -93,6 +93,89 @@
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)));
+}
+
} // namespace
} // namespace test
} // namespace emboss
diff --git a/compiler/util/parser_types.py b/compiler/util/parser_types.py
index a3ef34d..89cb186 100644
--- a/compiler/util/parser_types.py
+++ b/compiler/util/parser_types.py
@@ -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.
diff --git a/runtime/cpp/emboss_text_util.h b/runtime/cpp/emboss_text_util.h
index 9cfe03d..30f5dfd 100644
--- a/runtime/cpp/emboss_text_util.h
+++ b/runtime/cpp/emboss_text_util.h
@@ -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>
@@ -25,9 +26,11 @@
#include <limits>
#include <sstream>
#include <string>
+#include <type_traits>
+#include <utility>
#include <vector>
-#include "runtime/cpp/emboss_defines.h"
+#include "third_party/emboss/runtime/cpp/emboss_defines.h"
namespace emboss {
@@ -80,12 +83,19 @@
return result;
}
+ TextOutputOptions Json(bool new_value) const {
+ TextOutputOptions result = *this;
+ result.json_ = 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 json() const { return json_; }
bool allow_partial_output() const { return allow_partial_output_; }
private:
@@ -95,6 +105,7 @@
bool multiline_ = false;
bool digit_grouping_ = false;
bool allow_partial_output_ = false;
+ bool json_ = false;
::std::uint8_t numeric_base_ = 10;
};
@@ -337,13 +348,17 @@
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,
+ if (options.json()) {
+ WriteIntegerToTextStream(view->Read(), stream, 10, false);
+ } else {
+ 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());
+ }
}
}
@@ -562,6 +577,10 @@
// 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;
@@ -585,6 +604,14 @@
}
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 {
@@ -636,18 +663,23 @@
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 || (options.comments() && !options.json())) {
+ 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());
+ stream, options.json() ? 10 : options.numeric_base(),
+ options.json() ? false : options.digit_grouping());
}
}
@@ -756,7 +788,34 @@
void WriteArrayToTextStream(Array *array, Stream *stream,
const TextOutputOptions &options) {
TextOutputOptions element_options = options.PlusOneIndent();
- if (options.multiline()) {
+ if (options.json()) {
+ element_options = element_options.WithComments(false)
+ .WithDigitGrouping(false)
+ .WithNumericBase(10);
+ 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());
+ } else if (!first) {
+ stream->Write(" ");
+ }
+ (*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) {
@@ -896,4 +955,4 @@
} // namespace emboss
-#endif // EMBOSS_RUNTIME_CPP_EMBOSS_TEXT_UTIL_H_
+#endif // THIRD_PARTY_EMBOSS_RUNTIME_CPP_EMBOSS_TEXT_UTIL_H_
diff --git a/runtime/cpp/test/emboss_array_view_test.cc b/runtime/cpp/test/emboss_array_view_test.cc
index 1f632e8..15cb9eb 100644
--- a/runtime/cpp/test/emboss_array_view_test.cc
+++ b/runtime/cpp/test/emboss_array_view_test.cc
@@ -12,15 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "runtime/cpp/emboss_array_view.h"
+#include "third_party/emboss/runtime/cpp/emboss_array_view.h"
+#include <cstddef>
+#include <cstdint>
#include <string>
-#include <type_traits>
-#include "absl/strings/str_format.h"
-#include "gtest/gtest.h"
-#include "runtime/cpp/emboss_prelude.h"
-#include "runtime/cpp/emboss_text_util.h"
+#include "testing/base/public/gunit.h"
+#include "third_party/absl/strings/str_format.h"
+#include "third_party/emboss/runtime/cpp/emboss_memory_util.h"
+#include "third_party/emboss/runtime/cpp/emboss_prelude.h"
+#include "third_party/emboss/runtime/cpp/emboss_text_util.h"
+#include "third_party/emboss/runtime/cpp/emboss_view_parameters.h"
namespace emboss {
namespace support {
@@ -215,6 +218,18 @@
"0x8, 0xd, 0x15, 0x22, 0x37, 0x59 }");
}
+TEST(ArrayView, TextFormatOutput_AsJson) {
+ signed char bytes[16] = {-3, 2, -1, 1, 0, 1, 1, 2,
+ 3, 5, 8, 13, 21, 34, 55, 89};
+ auto buffer = ReadWriteContiguousBuffer{
+ reinterpret_cast</**/ ::std::uint8_t*>(bytes), sizeof bytes};
+ auto byte_array =
+ ArrayView<FixedIntView<8>, ReadWriteContiguousBuffer, 1>{buffer};
+
+ EXPECT_EQ("[-3, 2, -1, 1, 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]",
+ WriteToString(byte_array, TextOutputOptions().Json(true)));
+}
+
TEST(ArrayView, TextFormatOutput_8BitIntElementTypes) {
::std::uint8_t bytes[1] = {65};
auto buffer = ReadWriteContiguousBuffer{bytes, sizeof bytes};
diff --git a/testdata/text_format.emb b/testdata/text_format.emb
index 7458170..a8fb026 100644
--- a/testdata/text_format.emb
+++ b/testdata/text_format.emb
@@ -14,6 +14,7 @@
-- Structures used specifically to test text format input and output.
+[$default byte_order: "LittleEndian"]
[(cpp) namespace: "emboss::test"]
@@ -34,3 +35,31 @@
2 [+2] Vanilla b
[text_output: "Skip"]
4 [+2] Vanilla c
+
+enum JsonTestEnum:
+ ZERO = 0
+ ONE = 1
+ TWO = 2
+ THREE = 3
+ FOUR = 4
+
+struct JsonTestArrayStruct:
+ 0 [+1] UInt element_one
+ 1 [+1] UInt element_two
+ 2 [+1] UInt element_three
+ 3 [+1] UInt element_four
+
+
+struct JsonTestStruct:
+ 0 [+1] JsonTestEnum one_byte_enum
+ 1 [+1] bits:
+ 0 [+7] UInt seven_bit_uint
+ 7 [+1] Flag one_bit_flag
+
+ 2 [+1] UInt one_byte_uint
+ 3 [+2] UInt two_byte_uint
+ 5 [+4] UInt four_byte_uint
+ 9 [+8] UInt eight_byte_uint
+ 17 [+10] UInt:8[] uint8_array
+ 27 [+20] UInt:16[] uint16_array
+ 47 [+40] JsonTestArrayStruct[] struct_array