Page MenuHomePhabricator

IOChannel
Updated 2,465 Days AgoPublic

Summary

'IOChannel' is designed as a wrapper and, depending on usage, a replacement for std::cout and printf(). Its sports a number of unique and useful features.

  • Multiple asynchronous outputs.
  • Message priorities (verbosity).
  • Message categories.
  • Built-in output formatting.
  • Advanced memory tools.

Setting Up IOChannel

IOChannel Instance

Use of IOChannel requires #include <iochannel.hpp>.

For convenience, a single static global instance of IOChannel, ioc, exists in that header. It is suitable for most purposes, though a custom iochannel instance may be declared. All inputs and outputs that the developer wishes to interface with one another via this class must share the same instance.

IOFormat

IOChannel uses a number of enumerations and data types for formatting, data manipulation, and advanced usage. These are in the pawlib::ioformat namespace. To save typing, it may be helpful to import that particular namespace via using namespace pawlib::ioformat;, even if the primary namespace pawlib is not imported.

Documentation Assumptions

For purposes of expediency, the default global static instance pawlib::ioc will be used in this documentation. Furthermore, we will assume that using namespace pawlib::ioformat; and using namespace pawlib are being used. All namespaces outside of the scope of PawLIB will be stated explicitly.

Concepts

IOChannel uses two unique concepts: Verbosity and Category.

Category

The benefit to having categories on messages is that you can route different kinds of messages to different outputs. For example, you might send all errors and warnings to a debug terminal, and reserve "normal" messages for game notifications.

CategoryEnumUse
Normalcat_normalRegular use messages, especially those you want the user to see.
Warningcat_warningWarnings about potential problems.
Errorcat_errorError messages.
Debugcat_debugMessages that might help you track down problems.
Allcat_allAll of the above.

One of the advantages of this system is that you can actually leave messages in the code, and just control when and how they are processed and broadcast. No more commenting out (and then losing) your debugging print statements!

Verbosity

Some messages we need to see every time, and others only in special circumstances. This is what verbosity is for.

VerbosityEnumUse
Quietvrb_quietOnly essential messages and errors. For normal end-use. Shipping default.
Normalvrb_normalCommon messages and errors. For common and normal end-user testing.
Chattyvrb_chattyMost messages and errors. For detailed testing and debugging.
Too Much Informationvrb_tmiAbsolutely everything. For intense testing, detailed debugging, and driving the developers crazy.

One example of verbosity in action would be in debugging messages. A notification about a rare and potentially problematic function being called might be vrb_normal, while the output of a loop iterator would probably be vrb_tmi.

Input

General

All input is done using the stream insertion (<<) operator, in the same manner as with std::cout. Before a message is broadcast, the io_end enumeration must be passed.

io_end serves both as an "end of transmission" flag and a final newline character. Thus, \n is not needed if the output should be displayed on a single line. This functionality also allows a single transmission to be split up over multiple lines, if necessary. (See Stream Control)

ioc << "This is the first part. ";
//Some more code here.
ioc << "This is the second part." << io_end;

`io_end` also clears any formatting set during the stream.

//OUTPUT: "This is the first part. This is the second part."

Strings

IOChannel natively supports string literals, cstring (char arrays), std::string, and pawlib::string.

IMPORTANT: pawlib::string support is upcoming.

These are passed in using the << operator, as with anything being input into IOChannel. The message will not be broadcast until io_end is passed.

ioc << "Hello, world!" << io_end;
//OUTPUT: "Hello, world!"

char* cstr = "I am a Cstring.\0";
ioc << cstr << io_end;
//OUTPUT: "I am a Cstring."

std::string stdstr = "I am a standard string.";
ioc << stdstr << io_end;
//OUTPUT: "I am a standard string."

Formatting

Cross-platform output formatting is built in to IOChannel. This means that formatting can be set using the IOFormat enumerations, and it will display correctly on each output and environment.

IMPORTANT: Currently, only ANSI is used. Windows support, formatting-removed, and an easy-to-parse formatting flag system for custom outputs will be added soon.
ioc << ta_bold << fg_red << "This is bold, red text. " << ta_underline << fg_blue << bg_yellow << "This is bold, underline, blue text with a yellow background. " << ta_none << fg_none << bg_none << "This is normal text." << io_end;
//The output is exactly what you'd expect.

Variable Input

IOChannel supports all basic C/C++ data types.

  • Boolean (bool)
  • Char (char)
  • Integer (int) and its various forms.
  • Float (float)
  • Double (double)

Boolean

Output for boolean is pretty basic and boring.

bool foo = true;
ioc << foo << io_end;
//OUTPUT: "TRUE"

The output style can be adjusted, however, using the bool_ enumerations.

