Category talk:Wren-fmt

From Rosetta Code

Source code

/* Module "fmt.wren" */

/* Conv contains routines which do conversions between types. */
class Conv {
    // All possible digits.
    static digits { "0123456789abcdefghijklmnopqrstuvwxyz" }

    // All possible digits (upper case).
    static upperDigits { "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" }

    // Maximum safe integer = 2^53 - 1.
    static maxSafeInt { 9007199254740991 }

    // Prefix map for different bases.
    static prefixes { { "b": "0b", "t": "0t", "o": "0o", "d": "0d", "x": "0x", "X": "0X" } }

    // Converts an integer to a numeric ASCII string with a base between 2 and 36.
    static itoa(n, b) {
        if (!(n is Num && n.isInteger && n.abs <= maxSafeInt)) Fiber.abort("Argument must be a safe integer.")
        if (b < 2 || b > 36) Fiber.abort("Base must be between 2 and 36.")
        if (n == 0) return "0"
        var neg = (n < 0)
        if (neg) n = -n
        var res = ""
        while (n > 0) {
            res = res + "%(digits[n%b])"
            n = (n/b).floor
        }
        return ((neg) ? "-" : "") + res[-1..0]
    }

    // Private helper function. Converts ASCII string to upper case.
    static upper_(s) { s.bytes.map { |b|
        return String.fromByte((b >= 97 && b <= 122) ? b - 32 : b)
    }.join() }

    // As itoa(n, b) but resulting digits are upper case.
    static Itoa(n, b) { (b < 11) ? itoa(n, b) : upper_(itoa(n, b)) }

    // Converts a numeric ASCII string with a base between 2 and 36 to an integer.
    // The string can optionally begin with a base specifier provided it is consistent with the base.
    static atoi(s, b) {
        if (!(s is String && s != "" && s.count == s.bytes.count)) Fiber.abort("Argument must be an ASCII string.")
        if (b < 2 || b > 36) Fiber.abort("Base must be between 2 and 36.")
        var neg = false
        if (s.startsWith("+")) {
            s = s[1..-1]
        } else if (s.startsWith("-")) {
            s = s[1..-1]
            neg = true
        }
        if (s == "") Fiber.abort("String must contain some digits.")
        s = upper_(s)
        if ((s.startsWith("0B") && b == 2) || (s.startsWith("0T") && b == 3) ||
            (s.startsWith("0O") && b == 8) || (s.startsWith("0X") && b == 16)) {
            s = s[2..-1]
            if (s == "") Fiber.abort("String after base specifier must contain some digits.")
        }
        var res = 0
        var digs = upperDigits[0...b]
        for (d in s) {
            var ix = digs.indexOf(d)
            if (ix == -1) Fiber.abort("String contains an invalid digit '%(d)'.")
            res = res * b + ix
        }
        return (neg) ? -res : res
    }

    // Convenience versions of itoa and atoi which use a base of 10.
    static itoa(s) { itoa(s, 10) }
    static atoi(s) { atoi(s, 10) }

    // Integer/bool conversion routines.
    static itob(i) { (i is Num && i.isInteger) ? (i != 0) : null }
    static btoi(b) { (b is Bool) ? (b ? 1 : 0) : null }

    // Integer/character conversion routines.
    static itoc(i) { (i is Num && i.isInteger && i >= 0 && i <= 0x10ffff) ? String.fromCodePoint(i) : null }
    static ctoi(c) { (c is String && c.count == 1) ? c.codePoints[0] : null }

    static bin(n) { itoa(n, 2) }        // Converts an integer to binary.
    static ter(n) { itoa(n, 3) }        // Converts an integer to ternary.
    static oct(n) { itoa(n, 8) }        // Converts an integer to octal.
    static dec(n) { itoa(n, 10) }       // Ensures safe decimal integers printed as such.
    static hex(n) { itoa(n, 16) }       // Converts an integer to hex.
    static Hex(n) { Conv.Itoa(n, 16) }  // Converts an integer to hex (upper case digits).

    static pdec(n) { ((n >= 0) ? "+" : "") + dec(n) }  // Adds '+' for non-negative integers.
    static mdec(n) { ((n >= 0) ? " " : "") + dec(n) }  // Only uses '-', leaves space for '+'.

    // Converts a non-negative integer to its ordinal equivalent.
    static ord(n) {
        if (!(n is Num && n.isInteger && n >= 0)) Fiber.abort("Argument must be a non-negative integer.")
        var m = n % 100
        if (m >= 4 && m <= 20) return "%(n)th"
        m = m % 10
        var suffix = "th"
        if (m == 1) {
            suffix = "st"
        } else if (m == 2) {
            suffix = "nd"
        } else if (m == 3) {
            suffix = "rd"
        }
        return "%(n)%(suffix)"
    }

    // Converts an integer to its unicode superscript equivalent.
    static superscript(n) {
        if (!(n is Num && n.isInteger)) Fiber.abort("Argument must be an integer.")                   
        var ss = {
            "0": "⁰", "1": "¹", "2": "²", "3": "³", "4": "⁴", "5": "⁵", "6": "⁶",
            "7": "⁷", "8": "⁸", "9": "⁹", "-": "⁻", "+": "⁺", "e": "ᵉ"
        }
        return Fmt.i(0, n).map { |d| ss.containsKey(d) ? ss[d] : d }.join()
    }

    // Converts an integer to its unicode subscript equivalent.
    static subscript(n) {
        if (!(n is Num && n.isInteger)) Fiber.abort("Argument must be an integer.")
        var ss = {
            "0": "₀", "1": "₁", "2": "₂", "3": "₃", "4": "₄", "5": "₅", "6": "₆",
            "7": "₇", "8": "₈", "9": "₉", "-": "₋", "+": "₊", "e": "ₑ"
        }
        return Fmt.i(0, n).map { |d| ss.containsKey(d) ? ss[d] : d }.join()
    }

    // Converts a numerator and denominator to their unicode fraction equivalent.
    // If there is none, returns a string of the form "n/d".
    static fraction(n, d) {
        if (!(n is Num && n.isInteger && n > 0)) Fiber.abort("n must be a positive integer.")
        if (!(d is Num && d.isInteger && d > 0)) Fiber.abort("d must be a positive integer.")
        var fracs = {
            "1/4": "¼", "1/2": "½", "3/4": "¾", "1/7": "⅐", "1/9": "⅑", "1/10": "⅒",
            "1/3": "⅓", "2/3": "⅔", "1/5": "⅕", "2/5": "⅖", "3/5": "⅗", "4/5": "⅘",
            "1/6": "⅙", "5/6": "⅚", "1/8": "⅛", "3/8": "⅜", "5/8": "⅝", "7/8": "⅞"
        }
        var frac = "%(n)/%(d)"
        return fracs.containsKey(frac) ? fracs[frac] : frac
    }

    // Converts 's' to the plural form 'p' if and only if the absolute value of 'n' is not equal to 1.
    static plural(n, s, p) { (n.abs != 1) ? p : s }

    // Convenience version of the above method which forms the plural by adding 's' to the singular.
    static plural(n, s)    { (n.abs != 1) ? s + "s" : s }

    // Makes a UTF-8 or byte string printable by replacing non-printable characters
    // with escaped ones. If 'asBytes' is true, not just control characters
    // but all bytes > 127 are regarded as non-printable.
    static printable(s, asBytes) {
        var res = ""
        var chars = asBytes ? s.bytes : s.codePoints
        var lets = "abtnvfr"
        for (c in chars) {
            if (c == -1) {
                res = res + "\ufffd"
            } else if (c == 0) {
                res = res + "\\0"
            } else if (c >= 7 && c <= 13) {
                res = res + "\\" + lets[c - 7]
            } else if (c == 27) {
                res = res + "\\e"
            } else if (c < 32 || c == 127 || (asBytes && c > 127)) {
                res = res + "\\x" + Fmt.swrite("$02x", c)
            } else if (!asBytes && c >= 128 && c < 160) {
                res = res + esc + "\\u00" + Fmt.swrite("$02x", c)
            } else {
                res = res + String.fromCodePoint(c)
            }
        }
        return res
    }

    // Returns the unicode infinity symbol.
    static infinity { "∞" }
}

