Jump to content
IGNORED

Adding an INPUT statement to Forth


TheBF

Recommended Posts

Many times those who want to try Forth are frustrated by the absence of simple features that they took for granted in BASIC.

One of those things in my experience was the BASIC word INPUT. It allows us to get user input very easily.

 

Forth was created by a person who started from the premis that he never knew what a program was going to require in precise terms so he assumed very little with his language but gave himself the tools to build what he needed rather than make it in advance. Charles Moore is something of an enigma in programming because he is happy to start from scratch every time. For us normal humans it's handy to have some things "in the can".

 

The following code adds INPUT to Camel Forth. (it will require tweaking for other flavours of Forth)

 

The important difference is that there must be two "INPUTs" for Forth. One for strings and one for numbers. BASIC knows what kind of variable you use with INPUT so it can run the right code. Forth has no knowledge like that so you have to use $INPUT or #INPUT as needed. The good news is that #INPUT uses $INPUT to gather the number string and then converts it.

 

I have made #INPUT with a much smaller error message that TI BASIC.

I could not justify all the space for

 

* WARNING

INPUT ERROR IN 100

TRY AGAIN"

 

:)

 

But it's simple to change if you want it.

\ INPUT.FTH   creates input like BASIC
\ *Difference* there is a separate input for numbers and strings

DECIMAL
: "?"      BEEP CR ." ?  " ;   \ we can reuse this gem

: $ACCEPT ( $addr -- )   DUP  1+ 80 ACCEPT  SWAP C!  ;

: $INPUT  ( $addr -- )  "?"  $ACCEPT ;  \ "?" to look like TI-BASIC

: #INPUT  ( variable -- )   \ made to look like TI-BASIC
          BEGIN
            PAD $INPUT      \ $ACCEPT text into temp buffer PAD
            PAD ?NUMBER 0=   \ convert the number in PAD

          WHILE              \ while the conversion is bad we do this
             CR HONK ." input error "
             CR DROP
          REPEAT
          SWAP ! ;           \ store the number in the variable on the stack)


\ Usage:
\
\ VARIABLE A$ 100 ALLOT      \ string variables need more space
\ VARIABLE X
\
\ A$ $INPUT
\
\ X  #INPUT
Edited by TheBF
  • Like 2
Link to comment
Share on other sites

Converting your code to fbForth 2.0 took more than a little tweaking! :P Instead of ?NUMBER ( addr -- n ) and ACCEPT ( addr maxcnt -- chrcnt ), there are NUMBER ( addr -- d ) and EXPECT ( addr maxcnt -- ). The problems with NUMBER are (1) it returns a double number, i.e., 32 bits, and (2) an error causes it to abort with a system error message. To keep the ported code as close as possible to your code, I defined ?NUMBER and ACCEPT . I maintained the double-number output of NUMBER for ?NUMBER to avoid surprises for those used to fbForth and TI Forth. NUMBER only works with a blank-terminated numeric string, such as stored by WORD , but EXPECT stashes two nulls (ASCII zeroes) after the input string! Also, VARIABLE requires an initial value when it defines a new variable.

 

I don’t want to derail this thread with too much discussion about the vagaries of fbForth 2.0, so without further ado, in the spoiler below is the Camel Forth code ported to that other dialect of Forth:

 

 

 

\ ===fbForth 2.0 version===============================================
\ INPUT.FTH creates input like BASIC
\ *Difference* there is a separate input for numbers and strings
HEX
: ?NUMBER ( addr -- d flag )
\ (NUMBER) expects a double number (0 to start) and an address
0 0 ROT \ S:d addr
DUP 1+ \ first byte assumed to be char count S:d addr addr+1
C@ 02D = \ check for '-' \ S:d addr ?-
DUP >R \ dup '-' flag; push to return stack S:d addr ?- R:?-
+ \ add flag to addr to advance position if '-'
-1 \ starting value for DPL \ S:d addr -1 R:?-
BEGIN
DPL ! \ start DPL with -1 \ S:d addr R:?-
\ (NUMBER) returns updated d and address of first non-digit
(NUMBER) \ S:d addr
DUP C@ \ check first non-digit char S:d addr [addr] R:?-
\ if success, 1st num is TRUE flag; 2nd num exits loop
\ if '.' found, 1st num is 0 for DPL; 2nd num loops again
\ if failure, 1st num is FALSE flag; 2nd num exits loop
CASE
0 OF 1 1 ENDOF \ success if null char
BL OF 1 1 ENDOF \ success if blank char
02E OF 0 0 ENDOF \ '.' found; loop again for rest of number
ELSEOF 0 1 ENDOF \ failure
ENDCASE
UNTIL \ loop if not 0 S:d addr flag R:?-
SWAP DROP \ drop address S:d flag R:?-
R> \ S:d flag ?-
IF
>R \ S:d R:flag
DMINUS \ negate d if ?- is TRUE
R> \ S:d flag
THEN ;
: ACCEPT ( addr maxcnt -- chrcnt )
OVER OVER \ S:addr maxcnt addr maxcnt
EXPECT \ S:addr maxcnt
1+ 0 DO \ check for null up to maxcnt S:addr
DUP \ dup 1st char address S:addr addr
I + C@ 0= \ check if next char is null S:addr [addr+I]
IF \ we're outta here if null char S:addr
DROP \ drop leftover addr S:
I \ leave chrcnt S:chrcnt
LEAVE \ leave loop
THEN
LOOP ;
DECIMAL
: "?" BEEP CR ." ? " ; \ we can reuse this gem
: $ACCEPT ( $addr -- ) DUP 1+ 80 ACCEPT SWAP C! ;
: $INPUT ( $addr -- ) "?" $ACCEPT ; \ "?" to look like TI-BASIC
: #INPUT ( variable -- ) \ made to look like TI-BASIC
BEGIN
PAD $INPUT \ $ACCEPT text into temp buffer PAD
PAD ?NUMBER 0= \ convert the number in PAD
WHILE \ while the conversion is bad we do this
CR HONK ." input error "
CR DROP DROP \ drop double number
REPEAT
DROP \ unconditionally drop MSW of double number
SWAP ! ; \ store the number in the variable on the stack)
\ Usage:
\
\ 0 VARIABLE A$ 100 ALLOT \ string variables need more space
\ 0 VARIABLE X
\
\ A$ $INPUT
\

