Racket/base24

Från Täpp-Anders
Hoppa till navigeringHoppa till sök
#lang racket

;;;;;
;; BASE24 encoder/decoder
;; Täpp-Anders Sikvall, anders@sikvall.se, 2026-04-07
;;
;; USAGE
;;          racket base24.rkt --encode < INPUT_FILE > OUTPUT_FILE
;;          racket base24.rkt --decode < INPUT_FILE > OUTPUT_FILE
;;
;; COMMENT
;;          This does not precede the coded data with any kind of header
;;          and the error control and handling is abysmally minimal but
;;          it shows the ease of implementing something like this compared
;;          to languages like C.
;;
;; EXPLANATION
;;          The program treats the entire input as a bignum and then basically
;;          converts it to the output by repeated division and looking up the
;;          quotient in the BASE24 alphabet. Since the bignum implementation
;;          is so efficient in Racket/Scheme this means it will be effective
;;          even for moderately large files.
;;
;;

;; BASE24 Alfabet (exkluderar förväxlingsbara tecken)
(define alphabet "BCDFGHJKMPQRTVWXY2346789")
(define base 24)
(define max-col 68)

;; Skapar en map för snabb uppslagning vid avkodning
(define char-to-val
  (for/hash ([c (in-string alphabet)]
             [i (in-naturals)])
    (values c i)))

;; Hjälpfunktion för att skriva ut med radbrytningar
(define (display-with-breaks str)
  (let loop ([remaining str])
    (cond
      [(<= (string-length remaining) max-col)
       (displayln remaining)]
      [else
       (displayln (substring remaining 0 max-col))
       (loop (substring remaining max-col))])))

;; --- ENCODER ---
(define (encode)
  (let* ([input-bytes (port->bytes (current-input-port))]
         [num (bytes->integer input-bytes)])
    (if (zero? num)
        (displayln (string-ref alphabet 0))
        (let loop ([n num] [res '()])
          (if (zero? n)
              (display-with-breaks (list->string res))
              (loop (quotient n base)
                    (cons (string-ref alphabet (remainder n base)) res)))))))

;; --- DECODER ---
(define (decode)
  ;; Vi rensar bort eventuella radbrytningar/whitespaces innan vi avkodar
  (let* ([raw-input (port->string (current-input-port))]
         [input-str (regexp-replace* #px"\\s+" raw-input "")]
         [num (for/fold ([n 0])
                        ([char (in-string input-str)])
                (+ (* n base) (hash-ref char-to-val char)))])
    (display (integer->bytes num))))

;; --- KONVERTERINGSFUNKTIONER ---
(define (bytes->integer b)
  (for/fold ([n 0])
            ([byte (in-bytes b)])
    (+ (arithmetic-shift n 8) byte)))

(define (integer->bytes n)
  (if (zero? n)
      (bytes 0)
      (let loop ([i n] [res '()])
        (if (zero? i)
            (list->bytes res)
            (loop (arithmetic-shift i -8)
                  (cons (bitwise-and i #xFF) res))))))

;; --- CLI HANTERING ---
(define (main)
  (match (current-command-line-arguments)
    [(vector (or "--encode" "-e")) (encode)]
    [(vector (or "--decode" "-d")) (decode)]
    [_ (displayln "Användning: racket base24.rkt [--encode | --decode]")]))

(main)