Warning
Keel is under active development
Keel is a fast, statically-typed interpreted language that aims to combine Rust-like syntax with Python's ease-of-use.
Its goal is to provide a faster alternative to Python that sits closer to low-level languages while remaining accessible to a wide audience.
Website Try Keel in your browser
- Fast: ~2-10x faster than Python (benchmarks), with aggressive compile-time optimizations
- Familiar syntax: Rust-like, with Python's ease-of-use
- Statically typed, zero annotations: full type inference, static type checking, polymorphism
- FFI support: call C/dynamic libraries directly from Keel
- Built-in REPL
curl -fsSL https://raw.githubusercontent.com/horacehoff/keel/main/install.sh | shDownload the latest .zip from the releases page and add the binary to your PATH.
Make sure Rust is installed.
git clone https://github.com/horacehoff/keel && cd keel && cargo build --release
./target/release/keel myfile.klkeel program.kl # Run a file
keel # Start the REPL
keel -v/--version # Print version
keel -h/--help # Print help- Replace the LALRPOP parser with a handwritten parser and Logos lexer (in progress)
- Better module system (in progress)
- Struct methods
- Higher-order functions
- Better embedding API with limits
- Variables & Types
- Functions
- Blocks
- Conditions
- Loops
- Match
- Try/Catch blocks
- Importing other ".kl" files
- Importing dynamic libraries
- Embedding (experimental)
- Arrays
- Slices
- Structs
- Arithmetic Operations
- Documentation
Types are inferred and are never written explicitly.
let x = 42;
let name = "Keel";
let ratio = 3.14;
let flag = true;
let numbers = [1, 2, 3, 4, 5];Built-in types: Integer (i32), Float (f64), Boolean, String, Array<T>.
A
main()function is required when executing a.klfile.
It is the starting point for the execution of the program.
fn add(a, b) {
return a + b;
}
fn main() {
print(add(10, 32));
}print("Beginning of program");
let y = 20;
// All blocks are anonymous namespace scopes
// (e.g. trying to access x outside of the following block would yield an error)
{
let x = 10 + y;
print(x);
}let x = 20;
if x == 20 {
print("20!");
} else if x == 15 {
print("15!");
} else {
print("else!");
}Inline conditions work as expressions:
let my_number = 42;
let the_answer = if my_number == 42 { "It's the answer!" } else { "It's not the answer..." };
print(the_answer);Use break to exit the loop.
Use continue to skip to the next iteration.
let i = 0;
while i < 10 {
print(i);
i += 1;
}// Using _ as the variable name in a for loop will discard the value,
// making the program faster, but preventing access to the element
for x in [0,1,2,3] {
for _ in "abcd" {
print(x);
}
}Loops indefinitely until flow is stopped
let i = 0;
loop {
i += 1;
print("i is: "+str(i));
if i == 10 {
break;
}
}
print("End of the loop!");Loops over a range of integers
let x = 0;
// Loops from i=0 to i = max-1
for i in 0..10000000 {
x += i;
}
print(x);let x = 0;
// Defaults to 0
for i in ..10 {
print(i);
x += 1;
}
print(x);Match statements currently don't support binding variables
let x = "hello";
match x {
"hello" => {
print("Hi!");
}
"goodbye" => {
print("Bye!");
}
_ => {
print("You said: "+x);
}
}This is heavily subject to change
The list of catchable errors is available here.
Errors can be caught with:
try {
// error-prone code here
} catch "index_out_of_bounds" { // matches a specific error
// code here
} catch "slice_out_of_bounds" {
// code here
} catch e { // binds the error (a string) to a variable
// code here
}catch e is the catch-all, it handles any error not matched above and binds the error to e. If there is one, it must come last.
If no catch matches, the error propagates to the enclosing try, or crashes the program if there isn't one.
You can throw errors with throw("error here"), which raises a catchable error. In this case, it would be caught by catch "error here".
You can import other .kl files with the following syntax:
import "fibonacci_lib.kl" // all functions/structs are available under fibonacci_lib::
import "other_lib.kl" as mylib // all functions/structs are available under mylib::
fn main() {print(mylib::my_func(42));}
Imports can be nested, and circular imports trigger an error and crash the program.
You can load functions from dynamic libraries by specifying each function's signature, with the following syntax:
dylib "dynamic_library_path" {
function_return_type function_name(function_arg_type_1, function_arg_type_1, ..., function_arg_type_n);
}For example:
dylib "my_test.dylib" {
int add(int, int);
float add(float, float);
string add(string, string);
int sum(int[], int);
}
fn fib(n) {
if n <= 1 {
return n;
}
return fib(my_test::add(n, -1)) + fib(my_test::add(n,-2));
}
fn main() {
print(my_test::add(6, 1));
print(fib(25));
}If the extension is omitted, Keel will choose the correct extension based on your OS. For example:
// On macOS, it will try to load "my_test.dylib".
// On Windows, it will try to load "my_test.dll".
// On Linux, it will try to load "my_test.so".
dylib "my_test" {
int add(int, int);
float add(float, float);
string add(string, string);
int sum(int[], int);
}
fn main() {print(my_test::add(6,1));}The API is subject to change
Keel can be embedded in other programs through a C ABI. Build it as a dynamic library:
cargo build --profile embed --features embed
# The library will be in target/embed/libkeel.dylib (OR .so / .dll)Two functions are exposed:
extern char* keel_run(const char* code); // Runs the code and returns the output
extern void keel_free_output(char* output); // Frees the returned stringErrors are returned in the output string and don't crash the host.
Arrays are homogeneous and can only hold one type.
let nums = [3, 1, 4, 1, 5, 9];
nums.sort();
print(nums[0]); // 1
print(nums.len()); // 6
nums.push(2);
print(nums.contains(9)); // truelet nums = [3, 1, 4, 1, 5, 9];
print(nums[..2]); // [3,1]
print(nums[0..2]); // [3,1]
print(nums[2..4]); // [4,1]let msg = "Hello world";
print(msg[..5]); // "Hello"
print(msg[0..5]); // "Hello"
print(msg[6..11]); // "world"struct TestStruct {
x: int[][],
y:bool
}
struct MyStruct {
first_field:float,
second_field:TestStruct[],
third_field:string
}
let x = MyStruct {
first_field:10.0,
second_field:[
TestStruct {
x: [[0,1,2], [3,4,5]],
y:false
}],
third_field: "Hello, World!"
};
x.third_field = x.third_field.uppercase();
x.second_field[0].x[0][0] += 99;
print(x.third_field); // "HELLO, WORLD!"
print(x.second_field[0]); // TestStruct {x:[[99,1,2],[3,4,5]],y:false}
print(x); // MyStruct {first_field:10,second_field:[TestStruct {x:[[99,1,2],[3,4,5]],y:false}],third_field:"HELLO, WORLD!"}let x = 0;
x = x + 1;
x += 1;
x = x - 1;
x -= 1;
x = x * 1;
x *= 1;
x = x / 1;
x /= 1;
x = x % 1;
x %= 1;
x = x ^ 1;
x ^= 1;
print(x == 1);
print(x != 1);
print(x > 1);
print(x >= 1);
print(x < 1);
print(x <= 1);
print(x > 1 || x < 1);
print(x > 1 && x < 1);
