nova-std 0.3.0

The Nova language's standard library sources (internal to the nova toolchain)
Documentation
//! Padding and fixed-point formatting, as methods on `Int`, `String` and
//! `Float`.
//!
//! `pad` writes an integer padded with zeros, `pad_left` and `pad_right`
//! align a string with spaces, and `fixed` writes a float with a set number
//! of decimal places. The padding methods never truncate: a value already as
//! wide as asked comes back as it is.
//!
//! Padding integers and strings to a width, and writing a float to a fixed
//! number of places:
//!
//! ```
//! assert_eq(7.pad(3), "007")
//! assert_eq((-5).pad(3), "-05")
//! assert_eq(1234.pad(2), "1234") // never truncated
//! assert_eq("ab".pad_left(4), "  ab")
//! assert_eq("ab".pad_right(4), "ab  ")
//! assert_eq((100.0 / 3.0).fixed(2), "33.33")
//! assert_eq((2.5).fixed(0), "2") // a tie goes to the even digit
//!
//! // A row of a table, in columns of fixed width.
//! let row = "${"apples".pad_right(8)}|${3.pad(4)}|${(0.5).fixed(2).pad_left(6)}"
//! assert_eq(row, "apples  |0003|  0.50")
//! ```

// Nova standard library -- formatting.
//
// Methods rather than top-level `pub fn`s, and that is not a style choice.
// Nova has no imports and no qualified paths: every std module's public names
// are glob-imported into every other module, and a collision resolves
// *silently in the user's favour*, so a top-level `pub fn pad` would take
// `pad` from every Nova program ever written and a program with its own `pad`
// would lose access to this one with no diagnostic. `std/strings` refused the
// same trade for `join`; `std/log` refused it for five level names.

impl Int {
    // Zero-pad to `width`, with the sign counted toward it: `(7).pad(2)` is
    // "07" and `(-5).pad(3)` is "-05", matching Rust's `{:03}`. A value
    // already at or beyond `width` is returned unpadded, never truncated --
    // a formatter that drops digits turns a display concern into a
    // correctness one.
    //
    // The early return is also the negative-width clamp: `len` is at least 1
    // for every Int, so a negative `width` takes that branch and
    // `String::repeat` -- which *panics* on a negative count -- is never
    // reached with one. The upper end of `width` is not clamped at all --
    // an absurdly large one (e.g. `i64::MAX`) reaches `String::repeat` and
    // aborts cleanly with `array length is too large` (measured), guarded
    // by the array-length check at `crates/nova-mir/src/lower.rs:1080-1101`
    // rather than by anything in this function. A documented outcome, not
    // a silent one.
    /// Returns the integer as text, padded on the left with zeros to `width`
    /// characters.
    ///
    /// A negative number's minus sign counts toward `width` and stays in
    /// front: `(-5).pad(3)` is `"-05"`. A number already `width` characters or
    /// longer comes back without padding, never truncated, as does any number
    /// when `width` is zero or negative.
    pub fn pad(self, width: Int) -> String {
        let s = "${self}"
        let len = s.len()
        if len >= width { return s }
        let zeros = "0".repeat(width - len)
        if self < 0 {
            let digits = s.slice(1, len)
            "-${zeros}${digits}"
        } else {
            "${zeros}${s}"
        }
    }
}

impl String {
    // Right-align within `width` by padding with spaces on the left. At or
    // beyond `width`, returned unchanged. The early return clamps a negative
    // `width` before `repeat` can see it.
    //
    // `width` is counted in codepoints, as `String::len` counts them, not
    // in display columns: a wide or combining character will not align by
    // column. Measured: `"日本".pad_right(4)` is `"日本  "` (two spaces),
    // four codepoints wide but six columns in a monospace terminal.
    /// Returns the string right-aligned in `width` characters, with spaces
    /// added on the left.
    ///
    /// `width` counts characters, as `len` does, not columns on a screen, so a
    /// wide character such as `日` counts as one. A string already `width`
    /// characters or longer comes back as it is.
    pub fn pad_left(self, width: Int) -> String {
        let len = self.len()
        if len >= width { return self }
        let fill = " ".repeat(width - len)
        "${fill}${self}"
    }

    // Left-align within `width` by padding with spaces on the right. Same
    // codepoint-counted `width` as `pad_left` above.
    /// Returns the string left-aligned in `width` characters, with spaces
    /// added on the right.
    ///
    /// `width` counts characters, as for `pad_left`. A string already `width`
    /// characters or longer comes back as it is.
    pub fn pad_right(self, width: Int) -> String {
        let len = self.len()
        if len >= width { return self }
        let fill = " ".repeat(width - len)
        "${self}${fill}"
    }
}

impl Float {
    // Fixed-place decimal: `(100.0 / 3.0).fixed(2)` is "33.33".
    //
    // The rendering is done by the runtime, deliberately breaking this
    // project's habit of keeping policy in Nova. Decimal rendering of a
    // binary float is a solved problem with sharp edges -- shortest
    // round-trip, ties at the cut -- and Nova cannot begin on it, having no
    // `Float` to `Int` conversion at all. This is the case where the Rust
    // side is the correct implementation rather than the convenient one.
    //
    // `places` is clamped to 0..=17 by the intrinsic.
    /// Returns the number as text with `places` digits after the decimal
    /// point: `(100.0 / 3.0).fixed(2)` is `"33.33"`.
    ///
    /// It rounds the float's exact binary value to the nearest such text, a
    /// tie going to the even digit, so `(2.5).fixed(0)` is `"2"` and
    /// `(1.005).fixed(2)` is `"1.00"`, 1.005 being stored as a little less.
    /// A negative number that rounds to zero keeps its sign, as `-0.00`.
    /// `places` is clamped to `0..=17`, and 0 gives no decimal point. It never
    /// uses an exponent, and `NaN` and the infinities give `NaN`, `inf` and
    /// `-inf`.
    pub fn fixed(self, places: Int) -> String { float_fixed(self, places) }
}