Code Log: In which we split a string with RetroForth
Retro ships with both
s:split/char. They work the same way as far as I can tell, but one uses a string pattern to split on while the other splits based on a single character.
let str = "banana" str.split("a") // Returns an array: // => [ "b", "n", "n", "" ]
Meanwhile, with retro’s
s:split/string I get something different back.
'banana 'a s:split/string s:put nl s:put nl
That returns 2 strings,
That isn’t what I want!
As a first step, I’m going to explore
From retro’s glossary:
Data: sss-s Addr: - Float: -
Replace all instances of s2 in s1 with s3.
So, the following should return the string
'banana 'a 'X s:replace-all s:put
This indicates to me that retro already has a way to work over each element of a string, and I can rely on some inbuilt magic, perhaps, to inspect each element of a string.
The next words that seem worth exploring are
s:filter. Both of these words apply a quote to each element of a string.
Again, from the glossary:
Data: sq-s Addr: - Float: -
Execute the specified quote once for each character in the string. Builds a new string from the return value of the quote. The quote should return only one value.
Data: sq-s Addr: - Float: -
Execute the quote once for each value in the string. If the quote returns
TRUE, append the value into a new string. If
FALSEthe value will be discarded.
I think that
s:filter is where I want to start.
'banana [ 'a s:eq? ] s:filter s:put nl
Reading the description of how
s:filter works I guessed that the above example would return something like
Instead it returns nothing…so, that isn’t heaps useful to me at the moment.
'banana [ nl 'boom! s:put ] s:map
Meanwhile, the above returns
boom! once for each letter in banana!
BANG! That is something!
…but then I had a sudden realization — I’m a goober caught off guard by a type system — classic!
'banana [ $a eq? ] s:filter s:put nl
The issue was that I was checking for string equality when
s:filter is breaking the string into individual…you guessed it (you probably figured it out before I did)…characters!
So, the above now does indeed return
COOKING WITH FIRE! Next steps!? How do we leverage our new-found Promethean-power to split strings?
s:map may still be what we want to use, but now we know to do it with characters instead of strings.
A first attempt:
'banana [ $a eq? [ nl 'BOOM_it's_a_match! s:put nl ] if ] s:map
My first thought was to nest two quotes to check for a matching character. This doesn’t work, though. I’m not 100% certain why. The documentation says that nested quotes are kosher and my inner quote works a-okay when it is tested on its own:
$a $a eq? [ nl 'BOOM_it's_a_match! s:put nl ] if
s:map isn’t passing characters into the quotation like with
s:filter? We can test that theory easily enough:
'banana [ $a eq? [ nl 'Match s:put nl ] if ] s:filter s:put nl
That also doesn’t work — interestingly, though, it doesn’t work in the exact same way that
s:map didn’t work! In both instances I was dropped into the repl when I ran
retro split.retro (the name of this file). The expected result was for the code to be run, stuff be barfed to
stdout and then to be returned to the shell. I’m not sure how to act on this new information, but I’m gonna stash it away as something of note for the time being. Another clue. Another Scooby Snack.
A return to
s:filter, and abandoning nested quotes for the time being.
Success is in sight!
Rather than use a nested quotation, lets filter out all the
'banana, then, because there is no way to visualize an array in retro, lets iterate over that array to show each element in it! This is looking a whole lot like
'banana [ $a -eq? ] s:filter a:from-string [ c:put nl ] a:for-each
GLORIOUS! Success — the above code returns the following,
b n n
It was a bit of a journey but I’ve written a bit of retro that can mimic the behavior of
.split(), at least at first blush.
Thanks to the heroic efforts of a friend on IRC it was pointed out that the issue with my nested quotes is nothing to do with their being nested quotes and everything to do with the stack being unbalanced!
'banana [ $a eq? dup [ nl 'Match s:put nl ] if ] s:filter s:put nl
Discussion of this post on /r/Forth.