MiniKast

MiniKast is a simple language used as intermediate compilation step when compiling Kast code.

Not really intended to be written by humans.

Compared to Kast

Overview

MiniKast program has following top-level items:

Unlike Kast, in MiniKast these must be specified at top level.

The order should not matter.

Builtin types

Defining custom types

const MyType = <type definition>;

Structs

const MyStruct = struct {
    .a :: Int,
    .b :: String,
};

const main = () => (
    let foo :: MyStruct = {
        .a = 123,
        .b = "Hello",
    };
    let a :: Int32 = foo.a;
    let b :: String = foo.b;
);

Enums

Enumerated types with no extra data

const MyEnum = enum {
    | :Variant1
    | :Variant2
    | :LastVariant
};

const MyEnum_Variant1 = () -> MyEnum => (
    :Variant1
);

const main = () => (
    let my_enum :: MyEnum = :Variant1;
    # == only works for enums (TODO might change it to `is` keyword)
    if my_enum == :Variant2 then ();
);

Unions

Same as structs but all members share same memory (only single member is supposed to be used)

const MyUnion = struct {
    .a :: Int,
    .b :: String,
};

const main = () => (
    let foo :: MyUnion = { .a = value };
    let a = foo.a;
    # let b = foo.b; # This is undefined behavior!!!
)

Native types

You can specify types already present in the target (for things like C interop):

@native
const Texture = @opaque_type;

Type aliases

const AliasToInt = type Int;

Functions

Defining top-level function:

const fib = (n :: Int) -> Int => (
    if less[Int](n, 2) then (
        1
    ) else (
        add[Int](fib(sub[Int](n, 1)), fib(sub[Int](n, 2)))
    )
);

MiniKast also supports closures:

let b :: Int = 456;
# construct a closure (b is going to be captured by reference by default)
let add_b = (a :: Int) -> Int => (
    # native exprs allow embedding target-specific code
    @native "\(a) + \(b)" # we can use interpolations in native exprs
);

Templates

Templates are top level items which can have any amount of type arguments.

const GenericStruct = [T] {
    .field :: T,
};

const sqr = [T] (arg :: T) -> T => (
    mul[T](arg, arg)
);

Templates are not type checked until instantiated:

let four :: Int = sqr[Int](2);
let nine :: Float32 = sqr[Float32](3);

Expressions

Like Kast, MiniKast is an expression based language - there is no distinction between statements and expressions.

let unit :: () = ();
let x :: Int = uninitialized; # Must explicitly say is we want data to be uninitialized
let ref_to_x :: &Int = &x;
ref_to_x^ = 10;

let s :: String = if true then (
    "condition was true"
) else (
    "condition was false"
);

let list :: List[Int] = [1, 2, 3, 4, 5];

# only infinite loops are present
@loop (
    some_function(arg1, arg2, arg3); # calling a fn
);

# in order to break/continue loops, return from fns or similar use unwinding
let result :: Int = unwindable main_loop (
    @loop (
        if need_to_break then (
            unwind main_loop with 123;
        );
    );
    456 # if control flow reached the end of unwindable block, this would be result
);

Defer

defer keyword can be used to run code at the end of the current scope

let x = 1;
(
    defer (
        x = 2;
    );
    x = 3;
    print(x); # prints 3
);
print(x); # prints 2

Context system

MiniKast, same as Kast has implicit context system. Unlike Kast it is not checked at compile time and should be used carefully.

const MyContext = @context Int; # specify the context

const f = () => (
    let current_value = @current MyContext; # This is UB if MyContext was not initialied yet
);

const main = () => (
    (
        with MyContext = 123; # provide value for context for duration of the scope
        f();
    );
    # f() # this would result in UB
);

Every context is becoming a field in the implicit @context object which can be referred to explicitly. Techincally @context is stored behind a reference, and each function (default @call convention) has implicit argument - reference to current @context.

# save current implicit context state
let saved_context :: @context = @context;
# since implicit context is technically stored as reference,
# we can assign to that reference (might change?)
let &@context = saved_context;
# this provides a way to introduce context system from scratch
# (like in `@call "C"` functions, to be able to call regular functions)
let &@context = uninitialized;
@context.MyContext = 123;

Compilation Targets

MiniKast is supposed to be target indenpendent. Currently there is:

Other targets are planned in the future

Example code

If you want to see example code, check out this simple game made for Ludum Dare 59:

https://github.com/kuviman/megahonk

Uses raylib, transpiles to C and works in browser via emscripten.