text: // Copyright 2019 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "src/developer/debug/zxdb/console/format_node_console.h"
  
  #include "src/developer/debug/zxdb/common/ref_ptr_to.h"
  #include "src/developer/debug/zxdb/common/string_util.h"
  #include "src/developer/debug/zxdb/console/command_utils.h"
  #include "src/developer/debug/zxdb/console/format_name.h"
  #include "src/developer/debug/zxdb/console/string_util.h"
  #include "src/developer/debug/zxdb/expr/format.h"
  #include "src/developer/debug/zxdb/expr/format_node.h"
  #include "src/lib/fxl/memory/ref_counted.h"
  
  namespace zxdb {
  
  namespace {
  
  // Provides a way to execute a single callback when a set of deferred actions is complete.
  class AggregateDeferredCallback : public fxl::RefCountedThreadSafe<AggregateDeferredCallback> {
   public:
    // Returns a subordinate callback, the completion of which will contribute to the outer callback.
    fit::deferred_callback MakeSubordinate() {
      // This callback exists only to hold the bound reference to the outer callback.
      return fit::defer_callback([ref = RefPtrTo(this)]() {});
    }
  
   private:
    FRIEND_REF_COUNTED_THREAD_SAFE(AggregateDeferredCallback);
    FRIEND_MAKE_REF_COUNTED(AggregateDeferredCallback);
  
    explicit AggregateDeferredCallback(fit::deferred_callback cb) : outer_(std::move(cb)) {}
  
    fit::deferred_callback outer_;
  };
  
  // Some state needs to be tracked recursively as we iterate through the tree.
  struct RecursiveState {
    explicit RecursiveState(const ConsoleFormatOptions& opts) : options(opts) {}
  
    // Returns a modified version of the state for one additional level of recursion, advancing
    // both indent and tree depth.
    RecursiveState Advance() const;
  
    // Advances the tree depth but not the visual nesting depth.
    RecursiveState AdvanceNoIndent() const;
  
    // Returns true if the combination of options means the type should be shown.
    bool TypeForcedOn() const {
      return !inhibit_one_type && options.verbosity == ConsoleFormatOptions::Verbosity::kAllTypes;
    }
  
    // Returns true if the current item should be formatted in expanded tree mode or in one line.
    bool ShouldExpand() const {
      return options.wrapping == ConsoleFormatOptions::Wrapping::kExpanded ||
             (options.wrapping == ConsoleFormatOptions::Wrapping::kSmart && smart_indent_is_expanded);
    }
  
    int GetIndentAmount() const {
      if (!ShouldExpand())
        return 0;
      return options.indent_amount * indent_depth;
    }
  
    // Returns the string to indent a node at the given level.
    std::string GetIndentString() const { return std::string(GetIndentAmount(), ' '); }
  
    // Returns true if items at this level should be elided because the printing depth is too deep.
    bool DepthTooDeep() const { return tree_depth >= options.max_depth; }
  
    const ConsoleFormatOptions options;
  
    // Forces various information off for exactly one level of printing. Use for array printing, for
    // example, to avoid duplicating the type information for every entry.
    bool inhibit_one_name = false;
    bool inhibit_one_type = false;
  
    // How deep in the node tree we are, the top level is 0.
    int tree_depth = 0;
  
    // How many levels of visual indent we are, the top level is 0. This is often the same as the
    // tree depth but some things like pointer resolution are different levels of the tree but
    // are presented as the same visual level.
    int indent_depth = 0;
  
    // Number of pointers we've expanded so far in this level of recursion.
    int pointer_depth = 0;
  
    // Controls what mode of smart indenting (single-line or expanded) the current node should be
    // formatted in.
    bool smart_indent_is_expanded = true;
  };
  
  RecursiveState RecursiveState::Advance() const {
    RecursiveState result = *this;
    result.inhibit_one_name = false;
    result.inhibit_one_type = false;
  
    result.tree_depth++;
    result.indent_depth++;
  
    return result;
  }
  
  RecursiveState RecursiveState::AdvanceNoIndent() const {
    RecursiveState result = Advance();
    result.indent_depth--;  // Undo visual indent from Advance().
    return result;
  }
  
  void RecursiveDescribeFormatNode(FormatNode* node, fxl::RefPtr<EvalContext> context,
                                   const RecursiveState& state, fit::deferred_callback cb) {
    if (state.tree_depth == state.options.max_depth)
      return;  // Reached max depth, give up.
  
    auto on_complete = fit::defer_callback([weak_node = node->GetWeakPtr(), context,
                                            cb = std::move(cb), state = state.Advance()]() mutable {
      // Description is complete.
      if (!weak_node || weak_node->children().empty())
        return;
  
      // Check for pointer expansion to avoid recursing into too many.
      if (weak_node->description_kind() == FormatNode::kPointer) {
        if (state.pointer_depth == state.options.pointer_expand_depth)
          return;  // Don't recurse into another level of pointer.
        state.pointer_depth++;
      }
  
      // Recurse into children.
      auto aggregator = fxl::MakeRefCounted<AggregateDeferredCallback>(std::move(cb));
      for (const auto& child : weak_node->children())
        RecursiveDescribeFormatNode(child.get(), context, state, aggregator->MakeSubordinate());
    });
  
    if (node->state() != FormatNode::kDescribed)
      FillFormatNodeDescription(node, state.options, context, std::move(on_complete));
  }
  
  bool IsRust(const FormatNode* node) {
    if (!node->value().type())
      return false;
    return node->value().type()->GetLanguage() == DwarfLang::kRust;
  }
  
  // Forward decls, see the implementation for comments.
  OutputBuffer DoFormatNode(const FormatNode* node, const RecursiveState& state, int line_start_char);
  OutputBuffer DoFormatNodeOneWay(const FormatNode* node, const RecursiveState& state,
                                  int indent_amount);
  
  // Get a possibly-elided version of the type name for a medium verbosity level.
  std::string GetElidedTypeName(const RecursiveState& state, const std::string& name) {
    if (state.options.verbosity != ConsoleFormatOptions::Verbosity::kMinimal)
      return name;  // No eliding except in minimal mode.
  
    // Pick different thresholds depending on if we're doing multiline or not. The threshold and
    // the eliding length are different so we don't elide one or two characters.
    size_t always_allow_below = 64;
    size_t elide_to = 50;
    if (!state.ShouldExpand()) {
      always_allow_below /= 2;
      elide_to /= 2;
    }
  
    // Names shorter than this are always displayed in full.
    if (name.size() <= always_allow_below)
      return name;
  
    return name.substr(0, elide_to) + "…";
  }
  
  // Rust collections (structs) normally have the type name before the bracket like "MyStruct{...}".
  // This function appends this name.
  //
  // For some struct types including common standard containers the names can get very long. As a
  // result in "minimal" verbosity mode we never show the namespaces and also elide template
  // parameters when the template is long.
  void AppendRustCollectionName(const FormatNode* node, const RecursiveState& state,
                                OutputBuffer* out) {
    if (!IsRust(node) || !node->value().type())
      return;
  
    if (state.options.verbosity != ConsoleFormatOptions::Verbosity::kMinimal) {
      // Use the full identifier name in the more verbose modes.
      FormatIdentifierOptions opts;
      opts.show_global_qual = false;
      opts.elide_templates = false;
      opts.bold_last = false;
      out->Append(FormatIdentifier(node->value().type()->GetIdentifier(), opts));
      return;
    }
  
    // Special-case "Vec" with the macro that's normally used and omit the type.
    if (StringBeginsWith(node->type(), "alloc::vec::Vec<")) {
      out->Append("vec!");
      return;
    }
  
    // In minimal mode, extract just the last component of the identifier.
    const Identifier& ident = node->value().type()->GetIdentifier();
    if (ident.empty())
      return;
    ParsedIdentifier parsed =
        ToParsedIdentifier(Identifier(IdentifierQualification::kRelative, ident.components().back()));
  
    if (parsed.components()[0].has_template()) {
      // Some containers have very long template names. Since Rust structs always have the name, the
      // lengths can get a little bit nuts. Elide the template contents when too long by converting
      // all template parameters to one string and eliding.
      const auto& template_contents = parsed.components()[0].template_contents();
      std::string template_string;
      for (size_t i = 0; i < template_contents.size(); i++) {
        if (i > 0)
          template_string += ", ";
        template_string += template_contents[i];
      }
  
      // Replace the template parameters with our single string, elided if necessary.
      parsed.components()[0] = ParsedIdentifierComponent(parsed.components()[0].name(),
                                                         {GetElidedTypeName(state, template_string)});
    }
  
    FormatIdentifierOptions ident_opts;
    ident_opts.show_global_qual = false;
    ident_opts.elide_templates = false;
    ident_opts.bold_last = false;
    out->Append(FormatIdentifier(parsed, ident_opts));
  }
  
  // Writes the suffix for when there are multiple items in a collection or an array.
  void AppendItemSuffix(const RecursiveState& state, bool last_item, OutputBuffer* out) {
    if (state.ShouldExpand())
      out->Append("\n");  // Expanded children always end with a newline.
    else if (!last_item)
      out->Append(", ");  // Get commas for everything but the last.
  }
  
  // Returns true if type information should be displayed before the variable name, as in
  //   <TYPE> name = value
  bool ShouldPrependTypeNameBeforeName(const FormatNode* node, const RecursiveState& state) {
    if (!state.TypeForcedOn())
      return false;  // Never show types unless requested.
  
    if (node->type().empty())
      return false;  // Don't show empty types.
  
    if (!IsRust(node))
      return true;  // Non-Rust code always gets the type.
  
    if (node->description_kind() == FormatNode::kCollection ||
        node->description_kind() == FormatNode::kArray ||
        node->description_kind() == FormatNode::kRustTuple ||
        node->description_kind() == FormatNode::kRustTupleStruct) {
      // Rust structs and tuple structs are special. They encode the type after the variable, so:
      // "foo = StructName{...}" and we don't want to prepend it before "foo". But this doesn't happen
      // for tuples.
      //
      // Rust tuples don't have the name shown ever. The individual members will have their types
      // displayed which will encode the type of the tuple.
      return false;
    }
  
    return true;
  }
  
  // Appends the formatted name and "=" as needed by this node.
  void AppendNodeNameAndType(const FormatNode* node, const RecursiveState& state, OutputBuffer* out) {
    if (ShouldPrependTypeNameBeforeName(node, state))
      out->Append(Syntax::kComment, "(" + node->type() + ") ");
  
    if (!state.inhibit_one_name && !node->name().empty()) {
      // Node name. Base class names are less important so get dimmed. Especially STL base classes
      // can get very long so we also elide them unless verbose mode is turned on.
      if (node->child_kind() == FormatNode::kBaseClass) {
        out->Append(Syntax::kComment, GetElidedTypeName(state, node->name()));
      } else {
        // Normal variable-style node name.
        out->Append(Syntax::kVariable, node->name());
      }
  
      // Rust uses colons for most things, while C uses equals. The exception for Rust is local
      // variables which "=" to better match how the variables are declared.
      if (IsRust(node) && node->child_kind() != FormatNode::kVariable)
        out->Append(": ");
      else
        out->Append(" = ");
    }
  }
  
  // Writes the node's children. The caller sets up how it wants the children to be formatted by
  // passing both the recursive state for the node, and the state it wants to use for the children.
  //
  // The opening and closing strings are used to wrap the contents, e.g. "{" and "}". If these are
  // empty it's assumed that the first line doesn't need a newline after it in multiline mode
  // (normally the opening character would be followed by a newline and an indent in multiline mode).
  void AppendNodeChildren(const FormatNode* node, const RecursiveState& node_state,
                          const std::string& opening, const std::string& closing,
                          const RecursiveState& child_state, OutputBuffer* out) {
    out->Append(opening);
  
    if (child_state.DepthTooDeep()) {
      // Don't print the child names if those children will be elided. Otherwise this prints a
      // whole struct out with no data: "{foo=…, bar=…, baz=…}" which is wasteful of space and not
      // helpful.
      out->Append(Syntax::kComment, "…");
      out->Append(closing);
      return;
    }
  
    // Special-case empty ones because we never want wrapping.
    if (node->children().empty()) {
      out->Append(closing);
      return;
    }
  
    if (!opening.empty() && node_state.ShouldExpand())
      out->Append("\n");
  
    int child_indent = child_state.GetIndentAmount();
    for (size_t i = 0; i < node->children().size(); i++) {
      const FormatNode* child = node->children()[i].get();
  
      if (child->state() == FormatNode::kEmpty) {
        // Arrays can have "empty" nodes which use the name to indicate clipping ("...").
        if (!child->name().empty())
          out->Append(Syntax::kComment, child->name());
      } else {
        out->Append(DoFormatNode(child, child_state, child_indent));
      }
  
      // Separator (comma or newline).
      AppendItemSuffix(node_state, i + 1 == node->children().size(), out);
    }
  
    out->Append(node_state.GetIndentString() + closing);
  }
  
  OutputBuffer DoFormatArrayOrTupleNode(const FormatNode* node, const RecursiveState& state) {
    OutputBuffer out;
  
    RecursiveState child_state = state.Advance();
    // In multiline mode, show the names of the array indices ("[0] = ...").
    child_state.inhibit_one_name = !state.ShouldExpand();
  
    if (node->description_kind() == FormatNode::kArray) {
      // Arrays all have the same type so don't show the type for every child.
      child_state.inhibit_one_type = true;
  
      if (IsRust(node)) {
        // Rust sequences use "[...]".
        AppendRustCollectionName(node, state, &out);
        AppendNodeChildren(node, state, "[", "]", child_state, &out);
      } else {
        AppendNodeChildren(node, state, "{", "}", child_state, &out);
      }
    } else {
      // Rust tuple or tuple struct. These should not be empty.
      if (node->description_kind() == FormatNode::kRustTupleStruct) {
        // Tuple structs get the name, regular tuples have none.
        AppendRustCollectionName(node, state, &out);
      }
  
      // Tuples of length 1 never show the child names, even in expanded mode because it's not
      // helpful and looks weird.o
      //
      // We show the indices of tuple members in expanded mode for larger tuples even though
      // Rust wouldn't do that because highly nested data structures can get very difficult to
      // follow. Something like a closure or a generator can go on for >100 lines and have many
      // tuple members (>10). Without these indices the structure is not interpretable. This doesn't
      // come up when using println in the language very often because normally these types of
      // tuples are not printed.
      if (node->children().size() == 1)
        child_state.inhibit_one_name = true;
  
      AppendNodeChildren(node, state, "(", ")", child_state, &out);
    }
  
    return out;
  }
  
  OutputBuffer DoFormatCollectionNode(const FormatNode* node, const RecursiveState& state) {
    OutputBuffer out;
  
    // Rust structs are always prefixed with the struct name. This does nothing for non-Rust.
    AppendRustCollectionName(node, state, &out);
  
    // Special-case empty collections because we never want wrapping.
    if (node->children().empty()) {
      // Rust empty structs have no brackets.
      if (!IsRust(node))
        out.Append("{}");
      return out;
    }
  
    AppendNodeChildren(node, state, "{", "}", state.Advance(), &out);
    return out;
  }
  
  OutputBuffer DoFormatPointerNode(const FormatNode* node, const RecursiveState& state) {
    OutputBuffer out;
  
    // When type information is forced on, the type will have already been printed. Otherwise we print
    // a "(*)" to indicate the value is a pointer.
    if (!state.TypeForcedOn())
      out.Append(Syntax::kComment, "(*)");
  
    // Pointers will have a child node that expands to the pointed-to value. If this is in a
    // "described" state with no error, then show the data.
    //
    // Don't show errors dereferencing pointers here since for long structure listings with
    // potentially multiple bad pointers, these get very verbose. The user can print the specific
    // value and see the error if desired.
    if (node->children().empty() || node->children()[0]->state() != FormatNode::kDescribed ||
        node->children()[0]->err().has_error()) {
      // Just have the pointer address.
      out.Append(node->description());
    } else {
      // Have the pointed-to data, dim the address and show the data. Omit types because we already
      // printed the pointer type. We do not want to indent another level.
      RecursiveState child_state = state.AdvanceNoIndent();
      child_state.inhibit_one_name = true;
      child_state.inhibit_one_type = true;
  
      out.Append(Syntax::kComment, node->description() + " " + GetRightArrow() + " ");
      // Use the "one way" version to propagate our formatting mode. This node cant't get different
      // formatting than its child.
      out.Append(DoFormatNodeOneWay(node->children()[0].get(), child_state, 0));
    }
    return out;
  }
  
  OutputBuffer DoFormatReferenceNode(const FormatNode* node, const RecursiveState& state) {
    // References will have a child node that expands to the referenced value. If this is in a
    // "described" state, then we have the data and should show it. Otherwise omit this.
    if (node->children().empty() || node->children()[0]->state() != FormatNode::kDescribed) {
      // The caller should have expanded the references if it wants them shown. If not, display
      // a placeholder with the address. Show the whole thing dimmed to help indicate this isn't the
      // actual value.
      return OutputBuffer(Syntax::kComment, "(&)" + node->description());
    }
  
    // Have the pointed-to data, just show the value. We do not want to indent another level.
    RecursiveState child_state = state.AdvanceNoIndent();
    child_state.inhibit_one_name = true;
    child_state.inhibit_one_type = true;
  
    // Use the "one way" version to propagate our formatting mode. This node cant't get different
    // formatting than its child.
    return DoFormatNodeOneWay(node->children()[0].get(), child_state, 0);
  }
  
  // A Rust enum is currently like a reference in that is has a child that's the resolved type.
  // A resolved enum type will be a TupleStruct (unnamed members) or a Struct (named members). Enums
  // with no members are encoded as empty structs which the struct printing code will handle.
  OutputBuffer DoFormatRustEnum(const FormatNode* node, const RecursiveState& state) {
    if (node->children().empty()) {
      // The caller should have expanded the enum if it wants it to be shown. If not, display the
      // description which will generally be the short name of the enum.
      return OutputBuffer(Syntax::kComment, node->description());
    }
  
    // Have the pointed-to data, just show the value. We do not want to indent another level. The
    // type is also hidden because it will be a generated type. If requested, the type of the overall
    // enum will have already been printed.
    RecursiveState child_state = state.AdvanceNoIndent();
    child_state.inhibit_one_name = true;
    child_state.inhibit_one_type = true;
  
    // Use the "one way" version to propagate our formatting mode. This node can't get different
    // formatting than its child.
    return DoFormatNodeOneWay(node->children()[0].get(), child_state, 0);
  }
  
  // A generic node with one child for things like std::optional or std::atomic.
  OutputBuffer DoFormatWrapper(const FormatNode* node, const RecursiveState& state) {
    if (node->children().empty())
      return OutputBuffer(node->description() + node->wrapper_prefix() + node->wrapper_suffix());
  
    RecursiveState child_state = state.AdvanceNoIndent();
  
    OutputBuffer out;
    out.Append(node->description());
    out.Append(node->wrapper_prefix());
    out.Append(DoFormatNodeOneWay(node->children()[0].get(), child_state, 0));
    out.Append(node->wrapper_suffix());
    return out;
  }
  
  // Appends the description for a normal node (number or whatever).
  OutputBuffer DoFormatStandardNode(const FormatNode* node, const RecursiveState& state) {
    return OutputBuffer(node->description());
  }
  
  // Groups have no heading, they're always just a list of children.
  OutputBuffer DoFormatGroupNode(const FormatNode* node, const RecursiveState& state) {
    OutputBuffer out;
    AppendNodeChildren(node, state, std::string(), std::string(), state, &out);
    return out;
  }
  
  // Inner implementation for DoFormatNode() (see that for more). This does the actual formatting one
  // time according to the current formatting options and state.
  //
  // Some callers may want to call this directly rather than DoFormatNode() if they want to bypass the
  // two-pass formatting. This is useful for pointers which are two nodes (when auto dereferenced) but
  // appear as one and the expansion mode of the outer should be passed transparently to the nested
  // node.
  OutputBuffer DoFormatNodeOneWay(const FormatNode* node, const RecursiveState& state,
                                  int indent_amount) {
    OutputBuffer out;
  
    if (indent_amount)
      out.Append(std::string(indent_amount, ' '));
  
    AppendNodeNameAndType(node, state, &out);
    if (state.DepthTooDeep() || node->state() != FormatNode::kDescribed) {
      // Hit max depth, give up.
      out.Append(Syntax::kComment, "…");
    } else if (node->err().has_error()) {
      // Write the error.
      out.Append(Syntax::kComment, "<" + node->err().msg() + ">");
    } else {
      // Normal formatting.
      switch (node->description_kind()) {
        case FormatNode::kNone:
        case FormatNode::kBaseType:
        case FormatNode::kFunctionPointer:
        case FormatNode::kOther:
        case FormatNode::kString:
          // All these things just print the description only.
          out.Append(DoFormatStandardNode(node, state));
          break;
        case FormatNode::kGroup:
          out.Append(DoFormatGroupNode(node, state));
          break;
        case FormatNode::kArray:
        case FormatNode::kRustTuple:
        case FormatNode::kRustTupleStruct:
          out.Append(DoFormatArrayOrTupleNode(node, state));
          break;
        case FormatNode::kCollection:
          out.Append(DoFormatCollectionNode(node, state));
          break;
        case FormatNode::kPointer:
          out.Append(DoFormatPointerNode(node, state));
          break;
        case FormatNode::kReference:
          out.Append(DoFormatReferenceNode(node, state));
          break;
        case FormatNode::kRustEnum:
          out.Append(DoFormatRustEnum(node, state));
          break;
        case FormatNode::kWrapper:
          out.Append(DoFormatWrapper(node, state));
          break;
      }
    }
  
    return out;
  }
  
  // Formats one node. This layer provides the smart wrapping logic where we may try to format an
  // item twice, first to see if it fits on one line, and possibly again ig it doesn't.
  //
  // The caller of this function is in charge of computing (but not inserting) the appropriate level
  // of indent and also appending trailing newlines if necessary.
  //
  // The caller must compute the indenting amount because whether or not there's indenting for this
  // node depends the expansion level of the caller, not this node. (This item could be in single-line
  // mode and still be indented if the caller is expanded.)
  OutputBuffer DoFormatNode(const FormatNode* node, const RecursiveState& state, int indent_amount) {
    if (state.options.wrapping != ConsoleFormatOptions::Wrapping::kSmart)
      return DoFormatNodeOneWay(node, state, indent_amount);  // Nothing fancy to do.
  
    // Nodes with few children we try to fit on one line if possible in smart mode. Generally
    // structures with more than 4 children will always spill over to multiline mode, so this check is
    // more of an optimization to avoid trying and failing to format the whole thing as a single line
    // first. Even if it fits, many members on a line are not readable.
    if (state.smart_indent_is_expanded && node->children().size() <= 4) {
      // The previous node was in an expanded state. First try to format this one as non-expanded.
      // to see if it fits.
      RecursiveState one_line_state = state;
      one_line_state.smart_indent_is_expanded = false;
  
      OutputBuffer one_line = DoFormatNodeOneWay(node, one_line_state, indent_amount);
      if (static_cast<int>(one_line.UnicodeCharWidth()) <= state.options.smart_indent_cols)
        return one_line;  // Fits.
  
      // Too big, format as expanded.
      return DoFormatNodeOneWay(node, state, indent_amount);
    }
  
    // The previous node was already in one-line mode, stay in that mode.
    return DoFormatNodeOneWay(node, state, indent_amount);
  }
  
  }  // namespace
  
  void DescribeFormatNodeForConsole(FormatNode* node, const ConsoleFormatOptions& options,
                                    fxl::RefPtr<EvalContext> context, fit::deferred_callback cb) {
    RecursiveDescribeFormatNode(node, context, RecursiveState(options), std::move(cb));
  }
  
  OutputBuffer FormatNodeForConsole(const FormatNode& node, const ConsoleFormatOptions& options) {
    return DoFormatNode(&node, RecursiveState(options), 0);
  }
  
  fxl::RefPtr<AsyncOutputBuffer> FormatValueForConsole(ExprValue value,
                                                       const ConsoleFormatOptions& options,
                                                       fxl::RefPtr<EvalContext> context,
                                                       const std::string& value_name) {
    auto node = std::make_unique<FormatNode>(value_name, std::move(value));
    node->set_child_kind(FormatNode::kVariable);
  
    FormatNode* node_ptr = node.get();
  
    auto out = fxl::MakeRefCounted<AsyncOutputBuffer>();
    DescribeFormatNodeForConsole(node_ptr, options, context,
                                 fit::defer_callback([node = std::move(node), options, out]() {
                                   // Asynchronous expansion is complete, now format the completed
                                   // output.
                                   out->Complete(FormatNodeForConsole(*node, options));
                                 }));
    return out;
  }
  
  fxl::RefPtr<AsyncOutputBuffer> FormatVariableForConsole(const Variable* var,
                                                          const ConsoleFormatOptions& options,
                                                          fxl::RefPtr<EvalContext> context) {
    auto out = fxl::MakeRefCounted<AsyncOutputBuffer>();
    context->GetVariableValue(
        RefPtrTo(var), [name = var->GetAssignedName(), context, options, out](ErrOrValue value) {
          if (value.has_error()) {
            // In the error case, construct a node with the error set so the formatting with other
            // types of errors is consistent.
            FormatNode err_node(name);
            err_node.SetDescribedError(value.err());
            out->Complete(FormatNodeForConsole(err_node, options));
          } else {
            out->Complete(FormatValueForConsole(value.take_value(), options, context, name));
          }
        });
    return out;
  }
  
  fxl::RefPtr<AsyncOutputBuffer> FormatExpressionsForConsole(
      const std::vector<std::string>& expressions, const ConsoleFormatOptions& options,
      fxl::RefPtr<EvalContext> context) {
    // Put all of the expressions in a "group" node and then evaluate and format this node. This will
    // allow single- and multi-line formatting for the values and also handles all of the asynchronous
    // state across all of the expressions automatically.
    auto out = fxl::MakeRefCounted<AsyncOutputBuffer>();
    auto list_node = std::make_unique<FormatNode>(FormatNode::GroupTag());
    FormatNode* list_node_ptr = list_node.get();
  
    for (const std::string& expr : expressions) {
      // Use the expression as the description.
      auto child_node = std::make_unique<FormatNode>(expr, expr);
      list_node->children().push_back(std::move(child_node));
    }
  
    DescribeFormatNodeForConsole(
        list_node_ptr, options, context,
        fit::defer_callback([list_node = std::move(list_node), options, out]() {
          RecursiveState state(options);
          out->Complete(FormatNodeForConsole(*list_node, options));
        }));
    return out;
  }
  
  }  // namespace zxdb
  