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 auint.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.
| Construct | Value | Examples |
|---|---|---|
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.