Task: ASCII art diagram converter


C++: #include <array>
#include <bitset>
#include <iostream>

using namespace std;

struct FieldDetails {string_view Name; int NumBits;};



template <const char *T> consteval auto ParseDiagram()
{  
    
    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");
    
    
    constexpr auto numFields = 
        count(rawArt.begin(), rawArt.end(), '|') -
        count(rawArt.begin(), rawArt.end(), '\n') / 2;    
    array<FieldDetails, numFields> fields;
    
    
    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) 
            {
                
                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);
}


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


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)
    {
        
        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                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+)";
    
    
    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);
}






Python: 

def validate(diagram):

    
    
    rawlines = diagram.splitlines()
    lines = []
    for line in rawlines:
        if line != '':
            lines.append(line)
            
    
            
    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



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 = 

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)    
    
    
    
    hex = "78477bbf5496e12e1bf169a4" 
    
    unpack(results, hex)