bool foo = true;
ioc << bool_lower << foo << io_end;
//OUTPUT: "true"
ioc << bool_upper << foo << io_end;
//OUTPUT: "True"
ioc << bool_caps << foo << io_end;
//OUTPUT: "TRUE"
ioc << bool_numeral << foo << io_end;
//OUTPUT: "1"

Char

Since char can represent both an integer and a character, IOChannel lets you display it as either. By default, IOChannel displays the char as a literal character. Using the char_int flag forces it to print as an integer.

char foo = 'A';
ioc << "Character " << foo << " has ASCII value " << char_int << foo << io_end;
//OUTPUT: Character A has ASCII value 65

When output as an integer, char can be used with all of the enumerations for int (see that section).

Integer

An int can be represented in any base from binary (base 2) to base 35 using the base enumerations.

int foo = 12345;
ioc << "Binary: " << base_bin << foo << io_end;
ioc << "Octal: " << base_oct << foo << io_end;
ioc << "Decimal: " << base_dec << foo << io_end;
ioc << "Dozenal: " << base_doz << foo << io_end;
ioc << "Hexadecimal: " << base_hex << foo << io_end;
ioc << "Base 31: " << base_31 << foo << io_end;

/*OUTPUT:
Binary: 11000000111001
Octal: 30071
Decimal: 12345
Dozenal: 7189
Hexadecimal: 3039
Base 31: cq7
*/

In bases larger than decimal (10), the letter numerals can be output as lowercase (default) or uppercase.

int foo = 187254;
ioc << "Hexadecimal Lower: " << base_hex << foo << io_end;
ioc << "Hexadecimal Upper: " << num_upper << base_hex << foo << io_end;

/*OUTPUT:
Hexadecimal Lower: 2db76
Hexadecimal Upper: 2DB76
*/

Float and Double

Float and Double can only be output in base 10 directly. (Hexadecimal output is only possible through a pointer memory dump. See that section.) However, the precision and use of scientific notation can be modified. By default, precision is 14, and use of scientific notation is automatic for very large and small numbers.