\ X #INPUT

 

 

 

...lee

Link to comment
Share on other sites

Didn't mean to create a make work project Lee. Very sorry.

But wow! You really went to town there. That would take me a very longgg time I think.

 

 

I did this for HsForth for MS DOS to make ANS Forth ACCEPT functionality.

: ACCEPT        ( c-addr +n1 -- +n2 )   EXPECT SPAN @  ;  

But it does not seem to work as I "expect" in Turbo Forth ?

 

And yes it is sad that Forth does not have a simple text to number conversion word that seems to work across all forth versions.

 

For your reference here is what Brad Rodriguez created for Camel Forth. His ?NUMBER returns a single because CAMEL Forth does not do doubles.

 

The ANS standard word >NUMBER I find pretty useless in the real world. The standard is not finished in this area IMHO.

\ ========================================================================
\ S T R I N G  T O  N U M B E R   C O N V E R S I O N


: DIGIT?     ( char -- n -1)                \ if char is a valid digit
\            (      -- x  0 )               \ if char is not valid
              DUP 39 > 100 AND +            \ silly looking
              DUP 140 > 107 AND -  30 -     \ but it works!
              DUP BASE @ U< ;               \ 36 bytes

: ?SIGN       ( adr n -- adr' n')           \  f  get optional sign
\ advance adr/n if sign; return NZ if negative
              OVER C@                       \ -- adr n c
              2C - DUP ABS 1 = AND          \ -- +=-1, -=+1, else 0
              DUP IF 1+                     \ -- +=0, -=+2
                     >R 1 /STRING R>        \ -- adr' n' f
              THEN ;

: UD*         ( ud1 u2 -- ud3)              \ 32*16->32 multiply
              DUP >R * SWAP R> UM* ROT + ;  \ replaced 'UM* DROP' with '*' BF.

: >NUMBER     ( ud adr u -- ud' adr' u' )   \ convert string to number
              BEGIN  DUP
              WHILE
                  OVER C@ DIGIT? 0=
                  IF   DROP EXIT
                  THEN >R 2SWAP BASE @ UD*
                       R> M+ 2SWAP 1 /STRING
              REPEAT ;

: ?NUMBER     ( c-addr -- n -1 )            \ string->number
\ ;Z                   -- c-addr 0          \ if convert error
              DUP  0 0 ROT COUNT            \ -- ca ud adr n
              ?SIGN >R  >NUMBER             \ -- ca ud adr' n'
              IF   R> 2DROP 2DROP 0         \ -- ca 0   (error)
              ELSE 2DROP NIP R>
                 IF NEGATE THEN  -1         \ -- n -1   (ok)
              THEN ;

BF

Link to comment
Share on other sites

I did this for HsForth for MS DOS to make ANS Forth ACCEPT functionality.

: ACCEPT        ( c-addr +n1 -- +n2 )   EXPECT SPAN @  ;  
But it does not seem to work as I "expect" in Turbo Forth ?

 

That's because versions 1.2 onwards of TurboForth store keystrokes in VDP. Specifically, TIB returns a *VDP* address (try the phrase TIB @ $. - see? 3420 - that's a VDP address!) and EXPECT interprets the address passed to it as a VDP address.

 

WORD on the other hand is "clever". It knows that the TIB lives in VDP, and when it pulls a word out of the TIB, it copies it to a "word buffer" in CPU memory.

 

Confused yet? You will be! (You should be hearing the theme tune to the old TV show "Soap", right about now!)

 

This is a design decision that has come back to kick me in the ass a number of times.

 

 

The complexity arises because block buffers live in VDP memory in TF.

 

So, EXPECT stuffs keystrokes into VDP memory. WORD copies them out.

 

It still does actually work. Try this:

: test ( -- )
  tib @ 80 expect \ get up to 80 characters of text into TIB in VDP RAM
  begin bl word dup while cr type repeat
  2drop ;
 
test fred bloggs was ere
Here's ACCEPT and some test code:

 

: accept ( c-addr +n1 -- +n2 )
  tib @ swap expect      \ get up to n1 chars in tib in vdp
  tib @ swap span @ vmbr \ read from vdp to c-addr
  span @                 \ push n2
  span 0!                \ invalidate tib
;
Test code:

36 load \ load DUMP
create buff 80 chars allot
buff 80 accept
buff 80 dump
Edited by Willsy
Link to comment
Share on other sites

That's because versions 1.2 onwards of TurboForth store keystrokes in VDP. Specifically, TIB returns a *VDP* address (try the phrase TIB @ $. - see? 3420 - that's a VDP address!) and EXPECT interprets the address passed to it as a VDP address.

 

I will have to take another look at your code, but why does 3420h not also indicate a low RAM address?

 

...lee

Link to comment
Share on other sites

 

I will have to take another look at your code, but why does 3420h not also indicate a low RAM address?

 

...lee

Err... good question!

 

TF doesn't actually use low memory for anything until you tell it to by writing to the variable H.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...