/* Output generating routines for GDB.

   Copyright (C) 1999-2025 Free Software Foundation, Inc.

   Contributed by Cygnus Solutions.
   Written by Fernando Nasser for Cygnus.

   This file is part of GDB.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#ifndef GDB_UI_OUT_H
#define GDB_UI_OUT_H

#include <vector>

#include "gdbsupport/enum-flags.h"
#include "ui-style.h"

class ui_out_level;
class ui_out_table;
struct ui_file;

/* the current ui_out */

/* FIXME: This should not be a global but something passed down from main.c
   or top.c.  */
extern struct ui_out **current_ui_current_uiout_ptr (void);
#define current_uiout (*current_ui_current_uiout_ptr ())

/* alignment enum */
enum ui_align
  {
    ui_left = -1,
    ui_center,
    ui_right,
    ui_noalign
  };

/* flags enum */
enum ui_out_flag
{
  ui_source_list = (1 << 0),
  fix_multi_location_breakpoint_output = (1 << 1),
  /* This indicates that %pF should be disallowed in a printf format
     string.  */
  disallow_ui_out_field = (1 << 2),
  fix_breakpoint_script_output = (1 << 3),
};

DEF_ENUM_FLAGS_TYPE (ui_out_flag, ui_out_flags);

/* Prototypes for ui-out API.  */

/* A result is a recursive data structure consisting of lists and
   tuples.  */

enum ui_out_type
  {
    ui_out_type_tuple,
    ui_out_type_list
  };

/* The possible kinds of fields.  */
enum class field_kind
  {
    /* "FIELD_STRING" needs a funny name to avoid clashes with tokens
       named "STRING".  See PR build/25250.  FIELD_SIGNED is given a
       similar name for consistency.  */
    FIELD_SIGNED,
    FIELD_STRING,
  };

/* The base type of all fields that can be emitted using %pF.  */

struct base_field_s
{
  const char *name;
  field_kind kind;
};

/* A signed integer field, to be passed to %pF in format strings.  */

struct signed_field_s : base_field_s
{
  LONGEST val;
};

/* Construct a temporary signed_field_s on the caller's stack and
   return a pointer to the constructed object.  We use this because
   it's not possible to pass a reference via va_args.  */

static inline signed_field_s *
signed_field (const char *name, LONGEST val,
	      signed_field_s &&tmp = {})
{
  tmp.name = name;
  tmp.kind = field_kind::FIELD_SIGNED;
  tmp.val = val;
  return &tmp;
}

/* A string field, to be passed to %pF in format strings.  */

struct string_field_s : base_field_s
{
  const char *str;
};

/* Construct a temporary string_field_s on the caller's stack and
   return a pointer to the constructed object.  We use this because
   it's not possible to pass a reference via va_args.  */

static inline string_field_s *
string_field (const char *name, const char *str,
	      string_field_s &&tmp = {})
{
  tmp.name = name;
  tmp.kind = field_kind::FIELD_STRING;
  tmp.str = str;
  return &tmp;
}

/* A styled string.  */

struct styled_string_s
{
  /* The style.  */
  ui_file_style style;

  /* The string.  */
  const char *str;
};

/* Construct a temporary styled_string_s on the caller's stack and
   return a pointer to the constructed object.  We use this because
   it's not possible to pass a reference via va_args.  */

static inline styled_string_s *
styled_string (const ui_file_style &style, const char *str,
	       styled_string_s &&tmp = {})
{
  tmp.style = style;
  tmp.str = str;
  return &tmp;
}

class ui_out
{
 public:

  explicit ui_out (ui_out_flags flags = 0);
  virtual ~ui_out ();

  DISABLE_COPY_AND_ASSIGN (ui_out);

  void push_level (ui_out_type type);
  void pop_level (ui_out_type type);

  /* A table can be considered a special tuple/list combination with the
     implied structure: ``table = { hdr = { header, ... } , body = [ {
     field, ... }, ... ] }''.  If NR_ROWS is negative then there is at
     least one row.  */

  void table_begin (int nr_cols, int nr_rows, const std::string &tblid);
  void table_header (int width, ui_align align, const std::string &col_name,
		     const std::string &col_hdr);
  void table_body ();
  void table_end ();

  void begin (ui_out_type type, const char *id);
  void end (ui_out_type type);

