Category talk:Fmt: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎Source code: Found and fixed a minor bug.)
(→‎Source code: Fixed some bugs, lots of improvements. Hopefully fairly stable now.)
Line 3: Line 3:
<lang ecmascript>/* Module "fmt.wren" */
<lang ecmascript>/* Module "fmt.wren" */


/* Str contains routines which apply common transformations to strings. */
/* Str contains routines which manipulate strings in various ways. */
class Str {
class Str {
// Converts a string or list of strings to lower case.
// Converts a string to lower case.
static lower(s) {
static lower(s) {
if (!(s is String)) s = "%(s)"
if (!(s is String)) s = "%(s)"
Line 17: Line 17:
}
}


// Converts a string or list of strings to upper case.
// Converts a string to upper case.
static upper(s) {
static upper(s) {
if (!(s is String)) s = "%(s)"
if (!(s is String)) s = "%(s)"
Line 29: Line 29:
}
}


// Capitalizes the first character of a string or list of strings.
// Swaps the case of each character in a string.
static swapCase(s) {
if (!(s is String)) s = "%(s)"
if (s == "") return s
var cps = s.codePoints.toList
for (i in 0...cps.count) {
var c = cps[i]
if (c >= 65 && c <= 90) {
cps[i] = c + 32
} else if (c >= 97 && c <= 122) {
cps[i] = c - 32
}
}
return cps.reduce("") { |acc, c| acc + String.fromCodePoint(c) }
}

// Capitalizes the first character of a string.
static capitalize(s) {
static capitalize(s) {
if (!(s is String)) s = "%(s)"
if (!(s is String)) s = "%(s)"
Line 43: Line 59:
}
}


// Capitalizes the first character of each word of a string or list of strings.
// Capitalizes the first character of each word of a string.
static title(s) {
static title(s) {
if (!(s is String)) s = "%(s)"
if (!(s is String)) s = "%(s)"
Line 51: Line 67:
}
}


// Reverses the code points (not necessarily single bytes) of a string.
// Reverses the characters (not necessarily single bytes) of a string.
static reverse(s) {
static reverse(s) {
if (!(s is String)) s = "%(s)"
if (!(s is String)) s = "%(s)"
if (s == "") return s
return (s != "") ? s[-1..0] : s
}
return s.codePoints.toList[-1..0].reduce("") { |acc, c| acc + String.fromCodePoint(c) }

// Performs a circular shift of the characters of 's' one place to the left.
static lshift(s) {
if (!(s is String)) s = "%(s)"
var chars = s.toList
var count = chars.count
if (count < 2) return s
var t = chars[0]
for (i in 0..count-2) chars[i] = chars[i+1]
chars[-1] = t
return chars.join()
}

// Performs a circular shift of the characters of 's' one place to the right.
static rshift(s) {
if (!(s is String)) s = "%(s)"
var chars = s.toList
var count = chars.count
if (count < 2) return s
var t = chars[-1]
for (i in count-2..0) chars[i+1] = chars[i]
chars[0] = t
return chars.join()
}

/* The indices (or ranges thereof) for all the following functions are measured in codepoints (not bytes).
As with core library methods, the indices must be within bounds or errors will be generated. */

// Extracts the sub-string of 's' over the range 'r'.
static sub(s, r) {
if (!(r is Range)) Fiber.abort("Second argument must be a range.")
if (!(s is String)) s = "%(s)"
return s.toList[r].join()
}

// Gets the character of 's' at index 'i'.
static get(s, i) {
if (!(i is Num && i.isInteger && i >= 0)) Fiber.abort("Index must be a non-negative integer.")
if (!(s is String)) s = "%(s)"
return s.toList[i]
}

// Changes the character of 's' at index 'i' to the string 't'.
static change(s, i, t) {
if (!(i is Num && i.isInteger && i >= 0)) Fiber.abort("Index must be a non-negative integer.")
if (!(t is String)) Fiber.abort("Replacment must be a string.")
if (!(s is String)) s = "%(s)"
var chars = s.toList
chars[i] = t
return chars.join()
}

// Inserts at index 'i' of 's' the string 't'.
static insert(s, i, t) {
if (!(i is Num && i.isInteger && i >= 0)) Fiber.abort("Index must be a non-negative integer.")
if (!(t is String)) Fiber.abort("Insertion must be a string.")
if (!(s is String)) s = "%(s)"
var chars = s.toList
chars.insert(i, t)
return chars.join()
}

// Deletes the character of 's' at index 'i'.
static delete(s, i) {
if (!(i is Num && i.isInteger && i >= 0)) Fiber.abort("Index must be a non-negative integer.")
if (!(s is String)) s = "%(s)"
var chars = s.toList
chars.removeAt(i)
return chars.join()
}

// Exchanges the characters of 's' at indices 'i' and 'j'
static exchange(s, i, j) {
if (!(i is Num && i.isInteger && i >= 0)) Fiber.abort("First index must be a non-negative integer.")
if (!(j is Num && j.isInteger && j >= 0)) Fiber.abort("Second index must be a non-negative integer.")
if (!(s is String)) s = "%(s)"
if (i == j) return s
var chars = s.toList
var t = chars[i]
chars[i] = chars[j]
chars[j] = t
return chars.join()
}
}
}
}
Line 69: Line 167:
// Converts an integer to a numeric ASCII string with a base between 2 and 36.
// Converts an integer to a numeric ASCII string with a base between 2 and 36.
static itoa(n, b) {
static itoa(n, b) {
if (!(n is Num && n.isInteger) || n.abs > maxSafeInt) Fiber.abort("Argument must be a safe integer.")
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 (b < 2 || b > 36) Fiber.abort("Base must be between 2 and 36.")
if (n == 0) return "0"
if (n == 0) return "0"
Line 154: Line 252:
}
}


