15-Minute Language Tour
This page gives a complete, high-level tour of Tinsel—from basic syntax and expression-oriented control flow to custom structs, pattern matching, spatial grids, interval algebra, and declarative state-space search.
Every snippet on this page is runnable! You can also run examples/language_tour.tin via ./bin/tinsel run examples/language_tour.tin.
1. Variables, Mutability & Expression Blocks
Variables declared with let (as well as function parameters and for loop variables) are immutable by default. Use let mut whenever a variable needs to be reassigned with = or compound assignment (+=, -=, *=, etc.):
let base = 100
let mut score = base
score += 25
assert(score == 125)
// Simultaneous tuple reassignment (ideal for swaps and Fibonacci state updates)
let mut a = 1
let mut b = 2
(a, b) = (b, a + b)
assert(a == 2 && b == 3)
In Tinsel, almost every construct—blocks { ... }, if/else chains, match arms, and for ... yield loops—is an expression that evaluates to a value:
let delta = {
let step = 6
if step % 2 == 0 { step * 10 } else { step }
}
assert(delta == 60)
2. Primitives, Automatic bigint & String Interpolation
Tinsel provides 64-bit signed integers (int), arbitrary-precision integers (bigint), 64-bit floats (float), Unicode runes (char), booleans (bool), nil, and UTF-8 strings (string).
- Automatic
bigintPromotion: Integer literals and 64-bitintarithmetic (+,-,*) that overflow signed 64-bit bounds automatically promote tobigintwithout silent wrapping. - Character Arithmetic:
'7' - '0'yields7, and'a' + 2yields'c'. - Interpolated Strings (
f"..."): Embed arbitrary expressions inside{...}:
let mask = 0b1010_1100
let digit = '9' - '0'
let max_i64 = 9223372036854775807
let huge = max_i64 + 1 // Automatically promotes to bigint!
let msg = f"digit={digit}, hex=0x{mask.hex()}, huge={huge}"
assert(msg == "digit=9, hex=0xac, huge=9223372036854775808")
3. Collections, Slicing & Universal Membership (in / !in)
Tinsel includes dynamic arrays, fixed tuples, and hash maps with structural equality and hashing (arrays, tuples, vectors, and structs can all be used directly as map keys!).
let nums = [10, 20, 30, 40, 50]
assert(nums[-1] == 50) // Negative indexing from the end
assert(nums[1..4] == [20, 30, 40]) // Half-open range slice
assert(nums[1..-1] == [20, 30, 40])
assert(30 in nums && 99 !in nums) // Universal membership operators
// Map literals support bare identifier keys (strings), literals, or [expr] keys:
let visited = {
origin: true,
[vec2(3, 4)]: true
}
assert( visited.origin && vec2(3, 4) in visited )
4. for ... yield Comprehensions & Lazy .iter() Streams
A for loop containing yield evaluates directly to a new array of yielded items. When immediately followed by .sum(), .product(), .min(), .max(), or .len(), the compiler fuses the comprehension into a zero-allocation scalar accumulator loop:
let squares_of_evens = for x in 1..=6 {
if x % 2 == 0 {
yield x * x
}
}
assert(squares_of_evens == [4, 16, 36])
// Fused zero-allocation sum comprehension:
let mult_sum = (for x in 1..100 {
if x % 3 == 0 || x % 5 == 0 { yield x }
}).sum()
assert(mult_sum == 2318)
For massive or infinite sequences that need early termination, .iter() creates a lazy pull-based Iter stream (std/iter):
let first_three = (1..1_000_000_000)
.iter()
.filter(x -> x % 17 == 0)
.map(x -> x * x)
.take(3)
.collect()
assert(first_three == [289, 1156, 2601])
5. Functions, Closures & Forward Pipelines (|>)
Functions are declared with fn and support parameter pattern destructuring and default values. Anonymous lambdas use ->. The forward pipeline operator |> passes the left-hand value into the right-hand call (as the first argument, or at the _ placeholder):
from "std/array" import filter, map as arr_map
fn manhattan((x1, y1), (x2, y2) = (0, 0)) {
abs(x1 - x2) + abs(y1 - y2)
}
assert(manhattan((3, -4)) == 7)
let result = "10 -5 20 8 -2"
|> parse_ints
|> filter(x -> x > 0)
|> arr_map(x -> x * 2)
|> sum
assert(result == 76)
6. User-Defined struct, Option & Pattern Matching (match)
Define custom data types with struct. Struct instances automatically gain value-based equality (==), zero-sort hash keys (usable in maps and sets), to_string formatting, and pattern matching:
struct Beacon(id, pos, active = true) {
fn dist(self, other) {
self.pos <-> other.pos
}
}
let b1 = Beacon(1, vec2(0, 0))
let b2 = Beacon(2, vec2(3, 4))
assert(b1.dist(b2) == 7)
assert(b1 == Beacon(1, vec2(0, 0), true))
Tinsel provides first-class Option values (Some(v) and None) with postfix ? (returns true if Some) and ! (unwraps Some(v)), alongside rich match expressions:
struct Beacon(id, pos, active = true)
fn classify(item) {
match item {
Some(Beacon(id, pos, true)) if pos <-> vec2(0, 0) <= 10 -> f"active #{id}",
Some(Beacon(id, _, false)) -> f"offline #{id}",
None -> "empty",
_ -> "far"
}
}
let reading = Some(Beacon(2, vec2(4, 3)))
assert(reading? && reading!.id == 2)
assert(classify(reading) == "active #2")
7. Regex-Free Input Ingestion
Puzzle inputs rarely require regular expressions in Tinsel:
- String Template Patterns: Bind variables directly from structured lines (
intvalues are parsed automatically). - Unanchored
.scan(template): Extract all matching substrings from noisy text into an array of maps. - Fast Byte-Scanned
.ints(): Extract all signed integers from any string.
// 1. Full-line template destructuring
let line = "Sensor at x=50, y=-100: closest beacon is at x=10, y=20"
let "Sensor at x={sx}, y={sy}: closest beacon is at x={bx}, y={by}" = line
assert(vec2(sx, sy) <-> vec2(bx, by) == 160)
// 2. Unanchored pattern scanning across corrupted text
let noisy = "xmul(2,4)%&mul[3,7]!@^mul(5,5)+mul(11,8)"
let total = noisy.scan("mul({a},{b})").map(m -> m.a * m.b).sum()
assert(total == 121)
8. Spatial Geometry (vec2, vec3, <->, grid)
2D and 3D integer vectors (vec2, vec3), the infix Manhattan distance operator (<->), and flat row-major character grids (std/grid) make spatial puzzles effortless:
from "std/grid" import parse_grid
let g = parse_grid("#####\n#S..#\n#.###\n#..E#\n#####")
let start = g.find('S')
let goal = g.find('E')
// g.adj4(pos) automatically filters out-of-bounds coordinates
let open_moves = g.adj4(start).filter(p -> g[p] != '#')
assert(open_moves == [vec2(2, 1), vec2(1, 2)])
assert((start <-> goal) == 4)
assert(g.width == 5 && g.height == 5)
9. Interval Algebra (RangeSet) & Orbit Cycle Fast-Forwarding
When Part 2 scales coordinates to 10^{10} or iterations to 10^{12}, Tinsel's standard library solves them without brute-force loops:
from "std/intervals" import range_set
from "std/orbit" import orbit
// 1. Disjoint sweep-line RangeSet & piecewise interval trisection remap
let seeds = range_set([79..93, 55..68])
let remapped = seeds.remap([[50, 98, 2], [52, 50, 48]])
assert(remapped.len() == 27 && remapped.min() == 57)
// 2. Periodicity detection & O(1) modulo fast-forwarding over 1 trillion steps
let state_1t = orbit({
initial: 5,
steps: 1_000_000_000_000,
step: x -> (x * 3 + 1) % 100
})
assert(state_1t == 5)
10. Declarative State-Space Search & Memoization
std/search provides bfs, dfs, dijkstra, astar, toposort, and union_find, while std/memo provides automatic multi-argument memoize:
from "std/search" import dijkstra, union_find
from "std/memo" import memoize
// Weighted shortest path from 0 to 10
let path_res = dijkstra({
start: 0,
goal: 10,
step: x -> [[x + 1, 2], [x + 3, 5]]
})
assert(path_res.found && path_res.cost == 17)
// Disjoint-Set Union (Union-Find)
let uf = union_find(["A", "B", "C"])
uf.union("A", "B")
assert(uf.connected("A", "B") && uf.count() == 2)
// Top-down DP memoization
let mut ways = nil
ways = memoize(n -> if n <= 1 { 1 } else { ways(n - 1) + ways(n - 2) })
assert(ways(20) == 10946)
11. Why Tinsel? Eliminating the Puzzle Boilerplate Tax
In general-purpose languages like Go, Java, or Rust, solving algorithmic puzzles often requires writing 50–100 lines of plumbing (priority queue interfaces, coordinate bounds checks, composite state hashing, regex submatch parsing, and manual interval splitting) before expressing the actual algorithm.
| Capability | Typical General-Purpose Boilerplate | Tinsel Primitive |
|---|---|---|
| Input Parsing | regexp.MustCompile, strings.Split, strconv.Atoi loops |
let "x={x}, y={y}" = s, .ints(), .scan() |
| 2D Grids & Bounds | Nested [][]byte slices, manual dx/dy arrays, nx >= 0 && nx < w |
parse_grid, g[pos], pos in g, g.adj4(pos) |
| Manhattan Distance | abs(x1 - x2) + abs(y1 - y2) helper functions |
Infix operator: p1 <-> p2 |
| Composite Map Keys | String serialization hacks ("%d,%d") or custom equals/hashCode |
Universal structural hashing on tuples, arrays, vectors & structs |
| Shortest Path Search | 40+ lines of PriorityQueue / heap.Interface + visited maps |
dijkstra({ start, goal, step }) / astar / bfs |
| Interval Translation | 70+ lines of sweep-line sorting and 3-way segment splitting | range_set([...]).remap(rules) |
| 10^{12}-Step Cycles | Manual history map, index tracking, and offset modulo math | orbit({ initial, steps, step }) |
| Integer Overflow | Silent 64-bit wrap-around or verbose BigInteger method chains |
Automatic bigint promotion on 64-bit overflow |
Next Steps
- Ready to learn step by step? Begin Lesson 01: Variables, Expressions & Control Flow.
- Looking for practical patterns? Browse the Algorithmic Cookbook and Canonical AoC Walkthroughs.
- Need exact API specifications? Jump to Syntax, Grammar & Types or Global Built-ins & Receiver Methods.