API documentation

nanvm-lib

A NaN-boxing VM for FunctionalScript implemented in Rust. See todo/fjs-nanvm-integration.md and nanvm/lib/todo/ for design notes.

JS Operator Implementation Status

Operators on Any<A> (the top-level VM value type).

Arithmetic

OperatorDescriptionAny<A>Notes
+Addition / concat✅any/add.rs — ToPrimitive, then string concat or numeric addition; rejects mixed number/bigint
-Subtraction✅any/sub.rs — ToNumeric coercion; rejects mixed number/bigint
*Multiplication✅any/mul.rs → Numeric * Numeric
/Division✅bigint/div.rs — the quotient of BigInt::div_mod (schoolbook binary long division, shared with %), truncates toward zero; Number uses Rust's own /; Any-level dispatch in any/div.rs
%Remainder✅bigint/rem.rs — the remainder of BigInt::div_mod, sign follows the dividend; Number uses Rust's own %; Any-level dispatch in any/rem.rs
**Exponentiation✅bigint/pow.rs — exponentiation by squaring, throws on a negative exponent; numeric.rs implements Number::exponentiate's two departures from f64::powf/C99 pow (a NaN exponent, and an infinite exponent against a base of magnitude 1, are both NaN); not a core::ops trait (Rust has no exponentiation operator), so it's Any::pow/Numeric::pow, methods rather than operators

Unary

