Monday, January 8, 2007

GNU Readline

Today I was playing around with the command-line version of Factor. I am one of those guys who don't wanna touch the mouse whenever possible. So the command line is always a good option esp. when you have completion available. As far as I can see Factor doesn't use the GNU readline library. But there is a cheap workaround. Just download rlwrap and you can wrap readline around Factor. Installation is a piece of cake. Next step is to have command completion in Factor. So you must tell rlwrap all the words that are available in Factor.

Piece of cake: Just give rlwrap a file with all the words and you can call it like this:

rlwrap -f factor.words ./f -shell=tty

where all Factor words have been written in the file "factor.words".

So the question is: how do you get all the Factor words. Nothing easier than that. Factor is very reflective. What you have to do in the Factor listener is this:

[ word-name ] map
"allcomms.txt" <file-writer>
[ dupd swap stream-write stream-terpri ] each-with

Just for cosmetic reasons I sorted this file with "sort" in the GNU/Linux shell:

sort allcomms.txt > factor.words

Per default some characters are word separators in rlwrap. Unfortunately "-" too which is heavily used in Factor. Therefore just define all word breaking characters leaving out the dash. I put the whole thing in a file called "rf" and that's it's content:

rlwrap -b '(){}[],+=&^%$#@"";|\' -f factor.words -r ./f -shell=tty

(Added also the -r option which remembers words you type that are not in the completion file.)

I run Factor from the command-line by invoking "rf" and when I need the user interface I call "ui" in the Factor listener.

In the Factor listener you can also complete words. It's actually much better because its fuzzier. Whereas in the command-line with rlwrap you have to type in the first correct letters.


Daniel Ehrenberg said...

Hi Schmidt,
Thanks for the tips on using the Factor interface with readline. On my computer, the GUI is broken for some reason and I can't figure out why, so I use the CLI. Hopefully this will help. But why do you need to list all Factor words to make that work? The problem with that is that, if you define a new word, it won't be in there unless you rewrite all the words to the file. The words file would have to be updated very frequently to be at all accurate, and sometimes, you'd have to quit factor to update it, wouldn't you?
PS. you could best respond to be over IRC or by email (microdan at gmail dot com)

Hans Schmid said...

Hi Daniel,

yes you are right. Introspection is not dynamic in this case. GNU readline has a C interface which allows one to have any dynamic completion one wishes. So if Factor would use readline it could call a function/word in Factor and Factor could return all possible completions. But rlwrap doesn't/can't provide that. But the remember functionality in rlwrap is a good workaround. All in all rlwrap saves a lot of typing but is't very dynamic.

Chris Double said...

Nice work! You can sort the words in Factor using 'sort'. It takes a quotation that returns -1, 0, or 1 depeding on whether the two items passed to it are lesser, equal or greater. So for example:

all-words [ word-name ] map [ <=> ] sort

The 'with-stream' word is also useful. It calls a quotation with a stream bound as stdio. So it means less stack manipulation: Something like (untested):

"foo.txt" <file-writer> [
word-names [ . ] each
] with-stream

(Where 'word-names' returns your sequence of names).

Hans Schmid said...

Hi Chris,

thanks again for your comments. It's really helpful for me as a Factor beginner. I also found out that [ <=> ] sort is natural-sort in the standard. It's so much fun exploring Factor. with-stream is also very helpful. I thought I needed something like this when I did the little Factor scriptlets but didn't find something appropriate.

Thanks again,
Hans :-)

Hans Schmid said...

As a one-liner:

"foo.txt" <file-writer> [ all-words [ word-name ] map natural-sort [ . ] each ] with-stream

Hans Schmid said...

This might be better:

: word-names [ word-name ] map ;
: pprint-each [ . ] each ;
: file-output ( quot path -- )
swap <file-writer> swap with-stream ;

: allwords ( path -- )
[ all-words word-names natural-sort pprint-each ] file-output ;