/* Fmt contains routines which format numbers or strings in various ways. */
class Fmt {
    // Left justifies 's' in a field of minimum width 'w' using the pad character 'p'.
    static ljust(w, s, p) {
        if (!w.isInteger || w < 0) Fiber.abort("Width must be a non-negative integer.")
        if (!(p is String) || p.count != 1) Fiber.abort("Padder must be a single character string.")
        if (!(s is String)) s = "%(s)"
        var c = s.count
        return (w > c) ? s + p * (w - c) : s
    }

    // Right justifies 's' in a field of minimum width 'w' using the pad character 'p'.
    static rjust(w, s, p) {
        if (!w.isInteger || w < 0) Fiber.abort("Width must be a non-negative integer.")
        if (!(p is String) || p.count != 1) Fiber.abort("Padder must be a single character string.")
        if (!(s is String)) s = "%(s)"
        var c = s.count
        return (w > c) ? p * (w - c) + s : s
    }

    // Centers 's' in a field of minimum width 'w' using the pad character 'p'.
    static cjust(w, s, p) {
        if (!w.isInteger || w < 0) Fiber.abort("Width must be a non-negative integer.")
        if (!(p is String) || p.count != 1) Fiber.abort("Padder must be a single character string.")
        if (!(s is String)) s = "%(s)"
        var c = s.count
        if (w <= c) return s
        var l = ((w-c)/2).floor
        return p * l + s + p * (w - c - l)
    }

    // Convenience versions of the above which use a space as the pad character.
    static ljust(w, s) { ljust(w, s, " ") }
    static rjust(w, s) { rjust(w, s, " ") }
    static cjust(w, s) { cjust(w, s, " ") }

    // Right justifies 's' in a field of minimum width 'w' using the pad character '0'.
    // Unlike rjust, any sign or elided sign (i.e. space) will be placed before the padding.
    // Should normally only be used with numbers or numeric strings.
    static zfill(w, s) {
        if (!w.isInteger || w < 0) Fiber.abort("Width must be a non-negative integer.")
        if (!(s is String)) s = "%(s)"
        var c = s.count
        if (w <= c) return s
        var sign = (c > 0 && "-+ ".contains(s[0])) ? s[0] : ""
        if (sign == "") return "0" * (w - c) + s
        return sign + "0" * (w - c) + s[1..-1]
    }

    // Private helper method for 'commatize' method.
    // Checks whether argument is a numeric decimal string.
    static isDecimal_(n) {
        if (!(n is String && n != "" && "+- 0123456789".contains(n[0]))) return false
        if ("-+ ".contains(n[0])) {
            if (n.count == 1) return false
            n = n[1..-1]
        }
        return n.all { |c| "0123456789".contains(c) }
    }

    // Adds 'thousand separators' to a decimal integer or string.
    static commatize(n, c) {
        if (!(n is Num && n.isInteger) && !isDecimal_(n)) Fiber.abort("Argument is not a decimal integer nor string.")
        if (!(c is String) || c.count != 1) Fiber.abort("Separator must be a single character string.")
        if (n is Num) n = "%(Conv.dec(n))"
        var signed = "-+ ".contains(n[0])
        var sign = ""
        if (signed) {
            sign = n[0]
            n = n[1..-1]
        }
        if (n.startsWith("0") && n != "0") {
            n = n.trimStart("0")
            if (n == "") n = "0"
        }
        var i = n.count - 3
        while (i >= 1) {
            n = n[0...i] + c + n[i..-1]
            i = i - 3
        }
        return (signed) ? sign + n : n
    }

    // Convenience version of the above method which uses a comma as the separator.
    static commatize(n) { commatize(n, ",") }

    // Adds 'thousand' separators' to an ordinal number.
    static ordinalize(n, c) { commatize(n, c) + Conv.ord(n)[-2..-1] }

    // Convenience version of the above method which uses a comma as the separator.
    static ordinalize(n) { ordinalize(n, ",") }

    // Private helper method for 'abbreviate' method.
    static sub_(s, r) { s.toList[r].join() }

    // Abbreviates a string 's' to a maximum number of characters 'w' (non-overlapping) at either end
    // or, if 'w' is negative from the front only, using 'sep' as the separator.
    // Doesn't abbreviate a string unless at least one character would need to be suppressed.
    static abbreviate(w, s, sep) {
        if (!(w is Num && w.isInteger && w.abs >= 1)) Fiber.abort("Maximum width must be a positive integer.")
        if (!(sep is String)) Fiber.abort("Separator must be a string.")
        if (!(s is String)) s = "%(s)"
        var c = s.count
        if (c <= ((w < 0) ? -w : 2*w)) return s
        var le = (w >= 0) ? w : -w
        return sub_(s, 0...le) + sep + ((w >= 0) ? sub_(s, -le..-1) : "")
    }

    // Convenience version of the above method which uses 'three dots' as the separator.
    static abbreviate(w, s) { abbreviate(w, s, "...") }

    // Gets or sets precision for 'f(w, n)' style convenience methods.
    static precision { ( __precision != null) ? __precision : 6 }
    static precision=(p) { __precision = ((p is Num) && p.isInteger && p >= 0) ? p : __precision }

    /* 'Short name' methods, useful for formatting values in interpolated strings. */
 
    // Formats an integer 'n' in (d)ecimal, (b)inary, (t)ernary, (o)ctal, he(x) or upper case he(X).
    // Pads with spaces to a minimum width of 'w'.
    // Negative 'w' left justifies, non-negative 'w' right justifies.
    static d(w, n) { (w >= 0) ? rjust(w, Conv.dec(n)) : ljust(-w, Conv.dec(n)) }
    static b(w, n) { (w >= 0) ? rjust(w, Conv.bin(n)) : ljust(-w, Conv.bin(n)) }
    static t(w, n) { (w >= 0) ? rjust(w, Conv.ter(n)) : ljust(-w, Conv.ter(n)) }     
    static o(w, n) { (w >= 0) ? rjust(w, Conv.oct(n)) : ljust(-w, Conv.oct(n)) }
    static x(w, n) { (w >= 0) ? rjust(w, Conv.hex(n)) : ljust(-w, Conv.hex(n)) }
    static X(w, n) { (w >= 0) ? rjust(w, Conv.Hex(n)) : ljust(-w, Conv.Hex(n)) }

    // As above but pads with leading zeros instead of spaces.
    // Any minus sign will be placed before the padding.
    // When used with negative 'w' behaves the same as the above methods.
    static dz(w, n) { (w >= 0) ? zfill(w, Conv.dec(n)) : ljust(-w, Conv.dec(n)) }
    static bz(w, n) { (w >= 0) ? zfill(w, Conv.bin(n)) : ljust(-w, Conv.bin(n)) }
    static tz(w, n) { (w >= 0) ? zfill(w, Conv.ter(n)) : ljust(-w, Conv.ter(n)) }
    static oz(w, n) { (w >= 0) ? zfill(w, Conv.oct(n)) : ljust(-w, Conv.oct(n)) }
    static xz(w, n) { (w >= 0) ? zfill(w, Conv.hex(n)) : ljust(-w, Conv.hex(n)) }
    static Xz(w, n) { (w >= 0) ? zfill(w, Conv.Hex(n)) : ljust(-w, Conv.Hex(n)) }

    // Formats 'n' in decimal, space padded, with a leading '+' if 'n' is non-negative or '-' otherwise.
    static dp(w, n) { (w >= 0) ? rjust(w, Conv.pdec(n)) : ljust(-w, Conv.pdec(n)) }

    // Formats 'n' in decimal, space padded, with a leading ' ' if  'n' is non-negative or '-' otherwise.
    static dm(w, n) { (w >= 0) ? rjust(w, Conv.mdec(n)) : ljust(-w, Conv.mdec(n)) }

    // Formats 'n' in commatized form, space padded, using ',' as the separator.
    static dc(w, n) { (w >= 0) ? rjust(w, commatize(Conv.dec(n))): ljust(-w, commatize(Conv.dec(n))) }

    // Ranks a non-negative integer 'n' i.e. expresses it in ordinal form, space padded.
    static r(w, n) { (w >= 0) ? rjust(w, Conv.ord(n)) : ljust(-w, Conv.ord(n)) }

