Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Built-in Functions

Bop ships a very small set of built-in functions that are always in scope. They can’t be shadowed by user-defined fns. Host-backed builtins (I/O, time) are provided separately by the embedding host — see bop-sys’s StandardHost for the reference implementation.

Anything math- or conversion-shaped lives as a method on a value, not as a global — (-5).abs(), "42".to_int(), [1, 2, 3].len(). The global list is deliberately short: variadic (print), constructor-shaped (range), session-stateful (rand), callable-taking (try_call), and the shared error-signalling primitive (panic).

print(args...)

Prints values to the host’s stdout, separated by spaces. Returns none.

print("hello")           // hello
print("x =", 42)         // x = 42
print(1, "plus", 2)      // 1 plus 2

Accepts any number of arguments (including zero). Each argument is converted to its string representation (Display) automatically — you don’t need .to_str() first.

range(n) / range(start, end) / range(start, end, step)

Builds an array of int values.

range(5)           // [0, 1, 2, 3, 4]
range(2, 6)        // [2, 3, 4, 5]
range(0, 10, 2)    // [0, 2, 4, 6, 8]
range(5, 0)        // [5, 4, 3, 2, 1]  (auto-detects direction)
range(10, 0, -3)   // [10, 7, 4, 1]
  • With 1 arg: range(n)[0, 1, ..., n-1].
  • With 2 args: range(start, end) auto-detects direction.
  • With 3 args: explicit step (error if step == 0).
  • All arguments must be int — floats are rejected.
  • Maximum 10,000 elements. Bigger ranges raise a runtime error so loops can’t build gigabyte arrays by accident.

rand(n)

Returns a random integer from 0 to n - 1, inclusive. n must be a positive integer.

rand(6)     // 0..=5 (die roll)
rand(2)     // 0 or 1 (coin flip)

Uses a deterministic PRNG seeded per-session. The same inputs produce the same sequence — handy for tests, surprising for crypto (don’t use it for that).

try_call(callable)

Runs callable with no arguments and catches any non-fatal runtime error:

let r = try_call(fn() { return 1 / 0 })
print(match r {
  Ok(v)  => v,
  Err(e) => "caught: " + e.message,
})
// caught: Division by zero

On success returns Result::Ok(value); on a non-fatal error returns Result::Err(RuntimeError { message, line }). The Ok(v) / Err(e) pattern shorthand desugars to Result::Ok(v) / Result::Err(e) — see Error Handling. Fatal errors (step-limit / memory-limit / fn-call-depth) are not caught — they propagate past try_call unchanged so the sandbox invariant holds.

See Error Handling for the full story on try, try_call, Result, and RuntimeError.

panic(message)

Raise a non-fatal runtime error carrying message. Useful inside stdlib helpers (unwrap, expect, assert_eq, …) and user code that needs to bail with a readable error from an expression position where a plain return isn’t enough.

fn take_positive(n) {
  if n <= 0 { panic("n must be positive, got {n}") }
  return n * 2
}

print(take_positive(3))                           // 6
// print(take_positive(-1))                       // runtime error: n must be positive, got -1

Non-fatal, so try_call catches it:

let r = try_call(fn() { panic("deliberate") })
print(match r {
  Ok(_)  => "ok?",
  Err(e) => e.message,
})
// deliberate

Non-string arguments are stringified via Display, so panic(42) or panic(RuntimeError { message: "x", line: 0 }) also works without an explicit .to_str().

Everything else lives on a value

CategoryWasNow
Introspectiontype(x), inspect(x)x.type(), x.inspect()
Conversionstr(x), int(x), float(x)x.to_str(), x.to_int(), x.to_float()
Lengthlen(x)x.len() (arrays, strings, dicts)
Absolute / min / maxabs(x), min(a, b), max(a, b)x.abs(), a.min(b), a.max(b)
Trig / rootssqrt(x), sin(x), cos(x), tan(x)x.sqrt(), x.sin(), x.cos(), x.tan()
Roundingfloor(x), ceil(x), round(x)x.floor(), x.ceil(), x.round()
Power / log / exppow(b, e), log(x), exp(x)b.pow(e), x.log(), x.exp()

See Methods for the full per-type method catalogue.