Add a new option to allow partial test output for non-Ok() messages.
Adds a new `WithAllowPartialOutput()` method to `TextOutputOptions` to
allow `WriteToString()` to write partial output when called with a
non-`Ok()` view.
diff --git a/compiler/back_end/cpp/generated_code_templates b/compiler/back_end/cpp/generated_code_templates
index 804544d..83a369d 100644
--- a/compiler/back_end/cpp/generated_code_templates
+++ b/compiler/back_end/cpp/generated_code_templates
@@ -225,6 +225,8 @@
}
}
+ static constexpr bool IsAggregate() { return true; }
+
$_field_method_declarations_$
private:
@@ -305,40 +307,61 @@
continue;
}
-
// ** write_field_to_text_stream ** ////////////////////////////////////////////
if (has_$_field_name_$().ValueOr(false)) {
- 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(",");
+ // Don't try to read the field if `allow_partial_output` is set and the
+ // field can't be `Read()`. Aggregates should still be visited, even if
+ // 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_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(",");
+ }
+ emboss_reserved_local_stream->Write(" ");
}
- emboss_reserved_local_stream->Write(" ");
- }
- 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()) {
- emboss_reserved_local_stream->Write("\n");
+ 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()) {
+ emboss_reserved_local_stream->Write("\n");
+ }
+ } else if (emboss_reserved_local_field_options.allow_partial_output() &&
+ emboss_reserved_local_field_options.comments() &&
+ !$_field_name_$().IsAggregate() && !$_field_name_$().Ok()) {
+ if (emboss_reserved_local_field_options.multiline()) {
+ emboss_reserved_local_stream->Write(
+ emboss_reserved_local_field_options.current_indent());
+ }
+ emboss_reserved_local_stream->Write("# $_field_name_$: UNREADABLE\n");
}
}
-
// ** write_read_only_field_to_text_stream ** //////////////////////////////////
if (has_$_field_name_$().ValueOr(false) &&
emboss_reserved_local_field_options.comments()) {
- emboss_reserved_local_stream->Write(
- emboss_reserved_local_field_options.current_indent());
- // TODO(bolms): When there are multiline read-only fields, add an option
- // to TextOutputOptions to add `# ` to the current indent and use it here,
- // so that subsequent lines are also commented out.
- emboss_reserved_local_stream->Write("# $_field_name_$: ");
- $_field_name_$().WriteToTextStream(emboss_reserved_local_stream,
- emboss_reserved_local_field_options);
- emboss_reserved_local_stream->Write("\n");
+ if (!emboss_reserved_local_field_options.allow_partial_output() ||
+ $_field_name_$().IsAggregate() || $_field_name_$().Ok()) {
+ emboss_reserved_local_stream->Write(
+ emboss_reserved_local_field_options.current_indent());
+ // TODO(bolms): When there are multiline read-only fields, add an option
+ // to TextOutputOptions to add `# ` to the current indent and use it
+ // here, so that subsequent lines are also commented out.
+ emboss_reserved_local_stream->Write("# $_field_name_$: ");
+ $_field_name_$().WriteToTextStream(emboss_reserved_local_stream,
+ emboss_reserved_local_field_options);
+ emboss_reserved_local_stream->Write("\n");
+ } else {
+ if (emboss_reserved_local_field_options.multiline()) {
+ emboss_reserved_local_stream->Write(
+ emboss_reserved_local_field_options.current_indent());
+ }
+ emboss_reserved_local_stream->Write("# $_field_name_$: UNREADABLE\n");
+ }
}
// ** constant_structure_size_method ** ////////////////////////////////////////
@@ -520,6 +543,8 @@
::emboss::support::$_write_to_text_stream_function_$(
this, emboss_reserved_local_stream, emboss_reserved_local_options);
}
+
+ static constexpr bool IsAggregate() { return false; }
};
static constexpr $_virtual_view_type_name_$ $_name_$() {
@@ -622,6 +647,8 @@
this, emboss_reserved_local_stream, emboss_reserved_local_options);
}
+ static constexpr bool IsAggregate() { return false; }
+
$_write_methods_$
private:
diff --git a/compiler/back_end/cpp/testcode/condition_test.cc b/compiler/back_end/cpp/testcode/condition_test.cc
index 029abae..588a6f7 100644
--- a/compiler/back_end/cpp/testcode/condition_test.cc
+++ b/compiler/back_end/cpp/testcode/condition_test.cc
@@ -759,6 +759,44 @@
EXPECT_EQ("{ x: 1 }", ::emboss::WriteToString(reader));
}
+TEST(WriteToString, NotOkFieldsAreNotWritten) {
+ ::std::uint8_t buffer[2] = {0x00, 0x00};
+ auto reader = BasicConditionalWriter(buffer, 1U);
+ EXPECT_FALSE(reader.Ok());
+ EXPECT_EQ(
+ "{\n"
+ " x: 0 # 0x0\n"
+ " # xc: UNREADABLE\n"
+ "}",
+ ::emboss::WriteToString(
+ reader, ::emboss::MultilineText().WithAllowPartialOutput(true)));
+ EXPECT_EQ(
+ "{ x: 0 }",
+ ::emboss::WriteToString(
+ reader, ::emboss::TextOutputOptions().WithAllowPartialOutput(true)));
+}
+
+TEST(WriteToString, NotOkStructSubFieldsAreNotWritten) {
+ ::std::uint8_t buffer[2] = {0x00, 0x00};
+ auto reader = ConditionalInlineWriter(buffer, 2U);
+ EXPECT_FALSE(reader.Ok());
+ EXPECT_EQ(
+ "{\n"
+ " payload_id: 0 # 0x0\n"
+ " type_0: {\n"
+ " a: 0 # 0x0\n"
+ " # b: UNREADABLE\n"
+ " # c: UNREADABLE\n"
+ " }\n"
+ "}",
+ ::emboss::WriteToString(
+ reader, ::emboss::MultilineText().WithAllowPartialOutput(true)));
+ EXPECT_EQ(
+ "{ payload_id: 0, type_0: { a: 0 } }",
+ ::emboss::WriteToString(
+ reader, ::emboss::TextOutputOptions().WithAllowPartialOutput(true)));
+}
+
TEST(WriteToString, PresentFieldsNotWritten) {
::std::uint8_t buffer[2] = {0x00, 0x01};
auto reader = BasicConditionalWriter(buffer, 2U);
diff --git a/compiler/back_end/cpp/testcode/requires_test.cc b/compiler/back_end/cpp/testcode/requires_test.cc
index 661fe6a..6a21efc 100644
--- a/compiler/back_end/cpp/testcode/requires_test.cc
+++ b/compiler/back_end/cpp/testcode/requires_test.cc
@@ -201,6 +201,56 @@
EXPECT_FALSE(view.b_true().CouldWriteValue(false));
}
+TEST(WriteToString, NotOkFieldsAreNotWritten) {
+ ::std::array</**/ ::std::uint8_t, 3> buffer = {0x00, 0x00, 0x00};
+ auto reader = MakeRequiresIntegersView(&buffer);
+ EXPECT_FALSE(reader.Ok());
+ EXPECT_EQ(
+ "{\n"
+ " zero_through_nine: 0 # 0x0\n"
+ " # ten_through_twenty: UNREADABLE\n"
+ " disjoint: 0 # 0x0\n"
+ " # ztn_plus_ttt: UNREADABLE\n"
+ " # alias_of_zero_through_nine: UNREADABLE\n"
+ " zero_through_nine_plus_five: 5 # 0x5\n"
+ "}",
+ ::emboss::WriteToString(
+ reader, ::emboss::MultilineText().WithAllowPartialOutput(true)));
+ EXPECT_EQ(
+ "{ zero_through_nine: 0, disjoint: 0, zero_through_nine_plus_five: 5 }",
+ ::emboss::WriteToString(
+ reader, ::emboss::TextOutputOptions().WithAllowPartialOutput(true)));
+}
+
+TEST(WriteToString, NotOkArrayElementsAreNotWritten) {
+ ::std::array</**/ ::std::uint8_t, 4> buffer = {0x20, 0x00, 0x30, 0x05};
+ auto reader = MakeRequiresInArrayElementsView(&buffer);
+ EXPECT_FALSE(reader.Ok());
+ EXPECT_EQ(
+ "{\n"
+ " xs: {\n"
+ " [0]: {\n"
+ " # x: UNREADABLE\n"
+ " }\n"
+ " [1]: {\n"
+ " x: 0 # 0x0\n"
+ " }\n"
+ " [2]: {\n"
+ " # x: UNREADABLE\n"
+ " }\n"
+ " [3]: {\n"
+ " x: 5 # 0x5\n"
+ " }\n"
+ " }\n"
+ "}",
+ ::emboss::WriteToString(
+ reader, ::emboss::MultilineText().WithAllowPartialOutput(true)));
+ EXPECT_EQ(
+ "{ xs: { [0]: { }, { x: 0 }, { }, { x: 5 } } }",
+ ::emboss::WriteToString(
+ reader, ::emboss::TextOutputOptions().WithAllowPartialOutput(true)));
+}
+
} // namespace
} // namespace test
} // namespace emboss
diff --git a/doc/cpp-reference.md b/doc/cpp-reference.md
index 46ecdd0..779d777 100644
--- a/doc/cpp-reference.md
+++ b/doc/cpp-reference.md
@@ -1902,7 +1902,9 @@
`PlusOneIndent` returns a new `TextOutputOptions` with one more level of
indentation than the current `TextOutputOptions`. This is primarily intended for
use inside of `WriteToTextStream` methods, as a way to get an indented
-`TextOutputOptions` to pass to the `WriteToTextStream` methods of child objects.
+`TextOutputOptions` to pass to the `WriteToTextStream` methods of child
+objects. However, application callers may use `PlusOneIndent()`, possibly
+multiple times, to indent the entire output.
### `Multiline` method
@@ -1950,10 +1952,19 @@
`TextOutputOptions`, except for a new value for `digit_grouping()`. The new
numeric base should be 2, 10, or 16.
+### `WithAllowPartialOutput` method
+
+```c++
+TextOutputOptions WithAllowPartialOutput(bool new_value) const;
+```
+
+Returns a new `TextOutputOptions` with the same options as the current
+`TextOutputOptions`, except for a new value for `allow_partial_output()`.
+
### `current_indent` method
```c++
-::std::string current_indent() const;
+::std::string current_indent() const; // Default "".
```
Returns the current indent string.
@@ -1961,16 +1972,16 @@
### `indent` method
```c++
-::std::string indent() const;
+::std::string indent() const; // Default " ".
```
-Returns the indent string. The indent string is the string used for a single
-level of indentation; most callers will prefer `current_indent`.
+Returns the indent string. The indent string is the string used for a *single*
+level of indentation.
### `multiline` method
```c++
-bool multiline() const;
+bool multiline() const; // Default false.
```
Returns `true` if text output should use multiple lines, or `false` if text
@@ -1979,7 +1990,7 @@
### `digit_grouping` method
```c++
-bool digit_grouping() const;
+bool digit_grouping() const; // Default false.
```
Returns `true` if text output should include digit separators on numbers; i.e.
@@ -1988,7 +1999,7 @@
### `comments` method
```c++
-bool comments() const;
+bool comments() const; // Default false.
```
Returns `true` if text output should include comments, e.g., to show numbers in
@@ -1997,8 +2008,25 @@
### `numeric_base` method
```c++
-uint8_t numeric_base() const;
+uint8_t numeric_base() const; // Default 10.
```
Returns the numeric base that should be used for formatting numbers. This should
always be 2, 10, or 16.
+
+### `allow_partial_output` method
+
+```c++
+bool allow_partial_output() const; // Default false.
+```
+
+Returns `true` if text output should attempt to extract fields from a view that
+is not `Ok()`. If so:
+
+* `WriteToString()` or `WriteToTextStream()` should never `CHECK`-fail.
+* Atomic fields (e.g., `Int`, `UInt`, `enum`, `Flag`, etc. types) will not be
+ written to the text stream if they cannot be read.
+* If `comments()` is also `true`, unreadable atomic fields will be commented
+ in the text stream.
+* Aggregate fields (`struct`, `bits`, or arrays) will be written, but may be
+ missing fields or entirely empty if they have non-`Ok()` members.
diff --git a/runtime/cpp/emboss_array_view.h b/runtime/cpp/emboss_array_view.h
index e222436..a1544c6 100644
--- a/runtime/cpp/emboss_array_view.h
+++ b/runtime/cpp/emboss_array_view.h
@@ -248,6 +248,8 @@
WriteArrayToTextStream(this, stream, options);
}
+ static constexpr bool IsAggregate() { return true; }
+
BufferType BackingStorage() const { return buffer_; }
// Forwards to BufferType's ToString(), if any, but only if ElementView is a
diff --git a/runtime/cpp/emboss_enum_view.h b/runtime/cpp/emboss_enum_view.h
index f59cd6d..af4d7aa 100644
--- a/runtime/cpp/emboss_enum_view.h
+++ b/runtime/cpp/emboss_enum_view.h
@@ -151,6 +151,8 @@
::emboss::support::WriteEnumViewToTextStream(this, stream, options);
}
+ static constexpr bool IsAggregate() { return false; }
+
static constexpr int SizeInBits() { return Parameters::kBits; }
private:
diff --git a/runtime/cpp/emboss_prelude.h b/runtime/cpp/emboss_prelude.h
index 263b802..463f0fa 100644
--- a/runtime/cpp/emboss_prelude.h
+++ b/runtime/cpp/emboss_prelude.h
@@ -118,6 +118,8 @@
::emboss::support::WriteBooleanViewToTextStream(this, stream, options);
}
+ static constexpr bool IsAggregate() { return false; }
+
private:
BitBlock bit_block_;
};
@@ -297,6 +299,8 @@
support::WriteIntegerViewToTextStream(this, stream, options);
}
+ static constexpr bool IsAggregate() { return false; }
+
static constexpr int SizeInBits() { return Parameters::kBits; }
private:
@@ -460,6 +464,8 @@
support::WriteIntegerViewToTextStream(this, stream, options);
}
+ static constexpr bool IsAggregate() { return false; }
+
static constexpr int SizeInBits() { return Parameters::kBits; }
private:
@@ -649,6 +655,8 @@
support::WriteIntegerViewToTextStream(this, stream, options);
}
+ static constexpr bool IsAggregate() { return false; }
+
template <typename OtherView>
void CopyFrom(const OtherView &other) const {
Write(other.Read());
@@ -789,6 +797,8 @@
support::WriteFloatToTextStream(Read(), stream, options);
}
+ static constexpr bool IsAggregate() { return false; }
+
static constexpr int SizeInBits() { return Parameters::kBits; }
private:
diff --git a/runtime/cpp/emboss_text_util.h b/runtime/cpp/emboss_text_util.h
index 8640851..59bdc4e 100644
--- a/runtime/cpp/emboss_text_util.h
+++ b/runtime/cpp/emboss_text_util.h
@@ -73,12 +73,19 @@
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_;
@@ -86,6 +93,7 @@
bool comments_ = false;
bool multiline_ = false;
bool digit_grouping_ = false;
+ bool allow_partial_output_ = false;
::std::uint8_t numeric_base_ = 10;
};
@@ -691,36 +699,63 @@
stream->Write("{");
WriteShorthandArrayCommentToTextStream(array, stream, element_options);
for (::std::size_t i = 0; i < array->ElementCount(); ++i) {
- 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);
+ 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) {
- stream->Write(" ");
- if (i % 8 == 0) {
- 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(",");
+ 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(" }");
diff --git a/runtime/cpp/test/emboss_text_util_test.cc b/runtime/cpp/test/emboss_text_util_test.cc
index b7af4b8..7541576 100644
--- a/runtime/cpp/test/emboss_text_util_test.cc
+++ b/runtime/cpp/test/emboss_text_util_test.cc
@@ -420,6 +420,13 @@
EXPECT_EQ(2, new_options.numeric_base());
}
+TEST(TextOutputOptions, WithAllowPartialOutput) {
+ TextOutputOptions options;
+ TextOutputOptions new_options = options.WithAllowPartialOutput(true);
+ EXPECT_FALSE(options.allow_partial_output());
+ EXPECT_TRUE(new_options.allow_partial_output());
+}
+
// Small helper function for the various WriteIntegerToTextStream tests; just
// sets up a stream, forwards its arguments to WriteIntegerToTextStream, and
// then returns the text from the stream.
diff --git a/testdata/requires.emb b/testdata/requires.emb
index b37f61a..3317a98 100644
--- a/testdata/requires.emb
+++ b/testdata/requires.emb
@@ -85,3 +85,10 @@
if b_exists:
2 [+1] Flag b_true
[requires: this]
+
+
+struct RequiresInArrayElements:
+ struct Element:
+ 0 [+1] UInt:8 x [requires: 0 <= this <= 10]
+
+ 0 [+4] Element[] xs