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



Go: package main

import (
    "fmt"
    "log"
    "math/big"
    "strings"
)

type result struct {
    name  string
    size  int
    start int
    end   int
}

func (r result) String() string {
    return fmt.Sprintf("%-7s   %2d    %3d   %3d", r.name, r.size, r.start, r.end)
}

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

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

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

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

func main() {
    const diagram = `
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
         |                      ID                       |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                    QDCOUNT                    |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

        |                    ANCOUNT                    |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                    NSCOUNT                    |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
        |                    ARCOUNT                    |
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    `
    lines := validate(diagram)
    fmt.Println("Diagram after trimming whitespace and removal of blank lines:\n")
    for _, line := range lines {
        fmt.Println(line)
    }
    fmt.Println("\nDecoded:\n")
    results := decode(lines)
    hex := "78477bbf5496e12e1bf169a4" 
    unpack(results, hex)
}