A base 40 message encoder

This is a compact representation for strings that can consist of uppercase letters, digits, space, comma, period, and trailing NUL. There's a Python implementation of the encoder and a 6800 implementation of the decoder.

b40.asm

download raw

        cpu 6800
        include "motorola.inc"          ; Macros for things like fcc,db, etc.
CRXMIT  EQU $8000 + 3 * 4
RESET   EQU $8000 + 3 * 0

        ORG 0
b40_j:  RMB 1
b40_acc:  RMB 3
b40_tmp:  RMB 3
b40_cb: jmp CRXMIT

        ORG $0100
entry:
        ldx #encoded
        jsr b40print
        jmp reset
encoded: fcb 137, 98, 164, 124, 235, 2, 162, 186, 32, 248, 46, 30, 20, 217, 0

        ORG $0200
b40scl:  ; shift `b40acc << b` in place
        asl b40_acc+2
        rol b40_acc+1
        rol b40_acc
        deca
        bne b40scl
b40done:
        rts

; Call with data pointer in X.
b40print:
        ldab #3
        ldaa 0, x
        staa b40_acc+1
        inx
        ldaa 0, x
        staa b40_acc+2
        inx

b40step:
        clr b40_acc
        ldaa #3
        bsr b40scl

        lda b40_acc
        sta b40_tmp
        lda b40_acc+1
        sta b40_tmp+1
        lda b40_acc+2
        sta b40_tmp+2

        ; now b40_tmp = b40_acc = 8 * b40_acc(original)

        ldaa #2
        bsr b40scl
        ; now b40_acc = b40_acc(original) * 32

        ldaa b40_tmp+2
        adda b40_acc+2
        staa b40_acc+2
        ldaa b40_tmp+1
        adca b40_acc+1
        staa b40_acc+1
        ldaa b40_tmp+0
        adca b40_acc+0
        staa b40_acc+0
        ; now b40_acc = b40_acc(original) * 40
        ; and a has the most significant byte of b40_acc

        beq b40done
        cmpa #3
        ble b40sp
        cmpa #12
        ble b40dig

        adda #8         ; alphabetic
b40dig: adda #12        ; digit
b40sp:  adda #31        ; symbol
        jsr b40_cb
        dec b
        bne b40step
        bra b40print


b40.py

download raw

import string

class B40:
    cset = "".join(sorted("\0" + string.digits + string.ascii_uppercase + " .,"))
    assert len(cset) == 40

    def dec(self, bytes):
        i = 0
        j = 0
        while True:
            if j == 0:
                d = bytes[i] * 256
                if i + 1 < len(bytes):
                    d += bytes[i+1]
                print(f"[DEC] {d: 5}")
                i += 2
                j = 3
            print(d, end=" ")
            d = d * 40 # 24 bit temporary result
            top = d >> 16
            d = d & 0xffff
            print(d, top, end=" ")
            if top == 0:
                break
            c = self.cset[top]
            print(repr(c))
            yield c
            j -= 1
        print()

    def enc(self, message):
        message = [self.cset.find(c) for c in message.upper()]
        assert not any(i==-1 for i in message)

        print(len(message))
        while len(message) % 3:
            message.append(0)

        for i in range(0, len(message), 3):
            trio = message[i:i+3][::-1]
            d = 0
            for c in trio:
                d = (d + (c << 16) + 39) // 40
                print(f"{c:2} {repr(self.cset[c]):5}", f"{d:5d}", end=" ")
            print(f" {d: 5}")
            assert 0 <= d < 65536
            yield d >> 8
            yield d & 0xff
        yield 0


b40 = B40()
encoded = list(b40.enc("hello world 12,345.67"))
print(encoded)
decoded = list(b40.dec(encoded))
print(decoded)
print(repr(b40.cset))