Skip to content

Instantly share code, notes, and snippets.

@milesrout
Last active August 25, 2017 12:13
Show Gist options
  • Select an option

  • Save milesrout/ae27257352354204f9ca to your computer and use it in GitHub Desktop.

Select an option

Save milesrout/ae27257352354204f9ca to your computer and use it in GitHub Desktop.

Chalice:

Chalice is a C-like language that attempts to be modern without being "modern". Without having to worry about backwards compatibility with the legacy of C, Chalice has an opportunity to clean up the language significantly, removing many aspects from the language that are considered to have been mistakes, but without removing the aspects of C that make it such a strong, simple, easy-to-use language.

The syntax of Chalice is intentionally very similar to C. Chalice does not have classes or methods. Chalice does not have references (neither Java-style nor C++-style). Chalice is pass-by-value, like C. That isn't to say that Chalice is identical to C. Chalice doesn't have a carcinogenic declaration syntax. Chalice is designed to be able to be parsed using a single token of lookahead, and compiled in a single pass.

Chalice takes a lot of inspiration from the Linux kernel coding style guidelines. For example, spaces cannot be used as indentation. Trailing whitespace is a compiler error. Chalice takes inspiration from Go in consistently rejecting aesthetically displeasing code (and in some other areas).

Chalice does not take inspiration from C's braindead module/header system, nor its unbelievably awful preprocessor (which are really the same thing). Nor does it mix up booleans, integers and error codes (which are all different, incompatible types).

Some code examples follow.

The standard type stringbuf, defined in core/string, is a length-prefixed character buffer. It consists of some bytes representing the length of the string followed by that many bytes. This is intended for use with UTF-8-encoded strings. This is a big heavy variable-sized type. The length is encoded as in 1.

The standard type string, also defined in core/string, is an owning reference to a stringbuf. Some of its operations can or will reallocate the stringbuf. These are clearly documented.

The standard type str, also defined in core/string, is a non-owning view into an existing string. A str is fixed in size - just two memory addresses - and very cheap to copy, but you cannot add to it, for example.

Note the correspondence between C's T *t1, *t2; and Chalice's T* t1, t2;. They mean the same thing.

typedef struct {
	char* first, last;
} str;

One of the most unfortunate and confusing parts of C is that int[] means different things in different places. It means an array in some places, and a pointer in others. Here, arr1 is of type int* and arr2 is of type int[3].

// C
int foo(int arr1[3]) {
	int arr2[3];
}

In Chalice, this looks very different. You cannot have arrays as arguments or return values of functions, just like in C, but Chalice also don't have this implicit conversion of arrays to pointers-into-arrays.

// compiler error errors abound!
uint16[3] foo(uint16[3] arr1) {
	uint16[3] arr2;
	return arr2;
}

The above code gives a compiler error - you cannot have arrays as function parameters. The correct way to write this is as follows:

int foo(int* first, int* last) {
	uint16[256] buf;
}

In the same way, Chalice doesn't allow you to use arrays implicitly as pointers to their first element. Arrays aren't first class objects, but they don't pretend to be first class either.

// C
int foo(int *x);

int bar() {
	int y[256];
	foo(y);
}

The above behaviour doesn't seem too bad to experience C programmers, but actually doesn't make much sense when you think about it. Imagine if you could do this:

// HYPOTHETICAL C
struct blah {
	int x;
	float y;
};

int foo(int *n);

int bar() {
	struct blah b;
	foo(b);
}

Imagine if, when you wrote this, C just passed &b.x to the function. Madness! Yet passing &arr[0] to a function makes sense? Silly C.

How Chalice does this:

int foo(float p[usize n]);

int bar() {
	float[3] p;
	foo(p);
}

That is, the way Chalice does this is to provide simple syntactic sugar for this parameter passing. float p[usize n] is a bundle of both a float *p and a usize n argument, but importantly it ties them together! You can still pass them separately by using named parameters, writing foo(p=p, n=256) but this is so explicit that you can't really do it accidentally. If you just pass in p, without passing in n (which can only be done using a named parameter), n will be calculated for you. If it can't be calculated then that is a compile-time error.

Chalice modules:

Modules in Chalice are one of its biggest distinguishing features when compared to C.

// mod.ch
module mod {
    int foo();
    void bar(int x);
};

// a.ch
int foo()
{
    return 1;
}

void bar(int x)
{
}

// b.ch
use mod;

int baz() {
    return mod->foo();
}

void quux(int x) {
    mod->bar(x);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment