# Rhai Cheatsheet
Quick reference for Rhai scripting in Kelora. For detailed tutorials, see [Scripting Transforms](../tutorials/scripting-transforms.md). For practical examples: `kelora --help-examples`. For function reference: `kelora --help-functions`.
## Variables & Types
```rhai
let x = 42; // Integer (i64)
let price = 19.99; // Float (f64)
let name = "alice"; // String (double quotes only!)
let active = true; // Boolean (true/false)
let tags = [1, "two", 3.0]; // Array (mixed types ok)
let user = #{name: "bob", age: 30}; // Map/object literal
let empty = (); // Unit type (Rhai's "nothing")
type_of(x) // Returns: "i64", "string", "array", "map", "()"
x = "hello"; // Dynamic typing: can change type
```
**Key Points:**
- `let` required for new variables (no implicit declaration)
- Double quotes only for strings (`"text"` not `'text'`)
- Unit type `()` represents "nothing" (not null/undefined)
- Arrays and maps are reference types (modifying copies affects original)
## Operators
```rhai
// Arithmetic
a + b a - b a * b a / b a % b a ** b // power: 2**3 == 8
// Comparison
a == b a != b a < b a > b a <= b a >= b
// Logical
// Bitwise
a & b a | b a ^ b a << b a >> b
// Assignment
a = b a += b a -= b a *= b a /= b a %= b
// Ranges (for loops only)
1..5 // Exclusive: 1, 2, 3, 4
1..=5 // Inclusive: 1, 2, 3, 4, 5
// Membership
"key" in map // Check if key exists
```
## Control Flow
### If-Else
```rhai
if x > 10 {
print("big");
} else if x > 5 {
print("medium");
} else {
print("small");
}
// Ternary-style (if is an expression)
let category = if x > 10 { "big" } else { "small" };
```
### Switch
```rhai
let category = switch x {
1 => "one",
2 | 3 => "two or three", // Multiple cases
4..=6 => "four to six", // Range matching
_ => "other" // Default (underscore)
};
```
### Loops
```rhai
// Range loops
for i in 0..10 { print(i); } // 0 to 9
for i in 0..=10 { print(i); } // 0 to 10
// Array iteration
for item in array { print(item); }
// Map iteration
for (key, value) in map {
print(`${key} = ${value}`);
}
// While loop
while condition {
if done { break; }
if skip { continue; }
}
// Infinite loop
loop {
if should_stop { break; }
}
```
## Functions & Closures
```rhai
// Function definition
fn add(a, b) {
a + b // Last expr is return value
}
fn greet(name) {
return "Hello, " + name; // Explicit return
}
// Closures
// Closures in array methods
```
## Rhai Special Feature: Function-as-Method
Rhai allows calling any function as a method on its first argument:
```rhai
// These are equivalent:
extract_re(e.line, r"\d+") // Function call style
e.line.extract_re(r"\d+") // Method call style
// Use method style for chaining:
e.domain = e.url
.extract_domain()
.to_lower()
.strip();
// Both styles work for all functions:
to_int(e.port) // Function style
e.port.to_int() // Method style (more readable)
```
## Kelora Event Access
The global variable `e` represents the current event in `--filter` and `--exec` stages:
```rhai
// Direct field access
e.level // Top-level field
e.user.name // Nested field (maps)
e.scores[1] // Array indexing (0-based)
e.scores[-1] // Negative indexing (last element)
e.headers["user-agent"] // Bracket notation for special chars
// Field existence checking
"field" in e // Check top-level field exists
e.has_path("user.role") // Check nested path exists
// Safe field access with defaults
e.get_path("user.role", "guest") // Get nested with fallback
e.get_path("scores[0]", 0) // Works with array paths
// Field removal
e.password = () // Remove field (unit assignment)
e.ssn = () // Remove another field
e = () // Remove entire event (filtered out)
```
## Array & Map Operations
JSON arrays become native Rhai arrays with full functionality:
```rhai
// Array transformations
sorted(e.scores) // Sort numerically/lexicographically
reversed(e.items) // Reverse order
unique(e.tags) // Remove duplicates
dedup(e.values) // Remove consecutive duplicates
sorted_by(e.users, "age") // Sort objects by field
// Array methods
e.tags.len() // Length
e.tags.is_empty() // Check if empty
e.tags.join(", ") // Join to string
e.scores.sum() // Sum numbers
e.scores.min() // Minimum value
e.scores.max() // Maximum value
// Array access patterns
if e.items.len() > 0 {
e.first = e.items[0];
e.last = e.items[-1];
}
// Fan-out: convert array elements to separate events
emit_each(e.items) // Each element becomes an event
emit_each(e.items, #{ctx: "value"}) // Add base fields to each
// Map operations
for (key, val) in e {
print(`${key} = ${val}`);
}
```
## Type Conversions
```rhai
// Strict conversions (return () on error)
to_int(e.port) // String → integer
to_float(e.price) // String → float
to_bool(e.active) // String → boolean
// Safe conversions with defaults
to_int_or(e.port, 8080) // Use default if conversion fails
to_float_or(e.price, 0.0)
to_bool_or(e.active, false)
// String conversions
to_string(42) // Any → string
e.value.to_int() // Method style
// Type checking
type_of(e.field) // Get type as string
type_of(e.field) != "()" // Check if field has value
```
## Common Patterns
### Safe Nested Access
```rhai
// With default fallback
let role = e.get_path("user.role", "guest");
let port = to_int_or(e.port, 8080);
// With existence check
if e.has_path("user.profile.avatar") {
e.avatar = e.user.profile.avatar;
}
// Safe array access
if e.items.len() > 0 {
e.first_item = e.items[0];
}
```
### Conditional Field Removal
```rhai
// Remove debug fields in production
if e.level != "DEBUG" {
e.stack_trace = ();
e.debug_info = ();
}
// Remove entire event conditionally
if e.status < 400 { e = (); } // Only keep errors
```
### Method Chaining
```rhai
// Extract and normalize domain
e.domain = e.url
.extract_domain()
.to_lower()
.strip();
// Parse and extract from structured text
e.error_line = e.stack_trace
.extract_re(r"line (\d+)", 1)
.to_int_or(0);
```
### Array Processing
```rhai
// Get top N scores
e.top_3 = sorted(e.scores)[-3:];
// Extract names from sorted users
e.winners = sorted_by(e.users, "score")
.reverse()
.map(|u| u.name);
// Filter and count
e.active_items = e.items.filter(|i| i.status == "active");
e.active_count = e.active_items.len();
```
### Multi-Level Fan-Out
```rhai
# First exec: batches → separate events
--exec 'emit_each(e.batches)'
# Second exec: items → separate events with context
--exec 'let ctx = #{batch_id: e.id}; emit_each(e.items, ctx)'
# Filter the final events
--filter 'e.status == "active"'
```
## Global Context
```rhai
conf // Global config map (read-only after --begin)
metrics // Global metrics map (from track_* calls)
meta // Event metadata (filename, line numbers, raw line)
get_env("VAR", "default") // Environment variable access
// meta attributes:
meta.line // Original raw line (always available)
meta.line_num // Line number, 1-based (available with files)
meta.filename // Source filename (multi-file processing)
// Example usage:
--begin 'conf.env = get_env("ENVIRONMENT", "dev")'
// Multi-file tracking
--exec 'if e.level == "ERROR" { track_count(meta.filename) }'
// Debugging with line numbers
--exec 'eprint("Error at " + meta.filename + ":" + meta.line_num)'
```
## Error Handling Modes
**Default (resilient):**
- Parse errors → skip line, continue
- Filter errors → treat as false, drop event
- Exec errors → rollback, keep original event
**Strict mode (`--strict`):**
- Any error → abort immediately with exit code 1
## Rhai Quirks & Gotchas
| **JavaScript** | No `null`/`undefined` (use `()`), no single quotes, `let` required |
| **Python** | Braces required, no `:` after if/for, `!=` not `<>`, double quotes only |
| **Rust** | More permissive syntax, semicolons mostly optional, dynamic typing |
**Common mistakes:**
```rhai
// ❌ Wrong
x = 1 // Error: x not declared
let name = 'alice'; // Error: single quotes not allowed
if x > 5: print("big") // Error: colon not allowed, braces required
"5" + 3 // Error: no implicit conversion
// ✅ Correct
let x = 1; // Declare with let
let name = "alice"; // Double quotes
if x > 5 { print("big"); } // Braces required
"5".to_int() + 3 // Explicit conversion
```
**Special behaviors:**
- Last expression in block is return value (no `return` needed)
- Semicolons recommended but often optional
- Function calls without args: `e.len` same as `e.len()`
- No implicit type conversion (use `to_int()`, `to_float()`, etc.)
## Quick Reference
```rhai
// Event manipulation
e.field = value // Set field
e.field = () // Remove field
e = () // Remove event
// Type checking
type_of(e.field) // Get type
"field" in e // Field exists
// Safe access
e.get_path("a.b.c", default) // Nested with fallback
e.has_path("a.b.c") // Check nested exists
// Conversions
to_int_or(val, 0) // Safe int conversion
to_float_or(val, 0.0) // Safe float conversion
to_bool_or(val, false) // Safe bool conversion
// Arrays
e.items.len() // Length
e.items.is_empty() // Check empty
sorted(e.items) // Sort
unique(e.items) // Deduplicate
emit_each(e.items) // Fan out to events
// Strings
e.text.to_lower() // Lowercase
e.text.to_upper() // Uppercase
e.text.strip() // Trim whitespace
e.text.contains("word") // Substring check
e.text.extract_re(r"(\d+)", 1) // Regex extraction
// Environment & Context
get_env("VAR", "default") // Get env var
conf.key // Read config (from --begin)
metrics.key // Read metrics (in --end)
meta.filename // Current source filename
meta.line_num // Current line number (1-based)
meta.line // Original raw line
```
## See Also
- [Scripting Transforms Tutorial](../tutorials/scripting-transforms.md) - Detailed walkthrough with examples
- `kelora --help-functions` - Complete function catalogue
- `kelora --help-examples` - Practical log analysis patterns
- `kelora --help-rhai` - Language guide (this cheatsheet's source)
- [Rhai Documentation](https://rhai.rs) - Full Rhai language reference