ASCII art diagram converter: Difference between revisions

From Rosetta Code
Content added Content deleted
(added Ol)
Line 1,013: Line 1,013:
Name ARCOUNT value : 0110100110100100
Name ARCOUNT value : 0110100110100100
</pre>
</pre>
=={{header|C}}==
interpret text diagram as data structure
<lang C>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
enum { MAX_ROWS=14, MAX_NAMES=20, NAME_SZ=80 };

char *Lines[MAX_ROWS] = {
" +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+",
" | ID |",
" +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+",
" |QR| Opcode |AA|TC|RD|RA| Z | RCODE |",
" +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+",
" | QDCOUNT |",
" +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+",
" | ANCOUNT |",
" +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+",
" | NSCOUNT |",
" +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+",
" | ARCOUNT |",
" +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+"
};
typedef struct {
unsigned bit3s;
unsigned mask;
unsigned data;
char A[NAME_SZ+2];
}NAME_T;
NAME_T names[MAX_NAMES];
unsigned idx_name;
enum{ID,BITS,QDCOUNT,ANCOUNT,NSCOUNT,ARCOUNT,MAX_HDR};
unsigned header[MAX_HDR]; // for test
unsigned idx_hdr;

int bit_hdr(char *pLine);
int bit_names(char *pLine);
void dump_names(void);
void make_test_hdr(void);

int main(void){
char *p1; int rv;
printf("Extract meta-data from bit-encoded text form\n");
make_test_hdr();
idx_name = 0;
for( int i=0; i<MAX_ROWS;i++ ){
p1 = Lines[i];
if( p1==NULL ) break;
if( rv = bit_hdr(Lines[i]), rv>0) continue;
if( rv = bit_names(Lines[i]),rv>0) continue;
//printf("%s, %d\n",p1, numbits );
}
dump_names();
}

int bit_hdr(char *pLine){ // count the '+--'
char *p1 = strchr(pLine,'+');
if( p1==NULL ) return 0;
int numbits=0;
for( int i=0; i<strlen(p1)-1; i+=3 ){
if( p1[i] != '+' || p1[i+1] != '-' || p1[i+2] != '-' ) return 0;
numbits++;
}
return numbits;
}

int bit_names(char *pLine){ // count the bit-group names
char *p1,*p2 = pLine, tmp[80];
unsigned sz=0, maskbitcount = 15;
while(1){
p1 = strchr(p2,'|'); if( p1==NULL ) break;
p1++;
p2 = strchr(p1,'|'); if( p2==NULL ) break;
sz = p2-p1;
tmp[sz] = 0; // set end of string
int k=0;
for(int j=0; j<sz;j++){ // strip spaces
if( p1[j] > ' ') tmp[k++] = p1[j];
}
tmp[k]= 0; sz++;
NAME_T *pn = &names[idx_name++];
strcpy(&pn->A[0], &tmp[0]);
pn->bit3s = sz/3;
if( pn->bit3s < 16 ){
for( int i=0; i<pn->bit3s; i++){
pn->mask |= 1 << maskbitcount--;
}
pn->data = header[idx_hdr] & pn->mask;
unsigned m2 = pn->mask;
while( (m2 & 1)==0 ){
m2>>=1;
pn->data >>= 1;
}
if( pn->mask == 0xf ) idx_hdr++;

}
else{
pn->data = header[idx_hdr++];
}
}
return sz;
}

void dump_names(void){ // print extracted names and bits
NAME_T *pn;
printf("-name-bits-mask-data-\n");
for( int i=0; i<MAX_NAMES; i++ ){
pn = &names[i];
if( pn->bit3s < 1 ) break;
printf("%10s %2d X%04x = %u\n",pn->A, pn->bit3s, pn->mask, pn->data);
}
puts("bye..");
}

void make_test_hdr(void){
header[ID] = 1024;
header[QDCOUNT] = 12;
header[ANCOUNT] = 34;
header[NSCOUNT] = 56;
header[ARCOUNT] = 78;
// QR OP AA TC RD RA Z RCODE
// 1 0110 1 0 1 0 000 1010
// 1011 0101 0000 1010
// B 5 0 A
header[BITS] = 0xB50A;
}
</lang>
{{out}}
<pre>
Extract meta-data from bit-encoded text form
-name-bits-mask-data-
ID 16 X0000 = 1024
QR 1 X8000 = 1
Opcode 4 X7800 = 6
AA 1 X0400 = 1
TC 1 X0200 = 0
RD 1 X0100 = 1
RA 1 X0080 = 0
Z 3 X0070 = 0
RCODE 4 X000f = 10
QDCOUNT 16 X0000 = 12
ANCOUNT 16 X0000 = 34
NSCOUNT 16 X0000 = 56
ARCOUNT 16 X0000 = 78
bye..
</pre>


=={{header|C++}}==
=={{header|C++}}==
Uses C++20
Uses C++20

Revision as of 01:54, 8 March 2021

Task
ASCII art diagram converter
You are encouraged to solve this task according to the task description, using any language you may know.

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.

AArch64 Assembly

Works with: as version Raspberry Pi 3B version Buster 64 bits
or android 64 bits with application Termux

<lang AArch64 Assembly> /* ARM assembly AARCH64 Raspberry PI 3B or android 64 bits */ /* program asciiDiagram64.s */

/*******************************************/ /* Constantes file */ /*******************************************/ /* for this file see task include a file in language AArch64 assembly*/ .include "../includeConstantesARM64.inc"

/*******************************************/ /* Structures */ /********************************************/ /* Structure result */

   .struct  0

res_name: //

   .struct  res_name + 8

res_startzone: //

   .struct  res_startzone + 8

res_endzone: //

   .struct  res_endzone + 8

res_size: //

   .struct  res_size + 8

res_end:

/*********************************/ /* Initialized data */ /*********************************/ .data szMessDiagram: .asciz "Display diagramm :\n" szMessValBin: .asciz "\nBinary Value  :\n" szMessValZone: .asciz "\nZones values : \n" szMessResultTab: .asciz "Name @ start @ end @ size @ \n" szMessSplitZone: .asciz "Name @ value : @ \n"

szMessErrSep: .asciz "Error : no séparator in first position of line.\n" szMessErrlong: .asciz "Error : string hexa size not multiple to 4. \n" szCarriageReturn: .asciz "\n"

szLine1: .asciz "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+"

          .equ LGLINE,  . - szLine1

szLine2: .asciz "| ID |" szLine3: .asciz "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+" szLine4: .asciz "|QR| Opcode |AA|TC|RD|RA| Z | RCODE |" szLine5: .asciz "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+" szLine6: .asciz "| QDCOUNT |" szLine7: .asciz "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+" szLine8: .asciz "| ANCOUNT |" szLine9: .asciz "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+" szLine10: .asciz "| NSCOUNT |" szLine11: .asciz "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+" szLine12: .asciz "| ARCOUNT |" szLine13: .asciz "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+"

          .equ NBLINES,  (. - szLine1) /  LGLINE

bSeparator: .byte '|'

szValueHex: .asciz "78477bbf5496e12e1bf169a4" szValueHexTest: .asciz "0ABCDEFabcdef123" /*********************************/ /* UnInitialized data */ /*********************************/ .bss sZoneConv: .skip 24 tbZones: .skip res_end * NBLINES * 5 sBuffer: .skip 100 /*********************************/ /* code section */ /*********************************/ .text .global main main: // entry of program

   ldr x0,qAdrszMessDiagram
   bl affichageMess
   ldr x1,qAdrszLine1
   mov x3,#LGLINE
   mov x2,#0

1: // display diagram lines

   madd x0,x2,x3,x1
   bl affichageMess
   ldr x0,qAdrszCarriageReturn
   bl affichageMess
   add x2,x2,#1
   cmp x2,#NBLINES
   blt 1b
   
   ldr x0,qAdrszLine1          // first line address of diagram 
   bl decode                   // decode the diagram
   mov x8,x0                   // save result number
   mov x3,#0                   // indice
   mov x4,#res_end             // one result size
   ldr x5,qAdrtbZones          // table result address

