Skip to content

writer_base

Raymond Chen edited this page Mar 30, 2019 · 1 revision

The xlang::text::writer_base<T> template structure is defined in text_writer.h and implements simple buffered text output.

All of the types reside in the xlang::text namespace.

Overview

The writer_base buffers text until explicitly flushed, either to the console or to a file. If you fail to flush the writer_base, then the output is discarded.

It supports two types of output (which we will call "plain" and "code") and two output buffers.

Plain vs code output

The writer_base does not impose any semantics for "plain" or "code" output, but the intention is that "plain" output is similar to what you get with printf, whereas "code" output performs transformations appropriate for emitting code.

To customize plain output, override or overload the write method. To customize code output, override or overload the write_code method.

The writer_base struct provides only one type of write_code: It accepts a std::string_view and prints it as plain output.

To override write or write_code, simply implement them in your derived class.

To overload write, import the methods from the base class via the using declaration, and then add your own overloads.

The write_code method cannot be overloaded.

struct my_writer : xlang::text::writer_base<my_writer>
{
    // Inherit all write() methods from writer_base.
    using writer_base::write;

    // Add one more for our custom type.
    void write(custom_type custom);

    // Override write_code for custom code generation.
    void write_code(std::string_view value);
};

Multiple buffers

The writer_base actually consists of two buffers, known as first and second. Output is appended to the first buffer, but you can swap the two buffers. When the contents of the writer are flushed, the first buffer is written first, followed by the second buffer.

The two buffers can be used if you discover information in an order different from the order you need to output it.

Note that output always goes to first. If you want to write to second, you need to swap, write, and then swap back.

writer w;

w.write("// Forward declarations\n");

for (auto const item& : items)
{
  // write the forward declaration to the header.
  w.write("struct @;\n", item.name);

  // write the implementation to the footer.
  w.swap();
  generate_definition(w, item);
  w.swap();
}

struct writer_base constructor

writer_base();

It can throw due to low memory.

Generating output

formatted write

template<typename... Args>
write(std::string_view const& format_string, Args const&... args);

Produces plain output. The following special characters are recognized in the format_string:

Character Meaning
% Take the next variadic argument and write it.
@ Take the next variadic argument (which must be convertible to std::string_view) and write_code it.
^ Take the next character and print it literally (escape). May not be the last character of the format string.

Behavior is undefined if the number of insertions does not equal the number of extra arguments.

Unformatted write

void write(std::string_view const& value);
void write(char const value);
void write(int32_t const value);
void write(int64_t const value);
void write(uint64_t const value);
void write(F const& f); // where F is a functor

Produces plain output.

In the last case, you can pass anything that supports function calls, and it will be called with the derived class as its parameter. For example,

struct my_writer : xlang::text::writer_base<my_writer>
{
};

my_writer w;
w.write("a%b%c%d", 1, [](my_writer& w) { w.flush_to_console(); }, 2);

The above example writes a1b to the writer's buffer, and then flushes the writer's buffers to the console, and then writes c2d to the writer's buffer.

The functor typically writes a complex data structure to the buffer. A class can implement operator()(writer&) to make itself self-printing. See How to make something writable for more details.

write_printf

template<typename... Args>
void write_printf(char const* format, Args const&... args);

Produces plain output by calling sprintf with the specified format string and and arguments and writing the result.

If result of the sprintf exceeds 128 characters, the behavior is undefined. (Will crash in practice.)

write_each

template<auto F, typename List, typename... Args>
void write_each(List const& list, Args const&... args);

The explicit template parameter F is a function pointer. The function is called as follows, once for each item in the list:

    F(writer, item, args...);

Example:

void write_add(my_writer& w, int i, const int extra)
{
  w.write(i + extra);
}

struct my_writer : xlang::text::writer_base<my_writer>
{
};

std::vector<int> v{ 1, 2, 3 };
my_writer w;
w.write_each<write_add>(v, 1); // writes "234"

write_code

void write_code(std::string_view const& value);

Produces code output. The default implementation treats code output and plain output identically.

write_temp

template<typename... Args>
std::string write_temp(std::string_view const& format_string, Args const&... args);

This produces plain output but captures the result in a std::string instead of appending it to the buffer.

Implementation note: This method is non-const because it is implemented by generating the output to the buffer, then resetting the buffer back to its original contents.

write_impl

void write_impl(std::string_view const& value);
void write_impl(char const value);

Appends the string or character to the output buffer. You are not expected to call this directly.

Override these methods to customize output further. For example, the python type writer uses it to apply indentation. Note that if you do override this method, you will probably need to override write_temp so its output doesn't get customized.

Saving results

flush_to_console

void flush_to_console() noexcept;

Write the contents of the buffers to stdout, and then clear the buffers. Since stdout is a text stream, newline characters in the buffer are printed as CR+LF.

flush_to_file

void flush_to_file(std::string const& filename);
void flush_to_file(std::experimental::filesystem::path const& filename);

Writes the contents of the buffers to the specified file, and then clear the buffers. If the file already exists and the contents of the buffers are identical to the current file contents, then the file is left unmodified. This avoids altering last-modified timestamps and triggering false rebuilds.

Other methods

swap

void swap() noexcept;

Exchanges the first and second buffers. See "Multiple buffers" above.

back

char back();

Returns the last character in the first buffer, or '\0' if the first buffer is empty.

file_equal

bool file_equal(std::string const& filename);

Returns true if the specified file exists and its contents are identical to the contents of the buffers.