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

Language Constructs

A language construct looks like name!(...), but it is not a function. The compiler knows its rules and may emit direct bytecode for it.

Fresh identities

fresh!() takes no arguments and creates a fresh scalar. Every executed call creates a distinct identity within the VM lifetime. Copies keep their identity, and skipped branches create none:

$a = fresh!();
$b = fresh!();
assert!($a != $b);

The construct runs at runtime. It cannot run in a constant expression, even through a function call. Compiled artifacts store the generation instruction and create identities when executed.

Array and string constructs

  • length!($value) returns the byte length of a string or the item count of an array, as a uint.
  • contains!($array, $value) checks array values with strict equality.
  • contains_key!($array, $key) checks an array key or index.
  • remove!($array, $key) removes and returns one entry.
  • swap_remove!($vec, $index) removes and returns one item without preserving vec order.
  • remove_first!($vec) removes and returns the first item.
  • remove_last!($vec) removes and returns the last item.

The remove forms change their target. remove! accepts vecs and dicts. The other forms accept vecs. They throw when no matching item exists. See Removing entries for their order and cost.

Assertions

assert! requires a bool. A false result throws AssertionError:

$value = 42;
assert!($value > 0, 'value must be positive');

The message is optional. The error includes the failed expression and its source location.

Output

  • write!(...) writes values to standard output.
  • write_line!(...) also writes a line ending.
  • write_error!(...) writes to standard error.
  • write_error_line!(...) also writes a line ending.

Each argument must be a string, int, or float.

Debug output

debug!(...) writes a source location and a structural view to standard error. It shows types, string byte lengths, object properties, and collection values. It hides private property values and sensitive callables. It stops after 64 items or 32 nested levels.

debug!(dict['ready' => true, 'count' => 2]);

Use debug! while working, then remove it from normal output paths.

Explicit discard

discard!($value) evaluates and ignores a value. It is the explicit way to ignore a result marked #[MustUse].

Sequencing expressions

sequence!($first, ..., $last) evaluates each expression once, from left to right, and produces the last expression’s value and type. It requires at least one expression and allows a trailing comma:

$result = sequence!(
  $value = 10,
  $value *= 2,
  $value + 1,
);

assert!($result == 21);
assert!($value == 20);

Each earlier expression behaves like an expression statement, including #[MustUse] checks. Use discard! on an individual expression to deliberately ignore a required result. If the whole sequence is an expression statement, the last expression also follows the usual discard rules.

A sequence shares the surrounding variable scope and control flow. return exits the enclosing callable, break and continue target the enclosing loops, and an exception skips the remaining expressions. Normal cleanup rules still apply. The compiler emits the expressions directly without a function call or closure.

Use it in a match arm that needs several expressions:

$input = '  hello  ';
$value = match ($input) {
  '' => 'empty',
  $_ => sequence!(
    $trimmed = Whim\Str\trim($input),
    assert!(length!($trimmed) > 0),
    $trimmed,
  ),
};

assert!($value == 'hello');

Only the selected arm evaluates its sequence. The arguments are expressions; statements such as if, foreach, and try still belong in statement bodies.

Object cloning

clone! copies an object and may replace named properties during the copy:

final readonly class Point {
  public function __construct(public int $x, public int $y) {}

  public function withY(int $y): Point {
    return clone!($this, y: $y);
  }
}

$first = new Point(1, 2);
$second = $first->withY(9);
assert!(($first->y, $second->y) == (2, 9));

Whim checks property visibility, readonly rules, and types during the clone.

Lifetime and process control

drop!($variable) releases one local now. It throws LeakedResourceError if another strong reference keeps a resource alive.

exit!() ends the process with status zero. exit!($status) requires an int and uses its low eight bits. Exit is not an exception: catch cannot catch it, and finally does not run after it.

panic!($message) reports a broken invariant. It evaluates one expression, which must produce a string, writes panic: message and the current stack trace to standard error, and ends the process with status 255. It is not an exception. catch cannot catch it, and finally does not run after it. Shutdown destructors still run.

A non-string message throws TypeError before the panic occurs. Errors thrown while evaluating the message also propagate normally. These errors can be caught and run pending finally blocks.

if (!contains_key!($states, $name)) {
  panic!("the state table has no entry for {$name}");
}

Panic traces hide TraceBoundary frames unless full traces are on. They also hide parameters marked SensitiveParameter. Use throw for errors that a caller may handle. Use panic! only when continuing would be wrong.

Platform information

These constructs take no arguments and return strings describing the compilation target. This defaults to the platform running Whim. The compiler stores these values as string constants.

ConstructValueExamples
cpu_architecture!()CPU architecture'x86_64', 'aarch64', 'riscv64'
operating_system!()Operating system'linux', 'macos', 'freebsd', 'windows'
operating_system_family!()Operating system family'unix', 'windows'
shared_library_prefix!()Shared library filename prefix'lib', ''
shared_library_suffix!()Shared library filename suffix, including the dot'.so', '.dylib', '.dll'
shared_library_extension!()Shared library extension without the dot'so', 'dylib', 'dll'
executable_suffix!()Executable filename suffix, including a dot if present'', '.exe'
executable_extension!()Executable extension without the dot'', 'exe'

You can use them in constant expressions, including constant declarations, parameter defaults, property defaults, and attributes:

const PLATFORM = operating_system!() . '/' . cpu_architecture!();

write_line!(PLATFORM);

Source paths and loading

  • file!() returns the current source file path.
  • directory!() returns its directory.
  • embed!('./file') reads a file while compiling.
  • require!($path) loads and runs a source file.
  • require_once!($path) does that at most once per resolved path.

file!() and directory!() also work in constant expressions:

const SOURCE_FILE = file!();
const SOURCE_DIRECTORY = directory!();

assert!(SOURCE_FILE == file!());
assert!(SOURCE_DIRECTORY == directory!());

See Loading Files for load and error rules.

Compile-time file embedding

embed! takes one literal relative path. Whim resolves it from the directory of the source file that contains the construct, reads the file while compiling, and stores its exact bytes as a string:

const TEMPLATE = embed!('./template.html');

Whim does not decode text or change line endings. The running program does not read the file. A source read from standard input cannot use embed! because it has no directory. Absolute paths, missing files, unreadable files, and directories cause compile errors.

The compiler reads an embedded file even when the construct appears in code that will not run. It reads each resolved path once per compilation. Do not use embed! for secrets: the bytes remain plain in bytecode and compiled artifacts.