    // As the above method but commatizes the ordinal number, using ',' as the separator.
    static rc(w, n) { (w >= 0) ? rjust(w, ordinalize(n)) : ljust(-w, ordinalize(n)) }

    // Pads a character (equivalent to the codepoint 'n') with spaces to a minimum width of 'w'.
    // Negative 'w' left justifies, non-negative 'w' right justifies.
    static c(w, n)  { (w >= 0) ? rjust(w, Conv.itoc(n)): ljust(-w, Conv.itoc(n)) }

    // Pads a string or value 'v' with spaces to a minimum width of 'w'.
    // Negative 'w' left justifies, non-negative 'w' right justifies.
    static s(w, v)  { (w >= 0) ? rjust(w, v) : ljust(-w, v) }

    // As 's' above but pads with leading zeros instead of spaces.
    // Any minus sign will be placed before the padding.
    // When used with negative 'w' behaves the same as the above method.
    static sz(w, v) { (w >= 0) ? zfill(w, v) : ljust(-w, v) }

    // Formats a string or value 'v' in commatized form, space padded, using ',' as the separator.
    static sc(w, v) {
        if (!(v is String)) v = "%(v)"
        return (w >= 0) ? rjust(w, commatize(v)): ljust(-w, commatize(v))
    }

    // These methods use the appropriate 'd' format if 'v' is a safe integer or the 's' format otherwise.
    static i(w, v)  { (v is Num && v.isInteger && v.abs <= Conv.maxSafeInt) ? d (w, v) : s (w, v) }
    static iz(w, v) { (v is Num && v.isInteger && v.abs <= Conv.maxSafeInt) ? dz(w, v) : sz(w, v) }
    static ic(w, v) { (v is Num && v.isInteger && v.abs <= Conv.maxSafeInt) ? dc(w, v) : sc(w, v) }

    // Middles a string or value 'v' within a field of minimum width 'w'. Pads with spaces.
    static m(w, v)  { cjust(w, v) }

    // 'Short name' synonym for abbreviate(w, s) method except doesn't abbreviate (rather than throwing
    // an error) if a width of '0' is passed.
    static a(w, v)  { (w != 0) ? abbreviate(w, v) : s(0, v) }

    // Enables a value to be printed in its 'normal' form (i.e. by applying the 'toString' method),
    // within a space-padded minimum field of width 'w', notwithstanding any special formatting 
    // that would otherwise be applied by 'short name' methods.
    static n(w, v) { s(w, v.toString) }

    // Applies the 's' format to the kind (i.e. type) of 'v'.
    static k(w, v) { s(w, v.type) }

    // Converts a list of Unicode code points to a string and then applies the 's' format to it.
    static u(w, v) {
        if (!(v is List)) Fiber.abort("Second argument must be list of code points.")
        var str = ""
        for (c in v) str = str + String.fromCodePoint(c)
        return s(w, str)
    }

    // Embeds a string or value 'v' in 'cc', a string with no more than two characters.
    // If it has none, no embedding takes place. If has one, it's doubled.
    // The first character is added at the left and the second at the right.
    static q(v, cc) {
        var len
        if (!(cc is String && (len = cc.count) < 3)) {
            Fiber.abort("Second argument must be a string with no more than 2 characters.")
        }
        if (len == 0) return (v is String) ? v : "%(v)"
        if (len == 1) cc = cc + cc
        return "%(cc[0])%(v)%(cc[1])"
    }

    // Convenience version of the above which uses double quotes as the embedding characters.
    static q(v) { "\"%(v)\"" }

    // Maps the boolean value 'b' to "yes" if true or "no" if false
    // and then applies the 's' format to the result. 
    static y(w, b) { Fmt.s(w, b ? "yes" : "no") }

    // Formats a number 'n' (using 'h' format) to a maximum precision of 14 decimal places.
    // It then converts it to exponential format and formats the mantissa to 'p' decimal places. 
    // The result is then padded with spaces to a minimum width 'w'.
    // Negative 'w' left justifies, non-negative 'w' right justifies.
    static e(w, n, p) {   
        var f = Fmt.h(w, n, 14).trim()
        if (f.contains("e") || n.isInfinity || n.isNan) return Fmt.s(w, n) // use 'normal' representation
        var dix = f.indexOf(".")
        if (dix >= 0) {
            f = f.replace(".", "")
        } else {
            dix = f.count
        }
        // look for index of first non-zero digit if there is one
        var nzix = -1
        var i = (f[0] == "-") ? 1 : 0
        while (i < f.count) {
            if (f[i] != "0") {
                nzix = i
                break
            }
            i = i + 1
        }
        if (nzix == -1) return "0e00"
        var delta = dix - nzix
        f = (nzix+1<f.count) ? f[nzix] + "." + f[nzix+1..-1] : f[nzix]
        if (n < 0) f = "-" + f
        f = Fmt.h(p+2, Num.fromString(f), p).trim()   
        var exp = (delta >= 0) ? Fmt.dz(2, delta-1) : Fmt.dz(3, delta-1)     
        return Fmt.s(w, "%(f)e%(exp)")         
    }

    // Works like 'e' except that the exponent symbol 'e' is replaced by upper case 'E'.
    static E(w, n, p) { Fmt.e(w, n, p).replace("e", "E") }

    // Applies the 's' format to the name of a number 'n'.
    static N(w, n) { Fmt.s(w, Name.fromNum(n)) }

    // Applies the 's' format to the ordinal name of an integer 'i'.
    static O(w, i) { Fmt.s(w, Name.ordinal(i)) }

    // Applies the 's' format to the superior (or superscript) version of a number 'n'.
    static S(w, n) { Fmt.s(w, Conv.superscript(n)) }

    // Applies the 's' format to the the inferior (or subscript) version of a number 'n'.
    static I(w, n) { Fmt.s(w, Conv.subscript(n)) }

    // Applies the 's' format to a fraction f[0] / f[1  where 'f' is a two element list. 
    static F(w, f) { Fmt.s(w, Conv.fraction(f[0], f[1])) }

    // Applies the 's' format to the plural of l[1] depending on l[0] and, if present, l[2] where 'l' is a
    // two or three element list. Forms the plural by just adding "s" to l[1] if 'l' is a two element list.
    static P(w, l) { Fmt.s(w, Conv.plural(l[0], l[1], l.count == 2 ? l[1] + "s" : l[2])) }

    // Makes a byte string printable and applies the 's' format to the result.
    static B(w, v) { Fmt.s(w, Conv.printable(v, true)) }

    // Makes a UTF-8 string printable and applies the 's' format to the result.
    static U(w, v) { Fmt.s(w, Conv.printable(v, false)) }

    // Repeats 'v', a string or value, 'n' times. 
    static R(n, v) { (v is String) ? v * n : v.toString * n } 

    // Pads a number 'n' with leading spaces to a minimum width 'w' and a precision of 'p' decimal places.
    // Precision is restricted to 14 places though entering a higher figure is not an error.
    // Numbers are rounded and/or decimal places are zero-filled where necessary.
    // Numbers which can't be expressed exactly use their default representation.
    // Negative 'w' left justifies, non-negative 'w' right justifies.
    static f(w, n, p) {
        if (!w.isInteger) Fiber.abort("Width must be an integer.")
        if (!(n is Num)) Fiber.abort("Argument must be a number.")
        if (!p.isInteger || p < 0) Fiber.abort("Precision must be a non-negative integer")
        if (n.abs > Conv.maxSafeInt || n.isInfinity || n.isNan) return s(w, n) // use 'normal' representation
        if (p > 14) p = 14
        var i = (p == 0) ? n.round : n.truncate
        var ns = "%(Conv.dec(i))"
        if (i == 0 && n < 0) ns = "-" + ns
        if (n.isInteger || p == 0) {
            if (p > 0) return s(w, ns + "." + "0" * p)
            return s(w, ns)
        }
        var d = (n - i).abs
        var pw = 10.pow(p)
        d = (d * pw).round
        if (d >= pw) {
            ns = "%(Conv.dec(n.round))"
            d = 0
        }
        if (d == 0) return s(w, ns + "." + "0" * p)
        var ds = "%(d)"
        var c = ds.count
        if (c < p) ds = "0" * (p-c) + ds
        return s(w, ns + "." + ds[0...p])
    }

