/* 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"


#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;
}


#include <array>
#include <bitset>
#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);
}


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);
}


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)
}


import Text.ParserCombinators.ReadP
import Control.Monad (guard)

data Field a = Field { fieldName :: String
                     , fieldSize :: Int
                     , fieldValue :: Maybe a}

instance Show a => Show (Field a) where
  show (Field n s a) = case a of
    Nothing -> n ++ "\t" ++ show s
    Just x -> n ++ "\t" ++ show s ++ "\t" ++ show x

newtype Data a = Data { fields :: [Field a] }

instance Show a => Show (Data a) where
  show (Data fs) = "NAME\tSIZE\tVALUE\n" ++ unlines (show <$> fs)

instance Read (Data a) where
  readsPrec _ = readP_to_S parseData

parseData = do n <- parseHeader
               guard (n `elem` [8,16,32,64]) -- check size of the table
               Data . concat <$> many1 (parseRow n)
  where
    parseRow n = do
      fs <- char '|' *> many parseField <* char '\n'
      guard $ sum (fieldSize <$> fs) == n -- check that size of all fields match the row size
      m <- parseHeader
      guard $ m == n -- check that all rows have the same size
      return fs

    parseHeader = do
      char '+'
      n <- length <$> many1 (string "--+")
      char '\n'
      return n

    parseField = do
      s1 <- many (char ' ')
      f <- munch1 $ not . (`elem` " |")
      s2 <- many (char ' ')
      char '|'
      let n = (length (s1 ++ f ++ s2) + 1) `div` 3
      return $ Field f n Nothing

-- emulation of reading a stream of bits
readData :: Data a -> [b] -> Data [b]
readData d = Data . go (fields d)
  where
    go fs [] = (\f -> f {fieldValue = Nothing}) <$> fs
    go (f:fs) s =
      let (x, xs) = splitAt (fieldSize f) s
      in f {fieldValue = Just x} : go fs xs

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

dataSample = concat
  ["011110000100011101111011101111110101010010010110",
   "111000010010111000011011111100010110100110100100"]


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. [email protected][) { ]
)

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

starter=:1 :0
  0"0 labels normalize m
)


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);
                        }
                    }
                }

            }
        }
    }

}


// ------------------------------------------------------------[ 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');


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)


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


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)


"""
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 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))


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>);


/*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)


use std::{borrow::Cow, io::Write};

pub type Bit = bool;

#[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 {}

#[derive(Clone, Copy, Debug)]
enum ParserState {
    Uninitialized,
    ExpectBorder,
    ExpectField,
    AllowEmpty,
}

#[derive(Clone, Copy, Debug)]
pub enum ParserError {
    ParsingFailed,
    UnexpectedEnd,
    InvalidBorder,
    WrongLineWidth,
    FieldExpected,
    BadField,
}

#[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>);

#[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),
    }
}


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


import math.big
import strings

struct Result {
    name  string
    size  int
    start int
    end   int
}

fn (r Result) str() string {
    return "${r.name:-7}   ${r.size:2}    ${r.start:3}   ${r.end:3}"
}

fn validate(diagram string) ?[]string {
    mut lines := []string{}
    for mut line in diagram.split("\n") {
        line = line.trim(" \t")
        if line != "" {
            lines << line
        }
    }
    if lines.len == 0 {
        return error("diagram has no non-empty lines!")
    }
    width := lines[0].len
    cols := (width - 1) / 3
    if cols != 8 && cols != 16 && cols != 32 && cols != 64 {
        return error("number of columns should be 8, 16, 32 or 64")
    }
    if lines.len%2 == 0 {
        return error("number of non-empty lines should be odd")
    }
    if lines[0] != "${strings.repeat_string("+--", cols)}+" {
        return error("incorrect header line")
    }
    for i, line in lines {
        if i == 0 {
            continue
        } else if i%2 == 0 {
            if line != lines[0] {
                return error("incorrect separator line")
            }
        } else if line.len != width {
            return error("inconsistent line widths")
        } else if line[0..1] != '|' || line[width-1..width] != '|' {
            return error("non-separator lines must begin and end with '|'")
        }
    }
    return lines
}

fn decode(lines []string) []Result {
    println("Name     Bits  Start  End")
    println("=======  ====  =====  ===")
    mut start := 0
    width := lines[0].len
    mut results := []Result{}
    for i, l in lines {
        if i%2 == 0 {
            continue
        }
        line := l[1..width-1]
        for n in line.split("|") {
            mut name := n
            size := (name.len + 1) / 3
            name = name.trim_space()
            res := Result{name, size, start, start + size - 1}
            results << res
            println(res)
            start += size
        }
    }
    return results
}

fn unpack(results []Result, hex string) {
    println("\nTest string in hex:")
    println(hex)
    println("\nTest string in binary:")
    bin := hex2bin(hex) or {'ERROR'}
    println(bin)
    println("\nUnpacked:\n")
    println("Name     Size  Bit pattern")
    println("=======  ====  ================")
    for res in results {
        println("${res.name:-7}   ${res.size:2}   ${bin[res.start..res.end+1]}")
    }
}

fn hex2bin(hex string) ?string {
    z := big.integer_from_radix(hex, 16)?
    return "${z.binary_str():096}"
}

fn main() {
    diagram := '
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
         |                      ID                       |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                    QDCOUNT                    |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

        |                    ANCOUNT                    |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                    NSCOUNT                    |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                    ARCOUNT                    |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    '
    lines := validate(diagram)?
    println("Diagram after trimming whitespace and removal of blank lines:\n")
    for line in lines {
        println(line)
    }
    println("\nDecoded:\n")
    results := decode(lines)
    hex := "78477bbf5496e12e1bf169a4" // test string
    unpack(results, hex)
}


import "/dynamic" for Tuple
import "/fmt" for Fmt
import "/big" for BigInt

var Result = Tuple.create("Result", ["name", "size", "start", "end"])

var validate = Fn.new { |diagram|
    var lines = []
    for (line in diagram.split("\n")) {
        line = line.trim(" \t")
        if (line != "") lines.add(line)
    }
    if (lines.count == 0) Fiber.abort("diagram has no non-empty lines!")
    var width = lines[0].count
    var cols = ((width - 1) / 3).floor
    if (cols != 8 && cols != 16 && cols != 32 && cols != 64) {
        Fiber.abort("number of columns should be 8, 16, 32 or 64")
    }
    if (lines.count%2 == 0) {
        Fiber.abort("number of non-empty lines should be odd")
    }
    if (lines[0] != "+--" * cols + "+") Fiber.abort("incorrect header line")
    var i = 0
    for (line in lines) {
        if (i == 0) {
            continue
        } else if (i%2 == 0) {
            if (line != lines[0]) Fiber.abort("incorrect separator line")
        } else if (line.count != width) {
            Fiber.abort("inconsistent line widths")
        } else if (line[0] != "|" || line[width-1] != "|") {
            Fiber.abort("non-separator lines must begin and end with '|'")
        }
        i = i + 1
    }
    return lines
}

var decode = Fn.new { |lines|
    System.print("Name     Bits  Start  End")
    System.print("=======  ====  =====  ===")
    var start = 0
    var width = lines[0].count
    var results = []
    var i = 0
    for (line in lines) {
        if (i%2 == 0) {
            i = i + 1
            continue
        }
        line = line[1...width-1]
        for (name in line.split("|")) {
            var size = ((name.count + 1) / 3).floor
            name = name.trim()
            var r = Result.new(name, size, start, start + size - 1)
            results.add(r)
            Fmt.print("$-7s   $2d    $3d   $3d", r.name, r.size, r.start, r.end)
            start = start + size
        }
        i = i + 1
    }
    return results
}

var hex2bin = Fn.new { |hex|
    var z = BigInt.fromBaseString(hex, 16)
    return Fmt.swrite("$0%(4*hex.count)s", z.toBaseString(2))
}

var unpack = Fn.new { |results, hex|
    System.print("\nTest string in hex:")
    System.print(hex)
    System.print("\nTest string in binary:")
    var bin = hex2bin.call(hex)
    System.print(bin)
    System.print("\nUnpacked:\n")
    System.print("Name     Size  Bit pattern")
    System.print("=======  ====  ================")
    for (res in results) {
        Fmt.print("$-7s   $2d   $s", res.name, res.size, bin[res.start..res.end])
    }
}

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

    |                    ANCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    NSCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ARCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
"""
var lines = validate.call(diagram)
System.print("Diagram after trimming whitespace and removal of blank lines:\n")
for (line in lines) System.print(line)
System.print("\nDecoded:\n")
var results = decode.call(lines)
var hex = "78477bbf5496e12e1bf169a4" // test string
unpack.call(results, hex)


F py_idiv(a, b)
   I a >= 0
      R a I/ b
   R (a - b + 1) I/ b

F BalancedTernary_int2ternary(n)
   I n == 0
      R [Int]()
   V n3 = ((n % 3) + 3) % 3
   I n3 == 0 {R [0] [+] BalancedTernary_int2ternary(py_idiv(n, 3))}
   I n3 == 1 {R [1] [+] BalancedTernary_int2ternary(py_idiv(n, 3))}
   I n3 == 2 {R [-1] [+] BalancedTernary_int2ternary(py_idiv((n + 1), 3))}
   X RuntimeError(‘’)

F BalancedTernary_neg(digs)
   R digs.map(d -> -d)

V table = [(0, -1), (1, -1), (-1, 0), (0, 0), (1, 0), (-1, 1), (0, 1)]

F BalancedTernary_add(a, b, =c = 0)
   I !(!a.empty & !b.empty)
      I c == 0
         R I !a.empty {a} E b
      E
         R BalancedTernary_add([c], I !a.empty {a} E b)
   E
      (V d, c) = :table[3 + (I !a.empty {a[0]} E 0) + (I !b.empty {b[0]} E 0) + c]
      V res = BalancedTernary_add(a[1..], b[1..], c)
      I !res.empty | d != 0
         R [d] [+] res
      E
         R res

V BalancedTernary_str2dig = [‘+’ = 1, ‘-’ = -1, ‘0’ = 0]
V BalancedTernary_dig2str = [1 = ‘+’, -1 = ‘-’, 0 = ‘0’]

T BalancedTernary
   [Int] digits

   F (inp)
      I all(inp.map(d -> d C (0, 1, -1)))
         .digits = copy(inp)
      E
         X ValueError(‘BalancedTernary: Wrong input digits.’)

   F to_int()
      R reversed(.digits).reduce(0, (y, x) -> x + 3 * y)

   F String()
      I .digits.empty
         R ‘0’
      R reversed(.digits).map(d -> BalancedTernary_dig2str[d]).join(‘’)

   F +(b)
      R BalancedTernary(BalancedTernary_add(.digits, b.digits))

   F -(b)
      R (.) + BalancedTernary(BalancedTernary_neg(b.digits))

   F *(b)
      F _mul([Int] a, b) -> [Int]
         I !(!a.empty & !b.empty)
            R [Int]()
         E
            [Int] x
            I   a[0] == -1 {x = BalancedTernary_neg(b)}
            E I a[0] ==  0 {}
            E I a[0] ==  1 {x = b}
            E
               assert(0B)
            V y = [0] [+] @_mul(a[1..], b)
            R BalancedTernary_add(x, y)

      R BalancedTernary(_mul(.digits, b.digits))

F createBalancedTernaryFromStr(inp)
   R BalancedTernary(reversed(inp).map(c -> BalancedTernary_str2dig[c]))
F createBalancedTernaryFromInt(inp)
   R BalancedTernary(BalancedTernary_int2ternary(inp))

V a = createBalancedTernaryFromStr(‘+-0++0+’)
print(‘a: ’a.to_int()‘ ’a)

V b = createBalancedTernaryFromInt(-436)
print(‘b: ’b.to_int()‘ ’b)

V c = createBalancedTernaryFromStr(‘+-++-’)
print(‘c: ’c.to_int()‘ ’c)

V r = a * (b - c)
print(‘a * (b - c): ’r.to_int()‘ ’r)


with Ada.Finalization;

package BT is

   type Balanced_Ternary is private;

   -- conversions
   function To_Balanced_Ternary (Num : Integer) return Balanced_Ternary;
   function To_Balanced_Ternary (Str : String)  return Balanced_Ternary;
   function To_Integer (Num : Balanced_Ternary)  return Integer;
   function To_string (Num : Balanced_Ternary)   return String;

   -- Arithmetics
   -- unary minus
   function "-" (Left : in Balanced_Ternary)
		return Balanced_Ternary;

   -- subtraction
   function "-" (Left, Right : in Balanced_Ternary)
		return Balanced_Ternary;

   -- addition
   function "+" (Left, Right : in Balanced_Ternary)
		return Balanced_Ternary;
   -- multiplication
   function "*" (Left, Right : in Balanced_Ternary)
		return Balanced_Ternary;

private
   -- a balanced ternary number is a unconstrained array of (1,0,-1)
   -- dinamically allocated, least significant trit leftmost
   type Trit is range -1..1;
   type Trit_Array is array (Positive range <>) of Trit;
   pragma Pack(Trit_Array);

   type Trit_Access is access Trit_Array;

   type Balanced_Ternary is new Ada.Finalization.Controlled
     with record
	Ref : Trit_access;
   end record;

   procedure Initialize (Object : in out Balanced_Ternary);
   procedure Adjust     (Object : in out Balanced_Ternary);
   procedure Finalize   (Object : in out Balanced_Ternary);

end BT;


-- Build a balanced ternary, as text, from an integer value or acceptable AppleScript substitute.
on BTFromInteger(n)
    try
        if (n mod 1 is not 0) then error (n as text) & " isn't an integer value"
        set n to n as integer
    on error errMsg
        display alert "BTFromInteger handler: parameter error" message errMsg buttons {"OK"} default button 1 as critical
        error number -128
    end try
    if (n is 0) then return "0"
    -- Positive numbers' digits will be indexed from the beginning of a list containing them, negatives' from the end.
    -- AppleScript indices are 1-based, so get the appropriate 1 or -1 add-in.
    set one to 1
    if (n < 0) then set one to -1
    set digits to {"0", "+", "-", "0"}
    -- Build the text digit by digit.
    set bt to ""
    repeat until (n = 0)
        set nMod3 to n mod 3
        set bt to (item (nMod3 + one) of digits) & bt
        set n to n div 3 + nMod3 div 2 -- + nMod3 div 2 adds in a carry when nMod3 is either 2 or -2.
    end repeat

    return bt
end BTFromInteger

-- Calculate a balanced ternary's integer value from its characters' Unicode numbers.
on integerFromBT(bt)
    checkInput(bt, "integerFromBT")
    set n to 0
    repeat with thisID in (get bt's id)
        set n to n * 3
        -- Unicode 48 is "0", 43 is "+", 45 is "-".
        if (thisID < 48) then set n to n + (44 - thisID)
    end repeat

    return n
end integerFromBT

-- Add two balanced ternaries together.
on addBTs(bt1, bt2)
    checkInput({bt1, bt2}, "addBTs")
    set {longerLength, shorterLength} to {(count bt1), (count bt2)}
    if (longerLength < shorterLength) then set {bt1, bt2, longerLength, shorterLength} to {bt2, bt1, shorterLength, longerLength}

    -- Add the shorter number's digits into a list of the longer number's digits, adding in carries too where appropriate.
    set resultList to bt1's characters
    repeat with i from -1 to -shorterLength by -1
        set {carry, item i of resultList} to sumDigits(item i of resultList, character i of bt2)
        repeat with j from (i - 1) to -longerLength by -1
            if (carry is "0") then exit repeat
            set {carry, item j of resultList} to sumDigits(carry, item j of resultList)
        end repeat
        if (carry is not "0") then set beginning of bt1 to carry
    end repeat
    -- Zap any leading zeros resulting from the cancelling out of the longer number's MSD(s).
    set j to -(count resultList)
    repeat while ((item j of resultList is "0") and (j < -1))
        set item j of resultList to ""
        set j to j + 1
    end repeat

    return join(resultList, "")
end addBTs

-- Multiply one balanced ternary by another.
on multiplyBTs(bt1, bt2)
    checkInput({bt1, bt2}, "multiplyBTs")
    -- Longer and shorter aren't critical here, but it's more efficient to loop through the lesser number of digits.
    set {longerLength, shorterLength} to {(count bt1), (count bt2)}
    if (longerLength < shorterLength) then set {bt1, bt2, shorterLength} to {bt2, bt1, longerLength}

    set multiplicationResult to "0"
    repeat with i from -1 to -shorterLength by -1
        set d2 to character i of bt2
        if (d2 is not "0") then
            set subresult to ""
            -- With each non-"0" subresult, begin with the appropriate number of trailing zeros.
            repeat (-1 - i) times
                set subresult to "0" & subresult
            end repeat
            -- Prepend the longer ternary as is.
            set subresult to bt1 & subresult
            -- Negate the result if the current multiplier from the shorter ternary is "-".
            if (d2 is "-") then set subresult to negateBT(subresult)
            -- Add the subresult to the total so far.
            set multiplicationResult to addBTs(multiplicationResult, subresult)
        end if
    end repeat

    return multiplicationResult
end multiplyBTs

-- Negate a balanced ternary by substituting the characters obtained through subtracting its sign characters' Unicode numbers from 88.
on negateBT(bt)
    checkInput(bt, "negateBT")
    set characterIDs to bt's id
    repeat with thisID in characterIDs
        if (thisID < 48) then set thisID's contents to 88 - thisID
    end repeat

    return string id characterIDs
end negateBT

(* Private handlers. *)

on checkInput(params as list, handlerName)
    try
        repeat with thisParam in params
            if (thisParam's class is text) then
                if (join(split(thisParam, {"-", "+", "0"}), "") > "") then error "\"" & thisParam & "\" isn't a balanced ternary number."
            else
                error "The parameter isn't text."
            end if
        end repeat
    on error errMsg
        display alert handlerName & " handler: parameter error" message errMsg buttons {"OK"} default button 1 as critical
        error number -128
    end try
end checkInput

-- "Add" two balanced ternaries and return both the carry and the result for the column.
on sumDigits(d1, d2)
    if (d1 is "0") then
        return {"0", d2}
    else if (d2 is "0") then
        return {"0", d1}
    else if (d1 = d2) then
        if (d1 = "+") then
            return {"+", "-"}
        else
            return {"-", "+"}
        end if
    else
        return {"0", "0"}
    end if
end sumDigits

on join(lst, delimiter)
    set astid to AppleScript's text item delimiters
    set AppleScript's text item delimiters to delimiter
    set txt to lst as text
    set AppleScript's text item delimiters to astid

    return txt
end join

on split(txt, delimiter)
    set astid to AppleScript's text item delimiters
    set AppleScript's text item delimiters to delimiter
    set lst to txt's text items
    set AppleScript's text item delimiters to astid

    return lst
end split

-- Test code:
set a to "+-0++0+"
set b to BTFromInteger(-436) --> "-++-0--"
set c to "+-++-"

set line1 to "a = " & integerFromBT(a)
set line2 to "b = " & integerFromBT(b)
set line3 to "c = " & integerFromBT(c)
tell multiplyBTs(a, addBTs(b, negateBT(c))) to ¬
    set line4 to "a * (b - c) = " & it & " or " & my integerFromBT(it)

return line1 & linefeed & line2 & linefeed & line3 & linefeed & line4