5.1 Pretty-printers

5.1.1 Convention for pretty-printed Output

Very often the structure of the data encoded in binary is not very intelligible. This is because it is usual for binary formats to be designed with goals in mind other than being readable by humans: compactness, detail etc.

In our pickle we of course want to provide access to the very finer detail of the data structures. However, we also want for the user to be able to peruse the data visually, and only look at the fine detail on demand.

Consider for example an ID3V1 tag data from some MP3 file. This is the result of mapping a ID3V1_Tag:

ID3V1_Tag {
  id=[0x54UB,0x41UB,0x47UB],
  title=[0x30UB,0x31UB,0x20UB,0x2dUB,0x20UB,...],
  artist=[0x4aUB,0x6fUB,0x61UB,0x71UB,0x75UB,...],
  album=[0x4dUB,0x65UB,0x6eUB,0x74UB,0x69UB,...],
  year=[0x20UB,0x20UB,0x20UB,0x20UB],
  data=struct {
    extended=struct {
      comment=[0x20UB,0x20UB,0x20UB,0x20UB,0x20UB,...],
      zero=0x0UB,
      track=0x1UB
    }
  },
  genre=0xffUB
}

Not very revealing. Fortunately, poke supports pretty printers. If a struct type has a method called _print, it will be used by poke as a pretty printer if the pretty-print option is set:

(poke) .set pretty-print yes

By convention, the output of pretty-printers should always start with #< and end with >. The convention makes it explicit for the user that everything she sees between #< and > is pretty-printed, and do not necessarily reflect the physical structure of the data. Also some information may be missing. In order to get an exact and complete description of the data, the user should .set pretty-print no and evaluate the value again at the prompt.

For example, in the following BPF instructions it is obvious at first sight that the shown register values are pretty-printed:

BPF_Insn = {
  ...
  regs=BPF_Regs {
     src=#<%r3>,
     dst=#<%r0>
  }
  ...
}

If the pretty-printed representation spans for more than one line, please place the opening #< in its own line, then the lines with the data, and finally > in its own line, starting at column 0.

Example of the MP3 tag above, this time pretty-printed:

#<
  genre: 255
  title: 01 - Eclipse De Mar
  artist: Joaquin Sabina
  album: Mentiras Piadosas
  year:
  comment:
  track: 1
>

5.1.2 Pretty Printing Optional Fields

Let’s say we are writing a pretty-printer method for a struct type that has an optional field. Like for example:

type Packet =
  struct
  {
    byte magic : magic in [MAGIC1,MAGIC2];
    byte n;
    byte[n] payload;
    if (magic == MAGIC2)
     PacketTrailer trailer;
  }

In this case, the struct value will have a trailer conditionally, which has to be tackled on the pretty-printer somehow.

An approach that often works good is to replicate the logic in the optional field condition expression, like this:

  struct
  {
    byte magic : magic in [MAGIC1,MAGIC2];
    [...]
    PacketTrailer trailer if packet_magic2_p (magic);

    method _print = void:
    {
      [...]
      if (magic == MAGIC2)
        pretty_print_trailer;
    }
  }

This works well in this simple example. In case the expression is big and complicated, we can avoid rewriting the same expression by encapsulating the logic in a function:

fun packet_magic2_p = int: { return magic == MAGIC2; }
type Packet =
  struct
  {
    byte magic : magic in [MAGIC1,MAGIC2];
    [...]
    if (packet_magic2_p (magic))
    PacketTrailer trailer;

    method _print = void:
    {
      [...]
      if (packet_magic2_p (magic))
        pretty_print_trailer;
    }
  }

However, this may feel weird, as the internal logic of the type somehow leaks its natural boundary into the external function packet_magic2_p.

An alternative is to use the following idiom, that checks whether the field actually exists in the struct:

type Packet =
  struct
  {
    byte magic : magic in [MAGIC1,MAGIC2];
    [...]
    if (packet_magic2_p (magic))
      PacketTrailer trailer;

    method _print = void:
    {
      [...]
      try pretty_print_trailer;
      catch if E_elem {}
    }
  }

This approach also works with unions:

type ID3V1_Tag =
  struct
  {
    [...]
    union
    {
      /* ID3v1.1  */
      struct
      {
        char[28] comment;
        byte zero = 0;
        byte track : track != 0;
      } extended;
      /* ID3v1  */
      char[30] comment;
    } data;
    [...]

    method _print = void:
    {
      [...]
      try print "  comment: " + catos (data.comment) + "\n";
      catch if E_elem
      {
        print "  comment: " + catos (data.extended.comment) + "\n";
        printf "  track: %u8d", data.extended.track;
      }
    }
  }