Appendix B

The Standard Environment

Before any user code is elaborated, an environment already exists: a collection of classes and values that the Definition presupposes and every program may use. This appendix records that standard environment, \(E_0\).

It is not a complete library reference — for that, see the Nex Reference — but the part of the standard environment on which the semantics of earlier chapters depends.

B.1The Root and the Foundational Classes

Every class conforms ultimately to Any, the root of the class hierarchy and the elaborated form of the type \(\mathsf{Any}\) (Section 4.1). A user class may write inherit Any explicitly, but does so implicitly in any case.

ClassRoutineSignatureMeaning
Anyto_string\(\to\) Stringuser-facing rendering
equalsAny \(\to\) Booleanvalue equality, used by =; overridable
clone\(\to\) Anycopy; collections override with deep copy

Four further foundational classes are present in \(E_0\):

B.2Scalar Classes

Of the type names the lexer recognises (Section 2.1), five denote scalar classes of the standard environment: Integer, Real, Char, Boolean, and String. Three more scalar classes, Byte, Integer16, and Integer32, are named by ordinary identifiers. Each inherits Comparable and Hashable, and it is through those — together with the arithmetic of the numeric classes — that the operators of Section 2.6 are given meaning on scalar values.

TypeValuesNotes
Integer64-bit integersliterals of int form; default numeric type; also written Integer64
Byte8-bit unsigned integers, \([0, 255]\)literals of byte form (200u8); element type of to_bytes
Integer1616-bit signed integersliterals of int16 form (8080i16)
Integer3232-bit signed integersliterals of int32 form (70000i32)
Realfloating-pointliterals of real form; Real / Integer yields Real
Charcharactersliterals of char form
Booleantrue, falseoperand and result of the logical operators
Stringtextliterals of string form; iterates by character

The arithmetic operators apply to numeric operands, returning the join of their types (Section 4.3): Real if either is, and Integer otherwise. An integer operand is admitted where a real is required, so total / count with total of type Real yields a Real; and an operand of type Byte, Integer16, or Integer32 is taken as the Integer of the same value, so the result is an Integer.

The comparison operators apply to Comparable operands through compare. Scalars are immutable and unstored, so identity equality == coincides with value equality = upon them (Section 5.3).

B.3Scalar Value Spaces

The previous section names the scalar classes; this one fixes the value spaces they denote — their ranges, their numeric formats, and the character model of Char and String.

The guiding decision here is worth stating plainly: the numeric tower is pinned down exactly and identically on every platform. The width of an integer, the behaviour of arithmetic on overflow, the format of a floating-point number, and the result of division are all fixed by this Definition, not left to the host.

Nex can compile to more than one platform — the Java virtual machine at present. An earlier edition left the integer width and overflow behaviour host-defined, so the same program could denote different values on different back ends. That latitude is withdrawn: a program’s arithmetic now means one thing on every conforming implementation, and a program may rely on it.

What remains host-defined is only what is genuinely internal and unobservable through the language, and never the result of an arithmetic operation.

Integer

Integer is a signed two’s-complement integer of exactly 64 bits, with range \([-2^{63},\, 2^{63}-1]\), on every platform.

Its arithmetic is checked: an operation (+, -, *, unary -, or ^) whose mathematical result lies outside that range does not wrap, widen, or lose precision — it raises an Arithmetic_Overflow exception.

Integer division / truncates toward zero, and the remainder % is the truncated remainder, taking the sign of the dividend, so a = (a / b) * b + (a % b) holds whenever the quotient exists: -7 / 2 is -3, -7 % 3 is -1, and 7 % -3 is 1. (This is the convention of C and Java, not the floored convention of Python.)

Division and remainder by zero have no integer result, and raise a Division_by_Zero exception. The one division whose mathematical result lies outside the range, \(-2^{63}\) / -1, raises Arithmetic_Overflow like any other overflow.

This is uniform across back ends: where a host’s native integers are wider or narrower than 64 bits, the implementation carries Integer as a 64-bit value and checks each operation, so a program’s integer arithmetic denotes the same value, or raises the same exception, everywhere.

