// Protocol Buffers - Google's data interchange format // Copyright 2024 Google LLC. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd // Author: kenton@google.com (Kenton Varda) // Based on original Protocol Buffers design by // Sanjay Ghemawat, Jeff Dean, and others. // // Utility class for writing text to a ZeroCopyOutputStream. #ifndef GOOGLE_PROTOBUF_IO_PRINTER_H__ #define GOOGLE_PROTOBUF_IO_PRINTER_H__ #include #include #include #include #include #include #include #include "absl/cleanup/cleanup.h" #include "absl/container/flat_hash_map.h" #include "absl/functional/any_invocable.h" #include "absl/functional/function_ref.h" #include "absl/log/absl_check.h" #include "absl/meta/type_traits.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "absl/types/variant.h" #include "google/protobuf/io/zero_copy_sink.h" // Must be included last. #include "google/protobuf/port_def.inc" namespace google { namespace protobuf { namespace io { // Records annotations about a Printer's output. class PROTOBUF_EXPORT AnnotationCollector { public: // Annotation is a offset range and a payload pair. This payload's layout is // specific to derived types of AnnotationCollector. using Annotation = std::pair, std::string>; // The semantic meaning of an annotation. This enum mirrors // google.protobuf.GeneratedCodeInfo.Annotation.Semantic, and the enumerator values // should match it. enum Semantic { kNone = 0, kSet = 1, kAlias = 2, }; virtual ~AnnotationCollector() = default; // Records that the bytes in file_path beginning with begin_offset and ending // before end_offset are associated with the SourceCodeInfo-style path. virtual void AddAnnotation(size_t begin_offset, size_t end_offset, const std::string& file_path, const std::vector& path) = 0; virtual void AddAnnotation(size_t begin_offset, size_t end_offset, const std::string& file_path, const std::vector& path, absl::optional semantic) { AddAnnotation(begin_offset, end_offset, file_path, path); } // TODO I don't see why we need virtuals here. Just a vector of // range, payload pairs stored in a context should suffice. virtual void AddAnnotationNew(Annotation&) {} }; // Records annotations about a Printer's output to a Protobuf message, // assuming that it has a repeated submessage field named `annotation` with // fields matching // // message ??? { // repeated int32 path = 1; // optional string source_file = 2; // optional int32 begin = 3; // optional int32 end = 4; // optional int32 semantic = 5; // } template class AnnotationProtoCollector : public AnnotationCollector { private: // Some users of this type use it with a proto that does not have a // "semantic" field. Therefore, we need to detect it with SFINAE. // go/ranked-overloads struct Rank0 {}; struct Rank1 : Rank0 {}; template static auto SetSemantic(Proto* p, int semantic, Rank1) -> decltype(p->set_semantic( static_cast(semantic))) { return p->set_semantic(static_cast(semantic)); } template static void SetSemantic(Proto*, int, Rank0) {} public: explicit AnnotationProtoCollector(AnnotationProto* annotation_proto) : annotation_proto_(annotation_proto) {} void AddAnnotation(size_t begin_offset, size_t end_offset, const std::string& file_path, const std::vector& path) override { AddAnnotation(begin_offset, end_offset, file_path, path, absl::nullopt); } void AddAnnotation(size_t begin_offset, size_t end_offset, const std::string& file_path, const std::vector& path, absl::optional semantic) override { auto* annotation = annotation_proto_->add_annotation(); for (int i = 0; i < path.size(); ++i) { annotation->add_path(path[i]); } annotation->set_source_file(file_path); annotation->set_begin(begin_offset); annotation->set_end(end_offset); if (semantic.has_value()) { SetSemantic(annotation, *semantic, Rank1{}); } } void AddAnnotationNew(Annotation& a) override { auto* annotation = annotation_proto_->add_annotation(); annotation->ParseFromString(a.second); annotation->set_begin(a.first.first); annotation->set_end(a.first.second); } private: AnnotationProto* annotation_proto_; }; // A source code printer for assisting in code generation. // // This type implements a simple templating language for substituting variables // into static, user-provided strings, and also tracks indentation // automatically. // // The main entry-point for this type is the Emit function, which can be used // as thus: // // Printer p(output); // p.Emit({{"class", my_class_name}}, R"cc( // class $class$ { // public: // $class$(int x) : x_(x) {} // private: // int x_; // }; // )cc"); // // Substitutions are of the form $var$, which is looked up in the map passed in // as the first argument. The variable delimiter character, $, can be chosen to // be something convenient for the target language. For example, in PHP, which // makes heavy use of $, it can be made into something like # instead. // // A literal $ can be emitted by writing $$. // // Substitutions may contain spaces around the name of the variable, which will // be ignored for the purposes of looking up the variable to substitute in, but // which will be reproduced in the output: // // p.Emit({{"foo", "bar"}}, "$ foo $"); // // emits the string " bar ". If the substituted-in variable is the empty string, // then the surrounding spaces are *not* printed: // // p.Emit({{"xyz", xyz}}, "$xyz $Thing"); // // If xyz is "Foo", this will become "Foo Thing", but if it is "", this becomes // "Thing", rather than " Thing". This helps minimize awkward whitespace in the // output. // // The value may be any type that can be stringified with `absl::StrCat`: // // p.Emit({{"num", 5}}, "x = $num$;"); // // If a variable that is referenced in the format string is missing, the program // will crash. Callers must statically know that every variable reference is // valid, and MUST NOT pass user-provided strings directly into Emit(). // // In practice, this means the first member of io::Printer::Sub here: // // p.Emit({{"num", 5}}, "x = $num$;"); // ^ // must always be a string literal. // // Substitutions can be configured to "chomp" a single character after them, to // help make indentation work out. This can be configured by passing a // io::Printer::Sub().WithSuffix() into Emit's substitution map: // p.Emit({io::Printer::Sub("var", var_decl).WithSuffix(";")}, R"cc( // class $class$ { // public: // $var$; // }; // )cc"); // // This will delete the ; after $var$, regardless of whether it was an empty // declaration or not. It will also intelligently attempt to clean up // empty lines that follow, if it was on an empty line; this promotes cleaner // formatting of the output. // // You can configure a large set of skippable characters, but when chomping, // only one character will actually be skipped at a time. For example, callback // substitutions (see below) use ";," by default as their "chomping set". // // p.Emit({io::Printer::Sub("var", 123).WithSuffix(";,")}, R"cc( // $var$,; // )cc"); // // will produce "123,". // // # Callback Substitution // // Instead of passing a string into Emit(), it is possible to pass in a callback // as a variable mapping. This will take indentation into account, which allows // factoring out parts of a formatting string while ensuring braces are // balanced: // // p.Emit( // {{"methods", [&] { // p.Emit(R"cc( // int Bar() { // return 42; // } // )cc"); // }}}, // R"cc( // class Foo { // public: // $methods$; // }; // )cc" // ); // // This emits // // class Foo { // public: // int Bar() { // return 42; // } // }; // // # Comments // // It may be desirable to place comments in a raw string that are stripped out // before printing. The prefix for Printer-ignored comments can be configured // in Options. By default, this is `//~`. // // p.Emit(R"cc( // // Will be printed in the output. // //~ Won't be. // )cc"); // // # Lookup Frames // // If many calls to Emit() use the same set of variables, they can be stored // in a *variable lookup frame*, like so: // // auto vars = p.WithVars({{"class_name", my_class_name}}); // p.Emit(R"cc( // class $class_name$ { // public: // $class_name$(int x); // // Etc. // }; // )cc"); // // WithVars() returns an RAII object that will "pop" the lookup frame on scope // exit, ensuring that the variables remain local. There are a few different // overloads of WithVars(); it accepts a map type, like absl::flat_hash_map, // either by-value (which will cause the Printer to store a copy), or by // pointer (which will cause the Printer to store a pointer, potentially // avoiding a copy.) // // p.Emit(vars, "..."); is effectively syntax sugar for // // { auto v = p.WithVars(vars); p.Emit("..."); } // // NOTE: callbacks are *not* allowed with WithVars; callbacks should be local // to a specific Emit() call. // // # Annotations // // If Printer is given an AnnotationCollector, it will use it to record which // spans of generated code correspond to user-indicated descriptors. There are // a few different ways of indicating when to emit annotations. // // The WithAnnotations() function is like WithVars(), but accepts maps with // string keys and descriptor values. It adds an annotation variable frame and // returns an RAII object that pops the frame. // // There are two different ways to annotate code. In the first, when // substituting a variable, if there is an annotation with the same name, then // the resulting expanded value's span will be annotated with that annotation. // For example: // // auto v = p.WithVars({{"class_name", my_class_name}}); // auto a = p.WithAnnotations({{"class_name", message_descriptor}}); // p.Emit(R"cc( // class $class_name$ { // public: // $class_name$(int x); // // Etc. // }; // )cc"); // // The span corresponding to whatever $class_name$ expands to will be annotated // as having come from message_descriptor. // // For convenience, this can be done with a single WithVars(), using the special // three-argument form: // // auto v = p.WithVars({{"class_name", my_class_name, message_descriptor}}); // p.Emit(R"cc( // class $class_name$ { // public: // $class_name$(int x); // // Etc. // }; // )cc"); // // // Alternatively, a range may be given explicitly: // // auto a = p.WithAnnotations({{"my_desc", message_descriptor}}); // p.Emit(R"cc( // $_start$my_desc$ // class Foo { // // Etc. // }; // $_end$my_desc$ // )cc"); // // The special $_start$ and $_end$ variables indicate the start and end of an // annotated span, which is annotated with the variable that follows. This // form can produce somewhat unreadable format strings and is not recommended. // // Note that whitespace after a $_start$ and before an $_end$ is not printed. // // # Indentation // // Printer tracks an indentation amount to add to each new line, independent // from indentation in an Emit() call's literal. The amount of indentation to // add is controlled by the WithIndent() function: // // p.Emit("class $class_name$ {"); // { // auto indent = p.WithIndent(); // p.Emit(R"cc( // public: // $class_name$(int x); // )cc"); // } // p.Emit("};"); // // This will automatically add one level of indentation to all code in scope of // `indent`, which is an RAII object much like the return value of `WithVars()`. // // # Old API // TODO: Delete this documentation. // // Printer supports an older-style API that is in the process of being // re-written. The old documentation is reproduced here until all use-cases are // handled. // // This simple utility class assists in code generation. It basically // allows the caller to define a set of variables and then output some // text with variable substitutions. Example usage: // // Printer printer(output, '$'); // map vars; // vars["name"] = "Bob"; // printer.Print(vars, "My name is $name$."); // // The above writes "My name is Bob." to the output stream. // // Printer aggressively enforces correct usage, crashing (with assert failures) // in the case of undefined variables in debug builds. This helps greatly in // debugging code which uses it. // // If a Printer is constructed with an AnnotationCollector, it will provide it // with annotations that connect the Printer's output to paths that can identify // various descriptors. In the above example, if person_ is a descriptor that // identifies Bob, we can associate the output string "My name is Bob." with // a source path pointing to that descriptor with: // // printer.Annotate("name", person_); // // The AnnotationCollector will be sent an annotation linking the output range // covering "Bob" to the logical path provided by person_. Tools may use // this association to (for example) link "Bob" in the output back to the // source file that defined the person_ descriptor identifying Bob. // // Annotate can only examine variables substituted during the last call to // Print. It is invalid to refer to a variable that was used multiple times // in a single Print call. // // In full generality, one may specify a range of output text using a beginning // substitution variable and an ending variable. The resulting annotation will // span from the first character of the substituted value for the beginning // variable to the last character of the substituted value for the ending // variable. For example, the Annotate call above is equivalent to this one: // // printer.Annotate("name", "name", person_); // // This is useful if multiple variables combine to form a single span of output // that should be annotated with the same source path. For example: // // Printer printer(output, '$'); // map vars; // vars["first"] = "Alice"; // vars["last"] = "Smith"; // printer.Print(vars, "My name is $first$ $last$."); // printer.Annotate("first", "last", person_); // // This code would associate the span covering "Alice Smith" in the output with // the person_ descriptor. // // Note that the beginning variable must come before (or overlap with, in the // case of zero-sized substitution values) the ending variable. // // It is also sometimes useful to use variables with zero-sized values as // markers. This avoids issues with multiple references to the same variable // and also allows annotation ranges to span literal text from the Print // templates: // // Printer printer(output, '$'); // map vars; // vars["foo"] = "bar"; // vars["function"] = "call"; // vars["mark"] = ""; // printer.Print(vars, "$function$($foo$,$foo$)$mark$"); // printer.Annotate("function", "mark", call_); // // This code associates the span covering "call(bar,bar)" in the output with the // call_ descriptor. class PROTOBUF_EXPORT Printer { private: struct AnnotationRecord; public: // This type exists to work around an absl type that has not yet been // released. struct SourceLocation { static SourceLocation current() { return {}; } absl::string_view file_name() const { return ""; } int line() const { return 0; } }; static constexpr char kDefaultVariableDelimiter = '$'; static constexpr absl::string_view kProtocCodegenTrace = "PROTOC_CODEGEN_TRACE"; // Sink type for constructing substitutions to pass to WithVars() and Emit(). class Sub; // Options for controlling how the output of a Printer is formatted. struct Options { Options() = default; Options(const Options&) = default; Options(Options&&) = default; Options(char variable_delimiter, AnnotationCollector* annotation_collector) : variable_delimiter(variable_delimiter), annotation_collector(annotation_collector) {} // The delimiter for variable substitutions, e.g. $foo$. char variable_delimiter = kDefaultVariableDelimiter; // An optional listener the Printer calls whenever it emits a source // annotation; may be null. AnnotationCollector* annotation_collector = nullptr; // The "comment start" token for the language being generated. This is used // to allow the Printer to emit debugging annotations in the source code // output. absl::string_view comment_start = "//"; // The token for beginning comments that are discarded by Printer's internal // formatter. absl::string_view ignored_comment_start = "//~"; // The number of spaces that a single level of indentation adds by default; // this is the amount that WithIndent() increases indentation by. size_t spaces_per_indent = 2; // Whether to emit a "codegen trace" for calls to Emit(). If true, each call // to Emit() will print a comment indicating where in the source of the // compiler the Emit() call occurred. // // If disengaged, defaults to whether or not the environment variable // `PROTOC_CODEGEN_TRACE` is set. absl::optional enable_codegen_trace = absl::nullopt; }; // Constructs a new Printer with the default options to output to // `output`. explicit Printer(ZeroCopyOutputStream* output); // Constructs a new printer with the given set of options to output to // `output`. Printer(ZeroCopyOutputStream* output, Options options); // Old-style constructor. Avoid in preference to the two constructors above. // // Will eventually be marked as deprecated. Printer(ZeroCopyOutputStream* output, char variable_delimiter, AnnotationCollector* annotation_collector = nullptr); Printer(const Printer&) = delete; Printer& operator=(const Printer&) = delete; // Pushes a new variable lookup frame that stores `vars` by reference. // // Returns an RAII object that pops the lookup frame. template auto WithVars(const Map* vars); // Pushes a new variable lookup frame that stores `vars` by value. // // Returns an RAII object that pops the lookup frame. template < typename Map = absl::flat_hash_map, typename = std::enable_if_t::value>, // Prefer the more specific span impl if this could be turned into // a span. typename = std::enable_if_t< !std::is_convertible>::value>> auto WithVars(Map&& vars); // Pushes a new variable lookup frame that stores `vars` by value. // // Returns an RAII object that pops the lookup frame. auto WithVars(absl::Span vars); // Looks up a variable set with WithVars(). // // Will crash if: // - `var` is not present in the lookup frame table. // - `var` is a callback, rather than a string. absl::string_view LookupVar(absl::string_view var); // Pushes a new annotation lookup frame that stores `vars` by reference. // // Returns an RAII object that pops the lookup frame. template auto WithAnnotations(const Map* vars); // Pushes a new variable lookup frame that stores `vars` by value. // // When writing `WithAnnotations({...})`, this is the overload that will be // called, and it will synthesize an `absl::flat_hash_map`. // // Returns an RAII object that pops the lookup frame. template > auto WithAnnotations(Map&& vars); // Increases the indentation by `indent` spaces; when nullopt, increments // indentation by the configured default spaces_per_indent. // // Returns an RAII object that removes this indentation. auto WithIndent(absl::optional indent = absl::nullopt) { size_t delta = indent.value_or(options_.spaces_per_indent); indent_ += delta; return absl::MakeCleanup([this, delta] { indent_ -= delta; }); } // Emits formatted source code to the underlying output. See the class // documentation for more details. // // `format` MUST be a string constant. void Emit(absl::string_view format, SourceLocation loc = SourceLocation::current()); // Emits formatted source code to the underlying output, injecting // additional variables as a lookup frame for just this call. See the class // documentation for more details. // // `format` MUST be a string constant. void Emit(absl::Span vars, absl::string_view format, SourceLocation loc = SourceLocation::current()); // Write a string directly to the underlying output, performing no formatting // of any sort. void PrintRaw(absl::string_view data) { WriteRaw(data.data(), data.size()); } // Write a string directly to the underlying output, performing no formatting // of any sort. void WriteRaw(const char* data, size_t size); // True if any write to the underlying stream failed. (We don't just // crash in this case because this is an I/O failure, not a programming // error.) bool failed() const { return failed_; } // -- Old-style API below; to be deprecated and removed. -- // TODO: Deprecate these APIs. template < typename Map = absl::flat_hash_map> void Print(const Map& vars, absl::string_view text); template void Print(absl::string_view text, const Args&... args); // Link a substitution variable emitted by the last call to Print to the // object described by descriptor. template void Annotate( absl::string_view varname, const SomeDescriptor* descriptor, absl::optional semantic = absl::nullopt) { Annotate(varname, varname, descriptor, semantic); } // Link the output range defined by the substitution variables as emitted by // the last call to Print to the object described by descriptor. The range // begins at begin_varname's value and ends after the last character of the // value substituted for end_varname. template void Annotate( absl::string_view begin_varname, absl::string_view end_varname, const Desc* descriptor, absl::optional semantic = absl::nullopt); // Link a substitution variable emitted by the last call to Print to the file // with path file_name. void Annotate( absl::string_view varname, absl::string_view file_name, absl::optional semantic = absl::nullopt) { Annotate(varname, varname, file_name, semantic); } // Link the output range defined by the substitution variables as emitted by // the last call to Print to the file with path file_name. The range begins // at begin_varname's value and ends after the last character of the value // substituted for end_varname. void Annotate( absl::string_view begin_varname, absl::string_view end_varname, absl::string_view file_name, absl::optional semantic = absl::nullopt) { if (options_.annotation_collector == nullptr) { return; } Annotate(begin_varname, end_varname, file_name, {}, semantic); } // Indent text by `options.spaces_per_indent`; undone by Outdent(). void Indent() { indent_ += options_.spaces_per_indent; } // Undoes a call to Indent(). void Outdent(); // FormatInternal is a helper function not meant to use directly, use // compiler::cpp::Formatter instead. template > void FormatInternal(absl::Span args, const Map& vars, absl::string_view format); // Injects a substitution listener for the lifetime of the RAII object // returned. // While the listener is active it will receive a callback on each // substitution label found. // This can be used to add basic verification on top of emit routines. auto WithSubstitutionListener( absl::AnyInvocable listener) { ABSL_CHECK(substitution_listener_ == nullptr); substitution_listener_ = std::move(listener); return absl::MakeCleanup([this] { substitution_listener_ = nullptr; }); } private: struct PrintOptions; struct Format; // Helper type for wrapping a variable substitution expansion result. template struct ValueImpl; using ValueView = ValueImpl; using Value = ValueImpl; // Provide a helper to use heterogeneous lookup when it's available. template using Void = void; template struct HasHeteroLookup : std::false_type {}; template struct HasHeteroLookup().find( std::declval()))>> : std::true_type {}; template ::value>> static absl::string_view ToStringKey(absl::string_view x) { return x; } template ::value>> static std::string ToStringKey(absl::string_view x) { return std::string(x); } Format TokenizeFormat(absl::string_view format_string, const PrintOptions& options); // Emit an annotation for the range defined by the given substitution // variables, as set by the most recent call to PrintImpl() that set // `use_substitution_map` to true. // // The range begins at the start of `begin_varname`'s value and ends after the // last byte of `end_varname`'s value. // // `begin_varname` and `end_varname may` refer to the same variable. void Annotate(absl::string_view begin_varname, absl::string_view end_varname, absl::string_view file_path, const std::vector& path, absl::optional semantic); // The core printing implementation. There are three public entry points, // which enable different slices of functionality that are controlled by the // `opts` argument. void PrintImpl(absl::string_view format, absl::Span args, PrintOptions opts); // This is a private function only so that it can see PrintOptions. static bool Validate(bool cond, PrintOptions opts, absl::FunctionRef message); static bool Validate(bool cond, PrintOptions opts, absl::string_view message); // Performs calls to `Validate()` to check that `index < current_arg_index` // and `index < args_len`, producing appropriate log lines if the checks fail, // and crashing if necessary. bool ValidateIndexLookupInBounds(size_t index, size_t current_arg_index, size_t args_len, PrintOptions opts); // Prints indentation if `at_start_of_line_` is true. void IndentIfAtStart(); // Prints a codegen trace, for the given location in the compiler's source. void PrintCodegenTrace(absl::optional loc); // The core implementation for "fully-elaborated" variable definitions. auto WithDefs(absl::Span vars, bool allow_callbacks); // Returns the start and end of the value that was substituted in place of // the variable `varname` in the last call to PrintImpl() (with // `use_substitution_map` set), if such a variable was substituted exactly // once. absl::optional> GetSubstitutionRange( absl::string_view varname, PrintOptions opts); google::protobuf::io::zc_sink_internal::ZeroCopyStreamByteSink sink_; Options options_; size_t indent_ = 0; bool at_start_of_line_ = true; bool failed_ = false; size_t paren_depth_ = 0; std::vector paren_depth_to_omit_; std::vector(absl::string_view)>> var_lookups_; std::vector< std::function(absl::string_view)>> annotation_lookups_; // If set, we invoke this when we do a label substitution. This can be used to // verify consistency of the generated code while we generate it. absl::AnyInvocable substitution_listener_; // A map from variable name to [start, end) offsets in the output buffer. // // This stores the data looked up by GetSubstitutionRange(). absl::flat_hash_map> substitutions_; // Keeps track of the keys in `substitutions_` that need to be updated when // indents are inserted. These are keys that refer to the beginning of the // current line. std::vector line_start_variables_; }; // Options for PrintImpl(). struct Printer::PrintOptions { // The callsite of the public entry-point. Only Emit() sets this. absl::optional loc; // If set, Validate() calls will not crash the program. bool checks_are_debug_only = false; // If set, the `substitutions_` map will be populated as variables are // substituted. bool use_substitution_map = false; // If set, the ${1$ and $}$ forms will be substituted. These are used for // a slightly janky annotation-insertion mechanism in FormatInternal, that // requires that passed-in substitution variables be serialized protos. bool use_curly_brace_substitutions = false; // If set, the $n$ forms will be substituted, pulling from the `args` // argument to PrintImpl(). bool allow_digit_substitutions = true; // If set, when a variable substitution with spaces in it, such as $ var$, // is encountered, the spaces are stripped, so that it is as if it was // $var$. If $var$ substitutes to a non-empty string, the removed spaces are // printed around the substituted value. // // See the class documentation for more information on this behavior. bool strip_spaces_around_vars = true; // If set, leading whitespace will be stripped from the format string to // determine the "extraneous indentation" that is produced when the format // string is a C++ raw string. This is used to remove leading spaces from // a raw string that would otherwise result in erratic indentation in the // output. bool strip_raw_string_indentation = false; // If set, the annotation lookup frames are searched, per the annotation // semantics of Emit() described in the class documentation. bool use_annotation_frames = true; }; // Helper type for wrapping a variable substitution expansion result. template struct Printer::ValueImpl { private: template struct IsSubImpl : std::false_type {}; template struct IsSubImpl> : std::true_type {}; public: using StringType = std::conditional_t; // These callbacks return false if this is a recursive call. using Callback = std::function; using StringOrCallback = absl::variant; ValueImpl() = default; // This is a template to avoid colliding with the copy constructor below. template >::value>> ValueImpl(Value&& value) // NOLINT : value(ToStringOrCallback(std::forward(value), Rank2{})) { if (absl::holds_alternative(this->value)) { consume_after = ";,"; } } // Copy ctor/assign allow interconversion of the two template parameters. template ValueImpl(const ValueImpl& that) { // NOLINT *this = that; } template ValueImpl& operator=(const ValueImpl& that); const StringType* AsString() const { return absl::get_if(&value); } const Callback* AsCallback() const { return absl::get_if(&value); } StringOrCallback value; std::string consume_after; bool consume_parens_if_empty = false; private: // go/ranked-overloads struct Rank0 {}; struct Rank1 : Rank0 {}; struct Rank2 : Rank1 {}; // Dummy template for delayed instantiation, which is required for the // static assert below to kick in only when this function is called when it // shouldn't. // // This is done to produce a better error message than the "candidate does // not match" SFINAE errors. template ()())> StringOrCallback ToStringOrCallback(Cb&& cb, Rank2); // Separate from the AlphaNum overload to avoid copies when taking strings // by value when in `owned` mode. StringOrCallback ToStringOrCallback(StringType s, Rank1) { return s; } StringOrCallback ToStringOrCallback(const absl::AlphaNum& s, Rank0) { return StringType(s.Piece()); } }; template template Printer::ValueImpl& Printer::ValueImpl::operator=( const ValueImpl& that) { // Cast to void* is required, since this and that may potentially be of // different types (due to the `that_owned` parameter). if (static_cast(this) == static_cast(&that)) { return *this; } using ThatStringType = typename ValueImpl::StringType; if (auto* str = absl::get_if(&that.value)) { value = StringType(*str); } else { value = absl::get(that.value); } consume_after = that.consume_after; consume_parens_if_empty = that.consume_parens_if_empty; return *this; } template template auto Printer::ValueImpl::ToStringOrCallback(Cb&& cb, Rank2) -> StringOrCallback { return Callback( [cb = std::forward(cb), is_called = false]() mutable -> bool { if (is_called) { // Catch whether or not this function is being called recursively. return false; } is_called = true; cb(); is_called = false; return true; }); } struct Printer::AnnotationRecord { std::vector path; std::string file_path; absl::optional semantic; // AnnotationRecord's constructors are *not* marked as explicit, // specifically so that it is possible to construct a // map by writing // // {{"foo", my_cool_descriptor}, {"bar", "file.proto"}} template < typename String, std::enable_if_t::value, int> = 0> AnnotationRecord( // NOLINT(google-explicit-constructor) const String& file_path, absl::optional semantic = absl::nullopt) : file_path(file_path), semantic(semantic) {} template ::value, int> = 0> AnnotationRecord( // NOLINT(google-explicit-constructor) const Desc* desc, absl::optional semantic = absl::nullopt) : file_path(desc->file()->name()), semantic(semantic) { desc->GetLocationPath(&path); } }; class Printer::Sub { public: template Sub(std::string key, Value&& value) : key_(std::move(key)), value_(std::forward(value)), annotation_(absl::nullopt) {} Sub AnnotatedAs(AnnotationRecord annotation) && { annotation_ = std::move(annotation); return std::move(*this); } Sub WithSuffix(std::string sub_suffix) && { value_.consume_after = std::move(sub_suffix); return std::move(*this); } Sub ConditionalFunctionCall() && { value_.consume_parens_if_empty = true; return std::move(*this); } absl::string_view key() const { return key_; } absl::string_view value() const { const auto* str = value_.AsString(); ABSL_CHECK(str != nullptr) << "could not find " << key() << "; found callback instead"; return *str; } private: friend class Printer; std::string key_; Value value_; absl::optional annotation_; }; template auto Printer::WithVars(const Map* vars) { var_lookups_.emplace_back( [vars](absl::string_view var) -> absl::optional { auto it = vars->find(ToStringKey(var)); if (it == vars->end()) { return absl::nullopt; } return ValueView(it->second); }); return absl::MakeCleanup([this] { var_lookups_.pop_back(); }); } template auto Printer::WithVars(Map&& vars) { var_lookups_.emplace_back( [vars = std::forward(vars)]( absl::string_view var) -> absl::optional { auto it = vars.find(ToStringKey(var)); if (it == vars.end()) { return absl::nullopt; } return ValueView(it->second); }); return absl::MakeCleanup([this] { var_lookups_.pop_back(); }); } template auto Printer::WithAnnotations(const Map* vars) { annotation_lookups_.emplace_back( [vars](absl::string_view var) -> absl::optional { auto it = vars->find(ToStringKey(var)); if (it == vars->end()) { return absl::nullopt; } return AnnotationRecord(it->second); }); return absl::MakeCleanup([this] { annotation_lookups_.pop_back(); }); } template auto Printer::WithAnnotations(Map&& vars) { annotation_lookups_.emplace_back( [vars = std::forward(vars)]( absl::string_view var) -> absl::optional { auto it = vars.find(ToStringKey(var)); if (it == vars.end()) { return absl::nullopt; } return AnnotationRecord(it->second); }); return absl::MakeCleanup([this] { annotation_lookups_.pop_back(); }); } inline void Printer::Emit(absl::string_view format, SourceLocation loc) { Emit({}, format, loc); } template void Printer::Print(const Map& vars, absl::string_view text) { PrintOptions opts; opts.checks_are_debug_only = true; opts.use_substitution_map = true; opts.allow_digit_substitutions = false; auto pop = WithVars(&vars); PrintImpl(text, {}, opts); } template void Printer::Print(absl::string_view text, const Args&... args) { static_assert(sizeof...(args) % 2 == 0, ""); // Include an extra arg, since a zero-length array is ill-formed, and // MSVC complains. absl::string_view vars[] = {args..., ""}; absl::flat_hash_map map; map.reserve(sizeof...(args) / 2); for (size_t i = 0; i < sizeof...(args); i += 2) { map.emplace(vars[i], vars[i + 1]); } Print(map, text); } template void Printer::Annotate(absl::string_view begin_varname, absl::string_view end_varname, const Desc* descriptor, absl::optional semantic) { if (options_.annotation_collector == nullptr) { return; } std::vector path; descriptor->GetLocationPath(&path); Annotate(begin_varname, end_varname, descriptor->file()->name(), path, semantic); } template void Printer::FormatInternal(absl::Span args, const Map& vars, absl::string_view format) { PrintOptions opts; opts.use_curly_brace_substitutions = true; opts.strip_spaces_around_vars = true; auto pop = WithVars(&vars); PrintImpl(format, args, opts); } inline auto Printer::WithDefs(absl::Span vars, bool allow_callbacks) { absl::flat_hash_map var_map; var_map.reserve(vars.size()); absl::flat_hash_map annotation_map; for (const auto& var : vars) { ABSL_CHECK(allow_callbacks || var.value_.AsCallback() == nullptr) << "callback arguments are not permitted in this position"; auto result = var_map.insert({var.key_, var.value_}); ABSL_CHECK(result.second) << "repeated variable in Emit() or WithVars() call: \"" << var.key_ << "\""; if (var.annotation_.has_value()) { annotation_map.insert({var.key_, *var.annotation_}); } } var_lookups_.emplace_back([map = std::move(var_map)](absl::string_view var) -> absl::optional { auto it = map.find(var); if (it == map.end()) { return absl::nullopt; } return ValueView(it->second); }); bool has_annotations = !annotation_map.empty(); if (has_annotations) { annotation_lookups_.emplace_back( [map = std::move(annotation_map)]( absl::string_view var) -> absl::optional { auto it = map.find(var); if (it == map.end()) { return absl::nullopt; } return it->second; }); } return absl::MakeCleanup([this, has_annotations] { var_lookups_.pop_back(); if (has_annotations) { annotation_lookups_.pop_back(); } }); } inline auto Printer::WithVars(absl::Span vars) { return WithDefs(vars, /*allow_callbacks=*/false); } } // namespace io } // namespace protobuf } // namespace google #include "google/protobuf/port_undef.inc" #endif // GOOGLE_PROTOBUF_IO_PRINTER_H__