retroforth/example/uuencode.retro
crc 660e52bdcb rename words in sys: to script: (old names still work in this release, but are deprecated)
FossilOrigin-Name: 1a43743f43076eb087ee4dd7fbfa96b8dfda2aa4ddcff41e6a5a9634ba6e239c
2020-09-14 19:55:02 +00:00

124 lines
2.5 KiB
Forth
Executable file

#!/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
~~~