The bitwise operations of Integer (bitwise_left_shift and its companions) operate on the low 32 bits, with bit 0 the least significant. They are the one part of the integer model that is not 64-bit, and they too behave identically on every platform. An integer literal that does not fit the 64-bit range is rejected (Section 2.2).

Real

Real is an IEEE 754 double-precision binary floating-point value on every platform, in both its representation and its arithmetic. A real literal denotes the nearest representable double, which may not be the decimal written, so that 0.1 + 0.2 evaluates to 0.30000000000000004 and not to 0.3.

Division follows IEEE 754: 1.0 / 0.0 yields \(+\infty\), -1.0 / 0.0 yields \(-\infty\), and 0.0 / 0.0 yields NaN — none of these raises. The remainder % on reals is likewise truncated (the fmod of C): the result has the sign of the dividend, so -7.5 % 2.0 is -1.5, and a real remainder by zero is NaN, not an exception.

Every ordering comparison (<, <=, >, >=) against NaN is false, as IEEE 754 prescribes. The special values \(\pm\infty\) and NaN are ordinary Real values, produced by arithmetic and compared by the IEEE rules — NaN is unequal to every value including itself. The standard environment provides is_nan, is_infinite, and is_finite to inspect them.

This is the deliberate asymmetry of the numeric tower: integer division by zero raises, because there is no integer to return, while real division by zero is the IEEE value. It is the split drawn by most languages that carry both an integral and a binary floating type, and it is fixed here on every platform rather than left to the host.

Byte, Integer16 and Integer32

Byte, Integer16, and Integer32 are integers of an exact width, for data whose layout the program does not choose, such as file contents and network messages. Byte is unsigned, with range \([0,\, 255]\); Integer16 and Integer32 are signed two’s-complement, with ranges \([-2^{15},\, 2^{15}-1]\) and \([-2^{31},\, 2^{31}-1]\). Their value spaces are fixed on every platform.

Each is a class in its own right. No conversion between any two of Integer, Byte, Integer16, and Integer32 is implicit, in either direction (Section 4.3). The routines to_byte, to_integer16, to_integer32, and to_integer convert explicitly: a conversion to a type whose range does not contain the value raises, and one to a wider type always succeeds. A value of a fixed-width type is equal only to a value of its own type with the same number.

Arithmetic on these types is arithmetic on Integer: each operand is taken as the Integer of the same value and the result is an Integer (Section 5.4), so it is subject to the checked-arithmetic rules above and cannot overflow the narrow type. Their ordering and equality operators apply to two values of the same type.

Their bitwise routines (bitwise_and, bitwise_or, bitwise_xor, bitwise_not, the shifts, the rotations, and bitwise_is_set, bitwise_set, bitwise_unset) operate on the whole width of the type — 8, 16, or 32 bits, with bit 0 the least significant — and yield a value of the same type. Right shift of a signed type copies the sign bit, and the logical right shift shifts in zeros; results wrap to the width, so 32767i16.bitwise_left_shift(1) is -2i16. A bit index must lie in \([0, w-1]\) for a type of width \(w\), and a shift count must be non-negative (a count of \(w\) or more shifts every original bit out, leaving zeros, or copies of the sign bit for an arithmetic right shift of a negative value); an argument outside these bounds raises. abs of the minimum value of a signed type, which has no positive counterpart, raises. (The bitwise routines of Integer keep their 32-bit behaviour, above.)

Boolean and the character model

Boolean has exactly the two values true and false.

A Char is a single Unicode code point. A character constant may be written as a literal character, as one of the named characters of Section 2.2, or as a decimal code point after #, so #65 denotes the same character as #A.

A String is a finite sequence of characters. Its length is the number of characters, it iterates character by character (Section 2.8), and char_at and chars address it by character position.

The encoding of a string is exposed only through to_bytes, which yields the UTF-8 bytes of the string as an array of Byte values, each in the range \([0, 255]\), on every platform. Its inverse is the constructor from_bytes: create String.from_bytes(\(a\)) decodes an Array[Byte] as UTF-8 and yields a String, and it raises if \(a\) is not well-formed UTF-8 rather than substituting a replacement character, so String.from_bytes(s.to_bytes()) denotes \(s\) exactly. The internal representation by which a host stores a string, and the relation between a character index and any underlying code-unit index, are host-defined and not observable except through to_bytes.