OperatorDescriptionAny<A>Notes
-Unary minus✅any/neg.rs — Neg for Any<A>
+Unary plus✅Any::unary_plus() method (coerces to number)
!Logical NOT✅any/not.rs — Not for Any<A>, via the new ToBoolean coercion (boolean_coercion.rs)
~Bitwise NOT✅Numeric::bitwise_not()/Any::bitwise_not() methods (no Rust operator to spell it with — Not is already !'s); Number: ToInt32 then bitwise-negate; BigInt: -x - 1, the exact spec identity, reusing Neg/Sub rather than a new algorithm
typeofType of✅any/typeof_.rs — Any::typeof_() method (no Rust operator to spell it with; the trailing underscore resolves the collision with Rust's own reserved typeof, the same convention FJS and JS use); returns "undefined", "object" (null and both container types), "boolean", "number", "bigint", "string", or "function"

Comparison

OperatorDescriptionAny<A>Notes
===Strict equality✅any/partial_eq.rs — PartialEq for Any<A>
!==Strict inequality✅Provided by PartialEq
<Less than✅any/relational.rs — IsLessThan: ToPrimitives both sides, lexicographic by UTF-16 code unit if both are strings, StringToBigInt if one side is a BigInt (decimal, 0x, 0o, and 0b literals), otherwise exact Number/BigInt comparison (via IEEE 754 bit decomposition, not a lossy f64 round-trip); NaN anywhere gives false in both directions
<=Less than or equal✅Any::le — !(y < x), except NaN anywhere stays false
>Greater than✅Any::gt — the reversed <: y < x
>=Greater or equal✅Any::ge — !(x < y), except NaN anywhere stays false

Bitwise

OperatorDescriptionAny<A>Notes
&AND✅any/bitand.rs — BitAnd for Any<A>; Number: ToInt32 each side, then native &; BigInt: bigint/bitand.rs — infinite-precision two's-complement AND over words (see bigint/mod.rs's bitwise_op); rejects mixed number/bigint
|OR✅any/bitor.rs — BitOr for Any<A>; same coercion as &; BigInt: bigint/bitor.rs
^XOR✅any/bitxor.rs — BitXor for Any<A>; same coercion as &; BigInt: bigint/bitxor.rs
<<Left shift✅Shl for Any<A>; Number: ToInt32 the left side, ToUint32(rhs) & 0x1F the shift count, then native <<; BigInt: bigint/shl.rs — x * 2^y exactly (arbitrary precision, no 32-bit wrap), delegating to >> for a negative y; throws a RangeError if the result would exceed fjs/types/bigint/module.f.mjs's maxLength (the smallest BigInt size limit across the engines FunctionalScript targets); the result's own buffer is reserved fallibly rather than through ordinary (abort-on-failure) Vec growth, though the container construction it's then handed to is not yet — see that file's own TODO
>>Signed right shift✅Shr for Any<A>; same Number coercion as <<, arithmetic (sign-extending) shift; BigInt: bigint/shr.rs — floor division by 2^y (not truncation: a negative BigInt rounds away from zero when a set bit is shifted out, so -1n >> 1_000_000n stays -1n), delegating to << for a negative y
>>>Unsigned right shift✅Any::unsigned_right_shift() method (no Rust operator for it — >> on a signed type is arithmetic, and there's no unsigned counterpart); Number: ToUint32 both sides (never ToInt32 — the one shift where the left operand's sign bit is never preserved); BigInt: always throws, since arbitrary-precision integers have no fixed width to be "unsigned" relative to

Logical

OperatorDescriptionAny<A>Notes
&&Logical AND✅any/and.rs — Any::logical_and() method (no core::ops trait fits: Rust's own && takes bool); the right operand is a thunk, impl FnOnce() -> Result<Any<A>, Any<A>>, established only if the left is truthy, so false && (1n / 0n) is false as in JS
||Logical OR✅any/or.rs — Any::logical_or() method, same reason and same thunk, established only if the left is falsy
??Nullish coalescing✅any/nullish_coalescing.rs — Any::nullish_coalescing() method, checked by matching on Unpacked::Nullish directly (allocation-free), not ToBoolean; the same thunk, established only if the left is nullish

Other

OperatorDescriptionAny<A>Notes
?:Conditional✅any/conditional.rs — Any::conditional() method; the EDAG's ["?:", c, t, e] (op3Id), covered by the corpus as a Group3. Both arms are thunks, impl FnOnce() -> Result<Any<A>, Any<A>>, and exactly the selected one is established — the corpus's unreached cases prove it on this side as amnesia's proof does on the FunctionalScript side
entryThe entry helper✅any/mod.rs — Any::entry(key) method, the body of the language's helper, (a, b) => { const x = Object.getOwnPropertyDescriptor(a, b); return x?.enumerable ? x.value : undefined; }, which the EDAG's ['entry'] node is as a function value and the Rust output binds as a static function of two arguments: the value of the enumerable own property the key names, an Object's field by last-duplicate-wins flat lookup (object/own_property.rs), an Array's element or a String's code unit by its canonical index string (array/member_access.rs, string/member_access.rs) — and undefined for a length, and for anything of a function, Number, Boolean or BigInt, as for every absent key; no getter invocation and no prototype chain; the receiver is checked before the key converts, so a nullish one throws whatever the key, and the key converts by to_string as ToPropertyKey does
hasEntryThe hasEntry helper✅any/mod.rs — Any::has_entry(key) method, the body of the helper beside entry, (a, b) => { const x = Object.getOwnPropertyDescriptor(a, b); return x?.enumerable === true; }, the EDAG's ['hasEntry'] as a function value: the presence of the same enumerable own property, true for one holding undefined, by the one lookup entry makes, with the same receiver check and key conversion
. / []Member access✅any/dot.rs — Any::dot(a, key).end(), the one property read: dot dispatches to array/member_access.rs, string/member_access.rs, object/member_access.rs and function/member_access.rs; Number/Boolean/BigInt have no own properties, so every key on one answers undefined; no prototype chain and no getters; a built-in member function is reached by a call, never a read (the chains row below)
?. / ?.() / chainsOptional chaining, method calls✅lambda/mod.rs — the EDAG's three continuation types (fjs/edag/README.md, Chains) as PropertyLambda, OptionLambda and OptionPropertyLambda, opened by Any::dot, Any::option_dot (any/dot.rs) and Any::option_call (any/option_call.rs), one method per legal step, closed by end or end_call; a key or arguments a region may skip are thunks, and every step is guard, thunk, operation; a call step calls an own property or element first, then the receiver type's built-in member function (lambda/method.rs, toString, and every Array member function the compiler admits (array/README.md), and String's and Number's (string/README.md) — todo/member-functions.md), then throws as JavaScript does
NumberThe Number cast✅any/mod.rs — Any::number(): ToNumeric, then a bigint converted to the nearest Number (bigint/to_f64.rs, ties to even, Infinity past the range); the EDAG's ['Number', e]. Differs from unary + only on a bigint, which + refuses
inEntry check✅"k" in o, the key a string literal naming no prototype property but length: the enumerable own entry of an object, an array or a function, or the length an array and a function own, a TypeError on a primitive. Answered by the runtime's body, runtime.in in fjs/js/runtime, which the compiled spelling calls through the runtime value — the naive path of DESIGN.md §13, proved by the corpus's in_x and in_0 groups; a native substitution is todo/in-native-substitution.md. The general operator, a computed key or a prototype name, stays out: it consults the prototype chain, which the language does not have (record).
instanceofInstance check✅any/instanceof_.rs — Any::instanceof_(x, Constructor::Array), one Dispatch over Unpacked: true of an Array, false of everything else, null and undefined included; never throws, since the right side is a Constructor variant by construction — the EDAG's ['instanceof', exp, 'Array'], whose constructor is a name from a closed list, not a value, so Constructor has one variant until the VM has a Map or a Set for the test to be true of

Number-cast scope

Any::number implements Number(value), including bigint rounding and overflow. Any::unary_plus and the internal ToNumber coercion remain separate: both reject bigints.

The Rust printer accepts ['Number', exp] both as a standalone operation and as a computed index in direct EDAG input, for example ['.', receiver, ['Number', key]]. It evaluates the receiver before the key; inside ?. and a |. continuation, the cast and its operand stay inside the guarded key thunk. These paths are covered by computedIndex in fjs/edag/rust/proof.f.mjs.

This is VM and EDAG-printer support. The source parser still does not admit Number(x) or a[Number(k)]; source admission remains tracked by spec/todo/2330-property-accessor.md.

Coercions

CoercionStatusLocation
To number✅number_coercion.rs
To string✅string_coercion.rs — Number::toString's round-half-to-even tie-break (round_tie_to_even) is decided by exact BigInt comparison, not a floating-point heuristic
To boolean✅boolean_coercion.rs — never throws, unlike the others
To primitive✅primitive_coercion.rs
To numeric✅numeric_coercion.rs
To int32✅int32_coercion.rs — ToInt32, operating on the already-coerced f64 a Number:: bitwise/shift op needs
To uint32✅int32_coercion.rs — ToUint32, shares its modulo 2^32 step with ToInt32; used for <</>>'s shift-count operand and both of >>>'s operands