Category talk:Wren-fmt: Difference between revisions
Content deleted Content added
m →Source code: Fixed syntax highlighting. |
→Source code: Added 'jslwrite' and family methods. |
||
(23 intermediate revisions by the same user not shown) | |||
Line 1:
===Source code===
<syntaxhighlight lang=
/* Conv contains routines which do conversions between types. */
Line 107:
return "%(n)%(suffix)"
}
// Converts an integer to its unicode superscript equivalent.
static superscript(n) {
if (!(n is Num && n.isInteger)) Fiber.abort("Argument must be an integer.")
var ss = {
"0": "⁰", "1": "¹", "2": "²", "3": "³", "4": "⁴", "5": "⁵", "6": "⁶",
"7": "⁷", "8": "⁸", "9": "⁹", "-": "⁻", "+": "⁺", "e": "ᵉ"
}
return Fmt.i(0, n).map { |d| ss.containsKey(d) ? ss[d] : d }.join()
}
// Converts an integer to its unicode subscript equivalent.
static subscript(n) {
if (!(n is Num && n.isInteger)) Fiber.abort("Argument must be an integer.")
var ss = {
"0": "₀", "1": "₁", "2": "₂", "3": "₃", "4": "₄", "5": "₅", "6": "₆",
"7": "₇", "8": "₈", "9": "₉", "-": "₋", "+": "₊", "e": "ₑ"
}
return Fmt.i(0, n).map { |d| ss.containsKey(d) ? ss[d] : d }.join()
}
// Converts a numerator and denominator to their unicode fraction equivalent.
// If there is none, returns a string of the form "n/d".
static fraction(n, d) {
if (!(n is Num && n.isInteger && n > 0)) Fiber.abort("n must be a positive integer.")
if (!(d is Num && d.isInteger && d > 0)) Fiber.abort("d must be a positive integer.")
var fracs = {
"1/4": "¼", "1/2": "½", "3/4": "¾", "1/7": "⅐", "1/9": "⅑", "1/10": "⅒",
"1/3": "⅓", "2/3": "⅔", "1/5": "⅕", "2/5": "⅖", "3/5": "⅗", "4/5": "⅘",
"1/6": "⅙", "5/6": "⅚", "1/8": "⅛", "3/8": "⅜", "5/8": "⅝", "7/8": "⅞"
}
var frac = "%(n)/%(d)"
return fracs.containsKey(frac) ? fracs[frac] : frac
}
// Converts 's' to the plural form 'p' if and only if the absolute value of 'n' is not equal to 1.
static plural(n, s, p) { (n.abs != 1) ? p : s }
// Convenience version of the above method which forms the plural by adding 's' to the singular.
static plural(n, s) { (n.abs != 1) ? s + "s" : s }
// Makes a UTF-8 or byte string printable by replacing non-printable characters
// with escaped ones. If 'asBytes' is true, not just control characters
// but all bytes > 127 are regarded as non-printable.
static printable(s, asBytes) {
var res = ""
var chars = asBytes ? s.bytes : s.codePoints
var lets = "abtnvfr"
for (c in chars) {
if (c == -1) {
res = res + "\ufffd"
} else if (c == 0) {
res = res + "\\0"
} else if (c >= 7 && c <= 13) {
res = res + "\\" + lets[c - 7]
} else if (c == 27) {
res = res + "\\e"
} else if (c < 32 || c == 127 || (asBytes && c > 127)) {
res = res + "\\x" + Fmt.swrite("$02x", c)
} else if (!asBytes && c >= 128 && c < 160) {
res = res + esc + "\\u00" + Fmt.swrite("$02x", c)
} else {
res = res + String.fromCodePoint(c)
}
}
return res
}
// Returns the unicode infinity symbol.
static infinity { "∞" }
}
Line 323 ⟶ 393:
// Convenience version of the above which uses double quotes as the embedding characters.
static q(v) { "\"%(v)\"" }
// Maps the boolean value 'b' to "yes" if true or "no" if false
// and then applies the 's' format to the result.
static y(w, b) { Fmt.s(w, b ? "yes" : "no") }
// Formats a number 'n' (using 'h' format) to a maximum precision of 14 decimal places.
Line 357 ⟶ 431:
// Works like 'e' except that the exponent symbol 'e' is replaced by upper case 'E'.
static E(w, n, p) { Fmt.e(w, n, p).replace("e", "E") }
// Applies the 's' format to the name of a number 'n'.
static N(w, n) { Fmt.s(w, Name.fromNum(n)) }
// Applies the 's' format to the ordinal name of an integer 'i'.
static O(w, i) { Fmt.s(w, Name.ordinal(i)) }
// Applies the 's' format to the superior (or superscript) version of a number 'n'.
static S(w, n) { Fmt.s(w, Conv.superscript(n)) }
// Applies the 's' format to the the inferior (or subscript) version of a number 'n'.
static I(w, n) { Fmt.s(w, Conv.subscript(n)) }
// Applies the 's' format to a fraction f[0] / f[1 where 'f' is a two element list.
static F(w, f) { Fmt.s(w, Conv.fraction(f[0], f[1])) }
// Applies the 's' format to the plural of l[1] depending on l[0] and, if present, l[2] where 'l' is a
// two or three element list. Forms the plural by just adding "s" to l[1] if 'l' is a two element list.
static P(w, l) { Fmt.s(w, Conv.plural(l[0], l[1], l.count == 2 ? l[1] + "s" : l[2])) }
// Makes a byte string printable and applies the 's' format to the result.
static B(w, v) { Fmt.s(w, Conv.printable(v, true)) }
// Makes a UTF-8 string printable and applies the 's' format to the result.
static U(w, v) { Fmt.s(w, Conv.printable(v, false)) }
// Repeats 'v', a string or value, 'n' times.
static R(n, v) { (v is String) ? v * n : v.toString * n }
// Pads a number 'n' with leading spaces to a minimum width 'w' and a precision of 'p' decimal places.
Line 395 ⟶ 497:
static g(w, n, p) {
var f = f(w, n, p)
if (f.contains("e")) return f
if (f.contains(".") && (f[-1] == "0" || f[-1] == " ")) {
var l1 = f.count
Line 408 ⟶ 511:
static h(w, n, p) {
var f = f(w, n, p)
if (f.contains("e")) return f
if (f.contains(".") && (f[-1] == "0" || f[-1] == " ")) {
var l1 = f.count
Line 416 ⟶ 520:
return f
}
// Works like 'h' except, if right justified, removes any trailing spaces before rejustifying.
static j(w, n, p) { (w >= 0) ? rjust(w, h(w, n, p).trimEnd()) : h(w, n, p) }
// Works like 'j' except derives the number to be formatted from its name.
static l(w, name, p) { j(w, Name.toNum(name), p) }
// As above but pads with leading zeros instead of spaces.
Line 425 ⟶ 535:
// 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 454 ⟶ 569:
static gc(w, n, p) {
var f = fc(w, n, p)
if (f.contains("e")) return f
if (f.contains(".") && (f[-1] == "0" || f[-1] == " ")) {
var l1 = f.count
Line 467 ⟶ 583:
static hc(w, n, p) {
var f = fc(w, n, p)
if (f.contains("e")) return f
if (f.contains(".") && (f[-1] == "0" || f[-1] == " ")) {
var l1 = f.count
Line 476 ⟶ 593:
}
//
static jc(w, n, p) { (w >= 0) ? rjust(w, hc(w, n, p).trimEnd()) : hc(w, n, p) }
// Works like 'jc' except derives the number to be formatted from its name.
static lc(w, name, p) { jc(w, Name.toNum(name), p) }
// 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)
Line 484 ⟶ 607:
var sign = (n.imag >= 0) ? " + " : " - "
var imag = f(w, n.imag.abs, p)
if (w < 0) imag = imag.trimEnd(" ")
return real + sign + imag + "i"
}
// Applies the 'fp' and 'f' formats respectively to each component, x and y, of a
// complex number 'n' before joining them together in the form x ± yi.
static zp(w, n, p) {
if (n is Num) return fp(w, n, p)
if (n.type.toString != "Complex") Fiber.abort("Argument must be a complex or real number.")
var real = fp(w, n.real, p)
var sign = (n.imag >= 0) ? " + " : " - "
var imag = f(w, n.imag.abs, p)
if (w < 0) imag = imag.trimEnd(" ")
return real + sign + imag + "i"
}
// Applies the 'fm' and 'f' formats respectively to each component, x and y, of a
// complex number 'n' before joining them together in the form x ± yi.
static zm(w, n, p) {
if (n is Num) return fm(w, n, p)
if (n.type.toString != "Complex") Fiber.abort("Argument must be a complex or real number.")
var real = fm(w, n.real, p)
var sign = (n.imag >= 0) ? " + " : " - "
var imag = f(w, n.imag.abs, p)
if (w < 0) imag = imag.trimEnd(" ")
return real + sign + imag + "i"
}
Line 493 ⟶ 641:
static g(w, n) { g(w, n, precision) }
static h(w, n) { h(w, n, precision) }
static j(w, n) { j(w, n, precision) }
static l(w, n) { l(w, n, precision) }
static z(w, n) { z(w, n, precision) }
static fz(w, n) { fz(w, n, precision) }
Line 500 ⟶ 650:
static gp(w, n) { gp(w, n, precision) }
static hp(w, n) { hp(w, n, precision) }
static zp(w, n) { zp(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 zm(w, n) { zm(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) }
static jc(w, n) { jc(w, n, precision) }
static lc(w, n) { lc(w, n, precision) }
// Private worker method which calls a 'short name' method and returns its result.
static callFn_(fn, w, v, p) {
Line 522 ⟶ 679:
(fn == "u") ? u(w, v) :
(fn == "q") ? q(v) :
(fn == "y") ? y(w, v) :
(fn == "e") ? e(w, v, p) :
(fn == "E") ? Fmt.E(w, v, p) :
(fn == "N") ? Fmt.N(w, v) :
(fn == "O") ? Fmt.O(w, v) :
(fn == "S") ? Fmt.S(w, v) :
(fn == "I") ? Fmt.I(w, v) :
(fn == "F") ? Fmt.F(w, v) :
(fn == "P") ? Fmt.P(w, v) :
(fn == "B") ? Fmt.B(w, v) :
(fn == "U") ? Fmt.U(w, v) :
(fn == "R") ? Fmt.R(w, v) :
(fn == "f") ? f(w, v, p) :
(fn == "g") ? g(w, v, p) :
(fn == "h") ? h(w, v, p) :
(fn == "j") ? j(w, v, p) :
(fn == "l") ? l(w, v, p) :
(fn == "z") ? z(w, v, p) :
(fn == "dz") ? dz(w, v) :
Line 542 ⟶ 711:
(fn == "gp") ? gp(w, v, p) :
(fn == "hp") ? hp(w, v, p) :
(fn == "zp") ? zp(w, v, p) :
(fn == "fm") ? fm(w, v, p) :
(fn == "gm") ? gm(w, v, p) :
(fn == "hm") ? hm(w, v, p) :
(fn == "zm") ? zm(w, v, p) :
(fn == "dp") ? dp(w, v) :
(fn == "dm") ? dm(w, v) :
Line 550 ⟶ 724:
(fn == "fc") ? fc(w, v, p) :
(fn == "gc") ? gc(w, v, p) :
(fn == "hc") ? hc(w, v, p) :
(fn == "jc") ? jc(w, v, p) :
(fn == "lc") ? lc(w, v, p) : Fiber.abort("Method not recognized.")
}
Line 556 ⟶ 732:
// 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', 'j', 'l', 'z', 'fz', 'gz', 'hz', 'fp', 'gp'
// 'hp', 'zp', 'fm', 'gm', 'hm', 'zm', 'fc', 'gc', 'hc', 'jc' or '
// 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 578 ⟶ 754:
static v(fn, w, seq, p) { v(fn, w, seq, p, ", ", "[]", "") }
static v(fn, w, seq) { v(fn, w, seq, precision, ", ", "[]", "") }
// As the 'v' method but abridges 'seq' to a maximum number
// of elements 'n' (non-overlapping) at either end or, if 'n' is negative,
// from the front only, using 'aa' as the separator. Doesn't abridge 'seq'
// unless at least one element would need to be suppressed.
static va(fn, w, seq, p, sep, bb, cc, n, aa) {
if (!(n is Num && n.isInteger && n.abs >= 1)) Fiber.abort("'n' must be a non-zero integer.")
if (!(aa is String)) Fiber.abort("'aa' must be a string.")
var c = seq.count
if (c <= ((n < 0) ? -n : 2*n)) return Fmt.v(fn, w, seq, p, sep, bb, cc)
var left = (seq is List) ? seq[0...n.abs] : seq.take(n.abs)
var sleft = Fmt.v(fn, w, left, p, sep, "", cc) + sep
var sright = ""
if (n > 0) {
var right = (seq is List) ? seq[-n..-1] : seq.skip(c - n)
sright = sep + Fmt.v(fn, w, right, p, sep, "", cc)
}
var res = sleft + aa + sright
if (bb != "") res = Fmt.q(res, bb)
return res
}
// Applies a 'short' formatting method to each element of a two-dimensional
Line 612 ⟶ 809:
// $[flag][width][.precision][letter] of which all bracketed items except [letter] are optional.
// The letter must be one of the 'short' methods:
// a, b, B, c, d, e, E, f, F, g, h, i, I, j, k, l, m, n, N, o, O, P, q, r, R, s, S, t, u, U, x, X, y 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', 'hp' or '
// (space) leaves a space for the sign but only prints minus ('dm',
// , commatizes the following number ('dc', 'rc', 'sc', 'ic', 'fc', 'gc, 'hc', 'jc' or '
// # 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
Line 624 ⟶ 821:
// 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', 'j', 'l' 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
Line 682 ⟶ 879:
var fn = ""
var ds = ""
if ("
fn = Conv.itoc(cp)
} else if (cp == 42) { // star
Line 717 ⟶ 914:
if (fn == "") {
if (!"
Fiber.abort("Unrecognized character in format string.")
}
Line 730 ⟶ 927:
fn = "dc"
}
} else if ((fn == "f" || fn == "g" || fn == "h" || fn == "z") && plus) {
fn = fn + "p"
} else if ((fn == "f" || fn == "g" || fn == "h" || fn == "z") && space) {
fn = fn + "m"
} else if ((fn == "r" || fn == "s" || fn == "i" || fn == "f" ||
fn == "g" || fn == "h" || fn == "j" || fn == "l") && comma) {
fn = fn + "c"
}
Line 744 ⟶ 943:
if (next < a.count) {
var e = a[next]
if ((e is Sequence) && !(e is String) &&
if (hash && "btodxX".contains(fn[0])) {
var rr = []
Line 781 ⟶ 980:
}
// 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)) }
// Synomyms of corresponding methods in System class - useful for aligning code.
static write(object) { System.write(object) }
static writeAll(sequence) { System.writeAll(sequence) }
static print() { System.print() }
static print(object) { System.print(object) }
static printAll(sequence) { System.printAll(sequence) }
// Gets or sets the separator for the 'jslwrite' method. The default is a single space.
static separator { (__separator != null) ? __separator : " " }
static separator=(s) { __separator = (s is String) ? s : s.toString }
// Returns a string formed from joining together the string representation of
// the elements of the list or sequence 'a' using the current separator.
static jslwrite(a) { a.join(separator) }
// Convenience versions of the 'jslwrite' method which allow from 2 to 6 arguments
// to be passed individually rather than in a list or sequence
static jswrite(a1, a2, a3, a4, a5, a6) { jsl.write([a1, a2, a3, a4, a5, a6]) }
static jswrite(a1, a2, a3, a4, a5) { jsl.write([a1, a2, a3, a4, a5]) }
static jswrite(a1, a2, a3, a4) { jsl.write([a1, a2, a3, a4]) }
static jswrite(a1, a2, a3) { jsl.write([a1, a2, a3]) }
static jswrite(a1, a2) { jsl.write([a1, a2]) }
// Applies jslwrite to the arguments and then 'writes' it (no following \n) to stdout.
static jwrite(a1, a2, a3, a4, a5, a6) { System.write(jslwrite([a1, a2, a3, a4, a5, a6])) }
static jwrite(a1, a2, a3, a4, a5) { System.write(jslwrite([a1, a2, a3, a4, a5])) }
static jwrite(a1, a2, a3, a4) { System.write(jslwrite([a1, a2, a3, a4])) }
static jwrite(a1, a2, a3) { System.write(jslwrite([a1, a2, a3])) }
static jwrite(a1, a2) { System.write(jslwrite([a1, a2])) }
static jlwrite(a) { System.write(jslwrite(a)) }
// Applies jslwrite to the arguments and then 'prints' it (with a following \n) to stdout.
static jprint(a1, a2, a3, a4, a5, a6) { System.print(jslwrite([a1, a2, a3, a4, a5, a6])) }
static jprint(a1, a2, a3, a4, a5) { System.print(jslwrite([a1, a2, a3, a4, a5])) }
static jprint(a1, a2, a3, a4) { System.print(jslwrite([a1, a2, a3, a4])) }
static jprint(a1, a2, a3) { System.print(jslwrite([a1, a2, a3])) }
static jprint(a1, a2) { System.print(jslwrite([a1, a2])) }
static jlprint(a) { System.print(jslwrite(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
static tprint(fmt, a, rowSize, sep) {
if (!(fmt is String)) Fiber.abort("First argument must be a string.")
if (!(a is Sequence)) Fiber.abort("Second argument must be a sequence.")
Line 814 ⟶ 1,055:
Fiber.abort("Third argument must be a positive integer.")
}
var count = 0
for (e in a) {
Fmt.write(fmt, e)
count = count + 1
if (count % rowSize == 0
System.print()
} else System.write(sep)
}
}
// 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 'sep'.
static cprint(fmt, a, colSize, sep) {
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.")
}
if (!(a is List)) a = a.toList
var ac = a.count
for (i in 0...colSize) {
var j = i
while (true) {
Fmt.write(fmt, a[j])
j = j + colSize
if (j >= ac) break
System.write(sep)
}
System.print()
}
}
// Convenience versions of the above methods which use a single space for the separator.
static tprint(fmt, a, rowSize) { tprint(fmt, a, rowSize, " ") }
static cprint(fmt, a, colSize) { cprint(fmt, a, colSize, " ") }
// Prints (with a following \n) an array 'a' to stdout using a typical layout.
Line 829 ⟶ 1,098:
// The settings for the other parameters are:
// 'fn' = "f" for numbers, "z" for complex numbers,"s" otherwise
// ('p' is ignored for latter) 'sep' = " ", 'cc' = "".
// The 'rns' parameter, if true, leaves a space for the sign of a real number but only prints minus.
static aprint(a, w, p, bb, rns) {
if (!(a is Sequence)) Fiber.abort("Second argument must be a sequence.")
if (!(a is List)) a = a.toList
var fn = (a.count > 0 && (a[0] is Num)) ? "f" :
(a.count > 0 && (a[0].type.toString == "Complex")) ? "z" : "s"
if (rns && fn != "s") fn = fn + "m"
System.print(Fmt.v(fn, w, a, p, " ", bb, ""))
}
Line 838 ⟶ 1,111:
// Convenience versions of the above method which use default values for
// some parameters.
static aprint(a, w, p, bb) { aprint(a, w, p,
static aprint(a, w, p) { aprint(a, w,
static aprint(a, w) { aprint(a,
static aprint(a) { aprint(a, 0, precision, "[]", false) }
// Prints (with a following \n) a matrix 'm' to stdout using a typical layout.
Line 849 ⟶ 1,123:
// 'fn' = "f" for numbers, "z" for complex numbers, "s" otherwise
// ('p' is ignored for latter) 'sep' = " ", 'cc' = "", 'ss' = "\n".
// The rns' parameter, if true, leaves a space for the sign of a real number but only prints minus.
static mprint(m, w, p, bb, rns) {
if (
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"
if (rns && fn != "s") fn = fn + "m"
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, bb) { mprint(m, w, p,
static mprint(m, w, p) { mprint(m, w,
static mprint(m, w) { mprint(m,
static mprint(m) { mprint(m, 0, precision, "|", false) }
// Formats a polynomial as a string.
// Polynomials are represented by an ordered list of coefficients
// from the term with the highest degree down to the constant term.
// Unless there is only one term, terms with zero coefficents are suppressed.
// Unless it's the constant term, the '1' in coefficients of exactly ± 1 is also suppressed.
// 'fmt' is applied to each coefficient, 'symbol' is the exponentiation
// symbol (e.g. "^") and 'variable' (e.g. "x") is the variable name.
// If symbol = "", unicode superscript characters are used for the degree.
static spwrite(fmt, coefs, symbol, variable) {
if (!(fmt is String)) Fiber.abort("First argument must be a string.")
if (!(coefs is List) || coefs.count == 0 || !(coefs[0] is Num)) {
Fiber.abort("Second argument must be a non-empty ordered list of numbers.")
}
if (!(symbol is String)) Fiber.abort("Third argument must be a string.")
if (!(variable is String)) Fiber.abort("Fourth argument must be a string.")
var degree = coefs.count - 1
if (degree == 0 || coefs.all { |c| c == 0 }) return Fmt.swrite(fmt, coefs[0])
var p = ""
for (i in 0..degree) {
var coef = coefs[i]
var pow = degree - i
if (coef == 0) continue
if (coef > 0) {
var t = Fmt.swrite(fmt, coef)
if (pow > 0 && coef == 1 && t[-1] == "1") t = t[0...-1]
p = p + " + " + t
} else {
var t = Fmt.swrite(fmt, -coef)
if (pow > 0 && coef == -1 && t[-1] == "1") t = t[0...-1]
p = p + " - " + t
}
if (pow > 1) {
if (symbol != "") {
p = p + Fmt.swrite("$s$s$d", variable, symbol, pow)
} else {
p = p + Fmt.swrite("$s$S", variable, pow)
}
} else if (pow == 1) {
p = p + variable
}
}
p = p.startsWith(" + ") ? p[3..-1] : "-" + p[3..-1]
return p
}
// Prints a polynomial without (pwrite) or with (pprint) a following \n to stdout.
static pwrite(fmt, coefs, symbol, variable) {
System.write(spwrite(fmt, coefs, symbol, variable))
}
static pprint(fmt, coefs, symbol, variable) {
System.print(spwrite(fmt, coefs, symbol, variable))
}
}
/* Name contains infrastructure and routines for converting numbers to their English names. */
class Name {
// Private method to initialize static fields.
static init_() {
__uk = false
__neg = "minus"
__point = "point"
__zero = "zero"
__small = [
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven",
"twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"
]
__tens = ["", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"]
__illions = [
"", " thousand", " million", " billion"," trillion", " quadrillion", " quintillion",
" sextillion", " septillion", " octillion", " nonillion", " decillion"
]
__irregularOrdinals = {
"nought": "noughth",
"one": "first",
"two": "second",
"three": "third",
"five": "fifth",
"eight": "eighth",
"nine": "ninth",
"twelve": "twelfth"
}
__revIrregulars = {}
for (me in __irregularOrdinals) __revIrregulars[me.value] = me.key
__names = {}
for (i in 0..19) __names[__small[i]] = i
for (i in 2..9) __names[__tens[i]] = i * 10
__names["hundred"] = 100
for (i in 1..11) __names[__illions[i][1..-1]] = 10.pow(i * 3)
__names["nought"] = 0
__zeros = ["zero", "nought", "nil", "none", "nothing"]
__points = ["point", "dot", "spot"]
}
// Private helper function. Converts ASCII string to lower case.
static lower_(s) { s.bytes.map { |b|
return String.fromByte((b >= 65 && b <= 90) ? b + 32 : b)
}.join() }
// Gets or sets whether names are to expressed in UK English i.e. 'and' is used to connect
// double or single digit numbers with larger numbers. The default is 'false'.
static uk { __uk }
static uk=(b) { __uk = (b is Bool) ? b : __uk}
// Gets or sets the prefix word for negative numbers. The default is "minus" though another
// possibility is "negative".
static negative { __neg }
static negative=(n) { __neg = (n is String) ? n : __neg }
// Gets or sets the word for the decimal point in non-integral numbers. The default is "point"
// though other possibilities are "dot" or "spot".
static point { __point }
static point=(p) { __point = (p is String) ? p : __point }
// Gets or sets the word for the number '0'. The default is "zero" though another
// possibility is 'nought'.
static zero { __zero }
static zero=(z) { __zero = (z is String) ? __small[0] = z : __zero }
// Returns the name of a number which can be positive or negative and can include a decimal point.
// Note that this is unsafe for numbers with an absolute value >= 2^53 but may work in some cases.
static fromNum(n) {
if (!(n is Num)) Fiber.abort("'n' must be a number.")
if (n.isInfinity || n.isNan) return Fmt.s(0, n)
var f = 0
if (!n.isInteger) {
f = n.fraction
n = n.truncate
}
var and = __uk ? "and " : ""
var t = ""
if (n < 0) {
t = __neg + " "
n = -n
}
if (n < 20) {
t = t + __small[n]
} else if (n < 100) {
t = t + __tens[(n/10).floor]
var s = n % 10
if (s > 0) t = t + "-" + __small[s]
} else if (n < 1000) {
t = t + __small[(n/100).floor] + " hundred"
var s = n % 100
if (s > 0) t = t + " " + and + fromNum(s)
} else {
var sx = ""
var i = 0
while (n > 0) {
var p = n % 1000
n = (n/1000).floor
if (p > 0) {
var ix = fromNum(p) + __illions[i]
if (sx != "") ix = ix + " " + sx
sx = ix
}
i = i + 1
}
t = t + sx
}
if (f > 0) {
t = t + " " + __point
for (d in f.toString.skip(2)) {
t = t + " %(__small[Num.fromString(d)])"
}
}
return t
}
// Returns the ordinal name of an integer (including negative integers).
// Note that this is unsafe for integers with an absolute value >= 2^53 but may work in some cases.
static ordinal(n) {
if (!(n is Num && n.isInteger)) Fiber.abort("'n' must be an integer.")
var s = fromNum(n)
var r = s[-1..0]
var i1 = r.indexOf(" ")
if (i1 != -1) i1 = s.count - 1 - i1
var i2 = r.indexOf("-")
if (i2 != -1) i2 = s.count - 1 - i2
var i = (i1 > i2) ? i1 : i2
i = i + 1
var x = __irregularOrdinals[s[i..-1]]
if (x) {
return s[0...i] + x
} else if (s[-1] == "y") {
return s[0...i] + s[i..-2] + "ieth"
} else {
return s[0...i] + s[i..-1] + "th"
}
}
// Translates the name of a number or ordinal integer into that number and returns it.
// Ignores case and custom settings but recognizes all suggested alternatives and some others.
static toNum(name) {
if (!(name is String) || name == "") Fiber.abort("'name' must be a non-empty string.")
name = lower_(name).replace(",", " ").replace("-", " ").replace(" and", "")
var words = name.split(" ").where { |w| w != "" }.toList
var isNegative = words[0] == "minus" || words[0] == "negative"
if (isNegative || words[0] == "plus") words = words[1..-1]
if (words[0] == "a") words[0] = "one"
if (words[-1].endsWith("ieth")) {
words[-1] = words[-1][0..-5] + "y"
} else if(__revIrregulars.containsKey(words[-1])) {
words[-1] = __revIrregulars[words[-1]]
} else if (words[-1].endsWith("th")) {
words[-1] = words[-1][0..-3]
}
var size = words.count
if (size == 1 && __zeros.contains(words[0])) return 0
if (size == 1 && words[0] == "nan") return Num.nan
if (size == 1 && words[0] == "infinity") return isNegative ? -Num.infinity : Num.infinity
var ix = -1
for (p in __points) {
if ((ix = words.indexOf(p)) >= 0) break
}
if (ix == 0) {
words.insert(0, "zero")
ix = 1
}
var dec = ""
if (ix > 0) {
for (word in words[ix+1..-1]) dec = dec + __names[word].toString
size = ix
}
var multiplier = 1
var lastNum = -1
var sum = 0
for (i in size-1..0) {
var num = __names[words[i]]
if (!num) Fiber.abort("'%(words[i])' is not a valid cardinal number.")
if (num == lastNum) Fiber.abort("'%(name)' is not a well formed numeric string.")
if (num >= 1000) {
if (lastNum >= 100) {
Fiber.abort("'%(name)' is not a well formed numeric string.")
}
multiplier = num
if (i == 0) sum = sum + multiplier
} else if (num >= 100) {
multiplier = multiplier * 100
if (i == 0) sum = sum + multiplier
} else if (num >= 20) {
if (lastNum >= 10 && lastNum <= 90) {
Fiber.abort("'%(name)' is not a well formed numeric string.")
}
sum = sum + num*multiplier
} else {
if (lastNum >= 1 && lastNum <= 90) {
Fiber.abort("'%(name)' is not a well formed numeric string.")
}
sum = sum + num*multiplier
}
lastNum = num
}
if (dec != "") sum = sum + Num.fromString("0." + dec)
return (isNegative) ? -sum : sum
}
}
Name.init_()</syntaxhighlight>
|
Latest revision as of 10:10, 15 April 2024
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)"
}
// Converts an integer to its unicode superscript equivalent.
static superscript(n) {
if (!(n is Num && n.isInteger)) Fiber.abort("Argument must be an integer.")
var ss = {
"0": "⁰", "1": "¹", "2": "²", "3": "³", "4": "⁴", "5": "⁵", "6": "⁶",
"7": "⁷", "8": "⁸", "9": "⁹", "-": "⁻", "+": "⁺", "e": "ᵉ"
}
return Fmt.i(0, n).map { |d| ss.containsKey(d) ? ss[d] : d }.join()
}
// Converts an integer to its unicode subscript equivalent.
static subscript(n) {
if (!(n is Num && n.isInteger)) Fiber.abort("Argument must be an integer.")
var ss = {
"0": "₀", "1": "₁", "2": "₂", "3": "₃", "4": "₄", "5": "₅", "6": "₆",
"7": "₇", "8": "₈", "9": "₉", "-": "₋", "+": "₊", "e": "ₑ"
}
return Fmt.i(0, n).map { |d| ss.containsKey(d) ? ss[d] : d }.join()
}
// Converts a numerator and denominator to their unicode fraction equivalent.
// If there is none, returns a string of the form "n/d".
static fraction(n, d) {
if (!(n is Num && n.isInteger && n > 0)) Fiber.abort("n must be a positive integer.")
if (!(d is Num && d.isInteger && d > 0)) Fiber.abort("d must be a positive integer.")
var fracs = {
"1/4": "¼", "1/2": "½", "3/4": "¾", "1/7": "⅐", "1/9": "⅑", "1/10": "⅒",
"1/3": "⅓", "2/3": "⅔", "1/5": "⅕", "2/5": "⅖", "3/5": "⅗", "4/5": "⅘",
"1/6": "⅙", "5/6": "⅚", "1/8": "⅛", "3/8": "⅜", "5/8": "⅝", "7/8": "⅞"
}
var frac = "%(n)/%(d)"
return fracs.containsKey(frac) ? fracs[frac] : frac
}
// Converts 's' to the plural form 'p' if and only if the absolute value of 'n' is not equal to 1.
static plural(n, s, p) { (n.abs != 1) ? p : s }
// Convenience version of the above method which forms the plural by adding 's' to the singular.
static plural(n, s) { (n.abs != 1) ? s + "s" : s }
// Makes a UTF-8 or byte string printable by replacing non-printable characters
// with escaped ones. If 'asBytes' is true, not just control characters
// but all bytes > 127 are regarded as non-printable.
static printable(s, asBytes) {
var res = ""
var chars = asBytes ? s.bytes : s.codePoints
var lets = "abtnvfr"
for (c in chars) {
if (c == -1) {
res = res + "\ufffd"
} else if (c == 0) {
res = res + "\\0"
} else if (c >= 7 && c <= 13) {
res = res + "\\" + lets[c - 7]
} else if (c == 27) {
res = res + "\\e"
} else if (c < 32 || c == 127 || (asBytes && c > 127)) {
res = res + "\\x" + Fmt.swrite("$02x", c)
} else if (!asBytes && c >= 128 && c < 160) {
res = res + esc + "\\u00" + Fmt.swrite("$02x", c)
} else {
res = res + String.fromCodePoint(c)
}
}
return res
}
// Returns the unicode infinity symbol.
static infinity { "∞" }
}
/* 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)\"" }
// Maps the boolean value 'b' to "yes" if true or "no" if false
// and then applies the 's' format to the result.
static y(w, b) { Fmt.s(w, b ? "yes" : "no") }
// 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) { Fmt.e(w, n, p).replace("e", "E") }
// Applies the 's' format to the name of a number 'n'.
static N(w, n) { Fmt.s(w, Name.fromNum(n)) }
// Applies the 's' format to the ordinal name of an integer 'i'.
static O(w, i) { Fmt.s(w, Name.ordinal(i)) }
// Applies the 's' format to the superior (or superscript) version of a number 'n'.
static S(w, n) { Fmt.s(w, Conv.superscript(n)) }
// Applies the 's' format to the the inferior (or subscript) version of a number 'n'.
static I(w, n) { Fmt.s(w, Conv.subscript(n)) }
// Applies the 's' format to a fraction f[0] / f[1 where 'f' is a two element list.
static F(w, f) { Fmt.s(w, Conv.fraction(f[0], f[1])) }
// Applies the 's' format to the plural of l[1] depending on l[0] and, if present, l[2] where 'l' is a
// two or three element list. Forms the plural by just adding "s" to l[1] if 'l' is a two element list.
static P(w, l) { Fmt.s(w, Conv.plural(l[0], l[1], l.count == 2 ? l[1] + "s" : l[2])) }
// Makes a byte string printable and applies the 's' format to the result.
static B(w, v) { Fmt.s(w, Conv.printable(v, true)) }
// Makes a UTF-8 string printable and applies the 's' format to the result.
static U(w, v) { Fmt.s(w, Conv.printable(v, false)) }
// Repeats 'v', a string or value, 'n' times.
static R(n, v) { (v is String) ? v * n : v.toString * n }
// 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("e")) return f
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("e")) return f
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
}
// Works like 'h' except, if right justified, removes any trailing spaces before rejustifying.
static j(w, n, p) { (w >= 0) ? rjust(w, h(w, n, p).trimEnd()) : h(w, n, p) }
// Works like 'j' except derives the number to be formatted from its name.
static l(w, name, p) { j(w, Name.toNum(name), 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 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("e")) return f
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("e")) return f
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
}
// Works like 'hc' except, if right justified, removes any trailing spaces before rejustifying.
static jc(w, n, p) { (w >= 0) ? rjust(w, hc(w, n, p).trimEnd()) : hc(w, n, p) }
// Works like 'jc' except derives the number to be formatted from its name.
static lc(w, name, p) { jc(w, Name.toNum(name), p) }
// 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)
if (w < 0) imag = imag.trimEnd(" ")
return real + sign + imag + "i"
}
// Applies the 'fp' and 'f' formats respectively to each component, x and y, of a
// complex number 'n' before joining them together in the form x ± yi.
static zp(w, n, p) {
if (n is Num) return fp(w, n, p)
if (n.type.toString != "Complex") Fiber.abort("Argument must be a complex or real number.")
var real = fp(w, n.real, p)
var sign = (n.imag >= 0) ? " + " : " - "
var imag = f(w, n.imag.abs, p)
if (w < 0) imag = imag.trimEnd(" ")
return real + sign + imag + "i"
}
// Applies the 'fm' and 'f' formats respectively to each component, x and y, of a
// complex number 'n' before joining them together in the form x ± yi.
static zm(w, n, p) {
if (n is Num) return fm(w, n, p)
if (n.type.toString != "Complex") Fiber.abort("Argument must be a complex or real number.")
var real = fm(w, n.real, p)
var sign = (n.imag >= 0) ? " + " : " - "
var imag = f(w, n.imag.abs, p)
if (w < 0) imag = imag.trimEnd(" ")
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 j(w, n) { j(w, n, precision) }
static l(w, n) { l(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 zp(w, n) { zp(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 zm(w, n) { zm(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) }
static jc(w, n) { jc(w, n, precision) }
static lc(w, n) { lc(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 == "y") ? y(w, v) :
(fn == "e") ? e(w, v, p) :
(fn == "E") ? Fmt.E(w, v, p) :
(fn == "N") ? Fmt.N(w, v) :
(fn == "O") ? Fmt.O(w, v) :
(fn == "S") ? Fmt.S(w, v) :
(fn == "I") ? Fmt.I(w, v) :
(fn == "F") ? Fmt.F(w, v) :
(fn == "P") ? Fmt.P(w, v) :
(fn == "B") ? Fmt.B(w, v) :
(fn == "U") ? Fmt.U(w, v) :
(fn == "R") ? Fmt.R(w, v) :
(fn == "f") ? f(w, v, p) :
(fn == "g") ? g(w, v, p) :
(fn == "h") ? h(w, v, p) :
(fn == "j") ? j(w, v, p) :
(fn == "l") ? l(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 == "zp") ? zp(w, v, p) :
(fn == "fm") ? fm(w, v, p) :
(fn == "gm") ? gm(w, v, p) :
(fn == "hm") ? hm(w, v, p) :
(fn == "zm") ? zm(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) :
(fn == "jc") ? jc(w, v, p) :
(fn == "lc") ? lc(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', 'j', 'l', 'z', 'fz', 'gz', 'hz', 'fp', 'gp',
// 'hp', 'zp', 'fm', 'gm', 'hm', 'zm', 'fc', 'gc', 'hc', 'jc' or 'lc' 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, ", ", "[]", "") }
// As the 'v' method but abridges 'seq' to a maximum number
// of elements 'n' (non-overlapping) at either end or, if 'n' is negative,
// from the front only, using 'aa' as the separator. Doesn't abridge 'seq'
// unless at least one element would need to be suppressed.
static va(fn, w, seq, p, sep, bb, cc, n, aa) {
if (!(n is Num && n.isInteger && n.abs >= 1)) Fiber.abort("'n' must be a non-zero integer.")
if (!(aa is String)) Fiber.abort("'aa' must be a string.")
var c = seq.count
if (c <= ((n < 0) ? -n : 2*n)) return Fmt.v(fn, w, seq, p, sep, bb, cc)
var left = (seq is List) ? seq[0...n.abs] : seq.take(n.abs)
var sleft = Fmt.v(fn, w, left, p, sep, "", cc) + sep
var sright = ""
if (n > 0) {
var right = (seq is List) ? seq[-n..-1] : seq.skip(c - n)
sright = sep + Fmt.v(fn, w, right, p, sep, "", cc)
}
var res = sleft + aa + sright
if (bb != "") res = Fmt.q(res, bb)
return res
}
// 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, B, c, d, e, E, f, F, g, h, i, I, j, k, l, m, n, N, o, O, P, q, r, R, s, S, t, u, U, x, X, y 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', 'hp' or 'zp' methods)
// (space) leaves a space for the sign but only prints minus ('dm', 'fm', 'gm', 'hm' or 'zm' methods)
// , commatizes the following number ('dc', 'rc', 'sc', 'ic', 'fc', 'gc, 'hc', 'jc' or 'lc' 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', 'j', 'l' 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 ("abBcdeEfFghiIjklmnNoOPqrRsStuUxXyz".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 (!"abBcdeEfFghiIjklmnNoOPqrRsStuUxXyz".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" || fn == "z") && plus) {
fn = fn + "p"
} else if ((fn == "f" || fn == "g" || fn == "h" || fn == "z") && space) {
fn = fn + "m"
} else if ((fn == "r" || fn == "s" || fn == "i" || fn == "f" ||
fn == "g" || fn == "h" || fn == "j" || fn == "l") && 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) && !"nuFPR".contains(fn)) {
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)) }
// Synomyms of corresponding methods in System class - useful for aligning code.
static write(object) { System.write(object) }
static writeAll(sequence) { System.writeAll(sequence) }
static print() { System.print() }
static print(object) { System.print(object) }
static printAll(sequence) { System.printAll(sequence) }
// Gets or sets the separator for the 'jslwrite' method. The default is a single space.
static separator { (__separator != null) ? __separator : " " }
static separator=(s) { __separator = (s is String) ? s : s.toString }
// Returns a string formed from joining together the string representation of
// the elements of the list or sequence 'a' using the current separator.
static jslwrite(a) { a.join(separator) }
// Convenience versions of the 'jslwrite' method which allow from 2 to 6 arguments
// to be passed individually rather than in a list or sequence
static jswrite(a1, a2, a3, a4, a5, a6) { jsl.write([a1, a2, a3, a4, a5, a6]) }
static jswrite(a1, a2, a3, a4, a5) { jsl.write([a1, a2, a3, a4, a5]) }
static jswrite(a1, a2, a3, a4) { jsl.write([a1, a2, a3, a4]) }
static jswrite(a1, a2, a3) { jsl.write([a1, a2, a3]) }
static jswrite(a1, a2) { jsl.write([a1, a2]) }
// Applies jslwrite to the arguments and then 'writes' it (no following \n) to stdout.
static jwrite(a1, a2, a3, a4, a5, a6) { System.write(jslwrite([a1, a2, a3, a4, a5, a6])) }
static jwrite(a1, a2, a3, a4, a5) { System.write(jslwrite([a1, a2, a3, a4, a5])) }
static jwrite(a1, a2, a3, a4) { System.write(jslwrite([a1, a2, a3, a4])) }
static jwrite(a1, a2, a3) { System.write(jslwrite([a1, a2, a3])) }
static jwrite(a1, a2) { System.write(jslwrite([a1, a2])) }
static jlwrite(a) { System.write(jslwrite(a)) }
// Applies jslwrite to the arguments and then 'prints' it (with a following \n) to stdout.
static jprint(a1, a2, a3, a4, a5, a6) { System.print(jslwrite([a1, a2, a3, a4, a5, a6])) }
static jprint(a1, a2, a3, a4, a5) { System.print(jslwrite([a1, a2, a3, a4, a5])) }
static jprint(a1, a2, a3, a4) { System.print(jslwrite([a1, a2, a3, a4])) }
static jprint(a1, a2, a3) { System.print(jslwrite([a1, a2, a3])) }
static jprint(a1, a2) { System.print(jslwrite([a1, a2])) }
static jlprint(a) { System.print(jslwrite(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 'sep'.
static tprint(fmt, a, rowSize, sep) {
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.")
}
var ac = a.count
var count = 0
for (e in a) {
Fmt.write(fmt, e)
count = count + 1
if (count % rowSize == 0 || count == ac) {
System.print()
} else System.write(sep)
}
}
// 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 'sep'.
static cprint(fmt, a, colSize, sep) {
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.")
}
if (!(a is List)) a = a.toList
var ac = a.count
for (i in 0...colSize) {
var j = i
while (true) {
Fmt.write(fmt, a[j])
j = j + colSize
if (j >= ac) break
System.write(sep)
}
System.print()
}
}
// Convenience versions of the above methods which use a single space for the separator.
static tprint(fmt, a, rowSize) { tprint(fmt, a, rowSize, " ") }
static cprint(fmt, a, colSize) { cprint(fmt, a, colSize, " ") }
// 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' = "".
// The 'rns' parameter, if true, leaves a space for the sign of a real number but only prints minus.
static aprint(a, w, p, bb, rns) {
if (!(a is Sequence)) Fiber.abort("Second argument must be a sequence.")
if (!(a is List)) a = a.toList
var fn = (a.count > 0 && (a[0] is Num)) ? "f" :
(a.count > 0 && (a[0].type.toString == "Complex")) ? "z" : "s"
if (rns && fn != "s") fn = fn + "m"
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, bb) { aprint(a, w, p, bb, false) }
static aprint(a, w, p) { aprint(a, w, p, "[]", false) }
static aprint(a, w) { aprint(a, w, precision, "[]", false) }
static aprint(a) { aprint(a, 0, precision, "[]", false) }
// 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".
// The rns' parameter, if true, leaves a space for the sign of a real number but only prints minus.
static mprint(m, w, p, bb, rns) {
if (!(m is List)) 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"
if (rns && fn != "s") fn = fn + "m"
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, bb) { mprint(m, w, p, bb, false) }
static mprint(m, w, p) { mprint(m, w, p, "|", false) }
static mprint(m, w) { mprint(m, w, precision, "|", false) }
static mprint(m) { mprint(m, 0, precision, "|", false) }
// Formats a polynomial as a string.
// Polynomials are represented by an ordered list of coefficients
// from the term with the highest degree down to the constant term.
// Unless there is only one term, terms with zero coefficents are suppressed.
// Unless it's the constant term, the '1' in coefficients of exactly ± 1 is also suppressed.
// 'fmt' is applied to each coefficient, 'symbol' is the exponentiation
// symbol (e.g. "^") and 'variable' (e.g. "x") is the variable name.
// If symbol = "", unicode superscript characters are used for the degree.
static spwrite(fmt, coefs, symbol, variable) {
if (!(fmt is String)) Fiber.abort("First argument must be a string.")
if (!(coefs is List) || coefs.count == 0 || !(coefs[0] is Num)) {
Fiber.abort("Second argument must be a non-empty ordered list of numbers.")
}
if (!(symbol is String)) Fiber.abort("Third argument must be a string.")
if (!(variable is String)) Fiber.abort("Fourth argument must be a string.")
var degree = coefs.count - 1
if (degree == 0 || coefs.all { |c| c == 0 }) return Fmt.swrite(fmt, coefs[0])
var p = ""
for (i in 0..degree) {
var coef = coefs[i]
var pow = degree - i
if (coef == 0) continue
if (coef > 0) {
var t = Fmt.swrite(fmt, coef)
if (pow > 0 && coef == 1 && t[-1] == "1") t = t[0...-1]
p = p + " + " + t
} else {
var t = Fmt.swrite(fmt, -coef)
if (pow > 0 && coef == -1 && t[-1] == "1") t = t[0...-1]
p = p + " - " + t
}
if (pow > 1) {
if (symbol != "") {
p = p + Fmt.swrite("$s$s$d", variable, symbol, pow)
} else {
p = p + Fmt.swrite("$s$S", variable, pow)
}
} else if (pow == 1) {
p = p + variable
}
}
p = p.startsWith(" + ") ? p[3..-1] : "-" + p[3..-1]
return p
}
// Prints a polynomial without (pwrite) or with (pprint) a following \n to stdout.
static pwrite(fmt, coefs, symbol, variable) {
System.write(spwrite(fmt, coefs, symbol, variable))
}
static pprint(fmt, coefs, symbol, variable) {
System.print(spwrite(fmt, coefs, symbol, variable))
}
}
/* Name contains infrastructure and routines for converting numbers to their English names. */
class Name {
// Private method to initialize static fields.
static init_() {
__uk = false
__neg = "minus"
__point = "point"
__zero = "zero"
__small = [
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven",
"twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"
]
__tens = ["", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"]
__illions = [
"", " thousand", " million", " billion"," trillion", " quadrillion", " quintillion",
" sextillion", " septillion", " octillion", " nonillion", " decillion"
]
__irregularOrdinals = {
"nought": "noughth",
"one": "first",
"two": "second",
"three": "third",
"five": "fifth",
"eight": "eighth",
"nine": "ninth",
"twelve": "twelfth"
}
__revIrregulars = {}
for (me in __irregularOrdinals) __revIrregulars[me.value] = me.key
__names = {}
for (i in 0..19) __names[__small[i]] = i
for (i in 2..9) __names[__tens[i]] = i * 10
__names["hundred"] = 100
for (i in 1..11) __names[__illions[i][1..-1]] = 10.pow(i * 3)
__names["nought"] = 0
__zeros = ["zero", "nought", "nil", "none", "nothing"]
__points = ["point", "dot", "spot"]
}
// Private helper function. Converts ASCII string to lower case.
static lower_(s) { s.bytes.map { |b|
return String.fromByte((b >= 65 && b <= 90) ? b + 32 : b)
}.join() }
// Gets or sets whether names are to expressed in UK English i.e. 'and' is used to connect
// double or single digit numbers with larger numbers. The default is 'false'.
static uk { __uk }
static uk=(b) { __uk = (b is Bool) ? b : __uk}
// Gets or sets the prefix word for negative numbers. The default is "minus" though another
// possibility is "negative".
static negative { __neg }
static negative=(n) { __neg = (n is String) ? n : __neg }
// Gets or sets the word for the decimal point in non-integral numbers. The default is "point"
// though other possibilities are "dot" or "spot".
static point { __point }
static point=(p) { __point = (p is String) ? p : __point }
// Gets or sets the word for the number '0'. The default is "zero" though another
// possibility is 'nought'.
static zero { __zero }
static zero=(z) { __zero = (z is String) ? __small[0] = z : __zero }
// Returns the name of a number which can be positive or negative and can include a decimal point.
// Note that this is unsafe for numbers with an absolute value >= 2^53 but may work in some cases.
static fromNum(n) {
if (!(n is Num)) Fiber.abort("'n' must be a number.")
if (n.isInfinity || n.isNan) return Fmt.s(0, n)
var f = 0
if (!n.isInteger) {
f = n.fraction
n = n.truncate
}
var and = __uk ? "and " : ""
var t = ""
if (n < 0) {
t = __neg + " "
n = -n
}
if (n < 20) {
t = t + __small[n]
} else if (n < 100) {
t = t + __tens[(n/10).floor]
var s = n % 10
if (s > 0) t = t + "-" + __small[s]
} else if (n < 1000) {
t = t + __small[(n/100).floor] + " hundred"
var s = n % 100
if (s > 0) t = t + " " + and + fromNum(s)
} else {
var sx = ""
var i = 0
while (n > 0) {
var p = n % 1000
n = (n/1000).floor
if (p > 0) {
var ix = fromNum(p) + __illions[i]
if (sx != "") ix = ix + " " + sx
sx = ix
}
i = i + 1
}
t = t + sx
}
if (f > 0) {
t = t + " " + __point
for (d in f.toString.skip(2)) {
t = t + " %(__small[Num.fromString(d)])"
}
}
return t
}
// Returns the ordinal name of an integer (including negative integers).
// Note that this is unsafe for integers with an absolute value >= 2^53 but may work in some cases.
static ordinal(n) {
if (!(n is Num && n.isInteger)) Fiber.abort("'n' must be an integer.")
var s = fromNum(n)
var r = s[-1..0]
var i1 = r.indexOf(" ")
if (i1 != -1) i1 = s.count - 1 - i1
var i2 = r.indexOf("-")
if (i2 != -1) i2 = s.count - 1 - i2
var i = (i1 > i2) ? i1 : i2
i = i + 1
var x = __irregularOrdinals[s[i..-1]]
if (x) {
return s[0...i] + x
} else if (s[-1] == "y") {
return s[0...i] + s[i..-2] + "ieth"
} else {
return s[0...i] + s[i..-1] + "th"
}
}
// Translates the name of a number or ordinal integer into that number and returns it.
// Ignores case and custom settings but recognizes all suggested alternatives and some others.
static toNum(name) {
if (!(name is String) || name == "") Fiber.abort("'name' must be a non-empty string.")
name = lower_(name).replace(",", " ").replace("-", " ").replace(" and", "")
var words = name.split(" ").where { |w| w != "" }.toList
var isNegative = words[0] == "minus" || words[0] == "negative"
if (isNegative || words[0] == "plus") words = words[1..-1]
if (words[0] == "a") words[0] = "one"
if (words[-1].endsWith("ieth")) {
words[-1] = words[-1][0..-5] + "y"
} else if(__revIrregulars.containsKey(words[-1])) {
words[-1] = __revIrregulars[words[-1]]
} else if (words[-1].endsWith("th")) {
words[-1] = words[-1][0..-3]
}
var size = words.count
if (size == 1 && __zeros.contains(words[0])) return 0
if (size == 1 && words[0] == "nan") return Num.nan
if (size == 1 && words[0] == "infinity") return isNegative ? -Num.infinity : Num.infinity
var ix = -1
for (p in __points) {
if ((ix = words.indexOf(p)) >= 0) break
}
if (ix == 0) {
words.insert(0, "zero")
ix = 1
}
var dec = ""
if (ix > 0) {
for (word in words[ix+1..-1]) dec = dec + __names[word].toString
size = ix
}
var multiplier = 1
var lastNum = -1
var sum = 0
for (i in size-1..0) {
var num = __names[words[i]]
if (!num) Fiber.abort("'%(words[i])' is not a valid cardinal number.")
if (num == lastNum) Fiber.abort("'%(name)' is not a well formed numeric string.")
if (num >= 1000) {
if (lastNum >= 100) {
Fiber.abort("'%(name)' is not a well formed numeric string.")
}
multiplier = num
if (i == 0) sum = sum + multiplier
} else if (num >= 100) {
multiplier = multiplier * 100
if (i == 0) sum = sum + multiplier
} else if (num >= 20) {
if (lastNum >= 10 && lastNum <= 90) {
Fiber.abort("'%(name)' is not a well formed numeric string.")
}
sum = sum + num*multiplier
} else {
if (lastNum >= 1 && lastNum <= 90) {
Fiber.abort("'%(name)' is not a well formed numeric string.")
}
sum = sum + num*multiplier
}
lastNum = num
}
if (dec != "") sum = sum + Num.fromString("0." + dec)
return (isNegative) ? -sum : sum
}
}
Name.init_()