ASCII art diagram converter

From Rosetta Code
Revision as of 15:21, 26 January 2014 by rosettacode>Gus.massa (fix tipo kine ->kind)
ASCII art diagram converter is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Given the RFC 1035 message diagram from Section 4.1.1 (Header section format) as a string: http://www.ietf.org/rfc/rfc1035.txt

+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                      ID                       |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    QDCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    ANCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    NSCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    ARCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Where (every column of the table is 1 bit):

ID is 16 bits
QR = Query (0) or Response (1)
Opcode = Four bits defining kind of query:
  0:    a standard query (QUERY)
  1:    an inverse query (IQUERY)
  2:    a server status request (STATUS)
  3-15: reserved for future use
AA = Authoritative Answer bit
TC = Truncation bit
RD = Recursion Desired bit
RA = Recursion Available bit
Z = Reserved
RCODE = Response code
QC = Question Count
ANC = Answer Count
AUC = Authority Count
ADC = Additional Count

Write a function, member function, class or template that accepts a similar multi-line string as input to define a data structure or something else able to decode or store a header with that specified bit structure.

If your language has macros, introspection, code generation, or powerful enough templates, then accept such string at compile-time to define the header data structure statically.

Such "Header" function or template should accept a table with 8, 16, 32 or 64 columns, and any number of rows. For simplicity the only allowed symbols to define the table are + - | (plus, minus, pipe), and whitespace. Lines of the input string composed just of whitespace should be ignored. Leading and trailing whitespace in the input string should be ignored, as well as before and after each table row. The box for each bit of the diagram takes four chars "+--+". The code should perform a little of validation of the input string, but for brevity a full validation is not required.

Bonus: perform a thoroughly validation of the input string.

D

This solution generates anonymous struct code at compile-time, that can be mixed-in inside a struct or class. <lang d>string makeStructFromDiagram(in string rawDiagram) pure @safe {

   import std.conv: text, format;
   import std.string: strip, splitLines, indexOf;
   import std.array: empty, popFront;
   static void commitCurrent(ref uint anonCount,
                             ref uint totalBits,
                             ref uint currentBits,
                             ref string code,
                             ref string currentName) pure @safe {
       if (currentBits) {
           code ~= "\t";
           currentName = currentName.strip;
           if (currentName.empty) {
               anonCount++;
               currentName = "anonymous_field_" ~ anonCount.text;
           }
           string type;
           if (currentBits == 1)
               type = "bool";
           else if (currentBits <= ubyte.sizeof * 8)
               type = "ubyte";
           else if (currentBits <= ushort.sizeof * 8)
               type = "ushort";
           else if (currentBits <= uint.sizeof * 8)
               type = "uint";
           else if (currentBits <= ulong.sizeof * 8)
               type = "ulong";
           //else if (currentBits <= ucent.sizeof * 8)
           //    type = "ucent";
           else assert(0, "Too many bits for the item "~ currentName);
           immutable byteOffset = totalBits / 8;
           immutable bitOffset = totalBits % 8;


           // Getter:
           code ~= "@property " ~ type ~ " " ~ currentName ~
                   "() const pure nothrow @safe {\n";
           code ~= "\t\t";
           if (currentBits == 1) {
               code ~= format("return (_payload[%d] & (1 << (7-%d)))"~
                              " ? true : false;",
                              byteOffset, bitOffset);
           } else if (currentBits < 8) {
               auto mask = (1 << currentBits) - 1;
               mask <<= 7 - bitOffset - currentBits + 1;
               code ~= format("return (_payload[%d] & 0b%08b) >> %d;",
                              byteOffset, mask,
                              7 - bitOffset - currentBits + 1);
           } else {
               assert(currentBits % 8 == 0);
               assert(bitOffset == 0);
               code ~= type ~ " v = 0;\n\t\t";
               code ~= "version(LittleEndian) {\n\t\t";
               foreach (immutable i; 0 .. currentBits / 8)
                   code ~=  "\tv |= (cast(" ~ type ~ ") _payload[" ~
                            text(byteOffset + i) ~ "]) << (" ~
                            text((currentBits / 8) - i - 1) ~
                            " * 8);\n\t\t";
               code ~= "} else static assert(0);\n\t\t";
               code ~= "return v;";
           }
           code ~= "\n";
           code ~= "\t}\n\t";


           // Setter:
           code ~= "@property void " ~ currentName ~ "(in " ~ type ~
                   " value) pure nothrow @safe {\n";
           code ~= "\t\t";
           if (currentBits < 8) {
               auto mask = (1 << currentBits) - 1;
               mask <<= 7 - bitOffset - currentBits + 1;
               code ~= format("_payload[%d] &= ~0b%08b;\n\t\t",
                              byteOffset, mask);
               code ~= "assert(value < " ~ text(1 << currentBits) ~
                       ");\n\t\t";
               code~=format("_payload[%d] |= cast(ubyte) value << %d;",
                              byteOffset,
                              7 - bitOffset - currentBits + 1);
           } else {
               assert(currentBits % 8 == 0);
               assert(bitOffset == 0);
               code ~= "version(LittleEndian) {\n\t\t";
               foreach (immutable i; 0 .. currentBits / 8)
                   code ~= "\t_payload[" ~ text(byteOffset + i) ~
                           "] = (value >> (" ~
                           text((currentBits / 8) - i - 1) ~
                           " * 8) & 0xff);\n\t\t";
               code ~= "} else static assert(0);";
           }
           code ~= "\n";
           code ~= "\t}\n";
           totalBits += currentBits;
       }
       currentBits = 0;
       currentName = null;
   }
   enum C : char { pipe='|', cross='+' }
   enum cWidth = 3; // Width of a bit cell in the table.
   immutable diagram = rawDiagram.strip;
   uint bitCountPerRow = 0, anonCount = 0, totalBits, currentBits;
   string currentName;
   string code = "struct {\n"; // Anonymous.
   foreach (line; diagram.splitLines) {
       line = line.strip;
       if (line[0] == C.cross) {
           commitCurrent(anonCount, totalBits,
                         currentBits, code, currentName);
           if (bitCountPerRow == 0)
               bitCountPerRow = (line.length - 1) / cWidth;
           else
               assert(bitCountPerRow == (line.length - 1) / cWidth);
       } else {
           // A field of some sort.
           while (line.length > 2) {
               assert(line[0] != '/',
                      "Variable length data not supported");
               assert(line[0] == C.pipe, "Malformed table");
               line.popFront;
               const idx = line[0 .. $ - 1].indexOf(C.pipe);
               if (idx != -1) {
                   const field = line[0 .. idx];
                   line = line[idx .. $];
                   commitCurrent(anonCount, totalBits,
                                 currentBits, code, currentName);
                   currentName = field;
                   currentBits = (field.length + 1) / cWidth;
                   commitCurrent(anonCount, totalBits,
                                 currentBits, code, currentName);
               } else {
                   // The full row or a continuation of the last.
                   currentName ~= line[0 .. $ - 1];
                   // At this point, line does not include the first
                   // C.pipe, but the length will include the last.
                   currentBits += line.length / cWidth;
                   line = line[$ .. $];
               }
           }
       }
   }
   // Using bytes to avoid endianness issues.
   // hopefully the compiler will optimize it, otherwise
   // maybe we could specialize the properties more.
   code ~= "\n\tprivate ubyte[" ~ text((totalBits + 7) / 8) ~
           "] _payload;\n";
   return code ~ "}";

}


