Caracal Documentation
The Caracal language reference.
Description
Caracal is an imperative, compiled language with a focus on simple and enjoyable syntax. It is designed to be statically typed while still feeling ergonomic through type inference.
Note: Syntax and semantics are still work-in-progress and may change.
Roadmap
The current focus is on getting a minimal version up and running. For that, I'm generating LLVM IR.
- Lexer: mostly done
- Parser: basic syntax available, advanced features in progress
- Typechecker: partially implemented
- Optimizer: not implemented yet
- Codegen: LLVM is already generating some native binaries
Constants
To declare constants in Caracal, we use similar syntax to parameters, except we use double colons ::. They can't be changed during runtime as the name implies. Es can be declared in global scope or function scope. The type is infered during compilation, but you can use an explicit type.
// infered type of OS
currentOS :: OS.Windows;
// explicit type of i32
year : i32 : 2026;
Variables
Variables are similar to constants but they can be changed during runtime. We declare them with :=. Unlike constants, variables can't be declared in global scope. We can also state the type explicitly, same as constants.
def getRandomNumber() i32
{
// chosen by fair dice roll.
// guaranteed to be random.
x := 4;
return x;
}
Functions
Functions use def keyword to declare functions. Parameters start with the name and then the type, they are immutable by default.
def square(x: i32)
{
return x * x;
}
We can also declare external C functions with the #extern annotation. This form also supports C's variadic parameters with .... The code within the braces is ignored.
#extern()
def puts(message: string) i32 {}
#extern()
def printf(message: string, ...) i32 {}
Enums
Enums in Caracal are scoped constants with the same type, they work similar to C/C++.
enum Values
{
First :: 1
Second :: 2
Third :: 3
}
s :: Values.Second;
The #step(n) annotation allows creating C-like enums where each member increases by n unless a value is manually assigned. If no annotation is used, enums default to step(1).
#step(10)
enum BuildStage
{
Lexer // 0
Parser // 10
Typechecker :: 50 // 50
Optimizer // 60
}
#flag creates bit-flag enums where each member is a power of two.
#flag
enum Permission
{
Read // 1 (2^0)
Write // 2 (2^1)
Execute // 4 (2^2)
}
Types
Types are the data objects of the language, they are similar to structs in other languages and can contain fields and methods. Members are accessed with a dot ., similar to this. in other languages.
type Two
{
one :: 1
def one() i32
{
return .one;
}
def value() i32
{
return .one() + .one();
}
}
Variants
Variants, also known as sum types or tagged unions, are Caracal's way to do polymorphism, they allow you to put different types behind a unified interface. They can be extended from multiple files.
Note: Syntax for variants isn't fully defined yet.
Control Flow
Return
Return works like in other languages, you can either specify what you want to return or just have it on it's own for functions that return nothing.
def five() i32
{
return 5;
}
def nothing()
{
return;
}
If
If works like in other languages, except that the parenthesis around the condition aren't required. You can still add them if you want tho.
def func() i32
{
// parenthesis around the condition are allowed but not required
if (false)
{
return 1;
}
else if true
{
return 2;
}
else
{
return 3;
}
}
While
While also works like in other languages, it loops until the condition isn't true anymore.
def stuff()
{
i := 0;
while i < 3
{
i = i + 1;
}
}
Skip
Skip works like continue in other languages, it skips the rest of the loop body and restarts the loop.
def stuff()
{
i := 0;
while i < 10
{
i = i + 1;
if i > 5 and i < 7
{
skip;
}
}
}
Break
Break works like in other languages, it breaks out of the loop and continues after the loop body.
def stuff()
{
i := 0;
while true
{
i = i + 1;
if i >= 10
break;
}
}
Trailing if
Caracal supports trailing if for return, break and skip statements. It is just syntax sugar but makes the code a bit nicer to read.
break if x > 10;
skip if i > 3;
return 0 if true;
vs
if x > 10
break;
if i > 3
skip;
if true
return 0;