Category talk:Fmt: Difference between revisions
Content added Content deleted
(→Source code: Module reorganization - moving Str class to new 'str' module.) |
(→Source code: Blanked page ahead of deletion.) |
||
Line 1: | Line 1: | ||
===Source code=== |
|||
<lang ecmascript>/* Module "fmt.wren" */ |
|||
/* 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] |
|||
} |
|||
// Private helper function. Converts ASCII string to upper case. |
|||
static upper_(s) { s.bytes.map { |b| |
|||
return String.fromByte((b >= 97 && b <= 122) ? b - 32 : b) |
|||
}.join() } |
|||
// As itoa(n, b) but resulting digits are upper case. |
|||
static Itoa(n, b) { (b < 11) ? itoa(n, b) : 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 = upper_(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] |
|||
} |
|||
// Private helper method for 'commatize' method. |
|||
// 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] } |
|||
// Private helper method for 'abbreviate' method. |
|||
static sub_(s, r) { s.toList[r].join() } |
|||
// 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 sub_(s, 0...le) + sep + ((w >= 0) ? 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_Conv = Conv |
|||
var Fmt_Fmt = Fmt</lang> |