| // Protocol Buffers - Google's data interchange format |
| // Copyright 2008 Google Inc. All rights reserved. |
| // https://developers.google.com/protocol-buffers/ |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| // 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 <map> |
| #include <string> |
| #include <type_traits> |
| #include <utility> |
| #include <vector> |
| |
| #include "google/protobuf/stubs/logging.h" |
| #include "google/protobuf/stubs/common.h" |
| #include "absl/cleanup/cleanup.h" |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/functional/function_ref.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>; |
| |
| 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; |
| |
| // TODO(gerbens) 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; |
| // } |
| template <typename AnnotationProto> |
| class AnnotationProtoCollector : public AnnotationCollector { |
| 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 { |
| 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); |
| } |
| |
| 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 substiting 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 |
| // 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({{"xzy", 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 is referenced in the format string that 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(). |
| // |
| // # 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; |
| // } |
| // }; |
| // |
| // # 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(b/242326974): 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: |
| // 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() { return "<unknown>"; } |
| int line() { return 0; } |
| }; |
| |
| struct AnnotationRecord { |
| std::vector<int> path; |
| std::string file_path; |
| |
| // 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) |
| : file_path(file_path) {} |
| |
| template <typename Desc, |
| // This SFINAE clause excludes char* from matching this |
| // constructor. |
| std::enable_if_t<std::is_class<Desc>::value, int> = 0> |
| AnnotationRecord(const Desc* desc) // NOLINT(google-explicit-constructor) |
| : file_path(desc->file()->name()) { |
| desc->GetLocationPath(&path); |
| } |
| }; |
| |
| // Sink type for constructing values to pass to WithVars() and Emit(). |
| template <typename K, bool allow_callbacks> |
| struct VarDefinition { |
| using StringOrCallback = absl::variant<std::string, std::function<void()>>; |
| |
| template <typename Key, typename Value> |
| VarDefinition(Key&& key, Value&& value) |
| : key(std::forward<Key>(key)), |
| value(ToStringOrCallback(std::forward<Value>(value), Rank2{})), |
| annotation(absl::nullopt) {} |
| |
| // NOTE: This is an overload rather than taking optional<AnnotationRecord> |
| // with a default argument of nullopt, because we want to pick up |
| // AnnotationRecord's user-defined conversions. Because going from |
| // e.g. Descriptor* -> optional<AnnotationRecord> requires two user-defined |
| // conversions, this does not work. |
| template <typename Key, typename Value> |
| VarDefinition(Key&& key, Value&& value, AnnotationRecord annotation) |
| : key(std::forward<Key>(key)), |
| value(ToStringOrCallback(std::forward<Value>(value), Rank2{})), |
| annotation(std::move(annotation)) {} |
| |
| K key; |
| StringOrCallback value; |
| absl::optional<AnnotationRecord> annotation; |
| |
| 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 <bool allowed = allow_callbacks> |
| StringOrCallback ToStringOrCallback(std::function<void()> cb, Rank2) { |
| static_assert( |
| allowed, "callback-typed variables are not allowed in this location"); |
| return cb; |
| } |
| |
| // Separate from the AlphaNum overload to avoid copies when taking strings |
| // by value. |
| StringOrCallback ToStringOrCallback(std::string s, Rank1) { return s; } |
| |
| StringOrCallback ToStringOrCallback(const absl::AlphaNum& s, Rank0) { |
| return std::string(s.Piece()); |
| } |
| }; |
| |
| public: |
| static constexpr char kDefaultVariableDelimiter = '$'; |
| static constexpr absl::string_view kProtocCodegenTrace = |
| "PROTOC_CODEGEN_TRACE"; |
| |
| // 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 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) : Printer(output, Options{}) {} |
| |
| // 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(output, Options{variable_delimiter, annotation_collector}) {} |
| |
| 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) { |
| var_lookups_.emplace_back([vars](absl::string_view var) -> LookupResult { |
| auto it = vars->find(std::string(var)); |
| if (it == vars->end()) { |
| return absl::nullopt; |
| } |
| return absl::string_view(it->second); |
| }); |
| return absl::MakeCleanup([this] { var_lookups_.pop_back(); }); |
| } |
| |
| // Pushes a new variable lookup frame that stores `vars` by value. |
| // |
| // When writing `WithVars({...})`, 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, std::string>, |
| std::enable_if_t<!std::is_pointer<Map>::value, int> = 0> |
| auto WithVars(Map&& vars) { |
| var_lookups_.emplace_back([vars = std::forward<Map>(vars)]( |
| absl::string_view var) -> LookupResult { |
| auto it = vars.find(std::string(var)); |
| if (it == vars.end()) { |
| return absl::nullopt; |
| } |
| return absl::string_view(it->second); |
| }); |
| return absl::MakeCleanup([this] { var_lookups_.pop_back(); }); |
| } |
| |
| auto WithVars(std::initializer_list< |
| VarDefinition<std::string, /*allow_callbacks=*/false>> |
| 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) { |
| annotation_lookups_.emplace_back( |
| [vars](absl::string_view var) -> absl::optional<AnnotationRecord> { |
| auto it = vars->find(std::string(var)); |
| if (it == vars->end()) { |
| return absl::nullopt; |
| } |
| return AnnotationRecord(it->second); |
| }); |
| return absl::MakeCleanup([this] { annotation_lookups_.pop_back(); }); |
| } |
| |
| // 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) { |
| annotation_lookups_.emplace_back( |
| [vars = std::forward<Map>(vars)]( |
| absl::string_view var) -> absl::optional<AnnotationRecord> { |
| auto it = vars.find(std::string(var)); |
| if (it == vars.end()) { |
| return absl::nullopt; |
| } |
| return AnnotationRecord(it->second); |
| }); |
| return absl::MakeCleanup([this] { annotation_lookups_.pop_back(); }); |
| } |
| |
| // 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()) { |
| Emit({}, format, loc); |
| } |
| |
| // 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(std::initializer_list< |
| VarDefinition<absl::string_view, /*allow_callbacks=*/true>> |
| 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(b/242326974): Deprecate these APIs. |
| |
| template <typename Map = absl::flat_hash_map<std::string, std::string>> |
| void Print(const Map& vars, absl::string_view text) { |
| PrintOptions opts; |
| opts.checks_are_debug_only = true; |
| opts.use_substitution_map = true; |
| opts.allow_digit_substitions = false; |
| |
| auto pop = WithVars(&vars); |
| PrintImpl(text, {}, opts); |
| } |
| |
| template <typename... Args> |
| void 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<std::string, std::string> map; |
| map.reserve(sizeof...(args) / 2); |
| for (size_t i = 0; i < sizeof...(args); i += 2) { |
| map.emplace(std::string(vars[i]), std::string(vars[i + 1])); |
| } |
| |
| Print(map, text); |
| } |
| |
| // 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) { |
| Annotate(varname, varname, descriptor); |
| } |
| |
| // 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 SomeDescriptor> |
| void Annotate(absl::string_view begin_varname, absl::string_view end_varname, |
| const SomeDescriptor* descriptor) { |
| if (options_.annotation_collector == nullptr) { |
| return; |
| } |
| |
| std::vector<int> path; |
| descriptor->GetLocationPath(&path); |
| Annotate(begin_varname, end_varname, descriptor->file()->name(), path); |
| } |
| |
| // 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) { |
| Annotate(varname, varname, file_name); |
| } |
| |
| // 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) { |
| if (options_.annotation_collector == nullptr) { |
| return; |
| } |
| |
| Annotate(begin_varname, end_varname, file_name, {}); |
| } |
| |
| // 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. |
| void FormatInternal(absl::Span<const std::string> args, |
| const std::map<std::string, std::string>& 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); |
| } |
| |
| private: |
| // Options for PrintImpl(). |
| struct 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_substitions = 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; |
| }; |
| |
| // 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); |
| |
| // 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. This |
| // is a private function to avoid users being able to set `allow_callbacks`. |
| template <typename K, bool allow_callbacks> |
| auto WithDefs(std::initializer_list<VarDefinition<K, allow_callbacks>> vars); |
| |
| // 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; |
| |
| using LookupResult = |
| absl::optional<absl::variant<absl::string_view, std::function<void()>>>; |
| |
| std::vector<std::function<LookupResult(absl::string_view)>> var_lookups_; |
| |
| std::vector< |
| std::function<absl::optional<AnnotationRecord>(absl::string_view)>> |
| annotation_lookups_; |
| |
| // A map from variable name to [start, end) offsets in the output buffer. |
| // |
| // This stores the data looked up by GetSubstitutionRange(). |
| std::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_; |
| }; |
| |
| template <typename K, bool allow_callbacks> |
| auto Printer::WithDefs( |
| std::initializer_list<VarDefinition<K, allow_callbacks>> vars) { |
| absl::flat_hash_map<K, absl::variant<std::string, std::function<void()>>> |
| var_map; |
| var_map.reserve(vars.size()); |
| |
| absl::flat_hash_map<K, AnnotationRecord> annotation_map; |
| |
| for (auto& var : vars) { |
| auto result = var_map.insert({var.key, var.value}); |
| GOOGLE_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) -> LookupResult { |
| auto it = map.find(var); |
| if (it == map.end()) { |
| return absl::nullopt; |
| } |
| if (auto* str = absl::get_if<std::string>(&it->second)) { |
| return absl::string_view(*str); |
| } |
| |
| auto* f = absl::get_if<std::function<void()>>(&it->second); |
| GOOGLE_CHECK(f != nullptr); |
| return *f; |
| }); |
| |
| 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( |
| std::initializer_list<VarDefinition<std::string, /*allow_callbacks=*/false>> |
| vars) { |
| return WithDefs(vars); |
| } |
| } // namespace io |
| } // namespace protobuf |
| } // namespace google |
| |
| #include "google/protobuf/port_undef.inc" |
| |
| #endif // GOOGLE_PROTOBUF_IO_PRINTER_H__ |