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

Intrinsics

Intrinsics are compiler-owned language operations.

They are not ordinary library functions, and they are not imported through use.

FOL currently keeps three layers separate:

  • intrinsics: compiler-owned operations such as .eq(...), .len(...), check(...), and panic(...)
  • core: the minimal runtime model with no heap and no OS/runtime services
  • memo: heap-backed library/runtime support without OS/runtime services
  • std: hosted/runtime services on top of memo, such as console, filesystem, networking, serialization, and other richer services

This split is not a source-level import trick and not an object-system feature. It is an artifact runtime model selected through build.fol.

If an operation can live as an ordinary library API, that is usually the better home for it. Intrinsics are reserved for surfaces the compiler must understand directly.

Surfaces

The current compiler recognizes three intrinsic surfaces.

Dot-root calls

These are written with a leading dot:

.eq(a, b)
.not(flag)
.len(items)
.echo(value)

Dot-root intrinsics are the main current intrinsic family.

Keyword calls

These look like language keywords rather than dot calls:

check(read_code(path))
panic("unreachable state")

The current V1 compiler treats check and panic as intrinsics too, even though they are not written with ..

Operator aliases

Some future intrinsic surfaces are written like operators:

value as target_type
value cast target_type

These are registry-owned now, but they are not implemented in the current V1 compiler.

Current V1 implemented intrinsics

The current compiler implements this subset end to end through type checking and lowering.

For current V1, backend execution of the implemented intrinsic set still goes through the current runtime layer where policy matters. The runtime contract is being split by fol-model, so the rule is:

  • core artifacts must not rely on heap-backed or hosted facilities
  • memo artifacts may use heap-backed facilities but not hosted services
  • bundled std wrappers require a memo artifact plus explicit internal standard dependency

In the current implementation that means:

  • .len(...) uses the runtime length helper
  • .echo(...) uses the runtime echo hook and formatting contract
  • check(...) uses the runtime recoverable-result inspection contract
  • scalar comparisons and .not(...) may lower to native target operations

Comparison

.eq(left, right)
.nq(left, right)
.lt(left, right)
.gt(left, right)
.ge(left, right)
.le(left, right)

Current V1 rule:

  • .eq(...) and .nq(...) work on comparable scalar pairs
  • .lt(...), .gt(...), .ge(...), and .le(...) work on ordered scalar pairs

If you call them with the wrong number of arguments or with unsupported type families, the compiler reports an intrinsic-specific type error.

Boolean

.not(flag)

Current V1 rule:

  • .not(...) accepts exactly one bol

Query

.len(items)

Current V1 rule:

  • .len(...) accepts exactly one operand
  • the operand must currently be one of:
    • str
    • arr[...]
    • vec[...]
    • seq[...]
    • set[...]
    • map[...]

In the current compiler, .len(...) is the only implemented query intrinsic. Under the runtime model split, array .len(...) belongs to core, while string and dynamic-container .len(...) belongs to memo/std.

Diagnostic

.echo(value)

Current V1 rule:

  • .echo(...) accepts exactly one argument
  • it emits the value through the std runtime hook
  • it then forwards the same value unchanged

.echo(...) belongs to std, not core or memo.

So this is valid:

fun[] main(flag: bol): bol = {
    return .echo(flag)
}

Recoverable and control intrinsics

check(read_code(path))
panic("fatal")

Current V1 rule:

  • check(expr) asks whether a recoverable routine call failed and returns bol
  • panic(...) aborts control flow immediately

These are described in more detail in the recoverable-error chapter.

Current V1 deferred intrinsics

The registry already reserves more names than the compiler implements.

That does not mean they work today.

Reserved but deferred for likely V1.x

  • as
  • cast
  • assert
  • .cap(...)
  • .is_empty(...)
  • .low(...)
  • .high(...)

These are recognized as registry-owned language surfaces, but the current compiler rejects them with explicit milestone-boundary diagnostics.

Reserved for later V2

  • bitwise helpers such as .bit_and(...), .bit_or(...), .shl(...), .shr(...), .rotl(...), .rotr(...), .pop_count(...), .clz(...), .ctz(...), .byte_swap(...), .bit_reverse(...)
  • overflow-mode helpers such as .checked_add(...), .wrapping_add(...), .saturating_add(...), .overflowing_add(...), and their subtraction forms

These are intentionally reserved now so the language can grow without accidental user-space name collisions, but they are not part of the current V1 compiler.

Reserved for later V3

  • .de_alloc(...)
  • .give_back(...)
  • .address_of(...)
  • .pointer_value(...)
  • .borrow_from(...)

These depend on later ownership, pointer, and low-level systems semantics.

Library-preferred surfaces

Some names are kept in the registry roadmap only as placeholders while the language decides whether they should really stay compiler-owned.

Current examples:

  • .add(...)
  • .sub(...)
  • .mul(...)
  • .div(...)
  • .abs(...)
  • .min(...)
  • .max(...)
  • .clamp(...)
  • .floor(...)
  • .ceil(...)
  • .round(...)
  • .trunc(...)
  • .pow(...)
  • .sqrt(...)

The current direction is that many of these may fit better in core or std instead of becoming permanent compiler intrinsics.

Intrinsics are not shell operations

Do not confuse intrinsics with shell syntax such as nil and postfix unwrap !.

For example:

ali MaybeText: opt[str]
ali Failure: err[str]

fun[] unwrap_optional(value: MaybeText): str = {
    return value!
}

fun[] unwrap_failure(value: Failure): str = {
    return value!
}

That ! surface is part of shell typing, not the intrinsic registry.

Likewise, recoverable routine calls such as:

fun[] read_code(path: str): int / str = { ... }

are handled with:

  • check(expr)
  • expr || fallback

not with shell unwrap.

Current compiler truth

The current compiler has one shared intrinsic registry crate:

fol-intrinsics

That registry is the source of truth for:

  • canonical intrinsic names and aliases
  • milestone availability (V1 / V2 / V3)
  • type-checking selection rules
  • lowering mode
  • backend/runtime role classification

The current runtime companion for implemented V1 intrinsics is:

fol-runtime

  • intrinsic names
  • aliases
  • categories
  • current milestone availability
  • deferred-roadmap classification
  • lowering mode
  • backend-facing role

So the short rule is:

  • parser recognizes intrinsic syntax
  • fol-intrinsics owns intrinsic identity
  • type checking validates intrinsic calls
  • lowering maps them to explicit IR shapes

This page should describe only the subset that is actually implemented, plus clearly marked deferred surfaces.