Ratchet is a small, imperative, strongly typed language for tooling and scripting, with C-like syntax and explicit memory choices.
Experimental and under active development. Coroutines are planned.
See the language documentation, grammar, and test programs for more detail and examples.
module main; // Every source file starts with its module declaration.
// Example function
fn int clamp(int value, int low, int high) {
if (value < low) {
return low;
}
if (value > high) {
return high;
}
return value;
}
// null means the function returns no value
fn null printScore(int score) {
if (score < 0) {
echo("Score cannot be negative.");
return;
}
echo("Score:");
echo(clamp(score, 0, 100));
}
fn bool program() {
echo("Hello, Ratchet!"); // Print a value followed by a newline.
int score = 120;
int bounded = clamp(score, 0, 100);
echo(bounded); // 100
printScore(score);
printScore(-5); // "Score cannot be negative."
return false;
}module main;
struct Counter {
int value;
// Structs have methods with implicit "this"
method null add(int amount) {
this.value = this.value + amount;
}
}
fn bool program() {
Counter counter; // A struct value, stack allocated
counter.value = 10;
counter.value = counter.value + 5;
counter.add(5);
echo(counter.value); // 20
return false;
}Use in for a read-only borrow and inout for a mutable borrow, at both the declaration and the call.
module main;
struct Point {
int x;
int y;
}
// Borrow the caller's Point for mutation instead of copying it.
fn null move(inout Point point, int dx, int dy) {
point.x = point.x + dx;
point.y = point.y + dy;
}
// Read the caller's Point without copying it; writes through point are forbidden.
fn int sum(in Point point) {
return point.x + point.y;
}
fn bool program() {
Point point;
point.x = 1;
point.y = 2;
move(inout point, 3, 4); // Repeat inout at the call: point is now (4, 6).
echo(sum(in point)); // Repeat in for a read-only borrow. Prints 10.
return false;
}module main;
fn bool program() {
int value = 5; // Values behave as you expect
int copy = value;
copy = 9;
echo(value); // 5
int& shared = new int& { 5 }; // Allocate an integer managed by the GC.
int& alias = shared; // Copy the reference: both names refer to the same object.
alias.value = 9; // .value accesses the integer inside the managed object.
echo(shared.value); // 9
return false;
}module main;
fn bool program() {
// Inline arrays have a fixed size and copy their elements.
int[3] local; // Three integers stored inline; indices are 0..2.
local[0] = 7;
int[3] copy = local; // Independent storage.
copy[0] = 42;
echo(local[0]); // 7
echo(copy[0]); // 42
// Managed arrays live on the heap and can grow.
int count = 3;
int[]& heap = new int[count]&; // Initial length is chosen at runtime.
heap[0] = 7;
int[]& alias = heap; // Share the array instead of copying its elements.
alias[0] = 99;
echo(heap[0]); // 99
heap.add(10); // Append; grow the backing storage when needed.
echo(alias.length); // 4: both references see the appended element.
echo(heap[3]); // 10: the new element is at the old length.
// Accessing heap[4] would cause a runtime bounds error.
return false;
}Every value type T has both an & and a * variant, including primitives, structs, and arrays. T& is managed by the runtime; T* is manually managed by your code.
Pointer support is implemented; free() is still in development. These examples show the type forms and intended cleanup:
| Type | Runtime-managed | Manually managed |
|---|---|---|
| Integer | int& |
int* |
| Struct | Point& |
Point* |
| Array | int[]& |
int[]* |
module main;
fn bool program() {
int& managed = new int& { 5 }; // The runtime manages this allocation's lifetime.
int* manual = new int* { 5 }; // Your code must release this allocation.
int* alias = manual; // Copy the pointer, not the allocated integer.
int[]& managedArray = new int[3]&; // Three integers managed by the runtime.
int[]* manualArray = new int[3]*; // Three integers with a manual lifetime.
managedArray[0] = 7; // Both forms use the same indexing syntax.
manualArray[0] = 7;
free(manual); // Still in development: release each allocation once.
free(manualArray); // Manual arrays also require explicit cleanup.
// Both manual and alias now point to freed memory and must not be used.
return false;
}Each file declares its module. use makes names from another module available in that file; qualified names work too.
example/numbers.ratc:
module numbers; // Declarations in this file belong to "numbers".
fn int doubleValue(int value) {
return value * 2;
}example/main.ratc:
module main;
use numbers; // Allow unqualified names from numbers in this file.
fn bool program() {
echo(doubleValue(21)); // 42: found through the use statement.
echo(numbers::doubleValue(5)); // 10: qualification also works without use.
return false;
}Coming soon....