/* Fmt contains some routines to format numbers or strings in various ways. */
/* Fmt contains routines which format numbers or strings in various ways. */
class Fmt {
class Fmt {
// Left justifies 's' in a field of minimum width 'w' using the pad character 'p'.
// Left justifies 's' in a field of minimum width 'w' using the pad character 'p'.
static lpad(w, s, p) {
static ljust(w, s, p) {
if (!w.isInteger || w < 0) Fiber.abort("Width must be a non-negative integer.")
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 (!(p is String) || p.count != 1) Fiber.abort("Padder must be a single character string.")
Line 166: Line 264:


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

// Right justifies 's' in a field of minimum width 'w' using the pad character '0'.
// Unlike rpad, 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 rzpad(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 && (s[0] == "-" || s[0] == "+" || s[0] == " ")) ? s[0] : ""
if (sign == "") return "0" * (w - c) + s
return sign + "0" * (w - c) + s[1..-1]
}
}


// Centers 's' in a field of minimum width 'w' using the pad character 'p'.
// Centers 's' in a field of minimum width 'w' using the pad character 'p'.
static cpad(w, s, p) {
static cjust(w, s, p) {
if (!w.isInteger || w < 0) Fiber.abort("Width must be a non-negative integer.")
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 (!(p is String) || p.count != 1) Fiber.abort("Padder must be a single character string.")
Line 199: Line 284:


// Convenience versions of the above which use a space as the pad character.
// Convenience versions of the above which use a space as the pad character.
static lpad(w, s) { lpad(w, s, " ") }
static ljust(w, s) { ljust(w, s, " ") }
static rpad(w, s) { rpad(w, s, " ") }
static rjust(w, s) { rjust(w, s, " ") }
static cpad(w, s) { cpad(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]
}


// Checks whether argument is a numeric decimal string.
// Checks whether argument is a numeric decimal string.
static isDecimal(n) { n is String && n != "" && n.all { |c| "-0123456789".contains(c) } }
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.
// Adds 'thousand separators' to a decimal integer or string.
static commatize(n, c) {
static commatize(n, c) {
Line 211: Line 316:
if (!(c is String) || c.count != 1) Fiber.abort("Separator must be a single character 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))"
if (n is Num) n = "%(Conv.dec(n))"
var neg = (n[0] == "-")
var signed = "-+ ".contains(n[0])
if (neg) n = n[1..-1]
var sign = ""
if (signed) {
sign = n[0]
n = n[1..-1]
}
if (n.startsWith("0") && n != "0") {
if (n.startsWith("0") && n != "0") {
n = n.trimStart("0")
n = n.trimStart("0")
Line 222: Line 331:
i = i - 3
i = i - 3
}
}
return (neg) ? "-" + n : n
return (signed) ? sign + n : n
}
}


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

// Convenience method which commatizes an ordinal number using a comma as the separator.
static ordinalize(n) { commatize(n) + Conv.ord(n)[-2..-1] }

// 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 non-zero 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 Str.sub(s, 0...le) + sep + ((w >= 0) ? Str.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)' convenience method.
// Gets or sets precision for 'f(w, n)' convenience method.
static precision { (__precision != null) ? __precision : 6 }
static precision { ( __precision != null) ? __precision : 6 }
static precision=(p) { __precision = ((p is Num) && p.isInteger && p >= 0) ? p : __precision }
static precision=(p) { __precision = ((p is Num) && p.isInteger && p >= 0) ? p : __precision }


Line 237: Line 365:
// Pads with spaces to a minimum width of 'w'.
// Pads with spaces to a minimum width of 'w'.
// Negative 'w' left justifies, non-negative 'w' right justifies.
// Negative 'w' left justifies, non-negative 'w' right justifies.
static d(w, n) { (w >= 0) ? rpad(w, Conv.dec(n)) : lpad(-w, Conv.dec(n)) }
static d(w, n) { (w >= 0) ? rjust(w, Conv.dec(n)) : ljust(-w, Conv.dec(n)) }
static b(w, n) { (w >= 0) ? rpad(w, Conv.bin(n)) : lpad(-w, Conv.bin(n)) }
static b(w, n) { (w >= 0) ? rjust(w, Conv.bin(n)) : ljust(-w, Conv.bin(n)) }
static o(w, n) { (w >= 0) ? rpad(w, Conv.oct(n)) : lpad(-w, Conv.oct(n)) }
static o(w, n) { (w >= 0) ? rjust(w, Conv.oct(n)) : ljust(-w, Conv.oct(n)) }
static x(w, n) { (w >= 0) ? rpad(w, Conv.hex(n)) : lpad(-w, Conv.hex(n)) }
static x(w, n) { (w >= 0) ? rjust(w, Conv.hex(n)) : ljust(-w, Conv.hex(n)) }
static X(w, n) { (w >= 0) ? rpad(w, Conv.Hex(n)) : lpad(-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.
// As above but pads with leading zeros instead of spaces.
// Any minus sign will be placed before the padding.
// Any minus sign will be placed before the padding.
// When used with negative 'w' behaves the same as the above methods.
// When used with negative 'w' behaves the same as the above methods.
static dz(w, n) { (w >= 0) ? rzpad(w, Conv.dec(n)) : lpad(-w, Conv.dec(n)) }
static dz(w, n) { (w >= 0) ? zfill(w, Conv.dec(n)) : ljust(-w, Conv.dec(n)) }
static bz(w, n) { (w >= 0) ? rzpad(w, Conv.bin(n)) : lpad(-w, Conv.bin(n)) }
static bz(w, n) { (w >= 0) ? zfill(w, Conv.bin(n)) : ljust(-w, Conv.bin(n)) }
static oz(w, n) { (w >= 0) ? rzpad(w, Conv.oct(n)) : lpad(-w, Conv.oct(n)) }
static oz(w, n) { (w >= 0) ? zfill(w, Conv.oct(n)) : ljust(-w, Conv.oct(n)) }
static xz(w, n) { (w >= 0) ? rzpad(w, Conv.hex(n)) : lpad(-w, Conv.hex(n)) }
static xz(w, n) { (w >= 0) ? zfill(w, Conv.hex(n)) : ljust(-w, Conv.hex(n)) }
static Xz(w, n) { (w >= 0) ? rzpad(w, Conv.Hex(n)) : lpad(-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.
// Formats 'n' in decimal, space padded, with a leading '+' if 'n' is non-negative or '-' otherwise.
static dp(w, n) { (w >= 0) ? rpad(w, Conv.pdec(n)) : lpad(-w, Conv.pdec(n)) }
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.
// Formats 'n' in decimal, space padded, with a leading ' ' if 'n' is non-negative or '-' otherwise.
static dm(w, n) { (w >= 0) ? rpad(w, Conv.mdec(n)) : lpad(-w, Conv.mdec(n)) }
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
// Formats 'n' in commatized form, space padded, using ',' as the separator
static dc(w, n) { (w >= 0) ? rpad(w, commatize(Conv.dec(n))): lpad(-w, commatize(Conv.dec(n))) }
static dc(w, n) { (w >= 0) ? rjust(w, commatize(Conv.dec(n))): ljust(-w, commatize(Conv.dec(n))) }


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


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


// Embeds a string or value 'v' in double quotes.
// Embeds a string or value 'v' in double quotes.
Line 300: Line 428:
// Any minus sign will be placed before the padding.
// Any minus sign will be placed before the padding.
// When used with negative 'w' behaves the same as the above method.
// When used with negative 'w' behaves the same as the above method.
static fz(w, n, p) { (w >= 0) ? rzpad(w, f(w, n, p).trimStart()) : f(w, n, p) }
static fz(w, n, p) { (w >= 0) ? zfill(w, f(w, n, p).trimStart()) : f(w, n, p) }


// Convenience versions of the above methods which use the default precision.
// Convenience versions of the above methods which use the default precision.

Revision as of 17:54, 11 May 2020

Source code

<lang ecmascript>/* Module "fmt.wren" */

/* Str contains routines which manipulate strings in various ways. */ class Str {

   // Converts a string to lower case.
   static lower(s) {
       if (!(s is String)) s = "%(s)"
       if (s == "") return s
       var cps = s.codePoints.toList
       for (i in 0...cps.count) {
           var c = cps[i]
           if (c >= 65 && c <= 90) cps[i] = c + 32
       }
       return cps.reduce("") { |acc, c| acc + String.fromCodePoint(c) }
   }
   // Converts a string to upper case.
   static upper(s) {
       if (!(s is String)) s = "%(s)"
       if (s == "") return s
       var cps = s.codePoints.toList
       for (i in 0...cps.count) {
           var c = cps[i]
           if (c >= 97 && c <= 122) cps[i] = c - 32
       }
       return cps.reduce("") { |acc, c| acc + String.fromCodePoint(c) }
   }
   // Swaps the case of each character in a string.
   static swapCase(s) {
       if (!(s is String)) s = "%(s)"
       if (s == "") return s
       var cps = s.codePoints.toList
       for (i in 0...cps.count) {
           var c = cps[i]
           if (c >= 65 && c <= 90) {
               cps[i] = c + 32
           } else if (c >= 97 && c <= 122) {
               cps[i] = c - 32
           }
       }
       return cps.reduce("") { |acc, c| acc + String.fromCodePoint(c) }
   }
   // Capitalizes the first character of a string.
   static capitalize(s) {
       if (!(s is String)) s = "%(s)"
       if (s == "") return s
       var cps = s.codePoints.toList
       var start = (s.startsWith("[") && cps.count > 1) ? 1 : 0
       var c = cps[start]
       if (c >= 97 && c <= 122) {
           cps[start] = c - 32
           return cps.reduce("") { |acc, c| acc + String.fromCodePoint(c) }
       }
       return s
   }
   // Capitalizes the first character of each word of a string.
   static title(s) {
       if (!(s is String)) s = "%(s)"
       if (s == "") return s
       var words = s.split(" ")
       return words.map { |w| capitalize(w) }.join(" ")
   }
   // Reverses the characters (not necessarily single bytes) of a string.
   static reverse(s) {
       if (!(s is String)) s = "%(s)"
       return (s != "") ? s[-1..0] : s
   }
   // Performs a circular shift of the characters of 's' one place to the left.
   static lshift(s) {
       if (!(s is String)) s = "%(s)"
       var chars = s.toList
       var count = chars.count
       if (count < 2) return s
       var t = chars[0]
       for (i in 0..count-2) chars[i] = chars[i+1]
       chars[-1] = t
       return chars.join()
   }
   // Performs a circular shift of the characters of 's' one place to the right.
   static rshift(s) {
       if (!(s is String)) s = "%(s)"
       var chars = s.toList
       var count = chars.count
       if (count < 2) return s
       var t = chars[-1]
       for (i in count-2..0) chars[i+1] = chars[i]
       chars[0] = t
       return chars.join()
   }
   /* The indices (or ranges thereof) for all the following functions are measured in codepoints (not bytes).
      As with core library methods, the indices must be within bounds or errors will be generated. */
   // Extracts the sub-string of 's' over the range 'r'.
   static sub(s, r) {
       if (!(r is Range)) Fiber.abort("Second argument must be a range.")
       if (!(s is String)) s = "%(s)"
       return s.toList[r].join()
   }
   // Gets the character of 's' at index 'i'.
   static get(s, i) {
       if (!(i is Num && i.isInteger && i >= 0)) Fiber.abort("Index must be a non-negative integer.")
       if (!(s is String)) s = "%(s)"
       return s.toList[i]
   }
   // Changes the character of 's' at index 'i' to the string 't'.
   static change(s, i, t) {
       if (!(i is Num && i.isInteger && i >= 0)) Fiber.abort("Index must be a non-negative integer.")
       if (!(t is String)) Fiber.abort("Replacment must be a string.")
       if (!(s is String)) s = "%(s)"
       var chars = s.toList
       chars[i] = t
       return chars.join()
   }
   // Inserts at index 'i' of 's' the string 't'.
   static insert(s, i, t) {
       if (!(i is Num && i.isInteger && i >= 0)) Fiber.abort("Index must be a non-negative integer.")
       if (!(t is String)) Fiber.abort("Insertion must be a string.")
       if (!(s is String)) s = "%(s)"
       var chars = s.toList
       chars.insert(i, t)
       return chars.join()
   }
   // Deletes the character of 's' at index 'i'.
   static delete(s, i) {
       if (!(i is Num && i.isInteger && i >= 0)) Fiber.abort("Index must be a non-negative integer.")
       if (!(s is String)) s = "%(s)"
       var chars = s.toList
       chars.removeAt(i)
       return chars.join()
   }
   // Exchanges the characters of 's' at indices 'i' and 'j'
   static exchange(s, i, j) {
       if (!(i is Num && i.isInteger && i >= 0)) Fiber.abort("First index must be a non-negative integer.")
       if (!(j is Num && j.isInteger && j >= 0)) Fiber.abort("Second index must be a non-negative integer.")
       if (!(s is String)) s = "%(s)"
       if (i == j) return s
       var chars = s.toList
       var t = chars[i]
       chars[i] = chars[j]
       chars[j] = t
       return chars.join()
   }

}

/* Conv contains routines which do conversions between types. */ class Conv {

   // All possible digits.
   static digits { "0123456789abcdefghijklmnopqrstuvwxyz" }
   // Maximum safe integer = 2^53 - 1.
   static maxSafeInt { 9007199254740991 }
   // 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]
   }
   // As itoa(n, b) but resulting digits are upper case.
   static Itoa(n, b) { (b < 11) ? itoa(n, b) : Str.upper(itoa(n, b)) }
   // Converts a numeric ASCII string with a base between 2 and 36 to an integer.
   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 = Str.lower(s)
       if ((s.startsWith("0b") && b != 2) || (s.startsWith("0o") && b != 8) || (s.startsWith("0x") && b != 16)) {
           Fiber.abort("Inconsistent base specifier.")
       }
       if (s.startsWith("0b") || s.startsWith("0o") || s.startsWith("0x")) {
           s = s[2..-1]
           if (s == "") Fiber.abort("String after base specifier must contain some digits.")
       }
       var res = 0
       var digs = digits[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 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 an 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)"
   }

}

/* 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]
   }
   // 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, ",") }
   // Convenience method which commatizes an ordinal number using a comma as the separator.
   static ordinalize(n) { commatize(n) + Conv.ord(n)[-2..-1] }
   // 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 non-zero 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 Str.sub(s, 0...le) + sep + ((w >= 0) ? Str.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)' convenience method.
   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, (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 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 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))) }
   // 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) }
   // Centers a string or value 'v' within a field of minimum width 'w'. Pads with spaces.
   static c(w, v) { cjust(w, v) }
   // Embeds a string or value 'v' in double quotes.
   static q(v) { "\"%(v)\"" }
   // Pads a number 'n' with leading spaces to a minimum width 'w' and a precision of 'p' decimal places.
   // 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
       var i = (p == 0) ? n.round :
               (n >= 0) ? n.floor : n.ceil
       var ns = "%(Conv.dec(i))"
       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 / pw
       if (d == 0) return s(w, ns + "." + "0" * p)
       var ds = "%(d)"[2..-1]
       var c = ds.count
       if (c < p) ds = ds + "0" * (p - c)
       return s(w, ns + "." + ds[0...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 method.
   static fz(w, n, p) { (w >= 0) ? zfill(w, f(w, n, p).trimStart()) : f(w, n, p) }
   // Convenience versions of the above methods which use the default precision.
   static f(w, n) { f(w, n, precision) }
   static fz(w, n) { fz(w, n, precision) }

}

// Type aliases for classes in case of any name clashes with other modules. var Fmt_Str = Str var Fmt_Conv = Conv var Fmt_Fmt = Fmt</lang>