Category talk:Fmt

From Rosetta Code
Revision as of 17:54, 11 May 2020 by PureFox (talk | contribs) (→‎Source code: Fixed some bugs, lots of improvements. Hopefully fairly stable now.)

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>