Pulse
Pulse is an “event driven” (as opposed to “food motivated” or “functional”) programming language that I designed and implemented over the course of a week. If you just wanna play around with it, then go here.
Some backstory
I was 19 when I took my first crack at writing an interpreter, and like most people who wonder about programming languages, I found Bob Nystrom’s awesome book.
My plan was to follow along with the book, and spice it up a little bit. Instead of Java, I’d follow along in C++, and rather than implement Lox (the language described in the book), I would implement my own language, which I called Proxi. I figured I could work out the changes I’d need to make as I coded along, and that by the end, we’d have two different languages.
So what was Proxi?
I designed Proxi by looking at Lox and mixing it with a 100000:1 homeopathic drop of whatever it is I liked about C++.
The result was a weird, derivative mess of a language. It offered no new or interesting features, and all the stuff I changed from Lox just made it harder to follow the book.
After tinkering with Proxi for a couple weeks, and dealing with the fallout of starting college during a pandemic, I put the project away.
I forget how far I got, and I don’t want to skim the repo for fear of some lingering, adolescent residue. But I think I tapped out once it got time to wire up the more complicated parts of the treewalk interpreter.
In other words, not very far.
Couple years later
Proxi lingered in my mind for a while.
It wasn’t unique in the graveyard of unfinished things, but it is a project I was fond of. I tried tackling it at a time when I really started to grow as a programmer. And also, I was 19. Everything I did at that age gets a buff in terms of perceived importance.
But mostly I was uncomfortable that I never finished it.
I’d always felt that the things I made for myself were inherently more meaningful and better in some abstract way, than the things I made for work. But after doing a mental catalog of the projects I’d actually finished, you’d think it was a corporate only event.
I wanted to finish something for myself. And programming languages are cool. So I decided I’d try writing an interpreter again.
But what language would I write it for? Proxi was out of the question- A quick skim of the design document reminded me of all its shortcomings. And I didn’t want to implement Lox. I mean, I’ve already copied Bob enough. One look at his blog and you’ll see how far I’ve stretched the term “artistic inspiration”.
But most importantly, the language needed to be weird and it needed to be small. Weird, because esolangs are fun, and small, because I actually wanted to finish it.
I figured if it was something I could bang out in a week or two, then there was no chance I’d lose steam before getting it done.
There was also a third requirement. It needed to be mine. I didn’t want to paint by numbers someone else’s baby. I wanted my own.
So I got to designing.
The Design
I opened up notepad and imagined the simplest procedural language I could think of.
I knew I wanted variables.
var lookAVariable = 100;
and um… that was kind of it?
I decided early on that Pulse would have no traditional control flow, and so I waved goodbye to if and while and for.
And functions and OOP were definitely out of scope given the time limit and my newbie-ness; also they’re overrated.
So all I had was something like this.
var lookAVariable;
lookAVariable = 0;
// pretend the syntax highlighting is working here
// v
print lookAVariable + 100;
// plus other arithmetic and logical operators
Basically a glorified calculator.
Now this was fine, and totally doable in my time limit. But it wasn’t very interesting, and it wasn’t really a programming language in the way I had imagined. So I kept thinking.
I started thinking about callbacks and signals and stuff and imagined a language where every line of code needed to be triggered by a named event.
This was mildly more interesting, and I got something like this.
var foo = "hey" : _bar; // not actually processed
print foo : _bar; // foo doesn't exist or get printed
emit _bar; // until here
var foo = "hey"; and print foo; are not actually executed at first. Rather, the statements are created and subscribed to the _bar event.
Only when the interpreter reaches the emit _bar; line does it execute all statements subscribed to _bar, and those lines finally run.
This was cool, but still didn’t really solve my issue. Choosing which events to emit was not that different to what I had before.
var foo = "a" : _this;
print foo : _this;
var bar = "b" : _that;
print bar : _that;
emit _that;
Is equivalent to:
var bar = "b";
print bar;
And adding more emit statements would just be a quicker way to write out blocks of code.
What I really needed was a way to decide whether certain bits of code would run or not. I needed branching.
var foo = "a" : _this;
print foo : _this;
emit _this ? some_condition;
// ^ feast your eyes
Here, _this is only emitted if some_condition evaluates to true (anything non-zero). some_condition can be any sort of expression. It can be a variable, inequality, whatever you’d expect. Control flow is back on the menu.
The ?? syntax follows naturally from ?. Instead of firing once if the condition is true, it keeps firing until it’s false.
print counter : count_up;
counter = counter + 1 : count_up;
emit count_up ?? counter <= 5;
That’s a loop.
Now it was almost done. But there was one thing missing. Arrays, or lists, or whatever. Now I’m not actually sure if this was necessary to push it into the realm of Turing Completeness, but it felt like a safe bet to make.
Arrays work similarly to everything else, but with different declaration syntax and a push operator.
var[] scores : init;
scores <- 10 : init;
scores <- 20 : init;
scores <- "hi": init; // heterogenous values!
emit setup;
var[] declares an empty list, and <- pushes a value into it. Access and modification work how you’d expect.
var temp = scores[1] : read;
scores[1] = 99 : write;
The last notable thing to talk about are the emit statements themselves.
They’re interesting in that they’re the only statements that don’t need to be subscribed to an event in order to be executed. But, that doesn’t necessarily mean they can’t be subscribed to an event.
Here’s a simple example.
counter = counter + 1 : increment;
print counter : log;
emit log : increment;
emit increment;
Here, emit log will not be executed on the first pass. Instead it will be subscribed to the increment event. When increment fires, it bumps the counter and then triggers log, which prints it. The print is a side effect of the increment event, not a direct call. This is a bit of a contrived example, but this sort of event chaining is critical when trying to do anything complicated in Pulse.
The Name
After throwing it all together I needed a name. Part of me was tempted to resurrect Proxi, but that didn’t really fit or feel right.
I eventually settled on Pulse (keeping the P from Proxi) as I felt like it worked nicely with the idea of signals/events being emitted and listened to.
Implementing the interpreter took about a week thanks everything I learned in the aforementioned awesome book. And after officially naming it, it’s now done.
Proxi never came to life, but at least something else has a Pulse (wink wink wink nudge wink ooh yeah).
On Turing completeness.
Now a question I keep coming back to is:
“is Pulse Turing Complete?”
And that, I can answer with a resounding “I don’t know”.
I think it’s TC. It’s got arbitrary memory, branching, and loops. But really, I don’t know. Statements subscribe to events at parse time, not dynamically at runtime so the event graph is fixed before execution.
And I think that might limit Pulse in subtle ways, but I’m not sure. So far, I’ve been able to implement any little algorithm I can think of. As long as my head doesn’t start hurting.
If you wanna take a crack at writing your own Pulse scripts then go here!
Or just use the interpreter below.
>