Category talk:Wren-fmt: Difference between revisions
Content added Content deleted
m (Added quotes to 'lang' attribute.) |
(→Source code: Some minor improvements.) |
||
Line 425:
// As above but prepends non-negative numbers with a '+' sign.
static fp(w, n, p) { signFloat_("f", w, n, p, "+") }
static gp(w, n, p) { signFloat_("g", w, n, p, "+") }
static hp(w, n, p) { signFloat_("h", w, n, p, "+") }
// As above but prepends non-negative numbers with a space.
static fm(w, n, p) { signFloat_("f", w, n, p, " ") }
static gm(w, n, p) { signFloat_("g", w, n, p, " ") }
static hm(w, n, p) { signFloat_("h", w, n, p, " ") }
// Private helper method for signing floating point numbers.
static signFloat_(fn, w, n, p, ch) {
var fmt = "$%(w).%(p)%(fn)"
if (n < 0) return swrite(fmt, n)
if (n > 0) return swrite(fmt, -n).replace("-",
return swrite(fmt, -1).replace("-1", "
}
Line 500 ⟶ 505:
static gp(w, n) { gp(w, n, precision) }
static hp(w, n) { hp(w, n, precision) }
static fm(w, n) { fm(w, n, precision) }
static gm(w, n) { gm(w, n, precision) }
static hm(w, n) { hm(w, n, precision) }
static fc(w, n) { fc(w, n, precision) }
static gc(w, n) { gc(w, n, precision) }
Line 542 ⟶ 550:
(fn == "gp") ? gp(w, v, p) :
(fn == "hp") ? hp(w, v, p) :
(fn == "fm") ? fm(w, v, p) :
(fn == "gm") ? gm(w, v, p) :
(fn == "hm") ? hm(w, v, p) :
(fn == "dp") ? dp(w, v) :
(fn == "dm") ? dm(w, v) :
Line 556 ⟶ 567:
// 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 'e', 'E', 'f', 'g', 'h', 'z', 'fz', 'gz', 'hz', 'fp', 'gp', 'hp',
// '
// The resulting strings are then joined together using the separator 'sep'.
// having first applied the 'q' method, with parameter 'cc', to each of them.
Line 615 ⟶ 626:
// If present, the flag (there can only be one) must be one of the following:
// + always prints a + or - sign ('dp', 'fp', 'gp' or 'hp' methods)
// (space) leaves a space for the sign but only prints minus ('dm',
// , commatizes the following number ('dc', 'rc', 'sc', 'ic', 'fc', 'gc' or 'hc' methods)
// # adds the appropriate prefix for the number formats: b, t, o, d, x and X
Line 732 ⟶ 743:
} else if ((fn == "f" || fn == "g" || fn == "h") && plus) {
fn = fn + "p"
} else if ((fn == "f" || fn == "g" || fn == "h") && space) {
fn = fn + "m"
} else if ((fn == "r" || fn == "s" || fn == "i" || fn == "f" ||
fn == "g" || fn == "h") && comma) {
Line 781 ⟶ 794:
}
// Convenience versions of the 'slwrite' method which allow up to
// to be passed individually rather than in a list.
static swrite(fmt, a1, a2, a3, a4, a5
static swrite(fmt, a1, a2, a3, a4, a5)
static swrite(fmt, a1, a2, a3, a4)
static swrite(fmt, a1, a2, a3)
static swrite(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, a6) { System.write(slwrite(fmt, [a1, a2, a3, a4, a5, a6])) }
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
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, a6) { System.print(slwrite(fmt, [a1, a2, a3, a4, a5, a6])) }
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
static lprint(fmt, a) { System.print(slwrite(fmt, a)) }
// Prints (with a following \n) a sequence 'a' to stdout in tabular form
|
Revision as of 10:31, 3 September 2022
Source code
/* 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.
// The string can optionally begin with a base specifier provided it is consistent with the base.
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)) {
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, ",") }
// Adds 'thousand' separators' to an ordinal number.
static ordinalize(n, c) { commatize(n, c) + Conv.ord(n)[-2..-1] }
// Convenience version of the above method which uses a comma as the separator.
static ordinalize(n) { ordinalize(n, ",") }
// 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 positive 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)) }
// As the above method but commatizes the ordinal number, using ',' as the separator.
static rc(w, n) { (w >= 0) ? rjust(w, ordinalize(n)) : ljust(-w, ordinalize(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) }
// As 's' 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 sz(w, v) { (w >= 0) ? zfill(w, v) : ljust(-w, v) }
// Formats a string or value 'v' in commatized form, space padded, using ',' as the separator.
static sc(w, v) {
if (!(v is String)) v = "%(v)"
return (w >= 0) ? rjust(w, commatize(v)): ljust(-w, commatize(v))
}
// These methods use the appropriate 'd' format if 'v' is a safe integer or the 's' format otherwise.
static i(w, v) { (v is Num && v.isInteger && v.abs <= Conv.maxSafeInt) ? d (w, v) : s (w, v) }
static iz(w, v) { (v is Num && v.isInteger && v.abs <= Conv.maxSafeInt) ? dz(w, v) : sz(w, v) }
static ic(w, v) { (v is Num && v.isInteger && v.abs <= Conv.maxSafeInt) ? dc(w, v) : sc(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) }
// 'Short name' synonym for abbreviate(w, s) method except doesn't abbreviate (rather than throwing
// an error) if a width of '0' is passed.
static a(w, v) { (w != 0) ? abbreviate(w, v) : s(0, v) }
// Enables a value to be printed in its 'normal' form (i.e. by applying the 'toString' method),
// within a space-padded minimum field of width 'w', notwithstanding any special formatting
// that would otherwise be applied by 'short name' methods.
static n(w, v) { s(w, v.toString) }
// Applies the 's' format to the kind (i.e. type) of 'v'.
static k(w, v) { s(w, v.type) }
// Converts a list of Unicode code points to a string and then applies the 's' format to it.
static u(w, v) {
if (!(v is List)) Fiber.abort("Second argument must be list of code points.")
var str = ""
for (c in v) str = str + String.fromCodePoint(c)
return s(w, str)
}
// 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)\"" }
// Formats a number 'n' (using 'h' format) to a maximum precision of 14 decimal places.
// It then converts it to exponential format and formats the mantissa to 'p' decimal places.
// The result is then padded with spaces to a minimum width 'w'.
// Negative 'w' left justifies, non-negative 'w' right justifies.
static e(w, n, p) {
var f = Fmt.h(w, n, 14).trim()
if (f.contains("e") || n.isInfinity || n.isNan) return Fmt.s(w, n) // use 'normal' representation
var dix = f.indexOf(".")
if (dix >= 0) {
f = f.replace(".", "")
} else {
dix = f.count
}
// look for index of first non-zero digit if there is one
var nzix = -1
var i = (f[0] == "-") ? 1 : 0
while (i < f.count) {
if (f[i] != "0") {
nzix = i
break
}
i = i + 1
}
if (nzix == -1) return "0e00"
var delta = dix - nzix
f = (nzix+1<f.count) ? f[nzix] + "." + f[nzix+1..-1] : f[nzix]
if (n < 0) f = "-" + f
f = Fmt.h(p+2, Num.fromString(f), p).trim()
var exp = (delta >= 0) ? Fmt.dz(2, delta-1) : Fmt.dz(3, delta-1)
return Fmt.s(w, "%(f)e%(exp)")
}
// Works like 'e' except that the exponent symbol 'e' is replaced by upper case 'E'.
static E(w, n, p) { e(w, n, p).replace("e", "E") }
// 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" || f[-1] == " ")) {
var l1 = f.count
f = f.trimEnd("0 ")
if (f[-1] == ".") f = f + "0"
f = f + (" " * (l1 - f.count))
}
return f
}
// Works like 'f' except replaces any trailing zeros after the decimal point with spaces.
// If the resulting string would end with a decimal point, that is also replaced with a space.
static h(w, n, p) {
var f = f(w, n, p)
if (f.contains(".") && (f[-1] == "0" || f[-1] == " ")) {
var l1 = f.count
f = f.trimEnd("0 ")
if (f[-1] == ".") f = f[0..-2]
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) }
static hz(w, n, p) { (w >= 0) ? zfill(w, h(w, n, p).trimStart()) : h(w, n, p) }
// As above but prepends non-negative numbers with a '+' sign.
static fp(w, n, p) { signFloat_("f", w, n, p, "+") }
static gp(w, n, p) { signFloat_("g", w, n, p, "+") }
static hp(w, n, p) { signFloat_("h", w, n, p, "+") }
// As above but prepends non-negative numbers with a space.
static fm(w, n, p) { signFloat_("f", w, n, p, " ") }
static gm(w, n, p) { signFloat_("g", w, n, p, " ") }
static hm(w, n, p) { signFloat_("h", w, n, p, " ") }
// Private helper method for signing floating point numbers.
static signFloat_(fn, w, n, p, ch) {
var fmt = "$%(w).%(p)%(fn)"
if (n < 0) return swrite(fmt, n)
if (n > 0) return swrite(fmt, -n).replace("-", ch)
return swrite(fmt, -1).replace("-1", "%(ch)0")
}
// 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" || f[-1] == " ")) {
var l1 = f.count
f = f.trimEnd("0 ")
if (f[-1] == ".") f = f + "0"
f = f + (" " * (l1 - f.count))
}
return f
}
// Works like 'fc' except replaces any trailing zeros after the decimal point with spaces.
// If the resulting string would end with a decimal point, that is also replaced with a space.
static hc(w, n, p) {
var f = fc(w, n, p)
if (f.contains(".") && (f[-1] == "0" || f[-1] == " ")) {
var l1 = f.count
f = f.trimEnd("0 ")
if (f[-1] == ".") f = f[0..-2]
f = f + (" " * (l1 - f.count))
}
return f
}
// Applies the 'f' format to each component, x and y, of a complex number 'n'
// before joining them together in the form x ± yi.
static z(w, n, p) {
if (n is Num) return f(w, n, p)
if (n.type.toString != "Complex") Fiber.abort("Argument must be a complex or real number.")
var real = f(w, n.real, p)
var sign = (n.imag >= 0) ? " + " : " - "
var imag = f(w, n.imag.abs, p)
return real + sign + imag + "i"
}
// Convenience versions of the above methods which use the default precision.
static e(w, n) { e(w, n, precision) }
static E(w, n) { Fmt.E(w, n, precision) }
static f(w, n) { f(w, n, precision) }
static g(w, n) { g(w, n, precision) }
static h(w, n) { h(w, n, precision) }
static z(w, n) { z(w, n, precision) }
static fz(w, n) { fz(w, n, precision) }
static gz(w, n) { gz(w, n, precision) }
static hz(w, n) { hz(w, n, precision) }
static fp(w, n) { fp(w, n, precision) }
static gp(w, n) { gp(w, n, precision) }
static hp(w, n) { hp(w, n, precision) }
static fm(w, n) { fm(w, n, precision) }
static gm(w, n) { gm(w, n, precision) }
static hm(w, n) { hm(w, n, precision) }
static fc(w, n) { fc(w, n, precision) }
static gc(w, n) { gc(w, n, precision) }
static hc(w, n) { hc(w, n, precision) }
// Private worker method which calls a 'short name' method and returns its result.
static callFn_(fn, w, v, p) {
return (fn == "d") ? d(w, v) :
(fn == "b") ? b(w, v) :
(fn == "t") ? t(w, v) :
(fn == "o") ? o(w, v) :
(fn == "x") ? x(w, v) :
(fn == "X") ? Fmt.X(w, v) :
(fn == "r") ? r(w, v) :
(fn == "c") ? c(w, v) :
(fn == "s") ? s(w, v) :
(fn == "i") ? i(w, v) :
(fn == "m") ? m(w, v) :
(fn == "a") ? a(w, v) :
(fn == "n") ? n(w, v) :
(fn == "k") ? k(w, v) :
(fn == "u") ? u(w, v) :
(fn == "q") ? q(v) :
(fn == "e") ? e(w, v, p) :
(fn == "E") ? Fmt.E(w, v, p) :
(fn == "f") ? f(w, v, p) :
(fn == "g") ? g(w, v, p) :
(fn == "h") ? h(w, v, p) :
(fn == "z") ? z(w, v, p) :
(fn == "dz") ? dz(w, v) :
(fn == "bz") ? bz(w, v) :
(fn == "tz") ? tz(w, v) :
(fn == "oz") ? oz(w, v) :
(fn == "xz") ? xz(w, v) :
(fn == "Xz") ? Fmt.Xz(w, v) :
(fn == "sz") ? sz(w, v) :
(fn == "iz") ? iz(w, v) :
(fn == "fz") ? fz(w, v, p) :
(fn == "gz") ? gz(w, v, p) :
(fn == "hz") ? hz(w, v, p) :
(fn == "fp") ? fp(w, v, p) :
(fn == "gp") ? gp(w, v, p) :
(fn == "hp") ? hp(w, v, p) :
(fn == "fm") ? fm(w, v, p) :
(fn == "gm") ? gm(w, v, p) :
(fn == "hm") ? hm(w, v, p) :
(fn == "dp") ? dp(w, v) :
(fn == "dm") ? dm(w, v) :
(fn == "dc") ? dc(w, v) :
(fn == "rc") ? rc(w, v) :
(fn == "sc") ? sc(w, v) :
(fn == "ic") ? ic(w, v) :
(fn == "fc") ? fc(w, v, p) :
(fn == "gc") ? gc(w, v, p) :
(fn == "hc") ? hc(w, v, 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 'e', 'E', 'f', 'g', 'h', 'z', 'fz', 'gz', 'hz', 'fp', 'gp', 'hp',
// 'fm', 'gm', 'hm', 'fc', 'gc' or 'hc' 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, "")
var i = 0
for (e in seq) {
l[i] = q(callFn_(fn, w, e, p), cc)
i = i + 1
}
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, sep) { v(fn, w, seq, p, sep, "[]", "") }
static v(fn, w, seq, p) { v(fn, w, seq, p, ", ", "[]", "") }
static v(fn, w, seq) { v(fn, w, seq, precision, ", ", "[]", "") }
// Applies a 'short' formatting method to each element of a two-dimensional
// list or sequence 'm'.
// A Matrix or CMatrix object is automatically converted to a 2D list of numbers.
// The parameters: 'fn', 'w', 'p', 'sep', 'bb' and 'cc'
// are applied using the 'v' method to each row of 'm'.
// The rows are then joined together using the separator 'ss'.
static v2(fn, w, m, p, sep, bb, cc, ss) {
var s = m.type.toString
if (s == "Matrix" || s == "CMatrix") m = m.toList
var nr = m.count
if (nr == 0) return ""
var l = List.filled(nr, "")
var i = 0
for (row in m) {
l[i] = v(fn, w, row, p, sep, bb, cc)
i = i + 1
}
return l.join(ss)
}
// Convenience versions of the above method which use default values
// for some parameters.
static v2(fn, w, m, p, sep, bb, cc) { v(fn, w, m, p, sep, bb, cc, "\n") }
static v2(fn, w, m, p, sep, bb) { v(fn, w, m, p, sep, bb, "", "\n") }
static v2(fn, w, m, p, sep) { v(fn, w, m, p, sep, "|", "", "\n") }
static v2(fn, w, m, p) { v(fn, w, m, p, " ", "|", "", "\n") }
static v2(fn, w, m) { v(fn, w, m, precision, " ", "|", "", "\n") }
// 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:
// a, b, c, d, e, E, f, g, h, i, k, m, n, o, q, r, s, t, u, x, X or z.
// If present, the flag (there can only be one) must be one of the following:
// + always prints a + or - sign ('dp', 'fp', 'gp' or 'hp' methods)
// (space) leaves a space for the sign but only prints minus ('dm', 'fm', 'gm' or 'hm' methods)
// , commatizes the following number ('dc', 'rc', 'sc', 'ic', 'fc', 'gc' or 'hc' 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
// 0 when followed by an explicit width, pads with leading zeros rather than spaces:
// ('dz', 'bz', 'tz', 'oz', 'xz, 'Xz', 'sz', iz', 'fz', 'gz' and 'hz' methods)
// 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 one is passed.
// If present, the precision is the number of decimal places to be passed to the appropriate
// 'e', 'E', 'f', 'g', 'h' or 'z' 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. Finally, to print a literal
// dollar symbol use $$.
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 { |minusAllowed|
i = i + 1
if (i == le) Fiber.abort("Invalid format string.")
cp = cps[i]
var ns = ""
if (cp == 45) {
if (!minusAllowed) Fiber.abort("Invalid format string")
ns = "-"
i = i + 1
if (i == le) Fiber.abort("Invalid format string.")
cp = cps[i]
}
while (cp >= 48 && cp <= 57) {
ns = ns + Conv.itoc(cp)
i = i + 1
if (i == le) Fiber.abort("Invalid format string.")
cp = cps[i]
}
if (ns == "-") Fiber.abort("Invalid format string.")
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(true)
if (ns != "" && "*+,#".codePoints.contains(cp)) {
Fiber.abort("Invalid format string.")
}
var plus = false
var comma = false
var space = false
var hash = false
var fn = ""
var ds = ""
if ("abcdeEfghikmnoqrstuxXz".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(false)
} else if (cp == 43) { // plus sign
plus = true
ns = getNumber.call(true)
if (cp == 46) ds = getNumber.call(false)
} else if (cp == 44) { // comma
comma = true
ns = getNumber.call(true)
if (cp == 46) ds = getNumber.call(false)
} else if (cp == 46) { // dot
ds = getNumber.call(false)
} else if (cp == 32) { // space
space = true
ns = getNumber.call(true)
if (cp == 46) ds = getNumber.call(false)
} else if (cp == 35) { // hash
hash = true
ns = getNumber.call(true)
if (cp == 46) ds = getNumber.call(false)
} else {
Fiber.abort("Unrecognized character in format string.")
}
if (fn == "") {
if (!"abcdeEfghikmnoqrstuxXz".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" || fn == "h") && plus) {
fn = fn + "p"
} else if ((fn == "f" || fn == "g" || fn == "h") && space) {
fn = fn + "m"
} else if ((fn == "r" || fn == "s" || fn == "i" || fn == "f" ||
fn == "g" || fn == "h") && comma) {
fn = fn + "c"
}
if (ns == "") ns = "1"
if (ns[0] == "0" && ns.count > 1 && "dbtoxXsifgh".contains(fn[0])) {
fn = fn[0] + "z"
}
var w = Num.fromString(ns)
var p = (ds != "") ? Num.fromString(ds) : precision
if (next < a.count) {
var e = a[next]
if ((e is Sequence) && !(e is String) && fn != "n" && fn != "u") {
if (hash && "btodxX".contains(fn[0])) {
var rr = []
for (ee in e) {
var r = callFn_(fn, w, ee, p)
if (r[0] == "-") {
r = "-" + Conv.prefixes[fn[0]] + r[1..-1]
} else {
r = Conv.prefixes[fn[0]] + r
}
rr.add(r)
}
s = s + rr.join(" ")
} else {
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 6 arguments
// to be passed individually rather than in a list.
static swrite(fmt, a1, a2, a3, a4, a5, a6) { slwrite(fmt, [a1, a2, a3, a4, a5, a6]) }
static swrite(fmt, a1, a2, a3, a4, a5) { slwrite(fmt, [a1, a2, a3, a4, a5]) }
static swrite(fmt, a1, a2, a3, a4) { slwrite(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, a6) { System.write(slwrite(fmt, [a1, a2, a3, a4, a5, a6])) }
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, a6) { System.print(slwrite(fmt, [a1, a2, a3, a4, a5, a6])) }
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)) }
// Prints (with a following \n) a sequence 'a' to stdout in tabular form
// with a maximum of 'rowSize' elements per row. 'fmt' is applied individually
// to each element and formatted elements are separated by a single space.
static tprint(fmt, a, rowSize) {
if (!(fmt is String)) Fiber.abort("First argument must be a string.")
if (!(a is Sequence)) Fiber.abort("Second argument must be a sequence.")
if (!((rowSize is Num) && rowSize.isInteger && rowSize > 0)) {
Fiber.abort("Third argument must be a positive integer.")
}
fmt = fmt + " "
var count = 0
for (e in a) {
Fmt.write(fmt, e)
count = count + 1
if (count % rowSize == 0) System.print()
}
if (count % rowSize != 0) System.print()
}
// Prints (with a following \n) a sequence 'a' to stdout in columnar form
// with a maximum of 'colSize' elements per column. 'fmt' is applied individually
// to each element and formatted elements are separated by a single space.
static cprint(fmt, a, colSize) {
if (!(fmt is String)) Fiber.abort("First argument must be a string.")
if (!(a is Sequence)) Fiber.abort("Second argument must be a sequence.")
if (!((colSize is Num) && colSize.isInteger && colSize > 0)) {
Fiber.abort("Third argument must be a positive integer.")
}
fmt = fmt + " "
for (i in 0...colSize) {
var j = i
while (j < a.count) {
Fmt.write(fmt, a[j])
j = j + colSize
}
System.print()
}
}
// Prints (with a following \n) an array 'a' to stdout using a typical layout.
// An 'array' for this purpose is a list or sequence of objects.
// The parameters: 'w', 'p' and 'bb' are applied using the 'v' method to 'a'.
// The settings for the other parameters are:
// 'fn' = "f" for numbers, "z" for complex numbers,"s" otherwise
// ('p' is ignored for latter) 'sep' = " ", 'cc' = "".
static aprint(a, w, p, bb) {
var fn = (a.count > 0 && (a[0] is Num)) ? "f" :
(a.count > 0 && (a[0].type.toString == "Complex")) ? "z" : "s"
System.print(Fmt.v(fn, w, a, p, " ", bb, ""))
}
// Convenience versions of the above method which use default values for
// some parameters.
static aprint(a, w, p) { aprint(a, w, p, "[]") }
static aprint(a, w) { aprint(a, w, precision, "[]") }
static aprint(a) { aprint(a, 0, precision, "[]") }
// Prints (with a following \n) a matrix 'm' to stdout using a typical layout.
// A 'matrix' for this purpose is a two-dimensional list or sequence of objects.
// A Matrix or CMatrix object is automatically converted to a 2D list of numbers.
// The parameters: 'w', 'p' and 'bb' are applied using the 'v2' method to 'm'.
// The settings for the other parameters are:
// 'fn' = "f" for numbers, "z" for complex numbers, "s" otherwise
// ('p' is ignored for latter) 'sep' = " ", 'cc' = "", 'ss' = "\n".
static mprint(m, w, p, bb) {
var s = m.type.toString
if (s == "Matrix" || s == "CMatrix") m = m.toList
var fn = (m.count > 0 && m[0].count > 0 && (m[0][0] is Num)) ? "f" :
(m.count > 0 && m[0].count > 0 && (m[0][0].type.toString == "Complex")) ? "z" : "s"
System.print(Fmt.v2(fn, w, m, p, " ", bb, "", "\n"))
}
// Convenience versions of the above method which use default values for
// some parameters.
static mprint(m, w, p) { mprint(m, w, p, "|") }
static mprint(m, w) { mprint(m, w, precision, "|") }
static mprint(m) { mprint(m, 0, precision, "|") }
}