retroforth/example/StringToNumberWithBase.forth
crc d09a03ddca fix bug in StringToNumberWithBase; add string tokenizer example
FossilOrigin-Name: a5d4a3564e05d167ca72a87be254464c786f923048b262c645d8cb11b1a720a9
2017-10-22 19:30:47 +00:00

71 lines
2 KiB
Forth

A standard RETRO image only supports decimal base numbers. This
shows a way to add support for multiple bases.
I begin by creating a variable for the current base and words to
set this to specific values quickly.
~~~
#10 'Base var<n>
:decimal #10 !Base ;
:binary #2 !Base ;
:octal #8 !Base ;
:hex #16 !Base ;
~~~
Next, a string constant with the symbols for each digit. Note that
I'm only going to support uppercase for hexadecimal.
~~~
'0123456789ABCDEF 'DIGITS s:const
~~~
So conversion to a numeric value is pretty easy. The basic idea
here is:
- set a variable to hold the numeric value to zero (as a stating point)
- check to see if the first character is - for negative, set a modifier
- for each character in the string:
- convert to a numeric value (in this case, it's the index in the
DIGITS string)
- Multiply the last value of the number accumulator by the base
- Add the converted value
- Store the result in the number accumulator
- Multiply the final number by the modifier
~~~
{{
'Number var
'Mod var
:convert (c-) &DIGITS swap s:index-of @Number @Base * + !Number ;
:check-sign (s-s) dup fetch $- eq? [ #-1 !Mod n:inc ] [ #1 !Mod ] choose ;
---reveal---
:s:to-number<with-base> (s-n)
#0 !Number check-sign [ convert ] s:for-each @Number @Mod * ;
}}
~~~
Going the other way, back to a string, follows a similar process.
- Take a value
- Repeat:
- Divide by `Base`
- Convert result to character and append to a buffer
- If remainder is not zero, repeat
- If number is negative, append the '-' symbol to the buffer
- Reverse the buffer contents to return a string in the correct order
~~~
{{
'String d:create #12 allot
:check-sign (n-) n:negative? [ $- buffer:add ] if ;
:n->digit (n-c) &DIGITS + fetch ;
:convert (n-) [ @Base /mod swap n->digit buffer:add dup n:zero? ] until drop ;
---reveal---
:n:to-string<with-base> (n-s)
[ &String buffer:set dup n:abs convert check-sign ] buffer:preserve
&String s:reverse ;
}}
~~~