Error Handling

Every error carries a status code and a message. Scripts run from a file
include line numbers in error messages.

Error categories

Lexical

UNEXPECTED_CHARACTER
UNTERMINATED_STRING

Syntax

EXPECTED_TOKEN
UNEXPECTED_TOKEN
UNEXPECTED_EOF
INVALID_STATEMENT
INVALID_FUNCTION_DEF
INVALID_LITERAL

Semantic

UNKNOWN_VARIABLE
UNKNOWN_FUNCTION
UNDEFINED_KEY
ARRAY_INDEX_OUT_OF_RANGE
NOT_CALLABLE
REASSIGN_CONSTANT
REASSIGN_RESTRICTED
MODIFY_CONSTANT
MODIFY_RESTRICTED
INVALID_ACCESSOR
INVALID_OPERATOR
INVALID_ITERABLE
INVALID_ASSIGNMENT_TARGET
INVALID_ARGUMENT_COUNT
INVALID_OPERAND_TYPE
INVALID_ARGUMENT
INVALID_CONVERSION
TYPE_ERROR

Runtime

DIVISION_BY_ZERO
MODULO_BY_ZERO
STACK_OVERFLOW
ARITHMETIC_ERROR
OUT_OF_MEMORY
STEP_LIMIT_EXCEEDED
TIME_LIMIT_EXCEEDED
TIMEOUT
CONNECTION_FAILED
IO_ERROR
OPERATION_FAILED
ASSERTION_FAILED
RUNTIME_ERROR
UNSUPPORTED

File

FILE_NOT_FOUND
FILE_UNREADABLE
IMPORT_NOT_FOUND
IMPORT_READ_FAILED
PLUGIN_LOAD_FAILED

Common failure modes

# division by zero
print(1 / 0)              # DIVISION_BY_ZERO

# reassigning a constant
const pi = 3.14
pi = 3                   # REASSIGN_CONSTANT

# missing variable
print(undefined_name)     # UNKNOWN_VARIABLE

# incompatible comparison
print(1 == "one")         # TYPE_ERROR

# deep recursion
fn boom() { return boom() }
boom()                    # STACK_OVERFLOW (limit 256)

# failed assertion
std.assert.assert_eq(1, 2)   # ASSERTION_FAILED (not RUNTIME_ERROR)

Runtime failures distinguish what went wrong so scripts (and hosts) can
react accordingly:

  • CONNECTION_FAILED — the channel could not be established: refused
    connects, failed binds/listens, unresolvable hosts, unreachable daemons
    (D-Bus, PipeWire, PulseAudio).
  • IO_ERROR — an established channel or filesystem call failed mid-stream:
    failed reads/writes, dropped connections, oversized HTTP bodies.
  • OPERATION_FAILED — the call reached the other side, which said no:
    rejected D-Bus methods, refused device parameters, codec errors, failed
    process spawns.
  • ASSERTION_FAILED — an std.assert.* check failed.
  • TYPE_ERROR — operands of a compatible kind but incompatible types
    (1 == "one", a pipe returning a different type than its receiver).
  • INVALID_OPERAND_TYPE — a function/builtin got an argument of the wrong
    type (len(42), std.string.upper(42)).
  • INVALID_OPERATOR — internal only (the parser produced an operator the
    evaluator doesn’t implement); scripts never trigger it directly.
  • TIMEOUT — an await deadline passed (see below).
  • STEP_LIMIT_EXCEEDED / TIME_LIMIT_EXCEEDED — a sandboxed engine’s step
    budget or wall-clock deadline fired (see Embedding).
    Catchable like any error — the engine stays usable afterwards.
  • RUNTIME_ERROR — anything else; the message carries the details.

Errors from async work

Errors raised on background tasks (spawned fibers, network receive loops,
timers) are recorded and surfaced rather than lost:

  • std.async.await(future) re-throws the task’s error (as
    Async error: <message>) with the task’s original status — a task that
    fails an assertion surfaces ASSERTION_FAILED, not a flattened
    RUNTIME_ERROR. Use this for spawn/defer and single-shot
    timers, whose errors live on the returned future.
  • std.async.await(future, timeout_ms) throws TIMEOUT when the deadline
    passes first.
  • Background objects with no caller record the most recent failure and expose
    it via last_error(): repeating-timer handles (handle.last_error()),
    and the std.net objects tcp_client, tcp_server, udp, websocket, and
    http_server (nil when there is none).

See std.time for details on which surface applies to each
kind of background work. Concurrency errors in particular are covered in
Concurrency — Errors.

Assertions

std.assert is the lightweight way to fail fast in tests and preconditions:

std.assert.assert(x > 0, "x must be positive")   # throws on failure
std.assert.assert_eq(40 + 2, 42)                 # equality check
std.assert.assert_throws(fn() { 1 / 0 })         # expects an error

A failed assertion throws a normal Scrii error with a message, and in async
tasks it is stored on the future and re-thrown by await — see
std.assert for the full API.

Embedding

Host callbacks that throw from C++ surface in the script. Prefer throwing
ScriptError{status::..., msg} so scripts receive a clean status and
message. See Embedding.