| // 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 <cstddef> |
| #include <functional> |
| #include <initializer_list> |
| #include <string> |
| #include <type_traits> |
| #include <utility> |
| #include <vector> |
| |
| #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::pair<size_t, size_t>, 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<int>& path) = 0; |
| |
| virtual void AddAnnotation(size_t begin_offset, size_t end_offset, |
| const std::string& file_path, |
| const std::vector<int>& path, |
| absl::optional<Semantic> 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 <typename AnnotationProto> |
| 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 <typename Proto> |
| static auto SetSemantic(Proto* p, int semantic, Rank1) |
| -> decltype(p->set_semantic( |
| static_cast<typename Proto::Semantic>(semantic))) { |
| return p->set_semantic(static_cast<typename Proto::Semantic>(semantic)); |
| } |
| |
| template <typename Proto> |
| 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<int>& 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<int>& path, |
| absl::optional<Semantic> 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<string, string> 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<string, string> 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<string, string> 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 "<unknown>"; } |
| 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<bool> 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 <typename Map> |
| 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<absl::string_view, absl::string_view>, |
| typename = std::enable_if_t<!std::is_pointer<Map>::value>, |
| // Prefer the more specific span impl if this could be turned into |
| // a span. |
| typename = std::enable_if_t< |
| !std::is_convertible<Map, absl::Span<const Sub>>::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<const Sub> 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 <typename Map> |
| 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 <typename Map = absl::flat_hash_map<std::string, AnnotationRecord>> |
| 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<size_t> 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<const Sub> 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<absl::string_view, absl::string_view>> |
| void Print(const Map& vars, absl::string_view text); |
| |
| template <typename... Args> |
| 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 <typename SomeDescriptor> |
| void Annotate( |
| absl::string_view varname, const SomeDescriptor* descriptor, |
| absl::optional<AnnotationCollector::Semantic> 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 <typename Desc> |
| void Annotate( |
| absl::string_view begin_varname, absl::string_view end_varname, |
| const Desc* descriptor, |
| absl::optional<AnnotationCollector::Semantic> 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<AnnotationCollector::Semantic> 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<AnnotationCollector::Semantic> 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 <typename Map = absl::flat_hash_map<std::string, std::string>> |
| void FormatInternal(absl::Span<const std::string> 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<void(absl::string_view, SourceLocation)> 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 <bool owned> |
| struct ValueImpl; |
| |
| using ValueView = ValueImpl</*owned=*/false>; |
| using Value = ValueImpl</*owned=*/true>; |
| |
| // Provide a helper to use heterogeneous lookup when it's available. |
| template <typename...> |
| using Void = void; |
| |
| template <typename Map, typename = void> |
| struct HasHeteroLookup : std::false_type {}; |
| template <typename Map> |
| struct HasHeteroLookup<Map, Void<decltype(std::declval<Map>().find( |
| std::declval<absl::string_view>()))>> |
| : std::true_type {}; |
| |
| template <typename Map, |
| typename = std::enable_if_t<HasHeteroLookup<Map>::value>> |
| static absl::string_view ToStringKey(absl::string_view x) { |
| return x; |
| } |
| |
| template <typename Map, |
| typename = std::enable_if_t<!HasHeteroLookup<Map>::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<int>& path, |
| absl::optional<AnnotationCollector::Semantic> 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<const std::string> args, |
| PrintOptions opts); |
| |
| // This is a private function only so that it can see PrintOptions. |
| static bool Validate(bool cond, PrintOptions opts, |
| absl::FunctionRef<std::string()> 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<SourceLocation> loc); |
| |
| // The core implementation for "fully-elaborated" variable definitions. |
| auto WithDefs(absl::Span<const Sub> 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<std::pair<size_t, size_t>> 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<size_t> paren_depth_to_omit_; |
| |
| std::vector<std::function<absl::optional<ValueView>(absl::string_view)>> |
| var_lookups_; |
| |
| std::vector< |
| std::function<absl::optional<AnnotationRecord>(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<void(absl::string_view, SourceLocation)> |
| 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<std::string, std::pair<size_t, size_t>> 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<std::string> line_start_variables_; |
| }; |
| |
| // Options for PrintImpl(). |
| struct Printer::PrintOptions { |
| // The callsite of the public entry-point. Only Emit() sets this. |
| absl::optional<SourceLocation> 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 <bool owned> |
| struct Printer::ValueImpl { |
| private: |
| template <typename T> |
| struct IsSubImpl : std::false_type {}; |
| template <bool a> |
| struct IsSubImpl<ValueImpl<a>> : std::true_type {}; |
| |
| public: |
| using StringType = std::conditional_t<owned, std::string, absl::string_view>; |
| // These callbacks return false if this is a recursive call. |
| using Callback = std::function<bool()>; |
| using StringOrCallback = absl::variant<StringType, Callback>; |
| |
| ValueImpl() = default; |
| |
| // This is a template to avoid colliding with the copy constructor below. |
| template <typename Value, |
| typename = std::enable_if_t< |
| !IsSubImpl<absl::remove_cvref_t<Value>>::value>> |
| ValueImpl(Value&& value) // NOLINT |
| : value(ToStringOrCallback(std::forward<Value>(value), Rank2{})) { |
| if (absl::holds_alternative<Callback>(this->value)) { |
| consume_after = ";,"; |
| } |
| } |
| |
| // Copy ctor/assign allow interconversion of the two template parameters. |
| template <bool that_owned> |
| ValueImpl(const ValueImpl<that_owned>& that) { // NOLINT |
| *this = that; |
| } |
| |
| template <bool that_owned> |
| ValueImpl& operator=(const ValueImpl<that_owned>& that); |
| |
| const StringType* AsString() const { |
| return absl::get_if<StringType>(&value); |
| } |
| |
| const Callback* AsCallback() const { return absl::get_if<Callback>(&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 <typename Cb, typename = decltype(std::declval<Cb&&>()())> |
| 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 <bool owned> |
| template <bool that_owned> |
| Printer::ValueImpl<owned>& Printer::ValueImpl<owned>::operator=( |
| const ValueImpl<that_owned>& 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<const void*>(this) == static_cast<const void*>(&that)) { |
| return *this; |
| } |
| |
| using ThatStringType = typename ValueImpl<that_owned>::StringType; |
| |
| if (auto* str = absl::get_if<ThatStringType>(&that.value)) { |
| value = StringType(*str); |
| } else { |
| value = absl::get<Callback>(that.value); |
| } |
| |
| consume_after = that.consume_after; |
| consume_parens_if_empty = that.consume_parens_if_empty; |
| return *this; |
| } |
| |
| template <bool owned> |
| template <typename Cb, typename /*Sfinae*/> |
| auto Printer::ValueImpl<owned>::ToStringOrCallback(Cb&& cb, Rank2) |
| -> StringOrCallback { |
| return Callback( |
| [cb = std::forward<Cb>(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<int> path; |
| std::string file_path; |
| absl::optional<AnnotationCollector::Semantic> semantic; |
| |
| // AnnotationRecord's constructors are *not* marked as explicit, |
| // specifically so that it is possible to construct a |
| // map<string, AnnotationRecord> by writing |
| // |
| // {{"foo", my_cool_descriptor}, {"bar", "file.proto"}} |
| |
| template < |
| typename String, |
| std::enable_if_t<std::is_convertible<const String&, std::string>::value, |
| int> = 0> |
| AnnotationRecord( // NOLINT(google-explicit-constructor) |
| const String& file_path, |
| absl::optional<AnnotationCollector::Semantic> semantic = absl::nullopt) |
| : file_path(file_path), semantic(semantic) {} |
| |
| template <typename Desc, |
| // This SFINAE clause excludes char* from matching this |
| // constructor. |
| std::enable_if_t<std::is_class<Desc>::value, int> = 0> |
| AnnotationRecord( // NOLINT(google-explicit-constructor) |
| const Desc* desc, |
| absl::optional<AnnotationCollector::Semantic> semantic = absl::nullopt) |
| : file_path(desc->file()->name()), semantic(semantic) { |
| desc->GetLocationPath(&path); |
| } |
| }; |
| |
| class Printer::Sub { |
| public: |
| template <typename Value> |
| Sub(std::string key, Value&& value) |
| : key_(std::move(key)), |
| value_(std::forward<Value>(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<AnnotationRecord> annotation_; |
| }; |
| |
| template <typename Map> |
| auto Printer::WithVars(const Map* vars) { |
| var_lookups_.emplace_back( |
| [vars](absl::string_view var) -> absl::optional<ValueView> { |
| auto it = vars->find(ToStringKey<Map>(var)); |
| if (it == vars->end()) { |
| return absl::nullopt; |
| } |
| return ValueView(it->second); |
| }); |
| return absl::MakeCleanup([this] { var_lookups_.pop_back(); }); |
| } |
| |
| template <typename Map, typename, typename /*Sfinae*/> |
| auto Printer::WithVars(Map&& vars) { |
| var_lookups_.emplace_back( |
| [vars = std::forward<Map>(vars)]( |
| absl::string_view var) -> absl::optional<ValueView> { |
| auto it = vars.find(ToStringKey<Map>(var)); |
| if (it == vars.end()) { |
| return absl::nullopt; |
| } |
| return ValueView(it->second); |
| }); |
| return absl::MakeCleanup([this] { var_lookups_.pop_back(); }); |
| } |
| |
| template <typename Map> |
| auto Printer::WithAnnotations(const Map* vars) { |
| annotation_lookups_.emplace_back( |
| [vars](absl::string_view var) -> absl::optional<AnnotationRecord> { |
| auto it = vars->find(ToStringKey<Map>(var)); |
| if (it == vars->end()) { |
| return absl::nullopt; |
| } |
| return AnnotationRecord(it->second); |
| }); |
| return absl::MakeCleanup([this] { annotation_lookups_.pop_back(); }); |
| } |
| |
| template <typename Map> |
| auto Printer::WithAnnotations(Map&& vars) { |
| annotation_lookups_.emplace_back( |
| [vars = std::forward<Map>(vars)]( |
| absl::string_view var) -> absl::optional<AnnotationRecord> { |
| auto it = vars.find(ToStringKey<Map>(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 <typename Map> |
| 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 <typename... Args> |
| 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<absl::string_view, absl::string_view> 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 <typename Desc> |
| void Printer::Annotate(absl::string_view begin_varname, |
| absl::string_view end_varname, const Desc* descriptor, |
| absl::optional<AnnotationCollector::Semantic> semantic) { |
| if (options_.annotation_collector == nullptr) { |
| return; |
| } |
| |
| std::vector<int> path; |
| descriptor->GetLocationPath(&path); |
| Annotate(begin_varname, end_varname, descriptor->file()->name(), path, |
| semantic); |
| } |
| |
| template <typename Map> |
| void Printer::FormatInternal(absl::Span<const std::string> 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<const Sub> vars, |
| bool allow_callbacks) { |
| absl::flat_hash_map<std::string, Value> var_map; |
| var_map.reserve(vars.size()); |
| |
| absl::flat_hash_map<std::string, AnnotationRecord> 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<ValueView> { |
| 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<AnnotationRecord> { |
| 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<const Sub> 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__ |