  void field_signed (const char *fldname, LONGEST value,
		     const ui_file_style &style = ui_file_style ());
  void field_fmt_signed (int width, ui_align align, const char *fldname,
			 LONGEST value);
  /* Like field_signed, but print an unsigned value.  */
  void field_unsigned (const char *fldname, ULONGEST value);
  void field_core_addr (const char *fldname, struct gdbarch *gdbarch,
			CORE_ADDR address);
  void field_string (const char *fldname, const char *string,
		     const ui_file_style &style = ui_file_style ());
  void field_string (const char *fldname, const std::string &string,
		     const ui_file_style &style = ui_file_style ())
  {
    field_string (fldname, string.c_str (), style);
  }
  void field_stream (const char *fldname, string_file &stream,
		     const ui_file_style &style = ui_file_style ());
  void field_skip (const char *fldname);
  void field_fmt (const char *fldname, const char *format, ...)
    ATTRIBUTE_PRINTF (3, 4);
  void field_fmt (const char *fldname, const ui_file_style &style,
		  const char *format, ...)
    ATTRIBUTE_PRINTF (4, 5);

  void spaces (int numspaces) { do_spaces (numspaces); }
  void text (const char *string) { do_text (string); }
  void text (const std::string &string) { text (string.c_str ()); }

  /* Output a printf-style formatted string.  In addition to the usual
     printf format specs, this supports a few GDB-specific
     formatters:

     - '%pF' - output a field.

       The argument is a field, wrapped in any of the base_field_s
       subclasses.  signed_field for integer fields, string_field for
       string fields.  This is preferred over separate
       uiout->field_signed(), uiout_>field_string() etc. calls when
       the formatted message is translatable.  E.g.:

	 uiout->message (_("\nWatchpoint %pF deleted because the program has "
			 "left the block in\n"
			 "which its expression is valid.\n"),
			 signed_field ("wpnum", b->number));

     - '%p[' - output the following text in a specified style.
       '%p]' - output the following text in the default style.

       The argument to '%p[' is a ui_file_style pointer.  The argument
       to '%p]' must be nullptr.

       This is useful when you want to output some portion of a string
       literal in some style.  E.g.:

	 uiout->message (_(" %p[<repeats %u times>%p]"),
			 metadata_style.style ().ptr (),
			 reps, repeats, nullptr);

     - '%ps' - output a styled string.

       The argument is the result of a call to styled_string.  This is
       useful when you want to output some runtime-generated string in
       some style.  E.g.:

	 uiout->message (_("this is a target address %ps.\n"),
			 styled_string (address_style.style (),
					paddress (gdbarch, pc)));

     Note that these all "abuse" the %p printf format spec, in order
     to be compatible with GCC's printf format checking.  This is OK
     because code in GDB that wants to print a host address should use
     host_address_to_string instead of %p.  */
  void message (const char *format, ...) ATTRIBUTE_PRINTF (2, 3);
  void vmessage (const ui_file_style &in_style,
		 const char *format, va_list args) ATTRIBUTE_PRINTF (3, 0);

  void wrap_hint (int indent) { do_wrap_hint (indent); }

  void flush () { do_flush (); }

  /* Redirect the output of a ui_out object temporarily.  */
  void redirect (ui_file *outstream) { do_redirect (outstream); }

  ui_out_flags test_flags (ui_out_flags mask)
  { return m_flags & mask; }

  /* HACK: Code in GDB is currently checking to see the type of ui_out
     builder when determining which output to produce.  This function is
     a hack to encapsulate that test.  Once GDB manages to separate the
     CLI/MI from the core of GDB the problem should just go away ....  */

  bool is_mi_like_p () const { return do_is_mi_like_p (); }

  bool query_table_field (int colno, int *width, int *alignment,
			  const char **col_name);

  /* Return true if this stream is prepared to handle style
     escapes.  */
  virtual bool can_emit_style_escape () const = 0;

  /* Return the ui_file currently used for output.  */
  virtual ui_file *current_stream () const = 0;

  /* An object that starts and finishes displaying progress updates.  */
  class progress_update
  {
  public:
    /* Represents the printing state of a progress update.  */
    enum state
    {
      /* Printing will start with the next update.  */
      START,
      /* Printing has already started.  */
      WORKING,
      /* Progress bar printing has already started.  */
      BAR
    };

    /* SHOULD_PRINT indicates whether something should be printed for a tty.  */
    progress_update ()
    {
      m_uiout = current_uiout;
      m_uiout->do_progress_start ();
    }

    ~progress_update ()
    {
      m_uiout->do_progress_end ();
    }

    progress_update (const progress_update &) = delete;
    progress_update &operator= (const progress_update &) = delete;

    /* Emit some progress for this progress meter.  Includes current
       amount of progress made and total amount in the display.  */
    void update_progress (const std::string& msg, const char *unit,
			  double cur, double total)
    {
      m_uiout->do_progress_notify (msg, unit, cur, total);
    }

    /* Emit some progress for this progress meter.  */
    void update_progress (const std::string& msg)
    {
      m_uiout->do_progress_notify (msg, "", -1, -1);
    }