A double-quoted string literal interprets the standard backslash escapes (Section 2.2) — so "\n" is a newline and \u{h} a code point — while a single-quoted literal is raw. A control character may also be written with its character constant or obtained from the standard environment.

TypeGuaranteed by the DefinitionHost-defined
Integersigned 64-bit two’s-complement, range \([-2^{63}, 2^{63}-1]\), on every platform; checked—overflow and division by zero raise; bitwise on low 32 bits—
RealIEEE 754 double, in representation and arithmetic; division by zero yields \(\pm\infty\) / NaN per IEEE—
Chara Unicode code pointinternal representation
Byteunsigned 8-bit, \([0, 255]\); to_byte raises outside the range; arithmetic yields Integer; bitwise on 8 bits—
Integer16signed 16-bit two’s-complement, \([-2^{15}, 2^{15}-1]\); to_integer16 raises outside the range; arithmetic yields Integer; bitwise on 16 bits—
Integer32signed 32-bit two’s-complement, \([-2^{31}, 2^{31}-1]\); to_integer32 raises outside the range; arithmetic yields Integer; bitwise on 32 bits—
Stringsequence of characters; length in characters; to_bytes is UTF-8 as Bytes; from_bytes its strict inverseinternal encoding; character-to-code-unit mapping

B.4Collection Classes

Three generic collection classes are present in \(E_0\), each a Cursor source so that across may traverse it.

Array[T]

An ordered, growable sequence. The display [e₁, …, eₙ] is a derived form constructing an Array (Appendix C). Representative routines: get(i) \(\to\) T, add(x) to append, length \(\to\) Integer.

Map[K, V]

An association of keys to values. The display {k₁: v₁, …} is a derived form; {} is the empty map. Representative routines: get(k) \(\to\) V, put(k, v), iteration yielding [key, value] pairs.

Set[T]

An unordered collection of distinct values. The display #{e₁, …} is a derived form; #{} is the empty set, which must be written with the explicit set-display syntax to distinguish it from the empty map.

The constructor from_array builds a set from an array, discarding duplicates. The routines union, intersection, difference, contains, and size provide the usual set algebra.

Byte_Array (a library class)

Byte_Array, loaded with intern data/Byte_Array, is not a class of \(E_0\): it is a shipped library class, described in the reference rather than in this Definition. It is a fixed-size, mutable sequence of Bytes that a host may keep packed, one byte per element, and it is a Cursor source whose items are Bytes, so across may traverse it. Unlike Array[Byte] it has no add; its slice, concat, and copy yield new values that share no storage with the original.

B.5Concurrency Classes

The classes underlying Chapter 6 are part of the standard environment.

ClassRoutineMeaning
Task[T]awaitblock until done; yield result of type T
await(ms)timed await; nil on timeout
is_done, is_cancelledcompletion and cancellation state
cancelrequest cancellation
await_any, await_all(class methods) wait on a collection of tasks
Channel[T]send(v), receiveblocking communication (Section 6.2)
try_send, try_receivenon-blocking variants used by select
with_capacity(n)(constructor) a buffered channel
close, is_closed, size, capacitychannel state

A spawn whose block assigns result of type \(T\) yields a Task[T]; one that does not yields a plain Task.

B.6Built-in Values and Effects

A handful of values are bound in the top-level bindings of \(E_0\). Chief among them is print, which renders its argument (via to_string) and writes it to the standard output — a host effect, and the principal observable behaviour of many programs.

The Console class provides finer output control, including new_line. A line break in output may be written as the \n escape in a double-quoted string (Section 2.2), or produced with Console.new_line or the character constant #newline.

The Process class provides a further host effect. create Process (or create Process.self) is a handle to the running program itself, exposing its environment (getenv, setenv) and launch arguments (command_line). create Process.command(…) instead builds a child operating-system process, configured before start and afterward controlled and communicated with through its own environment, standard streams, and lifecycle (wait, terminate).

The exception values raised by the language itself — on a nil dereference, a failed contract, a failed runtime argument check, an out-of-range collection access — are also part of the environment. They are the values a rescue block receives in exception (Section 5.7).