« Oatmeal

Tagged "code log"

Follow this tag

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"
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,

b
anana

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:

s:replace-all

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:

s:map

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,

s:filter

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,

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

Gotta dup!


Discussion of this post on /r/Forth.

Code Log: In which I explore RetroForth

I like systems that I can hold completely in my head. I like teeny tiny things that are, more or less, totally knowable. This is why I like Forth.

The most useful (perhaps better read as good for the sorts of things I’m interested in doing” (aka, not embedded systems programming”)) Forth system that I’ve run across in my adventures is RetroForth. Retro describes itself as:

…not a traditional Forth. Drawing influence from colorForth, it uses prefixes to guide the compiler. From Joy and Factor, it uses quotations (anonymous, nestable functions) and combinators (functions that operate on functions) for much of the stack and flow control. It also adds vocabularies for working with strings, arrays, and other data types.

It runs on a bespoke virtual machine that is implemented in C, Python, Pascal, C#, JavaScript, TypeScript, Nim and Retro itself. This means that Retro is bananas portable.

Best of all, perhaps, if you ask me, is that Retro treats literate programming as the norm! Here is a collection of examples maintained by the mind behind Retro, @crc and here is a little snippet I wrote. They’re all valid Retro programs, complete with human-readable prose.

I’ve probably got another blog blergh in me about why I like literate programming, and what I think the advantages to literate programming are over, say, really heavily commented programs…another day! Here is pbat.ch on the subject of literate programming.


Have I sold you on Retro? If so (I hope so) here is a very quick, naïve tour of the barest of bones text game engine that I’m working on.

First and foremost, this is a derivative work, based heavily on some example code shared with me by @crc on irc.

My goal writing this was to produce a basic starting point for a text-based game. I set out to achieve a few things:

  • Define and maintain global state
  • Display info about this global state
  • Provide a very basic game ui
  • Process player input and act accordingly to this input
  • Allow a player to exit the game (arguably the most important feature of any game…)

State

My state lives in variables of the shape Player.thing, e.g. Player.xp, Player.location, etc.

I build these variables by iterating over an array of strings, and applying each to my prefix (Player). This leaves me with a bunch of empty variables.

{ 'xp 'loc 'whoops } [ 'Player.%s s:format var ] a:for-each

Next up, I define initial values for the newly minted variables. This is done by defining a new word that dumps values (here, all integers) into the variables. Note — I’ve only defined the word to do this as this point, I haven’t actually called the word, yet, so the variables are, at this point, all still empty.

I also define a word to display the current game state. This works by putting the current game state, @Player.xp and @Player.loc, onto the stack, then a format string (this should be a wee bit familiar to anyone familiar with format strings from C), and then barfing that contents out to stdout with s:put, the string-specific equivalent to a more classical Forth system’s . word.

:state-init
    #7 !Player.loc
    #0 !Player.whoops
    #100 !Player.xp ;

:state-display
    @Player.whoops
    @Player.xp
    @Player.loc
    '_LOC:_%n\n__XP:_%n\nUhOh:_%n\n s:format s:put ;

With these three items complete I have what I need for the time being to manage state. First I define some variables to hold my state, then a word to initialize those variables with some values, and finally a word to display the state to the player.

Process player input

