Getting Started Guide

Getting Started Guide

Setup

Install the rill CLI tools:

npm install -g @rcrsr/rill-cli

Test the installation:

echo '"Hello"' | rill-exec -

The - flag reads from stdin.

What is rill?

rill is a scripting language where data flows through pipes. Instead of assigning values to variables and then using them, you pipe data from one operation to the next.

"hello" -> .len
# Result: 5

rill is designed for AI agents that generate and execute scripts—sequences of operations where each step transforms or acts on data from the previous step.

Your First Script

The simplest rill script is just a value:

"Hello, world!"

This evaluates to "Hello, world!". The last value in a script is its result.

Pipes: The Core Concept

The -> operator pipes a value to the next operation:

"hello" -> .trim
# Result: "hello"

42 -> ($ + 8)
# Result: 50

The special variable $ refers to the current piped value. Inside a pipe chain, $ holds whatever was piped in.

When you write .trim without an explicit receiver, it’s syntactic sugar for $.trim(). The method is called on the current pipe value. Parentheses can be omitted when a method takes no arguments.

Chaining Operations

Pipes chain naturally:

"  hello world  " -> .trim -> .split(" ")
# Result: ["hello", "world"]

Methods can also chain directly without ->:

"  hello world  ".trim.split(" ")
# Result: ["hello", "world"]

Each -> passes its left side to its right side. The result flows through the chain.

Variables

Capture values into named variables with => $name:

"hello" => $greeting
$greeting -> .len => $length
$length
# Result: 5

Variables start with $ and hold any value type.

Data Types

rill has seven value types:

TypeExample
String"hello"
Number42, 3.14, -7
Booleantrue, false
List[1, 2, 3]
Dict[name: "alice", age: 30]
Tuple*[1, 2, 3]
Closure|x|($x + 1)

Strings

Strings support interpolation with {expression}. Any valid expression works:

"alice" => $name
3 => $a
5 => $b
10 => $count
true => $ok
"Hello, {$name}!"                    # Variable interpolation
"sum: {$a + $b}"                     # Arithmetic
"valid: {$count > 0}"                # Comparison
"status: {$ok ? \"yes\" ! \"no\"}"   # Conditional
"upper: {$name -> .upper}"           # Method chain

Use {{ and }} for literal braces:

"JSON: {{\"key\": \"value\"}}"       # Produces: JSON: {"key": "value"}

Multiline strings use triple-quote syntax (also supports interpolation):

"World" => $name
"""
Hello, {$name}!
Line two
"""

Lists and Dicts

Lists hold ordered values:

[1, 2, 3] => $nums
$nums[0]        # 1
$nums[-1]       # 3 (last element)
$nums -> .len   # 3

Dicts hold key-value pairs:

[name: "alice", age: 30] => $person
$person.name    # "alice"
$person.age     # 30

Methods

Methods are called with .name() syntax. The pipe value becomes the implicit first argument:

"hello world" -> .split(" ")
# Result: ["hello", "world"]

"hello" -> .contains("ell")
# Result: true

[1, 2, 3] -> map |x|($x * 2)
# Result: [2, 4, 6]

Common string methods: .len, .trim, .split(), .contains(), .match(), .is_match(), .lower, .upper, .replace(), .replace_all()

Common list operations: .len, .head, .tail, .at(), .join(), map $fn, filter { }

Conditionals

Use ? for if-else decisions:

# condition ? then-branch ! else-branch
true ? "yes" ! "no"
# Result: "yes"

5 -> ($ > 3) ? "big" ! "small"
# Result: "big"

The else branch (! ...) is optional:

# Only runs then-branch if true
true ? "executed"

Piped Conditionals

When you pipe into ?, the pipe value becomes the condition:

"hello" -> .contains("ell") ? "found it"
# Result: "found it"

"" -> .empty ? "nothing here" ! "has content"
# Result: "nothing here"

Long conditionals can split across lines using ? and ! as continuations:

"test" => $input
$input -> .contains("x")
  ? "found"
  ! "missing"
# Result: "missing"

Loops

For-Each Loop

