xo-umbrella2/xo-indentlog2/include/xo/indentlog2/print/PpState.hpp

263 lines
9.6 KiB
C++

/** @file PpState.hpp
*
* @author Roland Conybeare, Jul 2026
**/
#pragma once
#include "PpConfig.hpp"
#include "xo/indentlog2/LogBuffer.hpp"
#include <xo/arena/DArena.hpp>
#include <xo/arena/DArenaVector.hpp>
namespace xo::pp {
class PpStringToken; // see PpToken.hpp
/**
* Use:
* @code
* // create pretty-printer
* PpState pps(PpConfig());
* LogBuffer logbuf(ArenaConfig(..));
* pps.connect_output(&logbuf);
*
* // pretty-print foo(apple, banana).
* // If space-limited, split onto multiple lines like:
* // foo(
* // apple,
* // banana)
* //
* pps.put_cstr("foo");
* pps.put_cstr("(");
*
* pps.begin();
* pps.split();
*
* pps.put_cstr("apple");
* pps.put_cstr(",");
*
* pps.split();
*
* pps.put_cstr("banana");
* pps.put_cstr(")");
*
* pps.end();
* @endcode
**/
class PpState {
public:
using MemorySizeVisitor = xo::mm::MemorySizeVisitor;
using ArenaConfig = xo::mm::ArenaConfig;
using DArena = xo::mm::DArena;
using Span = LineState::Span; // = xo::mm::span<char>
using ScanStack = xo::mm::DArenaVector<uint32_t>;
using PrintStack = xo::mm::DArenaVector<uint32_t>;
public:
/** Create instance from configuration @p cfg.
* Allocates arena for @ref logbuf_
**/
explicit PpState(const PpLayoutConfig & layout);
/** true iff printer currently has an open string
* (only one permitted at a time, since zero-copy)
**/
bool has_open_string() const { return current_open_string_ != nullptr; }
/** visit mapped storage pools, call @p fn(pool) for each one **/
void visit_pools(const MemorySizeVisitor & fn) const;
/** connect printer to output @p p_out **/
void connect_output(LogBuffer * p_out);
/** the LogBuffer this state drains into. Used by PrettySink::verify_ok() **/
const LogBuffer * _out() const noexcept { return p_out_; }
/** append @p str to pretty printer as a single token;
* triggers printing when at top level.
**/
void put(std::string_view str);
/** append @p c_str to pretty printer as a single token;
* triggers printing at top-level
**/
void put_cstr(const char * c_str);
/** append @p str as a single token, with escapes expanded per
* xo::pp::Escape (see xo/ppsink/escape.hpp), surrounded by
* Escape::c_quote iff @p quote_flag.
*
* Always exactly one token, so the pretty printer can never break a
* line inside the escaped string, or between a quote and its content.
**/
void put_with_escape(std::string_view str, bool quote_flag);
/** begin a group; @p offset adds to the running indent until end().
* the no-arg form indents one nesting level (config indent_width).
**/
void begin(int32_t offset);
void begin() { this->begin(layout_config_.indent_width()); }
/** begin a group whose indent ORIGIN is the current output column,
* rather than the running indent.
**/
void begin_here(int32_t offset);
void begin_here() { this->begin_here(0); }
/** optional break. Group fits: emit @p spaces spaces.
* Group breaks: newline + (running_indent + @p offset).
**/
void split(uint32_t spaces, int32_t offset);
void split() { this->split(0, 0); }
void split(uint32_t spaces) { this->split(spaces, 0); }
/** forced break: always newline + (running_indent + @p offset),
* forcing every enclosing group to break.
**/
void newline(int32_t offset = 0);
/** end a group (append @c k_end token); may trigger printing **/
void end();
/** Special case for assembling a string token incrementally
* (Relying on this for interaction w/ exogenous operator<<).
*
* Similar to put() / put_cstr(), except that string length
* isn't determined yet. Create string token, ensuring
* contiguous memory for at least @p min_z chars
**/
Span open_string(uint32_t min_z);
/** Finalize a string token initiated previously by @ref open_string(). **/
void commit_string(Span str);
private:
/** common implementation of begin() / begin_here() **/
void _begin_aux(int32_t offset, bool align_here);
private:
/** count visible chars in range [lo, hi) **/
uint32_t count_visible_chars(const char * lo,
const char * hi) const;
/** Available buffer space (in bytes).
* Can expand buffer up to size tk_buffer_.reserved()
**/
uint32_t available() const;
/** Allocate token (always logically at scan_ix_) of size @p z **/
void * alloc(uint32_t z);
/** Allocate at @ref scan_ix_. Helper for alloc() **/
void * alloc_scan_aux(uint32_t z);
/** Expand to make space (at @ref scan_ix_) for token with size @p z **/
void * expand_for(uint32_t z);
/** adjust index values in [tk_src, tk_src+z)
* to new range [tk_dest, tk_dest+z)
**/
void reindex_stacks(const char * tk_dest,
const char * tk_src,
uint32_t z);
/** print ready tokens.
* A token is ready if it's size if known,
* so we know whether it fits.
**/
void check_print_ready();
/** write @p n spaces to the output (no newline) **/
void write_spaces(uint32_t n);
private:
/**
*
* tk_buffer
* 0 limit_ix
* | |
* ........11111222334455555........
* ^ ^
* print_ix scan_ix
*
* or wrapped:
*
* tk_buffer
* 0 extent limit_ix
* | | |
* 222334455555.............11111___
* ^ ^
* scan_ix print_ix
**/
/** Configuration: layout-specific: max width, indent etc. **/
PpLayoutConfig layout_config_;
/** Configuration: logbuffer size / debug **/
PpLogbufConfig logbuf_config_;
/** Token buffer.
* Large enough to contain tokens up to @ref config_.right_margin_
**/
DArena tk_buffer_;
/** PpTokens have variable size and each token must occupy
* contiguous memory.
*
* Current buffer extent, when buffer is wrapped
* (with @ref scan_ix_ < @ref print_ix_).
* Established whenever buffer is an unwrapped state
* (with @ref print_ix_ <= scan_ix_) but next scanned token
* would be too big for remaining buffer space
* @c [scan_ix_, tk_buffer_.limit_).
*
* Memory in [extent_, tk_buffer_.limit_) is unusable
* until either buffer unwraps (@ref print_ix_ resets to 0)
* or buffer fills and printer printer expands it.
**/
uint32_t extent_ = 0;
/** append to @ref tk_buffer_ starting at this position **/
uint32_t scan_ix_ = 0;
/** print from @ref tk_buffer_ starting at this position **/
uint32_t print_ix_ = 0;
/** Counts number of visible characters from incoming string tokens
* since inception. Monotonically increasing.
**/
uint32_t scan_viz_total_ = 0;
/** Counts number of characters from incoming string tokens
* since inception. Monotonically increasing.
**/
uint32_t scan_total_ = 0;
/** Counts visible pretty-printed characters
* since inception, excluding whitespace inserted by
* pretty printer itself. Chases @ref scan_viz_total_
*
* Invariant: @ref print_viz_total_ <= @ref scan_viz_total_
**/
uint32_t print_viz_total_ = 0;
/** Counts pretty-printed characters since inception,
* excluding whitespace inserted by pretty printer itself.
* Chases @ref scan_total_.
*
* Invariant: @ref print_total_ <= @ref scan_total_
**/
uint32_t print_total_ = 0;
/** Indentifies k_begin PpTokens for which size has not yet been computed.
* Each entry is an index into @ref tk_buffer_.
* Size for k_begin tokens is unknown until pretty printer
* encounters matching k_end token.
**/
ScanStack scan_stack_;
/** Holds PpTokens for which size *has* been computed,
* but which haven't yet been printed to @c *p_out_.
**/
PrintStack print_stack_;
/** running indent while printing: sum of the offsets of the currently
* open groups (k_begin += offset, k_end -= offset). A broken split
* indents to (print_indent_ + that split's own offset).
**/
int32_t print_indent_ = 0;
/** if non-null: a string token initiated by @ref open_string().
* Actual size established when @ref commit_string() runs.
* At most one such token can exist.
* Used to support interaction with external operator<<
**/
PpStringToken * current_open_string_ = nullptr;
/** output buffer. Independently tracks lpos, visible chars **/
LogBuffer * p_out_ = nullptr;
};
} /*namespace xo::pp*/
/* end PpState.hpp */