4

I have used CREATE to create an array of strings:

create mystringarray s" This" , s" is" , s" a", s" list" ,

And I want to sort this in ascending order. I've found some tutorials for assembly language online, but I want to do it in Forth. What is the best practice method?

Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
Manuel Rodriguez
  • 734
  • 4
  • 18
  • Funny fact: another question with **the same** issues — [How do I implement an array of strings?](https://stackoverflow.com/questions/8693341/how-do-i-implement-an-array-of-strings) – ruvim Apr 19 '18 at 21:53
  • You don't have an array of strings. There is no such thing as an array abstraction in Forth that can be piled on top of a string abstraction. Give me the code to print your third string and you understand what I mean Albert – Albert van der Horst Sep 19 '19 at 18:12

2 Answers2

7

You need to first make sure that your data representation is accurate.

A literal string in Forth is obtained using the word s" and so you would write, for example:

s" This"  ok

Once entered, if you do .s, you'll see two values:

.s <2> 7791776 4  ok

This is a pointer to the actual string (array of characters), and a count of the number of characters in the string. Certain words in Forth understand this string representation. type is one of them. If you now entered type, you'd get the string typed on the display:

type This ok

So now you know you need two cells to represent a string obtained by s". Your create needs to take this into account and use the 2, word to store 2 cells per entry, rather than , which stores only one cell:

create myStringArray
    s" This" 2,
    s" is" 2,
    s" an" 2,
    s" array" 2,
    s" of" 2,
    s" strings" 2,

This is an array of address/count pairs for the strings. If you want to access one of them, you can do so as follows:

: myString ( u1 -- caddr u1 )  \ given the index, get the string address/count
    \ fetch 2 cells from myStringArray + (sizeof 2 cells)*index
    myStringArray swap 2 cells * + 2@ ;

Breaking this down, you need to take the base of your array variable, myStringArray and add to it the correct offset to the string address/count you want. That offset is the size of an array entry (2 cells) times the index (which is on the data stack). Thus the expression, myStringArray swap 2 cells * +. This is followed by 2@ which retrieves the double word (address and count) at that location.

Put to use...

3 myString type array ok
0 myString type This ok

etc...

Now that you know the basics of indexing the array, then the "best practice" of sorting would follow the normal best practices for choosing a sort algorithm for the kind of array you wish to sort. In this case, a bubble sort is probably appropriate for a very small array of strings. You would use the compare word to compare two strings. For example:

s" This" 0 myString compare .s <1> 0  ok

Result is 0 meaning the strings are equal.

lurker
  • 56,987
  • 9
  • 69
  • 103
  • I think this answer *should* include some kind of sort-implementation that actually sorts an array (before it is accepted), but I gave it an upvote because it's a contribution in FORTH and that is in itself awesome. And your answers are generally awesome and I want you to stick around. – Evan Carroll Apr 07 '18 at 02:33
  • @EvanCarroll My intention was not to completely do all of the work for the OP but to clear up the issues they were having which prevented them from completing the work. Any further elaboration would be a translation of some common sort algorithm which is well known over to Forth. In addition, there are numerous approaches, the selection of which depends upon the OP's specific sorting scenario which has not been described. It was a very general question which indicated more a lack of the necessary Forth prerequisites than the sorting itself. – lurker Apr 07 '18 at 10:33
  • 1
    @lurker, it is need to take into account the following issue. [Excerption from the Standard](https://forth-standard.org/standard/file/Sq): "Since an implementation may choose to provide only one buffer for interpreted strings, an interpreted string is subject to being overwritten by the next execution of S" in interpretation state" – ruvim Apr 14 '18 at 07:59
  • 1
    @ruvim yes you are correct. Thank you for catching that. I will update my answer when I have a little more time. – lurker Apr 14 '18 at 11:25
  • The easy way to solve the issue of transient buffer is to wrap the strings as `:noname s" This" 2, s" is" 2, ... ; execute` – ruvim Apr 14 '18 at 14:40
5

The best practice method to sort an array is to use some existing library. If the existing libraries don't fit your needs, or your main purpose is learning — then it makes sense to implement your own library.

Using a library

For example, Cell array module from The Forth Foundation Library (FFL) can be used to sort an array of any items.

Code example

include ffl/car.fs
include ffl/str.fs

0 car-new value arr  \ new array in the heap

\ shortcut to keep -- add string into our 'arr' array
: k ( a1 u1 -- ) str-new dup arr car-push str-set ;

\ set compare method
:noname ( a1 a2 -- n ) >r str-get r> str-get compare ; arr car-compare!

\ dump strings from the array
: dump-arr ( -- ) arr car-length@ 0 ?do i arr car-get str-get type cr loop ;

\ populate the array
s" This" k s" is" k s" a" k s" list" k

\ test sorting
dump-arr cr
arr car-sort 
dump-arr cr

The output

This
is
a
list

This
a
is
list

Using bare Forth

If you need a bare Forth solution just for learning, look at bubble sort sample.

An array of strings should contain the string addresses only. The strings themselves should be kept in some another place. It is useful to use counted string format in this case — so, we use c" word for string literals. To keep the strings themselves we place the initialization code into a definition (:noname in this case) — it will keep the strings in the dictionary space.

Bubble sort is adapted from the variant for numbers to the variant for strings just with replacing the word for compare items. Note that 2@ word returns the value of the lowest address at the top.

Code example

\ some helper words
: bounds ( addr1 u1 -- addr1 addr2 ) over + swap ;
: lt-cstring ( a1 a2 -- flag ) >r count r> count compare -1 = ;

\ create an array of counted strings
:noname ( -- addr cnt )
  here
    c" This" , c" is" , c" a" , c" list" ,
  here over - >cells
; execute constant cnt constant arr

\ dump strings from the array  
: dump-arr ( -- ) cnt 0 ?do i cells arr + @ count type cr loop ;

\ bubble sort
: sort-arr ( -- )
  cnt 2 u< if exit then
  cnt 1 do true
    arr cnt i - cells bounds do
      i 2@ ( a2 a1 ) lt-cstring if i 2@ swap i 2! false and then
    cell +loop
    if leave then
  loop
;

\ test sorting
dump-arr cr
sort-arr
dump-arr cr

\ the output is the same as before
ruvim
  • 7,151
  • 2
  • 27
  • 36