Commentary

Parameterized types (generics)

Because type paramters work at compile-time and not runtime, we need a better syntax for them. Ki uses @ for compile-time info, so something like:

type StringArray(array(@type: string)) {}

Specifying these in a struct works like this:

struct GameStruct(@human_type, @monster_type, @field_type) {
    var human: @human_type
    var monster: @monster_type
    var field: @field_type
}

:fields works the same way:

type Game(@human_type, @monster_type, @field_type) {
    :fields {
        var human: @human_type
        var monster: @monster_type
        var field: @field_type
    }
}

Parameterizing a type:

struct GameStruct(@human_type, @monster_type, @field_type) {
    var human: @human_type
    var monster: @monster_type
    var field: @field_type
}

type Game(GameStruct(@human_type: Human, @monster_type: Monster, @field_type: Field)) {}
array GameArray(@type: Game)

This is only for parameterizing though. If you want to build a type on a base type without parameterization you have to use the full syntax:

uint Counter {} # Bad
type Counter(uint) {} # Good

@globals

I think @globals can't be a thing. Unless there's some special function that initializes it before main runs, it will have uninitialized fields. I guess we could require with to access them, but fuck that's terrible.

range

Empty ranges are a thing:

fn main() {
    range indices: [0..0)
}

Naming

I think maybe the higher-level types (array, dynarray, string, dynstring, thread) should be capitalized. Scalars can remain lowercased.

Returning Values vs. Pointers

Unlike C, Ki functions can return values. This is what that looks like in C:

void build_person(Person *p) {
    p->name = "Charlie";
    p->age = 33;
}

int main(void) {
    Person p;

    build_person(&p);
}

Here, p is an output parameter to build_person. So essentially, the calling function pre-allocates the data somehow, then always passes a pointer to it as an output parameter to the called function.

OK, that's fine. Errors work... similarly?

void build_person(Person **p, Error *e) {
    *p = malloc(sizeof(Person))
    if (!(*p)) {
        e->occurred = true;
        e->msg = "Failed to allocate memory";
        e->code = 10;
        return;
    }
    p->name = "Charlie";
    p->age = 33;
}

int main(void) {
    Person *p = NULL;
    Error e = { 0 };

    build_person(&person, &e);

    if (e.occurred) {
        fprintf(stderr, e.msg);
        exit(e.code);
    }
}

With error handlers:

void build_person(Person **p, Error *e) {
    *p = malloc(sizeof(Person))
    if (!(*p)) {
        e->occurred = true;
        e->type = ERROR_TYPE_ALLOCATION_FAILURE;
        e->msg = "Failed to allocate memory";
        e->code = 10;
        return;
    }
    p->name = "Charlie";
    p->age = 33;
}

int main(void) {
    Person *p = NULL;
    Error e = { 0 };

    build_person(&person, &e);

    if (e.occurred) {
        ErrorHandler *eh = error_lookup_handler(e.type);
        if (eh) {
            eh(&e);
        }
        else {
            fprintf(stderr, e.msg);
            exit(e.code);
        }
    }
}