Next I will define what I’ll call my UX words — these are the words triggered by player input. They’re all pretty trivial and follow the same pattern — they dump a new line, nl onto the stack, then a string (anything starting with a single quote, ' with underscores in place of spaces) and print both of those to stdout. Next, another nl for cleanliness and then I do something with my global state, either incrementing or decrementing a variable.

:mic-check 
    nl 'testing_testing_1_2_3! s:put nl &Player.xp v:inc ;

:banana-boat
    nl 'banana_boat! s:put nl &Player.xp v:dec ;

:unknown-input
    nl '_is_not_a_known_input s:append s:put nl &Player.whoops v:inc ;

The most interesting of my UX words is the final one, unknown-input. It does basically the same thing as the other two, but appends whatever the player input to a predefined string to let the player know that the program doesn’t know what to do with their input…and then it increments our whoops” counter.

Now that the UX words are defined I can put them to work! They’re all going to be triggered by specific player input. If a player inputs a t I’ll trigger mic-check, a b will trigger banana-boat, q will trigger the in-built bye word, exiting the program, and anything else will fall back to the unknown-input word.

I’ve also defined a hint word that triggers the state-display word, defined earlier, as well as displaying a string acting as the game’s ui.

:process-input
    't [ mic-check ] s:case
    'b [ banana-boat ] s:case
    'q [ bye ] s:case
    unknown-input ;

:hint
    nl state-display nl '(t)est_(b)anana_(q)uit s:put nl nl ;

Game loop

Now in the home stretch, the next step is to define a basic game loop that pulls everything we’ve written together. The core of the game loop, like most game loops, is a while loop. Before the while loop is started, though, I’ve gotta initiate my starting state using state-init, and displaying a friendly welcome message. That done, I then start the game loop itself. The game loop listens for player input using s:get (akin to something like io.read in Lua), triggers a turn (which is a wrapper around the process-input word) and then returns TRUE. As long as the game loop continues to return TRUE the game will march on.

:turn
    clear process-input hint ;

:welcome-player
    nl 'Welcome!_╰(˙ᗜ˙)੭━☆゚.*・。゚ s:put nl ;

state-init welcome-player hint
[ s:get turn TRUE ] while

That is it! That is a wicked minimal text based game thing!

Here is a link to a cleaned up version of the same code.

Follow up

For more on Forth and Retro, check out these links:


Hold it!?” you may have shouted — “why the heck should I care about Forth when I’ve got Swift, TypeScript and JAVA!?”

Touche. Those languages are wickedly more powerful and … dare I say … pragmatic than Forth. Even if you don’t plan to write a heap of Forth, not a single line even, I think Forth is worth learning a bit about. Forth is built around the core concept of a stack. Stacks aren’t unique to Forth; they are everywhere. Understanding how to leverage the stack can be really useful. I’ve found my love of Forth has made it easier for me to groke how C and Lua inter-op. When you call a Lua function from C you don’t pass the parameters into the function directly, first you push the Lua function on to the stack, then any parameters that the function takes. Then you call the function and tell C what items from the stack to pop into it. Stacks and stacks!

Another point I wasn’t able to sneak in up above is that most Forth systems (with some exceptions) have very minimal supporting ecosystems. This is a blessing and a curse. A curse because if you want to do a thing you probably have to do that thing fully or near-to-fully on your own. A blessing because there isn’t a package manager or a complicated build tool to get eaten by. Heck! I don’t even use syntax highlighting when I write Forth most of the time.

In reply to: Code Log: In which I explore how to make sounds

A few posts ago I set out to make a program that could bleep and bloop. I was met with middling success.

But then, while noodling with GUI programming in Racket, I stumbled across this excellent post, Learn Racket by Example: GUI Programming, that walks you through how to create a program that bleeps and bloops!

BEHOLD!

Screenshot of a small program, filled with sliders and emoji that bleep and bloop!

With this tiny program I’m able to bleep and bloop in style! Not heaps useful, but heaps fun.

Here is a link to my take on the walk through.

Code Log: In which I explore how to make sounds

What is a code log? It is sort of like live coding, but badly done, and all in text.

I’ve drawn a heap of pixels to the screen over the years, but I haven’t made computers bleep or bloop much. Here are some notes on some quick audio explorations.

Git gets grumpy

First, a quick aside on a silly error I ran into trying to clone a git repo.

git@codeberg.org: Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights

Despite having everything properly configured in my ~/.ssh/config git wasn’t playing nice with Codeberg. To remedy I ran:

ssh-add ~/.ssh/my_awesome_key

Where my_awesome_key was the name of my private key.

This ensures that your ssh-agent is aware of the specific key you are trying to use.

Goals

My goal is to either emit 1 or more tones or to generate some sort of white noise. Ideally I would like the running program to play the audio directly, but I’d also settle for the program writing a file that can then play the audio.

The silliest/simplest way that I know of to make my computer bloop is to send the \a escape sequence to stdout. \a is the escape sequence for alert, so, at least on macOS, it triggers the OS-level alert sound.

echo -e '\a'

This, however, is lame and feels like cheating.

I know that in order to make my computer bleep and bloop I will need access to its audio APIs. Some quick searching surfaced a variety of ways to do this — the ones I’m most familiar with leverage game engines, tools like löve2d. Those also sort of feel like cheating. So, I continue onward!

A brief sampling of some stuff found in quick research mode

Hissssssss 🐍

Python is a language I don’t often use…nor have I ever really done so. It isn’t my go-to hammer. But this page brought me to simpleaudio.

After doing a quick pip3 install simpleaudio I got the sample to play what sounds like a piano!

import simpleaudio.functionchecks as fc

fc.LeftRightCheck.run()

This is very promising!

Tragedy!

Next up I tried to run one of simpleaudios more involved examples that leverages numpy, a dependency I know nothing about 🤷…and that threw the following error:

Traceback (most recent call last):
  File "/Users/eli/Desktop/noise.py", line 12, in <module>
      t = np.linspace(0, T, T * sample_rate, False)
      File "<__array_function__ internals>", line 5, in linspace
      File "/usr/local/lib/python3.9/site-packages/numpy/core/function_base.py", line 120, in linspace
  num = operator.index(num)
  TypeError: 'float' object cannot be interpreted as an integer

I MUST REMAIN FOCUSED ON THE BLOOPS! Gonna nope out of this!

WWW Audio API

If I resemble any sort of developer it is a web developer; the web calls to me!

Web Audio API 🏇

Researching the Web Audio API brought me to this excellent demo. An excellent demo that didn’t work in my default browser!? This was devastating and led me to realize (perhaps (read as most assuredly”) falsely?) that Web Audio API feels like a cheat because it isn’t my computer beeping, it is my computer as dictated by my browser…this is an absurd reason for giving up on it, but it was my reason nonetheless.

Next I took a brief excursion into ASM, thinking that if I want my computer to really bleep and bloop I gotta do it in assembly…but…wooof. It’d be rad., but perhaps not for this code log.

Diversions and sleepiness set in

With the wanderings into ASM complete I was getting sleepy — desperate times and all that — my measure became desperate.

Rather than play the tone directly what about writing it to a file and playing that back? Here is a post about generating a .wav file in C, and another post from the same source about doing it in Python.

Promising, but a bit complex.

Cheats abound

It isn’t #programming, but I know I can generate a very simple .wav file with ffmpeg!

ffmpeg -f lavfi -i "sine=frequency=1000:duration=5" beeeeep.wav

Or, a lower tone

ffmpeg -f lavfi -i "sine=frequency=800:duration=5" bloooop.wav

In conclusion

I didn’t 100%, or even really 60% accomplish exactly what I set out to initially, but for my inaugural code log I’m willing to call this a success.

Some other programs I’d like to explore more related to computer audio:


EDIT: Don’t miss the follow up post!