  private:

    struct ui_out *m_uiout;
  };

protected:

  virtual void do_table_begin (int nbrofcols, int nr_rows, const char *tblid)
    = 0;
  virtual void do_table_body () = 0;
  virtual void do_table_end () = 0;
  virtual void do_table_header (int width, ui_align align,
				const std::string &col_name,
				const std::string &col_hdr) = 0;

  virtual void do_begin (ui_out_type type, const char *id) = 0;
  virtual void do_end (ui_out_type type) = 0;
  virtual void do_field_signed (int fldno, int width, ui_align align,
				const char *fldname, LONGEST value,
				const ui_file_style &style) = 0;
  virtual void do_field_unsigned (int fldno, int width, ui_align align,
				  const char *fldname, ULONGEST value) = 0;
  virtual void do_field_skip (int fldno, int width, ui_align align,
			      const char *fldname) = 0;
  virtual void do_field_string (int fldno, int width, ui_align align,
				const char *fldname, const char *string,
				const ui_file_style &style) = 0;
  virtual void do_field_fmt (int fldno, int width, ui_align align,
			     const char *fldname, const ui_file_style &style,
			     const char *format, va_list args)
    ATTRIBUTE_PRINTF (7, 0) = 0;
  virtual void do_spaces (int numspaces) = 0;
  virtual void do_text (const char *string) = 0;
  virtual void do_message (const ui_file_style &style,
			   const char *format, va_list args)
    ATTRIBUTE_PRINTF (3,0) = 0;
  virtual void do_wrap_hint (int indent) = 0;
  virtual void do_flush () = 0;
  virtual void do_redirect (struct ui_file *outstream) = 0;

  virtual void do_progress_start () = 0;
  virtual void do_progress_notify (const std::string &, const char *,
				   double, double) = 0;
  virtual void do_progress_end () = 0;

  /* Set as not MI-like by default.  It is overridden in subclasses if
     necessary.  */

  virtual bool do_is_mi_like_p () const
  { return false; }

 private:
  void call_do_message (const ui_file_style &style, const char *format,
			...);

  ui_out_flags m_flags;

  /* Vector to store and track the ui-out levels.  */
  std::vector<std::unique_ptr<ui_out_level>> m_levels;

  /* A table, if any.  At present only a single table is supported.  */
  std::unique_ptr<ui_out_table> m_table_up;

  void verify_field (int *fldno, int *width, ui_align *align);

  int level () const;
  ui_out_level *current_level () const;
};

/* Start a new tuple or list on construction, and end it on
   destruction.  Normally this is used via the typedefs
   ui_out_emit_tuple and ui_out_emit_list.  */
template<ui_out_type Type>
class ui_out_emit_type
{
public:

  ui_out_emit_type (struct ui_out *uiout, const char *id)
    : m_uiout (uiout)
  {
    uiout->begin (Type, id);
  }

  ~ui_out_emit_type ()
  {
    m_uiout->end (Type);
  }

  DISABLE_COPY_AND_ASSIGN (ui_out_emit_type);

private:

  struct ui_out *m_uiout;
};

typedef ui_out_emit_type<ui_out_type_tuple> ui_out_emit_tuple;
typedef ui_out_emit_type<ui_out_type_list> ui_out_emit_list;

/* Start a new table on construction, and end the table on
   destruction.  */
class ui_out_emit_table
{
public:

  ui_out_emit_table (struct ui_out *uiout, int nr_cols, int nr_rows,
		     const char *tblid)
    : m_uiout (uiout)
  {
    m_uiout->table_begin (nr_cols, nr_rows, tblid);
  }

  ~ui_out_emit_table ()
  {
    m_uiout->table_end ();
  }

  ui_out_emit_table (const ui_out_emit_table &) = delete;
  ui_out_emit_table &operator= (const ui_out_emit_table &) = delete;

private:

  struct ui_out *m_uiout;
};

/* On construction, redirect a uiout to a given stream.  On
   destruction, pop the last redirection by calling the uiout's
   redirect method with a NULL parameter.  */
class ui_out_redirect_pop
{
public:

  ui_out_redirect_pop (ui_out *uiout, ui_file *stream)
    : m_uiout (uiout)
  {
    m_uiout->redirect (stream);
  }

  ~ui_out_redirect_pop ()
  {
    m_uiout->redirect (NULL);
  }

  ui_out_redirect_pop (const ui_out_redirect_pop &) = delete;
  ui_out_redirect_pop &operator= (const ui_out_redirect_pop &) = delete;

private:
  struct ui_out *m_uiout;
};

struct buffered_streams;