void main() { // Testing.

   import std.stdio;
   enum diagram = "
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |                      ID                       |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |                    QDCOUNT                    |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |                    ANCOUNT                    |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |                    NSCOUNT                    |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |                    ARCOUNT                    |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+";
   // To debug the code generation:
   //pragma(msg, diagram.makeStructFromDiagram);
   // Usage.
   static struct Header {
       mixin(diagram.makeStructFromDiagram);
   }
   Header h;
   h.ID = 10;
   h.RA = true;
   h.ARCOUNT = 255;
   h.Opcode = 7;
   // See the byte representation to test the setter's details.
   h._payload.writeln;
   // Test the getters:
   assert(h.ID == 10);
   assert(h.RA == true);
   assert(h.ARCOUNT == 255);
   assert(h.Opcode == 7);

}</lang>

Output:
[0, 10, 56, 128, 0, 0, 0, 0, 0, 0, 0, 255]

Uncommenting the pragma(msg) in the main function, you can see that it generates code like: <lang d>struct {

   @property ushort ID() const pure nothrow @safe {
       ushort v = 0;
       version(LittleEndian) {
           v |= (cast(ushort) _payload[0]) << (1 * 8);
           v |= (cast(ushort) _payload[1]) << (0 * 8);
       } else static assert(0);
       return v;
   }
   @property void ID(in ushort value) pure nothrow @safe {
       version(LittleEndian) {
           _payload[0] = (value >> (1 * 8) & 0xff);
           _payload[1] = (value >> (0 * 8) & 0xff);
       } else static assert(0);
   }

...

   private ubyte[12] _payload;

}</lang>

It also supports larger values like this, that is 32 bits long:

+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                   ThirtyTwo                   |
|                                               |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+