Skip to footer navigation.

« Oatmeal

Code Log: In which we split a string with RetroForth

Today’s goal? To write a bit of retro that mimic’s JavaScript’s inbuilt .split() function.

Retro ships with both s:split/string and 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.

In JavaScript if I write something like,

let str = "banana"

// 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!

So, back to our challenge! How to get retro to mimic JavaScript’s .split()?

As a first step, I’m going to explore s:replace-all.

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 bXnXnX

'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:map and 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.

And then,


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 FALSE the 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 'aaa

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!

Rewinding to s:filter!

'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 'aaa!

COOKING WITH FIRE! Next steps!? How do we leverage our new-found Promethean-power to split strings?

I think 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

Perhaps 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 $a in '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 .split() in JavaScript!

'banana [ $a -eq? ] s:filter a:from-string [ c:put nl ] a:for-each

GLORIOUS! Success — the above code returns the following,


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

Gotta dup!

Discussion of this post on /r/Forth.