    // Works like 'f' except replaces any trailing zeros after the decimal point with spaces.
    // If the resulting string would end with a decimal point, a zero is first added back.
    static g(w, n, p) {
        var f = f(w, n, p)
        if (f.contains("e")) return f
        if (f.contains(".") && (f[-1] == "0" || f[-1] == " ")) {
            var l1 = f.count
            f = f.trimEnd("0 ")
            if (f[-1] == ".") f = f + "0"
            f = f + (" " * (l1 - f.count))
        }
        return f
    }

    // Works like 'f' except replaces any trailing zeros after the decimal point with spaces.
    // If the resulting string would end with a decimal point, that is also replaced with a space.    
    static h(w, n, p) {
        var f = f(w, n, p)
        if (f.contains("e")) return f
        if (f.contains(".") && (f[-1] == "0" || f[-1] == " ")) {
            var l1 = f.count
            f = f.trimEnd("0 ")
            if (f[-1] == ".") f = f[0..-2]
            f = f + (" " * (l1 - f.count))
        }
        return f
    }

    // Works like 'h' except, if right justified, removes any trailing spaces before rejustifying.
    static j(w, n, p) { (w >= 0) ? rjust(w, h(w, n, p).trimEnd()) : h(w, n, p) }

    // Works like 'j' except derives the number to be formatted from its name.
    static l(w, name, p) { j(w, Name.toNum(name), p) }

    // As above but pads with leading zeros instead of spaces.
    // Any minus sign will be placed before the padding.
    // When used with negative 'w' behaves the same as the above methods.
    static fz(w, n, p) { (w >= 0) ? zfill(w, f(w, n, p).trimStart()) : f(w, n, p) }
    static gz(w, n, p) { (w >= 0) ? zfill(w, g(w, n, p).trimStart()) : g(w, n, p) }
    static hz(w, n, p) { (w >= 0) ? zfill(w, h(w, n, p).trimStart()) : h(w, n, p) }

    // As above but prepends non-negative numbers with a '+' sign.
    static fp(w, n, p) { signFloat_("f", w, n, p, "+") }
    static gp(w, n, p) { signFloat_("g", w, n, p, "+") }
    static hp(w, n, p) { signFloat_("h", w, n, p, "+") }

    // As above but prepends non-negative numbers with a space.
    static fm(w, n, p) { signFloat_("f", w, n, p, " ") }
    static gm(w, n, p) { signFloat_("g", w, n, p, " ") }
    static hm(w, n, p) { signFloat_("h", w, n, p, " ") }

    // Private helper method for signing floating point numbers.
    static signFloat_(fn, w, n, p, ch) {
        var fmt = "$%(w).%(p)%(fn)"
        if (n < 0) return swrite(fmt, n)
        if (n > 0) return swrite(fmt, -n).replace("-", ch)
        return swrite(fmt, -1).replace("-1", "%(ch)0")
    }

    // Formats the integer part of 'n' in commatized form, space padded,
    // using ',' as the separator. The decimal part is not affected.
    static fc(w, n, p) {
        var f = f(w, n, p)
        if (f.contains("infinity") || f == "nan" || f.contains("e")) return f
        var ix = f.indexOf(".")
        var dp = (ix >= 0) ? f[ix..-1] : ""
        var c = dp.count
        w = (w >= 0) ? w - c : w + c
        if (w < 0) w = 0
        return dc(w, n.truncate) + dp
    }

    // Works like 'fc' except replaces any trailing zeros after the decimal point with spaces.
    // If the resulting string would end with a decimal point, a zero is first added back.
    static gc(w, n, p) {
        var f = fc(w, n, p)
        if (f.contains("e")) return f
        if (f.contains(".") && (f[-1] == "0" || f[-1] == " ")) {
            var l1 = f.count
            f = f.trimEnd("0 ")
            if (f[-1] == ".") f = f + "0"
            f = f + (" " * (l1 - f.count))
        }
        return f
    }

    // Works like 'fc' except replaces any trailing zeros after the decimal point with spaces.
    // If the resulting string would end with a decimal point, that is also replaced with a space.
    static hc(w, n, p) {
        var f = fc(w, n, p)
        if (f.contains("e")) return f
        if (f.contains(".") && (f[-1] == "0" || f[-1] == " ")) {
            var l1 = f.count
            f = f.trimEnd("0 ")
            if (f[-1] == ".") f = f[0..-2]
            f = f + (" " * (l1 - f.count))
        }
        return f
    }

    // Works like 'hc' except, if right justified, removes any trailing spaces before rejustifying.
    static jc(w, n, p) { (w >= 0) ? rjust(w, hc(w, n, p).trimEnd()) : hc(w, n, p) }

    // Works like 'jc' except derives the number to be formatted from its name.
    static lc(w, name, p) { jc(w, Name.toNum(name), p) }

    // Applies the 'f' format to each component, x and y, of a  complex number 'n' 
    // before joining them together in the form x ± yi.    
    static z(w, n, p) {
        if (n is Num) return f(w, n, p)
        if (n.type.toString != "Complex") Fiber.abort("Argument must be a complex or real number.")
        var real = f(w, n.real, p)
        var sign = (n.imag >= 0) ? " + " : " - "
        var imag = f(w, n.imag.abs, p)
        if (w < 0) imag = imag.trimEnd(" ")
        return real + sign + imag + "i"
    }

    // Applies the 'fp' and 'f' formats  respectively to each component, x and y, of a
    // complex number 'n' before joining them together in the form x ± yi.    
    static zp(w, n, p) {
        if (n is Num) return fp(w, n, p)
        if (n.type.toString != "Complex") Fiber.abort("Argument must be a complex or real number.")
        var real = fp(w, n.real, p)
        var sign = (n.imag >= 0) ? " + " : " - "
        var imag = f(w, n.imag.abs, p)
        if (w < 0) imag = imag.trimEnd(" ")
        return real + sign + imag + "i"
    }

    // Applies the 'fm' and 'f' formats  respectively to each component, x and y, of a
    // complex number 'n' before joining them together in the form x ± yi.    
    static zm(w, n, p) {
        if (n is Num) return fm(w, n, p)
        if (n.type.toString != "Complex") Fiber.abort("Argument must be a complex or real number.")
        var real = fm(w, n.real, p)
        var sign = (n.imag >= 0) ? " + " : " - "
        var imag = f(w, n.imag.abs, p)
        if (w < 0) imag = imag.trimEnd(" ")
        return real + sign + imag + "i"
    }

    // Convenience versions of the above methods which use the default precision.
    static e(w, n)  { e(w, n, precision)     }
    static E(w, n)  { Fmt.E(w, n, precision) }
    static f(w, n)  { f(w, n, precision)     }
    static g(w, n)  { g(w, n, precision)     }
    static h(w, n)  { h(w, n, precision)     }
    static j(w, n)  { j(w, n, precision)     }
    static l(w, n)  { l(w, n, precision)     }
    static z(w, n)  { z(w, n, precision)     }
    static fz(w, n) { fz(w, n, precision)    }
    static gz(w, n) { gz(w, n, precision)    }
    static hz(w, n) { hz(w, n, precision)    }
    static fp(w, n) { fp(w, n, precision)    }
    static gp(w, n) { gp(w, n, precision)    }
    static hp(w, n) { hp(w, n, precision)    }
    static zp(w, n) { zp(w, n, precision)    }
    static fm(w, n) { fm(w, n, precision)    }
    static gm(w, n) { gm(w, n, precision)    }
    static hm(w, n) { hm(w, n, precision)    }
    static zm(w, n) { zm(w, n, precision)    }
    static fc(w, n) { fc(w, n, precision)    }
    static gc(w, n) { gc(w, n, precision)    }
    static hc(w, n) { hc(w, n, precision)    }
    static jc(w, n) { jc(w, n, precision)    }    
    static lc(w, n) { lc(w, n, precision)    }    
  
