Category talk:Fmt: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎Source code: Fixed some bugs, lots of improvements. Hopefully fairly stable now.)
(→‎Source code: Blanked page ahead of deletion.)
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
===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>

Latest revision as of 10:37, 25 May 2020