Category talk:Fmt: Difference between revisions

From Rosetta Code
Content added Content deleted
(Added source code for 'fmt' module to its talk page.)
 
(→‎Source code: Bug fixes plus some minor improvements.)
Line 131: Line 131:
static dec(n) { itoa(n, 10) } // Ensures safe decimal integers printed as such.
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) { itoa(n, 16) } // Converts an integer to hex.
static Hex(n) { conv.Itoa(n, 16) } // Converts an integer to hex (upper case digits).
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 pdec(n) { ((n >= 0) ? "+" : "") + dec(n) } // Adds '+' for non-negative integers.
Line 172: Line 172:
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]
}
}


Line 192: Line 205:
// 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) { n is String && n != "" && 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 206: Line 219:
var i = n.count - 3
var i = n.count - 3
while (i >= 1) {
while (i >= 1) {
n = n[0...i] + "," + n[i..-1]
n = n[0...i] + c + n[i..-1]
i = i - 3
i = i - 3
}
}
Line 221: Line 234:
/* 'Short name' methods, useful for formatting values in interpolated strings. */
/* 'Short name' methods, useful for formatting values in interpolated strings. */


// Format an integer 'n' in (d)ecimal, (b)inary, (o)ctal, he(x) or upper case he(X).
// Formats an integer 'n' in (d)ecimal, (b)inary, (o)ctal, he(x) or upper case he(X).
// Pad 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) ? rpad(w, Conv.dec(n)) : lpad(-w, Conv.dec(n)) }
Line 230: Line 243:
static X(w, n) { (w >= 0) ? rpad(w, Conv.Hex(n)) : lpad(-w, Conv.Hex(n)) }
static X(w, n) { (w >= 0) ? rpad(w, Conv.Hex(n)) : lpad(-w, Conv.Hex(n)) }


// As above but pad with zeros instead of spaces.
// As above but pads with leading zeros instead of spaces.
// Any minus sign will be placed before the padding.
static dz(w, n) { (w >= 0) ? rpad(w, Conv.dec(n), "0") : lpad(-w, Conv.dec(n), "0") }
// When used with negative 'w' behaves the same as the above methods.
static bz(w, n) { (w >= 0) ? rpad(w, Conv.bin(n), "0") : lpad(-w, Conv.bin(n), "0") }
static oz(w, n) { (w >= 0) ? rpad(w, Conv.oct(n), "0") : lpad(-w, Conv.oct(n), "0") }
static dz(w, n) { (w >= 0) ? rzpad(w, Conv.dec(n)) : lpad(-w, Conv.dec(n)) }
static xz(w, n) { (w >= 0) ? rpad(w, Conv.hex(n), "0") : lpad(-w, Conv.hex(n), "0") }
static bz(w, n) { (w >= 0) ? rzpad(w, Conv.bin(n)) : lpad(-w, Conv.bin(n)) }
static Xz(w, n) { (w >= 0) ? rpad(w, Conv.Hex(n), "0") : lpad(-w, Conv.Hex(n), "0") }
static oz(w, n) { (w >= 0) ? rzpad(w, Conv.oct(n)) : lpad(-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) ? rzpad(w, Conv.Hex(n)) : lpad(-w, Conv.Hex(n)) }


// Formats 'n' in decimal 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) ? rpad(w, Conv.pdec(n)) : lpad(-w, Conv.pdec(n)) }


// Formats 'n' in decimal 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) ? rpad(w, Conv.mdec(n)) : lpad(-w, Conv.mdec(n)) }


// Formats 'n' in commatized form 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) ? rpad(w, commatize(Conv.dec(n))): lpad(-w, commatize(Conv.dec(n))) }


Line 282: Line 297:
}
}


// As above but pads with leading zeros instead of spaces.
// Convenience version of the above method which uses the default precision.
// Any minus sign will be placed before the padding.
static f(w, n) { f(w, precision, n) }
// When used with negative 'w' behaves the same as the above method.
}</lang>
static fz(w, n, p) { (w >= 0) ? rzpad(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>

Revision as of 15:20, 10 May 2020

Source code

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

/* Str contains routines which apply common transformations to strings. */ class Str {

   // Converts a string or list of strings 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 or list of strings 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) }
   }
   // Capitalizes the first character of a string or list of strings.
   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 or list of strings.
   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 code points (not necessarily single bytes) of a string.
   static reverse(s) {
       if (!(s is String)) s = "%(s)"
       if (s == "") return s
       return s.codePoints.toList[-1..0].reduce("") { |acc, c| acc + String.fromCodePoint(c) }
   }

}

/* 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 some routines to 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 lpad(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 rpad(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
   }
   // 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'.
   static cpad(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 lpad(w, s) { lpad(w, s, " ") }
   static rpad(w, s) { rpad(w, s, " ") }
   static cpad(w, s) { cpad(w, s, " ") }
   // Checks whether argument is a numeric decimal string.
   static isDecimal(n) { n is String && n != "" && 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 neg = (n[0] == "-")
       if (neg) 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 (neg) ? "-" + n : n
   }
   // Convenience version of the above method which uses a comma as the separator.
   static commatize(n) { commatize(n, ",") }
   // 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) ? rpad(w, Conv.dec(n)) : lpad(-w, Conv.dec(n)) }
   static b(w, n) { (w >= 0) ? rpad(w, Conv.bin(n)) : lpad(-w, Conv.bin(n)) } 
   static o(w, n) { (w >= 0) ? rpad(w, Conv.oct(n)) : lpad(-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) ? rpad(w, Conv.Hex(n)) : lpad(-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) ? rzpad(w, Conv.dec(n)) : lpad(-w, Conv.dec(n)) }
   static bz(w, n) { (w >= 0) ? rzpad(w, Conv.bin(n)) : lpad(-w, Conv.bin(n)) } 
   static oz(w, n) { (w >= 0) ? rzpad(w, Conv.oct(n)) : lpad(-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) ? rzpad(w, Conv.Hex(n)) : lpad(-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) ? rpad(w, Conv.pdec(n)) : lpad(-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) ? rpad(w, Conv.mdec(n)) : lpad(-w, Conv.mdec(n)) }
   // 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))) }
   // 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) ? rpad(w, v) : lpad(-w, v) }
   // Centers a string or value 'v' within a field of minimum width 'w'. Pads with spaces.
   static c(w, v) { cpad(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) ? rzpad(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>