A New Listener

The basic listener is very minimalistic. There are a few reasons for this.

• I don't use it often (I generally code in files or blocks,
using the listener only for brief tests) • A complex listener adds surface for bugs and incompatibilites
due to terminal and host configurations • It's harder to pipe data in and out of a more complex listener

This can be worked around in a number of ways. Tools like rlwrap can be used to provide a line editor and history, and one can always write their own custom listener...

But sometimes it's nice to experiment a bit. This is a modestly expanded version of the listener, adding some new functionality and providing a more flexible base to build on.

Current Features

• character breaking input
• suggestions on hitting TAB
• show stack on hitting ESC
• use retro-describe to get help on CTRL+K


retro -i -f alternate-listener.forth new-listener

The Code

The RETRO image has a 512 cell buffer between the kernel and the high level code. This is used for the Text Input Buffer, or TIB.

+------------------+ | kernel           | 0-1023 | input buffer     | 1024-1536 | standard library | 1537+ | ...              | | system buffers   | EOM - ??? +------------------+

~~~#1024 'TIB const ~~~

The listener needs to determine what to treat as the end of the token. I define end-of-token? for this.

~~~:end-of-token? (c-f) hook   { ASCII:CR ASCII:LF ASCII:SPACE } a:contains? ; ~~~

It's possible to get an empty string as an input. This isn't any good, so I define s:blank? for this.

~~~:s:blank? (s-sf) dup s:length n:zero? ; ~~~

There are certain keys I want to handle differently from others. The initial ones are backspace, tab, escape, and CTRL+K. I am defining handlers for these.

First is backspace. I trap ASCII:BS and ASCII:DEL for this.

~~~:handle:backspace (c-c) hook   dup { ASCII:BS ASCII:DEL } a:contains?   [ buffer:get buffer:get drop-pair ] if ; ~~~

Tab is used to display suggestions, based on the token input so far.

~~~{{   :sigil?     TIB fetch 'sigil:_ [ #7 + store ] sip d:lookup n:-zero? ;   :hint     nl TIB sigil? [ n:inc ] if d:words-beginning-with nl     TIB s:put ; ---reveal---   :handle:tab (c-c) hook     dup ASCII:HT eq? [ buffer:get drop hint ] if ; }} ~~~

Control + k (ASCII:VT) will display help for the word being typed. This assumes that retro-describe is in your $PATH and that the typed text is a complete word name (without a sigil).

~~~:handle:CTRL+K (c-c) hook   dup ASCII:VT eq? [ buffer:get drop                      TIB 'retro-describe_"%s" s:format nl                      unix:system TIB s:put ] if ; ~~~

The escape key will be used to display the stack.

~~~:handle:escape (c-c) hook   dup ASCII:ESC eq?   [ buffer:get drop nl &dump-stack dip nl TIB s:put ] if ; ~~~

To control the checks, I define two words. The first returns an array of handlers, the second processes them.

~~~:special-keys (-a) hook   { &handle:backspace &handle:tab &handle:escape &handle:CTRL+K } ;   :check (q-) hook   &call a:for-each ; ~~~

And with these, I can quickly implement the new-listener.

~~~{{   :guard   (-)  buffer:end TIB lt? [ TIB buffer:set ] if ;   :c:get   (-c) as{ 'liii.... i #1 d }as dup buffer:add ;   :s:get   (-s) [ TIB buffer:set                   [ guard c:get special-keys check end-of-token? ] until                   buffer:start s:chop ] buffer:preserve ;   :forever (q-) repeat &call sip again ; ---reveal---   :new-listener (-) hook     'stty_cbreak unix:system     [ s:get s:blank? &drop &interpret choose ] forever ;     [ 'stty_cbreak unix:system     #0 unix:exit ] &bye set-hook }} ~~~

All of the exposed words are hooks: your code can patch in and replace them as you see fit, making this much more mallable at runtime.

Future things to (maybe) explore:

• tab completion
• line editing
• input history