Tea is a statically-typed scripting language with a focus on memory safety and modern syntax. It combines the expressiveness and ease-of-use of scripting languages with key design principles for safer code.
- Scripting Language: Designed for automation, quick tasks, and rapid development
- Memory Safety: Explicit mutability control with
letandmutkeywords - Static Typing: Type annotations with inference capabilities for safer scripts
- Types and Methods: Data structures with associated methods defined using
fn TypeName.method_name(...)syntax - Control Flow: Standard control structures (
if/else,whileloops) withbreakandcontinue
- Explicit Mutability: Variables are immutable by default (
let), mutable when specified (let mut) - Type Methods: Methods are defined separately from type definitions using
fn TypeName.method_name(...)syntax - Type Annotations: Optional but explicit type annotations with
:for safer scripts - Arrow Syntax: Function return types specified with
-> - Memory Safety Focus: Controlled mutability helps prevent common scripting errors
Tea uses let for variable declarations with optional mutability and optionality:
let x = 42; // Immutable variable
let mut y = 10; // Mutable variable
let z: i32 = 100; // Explicit type annotation
let mut w: f32 = 3.14; // Mutable with type
let opt_var: i32? = 5; // Optional type
let mut opt_mut: f32? = 2.0; // Optional mutable type
Optional Types: Types can be marked as optional using the ? suffix. Optional types can hold a value or be null,
providing safer handling of potentially absent values. The null keyword is used to represent the absence of a value:
let opt_var: i32? = null; // Initialize with null
let opt_var: i32? = 42; // Assign a value
opt_var = null; // Assign null to optional type
Functions are declared with the fn keyword:
// Simple function
fn greet() {
// Function body
}
// Function with parameters and return type
fn add(a: i32, b: i32) -> i32 {
return a + b;
}
// Mutable function (can modify external state)
fn mut increment_counter() -> i32 {
// Implementation
}
Define custom data types with typedef:
typedef Point {
x: f32;
y: f32;
}
typedef Person {
name: string;
age: i32;
}
Implement methods for structs using fn TypeName.method_name(...) syntax:
// Regular method
fn Point.distance(other: Point) -> f32 {
let dx = self.x - other.x;
let dy = self.y - other.y;
return dx * dx + dy * dy;
}
// Mutable method (can modify the instance)
fn mut Point.move_by(dx: f32, dy: f32) {
self.x = self.x + dx;
self.y = self.y + dy;
}
Create instances with the new keyword:
let point = new Point {
x: 10.0,
y: 20.0,
};
let person = new Person {
name: 'Alice',
age: 30,
};
Standard control structures are supported:
// If-else statements
if x > 0 {
// Positive
} else {
// Non-positive
}
// While loops
let mut i = 0;
while i < 10 {
i = i + 1;
}
// Break statement - exits the loop immediately
let mut j = 0;
while j < 10 {
j = j + 1;
if j == 5 {
break; // Exit loop when j equals 5
}
}
// Continue statement - skips to next iteration
let mut sum = 0;
let mut k = 0;
while k < 10 {
k = k + 1;
// Skip even numbers
if k % 2 == 0 {
continue; // Skip even numbers
}
sum = sum + k;
}
Tea supports rich expressions with operator precedence:
let result = (a + b) * c - d / e;
let comparison = x > y && z <= w;
let negation = -value;
i32- 32-bit signed integer numbersf32- 32-bit floating-point numbersstring- Text strings- Custom struct types
typedef Rectangle {
width: f32;
height: f32;
}
fn Rectangle.area() -> f32 {
return self.width * self.height;
}
fn mut Rectangle.scale(factor: f32) {
self.width = self.width * factor;
self.height = self.height * factor;
}
fn main() {
let mut rect = new Rectangle {
width: 10.0,
height: 5.0,
};
let initial_area = rect.area();
rect.scale(2.0);
let scaled_area = rect.area();
// Example with optional types and null
let optional_width?: f32 = 15.0;
let mut optional_height?: f32 = 8.0;
let optional_result?: f32 = null; // Initialize with null
}
Tea supports binding native C functions to make them callable from Tea code. This allows you to extend Tea with system functionality, I/O operations, and performance-critical code written in C.
Native functions in C must follow this signature:
tea_val_t your_function_name(tea_fn_args_t* args)The function receives a context and a list of arguments, and must return a tea_val_t. Arguments are accessed by
calling tea_fn_args_pop() in a loop. Here's an example:
// Native function to print values (similar to built-in print)
static tea_val_t tea_print_values(tea_fn_args_t* args) {
for (;;) {
tea_var_t* arg = tea_fn_args_pop(args);
if (!arg) {
break; // No more arguments
}
const tea_val_t value = arg->val;
switch (value.type) {
case TEA_V_I32:
printf("%d ", value.i32);
break;
case TEA_V_F32:
printf("%f ", value.f32);
break;
case TEA_V_INST:
case TEA_V_UNDEF:
break;
}
}
return tea_val_undef();
}
// Native function to add two numbers
static tea_val_t tea_add_numbers(tea_fn_args_t* args) {
tea_var_t* arg1 = tea_fn_args_pop(args);
tea_var_t* arg2 = tea_fn_args_pop(args);
if (!arg1 || !arg2) {
// Handle error case - not enough arguments
return tea_val_undef();
}
if (arg1->val.type == TEA_V_I32 && arg2->val.type == TEA_V_I32) {
tea_val_t result = {0};
result.type = TEA_V_I32;
result.i32 = arg1->val.i32 + arg2->val.i32;
return result;
}
return tea_val_undef();
}Register your native functions with the Tea context using tea_bind_native_fn:
int main() {
tea_ctx_t context;
tea_interp_init(&context, "example.tea");
// Bind native functions
// Second NULL agument means that the function is global, but you can use your own types here instead
// For example 'Point', 'Foo', 'Bar' and e.t.c
tea_bind_native_fn(&context, NULL, "print", tea_print);
tea_bind_native_fn(&context, NULL, "add_numbers", tea_add_numbers);
tea_bind_native_fn(&context, NULL, "print_values", tea_print_values);
// Execute Tea code...
tea_interp_cleanup(&context);
return 0;
}Once bound, native functions can be called like regular Tea functions:
// Call the built-in print function
print('Hello from Tea!');
// Call custom native functions
let sum = add_numbers(10, 20);
print(sum);
// Print multiple values at once
print_values('Result:', sum, 'Done');
Native functions work with the tea_val_t type system:
i32(TEA_V_I32) - 32-bit signed integersf32(TEA_V_F32) - 32-bit floating-point numbersstring(TEA_V_INST) - Null-terminated strings wrapped as instancesinstance(TEA_V_INST) - Complex objects (structs)undef(TEA_V_UNDEF) - Uninitialized or error state
- Always validate arguments: Use
tea_fn_args_pop()to safely access arguments and check for NULL - Handle errors gracefully: Return
tea_val_undef()for error conditions - Memory management:
- Strings returned from native functions should be allocated with
tea_malloc
- Strings returned from native functions should be allocated with
- Performance: Use native functions for computationally intensive operations
- Argument handling: Process arguments in the order they were passed by calling
tea_fn_args_pop()sequentially
Tea uses CMake for building:
# Configure and build in one go
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
# Or for development builds
cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build --parallelTea is currently in development. The core language features are implemented including:
- ✅ Variable declarations and assignments
- ✅ Optional types with
?syntax andnullvalues - ✅ Function definitions and calls
- ✅ Mutable functions with
fn mutsyntax - ✅ Struct definitions and instantiation
- ✅ Method definitions using
fn TypeName.method_name(...)syntax - ✅ Control flow statements (
if/else,whileloops) - ✅ Loop control (
breakandcontinuestatements) - ✅ Expression evaluation
- ✅ Type system foundations
- ✅ Native function binding
Planned Features:
- 🔄 Advanced type inference
- 🔄 Module system