Funk & lit ruins

I’ve been trying to create a mix of Lua and JavaScript for the past 5+ years. The resulting waste of time is called lit, and it is still not nowhere close enough in terms of stability to be used for anything but "hello worlds".

I burned out on it a ton. At this point, I don’t think I will ever continue working on it. I was dreaming of a day, when I will write about it here. Well, this day has arrived, but it’s not a praise to the language, but instead a tale of another language, born out of frustration with lit development.

A few weeks ago I opened twitter, and came across little, a tiny language, written by my friend @Beariish.

Extra bragging rights, his pfp is actually made by me, even tho I don’t like how it came out anymoar..

Little is actually pretty complex for it’s size (~2.5k lines of C):

I messaged Nik about it, and it turns out that he wrote it in a single weekend. My eyes lit up, and I decided I want to give language development another go. But seeing how I spent so many years trying to mimic, what others have done already, I wanted a challenge. I wanted to design a language, that didn’t look like others did.

And so I went about my day, hovering above some ideas. I thought about maybe making a language, that only has strings. I read about different unique languages, that look like a recipe, image or a poem. And then it hit me.

A language, with the only variable type being a.. function!
And so, funk was born.

The basic concept

So most languages have strings, numbers, arrays and some type of object/map. At first, I thought that I would just implement strings, that actually just use function names as the base, and call it a day. But as I started working on the language, I figured out a way to implement array and map-like data, and friends suggested to use roman literals for numbers. So I would say, that funk is a pretty usable language, even if it’s not too easy to write in it.

And now I will be lazy, and quote the README here:
Here is a ‘Hello, world’, written in funk:

// ascii 44 (XLIV) is ,
// ascii 33 (XXXIII) is !
print(join(Hello, char(XLIV), space(), world, char(XXXIII)))

Looks pretty complex, huh? Let’s unpack it step-by-step.
First of all, funk has functions, that look just like JavaScript:

function greet() {
    print(hello)
}

greet()

Seems simple enough, but what is hello? Is it a string? Didn’t I tell you, that this language has only functions? Well, you see, hello is actually the opposite of an anonymous function: it has a name, but it has no code/body. And since, like in JS, you can pass functions as arguments, here we just pass function hello as an argument to print. We didn’t define hello? No problem, the language will do it for us, creating it at runtime, but it won’t be defined globally, so you won’t be able to call hello() from anywhere in the code, like you would be able to call that sweet sweet greet function, we just created.

Okay, but what is XLIV you may ask? These are roman literals. Since function names can’t start with a digit (tho, they can with a minus or they can have a dot inside of their name…), funk uses roman literals for representing numbers. For example, X is 10, and XLIV is 44. What about 0? It’s NULLA.

And by calling the function char with argument XLIV (44), we create a function named ,. Wild, I know. Same story happens with XXXIII, that becomes a function called !, and space() returns a function named . Then all of these functions are passed as arguments to join(), that creates a new function with the name consisting from all of these arguments combined, and finally, it is all printed…

Phew.

And if you don’t really like debugging with output looking something like MMMCMXCIX, there is a handy function, that outputs an actual human-readable number to the terminal:

printNumber(MMMCMXCIX) // 3999

Obviously, this is not a very easy language to use. It is far from being as unusable, as BF, but still even working with numbers gets pretty complex fast… (for example, 2 + 2 * 2 is add(2, multiply(2, 2))).

And here is a BF interpreter in funk:

set(myFile, file(join(demos, separator(), hello, char(XCV), world, dot(), bf)))
set(code, myFile())
set(memory, array())
set(variable(pointer), NULLA)
set(braces, array())
set(variable(ip), NULLA)
set(codeLength, length(code))
set(rightBrace, char(XCIII))

for(NULLA, CCC, () => push(memory, NULLA))

function getPointer() {
    return get(variable(pointer))
}

function getIp() {
    return get(variable(ip))
}

function incrementIp() {
    set(variable(ip), add(getIp(), I))
}

set(commands, map(
    // .
    dot(), () => printChar(memory(getPointer())),
    // ,
    char(XLIV), () => memory(getPointer(), substring(readLine(), NULLA)),
    // <
    char(LX), () => set(variable(pointer), subtract(getPointer(), I)),
    // >
    char(LXII), () => set(variable(pointer), add(getPointer(), I)),
    // +
    char(XLIII), () => memory(getPointer(), add(memory(getPointer()), I)),
    // -
    char(XLV), () => memory(getPointer(), subtract(memory(getPointer()), I)),
    // [
    char(XCI), {
        if(equal(memory(getPointer()), NULLA), {
            while(() => and(notEqual(substring(code, getIp()), rightBrace), lessEqual(getIp(), codeLength)), {
                incrementIp()
            })
        }, () => push(braces, getIp()))
    },
    // ]
    rightBrace, {
        if(equal(length(braces), NULLA), incrementIp, {
            set(variable(ip), subtract(pop(braces), I))
        })
    }
))

while(() => less(getIp(), codeLength), {
    set(command, commands(substring(code, getIp())))
    if(notNull(command), command)

    incrementIp()
})

If you want to understand the code more, please go and read the rest of the README, and I will continue writing original content here.

The internals

I wanted to write it as simple and as fast as possible, so I didn’t bother with compiling anything to AST. The code is being read token-by-token, then parsed using a very primitive parser and emitted straight into bytecode.

And even the VM is insanely basic. It has only 9 instructions:

// Pops a value from the stack and returns it
FUNK_INSTRUCTION_RETURN,
// Calls the value stackTop - argCount - 1
FUNK_INSTRUCTION_CALL,
// Searches for a function with provided name (if not found, returns NULL)
FUNK_INSTRUCTION_GET,
// Searches for a function with provided name (if not found, creates an empty one)
FUNK_INSTRUCTION_GET_STRING,
// Pops a single value of the stack
FUNK_INSTRUCTION_POP,
// Defines a function in the current scope
FUNK_INSTRUCTION_DEFINE,
// Defines a function globaly
FUNK_INSTRUCTION_DEFINE_GLOBAL,
// Pushes a NULL to the stack
FUNK_INSTRUCTION_PUSH_NULL,
// Pushes a function onto the stack, literally only used for lambdas
FUNK_INSTRUCTION_PUSH_CONSTANT

The compiler is around 1.5k lines of C code, plus the standard library.
And actually, it is the standard library, that does most of the heavy lifting. It handles all the basic operations, that lit implemented as instructions, such as number addition & stuff like that.

Even the garbage collector is as simple as it gets. It doesn’t count bytes allocated, so it never knows when to run, so it is only runs if you call it, and that means we get 0 annoying GC related bugs, when it runs at random times and deletes chunks of memory we didn’t root anywhere.

I had some tough time converting roman numbers to double and back, the implementation is not the cleanest and definitely not bug-free, but it works good enough for me.

The whole project took around a week, and I’m calling it complete. It has a ton of room for improvements, I’ve listed some of the more obvious one in the README (seriously, go read it). I don’t think I will ever touch it again or use it for anything, but it was a fun exercise, something to flex my muscles at, and finally, finally, a project, that got me programming for 8 hours straight without a break. I’ve not been this motivated for the past 2+ years. Really missed it.

Hopefully, this project would be helpful to someone to try and implement some basic features on top, it’s a great playground to learn language development. And here I am also hoping, that it won’t be the last project in the near future, that I finish.

Cheers, Egor.

You might also find interesting

Join the discussion!

Leave a Reply to eügor

Cancel
ooichu 1 year ago
Hi Egor, I too had the feeling that what I had been working on for so long had already been done by someone in one weekend. It was fe (https://github.com/rxi/fe) from rxi. I was trying to create an embeddable interpreted homoiconic language that was fast enough and easy to implement (minimalistic, but not toyish). At one point I felt that I had spent quite a lot of time and now I had to do something really cool to make up for the time spent and was stuck for about 2 years in experimentation with things that were far beyond the scope of the project.
Reply
eügor 1 year ago

Yes, I've seen fe, and other stuff rxi has made, he has been a huge inspiration for me for a long while. I would say, that fe is not his first language for sure tho, it't came from a bunch of experience with languages before. Actually rxi & bearish are good friends, and his stuff was inspired a lot by rxi's work, and so you could see the direct line of inspiration from rxi's work here too. In any case, I wish you luck with your projects, and I guess yeah, sticking to a smaller scope is always better, especially when you work alone. I've learned that again and again with game development, that you better finish something very tiny at it's core and expand it a bit later on, than have a huge core and never finish it..

Reply
ooichu 1 year ago
Thanks! Good luck with your projects, too!
Reply