Iterate over a list with each { body }:

[1, 2, 3] -> each { $ * 2 }
# Result: [2, 4, 6]

Inside the loop body, $ is the current element. The loop collects all body results into a new list.

While Loop

When the left side of @ is a boolean, it’s a while loop. Use $ as the accumulator:

0 -> ($ < 5) @ { $ + 1 }
# Result: 5

The body’s result becomes the next iteration’s $. The loop exits when the condition becomes false.

Note: Variables created inside the loop body exist only within that iteration (block scoping). Use $ for accumulation.

Breaking Out

Use break to exit a loop early:

[1, 2, 3, 4, 5] -> each {
  ($ == 3) ? break
  $
}
# Result: [1, 2]

Closures (Functions)

Define reusable logic with closure syntax |params| body. See Closures for advanced patterns including late binding and dict-bound closures.

|x|($x * 2) => $double

5 -> $double()
# Result: 10

[1, 2, 3] -> map $double
# Result: [2, 4, 6]

Multiple Parameters

|a, b|($a + $b) => $add

$add(3, 4)
# Result: 7

Type Annotations

Optional type hints help catch errors:

|name: string, age: number| "Name: {$name}, Age: {$age}"

Property Access

Access dict fields and list indices:

[name: "alice", scores: [85, 92, 78]] => $data

$data.name           # "alice"
$data.scores[0]      # 85
$data.scores[-1]     # 78 (last)

Safe Access

Use ?? for default values when a field might be missing:

$data.nickname ?? "anonymous"
# Result: "anonymous" (if no nickname field)

Use .? to check existence:

$data.?nickname      # false
$data.?name          # true

Blocks

Group multiple statements in braces. The last value is the block’s result:

{
  "hello" => $greeting
  $greeting -> .upper => $shouted
  "{$shouted}!"
}
# Result: "HELLO!"

Use return to exit a block early:

{
  5 => $x
  ($x > 3) ? ("big" -> return)
  "small"
}
# Result: "big"

Annotations

Annotations modify how statements execute. The most common is limit for loops:

# Limit loop to 100 iterations max ($ flows through as accumulator)
^(limit: 100) false -> ($ == false) @ {
  check_status()
}

Without a limit, while loops default to 10,000 max iterations.

Putting It Together

Here’s a complete example that processes a list of names:

["alice", "bob", "charlie"] => $names
$names -> map |name| { "{$name}: {$name -> .len} chars" } => $descriptions
$descriptions -> .join(", ")

Key Differences from Other Languages

  1. No = assignment — Use -> to pipe values into variables
  2. No null/undefined — Empty strings and lists are valid; “no value” doesn’t exist
  3. No exceptions — Errors halt execution; use conditionals for error handling
  4. Immutable types — Once a variable holds a string, it always holds strings
  5. Value semantics — All comparisons are by value, all copies are deep

Next Steps

  • Reference — Complete language specification
  • Closures — Late binding, dict-bound, and invocation patterns
  • Collectionseach, map, filter, fold operators
  • Examples — Workflow patterns

Quick Reference Card

# Pipes
value -> operation          # pipe value to operation
value => $var               # capture into variable
$                           # current pipe value

# Types
"string"                    # string
42, 3.14                    # number
true, false                 # boolean
[1, 2, 3]                   # list
[a: 1, b: 2]                # dict
|x|($x + 1)                 # closure

# Conditionals
cond ? then ! else          # if-else
value -> ? then             # piped if (value is condition)
cond                        # multi-line form
  ? then ! else

# Loops
list -> each { body }       # for-each
(bool) @ { body }           # while
@ { body } ? cond           # do-while
break                       # exit loop
return                      # exit block

# Collection Operators
-> each { body }            # sequential, all results
-> each(init) { $@ + $ }    # with accumulator
-> map { body }             # parallel, all results
-> fold(init) { $@ + $ }    # reduction, final only

# Access
$data.field                 # dict field
$list[0]                    # list index
$data.field ?? default      # with default
$data.?field                # existence check

# Annotations
^(limit: 100) statement     # set iteration limit