Reimplementing Lime in C

In my quest for minimalism, I decided I needed to stop carrying around two laptops---a Linux one that I used for developing Lime, and a MacOS one iOS development (and schoolwork). Turns out all of the Lime code I wrote used 32 bit assembler. The Mach kernel doesn't support 32-bit addressing, rendering all of that code unusable. Instead of emulating, I decided to totally revamp and rewrite the Lime language from scratch, this time starting in C instead of in Assembler. I realized that a lot of the work I put in developing an Assembler version focused on small-scale operations that gcc/clang would know how to do better than I could. structs and formal data types also mean that I have to spend less time developing a typing system and reimplementing basic operations, and more time actually working on interesting language features.

I took my time with implementing this, and came up with a pretty neat implementation, still utilizing my original stack-based design for the language.

I first implemented a basic stack as an array of longs. Integers are stored as long values, while strings/floats/etc.'s pointers are stored.

I then created a struct for a lambda function with partial application support:

typedef void (*func)();

typedef struct {
       int resolved;
       func function;
       int args_left;
       long* args;
       int args_len;
} lambda;

resolved is 0 if more arguments must be supplied, 1 if it has been processed, applied to the arguments, and the args pointer has been free()d.

The Lime language outputs three basic instructions: push, pop, and resolve. push and pop mutate the stack, and resolve pops a lambda pointer off the stack and applies it to the supplied arguments. My implementation for resolve was:

void resolve(int n) {
       f = *(lambda*)pop();
       if (f.args_left == 0) {
              f.function();
              f.resolved = 1;
       }

       for (int i = 0; i < n; i++) {
              f = apply_lambda(f, pop());
       }
       if (!f.resolved) {
              push((long)&f);
              fp = malloc(sizeof(lambda));
              f = *fp;
       }
}

As a result, Lime compiler output, instead of being a mess of assembler, now looks like, from the following Lime code:

System Message: ERROR/3 (<string>, line 54)

Error in "code" directive: maximum 1 argument(s) allowed, 3 supplied.

.. code:: c
   #include "lib/lime.h"

   int add3() {
          int stack_base_ptr = stack_ptr;
          push((long)3);
          push((long)get_arg(0, stack_base_ptr));
          push(make_lambda((void (*)())iadd, 2));
          resolve(2);
          clear_args(1, stack_base_ptr);
          return 0;
   }

   int main() {
          int stack_base_ptr = stack_ptr;
          push((long)2);
          push((long)1);
          push(make_lambda((void (*)())iadd, 2));
          resolve(2);

          push(make_lambda((void (*)())add3, 1));
          resolve(1);

          push(make_lambda((void (*)())iprint, 1));
          resolve(1);
          clear_args(0, stack_base_ptr);
          return 0;
   }

It's still a bit messy, with lots of ugly casting code and meaningless int return values; but with some macro (and potentially preprocessor) magic it'll get even more understandable. Instead of needing to convert between type systems and calling conventions for Lime, it'll be pretty simple to build a compatability layer to interoperate C and Lime functions, so I can leverage C's standard library in the development and use of Lime.