Lake is a new programming language, meant to replace C for the author personally, as a kernel- and game-development tool, and also to be useful to other people in the process.
/* calling the C function because the standard library is not ready */
puts(_ []char) int;
pub main() int
{
puts("Hello, world!")
return 0;
}
Idea:
- Like C, simple and small
- Like C, fast compilation
- Like C, doesn't get in your way
- Like C, doesn't have too many strong opinions
- Like C, low- to mid-level
- Like C, everything is explicit
- Unlike C, easy to write safe code in (but like C, it's not mandatory)
- Unlike C, well-specified conventions
- Unlike C, extra convenience features (simple type inference, operator overloading, generics etc.)
- Easy to mix Lake and C in the same project
- Really, just "C, but better"
Features:
- Procedural (best for given problem domains)
- Operator overloading (useful mainly for games)
- Simple type inference, some structural typing and other convenience features
- Postfix type syntax (similar to Go but a bit shorter)
- Macros (for language extension, not constants)
- Explicit memory management (but the compiler checks that you didn't make any obvious mistake)
- Flexible memory management (stack allocation, (unimplemented, but not too far away) reference counting, arenas, garbage collection etc.)
- Well-specified and nice type names (u8/i16 type, plus int for a machine integer and long for a pointer integer)
- Better switch, with a replacement for fallthrough
- Full-blown pointers, with pointer arithmetic and no need for
unsafe
blocks goto
and other unsafe things- Parametric polymorphism (with no instancing of any sort)
- (WIP) Good standard library (Plan 9 libc-inspired)
Future features:
- Easier error management (return values, with a twist!)
- Proper modularity (with headers, but no global namespace pollution)
- Standard build system, to avoid proliferation of Make, autoconf, CMake, Meson, SCons, Waf etc.
The rationale behind it is that C is a pretty good language for what it was designed for (kernel development) and stood the test of time and abuse by many people well enough to still be widely used 50 years later. However, it's an old language, and wasn't designed with some of the hindsight and resources we have now. While it's still a pretty good language for kernel development, it would be nice to have some of the convenience that higher-level languages give without losing the explicitness and control that C gives. Lake is supposed to fill that gap.
C has a few advantages I think are essential to the language. Everything is explicit; no code runs without at least one character indicating it. Also, separate compilation is trivial (though its benefits can easily be defeated by a bad header design) and the type system is flexible, while the entire language remains pretty simple and easy to implement from scratch.
It does have plenty of issues though; the declaration syntax is weird,
pointer decay is not ideal, const
is completely useless, headers are
easy to get wrong, and varargs, the preprocessor and standard library are
all messy and quickly hacked-together solutions that aren't very good.
Let's see how other systems languages fare as "C replacements":
-
C++ has all of C's problems, and adds a few of its own, due to being very large. It adds many features for OOP, functional-like programming, that aren't really needed on these problem domains, but bloat the language and compilation time considerably, and mean every library has its own way of doing things, which makes it difficult to keep a consistent coding style.
-
D has a nice standard library, and adds OOP and other stuff too. However, the standard library relies pretty heavily on garbage collection and, while you can turn it off, the library becomes useless then. This is not too bad for kernels, which don't use it anyway, but it is really annoying for games, which generally need to be pretty aware of memory and resource usage. Also, several of its features end up going to waste on these problem domains.
-
Rust attempts to make a "safe" systems programming language. The result, while theoretically and technically impressive, is very inconvenient to use, often whining about perfectly safe code. Even though you can get used to it, some of its stricter safety rules end up making the programmer rely too much on compiler optimisations (I've had a program crash when it ran without optimisation, out of memory exhaustion, even though it was perfectly correct). It doesn't know whether it wants to be a high- or low-level language, and uses functional idioms for low level things, obscuring what is the real resource usage of the program, while forcing the programmer to constantly worry about memory safety.
-
Go doesn't even qualify; it has a garbage collector that can't be turned off (which is bad for kernels). While it is pretty speedy, and the language overall has some interesting design decisions (and was a major inspiraton for Lake), it has very strong opinions on some subjects, that some (and in particular, the author) might not agree with. Also, you aren't going to see kernels written in Go so soon; that's not what the language was designed for.
-
V is pretty similar to Go, except for the lack of a garbage collector; it also has strong opinions, just different ones.
This is not meant to say these languages are bad, only why not simply using one of these: they are not really good matches for the uses the author had in mind. All of them were inspirations for Lake at some point.
Because it is like C, but less salty (say that out aloud, if you didn't get it). Also, C++ and D were already taken, and P (next letter in BCPL) was taken by a completely unrelated language. Lake seems to be vacant at this point (hopefully).
The Lake compiler is based on the cproc
C compiler, since both languages
are rather similar.
The (WIP) documentation is available under doc/.
cproc
is a C11 compiler using QBE as a backend. It is released
under the ISC license.
It was inspired by several other small C compilers including 8cc, c, lacc, and scc.
The compiler itself is written in standard C11 and can be built with any conforming C11 compiler.
The POSIX driver depends on POSIX.1-2008 interfaces, and the Makefile
requires a POSIX-compatible make(1).
At runtime, you will need QBE, an assembler, and a linker for the target system. Currently, Michael Forney's personal QBE branch is recommended, since it may address some issues that have not yet made it upstream.
All architectures supported by QBE should work (currently x86_64 and aarch64).
Run ./configure
to create a config.h
and config.mk
appropriate for
your system. If your system is not supported by the configure script,
you can create these files manually. config.h
should define several
string arrays (static char *[]
):
startfiles
: Objects to pass to the linker at the beginning of the link command.endfiles
: Objects to pass to the linker at the end of the link command (including libc).codegencmd
: The QBE command, and possibly explicit target flags.assemblecmd
: The assembler command.linkcmd
: The linker command.
You may also want to customize your environment or config.mk
with the
appropriate CC
, CFLAGS
and LDFLAGS
.
If you don't have QBE installed, you can build it from the included submodule (NOTE: BSD users will need to use gmake here), then add it to your PATH so that the driver will be able to run it.
make qbe
PATH=$PWD/qbe/obj:$PATH
Once this is done, you can build with
make
Run make check
to run the automated regression suite. It has been
updated to run in Lake, and reading it is probably the best way to get
a feel for what the language is about, until proper documentation is
in place. It is available under test/*.lk
.