    // Private worker method which calls a 'short name' method and returns its result.
    static callFn_(fn, w, v, p) {
        return (fn == "d")  ? d(w, v)        :                   
               (fn == "b")  ? b(w, v)        :
               (fn == "t")  ? t(w, v)        :
               (fn == "o")  ? o(w, v)        :
               (fn == "x")  ? x(w, v)        :
               (fn == "X")  ? Fmt.X(w, v)    :
               (fn == "r")  ? r(w, v)        :
               (fn == "c")  ? c(w, v)        :
               (fn == "s")  ? s(w, v)        :
               (fn == "i")  ? i(w, v)        :
               (fn == "m")  ? m(w, v)        :
               (fn == "a")  ? a(w, v)        :
               (fn == "n")  ? n(w, v)        :
               (fn == "k")  ? k(w, v)        :
               (fn == "u")  ? u(w, v)        :
               (fn == "q")  ? q(v)           :
               (fn == "y")  ? y(w, v)        :
               (fn == "e")  ? e(w, v, p)     :
               (fn == "E")  ? Fmt.E(w, v, p) :
               (fn == "N")  ? Fmt.N(w, v)    :
               (fn == "O")  ? Fmt.O(w, v)    :
               (fn == "S")  ? Fmt.S(w, v)    :
               (fn == "I")  ? Fmt.I(w, v)    :
               (fn == "F")  ? Fmt.F(w, v)    :
               (fn == "P")  ? Fmt.P(w, v)    :
               (fn == "B")  ? Fmt.B(w, v)    :
               (fn == "U")  ? Fmt.U(w, v)    :
               (fn == "R")  ? Fmt.R(w, v)    :
               (fn == "f")  ? f(w, v, p)     :
               (fn == "g")  ? g(w, v, p)     :
               (fn == "h")  ? h(w, v, p)     :
               (fn == "j")  ? j(w, v, p)     :
               (fn == "l")  ? l(w, v, p)     :
               (fn == "z")  ? z(w, v, p)     :
               (fn == "dz") ? dz(w, v)       :
               (fn == "bz") ? bz(w, v)       :
               (fn == "tz") ? tz(w, v)       :
               (fn == "oz") ? oz(w, v)       :
               (fn == "xz") ? xz(w, v)       :
               (fn == "Xz") ? Fmt.Xz(w, v)   :
               (fn == "sz") ? sz(w, v)       :
               (fn == "iz") ? iz(w, v)       :
               (fn == "fz") ? fz(w, v, p)    :
               (fn == "gz") ? gz(w, v, p)    :
               (fn == "hz") ? hz(w, v, p)    :
               (fn == "fp") ? fp(w, v, p)    :
               (fn == "gp") ? gp(w, v, p)    :
               (fn == "hp") ? hp(w, v, p)    :
               (fn == "zp") ? zp(w, v, p)    :
               (fn == "fm") ? fm(w, v, p)    :
               (fn == "gm") ? gm(w, v, p)    :
               (fn == "hm") ? hm(w, v, p)    :
               (fn == "zm") ? zm(w, v, p)    :
               (fn == "dp") ? dp(w, v)       :
               (fn == "dm") ? dm(w, v)       :
               (fn == "dc") ? dc(w, v)       :
               (fn == "rc") ? rc(w, v)       :
               (fn == "sc") ? sc(w, v)       :
               (fn == "ic") ? ic(w, v)       :
               (fn == "fc") ? fc(w, v, p)    :
               (fn == "gc") ? gc(w, v, p)    :
               (fn == "hc") ? hc(w, v, p)    : 
               (fn == "jc") ? jc(w, v, p)    : 
               (fn == "lc") ? lc(w, v, p)    : Fiber.abort("Method not recognized.")
    }

    // Applies a 'short' formatting method to each element of a list or sequence 'seq'.
    // The method to be applied is specified (as a string) in 'fn'.
    // The parameters to be passed to the method are specified in 'w' and 'p'
    // 'p' is needed for 'e', 'E', 'f', 'g', 'h', 'j', 'l', 'z', 'fz', 'gz', 'hz', 'fp', 'gp',
    // 'hp', 'zp', 'fm', 'gm', 'hm', 'zm', 'fc', 'gc', 'hc', 'jc' or 'lc' but is ignored otherwise.
    // The resulting strings are then joined together using the separator 'sep'.
    // having first applied the 'q' method, with parameter 'cc', to each of them.
    // Finally, the 'q' method is applied again, with parameter 'bb', to the whole
    // string, if a prefix/suffix is needed.
    static v(fn, w, seq, p, sep, bb, cc) {
        var l = List.filled(seq.count, "")
        var i = 0
        for (e in seq) {
            l[i] = q(callFn_(fn, w, e, p), cc)
            i = i + 1
        }
        return q(l.join(sep), bb)
    }

    // Convenience versions of the above method which use default values
    // for some parameters.
    static v(fn, w, seq, p, sep, bb) { v(fn, w, seq, p, sep, bb, "") }
    static v(fn, w, seq, p, sep)     { v(fn, w, seq, p, sep, "[]", "") }
    static v(fn, w, seq, p)          { v(fn, w, seq, p, ", ", "[]", "") }
    static v(fn, w, seq)             { v(fn, w, seq, precision, ", ", "[]", "") }

    // As the 'v' method but abridges 'seq' to a maximum number
    // of elements 'n' (non-overlapping) at either end or, if 'n' is negative,
    // from the front only, using 'aa' as the separator. Doesn't abridge 'seq'
    // unless at least one element would need to be suppressed.
    static va(fn, w, seq, p, sep, bb, cc, n, aa) {
        if (!(n is Num && n.isInteger && n.abs >= 1)) Fiber.abort("'n' must be a non-zero integer.")
        if (!(aa is String)) Fiber.abort("'aa' must be a string.")
        var c = seq.count
        if (c <= ((n < 0) ? -n : 2*n)) return Fmt.v(fn, w, seq, p, sep, bb, cc)
        var left   = (seq is List) ? seq[0...n.abs] : seq.take(n.abs)
        var sleft  = Fmt.v(fn, w, left, p, sep, "", cc) + sep
        var sright = ""
        if (n > 0) {
            var right = (seq is List) ? seq[-n..-1] : seq.skip(c - n)
            sright = sep + Fmt.v(fn, w, right, p, sep, "", cc)
        }
        var res = sleft + aa + sright
        if (bb != "") res = Fmt.q(res, bb)
        return res
    }

    // Applies a 'short' formatting method to each element of a two-dimensional 
    // list or sequence 'm'. 
    // A Matrix or CMatrix object is automatically converted to a 2D list of numbers.
    // The parameters: 'fn', 'w', 'p', 'sep', 'bb' and 'cc'
    // are applied using the 'v' method to each row of 'm'. 
    // The rows are then joined together using the separator 'ss'.
    static v2(fn, w, m, p, sep, bb, cc, ss) {
        var s = m.type.toString
        if (s == "Matrix" || s == "CMatrix") m = m.toList
        var nr = m.count
        if (nr == 0) return ""
        var l = List.filled(nr, "")
        var i = 0
        for (row in m) {
            l[i] = v(fn, w, row, p, sep, bb, cc)            
            i = i + 1
        }
        return l.join(ss)
    }

    // Convenience versions of the above method which use default values
    // for some parameters.
    static v2(fn, w, m, p, sep, bb, cc) { v(fn, w, m, p, sep, bb, cc, "\n") }
    static v2(fn, w, m, p, sep, bb)     { v(fn, w, m, p, sep, bb, "", "\n") }
    static v2(fn, w, m, p, sep)         { v(fn, w, m, p, sep, "|", "", "\n") }
    static v2(fn, w, m, p)              { v(fn, w, m, p, " ", "|", "", "\n") }
    static v2(fn, w, m)                 { v(fn, w, m, precision, " ", "|", "", "\n") }    