Precision can be modified using the set_precision(#) function. Scientific notation can be turned on with sci_on, and off using sci_none. It can also be reset to automatic with sci_auto.

float foo = 12345.12345678912345;
ioc << "Precision 5, no sci: " << set_precision(5) << foo << io_end;
ioc << "Precision 10, sci: " << set_precision(10) << sci_on << foo << io_end;

/*OUTPUT:
Precision 5, no sci: 12345.12304
Precision 10, sci: 1.2345123046e+4
*/

Both types work the same.

Pointer Input

One of the most powerful features of IOChannel is its handling of pointers - in addition to printing the value at known pointer types, it can print the address or raw memory for ANY pointer, even for custom objects.

Pointer Value

By default, IOChannel will attempt to print the value at the pointers. This can also be forced using ptr_value.

int foo = 12345;
int* fooptr = &foo;
ioc << "Value of foo: " << ptr_value << fooptr << io_end;

char* bar = "My name is Bob, and I am a coder.\0";
ioc << "Value of bar: " << bar << io_end;

/*OUTPUT:
Value of foo: 12345
Value of bar: My name is Bob, and I am a coder.
*/

Pointer Address

IOChannel can print out the address of the pointer in hexadecimal using 'ptr_address'. It displays with lowercase letter numerals by default, though these can be displayed in uppercase using num_upper. It is capable of doing this with any pointer, even for custom objects.

int foo = 12345;
int* fooptr = &foo;
ioc << "Address of foo: " << ptr_address << fooptr << io_end;

char* bar = "My name is Bob, and I am a coder.\0";
ioc << "Address of bar: " << ptr_address << num_upper << bar << io_end;

/*OUTPUT:
Address of foo: 0x7ffc33518308
Address of bar: 0x405AF0
*/

Pointer Memory Dump

IOChannel is capable of dumping the raw memory at any pointer using ptr_memory. The function is safe for most known pointer types (bool, int, float, and double), as the memory dump will not overrun the size of the variable. With char pointers (cstring), the only danger is when the cstring is not null terminated.

Spacing can be added between bytes (mem_bytesep) and bytewords (mem_wordsep), or both (mem_allsep). By default, the memory dumps with no spacing (mem_nosep).

int foo = 12345;
int* fooptr = &foo;
ioc << "Memory dump of foo: " << ptr_memory << mem_bytesep << fooptr << io_end;

char* bar = "My name is Bob, and I am a coder.\0";
ioc << "Memory dump of bar: " << ptr_memory << mem_allsep << bar << io_end;

/*OUTPUT:
Memory dump of foo: 39 30 00 00
Memory dump of bar: 4d 79 20 6e 61 6d 65 20 | 69 73 20 42 6f 62 2c 20 | 61 6e 64 20 49 20 61 6d | 20 61 20 63 6f 64 65 72 | 2e 00
*/

You can print memory from any pointer, though you must specify the number of bytes to read using read_bytes().

WARNING: This feature must be used with caution, as reading too many bytes can trigger segfaults or any number of memory errors. Use the sizeof operator in the read_bytes() argument to prevent these types of problems. (See code).

The following is the safest way to dump the raw memory for a custom object.

//Let's define a struct as our custom object, and make an instance of it.
struct CustomStruct
{
    int foo = 12345;
    double bar = 123.987654321;
    char faz[15] = "Hello, world!\0";
    void increment(){foo++;bar++;}
};
CustomStruct blah;

ioc << ptr_memory << mem_allsep << read_size(sizeof blah) << &blah << io_end;
/*OUTPUT:
39 30 00 00 00 00 00 00 | ad 1c 78 ba 35 ff 5e 40 | 48 65 6c 6c 6f 2c 20 77 | 6f 72 6c 64 21 00 00 00
*/

Stream Control

There are multiple enums for controlling IOChannel's output. All of these are compatible with std::cout and printf.

For example, one might want to display progress on the same line, and then move to a new line for a final message. This can be accomplished via...


ioc << "Let's Watch Progress!" << io_end;
ioc << fg_blue << ta_bold << io_send_keep;
for(int i=0; i<100; i++)
{
    //Some long drawn out code here.
    ioc << i << "%" << io_show_keep;
}
ioc << io_end << io_flush;
ioc << "Wasn't that fun?" << io_end;

/* FINAL OUTPUT:
Let's Watch Progress!
100%
Wasn't that fun?

The complete list of stream controls is as follows. Some notes...

  • \n is a newline.
  • \r is simply a carriage return (move to start of current line).
  • EoM indicates "End of Message", meaning IOChannel will broadcast the message at this point.
  • R means "Remove Formatting", where all format tags are reset to their defaults.
  • F indicates a "flush", which forces stdout to refresh. This is generally necessary when overwriting a line or moving to a new line after overwriting a previous one.
CommandAppendsEoM?RF?F?
io_end\nXX--
io_end_keep\nX----
io_sendXX--
io_send_keepX----
io_endline\n--X--
io_endline_keep\n------
io_show\rXXX
io_show_keep\rX--X
io_flush----X

Output

Internal Broadcast Settings

IOChannel can internally output to either printf() or std::cout (or neither). By default, it uses printf(). However, as stated, this can be changed.

IOChannel's internal output also broadcasts all messages by default. This can also be changed.

These settings are modified using the configure_echo() member function. (The arguments are all IOFormat enumerations).

//Set to use `std::cout`
ioc.configure_echo(echo_cout);

//Set to use `printf` and show only error messages (any verbosity)
ioc.configure_echo(echo_printf, vrb_tmi, cat_error);

//Set to use `cout` and show only "quiet" verbosity messages.
ioc.configure_echo(echo_cout, vrb_quiet);

//Turn off internal output.
ioc.configure_echo(echo_none);

External Broadcast with Signals

One of the primary features of IOChannel is that it can be connected to multiple outputs using signals. Examples of this might be if you want to output to a log file, or display messages in a console in your interface.

Main Signal (signal_all)

The main signal is signal_all, which requires a callback function of the form void callback(std::string, IOFormatVerbosity, IOFormatCategory), as seen in the following example.

//This is our callback function.
void print(std::string msg, IOFormatVerbosity vrb, IOFormatCategory cat)
{
    //Handle the message however we want.
    std::cout << msg;
}

//We connect the callback function to `signal_all` so we get all messages.
ioc.signal_all.connect(sigc::ptr_fun(print));

Category Signals (signal_c_...)

Almost all categories have a signal: signal_c_normal, signal_c_warning, signal_c_error, signal_c_testing, and signal_c_debug.

NOTE: cat_all is used internally, and does not have a signal. Use signal_all instead.

The callbacks for category signals require the form void callback(std::string, IOFormatVerbosity). Below is an example.

lang=c++
//This is our callback function.
void print_error(std::string msg, IOFormatVerbosity vrb)
{

//Handle the message however we want.
std::cout << msg;

}

//We connect the callback function to signal_c_error to get only error messages.
ioc.signal_c_error.connect(sigc::ptr_fun(print_error);

Verbosity Signals (signal_v_...)

Each verbosity has a signal: signal_v_quiet, signal_v_normal, signal_v_chatty, and signal_v_tmi. A signal is broadcast when any message of that verbosity or lower is transmitted.

The callbacks for verbosity signals require the form void callback(std::string, IOFormatCategory). Below is an example inside the context of a class (to demonstrate using sigc::mem_fun instead of sigc::ptr_fun).

class TestClass
{
    public:
        void output(std::string msg, IOFormatCategory cat)
        {
            //Handle the message however we want.
            std::cout << msg;
        }

        TestClass()
        {
            ioc.signal_v_normal.connect(sigc::mem_fun(this, &TestClass::output));
        }
};

Enumeration Lists

Base/Radix (IOFormatBase)

EnumBase
base_bin2
base_22
base_ter3
base_33
base_quat4
base_44
base_quin5
base_55
base_sen6
base_66
base_sep7
base_77
base_oct8
base_88
base_99
base_dec10
base_1010
base_und11
base_1111
base_duo12
base_doz12
base_1212
base_1313
base_tetra14
base_1414
base_pent15
base_1515
base_hex16
base_1616
base_1717
base_1818
base_1919
base_vig20
base_2020
base_2121
base_2222
base_2323
base_2424
base_2525
base_2626
base_2727
base_2828
base_2929
base_3030
base_3131
base_3232
base_3333
base_3434
base_3535
base_3636

Boolean Format (IOFormatBool)

EnumUse
bool_lowerLowercase - "true" or "false"
bool_upperUppercase - "True" or "False"
bool_capsAll caps - "TRUE" or "FALSE"
bool_numBinary numerals - "0" or "1"
bool_scottBSS [Team] Standard - "Yea" or "Nay"

Category (IOFormatCategory)

EnumUse
cat_normalThe default value - anything that doesn't fit elsewhere.
cat_warningWarnings, but not necessarily errors.
cat_errorError messages.
cat_debugDebug messages, such as variable outputs.
cat_testingMessages in tests. (Goldilocks automatically suppresses these during benchmarking.)
cat_allAll message categories. Does not have a correlating signal.

Echo Mode (IOEchoMode)

NOTE: These cannot be passed directly to IOChannel.
EnumUse
echo_noneNo internal output.
echo_printfInternal output uses printf().
echo_coutInternal output uses std::cout.

Memory Separators (IOFormatMemorySeperators)

EnumAction
mem_nosepOutput memory dump as one long string.
mem_bytesepOutput memory dump with spaces between bytes.
mem_wordsepOutput memory dump with bars between words (8 bytes).
mem_allsepOutput memory dump with spaces between bytes and bars between words.

Numeral Case (IOFormatNumeralCase)

EnumAction
num_lowerPrint all letter digits as lowercase.
num_upperPrint all letter digits as uppercase.

Pointer (IOFormatPointer)

EnumAction
ptr_valuePrint the value at the address.
ptr_addressPrint the actual memory address.
ptr_memoryDump the hexadecimal representation of the memory at the address.

Precision (set_precision)

set_precision(#) where # is the precision, as an integer representing the number of significands.

Memory Dump Read Size (read_size)

read_size(#) where # is the number of bytes to read and print, starting at the memory address.

WARNING: Misuse can cause SEGFAULT or other memory errors. Use with caution.

Scientific Notation (IOFormatSciNotation)

EnumAction
sci_noneNo scientific notation.
sci_autoAutomatically select the best option.
sci_onForce use of scientific notation.
WARNING: sci_none has been known to cause truncation in very large and very small values, regardless of precision.

Special (IOSpecial)

EnumAction
io_endEnd of message [EoM], newline, remove formatting.
io_end_keepEoM, newline, keep formatting.
io_sendEoM, remove formatting.
io_send_keepEoM, keep formatting.
io_endlineNewline, remove formatting.
io_endline_keepNewline, keep formatting.
io_showCarriage return and flush, EoM, remove formatting.
io_show_keepCarriage return and flush, EoM, retain formatting.
io_flushFlush only.
IMPORTANT: We'll be adding more later.

Text Attributes (IOFormatTextAttributes)

EnumAction
ta_noneTurn off all attributes.
ta_boldBold text.
ta_underlineUnderlined text.
ta_invertInvert foreground and background colors.

Text Background Color (IOFormatTextBG)

EnumAction
bg_noneDefault text background.
bg_blackBlack text background.
bg_redRed text background.
bg_greenGreen text background.
bg_yellowYellow text background.
bg_blueBlue text background.
bg_magentaMagenta text background.
bg_cyanCyan text background.
bg_whiteWhite text background.

Text Foreground Color (IOFormatTextFG)

EnumAction
fg_noneDefault text color.
fg_blackBlack text.
fg_redRed text.
fg_greenGreen text.
fg_yellowYellow text.
fg_blueBlue text.
fg_magentaMagenta text.
fg_cyanCyan text.
fg_whiteWhite text.

Verbosity/Priority (IOFormatVerbosity)

EnumUse
vrb_quietOnly essential messages and errors. For normal end-use.
vrb_normalCommon messages and errors. For common and normal end-user testing.
vrb_chattyMost messages and errors. For detailed testing and debugging.
vrb_tmiAbsolutely everything. For intense testing, detailed debugging, and driving the developers crazy.
Last Author
jcmcdonald
Last Edited
Nov 14 2015, 9:18 PM