← Back to articles

Building a programming lang, other updates.

2024-06-14 · 5 min read

Or: in which JC builds a programming language based on everything she wants to learn, but with the completely unrealistic deadline of three weeks.

But back to writing I am! Gosh, I haven't done this in what, six months? I think it's just out of striving for perfection, I need to stop doing that here.

I'd like to build an actual programming language. I've tried my hands at writing a few tree-walk interpreters, but I'd like to write a serious programming language now. Ok, not serious serious, but something I can see myself using to write hobby programs here and there, ya know?

First, a name: burly. An tribute to the (also first) place I've lived independently for almost a whole year now, that I'll be leaving so so soon. (See thoughts.)

Here are the few things I jotted down over the last few hours:

  • Compiler in C → should go directly to an executable that runs on the given system. Should run on my Mac with brew install burly. Everything will be bundled into one executable when compiled, there'll be automatic garbage collection, etc.
  • Built-in type system with generics. Default primitive types: nums, strs, bools. Structural types based on the primitive types: maps, arrays, and classes, which means OOP will be involved, of course. Do some research into types in other programming languages. Tempted to do what JavaScript does and represent nearly everything as a instance of some default Object class.
  • A good starting point for a package maanger. I'm thinking implementing something similar to ES6 import/export syntax.
  • Good standard library. Good starting points: cli; date; env; fmt; fs; http; json; io; md; path; random.
  • Usable REPL. I decided to start with a tree-walk interpreter, so maybe that'll be part of the standard library and the REPL will be based off that, or I'll compile off the fly. Not particularly sure about this one.
  • A default code formatter. Finally, I get to enforce my desire of the two-spaced tab look.
  • If I have time, I'd like to do more things: set up a testing module; and research and implement optimizations on them too, here. Instead of doing a single-pass compiler, it would be so cool to figure out something like JIT works. Don't know if I'll get there in the proposed timeframe, though.

I imagine the syntax would look something like this:

use { http } from "std/http";
use { env } from "std/env";
use { log } from "std/io";
use { Date } from "std/date";

var opts: Map<string, string> = env.load(".env");
var port: num = num(opts.get("PORT") || 3000);

var router: http.Router = new http.Router();

class CustomLogger {
  static fn log(msg) {
    for i : range(0, msg.length()) {
      output("[{}] {}", Date.now(), msg[i]);
    }

    -- or, an alternative
    -- for i : msg {
    --    output("[{}] {}", Date.now(), i);
    -- }
  } 
}

router.get("/", fn (...props) {
  CustomLogger.log("GET request made to /");
  var res: http.Response = props.response;
  res.send("Hello, World! Bye, Burly!");
})

router.listen(port, fn () {
  log("Listening on port ", str(num));
})
burly router.burly -o router

> Compiled successfully to ./router.

chmod +x ./router
./router

> Listening on port 3000

burly

> use math from "std/math";
> use { random } from "std/random";
> use { input, output } from "std/io";
> var game = fn (max: number=100, strikes: number=10) {
    var correct = random(0, max, fn (num) { 
      return math.round(num)
    });
    var strike: num = 1;
    var guess: num = num(input("I'm thinking of a number between 0 and {}. What's your guess? ", max)); -- should this kind of type deduction be taken care of?
    while (guess != correct) {
      if (guess < correct) {
        strike = strike + 1;
        output("My number is bigger than that. You have {} tries left.", strikes - strike);
      } else if (guess > correct) {
        strike = strike + 1;
        output("My number is smaller than that. You have {} tries left.", strikes - strike);
      } else {
        output("That's correct! You win.");
        break;
      }

      strike = strike + 1;
      if (strike == strikes) {
        output("You ran out of tries! The correct number was {}.", correct);
        break;
      }

      guess = num(input("I'm thinking of a number between 0 and {}. What's your guess? ", max));
    }
  }
> game()

^Some of the above might be missing types that should go there, this is just a thought concept. I do plan on implementing automatic deduction of types.

When I'm done with this, I'd like to take it out for a spin by writing the documentation site in the language (wow! actual documentation) and a clone of Letterloop with friends. If I don't finish 80% of this and throw it aside and completely forget about it the second would be amazing. Writing something in your own programming language with other people has to be insane.

Implementation

Maybe I'll do this the dumb way and start from a tree-walk interpreter with no types to a compiler with types. Let's do it!


Life updates!

I'm thinking about a few things, and I apologize for being particularly vague here but:

  • Two more months. Art. Gap year. Enjoy two more months. Figure it out.
  • Figure out a happy medium between worrying about the future and living in the moment.

I got into Recurse Center and I'll be doing the fall batch instead than what I originally wanted to do. Sometimes things don't always go your way.