    // Provides a 'sprintf' style method where the arguments are passed in a separate list and
    // formatted in turn by verbs embedded in a format string. Excess arguments are ignored but
    // it is an error to provide insufficient arguments. Verbs must be given in this form:
    // $[flag][width][.precision][letter] of which all bracketed items except [letter] are optional.
    // The letter must be one of the 'short' methods:
    // a, b, B, c, d, e, E, f, F, g, h, i, I, j, k, l, m, n, N, o, O, P, q, r, R, s, S, t, u, U, x, X, y or z.
    // If present, the flag (there can only be one) must be one of the following:
    //     +    always prints a + or - sign ('dp', 'fp', 'gp', 'hp' or 'zp' methods)
    //  (space) leaves a space for the sign but only prints minus ('dm', 'fm', 'gm', 'hm' or 'zm' methods)
    //     ,    commatizes the following number ('dc', 'rc', 'sc', 'ic', 'fc', 'gc, 'hc', 'jc' or 'lc' methods)
    //     #    adds the appropriate prefix for the number formats: b, t, o, d, x and X
    //     *    reads the width from the argument before the one to be formatted
    //     0    when followed by an explicit width, pads with leading zeros rather than spaces:
    //          ('dz', 'bz', 'tz', 'oz', 'xz, 'Xz', 'sz', iz', 'fz', 'gz' and 'hz' methods)            
    // If present, the width is the minimum width (+/-) to be passed to the appropriate method.
    // It doesn't include any '#' flag prefix. If [width] is absent, a width of one is passed.
    // If present, the precision is the number of decimal places to be passed to the appropriate
    // 'e', 'E', 'f', 'g', 'h', 'j', 'l' or 'z' style method. If absent, the default precision is passed.
    // Where any optional item is inappropriate to the method being used it is simply ignored.
    // Where one of the arguments is a sequence (other than a string) this method senses it
    // and applies the 'v' method to it. However, the 'sep' parameter is always a single space
    // and the 'bb' and 'cc' parameters are always empty strings. Finally, to print a literal
    // dollar symbol use $$.
    static slwrite(fmt, a) {
        if (!(fmt is String)) Fiber.abort("First argument must be a string.")
        if (!(a is List)) Fiber.abort("Second argument must be a list.")
        if (fmt == "") return ""
        var cps = fmt.codePoints.toList
        var le = cps.count      // number of codepoints
        var s = ""              // accumulates the result string
        var i = 0               // current codepoint index
        var cp = 0              // current codepoint
        var next = 0            // index of next argument to be formatted

        // Gets the next numeric string from the format.
        var getNumber = Fn.new { |minusAllowed|
            i = i + 1
            if (i == le) Fiber.abort("Invalid format string.")
            cp = cps[i]
            var ns = ""
            if (cp == 45) {
                if (!minusAllowed) Fiber.abort("Invalid format string")
                ns = "-"
                i = i + 1
                if (i == le) Fiber.abort("Invalid format string.")
                cp = cps[i]
            }
            while (cp >= 48 && cp <= 57) {
                ns = ns + Conv.itoc(cp)
                i = i + 1
                if (i == le) Fiber.abort("Invalid format string.")
                cp = cps[i]
            }
            if (ns == "-") Fiber.abort("Invalid format string.")
            return ns
        }

        while (i < le) {
            cp = cps[i]
            if (cp != 36) { // not a dollar sign
                s = s + Conv.itoc(cp)
            } else if (i < le -1 && cps[i + 1] == 36) { // check for $$
                s = s + "$"
                i = i + 1
            } else {
                var ns = getNumber.call(true)
                if (ns != "" && "*+,#".codePoints.contains(cp)) {
                    Fiber.abort("Invalid format string.")
                }
                var plus  = false
                var comma = false
                var space = false 
                var hash  = false
                var fn = ""
                var ds = ""
                if ("abBcdeEfFghiIjklmnNoOPqrRsStuUxXyz".codePoints.contains(cp)) { // format letter
                    fn = Conv.itoc(cp)
                } else if (cp == 42) {    // star
                    if (next < a.count) {
                        ns = "%(a[next])"
                        next = next + 1
                    } else {
                        Fiber.abort("Insufficient arguments passed.")
                    }
                    i = i + 1
                    cp = cps[i]
                    if (cp == 46) ds = getNumber.call(false)             
                } else if (cp == 43) {  // plus sign
                    plus = true
                    ns = getNumber.call(true)
                    if (cp == 46) ds = getNumber.call(false)
                } else if (cp == 44) {  // comma
                    comma = true
                    ns = getNumber.call(true)
                    if (cp == 46) ds = getNumber.call(false)        
                } else if (cp == 46) {  // dot
                    ds = getNumber.call(false)
                } else if (cp == 32) {  // space
                    space = true
                    ns = getNumber.call(true)
                    if (cp == 46) ds = getNumber.call(false)
                } else if (cp == 35) {  // hash
                    hash = true
                    ns = getNumber.call(true)
                    if (cp == 46) ds = getNumber.call(false)
                } else {
                    Fiber.abort("Unrecognized character in format string.")
                }

                if (fn == "") {
                    if (!"abBcdeEfFghiIjklmnNoOPqrRsStuUxXyz".codePoints.contains(cp)) {
                        Fiber.abort("Unrecognized character in format string.")
                    }
                    fn = Conv.itoc(cp)
                }
                if (fn == "d") {
                    if (plus) {
                        fn = "dp"
                    } else if (space) {
                        fn = "dm"
                    } else if (comma) {
                        fn = "dc"
                    }
                } else if ((fn == "f" || fn == "g" || fn == "h" || fn == "z") && plus) {
                    fn = fn + "p"
                } else if ((fn == "f" || fn == "g" || fn == "h" || fn == "z") && space) {
                    fn = fn + "m"
                } else if ((fn == "r" || fn == "s" || fn == "i" || fn == "f" || 
                            fn == "g" || fn == "h" || fn == "j" || fn == "l") && comma) {
                    fn = fn + "c"
                }
                if (ns == "") ns = "1"
                if (ns[0] == "0" && ns.count > 1 && "dbtoxXsifgh".contains(fn[0])) {
                    fn = fn[0] + "z"
                }
                var w = Num.fromString(ns)
                var p = (ds != "") ? Num.fromString(ds) : precision
                if (next < a.count) {
                    var e = a[next]
                    if ((e is Sequence) && !(e is String) && !"nuFPR".contains(fn)) {
                        if (hash && "btodxX".contains(fn[0])) {
                            var rr = []
                            for (ee in e) {
                                var r = callFn_(fn, w, ee, p)
                                if (r[0] == "-") {
                                    r = "-" + Conv.prefixes[fn[0]] + r[1..-1]
                                } else {
                                    r = Conv.prefixes[fn[0]] + r
                                }
                                rr.add(r)
                            }
                            s = s + rr.join(" ")
                        } else {
                            s = s + Fmt.v(fn, w, e, p, " ", "", "")
                        }
                    } else {
                        var r = callFn_(fn, w, e, p)
                        if (hash && "btodxX".contains(fn[0])) {
                            if (r[0] == "-") {
                                r = "-" + Conv.prefixes[fn[0]] + r[1..-1]
                            } else {
                                r = Conv.prefixes[fn[0]] + r
                            }
                        }
                        s = s + r
                    }
                    next = next + 1
                } else {
                    Fiber.abort("Insufficient arguments passed.")
                }
            }
            i = i + 1
        }
        return s
    }

    // Convenience versions of the 'slwrite' method which allow up to 6 arguments
    // to be passed individually rather than in a list.
    static swrite(fmt, a1, a2, a3, a4, a5, a6) { slwrite(fmt, [a1, a2, a3, a4, a5, a6]) }
    static swrite(fmt, a1, a2, a3, a4, a5)     { slwrite(fmt, [a1, a2, a3, a4, a5]) }
    static swrite(fmt, a1, a2, a3, a4)         { slwrite(fmt, [a1, a2, a3, a4]) }
    static swrite(fmt, a1, a2, a3)             { slwrite(fmt, [a1, a2, a3]) }
    static swrite(fmt, a1, a2)                 { slwrite(fmt, [a1, a2]) }
    static swrite(fmt, a1)                     { slwrite(fmt, [a1]) }

    // Applies slwrite to the arguments and then 'writes' it (no following \n) to stdout.
    static write(fmt, a1, a2, a3, a4, a5, a6) { System.write(slwrite(fmt, [a1, a2, a3, a4, a5, a6])) }
    static write(fmt, a1, a2, a3, a4, a5)     { System.write(slwrite(fmt, [a1, a2, a3, a4, a5])) }
    static write(fmt, a1, a2, a3, a4)         { System.write(slwrite(fmt, [a1, a2, a3, a4])) }
    static write(fmt, a1, a2, a3)             { System.write(slwrite(fmt, [a1, a2, a3])) }
    static write(fmt, a1, a2)                 { System.write(slwrite(fmt, [a1, a2])) }
    static write(fmt, a1)                     { System.write(slwrite(fmt, [a1])) }
    static lwrite(fmt, a)                     { System.write(slwrite(fmt, a)) }

    // Applies slwrite to the arguments and then 'prints' it (with a following \n) to stdout.
    static print(fmt, a1, a2, a3, a4, a5, a6) { System.print(slwrite(fmt, [a1, a2, a3, a4, a5, a6])) }   
    static print(fmt, a1, a2, a3, a4, a5)     { System.print(slwrite(fmt, [a1, a2, a3, a4, a5])) }
    static print(fmt, a1, a2, a3, a4)         { System.print(slwrite(fmt, [a1, a2, a3, a4])) }
    static print(fmt, a1, a2, a3)             { System.print(slwrite(fmt, [a1, a2, a3])) }
    static print(fmt, a1, a2)                 { System.print(slwrite(fmt, [a1, a2])) }
    static print(fmt, a1)                     { System.print(slwrite(fmt, [a1])) }
    static lprint(fmt, a)                     { System.print(slwrite(fmt, a)) }

    // Synomyms of corresponding methods in System class - useful for aligning code.
    static write(object)      { System.write(object)      } 
    static writeAll(sequence) { System.writeAll(sequence) }
    static print()            { System.print()            }
    static print(object)      { System.print(object)      }
    static printAll(sequence) { System.printAll(sequence) }

    // Prints (with a following \n) a sequence 'a' to stdout in tabular form
    // with a maximum of 'rowSize' elements per row. 'fmt' is applied individually
    // to each element and formatted elements are separated by 'sep'.
    static tprint(fmt, a, rowSize, sep) {
        if (!(fmt is String)) Fiber.abort("First argument must be a string.")
        if (!(a is Sequence)) Fiber.abort("Second argument must be a sequence.")
        if (!((rowSize is Num) && rowSize.isInteger && rowSize > 0)) {
            Fiber.abort("Third argument must be a positive integer.")
        }
        var ac = a.count
        var count = 0
        for (e in a) {
            Fmt.write(fmt, e)
            count = count + 1
            if (count % rowSize == 0 || count == ac) {
                System.print()
            } else System.write(sep)
        }
    }

    // Prints (with a following \n) a sequence 'a' to stdout in columnar form
    // with a maximum of 'colSize' elements per column. 'fmt' is applied individually
    // to each element and formatted elements are separated by 'sep'.
    static cprint(fmt, a, colSize, sep) {
        if (!(fmt is String)) Fiber.abort("First argument must be a string.")
        if (!(a is Sequence)) Fiber.abort("Second argument must be a sequence.")
        if (!((colSize is Num) && colSize.isInteger && colSize > 0)) {
            Fiber.abort("Third argument must be a positive integer.")
        }
        if (!(a is List)) a = a.toList
        var ac = a.count
        for (i in 0...colSize) {
            var j = i
            while (true) {
                Fmt.write(fmt, a[j])
                j = j + colSize
                if (j >= ac) break
                System.write(sep)
            }
            System.print()
        }
    }

    // Convenience versions of the above methods which use a single space for the separator.
    static tprint(fmt, a, rowSize) { tprint(fmt, a, rowSize, " ") }
    static cprint(fmt, a, colSize) { cprint(fmt, a, colSize, " ") }

    // Prints (with a following \n) an array 'a' to stdout using a typical layout.
    // An 'array' for this purpose is a list or sequence of objects.
    // The parameters: 'w', 'p' and 'bb' are applied using the 'v' method to 'a'.
    // The settings for the other parameters are:
    // 'fn' = "f" for numbers, "z" for complex numbers,"s" otherwise
    // ('p' is ignored for latter) 'sep' = " ", 'cc' = "".
    // The 'rns' parameter, if true, leaves a space for the sign of a real number but only prints minus.
    static aprint(a, w, p, bb, rns) {
        if (!(a is Sequence)) Fiber.abort("Second argument must be a sequence.")
        if (!(a is List)) a = a.toList
        var fn = (a.count > 0 && (a[0] is Num)) ? "f" :
                 (a.count > 0 && (a[0].type.toString == "Complex")) ? "z" : "s"
        if (rns && fn != "s") fn = fn + "m"
        System.print(Fmt.v(fn, w, a, p, " ", bb, ""))
    }

    // Convenience versions of the above method which use default values for
    // some parameters.
    static aprint(a, w, p, bb) { aprint(a, w, p, bb, false) }
    static aprint(a, w, p)     { aprint(a, w, p, "[]", false) }
    static aprint(a, w)        { aprint(a, w, precision, "[]", false) }
    static aprint(a)           { aprint(a, 0, precision, "[]", false) }

    // Prints (with a following \n) a matrix 'm' to stdout using a typical layout.
    // A 'matrix' for this purpose is a two-dimensional list or sequence of objects.
    // A Matrix or CMatrix object is automatically converted to a 2D list of numbers.
    // The parameters: 'w', 'p' and 'bb' are applied using the 'v2' method to 'm'.
    // The settings for the other parameters are:
    // 'fn' = "f" for numbers, "z" for complex numbers, "s" otherwise
    // ('p' is ignored for latter) 'sep' = " ", 'cc' = "", 'ss' = "\n".
    // The rns' parameter, if true, leaves a space for the sign of a real number but only prints minus.
    static mprint(m, w, p, bb, rns) {
       if (!(m is List)) m = m.toList
       var fn = (m.count > 0 && m[0].count > 0 && (m[0][0] is Num)) ? "f" :
                (m.count > 0 && m[0].count > 0 && (m[0][0].type.toString == "Complex")) ? "z" : "s"
       if (rns && fn != "s") fn = fn + "m"
       System.print(Fmt.v2(fn, w, m, p, " ", bb, "", "\n"))
    }

    // Convenience versions of the above method which use default values for
    // some parameters.
    static mprint(m, w, p, bb) { mprint(m, w, p, bb, false) } 
    static mprint(m, w, p)     { mprint(m, w, p, "|", false) }
    static mprint(m, w)        { mprint(m, w, precision, "|", false) }
    static mprint(m)           { mprint(m, 0, precision, "|", false) }

    // Formats a polynomial as a string.
    // Polynomials are represented by an ordered list of coefficients
    // from the term with the highest degree down to the constant term.
    // Unless there is only one term, terms with zero coefficents are suppressed.
    // Unless it's the constant term, the '1' in coefficients of exactly ± 1 is also suppressed.
    // 'fmt' is applied to each coefficient, 'symbol' is the exponentiation
    // symbol (e.g. "^") and 'variable' (e.g. "x") is the variable name.
    // If symbol = "", unicode superscript characters are used for the degree.
    static spwrite(fmt, coefs, symbol, variable) {
        if (!(fmt is String)) Fiber.abort("First argument must be a string.")
        if (!(coefs is List) || coefs.count == 0 || !(coefs[0] is Num)) {
            Fiber.abort("Second argument must be a non-empty ordered list of numbers.")
        }
        if (!(symbol is String)) Fiber.abort("Third argument must be a string.")
        if (!(variable is String)) Fiber.abort("Fourth argument must be a string.")
        var degree = coefs.count - 1
        if (degree == 0 || coefs.all { |c| c == 0 }) return Fmt.swrite(fmt, coefs[0])
        var p = ""
        for (i in 0..degree) {
            var coef = coefs[i]
            var pow = degree - i
            if (coef == 0) continue
            if (coef > 0) {
                var t = Fmt.swrite(fmt, coef)
                if (pow > 0 && coef == 1 && t[-1] == "1") t = t[0...-1]
                p = p + " + " + t
            } else {
                var t = Fmt.swrite(fmt, -coef)
                if (pow > 0 && coef == -1 && t[-1] == "1") t = t[0...-1]
                p = p + " - " + t
            }
            if (pow > 1) {
                if (symbol != "") {
                    p = p + Fmt.swrite("$s$s$d", variable, symbol, pow)
                } else {
                    p = p + Fmt.swrite("$s$S", variable, pow)
                }
            } else if (pow == 1) {
                p = p + variable
            }
        }
        p = p.startsWith(" + ") ? p[3..-1] : "-" + p[3..-1]
        return p
    }

