retroforth/example/iOS/GopherClient.forth
crc 311a3c0743 improve gopher client
FossilOrigin-Name: 8ac59488e3628ed48ec0bb2de2ce44c659036c4e8e7d875849f5398a58d93722
2018-08-05 22:56:17 +00:00

155 lines
3.5 KiB
Forth

This is a tiny Gopher client written in RETRO. It's only for the iOS implementation, but could be adapted for others with a little reworking of the file I/O code.
It's intentionally kept minimal, handling only text files and directories. The interface is line oriented.
# General Configuration
`GOPHER-DATA` is the name of the file to store the last downloaded content into.
`WrapPoint` holds the number of characters to display before wrapping output.
There is also a `Results` variable which will point to the data set being displayed.
~~~
'GopherData.txt 'GOPHER-DATA s:const
#34 'WrapPoint var<n>
'Results var
'TARGET d:create #1024 #64 * allot
~~~
# Download Data
Specify a server, a port number, and the initial seletor to fetch.
~~~
:grab (sns-)
&TARGET 'abcd 'dabc reorder gopher:get
drop ;
~~~
# Indexes
An output line will have a format like this:
1234: TXT About this server
1235: DIR Archive
1236: This is an information line or
unknown type
I define a variable `Displayed` to track how
many characters have been displayed so far.
~~~
{{
'Displayed var
~~~
A `wrap` word handles wrapping and indenting lines based on the `Displayed` characters and the global `WrapPoint`.
~~~
:wrap
@Displayed @WrapPoint gt?
[ #0 !Displayed nl '__________ s:put ]
[ &Displayed v:inc ] choose ;
~~~
Next is a word to display the directory type. This uses a simple case structure to handle things.
~~~
:type (c-)
$0 [ 'TXT_ s:put ] case
$1 [ 'DIR_ s:put ] case
drop '____ s:put ;
~~~
I define a `line` word to display the line number. This lets me pad the number with a leading number of spaces and have a colon and space following it.
~~~
:line (n-)
n:to-string dup
s:length #4 swap - [ sp ] times
s:put ':_ s:put ;
~~~
I now define a custom version of `s:put` that will call the `wrap` word defined earlier. I've not used `s:for-each` here as this will be called frequently, so it's a little faster to have a manually constructed loop here.
~~~
:s:put<w/wrap> (s-)
[ repeat fetch-next 0; c:put wrap again ]
call drop ;
~~~
The next word is `display`. This will tokenize a line and display the `type` and then the rest of the description using `s:put<w/wrap>`.
~~~
:display (s-)
#0 !Displayed
ASCII:HT s:tokenize #0 set:nth fetch
fetch-next type s:put<w/wrap> ;
~~~
~~~
---reveal---
~~~
And finally, tie everything together. This will display an index.
~~~
:display:index (-)
&TARGET ASCII:LF s:tokenize !Results
@Results fetch #2 - @Results store
#0 @Results
[ over line display nl n:inc ]
set:for-each drop ;
}}
~~~
# Text Viewer
Text files are really easy. We just read the downloaded file into a buffer and display it.
~~~
:display:text (-)
&TARGET s:put nl ;
~~~
# Final Bits
`display-by-type` takes a type identifer and then calls the proper viewer.
~~~
:display-by-type (c-)
$0 [ display:text ] case
$1 [ display:index ] case
dup c:put nl drop ;
~~~
`home` just fetches a starting index and displays it. I have this hardcoded to my personal Gopher server.
~~~
:home (-)
'forthworks.com #70 '/ grab
$1 display-by-type ;
~~~
The last bit is `g`, which is used to navigate a directory. Pass it the line number to load.
~~~
:g (n-)
@Results swap set:nth fetch
ASCII:HT s:tokenize
dup #0 set:nth fetch fetch [
[ #2 set:nth fetch ]
[ #3 set:nth fetch s:chop s:to-number ]
[ #1 set:nth fetch ] tri grab
] dip display-by-type ;
~~~
The final thing to do is just call `home` to get started.
~~~
home
~~~