retroforth/example/uudecode.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

72 lines
2.2 KiB
Forth
Executable file

#!/usr/bin/env retro
# uudecode
This is an implementation of `uudecode` in Retro. This takes a
file name and renders the decoded data to standard output.
I'm taking shortcuts in implementing this. Since it only writes
to the standard output, I'm going to ignore the `begin ...`
header as well as the footer. The words I define here will use
`pop drop` to exit the calling function.
~~~
:discard-header dup #0 #5 s:substr 'begin s:eq? 0; pop drop-pair drop ;
:discard-empty dup fetch $` eq? 0; pop drop-pair drop ;
:discard-end dup #0 #3 s:substr 'end s:eq? 0; pop drop-pair drop ;
~~~
Now for decoding. uuencode bundles three values into four six bit
characters. I read in four characters, shifting them into position.
The six bit characters are incremented by 32 to make sure they fall
into the printable range, so I also decrement them while packing.
~~~
:gather
#0 [ fetch-next #32 - #63 and #-18 shift ] dip +
[ fetch-next #32 - #63 and #-12 shift ] dip +
[ fetch-next #32 - #63 and #-6 shift ] dip +
[ fetch-next #32 - #63 and ] dip + ;
~~~
The decode process then is to take each bundle and split it back
into 8-bit characters. This is a simple bit of shifting and masking.
~~~
:decode
[ #16 shift #255 and c:put ]
[ #8 shift #255 and c:put ]
[ #255 and c:put ] tri ;
~~~
Iterating over the input file is fairly straightforward. I'm using
the `file:for-each-line` combinator to take care of splitting the
input into lines.
An input line consists of a character indicating the number of
unencoded characters in the line and then the encoded data. As
with the encoded data, the length is raised by 32 to push it
into the printable character set.
~~~
:length fetch-next #32 - ;
~~~
I define a word to iterate over the encoded data in a line.
This will divide the actual length by 3, then gather the
data and decode it.
~~~
:full-line #3 / [ gather decode ] times drop ;
~~~
Finally, load the file and iterate over each line.
~~~
#0 script:get-argument
[ s:keep
discard-header discard-empty discard-end
fetch-next #32 - dup #45 eq?
[ full-line ]
[ #3 / [ gather decode ] times gather decode drop ] choose ] file:for-each-line
~~~