/* Organizes writes to a collection of buffered output streams
   so that when flushed, output is written to all streams in
   chronological order.  */

struct buffer_group
{
  buffer_group (ui_out *uiout);

  /* Flush all buffered writes to the underlying output streams.  */
  void flush () const;

  /* Record contents of BUF and associate it with STREAM.  */
  void write (const char *buf, long length_buf, ui_file *stream);

  /* Record a wrap_here and associate it with STREAM.  */
  void wrap_here (int indent, ui_file *stream);

  /* Record a call to flush and associate it with STREAM.  */
  void flush_here (ui_file *stream);

private:

  struct output_unit
  {
    output_unit (std::string msg, int wrap_hint = -1, bool flush = false)
      : m_msg (msg), m_wrap_hint (wrap_hint), m_flush (flush)
    {}

    /* Write contents of this output_unit to the underlying stream.  */
    void flush () const;

    /* Underlying stream for which this output unit will be written to.  */
    ui_file *m_stream;

    /* String to be written to underlying buffer.  */
    std::string m_msg;

    /* Argument to wrap_here.  -1 indicates no wrap.  Used to call wrap_here
       during buffer flush.  */
    int m_wrap_hint;

    /* Indicate that the underlying output stream's flush should be called.  */
    bool m_flush;
  };

  /* Output_units to be written to buffered output streams.  */
  std::vector<output_unit> m_buffered_output;

  /* Buffered output streams.  */
  std::unique_ptr<buffered_streams> m_buffered_streams;
};

/* If FILE is a buffering_file, return it's underlying stream.  */

extern ui_file *get_unbuffered (ui_file *file);

/* Buffer output to gdb_stdout and gdb_stderr for the duration of FUNC.  */

template<typename F, typename... Arg>
void
do_with_buffered_output (F func, ui_out *uiout, Arg... args)
{
  buffer_group g (uiout);

  try
    {
      func (uiout, std::forward<Arg> (args)...);
    }
  catch (gdb_exception &ex)
    {
      /* Ideally flush would be called in the destructor of buffer_group,
	 however flushing might cause an exception to be thrown.  Catch it
	 and ensure the first exception propagates.  */
      try
	{
	  g.flush ();
	}
      catch (const gdb_exception &)
	{
	}

      throw_exception (std::move (ex));
    }

  /* Try was successful.  Let any further exceptions propagate.  */
  g.flush ();
}

/* Accumulate writes to an underlying ui_file.  Output to the
   underlying file is deferred until required.  */

struct buffering_file : public ui_file
{
  buffering_file (buffer_group *group, ui_file *stream)
    : m_group (group),
      m_stream (stream)
  { /* Nothing.  */ }

  /* Return the underlying output stream.  */
  ui_file *stream () const
  {
    return m_stream;
  }

  /* Record the contents of BUF.  */
  void write (const char *buf, long length_buf) override
  {
    m_group->write (buf, length_buf, m_stream);
  }

  /* Record a wrap_here call with argument INDENT.  */
  void wrap_here (int indent) override
  {
    m_group->wrap_here (indent, m_stream);
  }

  /* Return true if the underlying stream is a tty.  */
  bool isatty () override
  {
    return m_stream->isatty ();
  }

  /* Return true if ANSI escapes can be used on the underlying stream.  */
  bool can_emit_style_escape () override
  {
    return m_stream->can_emit_style_escape ();
  }

  /* Flush the underlying output stream.  */
  void flush () override
  {
    return m_group->flush_here (m_stream);
  }

private:

  /* Coordinates buffering across multiple buffering_files.  */
  buffer_group *m_group;

  /* The underlying output stream.  */
  ui_file *m_stream;
};

/* Attaches and detaches buffers for each of the gdb_std* streams.  */

struct buffered_streams
{
  buffered_streams (buffer_group *group, ui_out *uiout);

  ~buffered_streams ()
  {
    this->remove_buffers ();
  }

  /* Remove buffering_files from all underlying streams.  */
  void remove_buffers ();

private:

  /* True if buffers are still attached to each underlying output stream.  */
  bool m_buffers_in_place;

  /* Buffers for each gdb_std* output stream.  */
  buffering_file m_buffered_stdout;
  buffering_file m_buffered_stderr;
  buffering_file m_buffered_stdlog;
  buffering_file m_buffered_stdtarg;

  /* Buffer for current_uiout's output stream.  */
  std::optional<buffering_file> m_buffered_current_uiout;

  /* Additional ui_out being buffered.  */
  ui_out *m_uiout;

  /* Buffer for m_uiout's output stream.  */
  std::optional<buffering_file> m_buffered_uiout;
};

#endif /* GDB_UI_OUT_H */
