Category talk:Wren-fmt: Difference between revisions
Content deleted Content added
Minor bug-fix. |
→Source code: Several improvements, notably the addition of (s)printf style methods. |
||
Line 1: | Line 1: | ||
===Source code=== |
===Source code=== |
||
<lang ecmascript>/* Module "fmt.wren" */ |
<lang ecmascript>/* Module "fmt.wren" */ |
||
/* Conv contains routines which do conversions between types. */ |
/* Conv contains routines which do conversions between types. */ |
||
class Conv { |
class Conv { |
||
Line 9: | Line 9: | ||
// All possible digits (upper case). |
// All possible digits (upper case). |
||
static upperDigits { "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" } |
static upperDigits { "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" } |
||
// Maximum safe integer = 2^53 - 1. |
// Maximum safe integer = 2^53 - 1. |
||
static maxSafeInt { 9007199254740991 } |
static maxSafeInt { 9007199254740991 } |
||
// Prefix map for different bases. |
|||
static prefixes { { "b": "0b", "t": "0t", "o": "0o", "d": "0d", "x": "0x", "X": "0X" } } |
|||
// Converts an integer to a numeric ASCII string with a base between 2 and 36. |
// Converts an integer to a numeric ASCII string with a base between 2 and 36. |
||
static itoa(n, b) { |
static itoa(n, b) { |
||
Line 27: | Line 30: | ||
return ((neg) ? "-" : "") + res[-1..0] |
return ((neg) ? "-" : "") + res[-1..0] |
||
} |
} |
||
// Private helper function. Converts ASCII string to upper case. |
// Private helper function. Converts ASCII string to upper case. |
||
static upper_(s) { s.bytes.map { |b| |
static upper_(s) { s.bytes.map { |b| |
||
return String.fromByte((b >= 97 && b <= 122) ? b - 32 : b) |
return String.fromByte((b >= 97 && b <= 122) ? b - 32 : b) |
||
}.join() } |
}.join() } |
||
// As itoa(n, b) but resulting digits are upper case. |
// As itoa(n, b) but resulting digits are upper case. |
||
static Itoa(n, b) { (b < 11) ? itoa(n, b) : upper_(itoa(n, b)) } |
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. |
// Converts a numeric ASCII string with a base between 2 and 36 to an integer. |
||
static atoi(s, b) { |
static atoi(s, b) { |
||
Line 49: | Line 52: | ||
if (s == "") Fiber.abort("String must contain some digits.") |
if (s == "") Fiber.abort("String must contain some digits.") |
||
s = upper_(s) |
s = upper_(s) |
||
if ((s.startsWith("0B") && b != 2) || (s.startsWith(" |
if ((s.startsWith("0B") && b != 2) || (s.startsWith("0T") && b != 3) || |
||
(s.startsWith("0O") && b != 8) || (s.startsWith("0X") && b != 16)) { |
|||
Fiber.abort("Inconsistent base specifier.") |
Fiber.abort("Inconsistent base specifier.") |
||
} |
} |
||
if (s.startsWith("0B") || s.startsWith("0O") || s.startsWith("0X")) { |
if (s.startsWith("0B") || s.startsWith("0T") || s.startsWith("0O") || s.startsWith("0X")) { |
||
s = s[2..-1] |
s = s[2..-1] |
||
if (s == "") Fiber.abort("String after base specifier must contain some digits.") |
if (s == "") Fiber.abort("String after base specifier must contain some digits.") |
||
Line 65: | Line 69: | ||
return (neg) ? -res : res |
return (neg) ? -res : res |
||
} |
} |
||
// Convenience versions of itoa and atoi which use a base of 10. |
// Convenience versions of itoa and atoi which use a base of 10. |
||
static itoa(s) { itoa(s, 10) } |
static itoa(s) { itoa(s, 10) } |
||
static atoi(s) { atoi(s, 10) } |
static atoi(s) { atoi(s, 10) } |
||
// Integer/bool conversion routines. |
// Integer/bool conversion routines. |
||
static itob(i) { (i is Num && i.isInteger) ? (i != 0) : null } |
static itob(i) { (i is Num && i.isInteger) ? (i != 0) : null } |
||
static btoi(b) { (b is Bool) ? (b ? 1 : 0) : null } |
static btoi(b) { (b is Bool) ? (b ? 1 : 0) : null } |
||
// Integer/character conversion routines. |
// Integer/character conversion routines. |
||
static itoc(i) { (i is Num && i.isInteger && i >= 0 && i <= 0x10ffff) ? String.fromCodePoint(i) : null } |
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 ctoi(c) { (c is String && c.count == 1) ? c.codePoints[0] : null } |
||
static bin(n) { itoa(n, 2) } // Converts an integer to binary. |
static bin(n) { itoa(n, 2) } // Converts an integer to binary. |
||
static ter(n) { itoa(n, 3) } // Converts an integer to ternary. |
|||
static oct(n) { itoa(n, 8) } // Converts an integer to octal. |
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 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. |
||
static mdec(n) { ((n >= 0) ? " " : "") + dec(n) } // Only uses '-', leaves space for '+'. |
static mdec(n) { ((n >= 0) ? " " : "") + dec(n) } // Only uses '-', leaves space for '+'. |
||
// Converts |
// Converts a non-negative integer to its ordinal equivalent. |
||
static ord(n) { |
static ord(n) { |
||
if (!(n is Num && n.isInteger && n >= 0)) Fiber.abort("Argument must be a non-negative integer.") |
if (!(n is Num && n.isInteger && n >= 0)) Fiber.abort("Argument must be a non-negative integer.") |
||
Line 104: | Line 109: | ||
} |
} |
||
} |
} |
||
/* Fmt contains routines which format numbers or strings in various ways. */ |
/* Fmt contains routines which format numbers or strings in various ways. */ |
||
class Fmt { |
class Fmt { |
||
Line 115: | Line 120: | ||
return (w > c) ? s + p * (w - c) : s |
return (w > c) ? s + p * (w - c) : s |
||
} |
} |
||
// Right justifies 's' in a field of minimum width 'w' using the pad character 'p'. |
// Right justifies 's' in a field of minimum width 'w' using the pad character 'p'. |
||
static rjust(w, s, p) { |
static rjust(w, s, p) { |
||
Line 124: | Line 129: | ||
return (w > c) ? p * (w - c) + s : s |
return (w > c) ? p * (w - c) + s : s |
||
} |
} |
||
// Centers 's' in a field of minimum width 'w' using the pad character 'p'. |
// Centers 's' in a field of minimum width 'w' using the pad character 'p'. |
||
static cjust(w, s, p) { |
static cjust(w, s, p) { |
||
Line 135: | Line 140: | ||
return p * l + s + p * (w - c - l) |
return p * l + s + p * (w - c - l) |
||
} |
} |
||
// Convenience versions of the above which use a space as the pad character. |
// Convenience versions of the above which use a space as the pad character. |
||
static ljust(w, s) { ljust(w, s, " ") } |
static ljust(w, s) { ljust(w, s, " ") } |
||
static rjust(w, s) { rjust(w, s, " ") } |
static rjust(w, s) { rjust(w, s, " ") } |
||
static cjust(w, s) { cjust(w, s, " ") } |
static cjust(w, s) { cjust(w, s, " ") } |
||
// Right justifies 's' in a field of minimum width 'w' using the pad character '0'. |
// 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. |
// Unlike rjust, any sign or elided sign (i.e. space) will be placed before the padding. |
||
Line 153: | Line 158: | ||
return sign + "0" * (w - c) + s[1..-1] |
return sign + "0" * (w - c) + s[1..-1] |
||
} |
} |
||
// Private helper method for 'commatize' method. |
// Private helper method for 'commatize' method. |
||
// Checks whether argument is a numeric decimal string. |
// Checks whether argument is a numeric decimal string. |
||
Line 164: | Line 169: | ||
return n.all { |c| "0123456789".contains(c) } |
return 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 187: | Line 192: | ||
return (signed) ? sign + n : n |
return (signed) ? sign + n : n |
||
} |
} |
||
// Convenience version of the above method which uses a comma as the separator. |
// Convenience version of the above method which uses a comma as the separator. |
||
static commatize(n) { commatize(n, ",") } |
static commatize(n) { commatize(n, ",") } |
||
// Convenience method which commatizes an ordinal number using a comma as the separator. |
// Convenience method which commatizes an ordinal number using a comma as the separator. |
||
static ordinalize(n) { commatize(n) + Conv.ord(n)[-2..-1] } |
static ordinalize(n) { commatize(n) + Conv.ord(n)[-2..-1] } |
||
// Private helper method for 'abbreviate' method. |
// Private helper method for 'abbreviate' method. |
||
static sub_(s, r) { s.toList[r].join() } |
static sub_(s, r) { s.toList[r].join() } |
||
// Abbreviates a string 's' to a maximum number of characters 'w' (non-overlapping) at either end |
// 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. |
// or, if 'w' is negative from the front only, using 'sep' as the separator. |
||
Line 209: | Line 214: | ||
return sub_(s, 0...le) + sep + ((w >= 0) ? sub_(s, -le..-1) : "") |
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. |
// Convenience version of the above method which uses 'three dots' as the separator. |
||
static abbreviate(w, s) { abbreviate(w, s, "...") } |
static abbreviate(w, s) { abbreviate(w, s, "...") } |
||
// Gets or sets precision for 'f(w, n)' convenience |
// Gets or sets precision for 'f(w, n)' style convenience methods. |
||
static precision { ( __precision != null) ? __precision : 6 } |
static precision { ( __precision != null) ? __precision : 6 } |
||
static precision=(p) { __precision = ((p is Num) && p.isInteger && p >= 0) ? p : __precision } |
static precision=(p) { __precision = ((p is Num) && p.isInteger && p >= 0) ? p : __precision } |
||
/* 'Short name' methods, useful for formatting values in interpolated strings. */ |
/* '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). |
// Formats an integer 'n' in (d)ecimal, (b)inary, (t)ernary, (o)ctal, he(x) or upper case he(X). |
||
// Pads 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) ? rjust(w, Conv.dec(n)) : ljust(-w, Conv.dec(n)) } |
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 b(w, n) { (w >= 0) ? rjust(w, Conv.bin(n)) : ljust(-w, Conv.bin(n)) } |
||
static |
static t(w, n) { (w >= 0) ? rjust(w, Conv.ter(n)) : ljust(-w, Conv.ter(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)) } |
||
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. |
// As above but pads with leading zeros instead of spaces. |
||
// Any minus sign will be placed before the padding. |
// Any minus sign will be placed before the padding. |
||
// When used with negative 'w' behaves the same as the above methods. |
// 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 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 bz(w, n) { (w >= 0) ? zfill(w, Conv.bin(n)) : ljust(-w, Conv.bin(n)) } |
||
static tz(w, n) { (w >= 0) ? zfill(w, Conv.ter(n)) : ljust(-w, Conv.ter(n)) } |
|||
static oz(w, n) { (w >= 0) ? zfill(w, Conv.oct(n)) : ljust(-w, Conv.oct(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)) } |
||
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. |
// 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)) } |
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. |
// 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)) } |
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 |
// 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))) } |
static dc(w, n) { (w >= 0) ? rjust(w, commatize(Conv.dec(n))): ljust(-w, commatize(Conv.dec(n))) } |
||
// Ranks a non-negative integer 'n' i.e. expresses it in ordinal form, space padded. |
|||
static r(w, n) { (w >= 0) ? rjust(w, Conv.ord(n)) : ljust(-w, Conv.ord(n)) } |
|||
// Pads a character (equivalent to the codepoint 'n') with spaces to a minimum width of 'w'. |
|||
// Negative 'w' left justifies, non-negative 'w' right justifies. |
|||
static c(w, n) { (w >= 0) ? rjust(w, Conv.itoc(n)): ljust(-w, Conv.itoc(n)) } |
|||
// Pads a string or value 'v' with spaces to a minimum width of 'w'. |
// Pads a string or value 'v' 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 s(w, v) { (w >= 0) ? rjust(w, v) : ljust(-w, v) } |
static s(w, v) { (w >= 0) ? rjust(w, v) : ljust(-w, v) } |
||
// |
// Middles a string or value 'v' within a field of minimum width 'w'. Pads with spaces. |
||
static |
static m(w, v) { cjust(w, v) } |
||
// Embeds a string or value 'v' in 'cc', a string with no more than two characters. |
// Embeds a string or value 'v' in 'cc', a string with no more than two characters. |
||
Line 262: | Line 276: | ||
} |
} |
||
if (len == 0) return (v is String) ? v : "%(v)" |
if (len == 0) return (v is String) ? v : "%(v)" |
||
if (len == 1) cc = cc + cc |
if (len == 1) cc = cc + cc |
||
return "%(cc[0])%(v)%(cc[1])" |
return "%(cc[0])%(v)%(cc[1])" |
||
} |
} |
||
Line 289: | Line 303: | ||
var d = (n - i).abs |
var d = (n - i).abs |
||
var pw = 10.pow(p) |
var pw = 10.pow(p) |
||
d = (d * pw).round |
d = (d * pw).round |
||
if (d >= pw) { |
if (d >= pw) { |
||
ns = "%(Conv.dec(n.round))" |
ns = "%(Conv.dec(n.round))" |
||
d = 0 |
d = 0 |
||
} |
} |
||
if (d == 0) return s(w, ns + "." + "0" * p) |
if (d == 0) return s(w, ns + "." + "0" * p) |
||
var ds = "%(d)" |
var ds = "%(d)" |
||
Line 302: | Line 316: | ||
// Works like 'f' except replaces any trailing zeros after the decimal point with spaces. |
// Works like 'f' except replaces any trailing zeros after the decimal point with spaces. |
||
// If the resulting string would end with a decimal point, a zero is first added back. |
// If the resulting string would end with a decimal point, a zero is first added back. |
||
static g(w, n, p) { |
static g(w, n, p) { |
||
var f = f(w, n, p) |
var f = f(w, n, p) |
||
Line 353: | Line 367: | ||
static fc(w, n) { fc(w, n, precision) } |
static fc(w, n) { fc(w, n, precision) } |
||
static gc(w, n) { gc(w, n, precision) } |
static gc(w, n) { gc(w, n, precision) } |
||
// Private worker method which calls a'short name' method and returns its result. |
|||
static callFn_(fn, w, e, p) { |
|||
return (fn == "d") ? d(w, e) : |
|||
(fn == "b") ? b(w, e) : |
|||
(fn == "t") ? t(w, e) : |
|||
(fn == "o") ? o(w, e) : |
|||
(fn == "x") ? x(w, e) : |
|||
(fn == "X") ? Fmt.X(w, e) : |
|||
(fn == "r") ? r(w, e) : |
|||
(fn == "c") ? c(w, e) : |
|||
(fn == "s") ? s(w, e) : |
|||
(fn == "m") ? m(w, e) : |
|||
(fn == "q") ? q(e) : |
|||
(fn == "f") ? f(w, e, p) : |
|||
(fn == "g") ? g(w, e, p) : |
|||
(fn == "dz") ? dz(w, e) : |
|||
(fn == "bz") ? bz(w, e) : |
|||
(fn == "tz") ? tz(w, e) : |
|||
(fn == "oz") ? oz(w, e) : |
|||
(fn == "xz") ? xz(w, e) : |
|||
(fn == "Xz") ? Fmt.Xz(w, e) : |
|||
(fn == "fz") ? fz(w, e, p) : |
|||
(fn == "gz") ? gz(w, e, p) : |
|||
(fn == "dp") ? dp(w, e) : |
|||
(fn == "dm") ? dm(w, e) : |
|||
(fn == "dc") ? dc(w, e) : |
|||
(fn == "fc") ? fc(w, e, p) : |
|||
(fn == "gc") ? gc(w, e, p) : Fiber.abort("Method not recognized.") |
|||
} |
|||
// Applies a 'short' formatting method to each element of a list or sequence 'seq'. |
// Applies a 'short' formatting method to each element of a list or sequence 'seq'. |
||
// The method to be applied is specified (as a string) in 'fn'. |
// The method to be applied is specified (as a string) in 'fn'. |
||
// The parameters to be passed to the method are specified in 'w' and 'p' |
// The parameters to be passed to the method are specified in 'w' and 'p' |
||
// 'p' is needed for 'f', 'g', 'fz' or ' |
// 'p' is needed for 'f', 'g', 'fz', 'gz', 'fc' or 'gc' but is ignored otherwise. |
||
// The resulting strings are then joined together using the separator 'sep'. |
// The resulting strings are then joined together using the separator 'sep'. |
||
// having first applied the 'q' method, with parameter 'cc', to each of them. |
// having first applied the 'q' method, with parameter 'cc', to each of them. |
||
Line 366: | Line 410: | ||
for (i in 0...seq.count) { |
for (i in 0...seq.count) { |
||
var e = seq[i] |
var e = seq[i] |
||
l[i] = (fn |
l[i] = q(callFn_(fn, w, e, p), cc) |
||
(fn == "b") ? b(w, e) : |
|||
(fn == "o") ? o(w, e) : |
|||
(fn == "x") ? x(w, e) : |
|||
(fn == "X") ? Fmt.X(w, e) : |
|||
(fn == "s") ? s(w, e) : |
|||
(fn == "c") ? c(w, e) : |
|||
(fn == "f") ? f(w, e, p) : |
|||
(fn == "g") ? g(w, e, p) : |
|||
(fn == "dz") ? dz(w, e) : |
|||
(fn == "bz") ? bz(w, e) : |
|||
(fn == "oz") ? oz(w, e) : |
|||
(fn == "xz") ? xz(w, e) : |
|||
(fn == "Xz") ? Fmt.Xz(w, e) : |
|||
(fn == "fz") ? fz(w, e, p) : |
|||
(fn == "gz") ? gz(w, e, p) : |
|||
(fn == "dp") ? dp(w, e) : |
|||
(fn == "dm") ? dm(w, e) : |
|||
(fn == "dc") ? dc(w, e) : |
|||
(fn == "fc") ? fc(w, e, p) : |
|||
(fn == "gc") ? gc(w, e, p) : Fiber.abort("Method not recognized.") |
|||
l[i] = q(l[i], cc) |
|||
} |
} |
||
return q(l.join(sep), bb) |
return q(l.join(sep), bb) |
||
Line 397: | Line 420: | ||
static v(fn, w, seq, p) { v(fn, w, seq, p, ", ", "[]", "") } |
static v(fn, w, seq, p) { v(fn, w, seq, p, ", ", "[]", "") } |
||
static v(fn, w, seq) { v(fn, w, seq, precision, ", ", "[]", "") } |
static v(fn, w, seq) { v(fn, w, seq, precision, ", ", "[]", "") } |
||
// Provides a 'sprintf' style method where the arguments are passed in a separate list and |
|||
// formatted in turn by verbs embedded in a format string. Excess arguments are ignored but |
|||
// it is an error to provide insufficient arguments. Verbs must be given in this form: |
|||
// $[flag][width][.precision][letter] of which all bracketed items except [letter] are optional. |
|||
// The letter must be one of the 'short' methods: b, c, d, f, g, m, o, q, r, s, t, v, x or X. |
|||
// If present, the flag (there can only be one) must be one of the following: |
|||
// + always prints a + or - sign ('dp' method) |
|||
// (space) leaves a space for the sign but only prints minus ('dm' method) |
|||
// , commatizes the following number ('dc', 'fc' or 'gc' methods) |
|||
// # adds the appropriate prefix for the number formats: b, t, o, d, x and X. |
|||
// * reads the width from the argument before the one to be formatted |
|||
// If present, the width is the minimum width (+/-) to be passed to the appropriate method. |
|||
// It doesn't include any '#' flag prefix. If [width] is absent, a width of zero is passed. |
|||
// If present, the precision is the number of decimal places to be passed to the appropriate |
|||
// 'f' or 'g' style method. If absent, the default precision is passed. |
|||
// Where any optional item is inappropriate to the method being used it is simply ignored. |
|||
// Where one of the arguments is a sequence (other than a string) this method senses it |
|||
// and applies the 'v' method to it. However, the 'sep' parameter is always a single space |
|||
// and the 'bb' and 'cc' parameters are always empty strings. The '#' flag has no effect. |
|||
static slwrite(fmt, a) { |
|||
if (!(fmt is String)) Fiber.abort("First argument must be a string.") |
|||
if (!(a is List)) Fiber.abort("Second argument must be a list.") |
|||
if (fmt == "") return "" |
|||
var cps = fmt.codePoints.toList |
|||
var le = cps.count // number of codepoints |
|||
var s = "" // accumulates the result string |
|||
var i = 0 // current codepoint index |
|||
var cp = 0 // current codepoint |
|||
var next = 0 // index of next argument to be formatted |
|||
// Gets the next numeric string from the format. |
|||
var getNumber = Fn.new { |
|||
i = i + 1 |
|||
if (i == le) Fiber.abort("Invalid format string.") |
|||
cp = cps[i] |
|||
var ns = "" |
|||
while (cp >= 48 && cp <= 57) { |
|||
ns = ns + Conv.itoc(cp) |
|||
i = i + 1 |
|||
if (i == le) Fiber.abort("Invalid format string.") |
|||
cp = cps[i] |
|||
} |
|||
return ns |
|||
} |
|||
while (i < le) { |
|||
cp = cps[i] |
|||
if (cp != 36) { // not a dollar sign |
|||
s = s + Conv.itoc(cp) |
|||
} else if (i < le -1 && cps[i + 1] == 36) { // check for $$ |
|||
s = s + "$" |
|||
i = i + 1 |
|||
} else { |
|||
var ns = getNumber.call() |
|||
if (ns != "" && "*+,- #".codePoints.contains(cp)) { |
|||
Fiber.abort("Invalid format string.") |
|||
} |
|||
var plus = false |
|||
var comma = false |
|||
var minus = false |
|||
var space = false |
|||
var hash = false |
|||
var fn = "" |
|||
var ds = "" |
|||
if ("bcdfgmoqrstxX".codePoints.contains(cp)) { // format letter |
|||
fn = Conv.itoc(cp) |
|||
} else if (cp == 42) { // star |
|||
if (next < a.count) { |
|||
ns = "%(a[next])" |
|||
next = next + 1 |
|||
} else { |
|||
Fiber.abort("Insufficient arguments passed.") |
|||
} |
|||
i = i + 1 |
|||
cp = cps[i] |
|||
if (cp == 46) ds = getNumber.call() |
|||
} else if (cp == 43) { // plus sign |
|||
plus = true |
|||
ns = getNumber.call() |
|||
if (cp == 46) ds = getNumber.call() |
|||
} else if (cp == 44) { // comma |
|||
comma = true |
|||
ns = getNumber.call() |
|||
if (cp == 46) ds = getNumber.call() |
|||
} else if (cp == 45) { // minus sign |
|||
minus = true |
|||
ns = getNumber.call() |
|||
if (cp == 46) ds = getNumber.call() |
|||
} else if (cp == 46) { // dot |
|||
ds = getNumber.call() |
|||
} else if (cp == 32) { // space |
|||
space = true |
|||
ns = getNumber.call() |
|||
if (cp == 46) ds = getNumber.call() |
|||
} else if (cp == 35) { // hash |
|||
hash = true |
|||
ns = getNumber.call() |
|||
if (cp == 46) ds = getNumber.call() |
|||
} else { |
|||
Fiber.abort("Unrecognized character in format string.") |
|||
} |
|||
if (fn == "") { |
|||
if (!"bcdfgmoqrstxX".codePoints.contains(cp)) { |
|||
Fiber.abort("Unrecognized character in format string.") |
|||
} |
|||
fn = Conv.itoc(cp) |
|||
} |
|||
if (fn == "d") { |
|||
if (plus) { |
|||
fn = "dp" |
|||
} else if (space) { |
|||
fn = "dm" |
|||
} else if (comma) { |
|||
fn = "dc" |
|||
} |
|||
} else if ((fn == "f" || fn == "g") && comma) { |
|||
fn = fn + "c" |
|||
} |
|||
if (ns == "") ns = "1" |
|||
if (ns[0] == "0" && ns.count > 1 && "dbtoxXfg".contains(fn[0])) { |
|||
fn = fn[0] + "z" |
|||
} |
|||
var n = Num.fromString(ns) |
|||
var w = minus ? -n : n |
|||
var p = (ds != "") ? Num.fromString(ds) : precision |
|||
if (next < a.count) { |
|||
var e = a[next] |
|||
if ((e is Sequence) && !(e is String)) { |
|||
s = s + Fmt.v(fn, w, e, p, " ", "", "") |
|||
} else { |
|||
var r = callFn_(fn, w, e, p) |
|||
if (hash && "btodxX".contains(fn[0])) { |
|||
if (r[0] == "-") { |
|||
r = "-" + Conv.prefixes[fn[0]] + r[1..-1] |
|||
} else { |
|||
r = Conv.prefixes[fn[0]] + r |
|||
} |
|||
} |
|||
s = s + r |
|||
} |
|||
next = next + 1 |
|||
} else { |
|||
Fiber.abort("Insufficient arguments passed.") |
|||
} |
|||
} |
|||
i = i + 1 |
|||
} |
|||
return s |
|||
} |
|||
// Convenience versions of the 'slwrite' method which allow up to 5 arguments |
|||
// to be passed individually rather than in a list. |
|||
static swrite(fmt, a1, a2, a3, a4, a5) { slwrite(fmt, [a1, a2, a3, a4, a5]) } |
|||
static swrite(fmt, a1, a2, a3, a4) { slwrtie(fmt, [a1, a2, a3, a4]) } |
|||
static swrite(fmt, a1, a2, a3) { slwrite(fmt, [a1, a2, a3]) } |
|||
static swrite(fmt, a1, a2) { slwrite(fmt, [a1, a2]) } |
|||
static swrite(fmt, a1) { slwrite(fmt, [a1]) } |
|||
// Applies slwrite to the arguments and then 'writes' it (no following \n) to stdout. |
|||
static write(fmt, a1, a2, a3, a4, a5) { System.write(slwrite(fmt, [a1, a2, a3, a4, a5])) } |
|||
static write(fmt, a1, a2, a3, a4) { System.write(slwrite(fmt, [a1, a2, a3, a4])) } |
|||
static write(fmt, a1, a2, a3) { System.write(slwrite(fmt, [a1, a2, a3])) } |
|||
static write(fmt, a1, a2) { System.write(slwrite(fmt, [a1, a2])) } |
|||
static write(fmt, a1) { System.write(slwrite(fmt, [a1])) } |
|||
static lwrite(fmt, a) { System.write(slwrite(fmt, a)) } |
|||
// Applies slwrite to the arguments and then 'prints' it (with a following \n) to stdout. |
|||
static print(fmt, a1, a2, a3, a4, a5) { System.print(slwrite(fmt, [a1, a2, a3, a4, a5])) } |
|||
static print(fmt, a1, a2, a3, a4) { System.print(slwrite(fmt, [a1, a2, a3, a4])) } |
|||
static print(fmt, a1, a2, a3) { System.print(slwrite(fmt, [a1, a2, a3])) } |
|||
static print(fmt, a1, a2) { System.print(slwrite(fmt, [a1, a2])) } |
|||
static print(fmt, a1) { System.print(slwrite(fmt, [a1])) } |
|||
static lprint(fmt, a) { System.print(slwrite(fmt, a)) } |
|||
} |
} |
||
Revision as of 08:12, 1 July 2020
Source code
<lang ecmascript>/* Module "fmt.wren" */
/* Conv contains routines which do conversions between types. */ class Conv {
// All possible digits. static digits { "0123456789abcdefghijklmnopqrstuvwxyz" }
// All possible digits (upper case). static upperDigits { "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" }
// Maximum safe integer = 2^53 - 1. static maxSafeInt { 9007199254740991 }
// Prefix map for different bases. static prefixes { { "b": "0b", "t": "0t", "o": "0o", "d": "0d", "x": "0x", "X": "0X" } }
// 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("0T") && b != 3) || (s.startsWith("0O") && b != 8) || (s.startsWith("0X") && b != 16)) { Fiber.abort("Inconsistent base specifier.") } if (s.startsWith("0B") || s.startsWith("0T") || 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 = upperDigits[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 ter(n) { itoa(n, 3) } // Converts an integer to ternary. 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 a non-negative 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)' style convenience methods. 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, (t)ernary, (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 t(w, n) { (w >= 0) ? rjust(w, Conv.ter(n)) : ljust(-w, Conv.ter(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 tz(w, n) { (w >= 0) ? zfill(w, Conv.ter(n)) : ljust(-w, Conv.ter(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))) }
// Ranks a non-negative integer 'n' i.e. expresses it in ordinal form, space padded. static r(w, n) { (w >= 0) ? rjust(w, Conv.ord(n)) : ljust(-w, Conv.ord(n)) }
// Pads a character (equivalent to the codepoint 'n') with spaces to a minimum width of 'w'. // Negative 'w' left justifies, non-negative 'w' right justifies. static c(w, n) { (w >= 0) ? rjust(w, Conv.itoc(n)): ljust(-w, Conv.itoc(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) }
// Middles a string or value 'v' within a field of minimum width 'w'. Pads with spaces. static m(w, v) { cjust(w, v) }
// Embeds a string or value 'v' in 'cc', a string with no more than two characters. // If it has none, no embedding takes place. If has one, it's doubled. // The first character is added at the left and the second at the right. static q(v, cc) { var len if (!(cc is String && (len = cc.count) < 3)) { Fiber.abort("Second argument must be a string with no more than 2 characters.") } if (len == 0) return (v is String) ? v : "%(v)" if (len == 1) cc = cc + cc return "%(cc[0])%(v)%(cc[1])" }
// Convenience version of the above which uses double quotes as the embedding characters. static q(v) { "\"%(v)\"" }
// Pads a number 'n' with leading spaces to a minimum width 'w' and a precision of 'p' decimal places. // Precision is restricted to 14 places though entering a higher figure is not an error. // 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 if (p > 14) p = 14 var i = (p == 0) ? n.round : n.truncate var ns = "%(Conv.dec(i))" if (i == 0 && n < 0) ns = "-" + ns 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 if (d >= pw) { ns = "%(Conv.dec(n.round))" d = 0 } if (d == 0) return s(w, ns + "." + "0" * p) var ds = "%(d)" var c = ds.count if (c < p) ds = "0" * (p-c) + ds return s(w, ns + "." + ds[0...p]) }
// Works like 'f' except replaces any trailing zeros after the decimal point with spaces. // If the resulting string would end with a decimal point, a zero is first added back. static g(w, n, p) { var f = f(w, n, p) if (f.contains(".") && f[-1] == "0") { var l1 = f.count f = f.trimEnd("0") if (f[-1] == ".") f = f + "0" f = f + (" " * (l1 - f.count)) } return f }
// 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 fz(w, n, p) { (w >= 0) ? zfill(w, f(w, n, p).trimStart()) : f(w, n, p) } static gz(w, n, p) { (w >= 0) ? zfill(w, g(w, n, p).trimStart()) : g(w, n, p) }
// Formats the integer part of 'n' in commatized form, space padded, // using ',' as the separator. The decimal part is not affected. static fc(w, n, p) { var f = f(w, n, p) if (f.contains("infinity") || f == "nan" || f.contains("e")) return f var ix = f.indexOf(".") var dp = (ix >= 0) ? f[ix..-1] : "" var c = dp.count w = (w >= 0) ? w - c : w + c if (w < 0) w = 0 return dc(w, n.truncate) + dp }
// Works like 'fc' except replaces any trailing zeros after the decimal point with spaces. // If the resulting string would end with a decimal point, a zero is first added back. static gc(w, n, p) { var f = fc(w, n, p) if (f.contains(".") && f[-1] == "0") { var l1 = f.count f = f.trimEnd("0") if (f[-1] == ".") f = f + "0" f = f + (" " * (l1 - f.count)) } return f }
// Convenience versions of the above methods which use the default precision. static f(w, n) { f(w, n, precision) } static g(w, n) { g(w, n, precision) } static fz(w, n) { fz(w, n, precision) } static gz(w, n) { gz(w, n, precision) } static fc(w, n) { fc(w, n, precision) } static gc(w, n) { gc(w, n, precision) }
// Private worker method which calls a'short name' method and returns its result. static callFn_(fn, w, e, p) { return (fn == "d") ? d(w, e) : (fn == "b") ? b(w, e) : (fn == "t") ? t(w, e) : (fn == "o") ? o(w, e) : (fn == "x") ? x(w, e) : (fn == "X") ? Fmt.X(w, e) : (fn == "r") ? r(w, e) : (fn == "c") ? c(w, e) : (fn == "s") ? s(w, e) : (fn == "m") ? m(w, e) : (fn == "q") ? q(e) : (fn == "f") ? f(w, e, p) : (fn == "g") ? g(w, e, p) : (fn == "dz") ? dz(w, e) : (fn == "bz") ? bz(w, e) : (fn == "tz") ? tz(w, e) : (fn == "oz") ? oz(w, e) : (fn == "xz") ? xz(w, e) : (fn == "Xz") ? Fmt.Xz(w, e) : (fn == "fz") ? fz(w, e, p) : (fn == "gz") ? gz(w, e, p) : (fn == "dp") ? dp(w, e) : (fn == "dm") ? dm(w, e) : (fn == "dc") ? dc(w, e) : (fn == "fc") ? fc(w, e, p) : (fn == "gc") ? gc(w, e, p) : Fiber.abort("Method not recognized.") }
// Applies a 'short' formatting method to each element of a list or sequence 'seq'. // The method to be applied is specified (as a string) in 'fn'. // The parameters to be passed to the method are specified in 'w' and 'p' // 'p' is needed for 'f', 'g', 'fz', 'gz', 'fc' or 'gc' but is ignored otherwise. // The resulting strings are then joined together using the separator 'sep'. // having first applied the 'q' method, with parameter 'cc', to each of them. // Finally, the 'q' method is applied again, with parameter 'bb', to the whole // string, if a prefix/suffix is needed. static v(fn, w, seq, p, sep, bb, cc) { var l = List.filled(seq.count, "") for (i in 0...seq.count) { var e = seq[i] l[i] = q(callFn_(fn, w, e, p), cc) } return q(l.join(sep), bb) }
// Convenience versions of the above method which use default values // for some parameters. static v(fn, w, seq, p, sep, bb) { v(fn, w, seq, p, sep, bb, "") } static v(fn, w, seq, p) { v(fn, w, seq, p, ", ", "[]", "") } static v(fn, w, seq) { v(fn, w, seq, precision, ", ", "[]", "") }
// Provides a 'sprintf' style method where the arguments are passed in a separate list and // formatted in turn by verbs embedded in a format string. Excess arguments are ignored but // it is an error to provide insufficient arguments. Verbs must be given in this form: // $[flag][width][.precision][letter] of which all bracketed items except [letter] are optional. // The letter must be one of the 'short' methods: b, c, d, f, g, m, o, q, r, s, t, v, x or X. // If present, the flag (there can only be one) must be one of the following: // + always prints a + or - sign ('dp' method) // (space) leaves a space for the sign but only prints minus ('dm' method) // , commatizes the following number ('dc', 'fc' or 'gc' methods) // # adds the appropriate prefix for the number formats: b, t, o, d, x and X. // * reads the width from the argument before the one to be formatted // If present, the width is the minimum width (+/-) to be passed to the appropriate method. // It doesn't include any '#' flag prefix. If [width] is absent, a width of zero is passed. // If present, the precision is the number of decimal places to be passed to the appropriate // 'f' or 'g' style method. If absent, the default precision is passed. // Where any optional item is inappropriate to the method being used it is simply ignored. // Where one of the arguments is a sequence (other than a string) this method senses it // and applies the 'v' method to it. However, the 'sep' parameter is always a single space // and the 'bb' and 'cc' parameters are always empty strings. The '#' flag has no effect. static slwrite(fmt, a) { if (!(fmt is String)) Fiber.abort("First argument must be a string.") if (!(a is List)) Fiber.abort("Second argument must be a list.") if (fmt == "") return "" var cps = fmt.codePoints.toList var le = cps.count // number of codepoints var s = "" // accumulates the result string var i = 0 // current codepoint index var cp = 0 // current codepoint var next = 0 // index of next argument to be formatted
// Gets the next numeric string from the format. var getNumber = Fn.new { i = i + 1 if (i == le) Fiber.abort("Invalid format string.") cp = cps[i] var ns = "" while (cp >= 48 && cp <= 57) { ns = ns + Conv.itoc(cp) i = i + 1 if (i == le) Fiber.abort("Invalid format string.") cp = cps[i] } return ns }
while (i < le) { cp = cps[i] if (cp != 36) { // not a dollar sign s = s + Conv.itoc(cp) } else if (i < le -1 && cps[i + 1] == 36) { // check for $$ s = s + "$" i = i + 1 } else { var ns = getNumber.call() if (ns != "" && "*+,- #".codePoints.contains(cp)) { Fiber.abort("Invalid format string.") } var plus = false var comma = false var minus = false var space = false var hash = false var fn = "" var ds = "" if ("bcdfgmoqrstxX".codePoints.contains(cp)) { // format letter fn = Conv.itoc(cp) } else if (cp == 42) { // star if (next < a.count) { ns = "%(a[next])" next = next + 1 } else { Fiber.abort("Insufficient arguments passed.") } i = i + 1 cp = cps[i] if (cp == 46) ds = getNumber.call() } else if (cp == 43) { // plus sign plus = true ns = getNumber.call() if (cp == 46) ds = getNumber.call() } else if (cp == 44) { // comma comma = true ns = getNumber.call() if (cp == 46) ds = getNumber.call() } else if (cp == 45) { // minus sign minus = true ns = getNumber.call() if (cp == 46) ds = getNumber.call() } else if (cp == 46) { // dot ds = getNumber.call() } else if (cp == 32) { // space space = true ns = getNumber.call() if (cp == 46) ds = getNumber.call() } else if (cp == 35) { // hash hash = true ns = getNumber.call() if (cp == 46) ds = getNumber.call() } else { Fiber.abort("Unrecognized character in format string.") }
if (fn == "") { if (!"bcdfgmoqrstxX".codePoints.contains(cp)) { Fiber.abort("Unrecognized character in format string.") } fn = Conv.itoc(cp) } if (fn == "d") { if (plus) { fn = "dp" } else if (space) { fn = "dm" } else if (comma) { fn = "dc" } } else if ((fn == "f" || fn == "g") && comma) { fn = fn + "c" } if (ns == "") ns = "1" if (ns[0] == "0" && ns.count > 1 && "dbtoxXfg".contains(fn[0])) { fn = fn[0] + "z" } var n = Num.fromString(ns) var w = minus ? -n : n var p = (ds != "") ? Num.fromString(ds) : precision if (next < a.count) { var e = a[next] if ((e is Sequence) && !(e is String)) { s = s + Fmt.v(fn, w, e, p, " ", "", "") } else { var r = callFn_(fn, w, e, p) if (hash && "btodxX".contains(fn[0])) { if (r[0] == "-") { r = "-" + Conv.prefixes[fn[0]] + r[1..-1] } else { r = Conv.prefixes[fn[0]] + r } } s = s + r } next = next + 1 } else { Fiber.abort("Insufficient arguments passed.") } } i = i + 1 } return s }
// Convenience versions of the 'slwrite' method which allow up to 5 arguments // to be passed individually rather than in a list. static swrite(fmt, a1, a2, a3, a4, a5) { slwrite(fmt, [a1, a2, a3, a4, a5]) } static swrite(fmt, a1, a2, a3, a4) { slwrtie(fmt, [a1, a2, a3, a4]) } static swrite(fmt, a1, a2, a3) { slwrite(fmt, [a1, a2, a3]) } static swrite(fmt, a1, a2) { slwrite(fmt, [a1, a2]) } static swrite(fmt, a1) { slwrite(fmt, [a1]) }
// Applies slwrite to the arguments and then 'writes' it (no following \n) to stdout. static write(fmt, a1, a2, a3, a4, a5) { System.write(slwrite(fmt, [a1, a2, a3, a4, a5])) } static write(fmt, a1, a2, a3, a4) { System.write(slwrite(fmt, [a1, a2, a3, a4])) } static write(fmt, a1, a2, a3) { System.write(slwrite(fmt, [a1, a2, a3])) } static write(fmt, a1, a2) { System.write(slwrite(fmt, [a1, a2])) } static write(fmt, a1) { System.write(slwrite(fmt, [a1])) } static lwrite(fmt, a) { System.write(slwrite(fmt, a)) }
// Applies slwrite to the arguments and then 'prints' it (with a following \n) to stdout. static print(fmt, a1, a2, a3, a4, a5) { System.print(slwrite(fmt, [a1, a2, a3, a4, a5])) } static print(fmt, a1, a2, a3, a4) { System.print(slwrite(fmt, [a1, a2, a3, a4])) } static print(fmt, a1, a2, a3) { System.print(slwrite(fmt, [a1, a2, a3])) } static print(fmt, a1, a2) { System.print(slwrite(fmt, [a1, a2])) } static print(fmt, a1) { System.print(slwrite(fmt, [a1])) } static lprint(fmt, a) { System.print(slwrite(fmt, a)) }
}
// Type aliases for classes in case of any name clashes with other modules. var Fmt_Conv = Conv var Fmt_Fmt = Fmt</lang>