lib/var.x
Variant type for dynamic typing.
Primary API
| Function | Summary |
|---|---|
x2c_var_descriptor_index | Returns value’s dense built-in descriptor row, or -1 when it has none. |
x2c_var_tag_descriptor_index | Returns a built-in object or Symbol tag’s descriptor row, or -1. |
Var.is | Reports whether value has the requested runtime tag. |
Var.is_floating | Reports whether v belongs to the floating runtime family. |
Var.is_integer | Reports whether v belongs to the integer runtime family. |
Var.is_nil | Reports whether v is the typed empty List. |
Var.is_null | Reports whether v is the all-zero Null value. |
Var.is_object | Reports whether v holds a registered boxed object. |
Var.is_pointer | Reports whether v holds a native pointer. |
Var.is_reference | Reports whether v holds a pointer to a boxed handle. |
Var.is_void | Reports whether v is the absence sentinel void. |
Var.new | Constructs a Var with tag from its tag-directed variadic payload. |
Var.null | Returns Null, the all-zero Var that stands for external nil. |
Functions
x2c_var_descriptor_index
int x2c_var_descriptor_index(Var value)
Returns value’s dense built-in descriptor row, or -1 when it has none.
The row is the tag’s offset from <array> and includes the Symbol row.
Numbers, pointers, references, custom tags, and invalid encodings have no
built-in row.
Source: lib/var.x:209
x2c_var_tag_descriptor_index
int x2c_var_tag_descriptor_index(Symbol tag)
Returns a built-in object or Symbol tag’s descriptor row, or -1.
Source: lib/var.x:222
Var
Var.is
int Var.is(Var var, Symbol tag)
Reports whether value has the requested runtime tag.
This tests one family. A Var holding an unsigned char answers 0 for
<i32> even though both are integers. When any integer will do, ask
Var.is_integer, or compare Var.kind.
As with Var.tag, validate externally constructed bits first: an invalid
encoding uses the historical <f64> fallback.
Source: lib/var.x:284
Var.is_floating
int Var.is_floating(Var v)
Reports whether v belongs to the floating runtime family.
True for <f32>, <f64>, and <ldouble>, and for the discrete <nan>,
<+inf>, and <-inf> values. Read the payload with Var.floating, or
with Var.long_double_value when the tag is <ldouble> and the extra
precision matters.
Source: lib/var.x:309
Var.is_integer
int Var.is_integer(Var v)
Reports whether v belongs to the integer runtime family.
True for every signed and unsigned integer family from <u8> through
<ullong>, including the scope-owned wide boxes. Symbols are a kind of
their own and answer 0 here, even though Var.integer returns a Symbol’s
numeric value.
Source: lib/var.x:317
Var.is_nil
int Var.is_nil(Var v)
Reports whether v is the typed empty List.
The empty List is a native null pointer carrying the <list> tag.
Var.is_null answers 0 and Var.tag answers <list>. Every %() and
every exhausted List.cdr yields this one value, so an identity test on
the bits is a valid emptiness test.
Source: lib/var.x:381
Var.is_null
int Var.is_null(Var v)
Reports whether v is the all-zero Null value.
Null is a value: the null pointer and the external nil. It is legal
collection data, iterating a List can return it, and it is false in a
condition. Its tag decodes as <p48>, so there is no
dedicated null family for Var.is to match.
Write Null as (Var) { .u64 = 0 }. An unresolved C NULL macro also
converts to this value when its x2c target is Var.
Source: lib/var.x:364
Var.is_object
int Var.is_object(Var v)
Reports whether v holds a registered boxed object.
True for the builtin classes such as String, List, Array, Map,
File, and Iter, and for any tag registered with
Var.register_object_tag. A
pointer to a handle, such as <string*>, is <reference> instead and
answers 0 here.
Source: lib/var.x:331
Var.is_pointer
int Var.is_pointer(Var v)
Reports whether v holds a native pointer.
Source: lib/var.x:320
Var.is_reference
int Var.is_reference(Var v)
Reports whether v holds a pointer to a boxed handle.
Source: lib/var.x:322
Var.is_void
int Var.is_void(Var v)
Reports whether v is the absence sentinel void.
An API returns void to say there is nothing here. It is excluded from
every collection and iterator value domain: it cannot be pushed into an
Array or stored in a Map. Equality and identity still inspect it. Two
sentinels compare equal and identical, while one sentinel and one
ordinary value compare unequal. Truthiness, hashing, ordering, conversion,
and iteration still terminate on a void operand.
Null is the all-zero Var: legal collection data and false in a
condition. A void result may mean missing, exhausted, or invalid;
each API documents its meaning.
Var raw = (Var) { .u64 = 0 };
printf("null: void=%d null=%d\n", raw is void, raw.is_null());
printf("void: void=%d null=%d\n", void is void, void.is_null());
This is the test that accepts a void argument.
Source: lib/var.x:353
Var.new
Var Var.new(Symbol tag, ...)
Constructs a Var with tag from its tag-directed variadic payload.
The tag chooses how the argument is read, so pass exactly the C type the
tag names: an int for <i32>, an unsigned long for <u48>, a
double for <f64>, a long double for <ldouble>, a pointer for any
pointer, reference, or object family, and a Symbol’s numeric value for
<symbol>. <void> consumes no payload. Variadic arguments are not
converted for you. Assignment, Var boxed = 42;, is the usual way to box
a value. Use this constructor when the tag is computed at run time.
Immediate values are stored inline. Wide numeric tags allocate a box in the
active Scope. Pointer, reference, and object tags borrow the address and
encode only its low 48 bits; they do not take ownership, and the address
must satisfy the alignment implied by the tag. A tag previously registered
with Var.register_object_tag is accepted too and requires an 8-byte-
aligned pointer.
Raises: <bad-target> when tag is neither known nor registered,
<conv-range> when a scalar does not fit the tag’s payload width,
<bad-arg> when an <array> or <map> pointer is null, <alloc-fail>
when a wide box cannot be allocated, and <bad-enc> when a box address
cannot be represented or a custom object pointer is not 8-byte
aligned.
Source: lib/var.x:642
Var.null
Var Var.null(void)
Returns Null, the all-zero Var that stands for external nil.
Generated call adapters return it for a void target.
Source: lib/var.x:369
Advanced and interop API
| Function | Summary |
|---|---|
Var.box_long | Boxes a long into a scope-owned <long> value. |
Var.box_long_double | Boxes a long double into a scope-owned <ldouble> value. |
Var.box_long_long | Boxes a native signed long-long value without losing precision. |
Var.box_ulong | Boxes an unsigned long into a scope-owned <ulong> value. |
Var.box_ulong_long | Boxes a native unsigned long-long value without losing precision. |
Var.encoding_valid | Reports whether value has a valid structural Var encoding. |
Var.floating | Returns v’s payload as a double when its tag is floating, or 0.0. |
Var.integer | Returns v’s payload as a long when its tag is integral, or 0. |
Var.integer_compare | Orders the integer payloads of a and b, returning -1, 0, or 1. |
Var.integer_floating_compare | Orders an integer-kinded integer against a floating floating. |
Var.kind | Returns the Symbol naming the coarse category of v’s payload. |
Var.known_tag | Reports whether tag has a built-in encoding or registered custom row. |
Var.long_double_value | Returns the payload of an <ldouble> box, or 0.0 if v has another tag. |
Var.long_long_value | Returns an <llong> box’s signed payload, or 0 for another tag. |
Var.long_value | Returns the payload of a <long> box, or 0 if v has a different tag. |
Var.parse | Parses str as source text of kind kind and returns the boxed value. |
Var.pointer | Returns the raw address stored in v, or NULL if it holds no address. |
Var.register_object_tag | Reserves or returns a process-lifetime row for custom boxed-object tag. |
Var.tag | Returns the Symbol naming the exact family of v’s payload. |
Var.ulong_long_value | Returns a <ullong> box’s unsigned payload, or 0 for another tag. |
Var.ulong_value | Returns the payload of a <ulong> box, or 0 if v has a different tag. |
Var.wide_compare | Orders two wide boxes carrying the same tag, returning -1, 0, or 1. |
Var.wide_equal | Reports content equality for supported wide scalar boxes. |
Var.wide_hash | Returns a supported wide scalar box’s content hash, or 0 otherwise. |
Var
Var.box_long
Var Var.box_long(long value)
Boxes a long into a scope-owned <long> value.
A Var is eight bytes, and the tag consumes some of them. The five widest
native families, long, unsigned long, long long, unsigned long long, and long double, cannot be stored inline. Boxing one allocates a
small immutable box in the active scope, and the result lives for that
scope’s lifetime like any other scope allocation.
Two boxes of the same number are equal but not identical. On Var
operands == is Var.equal, which compares payloads, while === is
Var.same, which compares the 64 bits and therefore the box addresses.
Var five = Var.box_long(5L);
Var also = Var.box_long(5L);
printf("equal=%d same=%d tag=%s\n", five == also, five === also,
five.tag().str());
Raises: <alloc-fail> when the box cannot be allocated and <bad-enc>
when its address cannot be represented in a Var. Before Error
initialization, allocation failure uses the raw fatal floor.
Source: lib/var.x:470
Var.box_long_double
Var Var.box_long_double(long double value)
Boxes a long double into a scope-owned <ldouble> value.
This is the only family that keeps a full long double payload. The tag
names that C family; it does not promise a bit width. Var.floating
narrows the value to double; Var.long_double_value preserves the
extra precision.
Raises: <alloc-fail> when the box cannot be allocated and <bad-enc>
when its address cannot be represented in a Var. Before Error
initialization, allocation failure uses the raw fatal floor.
Source: lib/var.x:522
Var.box_long_long
Var Var.box_long_long(long long value)
Boxes a native signed long-long value without losing precision.
Raises: <alloc-fail> when the box cannot be allocated and <bad-enc>
when its address cannot be represented in a Var. Before Error
initialization, allocation failure uses the raw fatal floor.
Source: lib/var.x:496
Var.box_ulong
Var Var.box_ulong(unsigned long value)
Boxes an unsigned long into a scope-owned <ulong> value.
<ulong> is a distinct family from <long>, so a box made here never
compares equal to one made by Var.box_long even when both hold the same
bit pattern; Var.wide_equal requires matching tags. Read it back with
Var.ulong_value, the one reader that returns the payload unsigned.
Raises: <alloc-fail> when the box cannot be allocated and <bad-enc>
when its address cannot be represented in a Var. Before Error
initialization, allocation failure uses the raw fatal floor.
Source: lib/var.x:485
Var.box_ulong_long
Var Var.box_ulong_long(unsigned long long value)
Boxes a native unsigned long-long value without losing precision.
Raises: <alloc-fail> when the box cannot be allocated and <bad-enc>
when its address cannot be represented in a Var. Before Error
initialization, allocation failure uses the raw fatal floor.
Source: lib/var.x:507
Var.encoding_valid
int Var.encoding_valid(Var value)
Reports whether value has a valid structural Var encoding.
An address-bearing encoding must still refer to live storage of the right
type; wide encodings in particular require a readable Scope-owned box.
Source: lib/var.x:231
Var.floating
double Var.floating(Var v)
Returns v’s payload as a double when its tag is floating, or 0.0.
Handles <f32>, <f64>, and <ldouble>, and reconstructs NaN,
+Inf, and -Inf from their discrete tags. An <ldouble> payload is
narrowed to double; call Var.long_double_value to keep the full
precision.
Like the other raw payload readers this one is silent. An integer, a
String, or void reads as 0.0, and nothing distinguishes that from a
stored zero. When the tag is not already known, convert with
Var.convert in lib/varconvert.x, which raises a conversion failure;
the scalar-named readers such as Var.double do this for you.
An unhandled tag yields 0.0.
Source: lib/var.x:707
Var.integer
long Var.integer(Var v)
Returns v’s payload as a long when its tag is integral, or 0.
This reads the payload; it does not convert. It decodes every integer
family, from <u8> through <ullong>, including the scope-owned boxes, and
returns 0 for a tag it does not handle. A double reads as 0, and so does
a String; nothing reports the mismatch. A <ullong> or <llong>
payload is truncated to long, and a <ulong> above LONG_MAX comes
back negative. Var.ulong_value preserves the unsigned payload. A
Symbol reads as its numeric Symbol value.
When the tag might not be what you expect, convert instead of reading.
Var.convert in lib/varconvert.x raises a conversion failure.
Assigning a Var to an int or a long, and the scalar-named readers
such as Var.int, go through Var.convert too.
Var text = %"ada";
Var small = (unsigned char) 44;
printf("small=%ld text=%ld\n", small.integer(), text.integer());
An unhandled tag yields 0.
Source: lib/var.x:752
Var.integer_compare
int Var.integer_compare(Var a, Var b)
Orders the integer payloads of a and b, returning -1, 0, or 1.
Each value is decomposed into a sign and an unsigned magnitude first, so
the whole integer range orders correctly, including a <ullong> above
LONG_MAX against a negative <long>. Subtracting in a fixed-width integer
type could overflow.
Both arguments are assumed to be integer-kinded. Another value is decoded
by Var.integer, which reads it as 0. Confirm with Var.is_integer when
the kinds are not known.
Source: lib/var.x:932
Var.integer_floating_compare
int Var.integer_floating_compare(Var integer, Var floating)
Orders an integer-kinded integer against a floating floating.
Returns -1, 0, or 1 for less, equal, and greater. The integer is never
converted to floating point, so a large <ullong> and a nearby double
order by their true values. A floating value with a fractional part is
never equal to an integer.
+Inf is greater than every integer and -Inf is less than every
integer. NaN is not ordered and reports -1. Test the tag for <nan>
first if that distinction matters.
Source: lib/var.x:960
Var.kind
Symbol Var.kind(Var v)
Returns the Symbol naming the coarse category of v’s payload.
The categories are <integer>, <floating>, <symbol>, <object>,
<pointer>, <reference>, and <void>, and every tag belongs to exactly
one. Use kind when a group of families is treated alike. All twelve
integer widths answer <integer>, and every builtin class handle such as
String, List, Map, and File answers <object>.
<pointer> means a raw C pointer such as <u8*>, while <reference>
means a pointer to an object handle such as <string*>. NaN and the
infinities are <floating>. Only void is <void>.
Source: lib/var.x:297
Var.known_tag
int Var.known_tag(Symbol tag)
Reports whether tag has a built-in encoding or registered custom row.
Source: lib/var.x:61
Var.long_double_value
long double Var.long_double_value(Var v)
Returns the payload of an <ldouble> box, or 0.0 if v has another tag.
This is the only reader that preserves a long double. Every other
floating tag, <f64> included, yields 0.0 here instead of being widened.
Var.floating is the general floating reader.
A tag mismatch yields 0.0.
Source: lib/var.x:824
Var.long_long_value
long long Var.long_long_value(Var v)
Returns an <llong> box’s signed payload, or 0 for another tag.
Source: lib/var.x:810
Var.long_value
long Var.long_value(Var v)
Returns the payload of a <long> box, or 0 if v has a different tag.
The test is on the tag, so a <ulong>, <llong>, or <i32> value
reads as 0 with no complaint. Var.integer accepts any integer family;
use this one when the tag is known and the payload must survive intact.
A tag mismatch yields 0.
Source: lib/var.x:795
Var.parse
Var Var.parse(String str, Symbol kind)
Parses str as source text of kind kind and returns the boxed value.
The kinds understood are <int>, <float>, <double>, <string>,
<symbol>, and <char>. For <string>, matching %"..." or "..."
delimiters are removed; unquoted input is also accepted, and either form
is unescaped. <char> expects 'a' complete with its quotes and produces
an <i32>. Both <float> and <double> produce an <f64>; there is no
path here to <f32>.
Integer and floating kinds require the whole input to parse, apart from
surrounding whitespace. Malformed input, trailing text, a value outside
the native reader’s range, or an <int> outside int range returns
void, so a parsed numeric zero remains distinguishable from failure.
<symbol> accepts the leading atom or <...> Symbol literal and ignores
trailing text; no recognized leading Symbol produces the zero Symbol. A
kind this function does not handle returns void, and a bad character
literal gives <i32> -1.
Var count = Var.parse(%"42", <int>);
Var broken = Var.parse(%"abc", <int>);
Var refused = Var.parse(%"3", <u8>);
printf("%s=%s %s=%s refused=%d\n", count.tag().str(), count,
broken.tag().str(), broken, refused is void);
Raises: <alloc-fail> while constructing String or quoted-Symbol
output.
Source: lib/var.x:1061
Var.pointer
void *Var.pointer(Var v)
Returns the raw address stored in v, or NULL if it holds no address.
Every pointer, reference, and object family shares one decoder: the
payload is masked free of the subtype bits its family reserves, so the
result is the stored low-48-bit address for a <u8*>, a <string>
handle, and a registered custom object. The returned pointer is borrowed;
this operation does not retain it or change its lifetime. A value that is
not address-shaped, such as an integer, a double, a Symbol, a wide box,
or void, reads as NULL, and nothing distinguishes that from a stored
null pointer.
Nothing here proves that an accepted address is live or came from the
right constructor; that remains the typed API’s precondition. This raw
decoder
also accepts some reserved pointer-shaped bit patterns. Validate external
bits with Var.encoding_valid, then confirm the family with Var.tag or
Var.is before trusting the result.
Source: lib/var.x:1015
Var.register_object_tag
int Var.register_object_tag(Symbol tag)
Reserves or returns a process-lifetime row for custom boxed-object tag.
Before registration freezes, a repeated custom tag returns its existing
row; NULL, a built-in tag, or a full 32-row registry returns -1 without
changing the registry. Registration must finish before the first successful
Thread.start; afterward it raises <bad-state>. Native registry-mutex
failure aborts.
Source: lib/var.x:240
Var.tag
Symbol Var.tag(Var v)
Returns the Symbol naming the exact family of v’s payload.
The tag names one family: <i32>, <string>, <f64>, <symbol>, or a
registered custom object tag. Built-in families have one row in the
encoding table at the top of this file. Use Var.kind for a group of
families and Var.is to test one family.
Raw Null, the all-zero Var written (Var) { .u64 = 0 }, is a native
null pointer. It decodes as <p48>; there is no dedicated null family.
Test it with Var.is_null. An encoding matching no reserved row decodes
as <f64>, since the shifted double range covers everything left over.
Var raw = (Var) { .u64 = 0 };
printf("%s %s\n", raw.tag().str(), void.tag().str());
Source: lib/var.x:270
Var.ulong_long_value
unsigned long long Var.ulong_long_value(Var v)
Returns a <ullong> box’s unsigned payload, or 0 for another tag.
Source: lib/var.x:814
Var.ulong_value
unsigned long Var.ulong_value(Var v)
Returns the payload of a <ulong> box, or 0 if v has a different tag.
Prefer this to Var.integer for a <ulong>: the general reader casts the
payload to long, which reinterprets anything above LONG_MAX as
negative, while this reader returns the unsigned value. A tag that is
not <ulong> yields 0 silently, so test with Var.is when the tag is in
doubt.
A tag mismatch yields 0.
Source: lib/var.x:806
Var.wide_compare
int Var.wide_compare(Var a, Var b)
Orders two wide boxes carrying the same tag, returning -1, 0, or 1.
Integer families delegate to Var.integer_compare. An <ldouble> pair
compares as long double and falls back to a byte comparison when neither
operand is less than the other, so distinct NaN representations still
order deterministically.
0 means equal, and it is also the answer for mismatched tags and for an argument that is not a wide box. Check the tags first, or use the relational operators, which reach the runtime’s full ordering.
Source: lib/var.x:984
Var.wide_equal
int Var.wide_equal(Var a, Var b)
Reports content equality for supported wide scalar boxes.
Boxed <long>, <ulong>, <llong>, <ullong>, and <ldouble> values
are separate allocations, so bit identity says nothing about their
contents. This is the payload comparison that == uses for those tags. An
<ldouble> pair is compared byte for byte, so two NaNs sharing one
representation compare equal here.
A 0 result means “not equal as wide boxes”. It also covers an argument
that is not a wide box and a pair whose tags differ. For a general
equality test use ==, which reaches Var.equal and covers every
family.
Source: lib/var.x:867
Var.wide_hash
unsigned Var.wide_hash(Var v)
Returns a supported wide scalar box’s content hash, or 0 otherwise.
Source: lib/var.x:834
Runtime-internal callables
These callables connect runtime translation units. They are documented for source readers but are not supported as user API.
| Function | Summary |
|---|---|
Var.clone_wide | Clones the wide numeric value into a new box in the active Scope. |
Var.custom_descriptor_index | Returns a registered custom object’s row, or -1 for another value. |
Var.move_wide_to | Moves a wide numeric box into the destination Scope held by scope. |
Var.wide_owner | Returns the Scope owning a live wide numeric box, or NULL otherwise. |
Var
Var.clone_wide
Var Var.clone_wide(Var value)
Clones the wide numeric value into a new box in the active Scope.
The clone compares equal but not identical to value. Returns void when
value is not wide.
Raises: <alloc-fail> when the clone cannot be allocated, or <bad-enc>
if its address cannot be represented in a Var.
Source: lib/var.x:534
Var.custom_descriptor_index
int Var.custom_descriptor_index(Var value)
Returns a registered custom object’s row, or -1 for another value.
Source: lib/var.x:216
Var.move_wide_to
Self Var.move_wide_to(Self value, Scope *scope)
Moves a wide numeric box into the destination Scope held by scope.
The box is not copied and value keeps its identity. If the destination
slot is NULL, a new Scope is created there. Returns value unchanged for
another family.
For a wide value, raises <bad-arg> when scope is NULL, or
<alloc-fail> when a new destination Scope cannot be allocated.
Ownership
is unchanged on failure.
Source: lib/var.x:559
Var.wide_owner
Scope Var.wide_owner(Var value)
Returns the Scope owning a live wide numeric box, or NULL otherwise.
Source: lib/var.x:566
Design notes
Var encodes dynamic values in one 64-bit word. Its layout uses the IEEE
754 double-precision ranges:
0000:0000:0000:0000 - 7FEF:FFFF:FFFF:FFFF : [+0.0, MAX_DOUBLE] 7FF0:0000:0000:0000 - 7FFF:FFFF:FFFF:FFFF : [+Inf, +NaNs] 8000:0000:0000:0000 - FFEF:FFFF:FFFF:FFFF : [-0.0, MIN_DOUBLE] FFF0:0000:0000:0000 - FFFF:FFFF:FFFF:FFFF : [-Inf, -NaNs]
Special-value ranges carry custom tags and payloads. An f64 is shifted by
(1 << 52); shifted -DBL_MAX has a reserved encoding because all-one bits
mean void. Native pointers retain their raw lower 48 address bits.
The 64-bit Var layout is structured as follows:
Tag : Payload Tag : Payload bits Use
0000:0000:0000:0000 - 0000:FFFF:FFFF:FFFF 48+0 void*
0001:0000:0000:0000 - 0001:FFFF:FFFF:FFFF 48+0 u8*
0002:0000:0000:0000 - 0002:FFFF:FFFF:FFFF 48+0 i8*
0003:0000:0000:0000 - 0003:FFFF:FFFF:FFFF 47+1 u16*,i16*
0004:0000:0000:0000 - 0004:FFFF:FFFF:FFFF 46+2 u32*,i32*,f32*,ulong*
0005:0000:0000:0000 - 0005:FFFF:FFFF:FFFF 45+3 long*,f64*,ldouble*,
llong*,ullong*,long,
ulong,llong
0006:0000:0000:0000 - 0007:FFFF:FFFF:FFFF 45+3 builtin pointer families,
ullong,ldouble
0008:0000:0000:0000 - 000B:FFFF:FFFF:FFFF 45+3 builtin (Class) x 32
000C:0000:0000:0000 - 000F:FFFF:FFFF:FFFF 45+3 builtin (Class ) x 32
0010:0000:0000:0000 - 7FFF:FFFF:FFFF:FFFF 64+0 f64 > 0 + (52<<1)
8000:0000:0000:0000 - 8000:FFFF:FFFF:FFFF 48+0 u48
8001:0000:0000:0000 - 8001:FFFF:FFFF:FFFF 48+0 i48
8002:0000:0000:0000 - 8002:FFFF:FFFF:FFFF 32+16 32-bit vals, 16-bit tag
8003:0000:0000:0000 - 8003:FFFF:FFFF:FFFF 32+16 Special values
8004:0000:0000:0000 - 800B:FFFF:FFFF:FFFF 50+1 Symbols
800C:0000:0000:0000 - 800F:FFFF:FFFF:FFFF 45+3 user (Class) 32
8010:0000:0000:0000 - FFFF:FFFF:FFFF:FFFF 64+0 f64 < 0 + (52<<1)
A Var preserves its runtime kind. void is excluded from value-bearing
protocols.
Tests and examples
make verify (unittest/test-var.x) and make examples (type-conversion-matrix).