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




Java: import java.math.BigInteger;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class AsciiArtDiagramConverter {

    private static final String TEST = "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\r\n" +
            "|                      ID                       |\r\n" +
            "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\r\n" +
            "|QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |\r\n" +
            "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\r\n" +
            "|                    QDCOUNT                    |\r\n" +
            "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\r\n" +
            "|                    ANCOUNT                    |\r\n" +
            "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\r\n" +
            "|                    NSCOUNT                    |\r\n" +
            "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\r\n" +
            "|                    ARCOUNT                    |\r\n" +
            "+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+";

    public static void main(String[] args) {
        validate(TEST);
        display(TEST);
        Map<String,List<Integer>> asciiMap = decode(TEST);
        displayMap(asciiMap);
        displayCode(asciiMap, "78477bbf5496e12e1bf169a4");
    }

    private static void displayCode(Map<String,List<Integer>> asciiMap, String hex) {
        System.out.printf("%nTest string in hex:%n%s%n%n", hex);

        String bin = new BigInteger(hex,16).toString(2);

        
        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<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);
                
                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 ) {
                
                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);
                }
                
                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 {
                
                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++ ) {
                        
                        if ( test.charAt(k) == '|' ) {
                            throw new RuntimeException("ERROR 7:  Improper line format.  Line = " + test);
                        }
                    }
                }

            }
        }
    }

}