2:

   madd x6,x3,x4,x5             // compute result offset
   ldr x1,[x6,#res_name]       // zone name
   ldr x0,qAdrszMessResultTab
   bl strInsertAtCharInc       // insertion in message
   mov x7,x0
   ldr x0,[x6,#res_startzone]
   ldr x1,qAdrsZoneConv
   bl conversion10             // call decimal conversion
   mov x0,x7
   ldr x1,qAdrsZoneConv        // insert value conversion in message
   bl strInsertAtCharInc
   mov x7,x0
   ldr x0,[x6,#res_endzone]
   ldr x1,qAdrsZoneConv        // else display odd message
   bl conversion10             // call decimal conversion
   mov x0,x7
   ldr x1,qAdrsZoneConv        // insert value conversion in message
   bl strInsertAtCharInc
   mov x7,x0
   ldr x0,[x6,#res_size]
   ldr x1,qAdrsZoneConv        // else display odd message
   bl conversion10             // call decimal conversion
   mov x0,x7
   ldr x1,qAdrsZoneConv        // insert value conversion in message
   bl strInsertAtCharInc
   mov x7,x0
   bl affichageMess
   bl libererPlace             // liberation heap area 
   add x3,x3,#1
   cmp x3,x8
   blt 2b
   
   ldr x0,qAdrtbZones
   ldr x1,qAdrszValueHex
   //ldr x1,qAdrszValueHexTest
   bl extractValue             // convert string value hexa in binary string
   mov x7,x0                   // string binary address
   ldr x0,qAdrszMessValZone
   bl affichageMess
   
   mov x0,x7
   ldr x1,qAdrtbZones
   mov x2,x8                    // result number
   bl splitZone

100: // standard end of the program

   mov x0, #0                   // return code
   mov x8, #EXIT                // request to exit program
   svc #0                       // perform the system call

qAdrszCarriageReturn: .quad szCarriageReturn qAdrszMessResultTab: .quad szMessResultTab qAdrszMessDiagram: .quad szMessDiagram qAdrszMessValZone: .quad szMessValZone qAdrsZoneConv: .quad sZoneConv qAdrszLine1: .quad szLine1 qAdrszValueHex: .quad szValueHex qAdrszValueHexTest: .quad szValueHexTest /***************************************************/ /* decode lines */ /***************************************************/ // x0 contains diagram address // x0 return result counter decode:

   stp x1,lr,[sp,-16]!         // save  registres
   stp x2,x3,[sp,-16]!         // save  registres
   stp x4,x5,[sp,-16]!         // save  registres
   stp x6,x7,[sp,-16]!         // save  registres
   mov x5,x0
   mov x7,#LGLINE           // size line diagram
   ldr x3,qAdrbSeparator    // séparator
   ldrb w3,[x3]
   ldr x1,qAdrtbZones       // result table address
   mov x2,#0                // result counter
   mov x6,#1                // line 2

1:

   madd x0,x6,x7,x5         // compute line offset 
   ldrb w4,[x0]             // load one byte
   cmp w4,w3                // separator ?
   bne 99f                  // no -> error
   bl decodeOneLine         // decode a line
   mov x2,x0                // new result number
   add x6,x6,#2             // new line
   cmp x6,#NBLINES          // end ?
   blt 1b
   
   mov x0,x2                // return result counter
   b 100f

99:

   ldr x0,qAdrszMessErrSep
   bl affichageMess
   mov x0,#-1

100:

   ldp x6,x7,[sp],16         // restaur des  2 registres
   ldp x4,x5,[sp],16         // restaur des  2 registres
   ldp x2,x3,[sp],16         // restaur des  2 registres
   ldp x1,lr,[sp],16         // restaur des  2 registres
   ret

qAdrbSeparator: .quad bSeparator qAdrszMessErrSep: .quad szMessErrSep qAdrtbZones: .quad tbZones /***************************************************/ /* decode one line */ /***************************************************/ // x0 contains line diagram address // x1 contains table result // x2 contains result number // x3 contains séparator // x0 return new result number decodeOneLine:

   stp x1,lr,[sp,-16]!         // save  registres
   stp x2,x3,[sp,-16]!         // save  registres
   stp x4,x5,[sp,-16]!         // save  registres
   stp x6,x7,[sp,-16]!         // save  registres
   stp x8,x9,[sp,-16]!         // save  registres
   stp x10,x11,[sp,-16]!         // save  registres
   stp x12,x13,[sp,-16]!         // save  registres
   mov x11,x0          // save address diagram
   mov x7,x1           // save address table result
   mov x4,x2           // save result counter
   mov x0,#0           // zone size
   mov x5,#-1          // name text begin address
   mov x6,x3           // séparator
   mov x8,#res_end
   mov x10,#0          // zone start
   mov x12,#1          // character indice

1:

   ldrb w1,[x11,x12]   // load one byte
   cmp w1,#0           // line end ?
   beq 10f
   cmp w1,w6           // separator ?
   beq 3f
   cmp w1,#' '         // space ?
   bne 2f
   cmp x5,#0           // text name found ?
   mov x1,#0
   blt 11f
   strb w1,[x11,x12]   // yes -> 0 final text

11:

   add x0,x0,#1        // increment zone size
   add x12,x12,#1      // new byte
   b 1b                // and loop

2:

   cmp x5,#0           // text name found ?
   add x1,x11,x12      // no -> start zone 
   csel x5,x1,x5,lt
   add x0,x0,#1        // increment zone size
   add x12,x12,#1      // new byte
   b 1b                // and loop

3: // separator

   cmp x5,#0           // zone name ?
   blt 31f
   mov x1,#0
   strb w1,[x11,x12] // yes -> 0 final

31:

   madd x9,x4,x8,x7     // compute result offset 
   str x5,[x9,#res_name] // store address start name
   add x0,x0,#1        // compute zone size 
   cmp x0,#3
   mov x1,#2
   mov x5,#3
   csel x1,x5,x1,gt
   udiv x2,x0,x1    // / by size characters zone
   str x2,[x9,#res_size]
   cmp x4,#0           // first result ?
   csel x10,xzr,x10,eq
   beq 4f
   sub x10,x9,x8       // else start zone = prev end zone + 1
   ldr x10,[x10,#res_endzone]
   add x10,x10,#1

4:

   str x10,[x9,#res_startzone]
   add x10,x10,x2      // end zone = start zone + size - 1
   sub x10,x10,#1
   str x10,[x9,#res_endzone]
   add x4,x4,#1        // increment counter result
   mov x0,#0           // raz size zone
   add x10,x10,#1      // next byte
   mov x5,#-1          // no text name
   add x12,x12,#1      // next byte 
   b 1b                // and loop

10:

   mov x0,x4           // return result counter

100:

   ldp x12,x13,[sp],16         // restaur des  2 registres
   ldp x10,x11,[sp],16         // restaur des  2 registres
   ldp x8,x9,[sp],16         // restaur des  2 registres
   ldp x6,x7,[sp],16         // restaur des  2 registres
   ldp x4,x5,[sp],16         // restaur des  2 registres
   ldp x2,x3,[sp],16         // restaur des  2 registres
   ldp x1,lr,[sp],16         // restaur des  2 registres
   ret

/***************************************************/ /* convert strinh value hexa in binary string */ /***************************************************/ // x0 contains diagram address // x1 contains string hex value extractValue:

   stp x1,lr,[sp,-16]!         // save  registres
   stp x2,x3,[sp,-16]!         // save  registres
   stp x4,x5,[sp,-16]!         // save  registres
   stp x6,x7,[sp,-16]!         // save  registres
   stp x8,x9,[sp,-16]!         // save  registres
   mov x5,x0                   // save address
   ldr x0,qAdrszMessValBin
   bl affichageMess
   mov x6,x1                   // save address string hexa
   mov x2,#0
   mov x3,#0

1: // compute string size

   ldrb w4,[x1,x2]             // load byte
   cmp w4,#0                   // end string ?
   cinc x2,x2,ne
   bne 1b
   lsr x8,x2,#2                // control if multiple of 4
   lsl x3,x8,#2
   cmp x3,x2
   bne 99f                     // no -> error
   lsl x0,x2,#3                // compute size string * 8 
   add x0,x0,#1                // zero final
   bl reserverPlace            // reserve array on the heap
   mov x7,x0                   // address of heap array
   mov x1,x0                   // for routine
   mov x0,x6                   // address string value hexa
   bl conversionBin            // conversion string hexa -> binary
   mov x0,x7
   bl affichageMess
   ldr x0,qAdrszCarriageReturn
   bl affichageMess
   
   mov x0,x7                   // return address string binary
   b 100f

99:

   ldr x0,qAdrszMessErrlong
   bl affichageMess
   mov x0,#-1

100:

   ldp x8,x9,[sp],16         // restaur des  2 registres
   ldp x6,x7,[sp],16         // restaur des  2 registres
   ldp x4,x5,[sp],16         // restaur des  2 registres
   ldp x2,x3,[sp],16         // restaur des  2 registres
   ldp x1,lr,[sp],16         // restaur des  2 registres
   ret

qAdrszMessValBin: .quad szMessValBin qAdrszMessErrlong: .quad szMessErrlong /***************************************************/ /* decode lines */ /***************************************************/ // x0 contains address string binary // x1 contains table zones address // x2 contains result number splitZone:

   stp x1,lr,[sp,-16]!         // save  registres
   stp x2,x3,[sp,-16]!         // save  registres
   stp x4,x5,[sp,-16]!         // save  registres
   stp x6,x7,[sp,-16]!         // save  registres
   stp x8,x9,[sp,-16]!         // save  registres
   stp x10,x11,[sp,-16]!         // save  registres
   stp x12,x13,[sp,-16]!         // save  registres
   mov x5,x0
   mov x6,x1
   mov x3,#0                // indice table
   mov x4,#0                // indice string 
   mov x8,#res_end

1: // loop

   madd x7,x3,x8,x6          // compute result offset 
   ldr x0,[x7,#res_startzone]
   ldr x1,[x7,#res_size]    // zone size
   ldr x12,qAdrsBuffer
   mov x9,#0
   add x0,x0,x5

2: // copy bytes

   ldrb w10,[x0,x9]
   strb w10,[x12,x9]
   add x9,x9,#1
   cmp x9,x1                // zone size maxi ?
   blt 2b                   // no -> loop
   mov x10,#0               // 0 final 
   strb w10,[x12,x9]
                            // dislay name and value
   ldr x0,qAdrszMessSplitZone
   ldr x1,[x7,#res_name]
   bl strInsertAtCharInc
   mov x1,x12
   bl strInsertAtCharInc
   bl affichageMess
   bl libererPlace
   add x3,x3,#1
   cmp x3,x2                // end result ?
   blt 1b                   //  no -> loop

100:

   ldp x12,x13,[sp],16         // restaur des  2 registres
   ldp x10,x11,[sp],16         // restaur des  2 registres
   ldp x8,x9,[sp],16         // restaur des  2 registres
   ldp x6,x7,[sp],16         // restaur des  2 registres
   ldp x4,x5,[sp],16         // restaur des  2 registres
   ldp x2,x3,[sp],16         // restaur des  2 registres
   ldp x1,lr,[sp],16         // restaur des  2 registres
   ret

qAdrszMessSplitZone: .quad szMessSplitZone qAdrsBuffer: .quad sBuffer /***************************************************/ /* conversion chaine hexa en */ /***************************************************/ // x0 contains string address // x1 contains buffer address conversionBin:

   stp x1,lr,[sp,-16]!         // save  registres
   stp x2,x3,[sp,-16]!         // save  registres
   stp x4,x5,[sp,-16]!         // save  registres
   stp x6,x7,[sp,-16]!         // save  registres
   stp x8,x9,[sp,-16]!         // save  registres
   mov x2,#0
   mov x3,#0

1:

   ldrb w4,[x0,x2]
   cmp w4,#0                  // string end
   beq 10f
   subs w4,w4,#0x30           // conversion digits
   blt 5f
   cmp w4,#10
   blt 2f                     // digits 0 à 9 OK
   cmp w4,#18                 // < A ?
   blt 5f
   cmp w4,#24
   sub w5,w4,#8               // letters A-F
   csel w4,w5,w4,lt
   blt 2f
   cmp w4,#49                 // < a ?
   blt 5f
   cmp w4,#54                 // > f ?
   bgt 5f
   sub w4,w4,#39              // letters  a-f

2: // x4 contains value on right 4 bits

   mov x5,#0
   add x3,x3,#4               // size ' bits
   sub x7,x3,#1               // store indice

3:

   tst x4,#1                  // test first right bit
   mov x6,#48                 //    character '0'
   mov x8,#49                 //    character '1'
   csel x6,x8,x6,ne
   lsr x4,x4,1
   strb w6,[x1,x7]            // character ->   display zone
   sub x7,x7,#1               // prev position
   add x5,x5,#1               // next bit
   cmp x5,#4                  // end ? 
   blt 3b

5: // loop to next byte

   add x2,x2,#1
   b 1b

10:

   mov x6,#0
   strb w6,[x1,x3]            // zéro final

100:

   ldp x8,x9,[sp],16         // restaur des  2 registres
   ldp x6,x7,[sp],16         // restaur des  2 registres
   ldp x4,x5,[sp],16         // restaur des  2 registres
   ldp x2,x3,[sp],16         // restaur des  2 registres
   ldp x1,lr,[sp],16         // restaur des  2 registres
   ret

/********************************************************/ /* File Include fonctions */ /********************************************************/ /* for this file see task include a file in language AArch64 assembly */ .include "../includeARM64.inc" </lang>

Output:
Display diagramm :
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                      ID                       |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    QDCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    ANCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    NSCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    ARCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
Name ID start 0 end 15 size 16
Name QR start 16 end 16 size 1
Name Opcode start 17 end 20 size 4
Name AA start 21 end 21 size 1
Name TC start 22 end 22 size 1
Name RD start 23 end 23 size 1
Name RA start 24 end 24 size 1
Name Z start 25 end 27 size 3
Name RCODE start 28 end 31 size 4
Name QDCOUNT start 32 end 47 size 16
Name ANCOUNT start 48 end 63 size 16
Name NSCOUNT start 64 end 79 size 16
Name ARCOUNT start 80 end 95 size 16

Binary Value  :
011110000100011101111011101111110101010010010110111000010010111000011011111100010110100110100100

Zones values :
Name ID value : 0111100001000111
Name QR value : 0
Name Opcode value : 1111
Name AA value : 0
Name TC value : 1
Name RD value : 1
Name RA value : 1
Name Z value : 011
Name RCODE value : 1111
Name QDCOUNT value : 0101010010010110
Name ANCOUNT value : 1110000100101110
Name NSCOUNT value : 0001101111110001
Name ARCOUNT value : 0110100110100100

ARM Assembly

Works with: as version Raspberry Pi
or android 32 bits with application Termux

<lang ARM Assembly> /* ARM assembly Raspberry PI or android 32 bits */ /* program asciiDiagram.s */

/* REMARK 1 : this program use routines in a include file

  see task Include a file language arm assembly 
  for the routine affichageMess conversion10 
  see at end of this program the instruction include */

/* for constantes see task include a file in arm assembly */ /************************************/ /* Constantes */ /************************************/ .include "../constantes.inc"

/*******************************************/ /* Structures */ /********************************************/ /* Structure result */

   .struct  0

res_name: //

   .struct  res_name + 4

res_startzone: //

   .struct  res_startzone + 4

res_endzone: //

   .struct  res_endzone + 4

res_size: //

   .struct  res_size + 4

res_end:

/*********************************/ /* Initialized data */ /*********************************/ .data szMessDiagram: .asciz "Display diagramm :\n" szMessValBin: .asciz "\nBinary Value  :\n" szMessValZone: .asciz "\nZones values : \n" szMessResultTab: .asciz "Name @ start @ end @ size @ \n" szMessSplitZone: .asciz "Name @ value : @ \n"

szMessErrSep: .asciz "Error : no séparator in first position of line.\n" szMessErrlong: .asciz "Error : string hexa size not multiple to 4. \n" szCarriageReturn: .asciz "\n"

szLine1: .asciz "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+"

          .equ LGLINE,  . - szLine1

szLine2: .asciz "| ID |" szLine3: .asciz "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+" szLine4: .asciz "|QR| Opcode |AA|TC|RD|RA| Z | RCODE |" szLine5: .asciz "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+" szLine6: .asciz "| QDCOUNT |" szLine7: .asciz "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+" szLine8: .asciz "| ANCOUNT |" szLine9: .asciz "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+" szLine10: .asciz "| NSCOUNT |" szLine11: .asciz "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+" szLine12: .asciz "| ARCOUNT |" szLine13: .asciz "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+"

          .equ NBLINES,  (. - szLine1) /  LGLINE

bSeparator: .byte '|'

szValueHex: .asciz "78477bbf5496e12e1bf169a4" szValueHexTest: .asciz "0ABCDEFabcdef123" /*********************************/ /* UnInitialized data */ /*********************************/ .bss sZoneConv: .skip 24 tbZones: .skip res_end * NBLINES * 5 sBuffer: .skip 100 /*********************************/ /* code section */ /*********************************/ .text .global main main: @ entry of program

   ldr r0,iAdrszMessDiagram
   bl affichageMess
   ldr r1,iAdrszLine1
   mov r3,#LGLINE
   mov r2,#0

1: @ display diagram lines

   mla r0,r2,r3,r1
   bl affichageMess
   ldr r0,iAdrszCarriageReturn
   bl affichageMess
   add r2,r2,#1
   cmp r2,#NBLINES
   blt 1b
   
   ldr r0,iAdrszLine1          @ first line address of diagram 
   bl decode                   @ decode the diagram
   mov r8,r0                   @ save result number
   mov r3,#0                   @ indice
   mov r4,#res_end             @ one result size
   ldr r5,iAdrtbZones          @ table result address

2:

   mla r6,r3,r4,r5             @ compute result offset
   ldr r1,[r6,#res_name]       @ zone name
   ldr r0,iAdrszMessResultTab
   bl strInsertAtCharInc       @ insertion in message
   mov r7,r0
   ldr r0,[r6,#res_startzone]
   ldr r1,iAdrsZoneConv
   bl conversion10             @ call decimal conversion
   mov r0,r7
   ldr r1,iAdrsZoneConv        @ insert value conversion in message
   bl strInsertAtCharInc
   mov r7,r0
   ldr r0,[r6,#res_endzone]
   ldr r1,iAdrsZoneConv        @ else display odd message
   bl conversion10             @ call decimal conversion
   mov r0,r7
   ldr r1,iAdrsZoneConv        @ insert value conversion in message
   bl strInsertAtCharInc
   mov r7,r0
   ldr r0,[r6,#res_size]
   ldr r1,iAdrsZoneConv        @ else display odd message
   bl conversion10             @ call decimal conversion
   mov r0,r7
   ldr r1,iAdrsZoneConv        @ insert value conversion in message
   bl strInsertAtCharInc
   mov r7,r0
   bl affichageMess
   bl libererPlace             @ liberation heap area 
   add r3,r3,#1
   cmp r3,r8
   blt 2b
   
   ldr r0,iAdrtbZones
   ldr r1,iAdrszValueHex
   //ldr r1,iAdrszValueHexTest
   bl extractValue             @ convert string value hexa in binary string
   mov r7,r0                   @ string binary address
   ldr r0,iAdrszMessValZone
   bl affichageMess
   
   mov r0,r7
   ldr r1,iAdrtbZones
   mov r2,r8                    @ result number
   bl splitZone

100: @ standard end of the program

   mov r0, #0                   @ return code
   mov r7, #EXIT                @ request to exit program
   svc #0                       @ perform the system call

iAdrszCarriageReturn: .int szCarriageReturn iAdrszMessResultTab: .int szMessResultTab iAdrszMessDiagram: .int szMessDiagram iAdrszMessValZone: .int szMessValZone iAdrsZoneConv: .int sZoneConv iAdrszLine1: .int szLine1 iAdrszValueHex: .int szValueHex iAdrszValueHexTest: .int szValueHexTest /***************************************************/ /* decode lines */ /***************************************************/ // r0 contains diagram address // r0 return result counter decode:

   push {r1-r7,lr}          @ save  registers 
   mov r5,r0
   mov r7,#LGLINE           @ size line diagram
   ldr r3,iAdrbSeparator    @ séparator
   ldrb r3,[r3]
   ldr r1,iAdrtbZones       @ result table address
   mov r2,#0                @ result counter
   mov r6,#1                @ line 2

1:

   mla r0,r6,r7,r5          @ compute line offset 
   ldrb r4,[r0]             @ load one byte
   cmp r4,r3                @ separator ?
   bne 99f                  @ no -> error
   bl decodeOneLine         @ decode a line
   mov r2,r0                @ new result number
   add r6,r6,#2             @ new line
   cmp r6,#NBLINES          @ end ?
   blt 1b
   
   mov r0,r2                @ return result counter
   b 100f

99:

   ldr r0,iAdrszMessErrSep
   bl affichageMess
   mov r0,#-1

100:

   pop {r1-r7,lr}             @ restaur registers
   bx lr                      @ return

iAdrbSeparator: .int bSeparator iAdrszMessErrSep: .int szMessErrSep iAdrtbZones: .int tbZones /***************************************************/ /* decode one line */ /***************************************************/ // r0 contains line diagram address // r1 contains table result // r2 contains result number // r3 contains séparator // r0 return new result number decodeOneLine:

   push {r1-r12,lr}             @ save  registers 
   mov r11,r0          @ save address diagram
   mov r7,r1           @ save address table result
   mov r4,r2           @ save result counter
   mov r0,#0           @ zone size
   mov r5,#-1          @ name text begin address
   mov r6,r3           @ séparator
   mov r8,#res_end
   mov r10,#0          @ zone start
   mov r12,#1          @ character indice

1:

   ldrb r1,[r11,r12]   @ load one byte
   cmp r1,#0           @ line end ?
   beq 10f
   cmp r1,r6           @ separator ?
   beq 3f
   cmp r1,#' '         @ space ?
   bne 2f
   cmp r5,#0           @ text name found ?
   mov r1,#0
   strgeb r1,[r11,r12]   @ yes -> 0 final text
   add r0,r0,#1        @ increment zone size
   add r12,r12,#1      @ new byte
   b 1b                @ and loop

2:

   cmp r5,#0           @ text name found ?
   addlt r5,r11,r12    @ no -> start zone
   add r0,r0,#1        @ increment zone size
   add r12,r12,#1      @ new byte
   b 1b                @ and loop

3: @ separator

   cmp r5,#0           @ zone name ?
   mov r1,#0
   strgeb r1,[r11,r12] @ yes -> 0 final
   mla r9,r4,r8,r7     @ compute result offset 
   str r5,[r9,#res_name] @ store address start name
   add r0,r0,#1        @ compute zone size 
   cmp r0,#3
   movle r1,#2
   movgt r1,#3
   bl division         @ / by size characters zone
   str r2,[r9,#res_size]
   cmp r4,#0           @ first result ?
   moveq r10,#0        @ yes -> start zone = 0
   beq 4f
   sub r10,r9,r8       @ else start zone = prev end zone + 1
   ldr r10,[r10,#res_endzone]
   add r10,r10,#1

4:

   str r10,[r9,#res_startzone]
   add r10,r10,r2      @ end zone = start zone + size - 1
   sub r10,r10,#1
   str r10,[r9,#res_endzone]
   add r4,r4,#1        @ increment counter result
   mov r0,#0           @ raz size zone
   add r10,r10,#1      @ next byte
   mov r5,#-1          @ no text name
   add r12,r12,#1      @ next byte 
   b 1b                @ and loop

10:

   mov r0,r4           @ return result counter

100:

   pop {r1-r12,lr}     @ restaur registers
   bx lr               @ return

/***************************************************/ /* convert strinh value hexa in binary string */ /***************************************************/ // r0 contains diagram address // r1 contains string hex value extractValue:

   push {r1-r8,lr}             @ save  registers 
   mov r5,r0                   @ save address
   ldr r0,iAdrszMessValBin
   bl affichageMess
   mov r6,r1                   @ save address string hexa
   mov r2,#0
   mov r3,#0

1: @ compute string size

   ldrb r4,[r1,r2]             @ load byte
   cmp r4,#0                   @ end string ?
   addne r2,r2,#1
   bne 1b
   lsr r8,r2,#2                @ control if multiple of 4
   lsl r3,r8,#2
   cmp r3,r2
   bne 99f                     @ no -> error
   lsl r0,r2,#3                @ compute size string * 8 
   add r0,r0,#1                @ zero final
   bl reserverPlace            @ reserve array on the heap
   mov r7,r0                   @ address of heap array
   mov r1,r0                   @ for routine
   mov r0,r6                   @ address string value hexa
   bl conversionBin            @ conversion string hexa -> binary
   mov r0,r7
   bl affichageMess
   ldr r0,iAdrszCarriageReturn
   bl affichageMess
   
   mov r0,r7                   @ return address string binary
   b 100f

99:

   ldr r0,iAdrszMessErrlong
   bl affichageMess
   mov r0,#-1

100:

   pop {r1-r8,lr}              @ restaur registers
   bx lr                       @ return

iAdrszMessValBin: .int szMessValBin iAdrszMessErrlong: .int szMessErrlong /***************************************************/ /* decode lines */ /***************************************************/ // r0 contains address string binary // r1 contains table zones address // r2 contains result number splitZone:

   push {r1-r12,lr}         @ save  registers 
   mov r5,r0
   mov r6,r1
   mov r3,#0                @ indice table
   mov r4,#0                @ indice string 
   mov r8,#res_end

1: @ loop

   mla r7,r3,r8,r6          @ compute result offset 
   ldr r0,[r7,#res_startzone]
   ldr r1,[r7,#res_size]    @ zone size
   ldr r12,iAdrsBuffer
   mov r9,#0
   add r0,r0,r5

2: @ copy bytes

   ldrb r10,[r0,r9]
   strb r10,[r12,r9]
   add r9,r9,#1
   cmp r9,r1                @ zone size maxi ?
   blt 2b                   @ no -> loop
   mov r10,#0               @ 0 final 
   str r10,[r12,r9]
                            @ dislay name and value
   ldr r0,iAdrszMessSplitZone
   ldr r1,[r7,#res_name]
   bl strInsertAtCharInc
   mov r1,r12
   bl strInsertAtCharInc
   bl affichageMess
   bl libererPlace
   add r3,r3,#1
   cmp r3,r2                @ end result ?
   blt 1b                   @  no -> loop

100:

   pop {r1-r12,lr}          @ restaur registers
   bx lr                    @ return

iAdrszMessSplitZone: .int szMessSplitZone iAdrsBuffer: .int sBuffer /***************************************************/ /* conversion chaine hexa en */ /***************************************************/ // r0 contains string address // r1 contains buffer address conversionBin:

   push {r2-r7,lr}            @ save  registers 
   mov r2,#0
   mov r3,#0

1:

   ldrb r4,[r0,r2]
   cmp r4,#0                  @ string end
   beq 10f
   subs r4,r4,#0x30           @ conversion digits
   blt 5f
   cmp r4,#10
   blt 2f                     @ digits 0 à 9 OK
   cmp r4,#18                 @ < A ?
   blt 5f
   //vidregtit inter
   cmp r4,#24
   sublt r4,r4,#8             @ letters A-F
   blt 2f
   cmp r4,#49                 @ < a ?
   blt 5f
   cmp r4,#54                 @ > f ?
   bgt 5f
   sub r4,r4,#39              @ letters  a-f

2: @ r4 contains value on right 4 bits

   mov r5,#0
   add r3,r3,#4               @ size bits
   sub r7,r3,#1               @ store indice

3:

   lsrs r4,#1                 @ right bit in carry
   movcc r6,#48               @ flag carry off   character '0'
   movcs r6,#49               @ flag carry on    character '1'
   strb r6,[r1,r7]            @ character ->   display zone
   sub r7,r7,#1               @ prev position
   add r5,r5,#1               @ next bit
   cmp r5,#4                  @ end ? 
   blt 3b

5: @ loop to next byte

   add r2,r2,#1
   b 1b

10:

   mov r6,#0
   strb r6,[r1,r3]            @ zéro final

100:

   pop {r2-r7,lr}             @ restaur registers
   bx lr                      @ return

/***************************************************/ /* ROUTINES INCLUDE */ /***************************************************/ .include "../affichage.inc" </lang>

Output:
Display diagramm :
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                      ID                       |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    QDCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    ANCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    NSCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    ARCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
Name ID start 0           end 15          size 16
Name QR start 16          end 16          size 1
Name Opcode start 17          end 20          size 4
Name AA start 21          end 21          size 1
Name TC start 22          end 22          size 1
Name RD start 23          end 23          size 1
Name RA start 24          end 24          size 1
Name Z start 25          end 27          size 3
Name RCODE start 28          end 31          size 4
Name QDCOUNT start 32          end 47          size 16
Name ANCOUNT start 48          end 63          size 16
Name NSCOUNT start 64          end 79          size 16
Name ARCOUNT start 80          end 95          size 16

Binary Value  :
011110000100011101111011101111110101010010010110111000010010111000011011111100010110100110100100

Zones values :
Name ID value : 0111100001000111
Name QR value : 0
Name Opcode value : 1111
Name AA value : 0
Name TC value : 1
Name RD value : 1
Name RA value : 1
Name Z value : 011
Name RCODE value : 1111
Name QDCOUNT value : 0101010010010110
Name ANCOUNT value : 1110000100101110
Name NSCOUNT value : 0001101111110001
Name ARCOUNT value : 0110100110100100

C

interpret text diagram as data structure <lang C>

  1. include <stdlib.h>
  2. include <stdio.h>
  3. include <string.h>

enum { MAX_ROWS=14, MAX_NAMES=20, NAME_SZ=80 };

char *Lines[MAX_ROWS] = {

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

}; typedef struct {

  unsigned bit3s;
  unsigned mask;
  unsigned data;
  char A[NAME_SZ+2];

}NAME_T; NAME_T names[MAX_NAMES]; unsigned idx_name; enum{ID,BITS,QDCOUNT,ANCOUNT,NSCOUNT,ARCOUNT,MAX_HDR}; unsigned header[MAX_HDR]; // for test unsigned idx_hdr;

int bit_hdr(char *pLine); int bit_names(char *pLine); void dump_names(void); void make_test_hdr(void);

int main(void){

  char *p1;   int rv;
  printf("Extract meta-data from bit-encoded text form\n");
  make_test_hdr();
  idx_name = 0;
  for( int i=0; i<MAX_ROWS;i++ ){
     p1 = Lines[i];
     if( p1==NULL ) break;
     if( rv = bit_hdr(Lines[i]), rv>0) continue;
     if( rv = bit_names(Lines[i]),rv>0) continue;
     //printf("%s, %d\n",p1, numbits );
  }
  dump_names();

}

int bit_hdr(char *pLine){ // count the '+--'

  char *p1 = strchr(pLine,'+');
  if( p1==NULL ) return 0;
  int numbits=0;
  for( int i=0; i<strlen(p1)-1; i+=3 ){
     if( p1[i] != '+' || p1[i+1] != '-' || p1[i+2] != '-' ) return 0;
     numbits++;
  }
  return numbits;

}

int bit_names(char *pLine){ // count the bit-group names

  char *p1,*p2 = pLine, tmp[80];
  unsigned sz=0, maskbitcount = 15;
  while(1){
     p1 = strchr(p2,'|');  if( p1==NULL ) break;
     p1++;
     p2 = strchr(p1,'|');  if( p2==NULL ) break;
     sz = p2-p1;
     tmp[sz] = 0;  // set end of string
     int k=0;
     for(int j=0; j<sz;j++){  // strip spaces

if( p1[j] > ' ') tmp[k++] = p1[j];

     }
     tmp[k]= 0; sz++;
     NAME_T *pn = &names[idx_name++];
     strcpy(&pn->A[0], &tmp[0]);
     pn->bit3s = sz/3;
     if( pn->bit3s < 16 ){

for( int i=0; i<pn->bit3s; i++){ pn->mask |= 1 << maskbitcount--; } pn->data = header[idx_hdr] & pn->mask; unsigned m2 = pn->mask; while( (m2 & 1)==0 ){ m2>>=1; pn->data >>= 1; } if( pn->mask == 0xf ) idx_hdr++;

     }
     else{

pn->data = header[idx_hdr++];

     }
  }
  return sz;

}

void dump_names(void){ // print extracted names and bits

  NAME_T *pn;
  printf("-name-bits-mask-data-\n");
  for( int i=0; i<MAX_NAMES; i++ ){
     pn = &names[i];
     if( pn->bit3s < 1 ) break;
     printf("%10s %2d X%04x = %u\n",pn->A, pn->bit3s, pn->mask, pn->data);
  }
  puts("bye..");

}

void make_test_hdr(void){

  header[ID] = 1024;
  header[QDCOUNT] = 12;
  header[ANCOUNT] = 34;
  header[NSCOUNT] = 56;
  header[ARCOUNT] = 78;
  // QR OP   AA TC RD RA Z   RCODE
  // 1  0110 1  0  1  0  000 1010
  // 1011 0101 0000 1010
  // B    5    0    A
  header[BITS] = 0xB50A;

} </lang>

Output:
Extract meta-data from bit-encoded text form
-name-bits-mask-data-
        ID 16 X0000 = 1024
        QR  1 X8000 = 1
    Opcode  4 X7800 = 6
        AA  1 X0400 = 1
        TC  1 X0200 = 0
        RD  1 X0100 = 1
        RA  1 X0080 = 0
         Z  3 X0070 = 0
     RCODE  4 X000f = 10
   QDCOUNT 16 X0000 = 12
   ANCOUNT 16 X0000 = 34
   NSCOUNT 16 X0000 = 56
   ARCOUNT 16 X0000 = 78
bye..


C++

Uses C++20

The ASCII diagram is parsed at compile time into a data structure that is used at runtime to encode and decode the fields.

<lang cpp>#include <array>

  1. include <bitset>
  2. include <iostream>

using namespace std;

struct FieldDetails {string_view Name; int NumBits;};

// parses the ASCII diagram and returns the field name, bit sizes, and the // total byte size template <const char *T> consteval auto ParseDiagram() {

   // trim the ASCII diagram text
   constexpr string_view rawArt(T);
   constexpr auto firstBar = rawArt.find("|");
   constexpr auto lastBar = rawArt.find_last_of("|");
   constexpr auto art = rawArt.substr(firstBar, lastBar - firstBar);
   static_assert(firstBar < lastBar, "ASCII Table has no fields");
   
   // make an array for all of the fields
   constexpr auto numFields = 
       count(rawArt.begin(), rawArt.end(), '|') -
       count(rawArt.begin(), rawArt.end(), '\n') / 2;    
   array<FieldDetails, numFields> fields;
   
   // parse the diagram
   bool isValidDiagram = true;
   int startDiagramIndex = 0;
   int totalBits = 0;
   for(int i = 0; i < numFields; )
   {
       auto beginningBar = art.find("|", startDiagramIndex);
       auto endingBar = art.find("|", beginningBar + 1);
       auto field = art.substr(beginningBar + 1, endingBar - beginningBar - 1);
       if(field.find("-") == field.npos) 
       {
           int numBits = (field.size() + 1) / 3;
           auto nameStart = field.find_first_not_of(" ");
           auto nameEnd = field.find_last_not_of(" ");
           if (nameStart > nameEnd || nameStart == string_view::npos) 
           {
               // the table cannot be parsed
               isValidDiagram = false;
               field = ""sv;
           }
           else
           {
               field = field.substr(nameStart, 1 + nameEnd - nameStart);
           }
           fields[i++] = FieldDetails {field, numBits};
           totalBits += numBits;
       }
       startDiagramIndex = endingBar;
   }
   
   int numRawBytes = isValidDiagram ? (totalBits - 1) / 8 + 1 : 0;
   return make_pair(fields, numRawBytes);

}

// encode the values of the fields into a raw data array template <const char *T> auto Encode(auto inputValues) {

   constexpr auto parsedDiagram = ParseDiagram<T>();
   static_assert(parsedDiagram.second > 0, "Invalid ASCII talble");
   array<unsigned char, parsedDiagram.second> data;
   int startBit = 0;
   int i = 0;
   for(auto value : inputValues)
   {
       const auto &field = parsedDiagram.first[i++];
       int remainingValueBits = field.NumBits;
       while(remainingValueBits > 0)
       {
           // pack the bits from an input field into the data array
           auto [fieldStartByte, fieldStartBit] = div(startBit, 8);
           int unusedBits = 8 - fieldStartBit;
           int numBitsToEncode = min({unusedBits, 8, field.NumBits});
           int divisor = 1 << (remainingValueBits - numBitsToEncode);
           unsigned char bitsToEncode = value / divisor;
           data[fieldStartByte] <<= numBitsToEncode;
           data[fieldStartByte] |= bitsToEncode;
           value %= divisor;
           startBit += numBitsToEncode;
           remainingValueBits -= numBitsToEncode;
       }
   }
   
   return data;

}

// decode the raw data into the format of the ASCII diagram template <const char *T> void Decode(auto data) {

   cout << "Name      Bit Pattern\n";
   cout << "=======   ================\n";
   constexpr auto parsedDiagram = ParseDiagram<T>();
   static_assert(parsedDiagram.second > 0, "Invalid ASCII talble");
   int startBit = 0;
   for(const auto& field : parsedDiagram.first)
   {
       // unpack the bits from the data into a single field
       auto [fieldStartByte, fieldStartBit] = div(startBit, 8);
       unsigned char firstByte = data[fieldStartByte];
       firstByte <<= fieldStartBit;
       firstByte >>= fieldStartBit;
       int64_t value = firstByte;
       auto endBit = startBit + field.NumBits;
       auto [fieldEndByte, fieldEndBit] = div(endBit, 8);
       fieldEndByte = min(fieldEndByte, (int)(ssize(data) - 1));
       for(int index = fieldStartByte + 1; index <= fieldEndByte; index++)
       {
           value <<= 8;
           value += data[index];
       }
       value >>= fieldEndBit;
       startBit = endBit;
       
       cout << field.Name << 
           string_view("        ", (7 - field.Name.size())) << "   " << 
           string_view(bitset<64>(value).to_string()).substr(64 - field.NumBits, 64) <<  "\n";
   }
           

}

int main(void) {

   static constexpr char art[] = R"(
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |                      ID                       |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |                    QDCOUNT                    |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |                    ANCOUNT                    |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |                    NSCOUNT                    |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |                    ARCOUNT                    |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+)";
   
   // using the table above, encode the data below
   auto rawData = Encode<art> (initializer_list<int64_t> {
       30791,
       0, 15, 0, 1, 1, 1, 3, 15,
       21654,
       57646,
       7153,
       27044
   });
   
   cout << "Raw encoded data in hex:\n";
   for (auto v : rawData) printf("%.2X", v);
   cout << "\n\n";
   
   cout << "Decoded raw data:\n";
   Decode<art>(rawData);

}</lang>

Output:
Raw encoded data in hex:
78477BBF5496E12E1BF169A4

Decoded raw data:
Name      Bit Pattern
=======   ================
ID        0100011101111011
QR        1
Opcode    0011
AA        0
TC        0
RD        1
RA        1
Z         011
RCODE     0100
QDCOUNT   1001011011100001
ANCOUNT   0010111000011011
NSCOUNT   1111000101101001
ARCOUNT   0110100110100100

An invalid table will cause a compilation error - here the ANCOUNT field is missing. <lang cpp> static constexpr char missingFieldArt[] = R"(

   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |                      ID                       |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |QR|   Opcode  |AA|TC|RD|RA|    Z   |   RCODE   |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |                    QDCOUNT                    |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |                                               |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |                    NSCOUNT                    |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |                    ARCOUNT                    |
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+)";

</lang>

GCC 11 gives this error

Output:
../ascii-art-diagram-converter.cpp: In instantiation of 'void Decode(auto:12) [with const char* T = (& missingFieldArt); auto:12 = std::array<unsigned char, 12>]':
../ascii-art-diagram-converter.cpp:181:36:   required from here
../ascii-art-diagram-converter.cpp:98:40: error: static assertion failed: Invalid ASCII talble
   98 |     static_assert(parsedDiagram.second > 0, "Invalid ASCII talble");
      |                   ~~~~~~~~~~~~~~~~~~~~~^~~
../ascii-art-diagram-converter.cpp:98:40: note: '(((int)parsedDiagram.std::pair<std::array<FieldDetails, 13>, int>::second) > 0)' evaluates to false

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;
   import std.format: format;
   import std.string: strip, splitLines, indexOf;
   import std.array: empty, popFront;
   static void commitCurrent(ref uint anonCount,
                             ref uint totalBits,
                             ref size_t 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;
   size_t bitCountPerRow = 0, currentBits;
   uint anonCount = 0, totalBits;
   string currentName;
   string code = "struct {\n"; // Anonymous.
   foreach (line; diagram.splitLines) {
       assert(!line.empty);
       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]

Static support for BigEndian is easy to add.

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

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

Go

<lang go>package main

import (

   "fmt"
   "log"
   "math/big"
   "strings"

)

type result struct {

   name  string
   size  int
   start int
   end   int

}

func (r result) String() string {

   return fmt.Sprintf("%-7s   %2d    %3d   %3d", r.name, r.size, r.start, r.end)

}

func validate(diagram string) []string {

   var lines []string
   for _, line := range strings.Split(diagram, "\n") {
       line = strings.Trim(line, " \t")
       if line != "" {
           lines = append(lines, line)
       }
   }
   if len(lines) == 0 {
       log.Fatal("diagram has no non-empty lines!")
   }
   width := len(lines[0])
   cols := (width - 1) / 3
   if cols != 8 && cols != 16 && cols != 32 && cols != 64 {
       log.Fatal("number of columns should be 8, 16, 32 or 64")
   }
   if len(lines)%2 == 0 {
       log.Fatal("number of non-empty lines should be odd")
   }
   if lines[0] != strings.Repeat("+--", cols)+"+" {
       log.Fatal("incorrect header line")
   }
   for i, line := range lines {
       if i == 0 {
           continue
       } else if i%2 == 0 {
           if line != lines[0] {
               log.Fatal("incorrect separator line")
           }
       } else if len(line) != width {
           log.Fatal("inconsistent line widths")
       } else if line[0] != '|' || line[width-1] != '|' {
           log.Fatal("non-separator lines must begin and end with '|'")
       }
   }
   return lines

}

func decode(lines []string) []result {

   fmt.Println("Name     Bits  Start  End")
   fmt.Println("=======  ====  =====  ===")
   start := 0
   width := len(lines[0])
   var results []result
   for i, line := range lines {
       if i%2 == 0 {
           continue
       }
       line := line[1 : width-1]
       for _, name := range strings.Split(line, "|") {
           size := (len(name) + 1) / 3
           name = strings.TrimSpace(name)
           res := result{name, size, start, start + size - 1}
           results = append(results, res)
           fmt.Println(res)
           start += size
       }
   }
   return results

}

func unpack(results []result, hex string) {

   fmt.Println("\nTest string in hex:")
   fmt.Println(hex)
   fmt.Println("\nTest string in binary:")
   bin := hex2bin(hex)
   fmt.Println(bin)
   fmt.Println("\nUnpacked:\n")
   fmt.Println("Name     Size  Bit pattern")
   fmt.Println("=======  ====  ================")
   for _, res := range results {
       fmt.Printf("%-7s   %2d   %s\n", res.name, res.size, bin[res.start:res.end+1])
   }

}

func hex2bin(hex string) string {

   z := new(big.Int)
   z.SetString(hex, 16)
   return fmt.Sprintf("%0*b", 4*len(hex), z)

}

func main() {

   const diagram = `
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                      ID                       |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       |                    QDCOUNT                    |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       |                    ANCOUNT                    |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       |                    NSCOUNT                    |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       |                    ARCOUNT                    |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   `
   lines := validate(diagram)
   fmt.Println("Diagram after trimming whitespace and removal of blank lines:\n")
   for _, line := range lines {
       fmt.Println(line)
   }
   fmt.Println("\nDecoded:\n")
   results := decode(lines)
   hex := "78477bbf5496e12e1bf169a4" // test string
   unpack(results, hex)

}</lang>

Output:
Diagram after trimming whitespace and removal of blank lines:

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

Decoded:

Name     Bits  Start  End
=======  ====  =====  ===
ID        16      0    15
QR         1     16    16
Opcode     4     17    20
AA         1     21    21
TC         1     22    22
RD         1     23    23
RA         1     24    24
Z          3     25    27
RCODE      4     28    31
QDCOUNT   16     32    47
ANCOUNT   16     48    63
NSCOUNT   16     64    79
ARCOUNT   16     80    95

Test string in hex:
78477bbf5496e12e1bf169a4

Test string in binary:
011110000100011101111011101111110101010010010110111000010010111000011011111100010110100110100100

Unpacked:

Name     Size  Bit pattern
=======  ====  ================
ID        16   0111100001000111
QR         1   0
Opcode     4   1111
AA         1   0
TC         1   1
RD         1   1
RA         1   1
Z          3   011
RCODE      4   1111
QDCOUNT   16   0101010010010110
ANCOUNT   16   1110000100101110
NSCOUNT   16   0001101111110001
ARCOUNT   16   0110100110100100

J

<lang J>require'strings'

soul=: -. {. normalize=: [:soul' ',dltb;._2

mask=: 0: _1} '+' = {. partition=: '|' = mask #"1 soul labels=: ;@(([: <@}: <@dltb;._1)"1~ '|'&=)@soul names=: ;:^:(0 = L.)

unpacker=:1 :0

 p=. , partition normalize m
 p #.;.1 (8#2) ,@:#: ]

)

packer=:1 :0

 w=. -#;.1 ,partition normalize m
 _8 (#.\ ;) w ({. #:)&.> ]

)

getter=:1 :0

 nm=. labels normalize m
 (nm i. names@[) { ]

)

setter=:1 :0

 q=. '
 n=. q,~q,;:inv labels normalize m
 1 :('(',n,' i.&names m)}')

)

starter=:1 :0

 0"0 labels normalize m

)</lang>

Sample definition (note the deliberate introduction of extraneous whitespace in locations the task requires us to ignore it.

<lang j>sample=: 0 :0

 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

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

)

unpack=: sample unpacker pack=: sample packer get=: sample getter set=: sample setter start=: sample starter</lang>

Example data for sample definition:

<lang J>

  4095 13 5 6144 4096 'ID Opcode RCODE ARCOUNT QDCOUNT' set start

4095 0 13 0 0 0 0 0 5 4096 0 0 6144

  pack 4095 13 5 6144 4096 'ID Opcode RCODE ARCOUNT QDCOUNT' set start

15 255 104 5 16 0 0 0 0 0 24 0

  unpack 0 10 56 128 0 0 0 0 0 0 0 255

10 0 7 0 0 0 1 0 0 0 0 0 255

  'Opcode' get unpack 0 10 56 128 0 0 0 0 0 0 0 255

7</lang>

In other words:

unpack converts an octet sequence to the corresponding numeric sequence
pack converts a numeric sequence to the corresponding octet sequence
get extracts named elements from the numeric sequence
set updates named elements in the numeric sequence
start represents the default "all zeros" sequence which may be used to derive other sequences

Note that this implementation assumes that the ascii diagram represents the native word width on a single line, and assumes well formed data.

Java

Separate methods to validate, display, decode ASCII art, and decode hex value.

<lang Java> import java.math.BigInteger; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map;

public class AsciiArtDiagramConverter {

   private static final String TEST = "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\r\n" +
           "|                      ID                       |\r\n" +
           "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\r\n" +
           "|QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |\r\n" +
           "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\r\n" +
           "|                    QDCOUNT                    |\r\n" +
           "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\r\n" +
           "|                    ANCOUNT                    |\r\n" +
           "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\r\n" +
           "|                    NSCOUNT                    |\r\n" +
           "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\r\n" +
           "|                    ARCOUNT                    |\r\n" +
           "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+";
   public static void main(String[] args) {
       validate(TEST);
       display(TEST);
       Map<String,List<Integer>> asciiMap = decode(TEST);
       displayMap(asciiMap);
       displayCode(asciiMap, "78477bbf5496e12e1bf169a4");
   }
   private static void displayCode(Map<String,List<Integer>> asciiMap, String hex) {
       System.out.printf("%nTest string in hex:%n%s%n%n", hex);
       String bin = new BigInteger(hex,16).toString(2);
       //  Zero pad in front as needed
       int length = 0;
       for ( String code : asciiMap.keySet() ) {
           List<Integer> pos = asciiMap.get(code);
           length += pos.get(1) - pos.get(0) + 1;
       }
       while ( length > bin.length() ) {
           bin = "0" + bin;
       }
       System.out.printf("Test string in binary:%n%s%n%n", bin);
       System.out.printf("Name      Size  Bit Pattern%n");
       System.out.printf("-------- -----  -----------%n");
       for ( String code : asciiMap.keySet() ) {
           List<Integer> pos = asciiMap.get(code);
           int start = pos.get(0);
           int end   = pos.get(1);
           System.out.printf("%-8s    %2d  %s%n", code, end-start+1, bin.substring(start, end+1));
       }
   }


   private static void display(String ascii) {
       System.out.printf("%nDiagram:%n%n");
       for ( String s : TEST.split("\\r\\n") ) {
           System.out.println(s);
       }
   }
   private static void displayMap(Map<String,List<Integer>> asciiMap) {
       System.out.printf("%nDecode:%n%n");


       System.out.printf("Name      Size  Start    End%n");
       System.out.printf("-------- -----  -----  -----%n");
       for ( String code : asciiMap.keySet() ) {
           List<Integer> pos = asciiMap.get(code);
           System.out.printf("%-8s    %2d     %2d     %2d%n", code, pos.get(1)-pos.get(0)+1, pos.get(0), pos.get(1));
       }
   }
   private static Map<String,List<Integer>> decode(String ascii) {
       Map<String,List<Integer>> map = new LinkedHashMap<>();
       String[] split = TEST.split("\\r\\n");
       int size = split[0].indexOf("+", 1) - split[0].indexOf("+");
       int length = split[0].length() - 1;
       for ( int i = 1 ; i < split.length ; i += 2 ) {
           int barIndex = 1;
           String test = split[i];
           int next;
           while ( barIndex < length && (next = test.indexOf("|", barIndex)) > 0 ) {
               //  List is start and end of code.
               List<Integer> startEnd = new ArrayList<>();
               startEnd.add((barIndex/size) + (i/2)*(length/size));
               startEnd.add(((next-1)/size) + (i/2)*(length/size));
               String code = test.substring(barIndex, next).replace(" ", "");
               map.put(code, startEnd);
               //  Next bar
               barIndex = next + 1;
           }
       }
       return map;
   }
   private static void validate(String ascii) {
       String[] split = TEST.split("\\r\\n");
       if ( split.length % 2 != 1 ) {
           throw new RuntimeException("ERROR 1:  Invalid number of input lines.  Line count = " + split.length);
       }
       int size = 0;
       for ( int i = 0 ; i < split.length ; i++ ) {
           String test = split[i];
           if ( i % 2 == 0 ) {
               //  Start with +, an equal number of -, end with +
               if ( ! test.matches("^\\+([-]+\\+)+$") ) {
                   throw new RuntimeException("ERROR 2:  Improper line format.  Line = " + test);
               }
               if ( size == 0 ) {
                   int firstPlus = test.indexOf("+");
                   int secondPlus = test.indexOf("+", 1);
                   size = secondPlus - firstPlus;
               }
               if ( ((test.length()-1) % size) != 0 ) {
                   throw new RuntimeException("ERROR 3:  Improper line format.  Line = " + test);
               }
               //  Equally spaced splits of +, -
               for ( int j = 0 ; j < test.length()-1 ; j += size ) {
                   if ( test.charAt(j) != '+' ) {
                       throw new RuntimeException("ERROR 4:  Improper line format.  Line = " + test);
                   }
                   for ( int k = j+1 ; k < j + size ; k++ ) {
                       if ( test.charAt(k) != '-' ) {
                           throw new RuntimeException("ERROR 5:  Improper line format.  Line = " + test);
                       }
                   }
               }
           }
           else {
               //  Vertical bar, followed by optional spaces, followed by name, followed by optional spaces, followed by vdrtical bar
               if ( ! test.matches("^\\|(\\s*[A-Za-z]+\\s*\\|)+$") ) {
                   throw new RuntimeException("ERROR 6:  Improper line format.  Line = " + test);
               }
               for ( int j = 0 ; j < test.length()-1 ; j += size ) {
                   for ( int k = j+1 ; k < j + size ; k++ ) {
                       //  Vertical bar only at boundaries
                       if ( test.charAt(k) == '|' ) {
                           throw new RuntimeException("ERROR 7:  Improper line format.  Line = " + test);
                       }
                   }
               }
           }
       }
   }

} </lang>

Output:

Diagram:

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

Decode:

Name      Size  Start    End
-------- -----  -----  -----
ID          16      0     15
QR           1     16     16
Opcode       4     17     20
AA           1     21     21
TC           1     22     22
RD           1     23     23
RA           1     24     24
Z            3     25     27
RCODE        4     28     31
QDCOUNT     16     32     47
ANCOUNT     16     48     63
NSCOUNT     16     64     79
ARCOUNT     16     80     95

Test string in hex:
78477bbf5496e12e1bf169a4

Test string in binary:
011110000100011101111011101111110101010010010110111000010010111000011011111100010110100110100100

Name      Size  Bit Pattern
-------- -----  -----------
ID          16  0111100001000111
QR           1  0
Opcode       4  1111
AA           1  0
TC           1  1
RD           1  1
RA           1  1
Z            3  011
RCODE        4  1111
QDCOUNT     16  0101010010010110
ANCOUNT     16  1110000100101110
NSCOUNT     16  0001101111110001
ARCOUNT     16  0110100110100100

JavaScript

<lang javascript>// ------------------------------------------------------------[ Boilerplate ]-- const trimWhitespace = s => s.trim(); const isNotEmpty = s => s !== ; const stringLength = s => s.length; const hexToBin4 = s => parseInt(s, 16).toString(2).padStart(4, '0'); const concatHexToBin = (binStr, hexStr) => binStr.concat(, hexToBin4(hexStr)); const alignRight = n => s => `${s}`.padStart(n, ' '); const alignLeft = n => s => `${s}`.padEnd(n, ' '); const repeatChar = c => n => c.padStart(n, c); const joinWith = c => arr => arr.join(c); const joinNl = joinWith('\n'); const joinSp = joinWith(' ');

const printDiagramInfo = map => {

 const pName = alignLeft(8);
 const p5 = alignRight(5);
 const line = repeatChar('-');
 const res = [];
 res.push(joinSp([pName('Name'), p5('Size'), p5('Start'), p5('End')]));
 res.push(joinSp([line(8), line(5), line(5), line(5)]));
 [...map.values()].forEach(({label, bitLength, start, end}) => {
   res.push(joinSp([pName(label), p5(bitLength), p5(start), p5(end)]));
 })
 return res;

}

// -------------------------------------------------------------------[ Main ]-- const parseDiagram = dia => {

 const arr = dia.split('\n').map(trimWhitespace).filter(isNotEmpty);
 const hLine = arr[0];
 const bitTokens = hLine.split('+').map(trimWhitespace).filter(isNotEmpty);
 const bitWidth = bitTokens.length;
 const bitTokenWidth = bitTokens[0].length;
 const fields = arr.filter(e => e !== hLine);
 const allFields = fields.reduce((p, c) => [...p, ...c.split('|')], [])
     .filter(isNotEmpty);
 const lookupMap = Array(bitWidth).fill().reduce((p, c, i) => {
   const v = i + 1;
   const stringWidth = (v * bitTokenWidth) + (v - 1);
   p.set(stringWidth, v);
   return p;
 }, new Map())
 const fieldMetaMap = allFields.reduce((p, e, i) => {
   const bitLength = lookupMap.get(e.length);
   const label = trimWhitespace(e);
   const start = i ? p.get(i - 1).end + 1 : 0;
   const end = start - 1 + bitLength;
   p.set(i, {label, bitLength, start, end})
   return p;
 }, new Map());
 const pName = alignLeft(8);
 const pBit = alignRight(5);
 const pPat = alignRight(18);
 const line = repeatChar('-');
 const nl = '\n';
 return hexStr => {
   const binString = [...hexStr].reduce(concatHexToBin, );
   const res = printDiagramInfo(fieldMetaMap);
   res.unshift(joinNl(['Diagram:', ...arr, nl]));
   res.push(joinNl([nl, 'Test string in hex:', hexStr]));
   res.push(joinNl(['Test string in binary:', binString, nl]));
   res.push(joinSp([pName('Name'), pBit('Size'), pPat('Pattern')]));
   res.push(joinSp([line(8), line(5), line(18)]));
   [...fieldMetaMap.values()].forEach(({label, bitLength, start, end}) => {
     res.push(joinSp(
         [pName(label), pBit(bitLength),
           pPat(binString.substr(start, bitLength))]))
   })
   return joinNl(res);
 }

}

// --------------------------------------------------------------[ Run tests ]--

const dia = ` +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ID | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |QR| Opcode |AA|TC|RD|RA| Z | RCODE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QDCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ANCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | NSCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ARCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ `;

const parser = parseDiagram(dia);

parser('78477bbf5496e12e1bf169a4');</lang>

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


Name      Size Start   End
-------- ----- ----- -----
ID          16     0    15
QR           1    16    16
Opcode       4    17    20
AA           1    21    21
TC           1    22    22
RD           1    23    23
RA           1    24    24
Z            3    25    27
RCODE        4    28    31
QDCOUNT     16    32    47
ANCOUNT     16    48    63
NSCOUNT     16    64    79
ARCOUNT     16    80    95


Test string in hex:
78477bbf5496e12e1bf169a4
Test string in binary:
011110000100011101111011101111110101010010010110111000010010111000011011111100010110100110100100


Name      Size            Pattern
-------- ----- ------------------
ID          16   0111100001000111
QR           1                  0
Opcode       4               1111
AA           1                  0
TC           1                  1
RD           1                  1
RA           1                  1
Z            3                011
RCODE        4               1111
QDCOUNT     16   0101010010010110
ANCOUNT     16   1110000100101110
NSCOUNT     16   0001101111110001
ARCOUNT     16   0110100110100100

Julia

The validator() function can be customized. The one used only checks length. <lang julia>diagram = """

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

testhexdata = "78477bbf5496e12e1bf169a4"

struct BitField

   name::String
   bits::Int
   fieldstart::Int
   fieldend::Int

end

function diagramtostruct(txt)

   bitfields = Vector{BitField}()
   lines = map(strip, split(txt, "\n"))
   for row in 1:2:length(lines)-1
       nbits = sum(x -> x == '+', lines[row]) - 1
       fieldpos = findall(x -> x == '|', lines[row + 1])
       bitaccum = div(row, 2) * nbits
       for (i, field) in enumerate(fieldpos[1:end-1])
           endfield = fieldpos[i + 1]
           bitsize = div(endfield - field, 3)
           bitlabel = strip(lines[row + 1][field+1:endfield-1])
           bitstart = div(field - 1, 3) + bitaccum
           bitend = bitstart + bitsize - 1
           push!(bitfields, BitField(bitlabel, bitsize, bitstart, bitend))
       end
   end
   bitfields

end

binbyte(c) = string(parse(UInt8, c, base=16), base=2, pad=8) hextobinary(s) = reduce(*, map(binbyte, map(x -> s[x:x+1], 1:2:length(s)-1))) validator(binstring, fields) = length(binstring) == sum(x -> x.bits, fields)

function bitreader(bitfields, hexdata)

   println("\nEvaluation of hex data $hexdata as bitfields:")
   println("Name     Size          Bits\n-------  ----  ----------------")
   b = hextobinary(hexdata)
   @assert(validator(b, bitfields))
   for bf in bitfields
       pat = b[bf.fieldstart+1:bf.fieldend+1]
       println(rpad(bf.name, 9), rpad(bf.bits, 6), lpad(pat, 16))
   end

end

const decoded = diagramtostruct(diagram)

println("Diagram as bit fields:\nName Bits Start End\n------ ---- ----- ---") for bf in decoded

   println(rpad(bf.name, 8), rpad(bf.bits, 6), rpad(bf.fieldstart, 6), lpad(bf.fieldend, 4))

end

bitreader(decoded, testhexdata)

</lang>

Output:
Diagram as bit fields:
Name    Bits  Start  End
------  ----  -----  ---
ID      16    0       15
QR      1     16      16
Opcode  4     17      20
AA      1     21      21
TC      1     22      22
RD      1     23      23
RA      1     24      24
Z       3     25      27
RCODE   4     28      31
QDCOUNT 16    32      47
ANCOUNT 16    48      63
NSCOUNT 16    64      79
ARCOUNT 16    80      95

Evaluation of hex data 78477bbf5496e12e1bf169a4 as bitfields:
Name     Size          Bits
-------  ----  ----------------
ID       16    0111100001000111
QR       1                    0
Opcode   4                 1111
AA       1                    0
TC       1                    1
RD       1                    1
RA       1                    1
Z        3                  011
RCODE    4                 1111
QDCOUNT  16    0101010010010110
ANCOUNT  16    1110000100101110
NSCOUNT  16    0001101111110001
ARCOUNT  16    0110100110100100

Lua

Provided mainly to illustrate the string-parsing aspect, not necessarily the bit-structure aspect... <lang lua>local function validate(diagram)

 local lines = {}
 for s in diagram:gmatch("[^\r\n]+") do
   s = s:match("^%s*(.-)%s*$")
   if s~="" then lines[#lines+1]=s end
 end
 -- "a little of validation".."for brevity"
 assert(#lines>0, "FAIL:  no non-empty lines")
 assert(#lines%2==1, "FAIL:  even number of lines")
 return lines

end

local function parse(lines)

 local schema, offset = {}, 0
 for i = 2,#lines,2 do
   for part in lines[i]:gmatch("\|([^\|]+)") do
     schema[#schema+1] = { name=part:match("^%s*(.-)%s*$"), numbits=(#part+1)/3, offset=offset }
     offset = offset + (#part+1)/3
   end
 end
 return schema

end

local diagram = [[

 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

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

| ANCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | NSCOUNT |

+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ARCOUNT |

 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+  

]] -- extra whitespace added for testing

local schema = parse(validate(diagram)) print("NAME NUMBITS OFFSET") print("-------- -------- --------") for i = 1,#schema do

 local item = schema[i]
 print(string.format("%-8s  %8d  %8d", item.name, item.numbits, item.offset))

end</lang>

Output:
NAME       NUMBITS    OFFSET
--------  --------  --------
ID              16         0
QR               1        16
Opcode           4        17
AA               1        21
TC               1        22
RD               1        23
RA               1        24
Z                3        25
RCODE            4        28
QDCOUNT         16        32
ANCOUNT         16        48
NSCOUNT         16        64
ARCOUNT         16        80

Nim

The program is composed of three parts.

In the first part, a parser takes a diagram (string composed of lines) and produce a raw structure which describes the fields. The parser may be executed at compile time (if the input string is a constant) or at runtime. It performs a full validation of the structure, detecting syntax errors and structure errors.

The second part of the module produces a type declaration from the raw structure. This operation is done at compile time by calling a macro which constructs an abstract syntax tree to represent the structure. Nim describes bit fields in a way not very different of the C way by specifying the field length in a pragma, for instance “{.bitsize: 5.}.

After the type declaration has been produced, it is possible to declare variables and work with them as if the structure had been explicitly described using Nim syntax rather than using ASCII-art diagram.

The third part of the module produces a description at runtime. As Nim is statically typed, it is not possible to produce a new type and declare variables of this type. So, starting from the raw structure built by the parser, we produce a structure containing a storage area and a table of field positions and we provide two procedures to get and set a field value. These procedures uses masks and logical operations to read and write the values.

So, the behavior when using structures built at compile time and structure built at runtime is different. For a variable “s”, reading the content of field “f” is done using the normal syntax “s.f” in the first case and using “s.get("f")” in the second case.

Of course, the second mechanism may be used for all cases. The first mechanism is mainly interesting to show how it is possible, using Nim powerful macro system, to create a type ex nihilo.

<lang Nim>import macros import strutils import tables

const Diagram = """

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

type

 # Exceptions.
 SyntaxError = object of CatchableError
 StructureError = object of CatchableError
 FieldError = object of CatchableError
  1. ---------------------------------------------------------------------------------------------------

proc raiseException(exc: typedesc; linenum: int; message: string) =

 ## Raise an exception with a message including the line number.
 raise newException(exc, "Line $1: $2".format(linenum, message))


  1. Parser.

type

 # Allowed tokens.
 Token = enum tkSpace, tkPlus, tkMinus2, tkVLine, tkIdent, tkEnd, tkError
 # Lexer description.
 Lexer = object
   line: string      # Line to parse.
   linenum: int      # Line number.
   pos: int          # Current position.
   token: Token      # Current token.
   value: string     # Associated value (for tkIdent).
 # Description of a field.
 Field = tuple[name: string, length: int]
 # Description of fields in a row.
 RowFields = seq[Field]
 # Structure to describe fields.
 RawStructure = object
   size: int                # Size of a row in bits.
   rows: seq[RowFields]     # List of rows.
  1. ---------------------------------------------------------------------------------------------------

proc getNextToken(lexer: var Lexer) =

 ## Search next toke/* Nim */ n and update lexer state accordingly.
 doAssert(lexer.pos < lexer.line.high)
 inc lexer.pos
 let ch = lexer.line[lexer.pos]
 case ch
 of ' ':
   lexer.token = tkSpace
 of '+':
   lexer.token = tkPlus
 of '-':
   inc lexer.pos
   lexer.token = if lexer.line[lexer.pos] == '-': tkMinus2 else: tkError
 of '|':
   lexer.token = tkVLine
 of Letters:
   # Beginning of an identifier.
   lexer.value = $ch
   inc lexer.pos
   while lexer.pos < lexer.line.high and lexer.line[lexer.pos] in IdentChars:
     lexer.value.add(lexer.line[lexer.pos])
     inc lexer.pos
   dec lexer.pos
   lexer.token = tkIdent
 of '\n':
   # End of the line.
   lexer.token = tkEnd
 else:
   lexer.token = tkError
  1. ---------------------------------------------------------------------------------------------------

proc initLexer(line: string; linenum: int): Lexer =

 ## Initialize a lexer.
 result.line = line & '\n'   # Add a sentinel.
 result.linenum = linenum
 result.pos = -1
  1. ---------------------------------------------------------------------------------------------------

proc parseSepLine(lexer: var Lexer): int =

 ## Parse a separation line. Return the corresponding size in bits.
 lexer.getNextToken()
 while lexer.token != tkEnd:
   if lexer.token != tkMinus2:
     raiseException(SyntaxError, lexer.linenum, "“--” expected")
   lexer.getNextToken()
   if lexer.token != tkPlus:
     raiseException(SyntaxError, lexer.linenum, "“+” expected")
   inc result
   lexer.getNextToken()
  1. ---------------------------------------------------------------------------------------------------

proc parseFieldLine(lexer: var Lexer; structure: var RawStructure) =

 ## Parse a field description line and update the structure accordingly.
 var rowFields: RowFields    # List of fields.
 var prevpos = 0             # Previous position.
 var size = 0                # Total size.
 lexer.getNextToken()
 while lexer.token != tkEnd:
   # Parse a field.
   while lexer.token == tkSpace:
     lexer.getNextToken()
   if lexer.token != tkIdent:
     raiseException(SyntaxError, lexer.linenum, "Identifier expected")
   let id = lexer.value
   lexer.getNextToken()
   while lexer.token == tkSpace:
     lexer.getNextToken()
   if lexer.token != tkVLine:
     raiseException(SyntaxError, lexer.linenum, "“|” expected")
   if lexer.pos mod 3 != 0:
     raiseException(SyntaxError, lexer.linenum, "wrong position for “|”")
   # Build a field description.
   let fieldLength = (lexer.pos - prevpos) div 3
   rowFields.add((id, fieldLength))
   inc size, fieldLength
   prevpos = lexer.pos
   lexer.getNextToken()
 # Add the row fields description to the structure.
 if size != structure.size:
   raiseException(StructureError, lexer.linenum, "total size of fields doesn’t fit")
 structure.rows.add(rowFields)
  1. ---------------------------------------------------------------------------------------------------

proc parseLine(line: string; linenum: Positive; structure: var RawStructure) =

 ## Parse a line.
 # Eliminate spaces at beginning and at end, and ignore empty lines.
 let line = line.strip()
 if line.len == 0: return
 var lexer = initLexer(line, linenum)
 lexer.getNextToken()
 if lexer.token == tkPlus:
   # Separator line.
   let size = parseSepLine(lexer)
   if size notin {8, 16, 32, 64}:
     raiseException(StructureError, linenum,
                    "wrong structure size; got $1, expected 8, 16, 32 or 64".format(size))
   if structure.size > 0:
     # Not the first separation line.
     if size != structure.size:
       raiseException(StructureError, linenum,
                      "inconsistent size; got $1, expected $2".format(size, structure.size))
   else:
     structure.size = size
 elif lexer.token == tkVLine:
   # Fields description line.
   parseFieldLine(lexer, structure)
 else:
   raiseException(SyntaxError, linenum, "“+” or “|” expected")
  1. ---------------------------------------------------------------------------------------------------

proc parse(diagram: string): RawStructure =

 ## Parse a diagram describing a structure.
 var linenum = 0
 for line in diagram.splitLines():
   inc linenum
   parseLine(line, linenum, result)


  1. Generation of a structure type at compile time.
  2. Access to fields is done directly without getter or setter.

macro createStructType*(diagram, typeName: static string): untyped =

 ## Create a type from "diagram" whose name is given by "typeName".
 # C types to use as units.
 const Ctypes = {8: "cuchar", 16: "cushort", 32: "cuint", 64: "culong"}.toTable
 # Check that the type name is a valid identifier.
 if not typeName.validIdentifier():
   error("Invalid type name: " & typeName)
   return
 # Parse the diagram.
 var struct: RawStructure
 try:
   struct = parse(diagram)
 except SyntaxError, StructureError:
   error(getCurrentExceptionMsg())
   return
 # Build the beginning: "type <typeName> = object".
 # For now, the list of fields is empty.
 let ctype = Ctypes[struct.size]
 let recList = newNimNode(nnkRecList)
 result = nnkStmtList.newTree(
            nnkTypeSection.newTree(
              nnkTypeDef.newTree(
                ident(typeName),
                newEmptyNode(),
                nnkObjectTy.newTree(
                  newEmptyNode(),
                  newEmptyNode(),
                  recList))))
 # Add the fields.
 for row in struct.rows:
   if row.len == 1:
     # Single field in a unit. No need to specify the length.
     recList.add(newIdentDefs(
                   nnkPostfix.newTree(
                     ident("*"),
                     ident(row[0].name)),
                   ident(ctype)))
   else:
     # Several fields. Use pragma "bitsize".
     for field in row:
       let fieldNode = nnkPragmaExpr.newTree(
                         nnkPostfix.newTree(
                           ident("*"),
                           ident(field.name)),
                         nnkPragma.newTree(
                           nnkExprColonExpr.newTree(
                             ident("bitsize"),
                             newIntLitNode(field.length))))
       recList.add(newIdentDefs(fieldNode, ident(ctype)))


  1. Generation of a structure at runtime.
  2. Access to fields must be done via a specific getter or setter.

type

 # Unit to use.
 Unit = enum unit8, unit16, unit32, unit64
 # Position of a field in a unit.
 FieldPosition = tuple[row, start, length: int]
 # Description of the structure.
 Structure* = object
   names: seq[string]                        # Original names.
   positions: Table[string, FieldPosition]   # Mapping name (in lower case) => Position.
   # Storage.
   case unit: Unit:
   of unit8:
     s8: seq[uint8]
   of unit16:
     s16: seq[uint16]
   of unit32:
     s32: seq[uint32]
   of unit64:
     s64: seq[uint64]
  1. ---------------------------------------------------------------------------------------------------

proc createStructVar*(diagram: string): Structure =

 ## Create a variable for the structure described by "diagram".
 var rawStruct = parse(diagram)
 # Allocate the storage for the structure.
 case rawStruct.size
 of 8:
   result = Structure(unit: unit8)
   result.s8.setLen(rawStruct.rows.len)
 of 16:
   result = Structure(unit: unit16)
   result.s16.setLen(rawStruct.rows.len)
 of 32:
   result = Structure(unit: unit32)
   result.s32.setLen(rawStruct.rows.len)
 of 64:
   result = Structure(unit: unit64)
   result.s64.setLen(rawStruct.rows.len)
 else:
   raise newException(ValueError, "Internal error")
 # Build the table mapping field names to positions.
 for i, row in rawStruct.rows:
   var offset = 0
   for field in row:
     result.names.add(field.name)
     result.positions[field.name.toLower] = (row: i, start: offset, length: field.length)
     inc offset, field.length
  1. ---------------------------------------------------------------------------------------------------

proc get*(struct: Structure; fieldName: string): uint64 =

 ## Return the value of field "fieldName" in a structure.
 ## The value type is "uint64" and should be converted to another type if needed.
 # Get the position of the field.
 var row, start, length: int
 try:
   (row, start, length) = struct.positions[fieldName.toLower]
 except KeyError:
   raise newException(FieldError, "Invalid field: " & fieldName)
 let mask = 1 shl length - 1
 let endpos = start + length - 1
 case struct.unit
 of unit8:
   result = (struct.s8[row] and mask.uint8 shl (7 - endpos)) shr (7 - endpos)
 of unit16:
   result = (struct.s16[row] and mask.uint16 shl (15 - endpos)) shr (15 - endpos)
 of unit32:
   result = (struct.s32[row] and mask.uint32 shl (31 - endpos)) shr (31 - endpos)
 of unit64:
   result = (struct.s64[row] and mask.uint64 shl (63 - endpos)) shr (63 - endpos)
  1. ---------------------------------------------------------------------------------------------------

proc set*(struct: var Structure; fieldName: string; value: SomeInteger) =

 ## Set the value of the field "fieldName" in a structure.
 # Get the position of the field.
 var row, start, length: int
 try:
   (row, start, length) = struct.positions[fieldName.toLower]
 except KeyError:
   raise newException(FieldError, "Invalid field: " & fieldName)
 let mask = 1 shl length - 1
 let endpos = start + length - 1
 let value = value and mask   # Make sure that the value fits in the field.
 case struct.unit
 of unit8:
   struct.s8[row] =
     struct.s8[row] and not (mask.uint8 shl (7 - endpos)) or (value.uint8 shl (7 - endpos))
 of unit16:
   struct.s16[row] =
     struct.s16[row] and not (mask.uint16 shl (15 - endpos)) or (value.uint16 shl (15 - endpos))
 of unit32:
   struct.s32[row] =
     struct.s32[row] and not (mask.uint32 shl (31 - endpos)) or (value.uint32 shl (31 - endpos))
 of unit64:
   struct.s64[row] =
     struct.s64[row] and not (mask.uint64 shl (63 - endpos)) or (value.uint64 shl (63 - endpos))
  1. ---------------------------------------------------------------------------------------------------

iterator fields*(struct: Structure): uint64 =

 ## Yield the values of the successive fields of a structure
 for name in struct.positions.keys:
   yield struct.get(name)
  1. ---------------------------------------------------------------------------------------------------

iterator fieldPairs*(struct: Structure): tuple[key: string, val: uint64] =

 ## Yield names and values of the successive fields of a structure
 for name in struct.names:
   yield (name, struct.get(name))
  1. ---------------------------------------------------------------------------------------------------

proc `$`*(struct: Structure): string =

 ## Produce a representation of a structure.
 result = "("
 for name in struct.names:
   result.addSep(", ", 1)
   result.add(name & ": " & $struct.get(name))
 result.add(')')
  1. ---------------------------------------------------------------------------------------------------

proc toHex(struct: Structure): string =

 ## Return the hexadecimal representation of a structure.
 case struct.unit
 of unit8:
   for row in struct.s8:
     result.add(row.toHex(2))
 of unit16:
   for row in struct.s16:
     result.add(row.toHex(4))
 of unit32:
   for row in struct.s32:
     result.add(row.tohex(8))
 of unit64:
   for row in struct.s64:
     result.add(row.toHex(16))
  1. ———————————————————————————————————————————————————————————————————————————————————————————————————

when isMainModule:

 # Creation of a structure at compile time.
 # ----------------------------------------
 # Create the type "Header" to represent the structure described by "Diagram".
 createStructType(Diagram, "Header")
 # Declare a variable of type Header and initialize its fields.
 var header1 = Header(ID: 30791, QR: 0, Opcode: 15, AA: 0, TC: 1, RD: 1, RA: 1, Z: 3, RCODE:15,
                      QDCOUNT: 21654, ANCOUNT: 57646, NSCOUNT: 7153, ARCOUNT: 27044)
 echo "Header from a structure defined at compile time:"
 echo header1
 echo ""
 # Of course, it is possible to loop on the fields.
 echo "Same fields/values retrieved with an iterator:"
 for name, value in header1.fieldPairs:
   echo name, ": ", value
 echo ""
 # Hexadecimal representation.
 var h = ""
 var p = cast[ptr UncheckedArray[typeof(header1.ID)]](addr(header1))
 for i in 0..<(sizeof(header1) div sizeof(typeof(header1.ID))):
   h.add(p[i].toHex(4))
 echo "Hexadecimal representation: ", h
 echo ""


 # Creation of a structure at runtime.
 # -----------------------------------
 # Declare a variable initalized with the structure created at runtime.
 var header2 = createStructVar(Diagram)
 header2.set("ID", 30791)
 header2.set("QR", 0)
 header2.set("Opcode", 15)
 header2.set("AA", 0)
 header2.set("TC", 1)
 header2.set("RD", 1)
 header2.set("RA", 1)
 header2.set("Z", 3)
 header2.set("RCODE", 15)
 header2.set("QDCOUNT", 21654)
 header2.set("ANCOUNT", 57646)
 header2.set("NSCOUNT", 7153)
 header2.set("ARCOUNT", 27044)
 echo "Header from a structure defined at runtime: "
 echo header2
 echo ""
 # List fields using the "fieldPairs" iterator.
 echo "Same fields/values retrieved with an iterator:"
 for name, val in header2.fieldPairs():
   echo name, ": ", val
 echo ""
 # Hexadecimal representation.
 echo "Hexadecimal representation: ", header2.toHex()</lang>
Output:
Header from a structure defined at compile time:
(ID: 30791, QR: 0, Opcode: 15, AA: 0, TC: 1, RD: 1, RA: 1, Z: 3, RCODE: 15, QDCOUNT: 21654, ANCOUNT: 57646, NSCOUNT: 7153, ARCOUNT: 27044)

Same fields/values retrieved with an iterator:
ID: 30791
QR: 0
Opcode: 15
AA: 0
TC: 1
RD: 1
RA: 1
Z: 3
RCODE: 15
QDCOUNT: 21654
ANCOUNT: 57646
NSCOUNT: 7153
ARCOUNT: 27044

Hexadecimal representation: 7847F7DE5496E12E1BF169A4

Header from a structure defined at runtime: 
(ID: 30791, QR: 0, Opcode: 15, AA: 0, TC: 1, RD: 1, RA: 1, Z: 3, RCODE: 15, QDCOUNT: 21654, ANCOUNT: 57646, NSCOUNT: 7153, ARCOUNT: 27044)

Same fields/values retrieved with an iterator:
ID: 30791
QR: 0
Opcode: 15
AA: 0
TC: 1
RD: 1
RA: 1
Z: 3
RCODE: 15
QDCOUNT: 21654
ANCOUNT: 57646
NSCOUNT: 7153
ARCOUNT: 27044

Hexadecimal representation: 78477BBF5496E12E1BF169A4

Ol

<lang scheme> (import (owl parse))

(define format " +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ID | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |QR| Opcode |AA|TC|RD|RA| Z | RCODE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QDCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ANCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | NSCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ARCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+") (define (subA x) (- x #\A -1))

(define whitespace (byte-if (lambda (x) (has? '(#\newline #\return #\+ #\- #\|) x)))) (define maybe-whitespaces (greedy* whitespace))

(define format-parser

  (let-parse* (
        (key (greedy* (let-parse* (
              (* maybe-whitespaces)
              (sp1 (greedy* (imm #\space)))
              (name (greedy+ (byte-if (lambda (x) (or (<= #\A x #\Z) (<= #\a x #\z))))))
              (sp2 (greedy* (imm #\space))))
           (cons
              (/ (+ (length sp1) (length name) (length sp2) 1) 3)
              (list->string name)))))
        (* maybe-whitespaces))
     key))

(define table (parse format-parser (str-iter format) #f #f #f)) (unless table

  (print "Invalid template. Exiting.")
  (halt 1))

(print "table structure:" format) (print "is encoded as " table " ")

(define (decode table data)

  (let loop ((table (reverse table)) (bits data) (out #null))
     (if (null? table)
        out
     else
        (define name (cdar table))
        (define width (caar table))
        (define val (band bits (- (<< 1 width) 1)))
        (loop (cdr table) (>> bits width) (cons (cons name val) out)))))

(define binary-input-data #b011110000100011101111011101111110101010010010110111000010010111000011011111100010110100110100100) (print "decoding input data " binary-input-data)

(define datastructure (decode table binary-input-data))

(print) (print "parsed datastructure:") (for-each (lambda (x)

     (print (car x) " == " (cdr x) " (" (number->string (cdr x) 2) ")"))
  datastructure)

</lang>

Output:
$ ol ascii_art_diagram_converter.scm 
table structure:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                      ID                       |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR|   Opcode  |AA|TC|RD|RA|    Z   |   RCODE   |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    QDCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    ANCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    NSCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    ARCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
is encoded as ((16 . ID) (1 . QR) (4 . Opcode) (1 . AA) (1 . TC) (1 . RD) (1 . RA) (3 . Z) (4 . RCODE) (16 . QDCOUNT) (16 . ANCOUNT) (16 . NSCOUNT) (16 . ARCOUNT)) 
decoding input data 37224619292254864697109604772

parsed datastructure:
ID == 30791 (111100001000111)
QR == 0 (0)
Opcode == 15 (1111)
AA == 0 (0)
TC == 1 (1)
RD == 1 (1)
RA == 1 (1)
Z == 3 (11)
RCODE == 15 (1111)
QDCOUNT == 21654 (101010010010110)
ANCOUNT == 57646 (1110000100101110)
NSCOUNT == 7153 (1101111110001)
ARCOUNT == 27044 (110100110100100)

Perl

<lang perl>#!/usr/bin/perl

use strict; use warnings;

$_ = <<END;

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

END

my $template; my @names; while( /\| *(\w+) */g )

 {
 printf "%10s is %2d bits\n", $1, my $length = length($&) / 3;
 push @names, $1;
 $template .= "A$length ";
 }

my $input = '78477bbf5496e12e1bf169a4'; # as hex

my %datastructure; @datastructure{ @names } = unpack $template, unpack 'B*', pack 'H*', $input;

print "\ntemplate = $template\n\n"; use Data::Dump 'dd'; dd 'datastructure', \%datastructure;</lang>

Output:
        ID is 16 bits
        QR is  1 bits
    Opcode is  4 bits
        AA is  1 bits
        TC is  1 bits
        RD is  1 bits
        RA is  1 bits
         Z is  3 bits
     RCODE is  4 bits
   QDCOUNT is 16 bits
   ANCOUNT is 16 bits
   NSCOUNT is 16 bits
   ARCOUNT is 16 bits

template = A16 A1 A4 A1 A1 A1 A1 A3 A4 A16 A16 A16 A16

(
  "datastructure",
  {
    AA => 0,
    ANCOUNT => 1110000100101110,
    ARCOUNT => "0110100110100100",
    ID => "0111100001000111",
    NSCOUNT => "0001101111110001",
    Opcode => 1111,
    QDCOUNT => "0101010010010110",
    QR => 0,
    RA => 1,
    RCODE => 1111,
    RD => 1,
    TC => 1,
    Z => "011",
  },
)

Phix

Should work on any width, but didn't actually test, or verify width is 8/16/32/64. <lang Phix>function interpret(sequence lines)

   if remainder(length(lines),2)!=1 then
       crash("missing header/footer?")
   end if
   string l1 = lines[1]
   integer w = length(l1)
   integer bits = (w-1)/3  -- sug: check this is 8/16/32/64
   if l1!=join(repeat("+",bits+1),"--") then
       crash("malformed header?")
   end if
   sequence res = {}
   integer offset = 0
   for i=1 to length(lines) do
       string li = lines[i]
       if remainder(i,2) then
           if li!=l1 then
               crash("missing separator (line %d)?",{i})
           end if
       else
           if li[1]!='|' or li[w]!='|' then
               crash("missing separator on line %d",{i})
           end if
           integer k = 1
           while true do
               integer l = find('|',li,k+1)
               string desc = trim(li[k+1..l-1])
               {k,l} = {l,(l-k)/3}
               res = append(res,{desc,l,offset})
               offset += l
               if k=w then exit end if
           end while
       end if
   end for
   res = append(res,{"total",0,offset})
   return res

end function

procedure unpack(string data, sequence res)

   if length(data)*8!=res[$][3] then
       crash("wrong length")
   end if
   string bin = ""
   for i=1 to length(data) do
       bin &= sprintf("%08b",data[i])
   end for
   printf(1,"\n\nTest bit string:\n%s\n\nUnpacked:\n",{bin})
   for i=1 to length(res)-1 do
       {string name, integer bits, integer offset} = res[i]
       printf(1,"%7s, %02d bits: %s\n",{name,bits,bin[offset+1..offset+bits]})
   end for

end procedure

function trimskip(string diagram) -- -- split's ",no_empty:=true)" is not quite enough here. -- Note that if copy/paste slips in any tab characters, -- it will most likely trigger a length mismatch error. --

   sequence lines = split(diagram,'\n')
   integer prevlli = 0
   for i=length(lines) to 1 by -1 do
       string li = trim(lines[i])
       integer lli = length(li)
       if lli then
           if prevlli=0 then
               prevlli = lli
           elsif lli!=prevlli then
               crash("mismatching lengths")
           end if
           lines[i] = li
       else
           lines[i..i] = {}
       end if
   end for
   return lines

end function

constant diagram = """

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

"""

sequence lines = trimskip(diagram) sequence res = interpret(lines) printf(1,"--Name-- Size Offset\n") for i=1 to length(res) do

   printf(1," %-7s   %2d  %5d\n",res[i])

end for

unpack(x"78477bbf5496e12e1bf169a4",res)</lang>

Output:
--Name--  Size  Offset
 ID        16      0
 QR         1     16
 Opcode     4     17
 AA         1     21
 TC         1     22
 RD         1     23
 RA         1     24
 Z          3     25
 RCODE      4     28
 QDCOUNT   16     32
 ANCOUNT   16     48
 NSCOUNT   16     64
 ARCOUNT   16     80
 total      0     96


Test bit string:
011110000100011101111011101111110101010010010110111000010010111000011011111100010110100110100100

Unpacked:
     ID, 16 bits: 0111100001000111
     QR, 01 bits: 0
 Opcode, 04 bits: 1111
     AA, 01 bits: 0
     TC, 01 bits: 1
     RD, 01 bits: 1
     RA, 01 bits: 1
      Z, 03 bits: 011
  RCODE, 04 bits: 1111
QDCOUNT, 16 bits: 0101010010010110
ANCOUNT, 16 bits: 1110000100101110
NSCOUNT, 16 bits: 0001101111110001
ARCOUNT, 16 bits: 0110100110100100

Python

<lang python> """ http://rosettacode.org/wiki/ASCII_art_diagram_converter

Python example based off Go example:

http://rosettacode.org/wiki/ASCII_art_diagram_converter#Go

"""

def validate(diagram):

   # trim empty lines
   
   rawlines = diagram.splitlines()
   lines = []
   for line in rawlines:
       if line != :
           lines.append(line)
           
   # validate non-empty lines
           
   if len(lines) == 0:
       print('diagram has no non-empty lines!')
       return None
       
   width = len(lines[0])
   cols = (width - 1) // 3
   
   if cols not in [8, 16, 32, 64]: 
       print('number of columns should be 8, 16, 32 or 64')
       return None
       
   if len(lines)%2 == 0:
       print('number of non-empty lines should be odd')
       return None
   
   if lines[0] != (('+--' * cols)+'+'):
           print('incorrect header line')
           return None
   for i in range(len(lines)):
       line=lines[i]
       if i == 0:
           continue
       elif i%2 == 0:
           if line != lines[0]:
               print('incorrect separator line')
               return None
       elif len(line) != width:
           print('inconsistent line widths')
           return None
       elif line[0] != '|' or line[width-1] != '|':
           print("non-separator lines must begin and end with '|'")    
           return None
   
   return lines

"""

results is list of lists like:

[[name, bits, start, end],...

"""

def decode(lines):

   print("Name     Bits  Start  End")
   print("=======  ====  =====  ===")
   
   startbit = 0
   
   results = []
   
   for line in lines:
       infield=False
       for c in line:
           if not infield and c == '|':
               infield = True
               spaces = 0
               name = 
           elif infield:
               if c == ' ':
                   spaces += 1
               elif c != '|':
                   name += c
               else:
                   bits = (spaces + len(name) + 1) // 3
                   endbit = startbit + bits - 1
                   print('{0:7}    {1:2d}     {2:2d}   {3:2d}'.format(name, bits, startbit, endbit))
                   reslist = [name, bits, startbit, endbit]
                   results.append(reslist)
                   spaces = 0
                   name = 
                   startbit += bits
                   
   return results
                       

def unpack(results, hex):

   print("\nTest string in hex:")
   print(hex)
   print("\nTest string in binary:")
   bin = f'{int(hex, 16):0>{4*len(hex)}b}'
   print(bin)
   print("\nUnpacked:\n")
   print("Name     Size  Bit pattern")
   print("=======  ====  ================")
   for r in results:
       name = r[0]
       size = r[1]
       startbit = r[2]
       endbit = r[3]
       bitpattern = bin[startbit:endbit+1]
       print('{0:7}    {1:2d}  {2:16}'.format(name, size, bitpattern))


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

"""

lines = validate(diagram)

if lines == None:

   print("No lines returned")

else:

   print(" ")
   print("Diagram after trimming whitespace and removal of blank lines:")
   print(" ")
   for line in lines:
       print(line)
       
   print(" ")
   print("Decoded:")
   print(" ")
   results = decode(lines)    
   
   # test string
   
   hex = "78477bbf5496e12e1bf169a4" 
   
   unpack(results, hex)

</lang>

Output:
 
Diagram after trimming whitespace and removal of blank lines:
 
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                      ID                       |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    QDCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    ANCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    NSCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    ARCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 
Decoded:
 
Name     Bits  Start  End
=======  ====  =====  ===
ID         16      0   15
QR          1     16   16
Opcode      4     17   20
AA          1     21   21
TC          1     22   22
RD          1     23   23
RA          1     24   24
Z           3     25   27
RCODE       4     28   31
QDCOUNT    16     32   47
ANCOUNT    16     48   63
NSCOUNT    16     64   79
ARCOUNT    16     80   95

Test string in hex:
78477bbf5496e12e1bf169a4

Test string in binary:
011110000100011101111011101111110101010010010110111000010010111000011011111100010110100110100100

Unpacked:

Name     Size  Bit pattern
=======  ====  ================
ID         16  0111100001000111
QR          1  0               
Opcode      4  1111            
AA          1  0               
TC          1  1               
RD          1  1               
RA          1  1               
Z           3  011             
RCODE       4  1111            
QDCOUNT    16  0101010010010110
ANCOUNT    16  1110000100101110
NSCOUNT    16  0001101111110001
ARCOUNT    16  0110100110100100

Racket

Three files:

  • ascii-art-parser.rkt: provides the function ascii-art->struct, which converts ASCII art from a string (or input port) to a list of word-number, bit range and id
  • ascii-art-reader.rkt: uses this to provide a sytntax define-ascii-art-structure which defines a structure using the art work
  • test-ascci-art-reader.rkt: gives it all a rigourousish going over

Note that if you want to extend the word width too 32-bits (or more) add multiples of eight bit blocks horizontally (i.e. --+--+--+--+--+--+--+--+). IMO Having the diagrams 16-bits wide reflects the choice of a 16-bit word as the natural word size of the interface. If it were 32 or 64, the blocks would have to be wider.

ascii-art-parser.rkt Note that this is in the racket/base language so it doesn't overburden the modules that import it, especially since they're at the suntax phase. <lang racket>#lang racket/base (require (only-in racket/list drop-right)

        (only-in racket/string string-trim))

(provide ascii-art->struct)

reads ascii art from a string or input-port
returns
list of (word-number highest-bit lowest-bit name-symbol)
bits per word

(define (ascii-art->struct art)

 (define art-inport
   (cond
     [(string? art) (open-input-string art)]
     [(input-port? art) art]
     [else (raise-argument-error 'ascii-art->struct
                                 "(or/c string? input-port?)"
                                 art)]))
 (define lines
   (for/list ((l (in-port (lambda (p)
                            (define pk (peek-char p))
                            (case pk ((#\+ #\|) (read-line p))
                              (else eof)))
                          art-inport)))
     l))
 (when (null? lines)
   (error 'ascii-art->struct "no lines"))
 (define bit-re #px"[|+]([^|+]*)")
 (define cell-re #px"[|]([^|]*)")
 (define bit-boundaries (regexp-match-positions* bit-re (car lines)))
 (define bits/word (sub1 (length bit-boundaries)))
 (unless (zero? (modulo bits/word 8))
   (error 'ascii-art->struct "diagram is not a multiple of 8 bits wide"))
 (define-values (pos->bit-start# pos->bit-end#)
   (for/fold ((s# (hash)) (e# (hash)))
             ((box (in-range bits/word))
              (boundary (in-list bit-boundaries)))
     (define bit (- bits/word box 1))
     (values (hash-set s# (car boundary) bit)
             (hash-set e# (cdr boundary) bit))))
 (define fields
   (apply append
          (for/list ((line-number (in-naturals))
                     (line (in-list lines))
                     #:when (odd? line-number))
            (define word (quotient line-number 2))
            (define cell-positions (regexp-match-positions* cell-re line))
            (define cell-contents (regexp-match* cell-re line))
            (for/list ((cp (in-list (drop-right cell-positions 1)))
                       (cnt (in-list cell-contents)))
              (define cell-start-bit (hash-ref pos->bit-start# (car cp)))
              (define cell-end-bit (hash-ref pos->bit-end# (cdr cp)))
              (list word cell-start-bit cell-end-bit (string->symbol (string-trim (substring cnt 1))))))))
 (values fields bits/word))</lang>

ascii-art-reader.rkt <lang racket>#lang racket (require (for-syntax "ascii-art-parser.rkt")) (require (for-syntax racket/syntax))

(provide (all-defined-out))

(define-syntax (define-ascii-art-structure stx)

 (syntax-case stx ()
   [(_ id art)
    (let*-values (((all-fields bits/word) (ascii-art->struct (syntax-e #'art))))
      (with-syntax
          ((bytes->id (format-id stx "bytes->~a" #'id))
           (id->bytes (format-id stx "~a->bytes" #'id))
           (word-size (add1 (car (for/last ((f all-fields)) f))))
           (fld-ids (map cadddr all-fields))
           (fld-setters
            (cons
             #'id
             (for/list ((fld (in-list all-fields)))
               (let* ((bytes/word (quotient bits/word 8))
                      (start-byte (let ((word-no (car fld))) (* word-no bytes/word))))
                 `(bitwise-bit-field (integer-bytes->integer bs
                                                             #f
                                                             (system-big-endian?)
                                                             ,start-byte
                                                             ,(+ start-byte bytes/word))
                                     ,(caddr fld)
                                     ,(add1 (cadr fld)))))))
           (set-fields-bits
            (list*
             'begin
             (for/list ((fld (in-list all-fields)))
               (define val (cadddr fld))
               (define start-bit (cadr fld))
               (define end-bit (caddr fld))
               (define start-byte (let ((word-no (car fld))) (* word-no (quotient bits/word 8))))
               (define fld-bit-width (- start-bit end-bit -1))
               (define aligned?/width (and (= end-bit 0)
                                           (= (modulo start-bit 8) 7)
                                           (quotient fld-bit-width 8)))
               (case aligned?/width
                 [(2 4)
                  `(integer->integer-bytes ,val
                                           ,aligned?/width
                                           #f
                                           (system-big-endian?)
                                           rv
                                           ,start-byte)]
                 [else
                  (define the-byte (+ start-byte (quotient end-bit 8)))
                  `(bytes-set! rv
                               ,the-byte
                               (bitwise-ior (arithmetic-shift (bitwise-bit-field ,val 0 ,fld-bit-width)
                                                              ,(modulo end-bit 8))
                                            (bytes-ref rv ,the-byte)))])))))
        #`(begin
            (struct id fld-ids #:mutable)
            (define (bytes->id bs)
              fld-setters)
            (define (id->bytes art-in)
              (match-define (id #,@#'fld-ids) art-in)
              (define rv (make-bytes (* word-size #,(quotient bits/word 8))))
              set-fields-bits
              rv))))]))</lang>

test-ascii-art-reader.rkt <lang racket>#lang racket (require "ascii-art-reader.rkt") (require "ascii-art-parser.rkt") (require tests/eli-tester)

(define rfc-1035-header-art

 #<<EOS

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

 )

(define-values (rslt rslt-b/w) (ascii-art->struct rfc-1035-header-art))

(test

rslt-b/w => 16
rslt =>
'((0 15  0 ID)
  (1 15 15 QR)
  (1 14 11 Opcode)
  (1 10 10 AA)
  (1  9  9 TC)
  (1  8  8 RD)
  (1  7  7 RA)
  (1  6  4 Z)
  (1  3  0 RCODE)
  (2 15  0 QDCOUNT)
  (3 15  0 ANCOUNT)
  (4 15  0 NSCOUNT)
  (5 15  0 ARCOUNT)))

(define-ascii-art-structure rfc-1035-header #<<EOS +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ID | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |QR| Opcode |AA|TC|RD|RA| Z | RCODE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QDCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ANCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | NSCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ARCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ EOS

                    )

(define h-bytes

 (bytes-append
  (integer->integer-bytes #x1234 2 #f)
  (integer->integer-bytes #x5678 2 #f)
  (integer->integer-bytes #x9abc 2 #f)
  (integer->integer-bytes #xdef0 2 #f)
  (integer->integer-bytes #xfedc 2 #f)
  (integer->integer-bytes #xba98 2 #f)))

(define h-bytes~

 (bytes-append
  (integer->integer-bytes #x1234 2 #f (not (system-big-endian?)))
  (integer->integer-bytes #x5678 2 #f (not (system-big-endian?)))
  (integer->integer-bytes #x9abc 2 #f (not (system-big-endian?)))
  (integer->integer-bytes #xdef0 2 #f (not (system-big-endian?)))
  (integer->integer-bytes #xfedc 2 #f (not (system-big-endian?)))
  (integer->integer-bytes #xba98 2 #f (not (system-big-endian?)))))

(define h (bytes->rfc-1035-header h-bytes)) (define bytes-h (rfc-1035-header->bytes h))

(define h~ (bytes->rfc-1035-header h-bytes~)) (define bytes-h~ (rfc-1035-header->bytes h~))

(test

(rfc-1035-header-ID h) => #x1234
(rfc-1035-header-ARCOUNT h) => #xBA98
(rfc-1035-header-RCODE h) => 8
(rfc-1035-header-ID h~) => #x3412
(rfc-1035-header-ARCOUNT h~) => #x98BA
(rfc-1035-header-RCODE h~) => 6
h-bytes => bytes-h
h-bytes~ => bytes-h~)

(set-rfc-1035-header-RA! h 0)

(set-rfc-1035-header-Z! h 7) (test

(rfc-1035-header-Z (bytes->rfc-1035-header (rfc-1035-header->bytes h))) => 7
(rfc-1035-header-RA (bytes->rfc-1035-header (rfc-1035-header->bytes h))) => 0)

(set-rfc-1035-header-Z! h 15) ;; naughty -- might splat RA (test

(rfc-1035-header-Z (bytes->rfc-1035-header (rfc-1035-header->bytes h))) => 7
(rfc-1035-header-RA (bytes->rfc-1035-header (rfc-1035-header->bytes h))) => 0)</lang>
Output:

Nothing much to see... all tests pass

Raku

(formerly Perl 6)

Works with: Rakudo version 2018.05

<lang perl6>grammar RFC1025 {

   rule  TOP {  <.line-separator> [<line> <.line-separator>]+ }
   rule  line-separator { <.ws> '+--'+ '+' }
   token line  { <.ws> '|' +%% <field>  }
   token field  { \s* <label> \s* }
   token label { \w+[\s+\w+]* }

}

sub bits ($item) { ($item.chars + 1) div 3 }

sub deconstruct ($bits, %struct) {

   map { $bits.substr(.<from>, .<bits>) }, @(%struct<fields>);

}

sub interpret ($header) {

   my $datagram = RFC1025.parse($header);
   my %struct;
   for $datagram.<line> -> $line {
       FIRST %struct<line-width> = $line.&bits;
       state $from = 0;
       %struct<fields>.push: %(:bits(.&bits), :ID(.<label>.Str), :from($from.clone), :to(($from+=.&bits)-1))
         for $line<field>;
   }
   %struct

}

use experimental :pack;

my $diagram = q:to/END/;

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

END

my %structure = interpret($diagram);

say 'Line width: ', %structure<line-width>, ' bits'; printf("Name: %7s, bit count: %2d, bit %2d to bit %2d\n", .<ID>, .<bits>, .<from>, .<to>) for @(%structure<fields>); say "\nGenerate a random 12 byte \"header\""; say my $buf = Buf.new((^0xFF .roll) xx 12); say "\nShow it converted to a bit string"; say my $bitstr = $buf.unpack('C*')».fmt("%08b").join; say "\nAnd unpack it"; printf("%7s, %02d bits: %s\n", %structure<fields>[$_]<ID>, %structure<fields>[$_]<bits>,

 deconstruct($bitstr, %structure)[$_]) for ^@(%structure<fields>);</lang>
Output:
Line width: 16 bits
Name:      ID, bit count: 16, bit  0 to bit 15
Name:      QR, bit count:  1, bit 16 to bit 16
Name:  Opcode, bit count:  4, bit 17 to bit 20
Name:      AA, bit count:  1, bit 21 to bit 21
Name:      TC, bit count:  1, bit 22 to bit 22
Name:      RD, bit count:  1, bit 23 to bit 23
Name:      RA, bit count:  1, bit 24 to bit 24
Name:       Z, bit count:  3, bit 25 to bit 27
Name:   RCODE, bit count:  4, bit 28 to bit 31
Name: QDCOUNT, bit count: 16, bit 32 to bit 47
Name: ANCOUNT, bit count: 16, bit 48 to bit 63
Name: NSCOUNT, bit count: 16, bit 64 to bit 79
Name: ARCOUNT, bit count: 16, bit 80 to bit 95

Generate a random 12 byte "header"
Buf:0x<78 47 7b bf 54 96 e1 2e 1b f1 69 a4>

Show it converted to a bit string
011110000100011101111011101111110101010010010110111000010010111000011011111100010110100110100100

And unpack it
     ID, 16 bits: 0111100001000111
     QR, 01 bits: 0
 Opcode, 04 bits: 1111
     AA, 01 bits: 0
     TC, 01 bits: 1
     RD, 01 bits: 1
     RA, 01 bits: 1
      Z, 03 bits: 011
  RCODE, 04 bits: 1111
QDCOUNT, 16 bits: 0101010010010110
ANCOUNT, 16 bits: 1110000100101110
NSCOUNT, 16 bits: 0001101111110001
ARCOUNT, 16 bits: 0110100110100100

REXX

Some code was added to the REXX program to validate the input file. <lang rexx>/*REXX program interprets an ASCII art diagram for names and their bit length(s).*/ numeric digits 100 /*be able to handle large numbers. */ er= '***error*** illegal input txt' /*a literal used for error messages. */ parse arg iFID test . /*obtain optional input─FID & test─data*/ if iFID== | iFID=="," then iFID= 'ASCIIART.TXT' /*use the default iFID.*/ if test== | test=="," then test= 'cafe8050800000808080000a' /* " " " data.*/ w= 0; wb= 0;  !.= 0; $= /*W (max width name), bits, names. */ @.= 0; @.0= 1 /*!.α is structure bit position. */

                                                /* [↓]  read the input text file (iFID)*/
   do j=1  while lines(iFID)\==0;     q= linein(iFID);             say  '■■■■■text►'q
   q= strip(q);          if q==  then iterate /*strip leading and trailing blanks.   */
   _L= left(q, 1);       _R= right(q, 1)        /*get extreme left and right characters*/
                                                /* [↓]  is this record an "in-between"?*/
   if _L=='+'  then do;  if verify(q, '+-')\==0  then say er    "(invalid grid):"     q
                         iterate                /*skip this record, it's a single "+". */
                    end
   if _L\=='|'  |  _R\=="|"   then do;   say er  '(boundary): '   q;   iterate
                                   end
      do  until q=='|';  parse var  q    '|'  x  "|"  -1  q   /*parse record for names.*/
      n= strip(x);       w= max(w, length(n) );   if n==  then leave     /*is N null?*/
      if words(n)\==1         then do;  say er '(invalid name): '  n;     iterate j
                                   end          /* [↑]  add more name validations.     */
      $$= $;     nn= n;  upper $$ n             /*$$ and N  could be a mixed─case name.*/
      if wordpos(nn, $$)\==0  then do;  say er '(dup name):'       n;     iterate j
                                   end
      $= $ n                                    /*add the   N   (name)  to the $ list. */
      #= words($);     !.#= (length(x) + 1) % 3 /*assign the number of bits for  N.    */
      wb= max(wb, !.#)                          /*max # of bits; # names prev. to this.*/
      prev= # - 1;     @.#= @.prev + !.prev     /*number of names previous to this name*/
      end   /*until*/
   end      /*j*/

say if j==1 then do; say er ' (file not found): ' iFID; exit 12

             end
    do k=1  for words($)
    say right( word($, k), w)right(!.k, 4)        "bits,  bit position:"right(@.k, 5)
    end   /*k*/

say /* [↓] Any (hex) data to test? */ L= length(test); if L==0 then exit /*stick a fork in it, we're all done. */ bits= x2b(test) /*convert test data to a bit string. */ wm= length( x2d( b2x( copies(1, wb) ) ) ) + 1 /*used for displaying max width numbers*/ say 'test (hex)=' test " length=" L 'hexadecimal digits.' say

      do r=1  by 8+8  to L*4;   _1= substr(bits, r, 8, 0);    _2= substr(bits, r+8, 8, 0)
      say 'test (bit)='    _1   _2   "   hex="    lower( b2x(_1) )     lower( b2x(_2) )
      end   /*r*/

say

      do m=1  for words($)                      /*show some hexadecimal strings──►term.*/
      _= lower( b2x( substr( bits, @.m, !.m) )) /*show the hex string in lowercase.    */
      say right( word($, m), w+2)     '  decimal='right( x2d(_), wm)      "      hex="  _
      end   /*m*/

exit 0 /*stick a fork in it, we're all done. */ /*──────────────────────────────────────────────────────────────────────────────────────*/ lower: l= 'abcdefghijklmnopqrstuvwxyz'; u=l; upper u; return translate( arg(1), l, u)</lang>

output   when using the default input:
■■■■■text►
■■■■■text►
■■■■■text►     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
■■■■■text►     |                      ID                       |
■■■■■text►     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
■■■■■text►     |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
■■■■■text►     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
■■■■■text►     |                    QDCOUNT                    |
■■■■■text►     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
■■■■■text►     |                    ANCOUNT                    |
■■■■■text►     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
■■■■■text►     |                    NSCOUNT                    |
■■■■■text►     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
■■■■■text►     |                    ARCOUNT                    |
■■■■■text►     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
■■■■■text►
■■■■■text►

     ID  16 bits,  bit position:    1
     QR   1 bits,  bit position:   17
 OPCODE   4 bits,  bit position:   18
     AA   1 bits,  bit position:   22
     TC   1 bits,  bit position:   23
     RD   1 bits,  bit position:   24
     RA   1 bits,  bit position:   25
      Z   3 bits,  bit position:   26
  RCODE   4 bits,  bit position:   29
QDCOUNT  16 bits,  bit position:   33
ANCOUNT  16 bits,  bit position:   49
NSCOUNT  16 bits,  bit position:   65
ARCOUNT  16 bits,  bit position:   81

test (hex)= cafe8050800000808080000a     length= 24 hexadecimal digits.

test (bit)= 11001010 11111110    hex= CA FE
test (bit)= 10000000 01010000    hex= 80 50
test (bit)= 10000000 00000000    hex= 80 00
test (bit)= 00000000 10000000    hex= 00 80
test (bit)= 10000000 10000000    hex= 80 80
test (bit)= 00000000 00001010    hex= 00 0A

       ID   decimal= 51966       hex= cafe
       QR   decimal=     1       hex= 1
   OPCODE   decimal=     0       hex= 0
       AA   decimal=     0       hex= 0
       TC   decimal=     0       hex= 0
       RD   decimal=     0       hex= 0
       RA   decimal=     0       hex= 0
        Z   decimal=     5       hex= 5
    RCODE   decimal=     0       hex= 0
  QDCOUNT   decimal= 32768       hex= 8000
  ANCOUNT   decimal=   128       hex= 0080
  NSCOUNT   decimal= 32896       hex= 8080
  ARCOUNT   decimal=    10       hex= 000a

Rust

The solution implements a few additional features:

  • The input is thoroughly validated.
  • The width is intentionally not restricted, therefore diagrams of any width can be processed.
  • The parser allows omitting border for fields that occupy multiple full lines.
  • Fields can wrap over an end of a line.
  • When extracting field values, truncated input is recognized and unfilled fields are reported.


See the output below the source code.

<lang Rust>use std::{borrow::Cow, io::Write};

pub type Bit = bool;

  1. [derive(Clone, Debug)]

pub struct Field {

   name: String,
   from: usize,
   to: usize,

}

impl Field {

   pub fn new(name: String, from: usize, to: usize) -> Self {
       assert!(from < to);
       Self { name, from, to }
   }
   pub fn name(&self) -> &str {
       &self.name
   }
   pub fn from(&self) -> usize {
       self.from
   }
   pub fn to(&self) -> usize {
       self.to
   }
   pub fn size(&self) -> usize {
       self.to - self.from
   }
   pub fn extract_bits<'a>(
       &self,
       bytes: &'a [u8],
   ) -> Option<impl Iterator<Item = (usize, Bit)> + 'a> {
       if self.to <= bytes.len() * 8 {
           Some((self.from..self.to).map(move |index| {
               let byte = bytes[index / 8];
               let bit_index = 7 - (index % 8);
               let bit_value = (byte >> bit_index) & 1 == 1;
               (index, bit_value)
           }))
       } else {
           None
       }
   }
   fn extend(&mut self, new_to: usize) {
       assert!(self.to <= new_to);
       self.to = new_to;
   }

}

trait Consume: Iterator {

   fn consume(&mut self, value: Self::Item) -> Result<Self::Item, Option<Self::Item>>
   where
       Self::Item: PartialEq,
   {
       match self.next() {
           Some(v) if v == value => Ok(v),
           Some(v) => Err(Some(v)),
           None => Err(None),
       }
   }

}

impl<T: Iterator> Consume for T {}

  1. [derive(Clone, Copy, Debug)]

enum ParserState {

   Uninitialized,
   ExpectBorder,
   ExpectField,
   AllowEmpty,

}

  1. [derive(Clone, Copy, Debug)]

pub enum ParserError {

   ParsingFailed,
   UnexpectedEnd,
   InvalidBorder,
   WrongLineWidth,
   FieldExpected,
   BadField,

}

  1. [derive(Debug)]

pub(crate) struct Parser {

   state: Option<ParserState>,
   width: usize,
   from: usize,
   fields: Vec<Field>,

}

impl Parser {

   #[allow(clippy::new_without_default)]
   pub fn new() -> Self {
       Self {
           state: Some(ParserState::Uninitialized),
           width: 0,
           from: 0,
           fields: Vec::new(),
       }
   }
   pub fn accept(&mut self, line: &str) -> Result<(), ParserError> {
       if let Some(state) = self.state.take() {
           let line = line.trim();
           if !line.is_empty() {
               self.state = Some(match state {
                   ParserState::Uninitialized => self.parse_border(line)?,
                   ParserState::ExpectBorder => self.accept_border(line)?,
                   ParserState::ExpectField => self.parse_fields(line)?,
                   ParserState::AllowEmpty => self.extend_field(line)?,
               });
           }
           Ok(())
       } else {
           Err(ParserError::ParsingFailed)
       }
   }
   pub fn finish(self) -> Result<Vec<Field>, ParserError> {
       match self.state {
           Some(ParserState::ExpectField) => Ok(self.fields),
           _ => Err(ParserError::UnexpectedEnd),
       }
   }
   fn parse_border(&mut self, line: &str) -> Result<ParserState, ParserError> {
       self.width = Parser::border_columns(line).map_err(|_| ParserError::InvalidBorder)?;
       Ok(ParserState::ExpectField)
   }
   fn accept_border(&mut self, line: &str) -> Result<ParserState, ParserError> {
       match Parser::border_columns(line) {
           Ok(width) if width == self.width => Ok(ParserState::ExpectField),
           Ok(_) => Err(ParserError::WrongLineWidth),
           Err(_) => Err(ParserError::InvalidBorder),
       }
   }
   fn parse_fields(&mut self, line: &str) -> Result<ParserState, ParserError> {
       let mut slots = line.split('|');
       // The first split result is the space outside of the schema
       slots.consume("").map_err(|_| ParserError::FieldExpected)?;
       let mut remaining_width = self.width * Parser::COLUMN_WIDTH;
       let mut fields_found = 0;
       loop {
           match slots.next() {
               Some(slot) if slot.is_empty() => {
                   // The only empty slot is the last one
                   if slots.next().is_some() || remaining_width != 0 {
                       return Err(ParserError::BadField);
                   }
                   break;
               }
               Some(slot) => {
                   let slot_width = slot.chars().count() + 1; // Include the slot separator
                   if remaining_width < slot_width || slot_width % Parser::COLUMN_WIDTH != 0 {
                       return Err(ParserError::BadField);
                   }
                   let name = slot.trim();
                   if name.is_empty() {
                       return Err(ParserError::BadField);
                   }
                   // An actual field slot confirmed
                   remaining_width -= slot_width;
                   fields_found += 1;
                   let from = self.from;
                   let to = from + slot_width / Parser::COLUMN_WIDTH;
                   // If the slot belongs to the same field as the last one, just extend it
                   if let Some(f) = self.fields.last_mut().filter(|f| f.name() == name) {
                       f.extend(to);
                   } else {
                       self.fields.push(Field::new(name.to_string(), from, to));
                   }
                   self.from = to;
               }
               _ => return Err(ParserError::BadField),
           }
       }
       Ok(if fields_found == 1 {
           ParserState::AllowEmpty
       } else {
           ParserState::ExpectBorder
       })
   }
   fn extend_field(&mut self, line: &str) -> Result<ParserState, ParserError> {
       let mut slots = line.split('|');
       // The first split result is the space outside of the schema
       if slots.consume("").is_ok() {
           if let Some(slot) = slots.next() {
               if slots.consume("").is_ok() {
                   let slot_width = slot.chars().count() + 1;
                   let remaining_width = self.width * Parser::COLUMN_WIDTH;
                   if slot_width == remaining_width && slot.chars().all(|c| c == ' ') {
                       self.from += self.width;
                       self.fields.last_mut().unwrap().extend(self.from);
                       return Ok(ParserState::AllowEmpty);
                   }
               }
           }
       }
       self.accept_border(line)
   }
   const COLUMN_WIDTH: usize = 3;
   fn border_columns(line: &str) -> Result<usize, Option<char>> {
       let mut chars = line.chars();
       // Read the first cell, which is mandatory
       chars.consume('+')?;
       chars.consume('-')?;
       chars.consume('-')?;
       chars.consume('+')?;
       let mut width = 1;
       loop {
           match chars.consume('-') {
               Err(Some(c)) => return Err(Some(c)),
               Err(None) => return Ok(width),
               Ok(_) => {}
           }
           chars.consume('-')?;
           chars.consume('+')?;
           width += 1;
       }
   }

}

pub struct Fields(pub Vec<Field>);

  1. [derive(Clone, Debug)]

pub struct ParseFieldsError {

   pub line: Option<String>,
   pub kind: ParserError,

}

impl ParseFieldsError {

   fn new(line: Option<String>, kind: ParserError) -> Self {
       Self { line, kind }
   }

}

impl std::str::FromStr for Fields {

   type Err = ParseFieldsError;
   fn from_str(s: &str) -> Result<Self, Self::Err> {
       let mut parser = Parser::new();
       for line in s.lines() {
           parser
               .accept(line)
               .map_err(|e| ParseFieldsError::new(Some(line.to_string()), e))?;
       }
       parser
           .finish()
           .map(Fields)
           .map_err(|e| ParseFieldsError::new(None, e))
   }

}

impl Fields {

   pub fn print_schema(&self, f: &mut dyn Write) -> std::io::Result<()> {
       writeln!(f, "Name          Bits    Start   End")?;
       writeln!(f, "=================================")?;
       for field in self.0.iter() {
           writeln!(
               f,
               "{:<12} {:>5}      {:>3}   {:>3}",
               field.name(),
               field.size(),
               field.from(),
               field.to() - 1 // Range is exclusive, but display it as inclusive
           )?;
       }
       writeln!(f)
   }
   pub fn print_decode(&self, f: &mut dyn Write, bytes: &[u8]) -> std::io::Result<()> {
       writeln!(f, "Input (hexadecimal octets): {:x?}", bytes)?;
       writeln!(f)?;
       writeln!(f, "Name          Size    Bit pattern")?;
       writeln!(f, "=================================")?;
       for field in self.0.iter() {
           writeln!(
               f,
               "{:<12} {:>5}    {}",
               field.name(),
               field.size(),
               field
                   .extract_bits(&bytes)
                   .map(|it| it.fold(String::new(), |mut acc, (index, bit)| {
                       // Instead of simple collect, let's print it rather with
                       // byte boundaries visible as spaces
                       if index % 8 == 0 && !acc.is_empty() {
                           acc.push(' ');
                       }
                       acc.push(if bit { '1' } else { '0' });
                       acc
                   }))
                   .map(Cow::Owned)
                   .unwrap_or_else(|| Cow::Borrowed("N/A"))
           )?;
       }
       writeln!(f)
   }

}

fn normalize(diagram: &str) -> String {

   diagram
       .lines()
       .map(|line| line.trim())
       .filter(|line| !line.is_empty())
       .fold(String::new(), |mut acc, x| {
           if !acc.is_empty() {
               acc.push('\n');
           }
           acc.push_str(x);
           acc
       })

}

fn main() {

   let diagram = r"
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       |                      ID                       |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       |                    QDCOUNT                    |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       |                    ANCOUNT                    |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       |                    NSCOUNT                    |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       |                    ARCOUNT                    |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       |                   OVERSIZED                   |
       |                                               |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       | OVERSIZED |           unused                  |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       ";
   let data = b"\x78\x47\x7b\xbf\x54\x96\xe1\x2e\x1b\xf1\x69\xa4\xab\xcd\xef\xfe\xdc";
   // Normalize and print the input, there is no need and no requirement to
   // generate it from the parsed representation
   let diagram = normalize(diagram);
   println!("{}", diagram);
   println!();
   match diagram.parse::<Fields>() {
       Ok(fields) => {
           let mut stdout = std::io::stdout();
           fields.print_schema(&mut stdout).ok();
           fields.print_decode(&mut stdout, data).ok();
       }
       Err(ParseFieldsError {
           line: Some(line),
           kind: e,
       }) => eprintln!("Invalid input: {:?}\n{}", e, line),
       Err(ParseFieldsError {
           line: _,
           kind: e,
       }) => eprintln!("Could not parse the input: {:?}", e),
   }

}</lang>

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

Name          Bits    Start   End
=================================
ID              16        0    15
QR               1       16    16
Opcode           4       17    20
AA               1       21    21
TC               1       22    22
RD               1       23    23
RA               1       24    24
Z                3       25    27
RCODE            4       28    31
QDCOUNT         16       32    47
ANCOUNT         16       48    63
NSCOUNT         16       64    79
ARCOUNT         16       80    95
OVERSIZED       36       96   131
unused          12      132   143

Input (hexadecimal octets): [78, 47, 7b, bf, 54, 96, e1, 2e, 1b, f1, 69, a4, ab, cd, ef, fe, dc]

Name          Size    Bit pattern
=================================
ID              16    01111000 01000111
QR               1    0
Opcode           4    1111
AA               1    0
TC               1    1
RD               1    1
RA               1    1
Z                3    011
RCODE            4    1111
QDCOUNT         16    01010100 10010110
ANCOUNT         16    11100001 00101110
NSCOUNT         16    00011011 11110001
ARCOUNT         16    01101001 10100100
OVERSIZED       36    10101011 11001101 11101111 11111110 1101
unused          12    N/A

Tcl

This example is in need of improvement:

This example is *incorrect*. It relies on an assumption that sequential bitfields in the same byte can be parsed by the [binary] command, which is not the case. The test "appears" correct because encode and decode suffer the same bug and hence round-trip works. A wrapper which doesn't disturb the below code too much is in progress.

This is a nice task to illustrate a couple of important concepts in Tcl:

 * using dictionaries, taking advantage of their ordering properties
 * the binary command
 * using (semi-)structured text as part of your source code

In this implementation, parse produces a dictionary from names to bit-lengths. encode and decode use these to produce appropriate binary format strings, and then do what they say on the tin. As implemented, this is limited to unsigned numeric values in fields. Supporting unsigned values, strings and enums would require parsing a more complex annotation than only the ASCII art packet structure, but ought not take much more code. <lang Tcl> namespace eval asciipacket {

   proc assert {expr} {    ;# for "static" assertions that throw nice errors
       if {![uplevel 1 [list expr $expr]]} {
           raise {ASSERT ERROR} "{$expr} {[subst -noc $expr]}"
       }
   }
   proc b2h {data} {       ;# format a binary string in hex digits
       binary scan $data H* hex; set hex
   }
   proc parse {s} {
       set result {}                       ;# we will return a dictionary
       set s [string trim $s]              ;# remove whitespace
       set s [split $s \n]                 ;# split into lines
       set s [lmap x $s {string trim $x}]  ;# trim whitespace from each line
       set s [lassign $s border0]          ;# pop off top border row
                                           ;# calculate chars per row, chars per bit
       set rowlen [llength [string map {+ \ } $border0]]
       set bitlen [expr {([string length $border0] - 1) / $rowlen}]
       assert {$bitlen * $rowlen + 1 == [string length $border0]}
       foreach {row border} $s {
           assert {$border eq $border0}
           set row [string trim $row |]
           foreach field [split $row |] {
               set len [string length |$field]
               assert {$len % $bitlen == 0}
               set name [string trim $field]
               set nbits [expr {$len / $bitlen}]
               assert {![dict exists $result $name]}
               dict set result $name $nbits
           }
       }
       return $result
   }
   proc encode {schema values} {
       set bincodes {1 B 8 c 16 S 32 W}    ;# see binary(n)
       set binfmt ""                       ;# format string
       set binargs ""                      ;# positional args
       dict for {name bitlen} $schema {
           set val [dict get $values $name]
           if {[dict exists $bincodes $bitlen]} {
               set fmt "[dict get $bincodes $bitlen]"
           } else {
               set val [format %0${bitlen}b $val]
               set fmt "B${bitlen}"
           }
           append binfmt $fmt
           lappend binargs $val
       }
       binary format $binfmt {*}$binargs
   }


   proc decode {schema data} {
       set result   {}                     ;# we will return a dict
       set bincodes {1 B 8 c 16 S 32 W}    ;# see binary(n)
       set binfmt   ""                     ;# format string
       set binargs  ""                     ;# positional args
       dict for {name bitlen} $schema {
           if {[dict exists $bincodes $bitlen]} {
               set fmt "[dict get $bincodes $bitlen]u" ;# note unsigned
           } else {
               set fmt "B${bitlen}"
           }
           append binfmt $fmt
           lappend binargs $name
       }
       binary scan $data $binfmt {*}$binargs
       foreach _ $binargs {
           dict set result $_ [set $_]
       }
       return $result
   }

} </lang> And here is how to use it with the original test data: <lang Tcl> proc test {} {

   set header {
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       |                      ID                       |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       |                    QDCOUNT                    |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       |                    ANCOUNT                    |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       |                    NSCOUNT                    |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
       |                    ARCOUNT                    |
       +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   }
   set schema [asciipacket::parse $header]
   set values {
       ID 0xcafe
       QR 1
       Opcode 5
       AA 1
       TC 0
       RD 0
       RA 1
       Z  4
       RCODE 8
       QDCOUNT 0x00a5
       ANCOUNT 0x0a50
       NSCOUNT 0xa500
       ARCOUNT 0x500a
   }
   set pkt [asciipacket::encode $schema $values]
   puts "encoded packet (hex): [asciipacket::b2h $pkt]"
   array set decoded [asciipacket::decode $schema $pkt]
   parray decoded

} test </lang>

Output:
encoded packet (hex): cafe805080000080808000a50a50a500500a
decoded(AA)      = 1
decoded(ANCOUNT) = 2640
decoded(ARCOUNT) = 20490
decoded(ID)      = 51966
decoded(NSCOUNT) = 42240
decoded(Opcode)  = 0101
decoded(QDCOUNT) = 165
decoded(QR)      = 1
decoded(RA)      = 1
decoded(RCODE)   = 1000
decoded(RD)      = 0
decoded(TC)      = 0
decoded(Z)       = 100