Category talk:Fmt: Difference between revisions
Content added Content deleted
(→Source code: Temporary bug fix.) |
(→Source code: Found and fixed a minor bug.) |
||
Line 138: | Line 138: | ||
// Converts an integer to its ordinal equivalent. |
// Converts an integer to its ordinal equivalent. |
||
static ord(n) { |
static ord(n) { |
||
if (!(n is |
if (!(n is Num && n.isInteger && n >= 0)) Fiber.abort("Argument must be a non-negative integer.") |
||
var m = n % 100 |
var m = n % 100 |
||
if (m >= 4 && m <= 20) return "%(n)th" |
if (m >= 4 && m <= 20) return "%(n)th" |
Revision as of 17:24, 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>