Chapter 14: Standard Library Mastery
Tova's standard library goes far beyond collections and strings. It includes regex, date/time, validation, encoding, JSON, URL handling, advanced data structures, and terminal output. This chapter is your guided tour through the parts most developers use daily.
By the end, you'll build a log analyzer that ties together regex, datetime, collections, and terminal formatting.
Regular Expressions
Tova provides a clean regex API through six functions. All patterns use standard regex syntax:
regex_test — Does It Match?
All regex functions take the string first, then the pattern:
print(regexTest("abc 123", r"\d+")) // true
print(regexTest("abc 123", r"^\d+$")) // false (not entirely digits)
print(regexTest("12345", r"^\d+$")) // trueregex_match — Extract Captures
Returns a Result containing the full match and capture groups:
line = "2026-03-06 14:30:45 [ERROR] Disk full"
result = regexMatch(line, r"(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) \[(\w+)\] (.+)")
match result {
Ok(m) => {
print(m.match) // Full match: entire string
print(m.groups[0]) // "2026-03-06"
print(m.groups[1]) // "14:30:45"
print(m.groups[2]) // "ERROR"
print(m.groups[3]) // "Disk full"
}
Err(e) => print("No match")
}regex_find_all — Find Every Match
text = "Emails: alice@test.com and bob@work.org"
matches = regexFindAll(text, r"\w+@\w+\.\w+")
// Returns array of { match, index, groups } objects
// Extract all numbers from text
numbers = regexFindAll("Order #42: 3 items at $15 each", r"\d+")regex_replace — Find and Replace
// Normalize whitespace
cleaned = regexReplace("too many spaces", r"\s+", " ")
print(cleaned) // "too many spaces"
// Redact sensitive data
safe = regexReplace("Card: 1234-5678-9012-3456", r"\d{4}-\d{4}-\d{4}-(\d{4})", "****-****-****-$1")
print(safe) // "Card: ****-****-****-3456"regex_split — Split by Pattern
// Split on any combination of whitespace and punctuation
parts = regexSplit("one, two; three four", r"[,;\s]+")
print(parts) // ["one", "two", "three", "four"]regex_capture — Named Captures
Returns a Result containing named capture groups:
result = regexCapture(
"2026-03-06",
r"(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})"
)
match result {
Ok(groups) => {
print(groups.year) // "2026"
print(groups.month) // "03"
print(groups.day) // "06"
}
Err(e) => print("No match or no named groups")
}Raw Strings for Regex
Always use raw strings (r"...") for regex patterns. Without the r prefix, backslashes need double-escaping: "\\d+" vs r"\d+". Raw strings make patterns readable.
Date and Time
Tova's datetime module covers parsing, formatting, arithmetic, and human-readable output:
Getting the Current Time
timestamp = now() // Numeric timestamp (milliseconds)
iso_string = nowIso() // ISO 8601 string: "2026-03-06T14:30:45.123Z"Parsing Dates
d = dateParse("2026-03-06")
match d {
Ok(date) => print("Parsed: {dateFormat(date, 'date')}")
Err(msg) => print("Invalid: {msg}")
}date_parse returns a Result — invalid date strings produce Err.
Formatting Dates
d = now()
// Preset formats
print(dateFormat(d, "iso")) // "2026-03-06T14:30:45.123Z"
print(dateFormat(d, "date")) // "2026-03-06"
print(dateFormat(d, "time")) // "14:30:45"
print(dateFormat(d, "datetime")) // "2026-03-06 14:30:45"
// Custom format tokens
print(dateFormat(d, "DD/MM/YYYY")) // "06/03/2026"
print(dateFormat(d, "YYYY-MM-DD HH:mm")) // "2026-03-06 14:30"| Token | Meaning | Example |
|---|---|---|
YYYY | 4-digit year | 2026 |
MM | 2-digit month | 03 |
DD | 2-digit day | 06 |
HH | 2-digit hour (24h) | 14 |
mm | 2-digit minute | 30 |
ss | 2-digit second | 45 |
Date Arithmetic
today = now()
tomorrow = dateAdd(today, 1, "days")
next_week = dateAdd(today, 7, "days")
next_month = dateAdd(today, 1, "months")
next_year = dateAdd(today, 1, "years")
two_hours_later = dateAdd(today, 2, "hours")Date Differences
start = dateParse("2026-01-01").unwrap()
end_d = dateParse("2026-12-31").unwrap()
print(dateDiff(start, end_d, "days")) // 364
print(dateDiff(start, end_d, "months")) // 11
print(dateDiff(start, end_d, "hours")) // 8736Creating Dates from Parts
birthday = dateFrom({ year: 1990, month: 6, day: 15 })
meeting = dateFrom({ year: 2026, month: 3, day: 10, hour: 14, minute: 30 })Extracting Parts
d = now()
print(datePart(d, "year")) // 2026
print(datePart(d, "month")) // 3
print(datePart(d, "day")) // 6
print(datePart(d, "weekday")) // "Thursday" (or similar)Human-Readable Relative Time
recent = dateAdd(now(), -45, "minutes")
print(timeAgo(recent)) // "45 minutes ago"
old = dateAdd(now(), -3, "days")
print(timeAgo(old)) // "3 days ago"Validation
Quick checks for common string formats:
// Email validation
print(isEmail("alice@example.com")) // true
print(isEmail("not-an-email")) // false
// URL validation
print(isUrl("https://tova.dev")) // true
print(isUrl("not a url")) // false
// String content checks
print(isNumeric("12345")) // true
print(isNumeric("12.5")) // true
print(isAlpha("hello")) // true
print(isAlpha("hello123")) // false
print(isAlphanumeric("abc123")) // true
print(isHex("deadbeef")) // true
print(isUuid("550e8400-e29b-41d4-a716-446655440000")) // true
// Emptiness
print(isEmpty("")) // true
print(isEmpty(" ")) // false (whitespace is not empty)Combining Validators
Build validation pipelines using these as building blocks:
fn validate_signup(data) {
guard isEmail(data.email) else { return Err("Invalid email") }
guard len(data.password) >= 8 else { return Err("Password too short") }
guard isAlphanumeric(data.username) else { return Err("Username must be alphanumeric") }
Ok(data)
}Encoding
Convert data between formats for storage, transmission, or display:
Base64
encoded = base64Encode("Hello, Tova!")
print(encoded) // "SGVsbG8sIFRvdmEh"
decoded = base64Decode(encoded)
print(decoded) // "Hello, Tova!"Base64 is used for embedding binary data in text formats (emails, JSON, data URIs).
Hex
hex = hexEncode("Hello")
print(hex) // "48656c6c6f"
original = hexDecode(hex)
print(original) // "Hello"Hex encoding is common for hashes, colors, and binary inspection.
URL Encoding
safe = urlEncode("hello world & more")
print(safe) // "hello%20world%20%26%20more"
original = urlDecode(safe)
print(original) // "hello world & more"JSON
Parse, stringify, and pretty-print JSON:
// Object to JSON string
data = { name: "Alice", scores: [95, 87, 92], active: true }
json_str = jsonStringify(data)
print(json_str) // '{"name":"Alice","scores":[95,87,92],"active":true}'
// JSON string to object (returns Result — unwrap or match)
parsed = jsonParse(json_str).unwrap()
print(parsed.name) // "Alice"
print(parsed.scores[0]) // 95
// Pretty-printed JSON (for display and debugging)
pretty = jsonPretty(data)
print(pretty)
// {
// "name": "Alice",
// "scores": [95, 87, 92],
// "active": true
// }JSON + Result
jsonParse returns a Result — Ok(value) on success, Err(message) on failure. Use .unwrap() for trusted input, or match to handle errors gracefully.
URL Parsing and Building
Parsing URLs
result = parseUrl("https://api.example.com/users?page=2&limit=10#results")
match result {
Ok(parts) => {
print(parts.protocol) // "https"
print(parts.host) // "api.example.com"
print(parts.pathname) // "/users"
print(parts.search) // "?page=2&limit=10"
print(parts.hash) // "#results"
}
Err(msg) => print("Invalid URL: {msg}")
}Building URLs
url = buildUrl({
protocol: "https",
host: "api.tova.dev",
pathname: "/v2/search",
search: "q=hello&lang=en"
})
print(url) // "https://api.tova.dev/v2/search?q=hello&lang=en"Query String Operations
// Parse query string to object
params = parseQuery("name=Alice&age=30&city=Portland")
print(params.name) // "Alice"
print(params.age) // "30"
// Build query string from object
qs = buildQuery({ search: "hello world", page: "1", sort: "date" })
print(qs) // "search=hello%20world&page=1&sort=date"Random and Sampling
Generate random values and sample from collections:
// Random numbers
print(random()) // Float between 0 and 1
print(randomInt(1, 100)) // Int between 1 and 100
print(randomFloat(0, 10)) // Float between 0 and 10
// Pick from collections
colors = ["red", "green", "blue", "yellow"]
print(choice(colors)) // Random element
print(sample(colors, 2)) // 2 random elements (no repeats)
// Shuffle
deck = range(1, 53)
shuffled = shuffle(deck)
print(shuffled |> take(5)) // First 5 cards of shuffled deckAdvanced Collections
Beyond arrays, objects, and maps, Tova's stdlib provides specialized data structures for common patterns.
Counter — Count Occurrences
Counter takes an iterable and counts how often each value appears:
words = split("the quick brown fox jumps over the lazy fox the", " ")
counts = Counter(words)
print(counts.count("the")) // 3
print(counts.count("fox")) // 2
print(counts.count("missing")) // 0
// Most common elements
print(counts.most_common(3))
// [["the", 3], ["fox", 2], ["quick", 1]]
print(counts.total()) // 10 (total word count)
print(counts.length) // 8 (unique words)Counter is perfect for frequency analysis, histograms, and vote counting.
DefaultDict — Auto-Creating Keys
DefaultDict automatically creates a value for missing keys using a factory function:
// Group items by category
groups = DefaultDict(fn() [])
items = [
{ name: "Widget", category: "tools" },
{ name: "Sprocket", category: "parts" },
{ name: "Wrench", category: "tools" },
{ name: "Bolt", category: "parts" },
{ name: "Hammer", category: "tools" }
]
for item in items {
groups.get(item.category).push(item.name)
}
print(groups.get("tools")) // ["Widget", "Wrench", "Hammer"]
print(groups.get("parts")) // ["Sprocket", "Bolt"]
print(groups.get("other")) // [] (auto-created empty array)// Count with DefaultDict
word_counts = DefaultDict(fn() 0)
for word in split("the quick brown fox the fox", " ") {
word_counts.set(word, word_counts.get(word) + 1)
}OrderedDict — Predictable Key Order
OrderedDict maintains key-value pairs in insertion order:
config = OrderedDict([
["host", "localhost"],
["port", 3000],
["debug", true],
["log_level", "info"]
])
// Keys always come back in insertion order
print(config.keys()) // ["host", "port", "debug", "log_level"]
// Immutable updates — set returns a NEW OrderedDict
updated = config.set("port", 8080)
print(config.get("port")) // 3000 (original unchanged)
print(updated.get("port")) // 8080
// Check membership
print(config.has("host")) // true
print(config.has("timeout")) // falseDeque — Double-Ended Queue
Deque supports efficient push/pop from both ends:
dq = Deque([1, 2, 3])
// Add to front or back (returns new Deque)
dq2 = dq.push_front(0) // [0, 1, 2, 3]
dq3 = dq2.push_back(4) // [0, 1, 2, 3, 4]
// Peek without removing
print(dq3.peek_front()) // 0
print(dq3.peek_back()) // 4
// Pop returns [value, new_deque]
[front, rest] = dq3.pop_front()
print(front) // 0
print(rest.toArray()) // [1, 2, 3, 4]
[back, rest2] = rest.pop_back()
print(back) // 4
print(rest2.toArray()) // [1, 2, 3]Deques are useful for:
- Sliding windows (push to back, pop from front)
- Undo/redo stacks (push to back, pop from back)
- Work queues (enqueue at back, dequeue from front)
Terminal Output
Tova's terminal functions make CLI output readable and professional:
Text Styling
print(green("Success: Tests passed"))
print(red("Error: Build failed"))
print(yellow("Warning: Deprecated API"))
print(blue("Info: Server started"))
print(cyan("Debug: Request received"))
print(magenta("Trace: Function called"))
print(gray("Note: This is minor"))
print(bold("Important message"))
print(dim("Less important"))
print(underline("Emphasized text"))
print(strikethrough("Removed"))Combine styles by nesting:
print(bold(green("PASS")) ++ " test_login")
print(bold(red("FAIL")) ++ " test_signup")Tables
Display structured data in aligned columns:
data = [
{ name: "Alice", role: "Engineer", salary: 95000 },
{ name: "Bob", role: "Designer", salary: 82000 },
{ name: "Charlie", role: "Manager", salary: 105000 }
]
table(data)
// ┌─────────┬──────────┬────────┐
// │ name │ role │ salary │
// ├─────────┼──────────┼────────┤
// │ Alice │ Engineer │ 95000 │
// │ Bob │ Designer │ 82000 │
// │ Charlie │ Manager │ 105000 │
// └─────────┴──────────┴────────┘Panels
panel("Application Status", "All systems operational\nUptime: 99.9%")
// ╭─ Application Status ─────────╮
// │ All systems operational │
// │ Uptime: 99.9% │
// ╰──────────────────────────────╯Progress and Spinners
// Progress bar — wraps an iterable with a visual progress bar
items = range(0, 100)
for item in progress(items, { label: "Processing" }) {
// ... process each item ...
}
// Spinner — shows animated spinner while an async operation runs
result = await spin("Loading data...", fn() {
// ... do async work ...
await fetch_data(url)
})
// Spinner auto-completes when the callback finishesInteractive Input
name = ask("What is your name?")
confirmed = confirm("Deploy to production?")
color = choose("Pick a theme:", ["Dark", "Light", "Auto"])
colors = chooseMany("Select languages:", ["Tova", "Python", "JavaScript", "Go"])
password = secret("Enter API key:")Project: Log Analyzer
Let's build a log analyzer that combines regex, datetime, advanced collections, and terminal formatting:
// Sample log data
logs = [
"2026-03-06 09:15:23 [INFO] Server started on port 3000",
"2026-03-06 09:16:01 [WARN] Slow query: 2340ms",
"2026-03-06 09:16:45 [ERROR] Connection refused: redis://cache:6379",
"2026-03-06 09:17:02 [INFO] Request: GET /api/users (200, 45ms)",
"2026-03-06 09:17:30 [ERROR] Timeout: GET /api/reports (5000ms)",
"2026-03-06 09:18:00 [INFO] Request: GET /api/users (200, 38ms)",
"2026-03-06 09:18:45 [ERROR] Invalid JSON in request body",
"2026-03-06 09:19:30 [WARN] Memory usage: 78%"
]
// Parse each line with regex (regex_match returns Result)
fn parse_log(line) {
match regexMatch(line, r"(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) \[(\w+)\] (.+)") {
Ok(m) => Ok({ date: m.groups[0], time: m.groups[1], level: m.groups[2], message: m.groups[3] })
Err(_) => Err("Parse error")
}
}
parsed = logs
|> map(parse_log)
|> filter(fn(r) r.isOk())
|> map(fn(r) r.unwrap())
// Count by level
levels = Counter(parsed |> map(fn(e) e.level))
print(bold("=== Log Analysis ==="))
for entry in levels.most_common() {
color_fn = match entry[0] {
"ERROR" => red
"WARN" => yellow
_ => green
}
print(" {color_fn(padEnd(entry[0], 7))} {repeat('█', entry[1])} ({entry[1]})")
}
// Extract response times
fn extract_ms(msg) {
match regexMatch(msg, r"\((\d+)ms\)") {
Ok(m) => Some(toInt(m.groups[0]))
Err(_) => None
}
}
times = parsed
|> map(fn(e) extract_ms(e.message))
|> filter(fn(t) t.isSome())
|> map(fn(t) t.unwrap())
if len(times) > 0 {
print("")
print(bold("Response Times:"))
print(" Min: {min(times)}ms Max: {max(times)}ms Avg: {sum(times) / len(times)}ms")
}This project demonstrates the power of combining stdlib modules. Regex parses unstructured text. Counter summarizes frequencies. Terminal functions make the output professional. Each piece is simple — the combination is powerful.
Try "Log Analyzer" in PlaygroundExercises
Exercise 14.1: Write a parse_csv(text) function that uses regex_split to parse CSV text into an array of objects (using the first line as headers). Handle quoted fields that may contain commas.
Exercise 14.2: Build a date_range(start, end, step_unit) function that generates an array of dates between start and end, stepping by the given unit. For example, date_range("2026-01-01", "2026-01-07", "days") returns 7 dates.
Exercise 14.3: Create a schema_validator(schema) function that takes a schema object like { name: "string", age: "number", email: "email" } and returns a validator function. The validator should check each field using the appropriate is_* function and return Ok(data) or Err([list_of_errors]).
Exercise 14.4: Build a frequency analyzer that reads a text, counts character frequencies using Counter, and displays a horizontal bar chart using terminal colors. Highlight vowels in one color and consonants in another.
Challenge
Build a URL shortener data analyzer. Given a log of URL shortener events:
- Parse each log entry (timestamp, short_code, original_url, referrer, country)
- Use
Counterto find the most-clicked short URLs - Use
DefaultDictto group clicks by country - Calculate click trends over time using
date_diffanddate_part - Display a formatted report with
table(), colored headers, and summary statistics - Export the analysis as pretty-printed JSON