Merge pull request #134 from EricRahm/enum_trait
Split out enum trait generation
diff --git a/build_defs.bzl b/build_defs.bzl
index 14e6045..0c1e6a8 100644
--- a/build_defs.bzl
+++ b/build_defs.bzl
@@ -26,7 +26,7 @@
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
-def emboss_cc_library(name, srcs, deps = [], visibility = None, import_dirs = []):
+def emboss_cc_library(name, srcs, deps = [], visibility = None, import_dirs = [], enable_enum_traits = True):
"""Constructs a C++ library from an .emb file."""
if len(srcs) != 1:
fail(
@@ -45,6 +45,7 @@
name = name,
deps = [":" + name + "_ir"],
visibility = visibility,
+ enable_enum_traits = enable_enum_traits,
)
# Full Starlark rules for emboss_library and cc_emboss_library.
@@ -174,6 +175,8 @@
args.add_all(emboss_info.direct_ir)
args.add("--output-file")
args.add_all(headers)
+ if not ctx.attr.enable_enum_traits:
+ args.add("--no-cc-enum-traits")
ctx.actions.run(
executable = emboss_cc_compiler,
arguments = [args],
@@ -222,6 +225,9 @@
"_emboss_cc_runtime": attr.label(
default = "@com_google_emboss//runtime/cpp:cpp_utils",
),
+ "enable_enum_traits": attr.bool(
+ default = True,
+ ),
},
toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
)
@@ -244,6 +250,9 @@
allow_rules = ["emboss_library"],
allow_files = False,
),
+ "enable_enum_traits": attr.bool(
+ default = True,
+ ),
},
provides = [CcInfo, EmbossInfo],
)
diff --git a/compiler/back_end/cpp/BUILD b/compiler/back_end/cpp/BUILD
index e9ae268..1c496d6 100644
--- a/compiler/back_end/cpp/BUILD
+++ b/compiler/back_end/cpp/BUILD
@@ -225,6 +225,17 @@
)
emboss_cc_test(
+ name = "no_enum_traits_test",
+ srcs = [
+ "testcode/no_enum_traits_test.cc",
+ ],
+ deps = [
+ "//testdata:no_enum_traits_emboss",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+emboss_cc_test(
name = "start_size_range_test",
srcs = [
"testcode/start_size_range_test.cc",
diff --git a/compiler/back_end/cpp/emboss_codegen_cpp.py b/compiler/back_end/cpp/emboss_codegen_cpp.py
index ed5e66e..4ac71f0 100644
--- a/compiler/back_end/cpp/emboss_codegen_cpp.py
+++ b/compiler/back_end/cpp/emboss_codegen_cpp.py
@@ -45,6 +45,11 @@
choices=["always", "never", "if_tty", "auto"],
help="Print error messages using color. 'auto' is a "
"synonym for 'if_tty'.")
+ parser.add_argument("--cc-enum-traits",
+ action=argparse.BooleanOptionalAction,
+ default=True,
+ help="""Controls generation of EnumTraits by the C++
+ backend""")
return parser.parse_args(argv[1:])
@@ -58,17 +63,18 @@
os.isatty(sys.stderr.fileno())))
print(error.format_errors(errors, source_codes, use_color), file=sys.stderr)
-def generate_headers_and_log_errors(ir, color_output):
+def generate_headers_and_log_errors(ir, color_output, config: header_generator.Config):
"""Generates a C++ header and logs any errors.
Arguments:
ir: EmbossIr of the module.
color_output: "always", "never", "if_tty", "auto"
+ config: Header generation configuration.
Returns:
A tuple of (header, errors)
"""
- header, errors = header_generator.generate_header(ir)
+ header, errors = header_generator.generate_header(ir, config)
if errors:
_show_errors(errors, ir, color_output)
return (header, errors)
@@ -79,7 +85,8 @@
ir = ir_pb2.EmbossIr.from_json(f.read())
else:
ir = ir_pb2.EmbossIr.from_json(sys.stdin.read())
- header, errors = generate_headers_and_log_errors(ir, flags.color_output)
+ config = header_generator.Config(include_enum_traits=flags.cc_enum_traits)
+ header, errors = generate_headers_and_log_errors(ir, flags.color_output, config)
if errors:
return 1
if flags.output_file:
diff --git a/compiler/back_end/cpp/generated_code_templates b/compiler/back_end/cpp/generated_code_templates
index df9ecac..a0acc68 100644
--- a/compiler/back_end/cpp/generated_code_templates
+++ b/compiler/back_end/cpp/generated_code_templates
@@ -179,57 +179,7 @@
emboss_reserved_local_other.IntrinsicSizeIn${units}().Read());
}
- template <class Stream>
- bool UpdateFromTextStream(Stream *emboss_reserved_local_stream) const {
- ::std::string emboss_reserved_local_brace;
- if (!::emboss::support::ReadToken(emboss_reserved_local_stream,
- &emboss_reserved_local_brace))
- return false;
- if (emboss_reserved_local_brace != "{") return false;
- for (;;) {
- ::std::string emboss_reserved_local_name;
- if (!::emboss::support::ReadToken(emboss_reserved_local_stream,
- &emboss_reserved_local_name))
- return false;
- if (emboss_reserved_local_name == ",")
- if (!::emboss::support::ReadToken(emboss_reserved_local_stream,
- &emboss_reserved_local_name))
- return false;
- if (emboss_reserved_local_name == "}") return true;
- ::std::string emboss_reserved_local_colon;
- if (!::emboss::support::ReadToken(emboss_reserved_local_stream,
- &emboss_reserved_local_colon))
- return false;
- if (emboss_reserved_local_colon != ":") return false;
-${decode_fields}
- // decode_fields will `continue` if it successfully finds a field.
- return false;
- }
- }
-
- template <class Stream>
- void WriteToTextStream(
- Stream *emboss_reserved_local_stream,
- ::emboss::TextOutputOptions emboss_reserved_local_options) const {
- ::emboss::TextOutputOptions emboss_reserved_local_field_options =
- emboss_reserved_local_options.PlusOneIndent();
- if (emboss_reserved_local_options.multiline()) {
- emboss_reserved_local_stream->Write("{\n");
- } else {
- emboss_reserved_local_stream->Write("{");
- }
- bool emboss_reserved_local_wrote_field = false;
-${write_fields}
- // Avoid unused variable warnings for empty structures:
- (void)emboss_reserved_local_wrote_field;
- if (emboss_reserved_local_options.multiline()) {
- emboss_reserved_local_stream->Write(
- emboss_reserved_local_options.current_indent());
- emboss_reserved_local_stream->Write("}");
- } else {
- emboss_reserved_local_stream->Write(" }");
- }
- }
+${text_stream_methods}
static constexpr bool IsAggregate() { return true; }
@@ -301,6 +251,60 @@
emboss_reserved_local_size);
}
+// ** struct_text_stream ** ////////////////////////////////////////////////////
+ template <class Stream>
+ bool UpdateFromTextStream(Stream *emboss_reserved_local_stream) const {
+ ::std::string emboss_reserved_local_brace;
+ if (!::emboss::support::ReadToken(emboss_reserved_local_stream,
+ &emboss_reserved_local_brace))
+ return false;
+ if (emboss_reserved_local_brace != "{") return false;
+ for (;;) {
+ ::std::string emboss_reserved_local_name;
+ if (!::emboss::support::ReadToken(emboss_reserved_local_stream,
+ &emboss_reserved_local_name))
+ return false;
+ if (emboss_reserved_local_name == ",")
+ if (!::emboss::support::ReadToken(emboss_reserved_local_stream,
+ &emboss_reserved_local_name))
+ return false;
+ if (emboss_reserved_local_name == "}") return true;
+ ::std::string emboss_reserved_local_colon;
+ if (!::emboss::support::ReadToken(emboss_reserved_local_stream,
+ &emboss_reserved_local_colon))
+ return false;
+ if (emboss_reserved_local_colon != ":") return false;
+${decode_fields}
+ // decode_fields will `continue` if it successfully finds a field.
+ return false;
+ }
+ }
+
+ template <class Stream>
+ void WriteToTextStream(
+ Stream *emboss_reserved_local_stream,
+ ::emboss::TextOutputOptions emboss_reserved_local_options) const {
+ ::emboss::TextOutputOptions emboss_reserved_local_field_options =
+ emboss_reserved_local_options.PlusOneIndent();
+ if (emboss_reserved_local_options.multiline()) {
+ emboss_reserved_local_stream->Write("{\n");
+ } else {
+ emboss_reserved_local_stream->Write("{");
+ }
+ bool emboss_reserved_local_wrote_field = false;
+${write_fields}
+ // Avoid unused variable warnings for empty structures:
+ (void)emboss_reserved_local_wrote_field;
+ if (emboss_reserved_local_options.multiline()) {
+ emboss_reserved_local_stream->Write(
+ emboss_reserved_local_options.current_indent());
+ emboss_reserved_local_stream->Write("}");
+ } else {
+ emboss_reserved_local_stream->Write(" }");
+ }
+ }
+
+
// ** decode_field ** //////////////////////////////////////////////////////////
// If the field name matches ${field_name}, handle it, otherwise fall
// through to the next field.
@@ -772,6 +776,7 @@
// "the output of this rule is an indeterminate number of files, all of which
// should be used as input to this other rule," which would be necessary to
// generate all the .cc files and then build and link them into a library.
+// ** enum_traits ** ///////////////////////////////////////////////////////////
template <class Enum>
class EnumTraits;
diff --git a/compiler/back_end/cpp/header_generator.py b/compiler/back_end/cpp/header_generator.py
index c671d6e..8b585ca 100644
--- a/compiler/back_end/cpp/header_generator.py
+++ b/compiler/back_end/cpp/header_generator.py
@@ -21,6 +21,7 @@
import collections
import pkgutil
import re
+from typing import NamedTuple
from compiler.back_end.cpp import attributes
from compiler.back_end.util import code_template
@@ -77,6 +78,8 @@
# TODO(bolms): This should be a command-line flag.
_PRELUDE_INCLUDE_FILE = "runtime/cpp/emboss_prelude.h"
+_ENUM_VIEW_INCLUDE_FILE = "runtime/cpp/emboss_enum_view.h"
+_TEXT_UTIL_INCLUDE_FILE = "runtime/cpp/emboss_text_util.h"
# Cases allowed in the `enum_case` attribute.
_SUPPORTED_ENUM_CASES = ("SHOUTY_CASE", "kCamelCase")
@@ -86,6 +89,13 @@
assert name_conversion.is_case_conversion_supported("SHOUTY_CASE", _enum_case)
+class Config(NamedTuple):
+ """Configuration for C++ header generation."""
+
+ include_enum_traits: bool = True
+ """Whether or not to include EnumTraits in the generated header."""
+
+
def _get_namespace_components(namespace):
"""Gets the components of a C++ namespace
@@ -124,7 +134,7 @@
return re.sub("['\"\\\\]", r"\\\0", string)
-def _get_includes(module):
+def _get_includes(module, config: Config):
"""Returns the appropriate #includes based on module's imports."""
includes = []
for import_ in module.foreign_import:
@@ -138,6 +148,13 @@
code_template.format_template(
_TEMPLATES.include,
file_name=_cpp_string_escape(_PRELUDE_INCLUDE_FILE)))
+ if config.include_enum_traits:
+ includes.extend(
+ [code_template.format_template(
+ _TEMPLATES.include,
+ file_name=_cpp_string_escape(file_name))
+ for file_name in (_ENUM_VIEW_INCLUDE_FILE, _TEXT_UTIL_INCLUDE_FILE)
+ ])
return "".join(includes)
@@ -1059,14 +1076,14 @@
return "", declaration, definition
-def _generate_subtype_definitions(type_ir, ir):
+def _generate_subtype_definitions(type_ir, ir, config: Config):
"""Generates C++ code for subtypes of type_ir."""
subtype_bodies = []
subtype_forward_declarations = []
subtype_method_definitions = []
type_name = type_ir.name.name.text
for subtype in type_ir.subtype:
- inner_defs = _generate_type_definition(subtype, ir)
+ inner_defs = _generate_type_definition(subtype, ir, config)
subtype_forward_declaration, subtype_body, subtype_methods = inner_defs
subtype_forward_declarations.append(subtype_forward_declaration)
subtype_bodies.append(subtype_body)
@@ -1096,7 +1113,7 @@
return name
-def _generate_structure_definition(type_ir, ir):
+def _generate_structure_definition(type_ir, ir, config: Config):
"""Generates C++ for an Emboss structure (struct or bits).
Arguments:
@@ -1108,7 +1125,7 @@
suitable for insertion into the appropriate places in the generated header.
"""
subtype_bodies, subtype_forward_declarations, subtype_method_definitions = (
- _generate_subtype_definitions(type_ir, ir))
+ _generate_subtype_definitions(type_ir, ir, config))
type_name = type_ir.name.name.text
field_helper_type_definitions = []
field_method_declarations = []
@@ -1221,6 +1238,15 @@
else:
requires_check = ""
+ if config.include_enum_traits:
+ text_stream_methods = code_template.format_template(
+ _TEMPLATES.struct_text_stream,
+ decode_fields="\n".join(decode_field_clauses),
+ write_fields="\n".join(write_field_clauses))
+ else:
+ text_stream_methods = ""
+
+
class_forward_declarations = code_template.format_template(
_TEMPLATES.structure_view_declaration,
name=type_name)
@@ -1234,9 +1260,8 @@
requires_check=requires_check,
equals_method_body="\n".join(equals_method_clauses),
unchecked_equals_method_body="\n".join(unchecked_equals_method_clauses),
- decode_fields="\n".join(decode_field_clauses),
enum_usings="\n".join(enum_using_statements),
- write_fields="\n".join(write_field_clauses),
+ text_stream_methods=text_stream_methods,
parameter_fields="\n".join(parameter_fields),
constructor_parameters="".join(constructor_parameters),
forwarded_parameters="".join(forwarded_parameters),
@@ -1314,7 +1339,7 @@
for case in cases]
-def _generate_enum_definition(type_ir):
+def _generate_enum_definition(type_ir, include_traits=True):
"""Generates C++ for an Emboss enum."""
enum_values = []
enum_from_string_statements = []
@@ -1333,48 +1358,52 @@
code_template.format_template(_TEMPLATES.enum_value,
name=enum_value_name,
value=_render_integer(numeric_value)))
-
- enum_from_string_statements.append(
- code_template.format_template(_TEMPLATES.enum_from_name_case,
- enum=type_ir.name.name.text,
- value=enum_value_name,
- name=value.name.name.text))
-
- if numeric_value not in previously_seen_numeric_values:
- string_from_enum_statements.append(
- code_template.format_template(_TEMPLATES.name_from_enum_case,
+ if include_traits:
+ enum_from_string_statements.append(
+ code_template.format_template(_TEMPLATES.enum_from_name_case,
enum=type_ir.name.name.text,
value=enum_value_name,
name=value.name.name.text))
- enum_is_known_statements.append(
- code_template.format_template(_TEMPLATES.enum_is_known_case,
- enum=type_ir.name.name.text,
- name=enum_value_name))
+ if numeric_value not in previously_seen_numeric_values:
+ string_from_enum_statements.append(
+ code_template.format_template(_TEMPLATES.name_from_enum_case,
+ enum=type_ir.name.name.text,
+ value=enum_value_name,
+ name=value.name.name.text))
+
+ enum_is_known_statements.append(
+ code_template.format_template(_TEMPLATES.enum_is_known_case,
+ enum=type_ir.name.name.text,
+ name=enum_value_name))
previously_seen_numeric_values.add(numeric_value)
- return (
- code_template.format_template(
+
+ declaration = code_template.format_template(
_TEMPLATES.enum_declaration,
enum=type_ir.name.name.text,
- enum_type=enum_type),
- code_template.format_template(
+ enum_type=enum_type)
+ definition = code_template.format_template(
_TEMPLATES.enum_definition,
enum=type_ir.name.name.text,
enum_type=enum_type,
- enum_values="".join(enum_values),
+ enum_values="".join(enum_values))
+ if include_traits:
+ definition += code_template.format_template(
+ _TEMPLATES.enum_traits,
+ enum=type_ir.name.name.text,
enum_from_name_cases="\n".join(enum_from_string_statements),
name_from_enum_cases="\n".join(string_from_enum_statements),
- enum_is_known_cases="\n".join(enum_is_known_statements)),
- ""
- )
+ enum_is_known_cases="\n".join(enum_is_known_statements))
+
+ return (declaration, definition, "")
-def _generate_type_definition(type_ir, ir):
+def _generate_type_definition(type_ir, ir, config: Config):
"""Generates C++ for an Emboss type."""
if type_ir.HasField("structure"):
- return _generate_structure_definition(type_ir, ir)
+ return _generate_structure_definition(type_ir, ir, config)
elif type_ir.HasField("enumeration"):
- return _generate_enum_definition(type_ir)
+ return _generate_enum_definition(type_ir, config.include_enum_traits)
elif type_ir.HasField("external"):
# TODO(bolms): This should probably generate an #include.
return "", "", ""
@@ -1549,7 +1578,7 @@
return []
-def generate_header(ir):
+def generate_header(ir, config=Config()):
"""Generates a C++ header from an Emboss module.
Arguments:
@@ -1569,7 +1598,7 @@
method_definitions = []
for type_definition in ir.module[0].type:
declaration, definition, methods = _generate_type_definition(
- type_definition, ir)
+ type_definition, ir, config)
type_declarations.append(declaration)
type_definitions.append(definition)
method_definitions.append(methods)
@@ -1579,7 +1608,7 @@
type_definitions="".join(type_definitions),
method_definitions="".join(method_definitions))
body = _wrap_in_namespace(body, _get_module_namespace(ir.module[0]))
- includes = _get_includes(ir.module[0])
+ includes = _get_includes(ir.module[0], config)
return code_template.format_template(
_TEMPLATES.outline,
includes=includes,
diff --git a/compiler/back_end/cpp/testcode/no_enum_traits_test.cc b/compiler/back_end/cpp/testcode/no_enum_traits_test.cc
new file mode 100644
index 0000000..78dbd90
--- /dev/null
+++ b/compiler/back_end/cpp/testcode/no_enum_traits_test.cc
@@ -0,0 +1,48 @@
+// Copyright 2024 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.
+
+// Tests that an emb compiled with enable_enum_traits = False actually compiles.
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "testdata/no_enum_traits.emb.h"
+
+namespace emboss {
+namespace test {
+namespace {
+
+TEST(NoEnumTraits, Compiles) {
+ ::std::vector<uint8_t> backing_store(1);
+ auto view = MakeBarView(&backing_store);
+ view.foo().Write(Foo::VALUE);
+ EXPECT_TRUE(view.Ok());
+
+ // Check that we don't accidentally include `emboss_text_util.h` via our
+ // generated header.
+#ifdef EMBOSS_RUNTIME_CPP_EMBOSS_TEXT_UTIL_H_
+ const bool emboss_text_util_is_present = true;
+#else
+ const bool emboss_text_util_is_present = false;
+#endif
+
+ EXPECT_FALSE(emboss_text_util_is_present);
+}
+
+} // namespace
+} // namespace test
+} // namespace emboss
diff --git a/embossc b/embossc
index 0dac79f..7bc0227 100755
--- a/embossc
+++ b/embossc
@@ -20,6 +20,7 @@
import os
import sys
+
def _parse_args(argv):
parser = argparse.ArgumentParser(description="Emboss compiler")
parser.add_argument("--color-output",
@@ -49,6 +50,11 @@
nargs=1,
help="""File name to be used for the generated output
file. Defaults to input_file suffixed by '.h'""")
+ parser.add_argument("--cc-enum-traits",
+ action=argparse.BooleanOptionalAction,
+ default=True,
+ help="""Controls generation of EnumTraits by the C++
+ backend""")
parser.add_argument("input_file",
type=str,
nargs=1,
@@ -62,7 +68,7 @@
sys.path.append(base_path)
from compiler.back_end.cpp import ( # pylint:disable=import-outside-toplevel
- emboss_codegen_cpp
+ emboss_codegen_cpp, header_generator
)
from compiler.front_end import ( # pylint:disable=import-outside-toplevel
emboss_front_end
@@ -74,8 +80,9 @@
if errors:
return 1
+ config = header_generator.Config(include_enum_traits=flags.cc_enum_traits)
header, errors = emboss_codegen_cpp.generate_headers_and_log_errors(
- ir, flags.color_output)
+ ir, flags.color_output, config)
if errors:
return 1
diff --git a/runtime/cpp/emboss_array_view.h b/runtime/cpp/emboss_array_view.h
index 7287fce..674b2ad 100644
--- a/runtime/cpp/emboss_array_view.h
+++ b/runtime/cpp/emboss_array_view.h
@@ -22,12 +22,16 @@
#include <type_traits>
#include "runtime/cpp/emboss_arithmetic.h"
-#include "runtime/cpp/emboss_array_view.h"
-#include "runtime/cpp/emboss_text_util.h"
namespace emboss {
// Forward declarations for use by WriteShorthandArrayCommentToTextStream.
+class TextOutputOptions;
+namespace support {
+template <class Array, class Stream>
+void WriteShorthandAsciiArrayCommentToTextStream(
+ const Array *array, Stream *stream, const TextOutputOptions &options);
+}
namespace prelude {
template <class Parameters, class BitViewType>
class UIntView;
@@ -169,7 +173,7 @@
ElementViewIteratorDirection::kReverse>;
GenericArrayView() : buffer_() {}
- explicit GenericArrayView(const ElementViewParameterTypes &... parameters,
+ explicit GenericArrayView(const ElementViewParameterTypes &...parameters,
BufferType buffer)
: parameters_{parameters...}, buffer_{buffer} {}
@@ -348,30 +352,6 @@
static_cast<void>(options);
}
-// 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);
- }
-}
-
// Overload for arrays of UInt.
// Prints out the elements as ASCII characters for arrays of UInt:8.
template <class BufferType, class BitViewType, class Stream,
diff --git a/runtime/cpp/emboss_cpp_util.h b/runtime/cpp/emboss_cpp_util.h
index 27ef486..1e837ac 100644
--- a/runtime/cpp/emboss_cpp_util.h
+++ b/runtime/cpp/emboss_cpp_util.h
@@ -25,7 +25,6 @@
#include "runtime/cpp/emboss_defines.h"
#include "runtime/cpp/emboss_enum_view.h"
#include "runtime/cpp/emboss_memory_util.h"
-#include "runtime/cpp/emboss_text_util.h"
#include "runtime/cpp/emboss_view_parameters.h"
#endif // EMBOSS_RUNTIME_CPP_EMBOSS_CPP_UTIL_H_
diff --git a/runtime/cpp/emboss_enum_view.h b/runtime/cpp/emboss_enum_view.h
index af4d7aa..fa51f80 100644
--- a/runtime/cpp/emboss_enum_view.h
+++ b/runtime/cpp/emboss_enum_view.h
@@ -21,9 +21,21 @@
#include <string>
#include <utility>
-#include "runtime/cpp/emboss_text_util.h"
+#include "runtime/cpp/emboss_defines.h"
#include "runtime/cpp/emboss_view_parameters.h"
+// Forward declarations for optional text processing helpers.
+namespace emboss {
+class TextOutputOptions;
+namespace support {
+template <class Stream, class View>
+bool ReadEnumViewFromTextStream(View *view, Stream *stream);
+template <class Stream, class View>
+void WriteEnumViewToTextStream(View *view, Stream *stream,
+ const TextOutputOptions &options);
+} // namespace support
+} // namespace emboss
+
namespace emboss {
namespace support {
@@ -36,7 +48,7 @@
Parameters::kBits <= sizeof(ValueType) * 8,
"EnumView requires sizeof(ValueType) * 8 >= Parameters::kBits.");
template <typename... Args>
- explicit EnumView(Args &&... args) : buffer_{::std::forward<Args>(args)...} {}
+ explicit EnumView(Args &&...args) : buffer_{::std::forward<Args>(args)...} {}
EnumView() : buffer_() {}
EnumView(const EnumView &) = default;
EnumView(EnumView &&) = default;
@@ -124,25 +136,7 @@
template <class Stream>
bool UpdateFromTextStream(Stream *stream) const {
- ::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 TryToWrite(static_cast<ValueType>(value));
- } else if (token[0] == '-') {
- ::std::int64_t value;
- if (!DecodeInteger(token, &value)) return false;
- return TryToWrite(static_cast<ValueType>(value));
- } else {
- ValueType value;
- if (!TryToGetEnumFromName(token.c_str(), &value)) return false;
- return TryToWrite(value);
- }
+ return ::emboss::support::ReadEnumViewFromTextStream(this, stream);
}
template <class Stream>
diff --git a/runtime/cpp/emboss_prelude.h b/runtime/cpp/emboss_prelude.h
index 0fbaff4..8b5fa28 100644
--- a/runtime/cpp/emboss_prelude.h
+++ b/runtime/cpp/emboss_prelude.h
@@ -28,6 +28,30 @@
#include "runtime/cpp/emboss_cpp_util.h"
+// Forward declarations for optional text processing helpers.
+namespace emboss {
+class TextOutputOptions;
+namespace support {
+template <class Stream, class View>
+bool ReadBooleanFromTextStream(View *view, Stream *stream);
+template <class Stream, class View>
+void WriteBooleanViewToTextStream(View *view, Stream *stream,
+ const TextOutputOptions &);
+
+template <class Stream, class View>
+bool ReadIntegerFromTextStream(View *view, Stream *stream);
+template <class Stream, class View>
+void WriteIntegerViewToTextStream(View *view, Stream *stream,
+ const TextOutputOptions &options);
+
+template <class Stream, class View>
+bool ReadFloatFromTextStream(View *view, Stream *stream);
+template <class Stream, class Float>
+void WriteFloatToTextStream(Float n, Stream *stream,
+ const TextOutputOptions &options);
+} // namespace support
+} // namespace emboss
+
// This namespace must match the [(cpp) namespace] in the Emboss prelude.
namespace emboss {
namespace prelude {
@@ -101,15 +125,7 @@
template <class Stream>
bool UpdateFromTextStream(Stream *stream) const {
- ::std::string token;
- if (!::emboss::support::ReadToken(stream, &token)) return false;
- if (token == "true") {
- return TryToWrite(true);
- } else if (token == "false") {
- return TryToWrite(false);
- }
- // TODO(bolms): Provide a way to get an error message on parse failure.
- return false;
+ return ::emboss::support::ReadBooleanFromTextStream(this, stream);
}
template <class Stream>
@@ -136,7 +152,7 @@
"UIntView requires sizeof(ValueType) * 8 >= Parameters::kBits.");
template <typename... Args>
- explicit UIntView(Args &&... args) : buffer_{::std::forward<Args>(args)...} {}
+ explicit UIntView(Args &&...args) : buffer_{::std::forward<Args>(args)...} {}
UIntView() : buffer_() {}
UIntView(const UIntView &) = default;
UIntView(UIntView &&) = default;
@@ -295,7 +311,7 @@
template <class Stream>
void WriteToTextStream(Stream *stream,
- ::emboss::TextOutputOptions options) const {
+ ::emboss::TextOutputOptions &options) const {
support::WriteIntegerViewToTextStream(this, stream, options);
}
@@ -318,7 +334,7 @@
"IntView requires sizeof(ValueType) * 8 >= Parameters::kBits.");
template <typename... Args>
- explicit IntView(Args &&... args) : buffer_{::std::forward<Args>(args)...} {}
+ explicit IntView(Args &&...args) : buffer_{::std::forward<Args>(args)...} {}
IntView() : buffer_() {}
IntView(const IntView &) = default;
IntView(IntView &&) = default;
@@ -460,7 +476,7 @@
template <class Stream>
void WriteToTextStream(Stream *stream,
- ::emboss::TextOutputOptions options) const {
+ ::emboss::TextOutputOptions &options) const {
support::WriteIntegerViewToTextStream(this, stream, options);
}
@@ -605,7 +621,7 @@
"BcdView requires sizeof(ValueType) * 8 >= Parameters::kBits.");
template <typename... Args>
- explicit BcdView(Args &&... args) : buffer_{::std::forward<Args>(args)...} {}
+ explicit BcdView(Args &&...args) : buffer_{::std::forward<Args>(args)...} {}
BcdView() : buffer_() {}
BcdView(const BcdView &) = default;
BcdView(BcdView &&) = default;
@@ -645,7 +661,7 @@
template <class Stream>
void WriteToTextStream(Stream *stream,
- ::emboss::TextOutputOptions options) const {
+ ::emboss::TextOutputOptions &options) const {
// TODO(bolms): This shares the numeric_base() option with IntView and
// UIntView (and EnumView, for unknown enum values). It seems like an end
// user might prefer to see BCD values in decimal, even if they want to see
@@ -728,8 +744,7 @@
using ValueType = typename support::FloatType<Parameters::kBits>::Type;
template <typename... Args>
- explicit FloatView(Args &&... args)
- : buffer_{::std::forward<Args>(args)...} {}
+ explicit FloatView(Args &&...args) : buffer_{::std::forward<Args>(args)...} {}
FloatView() : buffer_() {}
FloatView(const FloatView &) = default;
FloatView(FloatView &&) = default;
@@ -797,7 +812,7 @@
template <class Stream>
void WriteToTextStream(Stream *stream,
- ::emboss::TextOutputOptions options) const {
+ ::emboss::TextOutputOptions &options) const {
support::WriteFloatToTextStream(Read(), stream, options);
}
diff --git a/runtime/cpp/emboss_text_util.h b/runtime/cpp/emboss_text_util.h
index ac2e1d9..9cfe03d 100644
--- a/runtime/cpp/emboss_text_util.h
+++ b/runtime/cpp/emboss_text_util.h
@@ -347,6 +347,19 @@
}
}
+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.
@@ -596,6 +609,29 @@
}
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());
@@ -682,6 +718,30 @@
}
}
+// 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.
diff --git a/runtime/cpp/test/emboss_array_view_test.cc b/runtime/cpp/test/emboss_array_view_test.cc
index e662779..1f632e8 100644
--- a/runtime/cpp/test/emboss_array_view_test.cc
+++ b/runtime/cpp/test/emboss_array_view_test.cc
@@ -20,6 +20,7 @@
#include "absl/strings/str_format.h"
#include "gtest/gtest.h"
#include "runtime/cpp/emboss_prelude.h"
+#include "runtime/cpp/emboss_text_util.h"
namespace emboss {
namespace support {
diff --git a/runtime/cpp/test/emboss_cpp_util_google_integration_test.cc b/runtime/cpp/test/emboss_cpp_util_google_integration_test.cc
index 55a1daf..d56286f 100644
--- a/runtime/cpp/test/emboss_cpp_util_google_integration_test.cc
+++ b/runtime/cpp/test/emboss_cpp_util_google_integration_test.cc
@@ -15,6 +15,7 @@
#include "absl/strings/string_view.h"
#include "gtest/gtest.h"
#include "runtime/cpp/emboss_cpp_util.h"
+#include "runtime/cpp/emboss_text_util.h"
namespace emboss {
namespace support {
diff --git a/runtime/cpp/test/emboss_enum_view_test.cc b/runtime/cpp/test/emboss_enum_view_test.cc
index 1127f49..dd2d8ac 100644
--- a/runtime/cpp/test/emboss_enum_view_test.cc
+++ b/runtime/cpp/test/emboss_enum_view_test.cc
@@ -16,6 +16,7 @@
#include "gtest/gtest.h"
#include "runtime/cpp/emboss_prelude.h"
+#include "runtime/cpp/emboss_text_util.h"
namespace emboss {
namespace support {
diff --git a/runtime/cpp/test/emboss_memory_util_test.cc b/runtime/cpp/test/emboss_memory_util_test.cc
index c4f9ed0..6974e59 100644
--- a/runtime/cpp/test/emboss_memory_util_test.cc
+++ b/runtime/cpp/test/emboss_memory_util_test.cc
@@ -12,15 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <array>
#include <string>
#if __cplusplus >= 201703L
#include <string_view>
#endif // __cplusplus >= 201703L
#include <vector>
-#include "runtime/cpp/emboss_memory_util.h"
-
#include "gtest/gtest.h"
+#include "runtime/cpp/emboss_memory_util.h"
#include "runtime/cpp/emboss_prelude.h"
namespace emboss {
@@ -175,13 +175,19 @@
// std::basic_string<> with non-default trailing template parameters.
template <class T>
struct NonstandardAllocator {
- using value_type = typename ::std::allocator_traits<::std::allocator<T>>::value_type;
- using pointer = typename ::std::allocator_traits<::std::allocator<T>>::pointer;
- using const_pointer = typename ::std::allocator_traits<::std::allocator<T>>::const_pointer;
+ using value_type =
+ typename ::std::allocator_traits<::std::allocator<T>>::value_type;
+ using pointer =
+ typename ::std::allocator_traits<::std::allocator<T>>::pointer;
+ using const_pointer =
+ typename ::std::allocator_traits<::std::allocator<T>>::const_pointer;
using reference = typename ::std::allocator<T>::value_type &;
- using const_reference = const typename ::std::allocator_traits<::std::allocator<T>>::value_type &;
- using size_type = typename ::std::allocator_traits<::std::allocator<T>>::size_type;
- using difference_type = typename ::std::allocator_traits<::std::allocator<T>>::difference_type;
+ using const_reference =
+ const typename ::std::allocator_traits<::std::allocator<T>>::value_type &;
+ using size_type =
+ typename ::std::allocator_traits<::std::allocator<T>>::size_type;
+ using difference_type =
+ typename ::std::allocator_traits<::std::allocator<T>>::difference_type;
template <class U>
struct rebind {
diff --git a/runtime/cpp/test/emboss_prelude_test.cc b/runtime/cpp/test/emboss_prelude_test.cc
index 9fb1cb6..889daae 100644
--- a/runtime/cpp/test/emboss_prelude_test.cc
+++ b/runtime/cpp/test/emboss_prelude_test.cc
@@ -18,6 +18,7 @@
#include "gtest/gtest.h"
#include "runtime/cpp/emboss_cpp_util.h"
+#include "runtime/cpp/emboss_text_util.h"
namespace emboss {
namespace prelude {
diff --git a/testdata/BUILD b/testdata/BUILD
index a04b4a6..a886d39 100644
--- a/testdata/BUILD
+++ b/testdata/BUILD
@@ -242,6 +242,14 @@
)
emboss_cc_library(
+ name = "no_enum_traits_emboss",
+ srcs = [
+ "no_enum_traits.emb",
+ ],
+ enable_enum_traits = False,
+)
+
+emboss_cc_library(
name = "start_size_range_emboss",
srcs = [
"start_size_range.emb",
diff --git a/testdata/no_enum_traits.emb b/testdata/no_enum_traits.emb
new file mode 100644
index 0000000..bbe295e
--- /dev/null
+++ b/testdata/no_enum_traits.emb
@@ -0,0 +1,24 @@
+# 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.
+
+-- Test .emb to ensure that compilation succedes if enum traits are disabled.
+
+[$default byte_order: "LittleEndian"]
+[(cpp) namespace: "emboss::test"]
+
+enum Foo:
+ VALUE = 10
+
+struct Bar:
+ 0 [+1] Foo foo
\ No newline at end of file