Skip to content

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:

tova
print(regexTest("abc 123", r"\d+"))       // true
print(regexTest("abc 123", r"^\d+$"))     // false (not entirely digits)
print(regexTest("12345", r"^\d+$"))       // true

regex_match — Extract Captures

Returns a Result containing the full match and capture groups:

tova
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

tova
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

tova
// 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

tova
// 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:

tova
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")
}
Try "Regex" in Playground

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

tova
timestamp = now()         // Numeric timestamp (milliseconds)
iso_string = nowIso()    // ISO 8601 string: "2026-03-06T14:30:45.123Z"

Parsing Dates

tova
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

tova
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"
TokenMeaningExample
YYYY4-digit year2026
MM2-digit month03
DD2-digit day06
HH2-digit hour (24h)14
mm2-digit minute30
ss2-digit second45

Date Arithmetic

tova
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

tova
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"))    // 8736

Creating Dates from Parts

tova
birthday = dateFrom({ year: 1990, month: 6, day: 15 })
meeting = dateFrom({ year: 2026, month: 3, day: 10, hour: 14, minute: 30 })

Extracting Parts

tova
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

tova
recent = dateAdd(now(), -45, "minutes")
print(timeAgo(recent))   // "45 minutes ago"

old = dateAdd(now(), -3, "days")
print(timeAgo(old))      // "3 days ago"
Try "Date/Time" in Playground

Validation

Quick checks for common string formats:

tova
// 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:

tova
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)
}
Try "Validation" in Playground

Encoding

Convert data between formats for storage, transmission, or display:

Base64

tova
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

tova
hex = hexEncode("Hello")
print(hex)   // "48656c6c6f"

original = hexDecode(hex)
print(original)   // "Hello"

Hex encoding is common for hashes, colors, and binary inspection.

URL Encoding

tova
safe = urlEncode("hello world & more")
print(safe)   // "hello%20world%20%26%20more"

original = urlDecode(safe)
print(original)   // "hello world & more"
Try "Encoding" in Playground

JSON

Parse, stringify, and pretty-print JSON:

tova
// 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 ResultOk(value) on success, Err(message) on failure. Use .unwrap() for trusted input, or match to handle errors gracefully.

URL Parsing and Building

Parsing URLs

tova
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

tova
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

tova
// 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"
Try "URL and JSON" in Playground

Random and Sampling

Generate random values and sample from collections:

tova
// 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 deck

Advanced 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:

tova
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:

tova
// 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)
tova
// 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:

tova
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"))  // false

Deque — Double-Ended Queue

Deque supports efficient push/pop from both ends:

tova
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)
Try "Advanced Collections" in Playground

Terminal Output

Tova's terminal functions make CLI output readable and professional:

Text Styling

tova
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:

tova
print(bold(green("PASS")) ++ " test_login")
print(bold(red("FAIL")) ++ " test_signup")

Tables

Display structured data in aligned columns:

tova
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

tova
panel("Application Status", "All systems operational\nUptime: 99.9%")
// ╭─ Application Status ─────────╮
// │ All systems operational       │
// │ Uptime: 99.9%                │
// ╰──────────────────────────────╯

Progress and Spinners

tova
// 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 finishes

Interactive Input

tova
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:")
Try "Terminal Output" in Playground

Project: Log Analyzer

Let's build a log analyzer that combines regex, datetime, advanced collections, and terminal formatting:

tova
// 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 Playground

Exercises

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:

  1. Parse each log entry (timestamp, short_code, original_url, referrer, country)
  2. Use Counter to find the most-clicked short URLs
  3. Use DefaultDict to group clicks by country
  4. Calculate click trends over time using date_diff and date_part
  5. Display a formatted report with table(), colored headers, and summary statistics
  6. Export the analysis as pretty-printed JSON

← Previous: Functional Programming | Next: I/O and System →

Released under the MIT License.