#!/usr/bin/env retro

uuencode

This is an implementation of uuencode in Retro. This takes a file name and renders the uuencoded data to standard output.

Prep Work

First, determine the size of the file.

~~~#0 script:get-argument file:R file:open &file:size &file:close bi 'SIZE const ~~~

With this, I can create a buffer to hold the file and read it into memory.

~~~'Data d:create  SIZE allot &Data #0 script:get-argument file:slurp ~~~

The Header

A uuencoded file starts with a header line of the form:

begin <mode> <file><newline>

I am hard coding the mode as 0644.

~~~:header (-) #0 script:get-argument 'begin_0644_%s1\n s:format s:put ; ~~~

Data Encoding

Here's how uuencoding works:

• Three characters are packed into a cell.

Cat = (C) 01000011 (a) 01100001 (t) 01110100

• This is divided into a quartet of six bit values.

Cat = 010000 110110 000101 110100

• 32 is added to each, yielding a printable character.
• At this point, "Cat" has become 0V%T.

So the basic encoding words will need to pack a cell and then unpack it into characters.

~~~:mask  #63 and ; :to-char #32 + ;   :decode (n-)   dup #18 shift mask to-char c:put   dup #12 shift mask to-char c:put   dup #6  shift mask to-char c:put                 mask to-char c:put ;   :pack   #0 [ fetch-next ] dip + #-8 shift      [ fetch-next ] dip + #-8 shift      [ fetch-next ] dip + ; ~~~

The actual conversion is pretty easy. Output is given as lines, starting with an encoded length followed by the encoded data. The encoded length is just the actual value with 32 added to it and displayed as an ASCII character.

Lines are 45 unencoded characters in length, so will start with an M, except for the last one, which may be shorter.

So:a

~~~:full-line       $M c:put #15 [ pack decode ] times nl ; :remaining-size  SIZE #45 mod #32 + c:put ; :partial-line    SIZE #45 mod #3 / [ pack decode ] times ; :final-bundle    SIZE #45 mod #3 mod                  #2 [ fetch-next #-16 shift swap fetch #-8 shift + decode ] case                  #1 [ fetch      #-16 shift                        decode ] case                  drop ;     ~~~

And then using the factors to do the conversion:

~~~:convert   &Data   SIZE #45 / &full-line times   remaining-size partial-line final-bundle nl ; ~~~

The Footer

A uuencoded file ends with two lines:

end

The backtick is used to denote a line with a length of zero characters.

~~~:footer $` c:put nl 'end s:put nl ; ~~~

Run Everything

~~~:uuencode header convert footer ; uuencode ~~~