    // Prints a polynomial without (pwrite) or with (pprint) a following \n to stdout.
    static pwrite(fmt, coefs, symbol, variable) {
        System.write(spwrite(fmt, coefs, symbol, variable))
    }

    static pprint(fmt, coefs, symbol, variable) {
        System.print(spwrite(fmt, coefs, symbol, variable))
    }
}

/* Name contains infrastructure and routines for converting numbers to their English names. */
class Name {
    // Private method to initialize static fields.
    static init_() {
        __uk = false
        __neg = "minus"
        __point = "point"
        __zero = "zero"
  
        __small =  [
            "zero", "one", "two", "three", "four", "five", "six",  "seven", "eight", "nine", "ten", "eleven",
            "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"
        ]

        __tens = ["", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"]

        __illions = [
            "", " thousand", " million", " billion"," trillion", " quadrillion", " quintillion", 
            " sextillion", " septillion", " octillion", " nonillion", " decillion"
        ]
 
        __irregularOrdinals = {
            "nought": "noughth", 
            "one":    "first",
            "two":    "second",
            "three":  "third",
            "five":   "fifth",
            "eight":  "eighth",
            "nine":   "ninth",
            "twelve": "twelfth"
        }

        __revIrregulars = {}
        for (me in __irregularOrdinals) __revIrregulars[me.value] = me.key

        __names = {}
        for (i in 0..19) __names[__small[i]] = i
        for (i in 2..9)  __names[__tens[i]] = i * 10 
        __names["hundred"] = 100
        for (i in 1..11) __names[__illions[i][1..-1]] = 10.pow(i * 3)
        __names["nought"] = 0

        __zeros = ["zero", "nought", "nil", "none", "nothing"]

        __points = ["point", "dot", "spot"]
    }

    // Private helper function. Converts ASCII string to lower case.
    static lower_(s) { s.bytes.map { |b|
        return String.fromByte((b >= 65 && b <= 90) ? b + 32 : b)
    }.join() }

    // Gets or sets whether names are to expressed in UK English i.e. 'and' is used to connect
    // double or single digit numbers with larger numbers. The default is 'false'.
    static uk     { __uk }
    static uk=(b) { __uk = (b is Bool) ? b : __uk}

    // Gets or sets the prefix word for negative numbers. The default is "minus" though another
    // possibility is "negative".
    static negative     { __neg  }
    static negative=(n) { __neg = (n is String) ? n : __neg }

    // Gets or sets the word for the decimal point in non-integral numbers. The default is "point"
    // though other possibilities are "dot" or "spot".
    static point     { __point }
    static point=(p) { __point = (p is String) ? p : __point }

    // Gets or sets the word for the number '0'. The default is "zero" though another
    // possibility is 'nought'.
    static zero     { __zero }
    static zero=(z) { __zero = (z is String) ? __small[0] = z : __zero }

    // Returns the name of a number which can be positive or negative and can include a decimal point.
    // Note that this is unsafe for numbers with an absolute value >= 2^53 but may work in some cases.
    static fromNum(n) {
        if (!(n is Num)) Fiber.abort("'n' must be a number.")
        if (n.isInfinity || n.isNan) return Fmt.s(0, n)
        var f = 0
        if (!n.isInteger) {
            f = n.fraction
            n = n.truncate
        }
        var and = __uk ?  "and " : ""
        var t = ""
        if (n < 0) {
            t = __neg + " "
            n = -n
        }
        if (n < 20) {
            t = t + __small[n]
        } else if (n < 100) {
            t = t + __tens[(n/10).floor]
            var s = n % 10
            if (s > 0) t = t + "-" + __small[s]
        } else if (n < 1000) {
            t = t + __small[(n/100).floor] + " hundred"
            var s = n % 100
            if (s > 0) t = t + " " + and + fromNum(s)
        } else {
            var sx = ""
            var i = 0
            while (n > 0) {
                var p = n % 1000
                n = (n/1000).floor
                if (p > 0) {
                    var ix = fromNum(p) + __illions[i]
                    if (sx != "") ix = ix + " " + sx
                    sx = ix
                }
                i = i + 1
            }
            t = t + sx
        }
        if (f > 0) {
            t = t + " " + __point
            for (d in f.toString.skip(2)) {
                t = t + " %(__small[Num.fromString(d)])"
            }
        }
        return t
    }

    // Returns the ordinal name of an integer (including negative integers).
    // Note that this is unsafe for integers with an absolute value >= 2^53 but may work in some cases.
    static ordinal(n) {
        if (!(n is Num && n.isInteger)) Fiber.abort("'n' must be an integer.")
        var s = fromNum(n)
        var r = s[-1..0]
        var i1 = r.indexOf(" ")
        if (i1 != -1) i1 = s.count - 1 - i1
        var i2 = r.indexOf("-")
        if (i2 != -1) i2 = s.count - 1 - i2
        var i = (i1 > i2) ? i1 : i2
        i = i + 1
        var x = __irregularOrdinals[s[i..-1]]
        if (x) {
            return s[0...i] + x
        } else if (s[-1] == "y") {
            return s[0...i] + s[i..-2] + "ieth"
        } else {
            return s[0...i] + s[i..-1] + "th"
        }
    }

    // Translates the name of a number or ordinal integer into that number and returns it.
    // Ignores case and custom settings but recognizes all suggested alternatives and some others.
    static toNum(name) {
        if (!(name is String) || name == "") Fiber.abort("'name' must be a non-empty string.")
        name = lower_(name).replace(",", " ").replace("-", " ").replace(" and", "")
        var words = name.split(" ").where { |w| w != "" }.toList
        var isNegative = words[0] == "minus" || words[0] == "negative"
        if (isNegative || words[0] == "plus") words = words[1..-1]
        if (words[0] == "a") words[0] = "one"
        if (words[-1].endsWith("ieth")) {
            words[-1] = words[-1][0..-5] + "y"
        } else if(__revIrregulars.containsKey(words[-1])) {
            words[-1] = __revIrregulars[words[-1]]
        } else if (words[-1].endsWith("th")) {
            words[-1] = words[-1][0..-3]
        }
        var size = words.count
        if (size == 1 && __zeros.contains(words[0])) return 0
        if (size == 1 && words[0] == "nan") return Num.nan
        if (size == 1 && words[0] == "infinity") return isNegative ? -Num.infinity : Num.infinity
        var ix = -1
        for (p in __points) {
            if ((ix = words.indexOf(p)) >= 0) break
        }
        if (ix == 0) {
            words.insert(0, "zero")
            ix = 1
        }
        var dec = ""
        if (ix > 0) {
            for (word in words[ix+1..-1]) dec = dec + __names[word].toString
            size = ix
        }
        var multiplier = 1
        var lastNum = -1
        var sum = 0
        for (i in size-1..0) {
            var num = __names[words[i]]
            if (!num) Fiber.abort("'%(words[i])' is not a valid cardinal number.")
            if (num == lastNum) Fiber.abort("'%(name)' is not a well formed numeric string.")
            if (num >= 1000) {
                if (lastNum >= 100) {
                    Fiber.abort("'%(name)' is not a well formed numeric string.")
                }
                multiplier = num
                if (i == 0) sum = sum + multiplier
            } else if (num >= 100) {
                multiplier = multiplier * 100
                if (i == 0) sum = sum + multiplier
            } else if (num >= 20) {
                if (lastNum >= 10 && lastNum <= 90) {
                    Fiber.abort("'%(name)' is not a well formed numeric string.")
                }
                sum = sum + num*multiplier
            } else {
                if (lastNum >= 1 && lastNum <= 90) {
                    Fiber.abort("'%(name)' is not a well formed numeric string.")
                }
                sum = sum + num*multiplier
            }
            lastNum = num
        }
        if (dec != "") sum = sum + Num.fromString("0." + dec)
        return (isNegative) ? -sum : sum
    }
}

Name.init_()