1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-05 13:59:01 -05:00

Add fmt modules (printf implementation) (denoland/deno_std#566)

Original: f7b511611c
This commit is contained in:
a2800276 2019-08-15 17:57:24 +02:00 committed by Ryan Dahl
parent 917b202354
commit 16e134d8a8
4 changed files with 1571 additions and 0 deletions

212
fmt/README.md Normal file
View file

@ -0,0 +1,212 @@
# Printf for Deno
This is very much a work-in-progress. I'm actively soliciting feedback.
What immediately follows are points for discussion.
If you are looking for the documentation proper, skip to:
"printf: prints formatted output"
below.
## Discussion
This is very much a work-in-progress. I'm actively soliciting feedback.
- What useful features are available in other languages apart from
Golang and C?
- behaviour of `%v` verb. In Golang, this is a shortcut verb to "print the
default format" of the argument. It is currently implemented to format
using `toString` in the default case and `inpect` if the `%#v`
alternative format flag is used in the format directive. Alternativly,
`%V` could be used to distinguish the two.
`inspect` output is not defined, however. This may be problematic if using
this code on other plattforms (and expecting interoperability). To my
knowledge, no suitable specification of object representation aside from JSON
and `toString` exist. ( Aside: see "[Common object formats][3]" in the
"Console Living Standard" which basically says "do whatever" )
- `%j` verb. This is an extension particular to this implementation. Currently
not very sophisticated, it just runs `JSON.stringify` on the argument.
Consider possible modifier flags, etc.
- `<` verb. This is an extension that assumes the argument is an array and will
format each element according to the format (surrounded by [] and seperated
by comma) (`<` Mnemonic: pull each element out of array)
- how to deal with more newfangled Javascript features ( generic Iterables,
Map and Set types, typed Arrays, ...)
- the implementation is fairly rough around the edges:
- currently contains little in the way of checking for
correctness. Conceivably, there will be a 'strict' form, e.g.
that ensures only Number-ish arguments are passed to %f flags
- assembles output using string concatenation instead of
utilizing buffers or other optimizations. It would be nice to
have printf / sprintf / fprintf (etc) all in one.
- float formatting is handled by toString() and to `toExponential`
along with a mess of Regexp. Would be nice to use fancy match
- some flags that are potentially applicable ( POSIX long and unsigned
modifiers are not likely useful) are missing, namely %q (print quoted), %U
(unicode format)
## Author
Tim Becker (tim@presseverykey.com)
## License
MIT
The implementation is inspired by POSIX and Golang (see above) but does
not port implementation code. A number of Golang test-cases based on:
https://golang.org/src/fmt/fmt_test.go
( BSD: Copyright (c) 2009 The Go Authors. All rights reserved. )
were used.
# printf: prints formatted output
sprintf converts and formats a variable number of arguments as is
specified by a `format string`. In it's basic form, a format string
may just be a literal. In case arguments are meant to be formatted,
a `directive` is contained in the format string, preceded by a '%' character:
%<verb>
E.g. the verb `s` indicates the directive should be replaced by the
string representation of the argument in the corresponding position of
the argument list. E.g.:
Hello %s!
applied to the arguments "World" yields "Hello World!"
The meaning of the format string is modelled after [POSIX][1] format
strings as well as well as [Golang format strings][2]. Both contain
elements specific to the respective programming language that don't
apply to JavaScript, so they can not be fully supported. Furthermore we
implement some functionality that is specific to JS.
## Verbs
The following verbs are supported:
| Verb | Meaning |
| ----- | -------------------------------------------------------------- |
| `%` | print a literal percent |
| `t` | evaluate arg as boolean, print `true` or `false` |
| `b` | eval as number, print binary |
| `c` | eval as number, print character corresponding to the codePoint |
| `o` | eval as number, print octal |
| `x X` | print as hex (ff FF), treat string as list of bytes |
| `e E` | print number in scientific/exponent format 1.123123e+01 |
| `f F` | print number as float with decimal point and no exponent |
| `g G` | use %e %E or %f %F depending on size of argument |
| `s` | interpolate string |
| `T` | type of arg, as returned by `typeof` |
| `v` | value of argument in 'default' format (see below) |
| `j` | argument as formatted by `JSON.stringify` |
## Width and Precision
Verbs may be modified by providing them with width and precision, either or
both may be omitted:
%9f width 9, default precision
%.9f default width, precision 9
%8.9f width 8, precision 9
%8.f width 9, precision 0
In general, 'width' describes the minimum length of the output, while 'precision'
limits the output.
| verb | precision |
| --------- | -------------------------------------------------------------- |
| `t` | n/a |
| `b c o` | n/a |
| `x X` | n/a for number, strings are truncated to p bytes(!) |
| `e E f F` | number of places after decimal, default 6 |
| `g G` | set maximum number of digits |
| `s` | truncate input |
| `T` | truncate |
| `v` | tuncate, or depth if used with # see "'default' format", below |
| `j` | n/a |
Numerical values for width and precision can be substituted for the `*` char, in
which case the values are obtained from the next args, e.g.:
sprintf ("%*.*f", 9,8,456.0)
is equivalent to
sprintf ("%9.9f", 456.0)
## Flags
The effects of the verb may be further influenced by using flags to modify the
directive:
| Flag | Verb | Meaning |
| ----- | --------- | -------------------------------------------------------------------------- |
| `+` | numeric | always print sign |
| `-` | all | pad to the right (left justify) |
| `#` | | alternate format |
| `#` | `b o x X` | prefix with `0b 0 0x` |
| `#` | `g G` | don't remove trailing zeros |
| `#` | `v` | ues output of `inspect` instead of `toString` |
| `' '` | | space character |
| `' '` | `x X` | leave spaces between bytes when printing string |
| `' '` | `d` | insert space for missing `+` sign character |
| `0` | all | pad with zero, `-` takes precedence, sign is appended in front of padding |
| `<` | all | format elements of the passed array according to the directive (extension) |
## 'default' format
The default format used by `%v` is the result of calling `toString()` on the
relevant argument. If the `#` flags is used, the result of calling `inspect()`
is interpolated. In this case, the precision, if set is passed to `inspect()` as
the 'depth' config parameter
## Positional arguments
Arguments do not need to be consumed in the order they are provded and may
be consumed more than once. E.g.:
sprintf("%[2]s %[1]s", "World", "Hello")
returns "Hello World". The precence of a positional indicator resets the arg counter
allowing args to be reused:
sprintf("dec[%d]=%d hex[%[1]d]=%x oct[%[1]d]=%#o %s", 1, 255, "Third")
returns `dec[1]=255 hex[1]=0xff oct[1]=0377 Third`
Width and precision my also use positionals:
"%[2]*.[1]*d", 1, 2
This follows the golang conventions and not POSIX.
## Errors
The following errors are handled:
Incorrect verb:
S("%h", "") %!(BAD VERB 'h')
Too few arguments:
S("%d") %!(MISSING 'd')"
[1]: https://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html
[2]: https://golang.org/pkg/fmt/
[3]: https://console.spec.whatwg.org/#object-formats

12
fmt/TODO Normal file
View file

@ -0,0 +1,12 @@
* "native" formatting, json, arrays, object/structs, functions ...
* %q %U
* Java has a %n flag to print the plattform native newline... in POSIX
that means "number of chars printed so far", though.
* use of Writer and Buffer internally in order to make FPrintf, Printf, etc.
easier and more elegant.
* see "Discussion" in README
*scanf , pack,unpack, annotated hex
* error handling, consistantly
* probably rewrite, now that I konw how it's done.

677
fmt/sprintf.ts Normal file
View file

@ -0,0 +1,677 @@
enum State {
PASSTHROUGH,
PERCENT,
POSITIONAL,
PRECISION,
WIDTH
}
enum WorP {
WIDTH,
PRECISION
}
class Flags {
plus?: boolean;
dash?: boolean;
sharp?: boolean;
space?: boolean;
zero?: boolean;
lessthan?: boolean;
width: number = -1;
precision: number = -1;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const min = Math.min;
const UNICODE_REPLACEMENT_CHARACTER = "\ufffd";
const DEFAULT_PRECISION = 6;
const FLOAT_REGEXP = /(-?)(\d)\.?(\d*)e([+-])(\d+)/;
enum F {
sign = 1,
mantissa,
fractional,
esign,
exponent
}
class Printf {
format: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args: any[];
i: number;
state: State = State.PASSTHROUGH;
verb: string = "";
buf: string = "";
argNum: number = 0;
flags: Flags = new Flags();
haveSeen: boolean[];
// barf, store precision and width errors for later processing ...
tmpError?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(format: string, ...args: any[]) {
this.format = format;
this.args = args;
this.haveSeen = new Array(args.length);
this.i = 0;
}
doPrintf(): string {
for (; this.i < this.format.length; ++this.i) {
let c = this.format[this.i];
switch (this.state) {
case State.PASSTHROUGH:
if (c === "%") {
this.state = State.PERCENT;
} else {
this.buf += c;
}
break;
case State.PERCENT:
if (c === "%") {
this.buf += c;
this.state = State.PASSTHROUGH;
} else {
this.handleFormat();
}
break;
default:
throw Error("Should be unreachable, certainly a bug in the lib.");
}
}
// check for unhandled args
let extras = false;
let err = "%!(EXTRA";
for (let i = 0; i !== this.haveSeen.length; ++i) {
if (!this.haveSeen[i]) {
extras = true;
err += ` '${Deno.inspect(this.args[i])}'`;
}
}
err += ")";
if (extras) {
this.buf += err;
}
return this.buf;
}
// %[<positional>]<flag>...<verb>
handleFormat(): void {
this.flags = new Flags();
let flags = this.flags;
for (; this.i < this.format.length; ++this.i) {
let c = this.format[this.i];
switch (this.state) {
case State.PERCENT:
switch (c) {
case "[":
this.handlePositional();
this.state = State.POSITIONAL;
break;
case "+":
flags.plus = true;
break;
case "<":
flags.lessthan = true;
break;
case "-":
flags.dash = true;
flags.zero = false; // only left pad zeros, dash takes precedence
break;
case "#":
flags.sharp = true;
break;
case " ":
flags.space = true;
break;
case "0":
// only left pad zeros, dash takes precedence
flags.zero = !flags.dash;
break;
default:
if (("1" <= c && c <= "9") || c === "." || c === "*") {
if (c === ".") {
this.flags.precision = 0;
this.state = State.PRECISION;
this.i++;
} else {
this.state = State.WIDTH;
}
this.handleWidthAndPrecision(flags);
} else {
this.handleVerb();
return; // always end in verb
}
} // switch c
break;
case State.POSITIONAL: // either a verb or * only verb for now, TODO
if (c === "*") {
let worp =
this.flags.precision === -1 ? WorP.WIDTH : WorP.PRECISION;
this.handleWidthOrPrecisionRef(worp);
this.state = State.PERCENT;
break;
} else {
this.handleVerb();
return; // always end in verb
}
default:
throw new Error(`Should not be here ${this.state}, library bug!`);
} // switch state
}
}
handleWidthOrPrecisionRef(wOrP: WorP): void {
if (this.argNum >= this.args.length) {
// handle Positional should have already taken care of it...
return;
}
let arg = this.args[this.argNum];
this.haveSeen[this.argNum] = true;
if (typeof arg === "number") {
switch (wOrP) {
case WorP.WIDTH:
this.flags.width = arg;
break;
default:
this.flags.precision = arg;
}
} else {
let tmp = wOrP === WorP.WIDTH ? "WIDTH" : "PREC";
this.tmpError = `%!(BAD ${tmp} '${this.args[this.argNum]}')`;
}
this.argNum++;
}
handleWidthAndPrecision(flags: Flags): void {
const fmt = this.format;
for (; this.i !== this.format.length; ++this.i) {
const c = fmt[this.i];
switch (this.state) {
case State.WIDTH:
switch (c) {
case ".":
// initialize precision, %9.f -> precision=0
this.flags.precision = 0;
this.state = State.PRECISION;
break;
case "*":
this.handleWidthOrPrecisionRef(WorP.WIDTH);
// force . or flag at this point
break;
default:
const val = parseInt(c);
// most likely parseInt does something stupid that makes
// it unusuable for this scenario ...
// if we encounter a non (number|*|.) we're done with prec & wid
if (isNaN(val)) {
this.i--;
this.state = State.PERCENT;
return;
}
flags.width = flags.width == -1 ? 0 : flags.width;
flags.width *= 10;
flags.width += val;
} // switch c
break;
case State.PRECISION:
if (c === "*") {
this.handleWidthOrPrecisionRef(WorP.PRECISION);
break;
}
const val = parseInt(c);
if (isNaN(val)) {
// one too far, rewind
this.i--;
this.state = State.PERCENT;
return;
}
flags.precision *= 10;
flags.precision += val;
break;
default:
throw new Error("can't be here. bug.");
} // switch state
}
}
handlePositional(): void {
if (this.format[this.i] !== "[") {
// sanity only
throw new Error("Can't happen? Bug.");
}
let positional = 0;
const format = this.format;
this.i++;
let err = false;
for (; this.i !== this.format.length; ++this.i) {
if (format[this.i] === "]") {
break;
}
positional *= 10;
const val = parseInt(format[this.i]);
if (isNaN(val)) {
//throw new Error(
// `invalid character in positional: ${format}[${format[this.i]}]`
//);
this.tmpError = "%!(BAD INDEX)";
err = true;
}
positional += val;
}
if (positional - 1 >= this.args.length) {
this.tmpError = "%!(BAD INDEX)";
err = true;
}
this.argNum = err ? this.argNum : positional - 1;
return;
}
handleLessThan(): string {
let arg = this.args[this.argNum];
if ((arg || {}).constructor.name !== "Array") {
throw new Error(`arg ${arg} is not an array. Todo better error handling`);
}
let str = "[ ";
for (let i = 0; i !== arg.length; ++i) {
if (i !== 0) str += ", ";
str += this._handleVerb(arg[i]);
}
return str + " ]";
}
handleVerb(): void {
const verb = this.format[this.i];
this.verb = verb;
if (this.tmpError) {
this.buf += this.tmpError;
this.tmpError = undefined;
if (this.argNum < this.haveSeen.length) {
this.haveSeen[this.argNum] = true; // keep track of used args
}
} else if (this.args.length <= this.argNum) {
this.buf += `%!(MISSING '${verb}')`;
} else {
let arg = this.args[this.argNum]; // check out of range
this.haveSeen[this.argNum] = true; // keep track of used args
if (this.flags.lessthan) {
this.buf += this.handleLessThan();
} else {
this.buf += this._handleVerb(arg);
}
}
this.argNum++; // if there is a further positional, it will reset.
this.state = State.PASSTHROUGH;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_handleVerb(arg: any): string {
switch (this.verb) {
case "t":
return this.pad(arg.toString());
break;
case "b":
return this.fmtNumber(arg as number, 2);
break;
case "c":
return this.fmtNumberCodePoint(arg as number);
break;
case "d":
return this.fmtNumber(arg as number, 10);
break;
case "o":
return this.fmtNumber(arg as number, 8);
break;
case "x":
return this.fmtHex(arg);
break;
case "X":
return this.fmtHex(arg, true);
break;
case "e":
return this.fmtFloatE(arg as number);
break;
case "E":
return this.fmtFloatE(arg as number, true);
break;
case "f":
case "F":
return this.fmtFloatF(arg as number);
break;
case "g":
return this.fmtFloatG(arg as number);
break;
case "G":
return this.fmtFloatG(arg as number, true);
break;
case "s":
return this.fmtString(arg as string);
break;
case "T":
return this.fmtString(typeof arg);
break;
case "v":
return this.fmtV(arg);
break;
case "j":
return this.fmtJ(arg);
break;
default:
return `%!(BAD VERB '${this.verb}')`;
}
}
pad(s: string): string {
const padding = this.flags.zero ? "0" : " ";
while (s.length < this.flags.width) {
if (this.flags.dash) {
s += padding;
} else {
s = padding + s;
}
}
return s;
}
padNum(nStr: string, neg: boolean): string {
let sign: string;
if (neg) {
sign = "-";
} else if (this.flags.plus || this.flags.space) {
sign = this.flags.plus ? "+" : " ";
} else {
sign = "";
}
const zero = this.flags.zero;
if (!zero) {
// sign comes in front of padding when padding w/ zero,
// in from of value if padding with spaces.
nStr = sign + nStr;
}
const pad = zero ? "0" : " ";
const len = zero ? this.flags.width - sign.length : this.flags.width;
while (nStr.length < len) {
if (this.flags.dash) {
nStr += pad; // left justify - right pad
} else {
nStr = pad + nStr; // right just - left pad
}
}
if (zero) {
// see above
nStr = sign + nStr;
}
return nStr;
}
fmtNumber(n: number, radix: number, upcase: boolean = false): string {
let num = Math.abs(n).toString(radix);
const prec = this.flags.precision;
if (prec !== -1) {
this.flags.zero = false;
num = n === 0 && prec === 0 ? "" : num;
while (num.length < prec) {
num = "0" + num;
}
}
let prefix = "";
if (this.flags.sharp) {
switch (radix) {
case 2:
prefix += "0b";
break;
case 8:
// don't annotate octal 0 with 0...
prefix += num.startsWith("0") ? "" : "0";
break;
case 16:
prefix += "0x";
break;
default:
throw new Error("cannot handle base: " + radix);
}
}
// don't add prefix in front of value truncated by precision=0, val=0
num = num.length === 0 ? num : prefix + num;
if (upcase) {
num = num.toUpperCase();
}
return this.padNum(num, n < 0);
}
fmtNumberCodePoint(n: number): string {
let s = "";
try {
s = String.fromCodePoint(n);
} catch (RangeError) {
s = UNICODE_REPLACEMENT_CHARACTER;
}
return this.pad(s);
}
fmtFloatSpecial(n: number): string {
// formatting of NaN and Inf are pants-on-head
// stupid and more or less arbitrary.
if (isNaN(n)) {
this.flags.zero = false;
return this.padNum("NaN", false);
}
if (n === Number.POSITIVE_INFINITY) {
this.flags.zero = false;
this.flags.plus = true;
return this.padNum("Inf", false);
}
if (n === Number.NEGATIVE_INFINITY) {
this.flags.zero = false;
return this.padNum("Inf", true);
}
return "";
}
roundFractionToPrecision(fractional: string, precision: number): string {
if (fractional.length > precision) {
fractional = "1" + fractional; // prepend a 1 in case of leading 0
let tmp = parseInt(fractional.substr(0, precision + 2)) / 10;
tmp = Math.round(tmp);
fractional = Math.floor(tmp).toString();
fractional = fractional.substr(1); // remove extra 1
} else {
while (fractional.length < precision) {
fractional += "0";
}
}
return fractional;
}
fmtFloatE(n: number, upcase: boolean = false): string {
const special = this.fmtFloatSpecial(n);
if (special !== "") {
return special;
}
const m = n.toExponential().match(FLOAT_REGEXP);
if (!m) {
throw Error("can't happen, bug");
}
let fractional = m[F.fractional];
const precision =
this.flags.precision !== -1 ? this.flags.precision : DEFAULT_PRECISION;
fractional = this.roundFractionToPrecision(fractional, precision);
let e = m[F.exponent];
// scientific notation output with exponent padded to minlen 2
e = e.length == 1 ? "0" + e : e;
const val = `${m[F.mantissa]}.${fractional}${upcase ? "E" : "e"}${
m[F.esign]
}${e}`;
return this.padNum(val, n < 0);
}
fmtFloatF(n: number): string {
const special = this.fmtFloatSpecial(n);
if (special !== "") {
return special;
}
// stupid helper that turns a number into a (potentially)
// VERY long string.
function expandNumber(n: number): string {
if (Number.isSafeInteger(n)) {
return n.toString() + ".";
}
const t = n.toExponential().split("e");
let m = t[0].replace(".", "");
const e = parseInt(t[1]);
if (e < 0) {
let nStr = "0.";
for (let i = 0; i !== Math.abs(e) - 1; ++i) {
nStr += "0";
}
return (nStr += m);
} else {
const splIdx = e + 1;
while (m.length < splIdx) {
m += "0";
}
return m.substr(0, splIdx) + "." + m.substr(splIdx);
}
}
// avoiding sign makes padding easier
const val = expandNumber(Math.abs(n)) as string;
const arr = val.split(".");
const dig = arr[0];
let fractional = arr[1];
const precision =
this.flags.precision !== -1 ? this.flags.precision : DEFAULT_PRECISION;
fractional = this.roundFractionToPrecision(fractional, precision);
return this.padNum(`${dig}.${fractional}`, n < 0);
}
fmtFloatG(n: number, upcase: boolean = false): string {
const special = this.fmtFloatSpecial(n);
if (special !== "") {
return special;
}
// The double argument representing a floating-point number shall be
// converted in the style f or e (or in the style F or E in
// the case of a G conversion specifier), depending on the
// value converted and the precision. Let P equal the
// precision if non-zero, 6 if the precision is omitted, or 1
// if the precision is zero. Then, if a conversion with style E would
// have an exponent of X:
// - If P > X>=-4, the conversion shall be with style f (or F )
// and precision P -( X+1).
// - Otherwise, the conversion shall be with style e (or E )
// and precision P -1.
// Finally, unless the '#' flag is used, any trailing zeros shall be
// removed from the fractional portion of the result and the
// decimal-point character shall be removed if there is no
// fractional portion remaining.
// A double argument representing an infinity or NaN shall be
// converted in the style of an f or F conversion specifier.
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fprintf.html
let P =
this.flags.precision !== -1 ? this.flags.precision : DEFAULT_PRECISION;
P = P === 0 ? 1 : P;
const m = n.toExponential().match(FLOAT_REGEXP);
if (!m) {
throw Error("can't happen");
}
let X = parseInt(m[F.exponent]) * (m[F.esign] === "-" ? -1 : 1);
let nStr = "";
if (P > X && X >= -4) {
this.flags.precision = P - (X + 1);
nStr = this.fmtFloatF(n);
if (!this.flags.sharp) {
nStr = nStr.replace(/\.?0*$/, "");
}
} else {
this.flags.precision = P - 1;
nStr = this.fmtFloatE(n);
if (!this.flags.sharp) {
nStr = nStr.replace(/\.?0*e/, upcase ? "E" : "e");
}
}
return nStr;
}
fmtString(s: string): string {
if (this.flags.precision !== -1) {
s = s.substr(0, this.flags.precision);
}
return this.pad(s);
}
fmtHex(val: string | number, upper: boolean = false): string {
// allow others types ?
switch (typeof val) {
case "number":
return this.fmtNumber(val as number, 16, upper);
break;
case "string":
let sharp = this.flags.sharp && val.length !== 0;
let hex = sharp ? "0x" : "";
const prec = this.flags.precision;
const end = prec !== -1 ? min(prec, val.length) : val.length;
for (let i = 0; i !== end; ++i) {
if (i !== 0 && this.flags.space) {
hex += sharp ? " 0x" : " ";
}
// TODO: for now only taking into account the
// lower half of the codePoint, ie. as if a string
// is a list of 8bit values instead of UCS2 runes
let c = (val.charCodeAt(i) & 0xff).toString(16);
hex += c.length === 1 ? `0${c}` : c;
}
if (upper) {
hex = hex.toUpperCase();
}
return this.pad(hex);
break;
default:
throw new Error(
"currently only number and string are implemented for hex"
);
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fmtV(val: any): string {
if (this.flags.sharp) {
let options =
this.flags.precision !== -1 ? { depth: this.flags.precision } : {};
return this.pad(Deno.inspect(val, options));
} else {
const p = this.flags.precision;
return p === -1 ? val.toString() : val.toString().substr(0, p);
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fmtJ(val: any): string {
return JSON.stringify(val);
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function sprintf(format: string, ...args: any[]): string {
let printf = new Printf(format, ...args);
return printf.doPrintf();
}

670
fmt/sprintf_test.ts Normal file
View file

@ -0,0 +1,670 @@
import { sprintf } from "./sprintf.ts";
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
import { test, runTests } from "https://deno.land/std/testing/mod.ts";
let S = sprintf;
test(function noVerb(): void {
assertEquals(sprintf("bla"), "bla");
});
test(function percent(): void {
assertEquals(sprintf("%%"), "%");
assertEquals(sprintf("!%%!"), "!%!");
assertEquals(sprintf("!%%"), "!%");
assertEquals(sprintf("%%!"), "%!");
});
test(function testBoolean(): void {
assertEquals(sprintf("%t", true), "true");
assertEquals(sprintf("%10t", true), " true");
assertEquals(sprintf("%-10t", false), "false ");
assertEquals(sprintf("%t", false), "false");
assertEquals(sprintf("bla%t", true), "blatrue");
assertEquals(sprintf("%tbla", false), "falsebla");
});
test(function testIntegerB(): void {
assertEquals(S("%b", 4), "100");
assertEquals(S("%b", -4), "-100");
assertEquals(
S("%b", 4.1),
"100.0001100110011001100110011001100110011001100110011"
);
assertEquals(
S("%b", -4.1),
"-100.0001100110011001100110011001100110011001100110011"
);
assertEquals(
S("%b", Number.MAX_SAFE_INTEGER),
"11111111111111111111111111111111111111111111111111111"
);
assertEquals(
S("%b", Number.MIN_SAFE_INTEGER),
"-11111111111111111111111111111111111111111111111111111"
);
// width
assertEquals(S("%4b", 4), " 100");
});
test(function testIntegerC(): void {
assertEquals(S("%c", 0x31), "1");
assertEquals(S("%c%b", 0x31, 1), "11");
assertEquals(S("%c", 0x1f4a9), "💩");
//width
assertEquals(S("%4c", 0x31), " 1");
});
test(function testIntegerD(): void {
assertEquals(S("%d", 4), "4");
assertEquals(S("%d", -4), "-4");
assertEquals(S("%d", Number.MAX_SAFE_INTEGER), "9007199254740991");
assertEquals(S("%d", Number.MIN_SAFE_INTEGER), "-9007199254740991");
});
test(function testIntegerO(): void {
assertEquals(S("%o", 4), "4");
assertEquals(S("%o", -4), "-4");
assertEquals(S("%o", 9), "11");
assertEquals(S("%o", -9), "-11");
assertEquals(S("%o", Number.MAX_SAFE_INTEGER), "377777777777777777");
assertEquals(S("%o", Number.MIN_SAFE_INTEGER), "-377777777777777777");
// width
assertEquals(S("%4o", 4), " 4");
});
test(function testIntegerx(): void {
assertEquals(S("%x", 4), "4");
assertEquals(S("%x", -4), "-4");
assertEquals(S("%x", 9), "9");
assertEquals(S("%x", -9), "-9");
assertEquals(S("%x", Number.MAX_SAFE_INTEGER), "1fffffffffffff");
assertEquals(S("%x", Number.MIN_SAFE_INTEGER), "-1fffffffffffff");
// width
assertEquals(S("%4x", -4), " -4");
assertEquals(S("%-4x", -4), "-4 ");
// plus
assertEquals(S("%+4x", 4), " +4");
assertEquals(S("%-+4x", 4), "+4 ");
});
test(function testIntegerX(): void {
assertEquals(S("%X", 4), "4");
assertEquals(S("%X", -4), "-4");
assertEquals(S("%X", 9), "9");
assertEquals(S("%X", -9), "-9");
assertEquals(S("%X", Number.MAX_SAFE_INTEGER), "1FFFFFFFFFFFFF");
assertEquals(S("%X", Number.MIN_SAFE_INTEGER), "-1FFFFFFFFFFFFF");
});
test(function testFloate(): void {
assertEquals(S("%e", 4), "4.000000e+00");
assertEquals(S("%e", -4), "-4.000000e+00");
assertEquals(S("%e", 4.1), "4.100000e+00");
assertEquals(S("%e", -4.1), "-4.100000e+00");
assertEquals(S("%e", Number.MAX_SAFE_INTEGER), "9.007199e+15");
assertEquals(S("%e", Number.MIN_SAFE_INTEGER), "-9.007199e+15");
});
test(function testFloatE(): void {
assertEquals(S("%E", 4), "4.000000E+00");
assertEquals(S("%E", -4), "-4.000000E+00");
assertEquals(S("%E", 4.1), "4.100000E+00");
assertEquals(S("%E", -4.1), "-4.100000E+00");
assertEquals(S("%E", Number.MAX_SAFE_INTEGER), "9.007199E+15");
assertEquals(S("%E", Number.MIN_SAFE_INTEGER), "-9.007199E+15");
assertEquals(S("%E", Number.MIN_VALUE), "5.000000E-324");
assertEquals(S("%E", Number.MAX_VALUE), "1.797693E+308");
});
test(function testFloatfF(): void {
assertEquals(S("%f", 4), "4.000000");
assertEquals(S("%F", 4), "4.000000");
assertEquals(S("%f", -4), "-4.000000");
assertEquals(S("%F", -4), "-4.000000");
assertEquals(S("%f", 4.1), "4.100000");
assertEquals(S("%F", 4.1), "4.100000");
assertEquals(S("%f", -4.1), "-4.100000");
assertEquals(S("%F", -4.1), "-4.100000");
assertEquals(S("%f", Number.MAX_SAFE_INTEGER), "9007199254740991.000000");
assertEquals(S("%F", Number.MAX_SAFE_INTEGER), "9007199254740991.000000");
assertEquals(S("%f", Number.MIN_SAFE_INTEGER), "-9007199254740991.000000");
assertEquals(S("%F", Number.MIN_SAFE_INTEGER), "-9007199254740991.000000");
assertEquals(S("%f", Number.MIN_VALUE), "0.000000");
assertEquals(
S("%.324f", Number.MIN_VALUE),
// eslint-disable-next-line max-len
"0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005"
);
assertEquals(S("%F", Number.MIN_VALUE), "0.000000");
assertEquals(
S("%f", Number.MAX_VALUE),
// eslint-disable-next-line max-len
"179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000"
);
assertEquals(
S("%F", Number.MAX_VALUE),
// eslint-disable-next-line max-len
"179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000"
);
});
test(function testString(): void {
assertEquals(S("%s World%s", "Hello", "!"), "Hello World!");
});
test(function testHex(): void {
assertEquals(S("%x", "123"), "313233");
assertEquals(S("%x", "n"), "6e");
});
test(function testHeX(): void {
assertEquals(S("%X", "123"), "313233");
assertEquals(S("%X", "n"), "6E");
});
test(function testType(): void {
assertEquals(S("%T", new Date()), "object");
assertEquals(S("%T", 123), "number");
assertEquals(S("%T", "123"), "string");
assertEquals(S("%.3T", "123"), "str");
});
test(function testPositional(): void {
assertEquals(S("%[1]d%[2]d", 1, 2), "12");
assertEquals(S("%[2]d%[1]d", 1, 2), "21");
});
test(function testSharp(): void {
assertEquals(S("%#x", "123"), "0x313233");
assertEquals(S("%#X", "123"), "0X313233");
assertEquals(S("%#x", 123), "0x7b");
assertEquals(S("%#X", 123), "0X7B");
assertEquals(S("%#o", 123), "0173");
assertEquals(S("%#b", 4), "0b100");
});
test(function testWidthAndPrecision(): void {
assertEquals(
S("%9.99d", 9),
// eslint-disable-next-line max-len
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009"
);
assertEquals(S("%1.12d", 9), "000000000009");
assertEquals(S("%2s", "a"), " a");
assertEquals(S("%2d", 1), " 1");
assertEquals(S("%#4x", 1), " 0x1");
assertEquals(
S("%*.99d", 9, 9),
// eslint-disable-next-line max-len
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009"
);
assertEquals(
S("%9.*d", 99, 9),
// eslint-disable-next-line max-len
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009"
);
assertEquals(S("%*s", 2, "a"), " a");
assertEquals(S("%*d", 2, 1), " 1");
assertEquals(S("%#*x", 4, 1), " 0x1");
});
test(function testDash(): void {
assertEquals(S("%-2s", "a"), "a ");
assertEquals(S("%-2d", 1), "1 ");
});
test(function testPlus(): void {
assertEquals(S("%-+3d", 1), "+1 ");
assertEquals(S("%+3d", 1), " +1");
assertEquals(S("%+3d", -1), " -1");
});
test(function testSpace(): void {
assertEquals(S("% -3d", 3), " 3 ");
});
test(function testZero(): void {
assertEquals(S("%04s", "a"), "000a");
});
// relevant test cases from fmt_test.go
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const tests: Array<[string, any, string]> = [
["%d", 12345, "12345"],
["%v", 12345, "12345"],
["%t", true, "true"],
// basic string
["%s", "abc", "abc"],
// ["%q", "abc", `"abc"`], // TODO: need %q?
["%x", "abc", "616263"],
["%x", "\xff\xf0\x0f\xff", "fff00fff"],
["%X", "\xff\xf0\x0f\xff", "FFF00FFF"],
["%x", "", ""],
["% x", "", ""],
["%#x", "", ""],
["%# x", "", ""],
["%x", "xyz", "78797a"],
["%X", "xyz", "78797A"],
["% x", "xyz", "78 79 7a"],
["% X", "xyz", "78 79 7A"],
["%#x", "xyz", "0x78797a"],
["%#X", "xyz", "0X78797A"],
["%# x", "xyz", "0x78 0x79 0x7a"],
["%# X", "xyz", "0X78 0X79 0X7A"],
// basic bytes : TODO special handling for Buffer? other std types?
// escaped strings : TODO decide whether to have %q
// characters
["%c", "x".charCodeAt(0), "x"],
["%c", 0xe4, "ä"],
["%c", 0x672c, "本"],
["%c", "日".charCodeAt(0), "日"],
// Specifying precision should have no effect.
["%.0c", "⌘".charCodeAt(0), "⌘"],
["%3c", "⌘".charCodeAt(0), " ⌘"],
["%-3c", "⌘".charCodeAt(0), "⌘ "],
// Runes that are not printable.
// {"%c", '\U00000e00', "\u0e00"}, // TODO check if \U escape exists in js
//["%c", '\U0010ffff'.codePointAt(0), "\U0010ffff"],
// Runes that are not valid.
["%c", -1, "<22>"],
// TODO surrogate half, doesn't make sense in itself, how
// to determine in JS?
// ["%c", 0xDC80, "<22>"],
["%c", 0x110000, "<22>"],
["%c", 0xfffffffff, "<22>"],
// TODO
// escaped characters
// Runes that are not printable.
// Runes that are not valid.
// width
["%5s", "abc", " abc"],
["%2s", "\u263a", " ☺"],
["%-5s", "abc", "abc "],
["%05s", "abc", "00abc"],
["%5s", "abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz"],
["%.5s", "abcdefghijklmnopqrstuvwxyz", "abcde"],
["%.0s", "日本語日本語", ""],
["%.5s", "日本語日本語", "日本語日本"],
["%.10s", "日本語日本語", "日本語日本語"],
// ["%08q", "abc", `000"abc"`], // TODO verb q
// ["%-8q", "abc", `"abc" `],
//["%.5q", "abcdefghijklmnopqrstuvwxyz", `"abcde"`],
["%.5x", "abcdefghijklmnopqrstuvwxyz", "6162636465"],
//["%.3q", "日本語日本語", `"日本語"`],
//["%.1q", "日本語", `"日"`]
// change of go testcase utf-8([日]) = 0xe697a5, utf-16= 65e5 and
// our %x takes lower byte of string "%.1x", "日本語", "e6"],,
["%.1x", "日本語", "e5"],
//["%10.1q", "日本語日本語", ` "日"`],
// ["%10v", null, " <nil>"], // TODO null, undefined ...
// ["%-10v", null, "<nil> "],
// integers
["%d", 12345, "12345"],
["%d", -12345, "-12345"],
// ["%d", ^uint8(0), "255"],
//["%d", ^uint16(0), "65535"],
//["%d", ^uint32(0), "4294967295"],
//["%d", ^uint64(0), "18446744073709551615"],
["%d", -1 << 7, "-128"],
["%d", -1 << 15, "-32768"],
["%d", -1 << 31, "-2147483648"],
//["%d", (-1 << 63), "-9223372036854775808"],
["%.d", 0, ""],
["%.0d", 0, ""],
["%6.0d", 0, " "],
["%06.0d", 0, " "], // 0 flag should be ignored
["% d", 12345, " 12345"],
["%+d", 12345, "+12345"],
["%+d", -12345, "-12345"],
["%b", 7, "111"],
["%b", -6, "-110"],
// ["%b", ^uint32(0), "11111111111111111111111111111111"],
// ["%b", ^uint64(0),
// "1111111111111111111111111111111111111111111111111111111111111111"],
// ["%b", int64(-1 << 63), zeroFill("-1", 63, "")],
// 0 octal notation not allowed in struct node...
["%o", parseInt("01234", 8), "1234"],
["%#o", parseInt("01234", 8), "01234"],
// ["%o", ^uint32(0), "37777777777"],
// ["%o", ^uint64(0), "1777777777777777777777"],
["%#X", 0, "0X0"],
["%x", 0x12abcdef, "12abcdef"],
["%X", 0x12abcdef, "12ABCDEF"],
// ["%x", ^uint32(0), "ffffffff"],
// ["%X", ^uint64(0), "FFFFFFFFFFFFFFFF"],
["%.20b", 7, "00000000000000000111"],
["%10d", 12345, " 12345"],
["%10d", -12345, " -12345"],
["%+10d", 12345, " +12345"],
["%010d", 12345, "0000012345"],
["%010d", -12345, "-000012345"],
["%20.8d", 1234, " 00001234"],
["%20.8d", -1234, " -00001234"],
["%020.8d", 1234, " 00001234"],
["%020.8d", -1234, " -00001234"],
["%-20.8d", 1234, "00001234 "],
["%-20.8d", -1234, "-00001234 "],
["%-#20.8x", 0x1234abc, "0x01234abc "],
["%-#20.8X", 0x1234abc, "0X01234ABC "],
["%-#20.8o", parseInt("01234", 8), "00001234 "],
// Test correct f.intbuf overflow checks. // TODO, lazy
// unicode format // TODO, decide whether unicode verb makes sense %U
// floats
["%+.3e", 0.0, "+0.000e+00"],
["%+.3e", 1.0, "+1.000e+00"],
["%+.3f", -1.0, "-1.000"],
["%+.3F", -1.0, "-1.000"],
//["%+.3F", float32(-1.0), "-1.000"],
["%+07.2f", 1.0, "+001.00"],
["%+07.2f", -1.0, "-001.00"],
["%-07.2f", 1.0, "1.00 "],
["%-07.2f", -1.0, "-1.00 "],
["%+-07.2f", 1.0, "+1.00 "],
["%+-07.2f", -1.0, "-1.00 "],
["%-+07.2f", 1.0, "+1.00 "],
["%-+07.2f", -1.0, "-1.00 "],
["%+10.2f", +1.0, " +1.00"],
["%+10.2f", -1.0, " -1.00"],
["% .3E", -1.0, "-1.000E+00"],
["% .3e", 1.0, " 1.000e+00"],
["%+.3g", 0.0, "+0"],
["%+.3g", 1.0, "+1"],
["%+.3g", -1.0, "-1"],
["% .3g", -1.0, "-1"],
["% .3g", 1.0, " 1"],
// //["%b", float32(1.0), "8388608p-23"],
// ["%b", 1.0, "4503599627370496p-52"],
// // Test sharp flag used with floats.
["%#g", 1e-323, "1.00000e-323"],
["%#g", -1.0, "-1.00000"],
["%#g", 1.1, "1.10000"],
["%#g", 123456.0, "123456."],
//["%#g", 1234567.0, "1.234567e+06"],
// the line above is incorrect in go (according to
// my posix reading) %f-> prec = prec-1
["%#g", 1234567.0, "1.23457e+06"],
["%#g", 1230000.0, "1.23000e+06"],
["%#g", 1000000.0, "1.00000e+06"],
["%#.0f", 1.0, "1."],
["%#.0e", 1.0, "1.e+00"],
["%#.0g", 1.0, "1."],
["%#.0g", 1100000.0, "1.e+06"],
["%#.4f", 1.0, "1.0000"],
["%#.4e", 1.0, "1.0000e+00"],
["%#.4g", 1.0, "1.000"],
["%#.4g", 100000.0, "1.000e+05"],
["%#.0f", 123.0, "123."],
["%#.0e", 123.0, "1.e+02"],
["%#.0g", 123.0, "1.e+02"],
["%#.4f", 123.0, "123.0000"],
["%#.4e", 123.0, "1.2300e+02"],
["%#.4g", 123.0, "123.0"],
["%#.4g", 123000.0, "1.230e+05"],
["%#9.4g", 1.0, " 1.000"],
// The sharp flag has no effect for binary float format.
// ["%#b", 1.0, "4503599627370496p-52"], // TODO binary for floats
// Precision has no effect for binary float format.
//["%.4b", float32(1.0), "8388608p-23"], // TODO s.above
// ["%.4b", -1.0, "-4503599627370496p-52"],
// Test correct f.intbuf boundary checks.
//["%.68f", 1.0, zeroFill("1.", 68, "")], // TODO zerofill
//["%.68f", -1.0, zeroFill("-1.", 68, "")], //TODO s.a.
// float infinites and NaNs
["%f", Number.POSITIVE_INFINITY, "+Inf"],
["%.1f", Number.NEGATIVE_INFINITY, "-Inf"],
["% f", NaN, " NaN"],
["%20f", Number.POSITIVE_INFINITY, " +Inf"],
// ["% 20F", Number.POSITIVE_INFINITY, " Inf"], // TODO : wut?
["% 20e", Number.NEGATIVE_INFINITY, " -Inf"],
["%+20E", Number.NEGATIVE_INFINITY, " -Inf"],
["% +20g", Number.NEGATIVE_INFINITY, " -Inf"],
["%+-20G", Number.POSITIVE_INFINITY, "+Inf "],
["%20e", NaN, " NaN"],
["% +20E", NaN, " +NaN"],
["% -20g", NaN, " NaN "],
["%+-20G", NaN, "+NaN "],
// Zero padding does not apply to infinities and NaN.
["%+020e", Number.POSITIVE_INFINITY, " +Inf"],
["%-020f", Number.NEGATIVE_INFINITY, "-Inf "],
["%-020E", NaN, "NaN "],
// complex values // go specific
// old test/fmt_test.go
["%e", 1.0, "1.000000e+00"],
["%e", 1234.5678e3, "1.234568e+06"],
["%e", 1234.5678e-8, "1.234568e-05"],
["%e", -7.0, "-7.000000e+00"],
["%e", -1e-9, "-1.000000e-09"],
["%f", 1234.5678e3, "1234567.800000"],
["%f", 1234.5678e-8, "0.000012"],
["%f", -7.0, "-7.000000"],
["%f", -1e-9, "-0.000000"],
// ["%g", 1234.5678e3, "1.2345678e+06"],
// I believe the above test from go is incorrect according to posix, s. above.
["%g", 1234.5678e3, "1.23457e+06"],
//["%g", float32(1234.5678e3), "1.2345678e+06"],
//["%g", 1234.5678e-8, "1.2345678e-05"], // posix, see above
["%g", 1234.5678e-8, "1.23457e-05"],
["%g", -7.0, "-7"],
["%g", -1e-9, "-1e-09"],
//["%g", float32(-1e-9), "-1e-09"],
["%E", 1.0, "1.000000E+00"],
["%E", 1234.5678e3, "1.234568E+06"],
["%E", 1234.5678e-8, "1.234568E-05"],
["%E", -7.0, "-7.000000E+00"],
["%E", -1e-9, "-1.000000E-09"],
//["%G", 1234.5678e3, "1.2345678E+06"], // posix, see above
["%G", 1234.5678e3, "1.23457E+06"],
//["%G", float32(1234.5678e3), "1.2345678E+06"],
//["%G", 1234.5678e-8, "1.2345678E-05"], // posic, see above
["%G", 1234.5678e-8, "1.23457E-05"],
["%G", -7.0, "-7"],
["%G", -1e-9, "-1E-09"],
//["%G", float32(-1e-9), "-1E-09"],
["%20.5s", "qwertyuiop", " qwert"],
["%.5s", "qwertyuiop", "qwert"],
["%-20.5s", "qwertyuiop", "qwert "],
["%20c", "x".charCodeAt(0), " x"],
["%-20c", "x".charCodeAt(0), "x "],
["%20.6e", 1.2345e3, " 1.234500e+03"],
["%20.6e", 1.2345e-3, " 1.234500e-03"],
["%20e", 1.2345e3, " 1.234500e+03"],
["%20e", 1.2345e-3, " 1.234500e-03"],
["%20.8e", 1.2345e3, " 1.23450000e+03"],
["%20f", 1.23456789e3, " 1234.567890"],
["%20f", 1.23456789e-3, " 0.001235"],
["%20f", 12345678901.23456789, " 12345678901.234568"],
["%-20f", 1.23456789e3, "1234.567890 "],
["%20.8f", 1.23456789e3, " 1234.56789000"],
["%20.8f", 1.23456789e-3, " 0.00123457"],
// ["%g", 1.23456789e3, "1234.56789"],
// posix ... precision(2) = precision(def=6) - (exp(3)+1)
["%g", 1.23456789e3, "1234.57"],
// ["%g", 1.23456789e-3, "0.00123456789"], posix...
["%g", 1.23456789e-3, "0.00123457"], // see above prec6 = precdef6 - (-3+1)
//["%g", 1.23456789e20, "1.23456789e+20"],
["%g", 1.23456789e20, "1.23457e+20"],
// arrays // TODO
// slice : go specific
// TODO decide how to handle deeper types, arrays, objects
// byte arrays and slices with %b,%c,%d,%o,%U and %v
// f.space should and f.plus should not have an effect with %v.
// f.space and f.plus should have an effect with %d.
// Padding with byte slices.
// Same for strings
["%2x", "", " "], // 103
["%#2x", "", " "],
["% 02x", "", "00"],
["%# 02x", "", "00"],
["%-2x", "", " "],
["%-02x", "", " "],
["%8x", "\xab", " ab"],
["% 8x", "\xab", " ab"],
["%#8x", "\xab", " 0xab"],
["%# 8x", "\xab", " 0xab"],
["%08x", "\xab", "000000ab"],
["% 08x", "\xab", "000000ab"],
["%#08x", "\xab", "00000xab"],
["%# 08x", "\xab", "00000xab"],
["%10x", "\xab\xcd", " abcd"],
["% 10x", "\xab\xcd", " ab cd"],
["%#10x", "\xab\xcd", " 0xabcd"],
["%# 10x", "\xab\xcd", " 0xab 0xcd"],
["%010x", "\xab\xcd", "000000abcd"],
["% 010x", "\xab\xcd", "00000ab cd"],
["%#010x", "\xab\xcd", "00000xabcd"],
["%# 010x", "\xab\xcd", "00xab 0xcd"],
["%-10X", "\xab", "AB "],
["% -010X", "\xab", "AB "],
["%#-10X", "\xab\xcd", "0XABCD "],
["%# -010X", "\xab\xcd", "0XAB 0XCD "],
// renamings
// Formatter
// GoStringer
// %T TODO possibly %#T object(constructor)
["%T", {}, "object"],
["%T", 1, "number"],
["%T", "", "string"],
["%T", undefined, "undefined"],
["%T", null, "object"],
["%T", S, "function"],
["%T", true, "boolean"],
["%T", Symbol(), "symbol"],
// %p with pointers
// erroneous things
// {"", nil, "%!(EXTRA <nil>)"},
// {"", 2, "%!(EXTRA int=2)"},
// {"no args", "hello", "no args%!(EXTRA string=hello)"},
// {"%s %", "hello", "hello %!(NOVERB)"},
// {"%s %.2", "hello", "hello %!(NOVERB)"},
// {"%017091901790959340919092959340919017929593813360", 0,
// "%!(NOVERB)%!(EXTRA int=0)"},
// {"%184467440737095516170v", 0, "%!(NOVERB)%!(EXTRA int=0)"},
// // Extra argument errors should format without flags set.
// {"%010.2", "12345", "%!(NOVERB)%!(EXTRA string=12345)"},
//
// // Test that maps with non-reflexive keys print all keys and values.
// {"%v", map[float64]int{NaN: 1, NaN: 1}, "map[NaN:1 NaN:1]"},
// more floats
["%.2f", 1.0, "1.00"],
["%.2f", -1.0, "-1.00"],
["% .2f", 1.0, " 1.00"],
["% .2f", -1.0, "-1.00"],
["%+.2f", 1.0, "+1.00"],
["%+.2f", -1.0, "-1.00"],
["%7.2f", 1.0, " 1.00"],
["%7.2f", -1.0, " -1.00"],
["% 7.2f", 1.0, " 1.00"],
["% 7.2f", -1.0, " -1.00"],
["%+7.2f", 1.0, " +1.00"],
["%+7.2f", -1.0, " -1.00"],
["% +7.2f", 1.0, " +1.00"],
["% +7.2f", -1.0, " -1.00"],
["%07.2f", 1.0, "0001.00"],
["%07.2f", -1.0, "-001.00"],
["% 07.2f", 1.0, " 001.00"], //153 here
["% 07.2f", -1.0, "-001.00"],
["%+07.2f", 1.0, "+001.00"],
["%+07.2f", -1.0, "-001.00"],
["% +07.2f", 1.0, "+001.00"],
["% +07.2f", -1.0, "-001.00"]
];
test(function testThorough(): void {
tests.forEach(
(t, i): void => {
// p(t)
let is = S(t[0], t[1]);
let should = t[2];
assertEquals(
is,
should,
`failed case[${i}] : is >${is}< should >${should}<`
);
}
);
});
test(function testWeirdos(): void {
assertEquals(S("%.d", 9), "9");
assertEquals(
S("dec[%d]=%d hex[%[1]d]=%#x oct[%[1]d]=%#o %s", 1, 255, "Third"),
"dec[1]=255 hex[1]=0xff oct[1]=0377 Third"
);
});
test(function formatV(): void {
let a = { a: { a: { a: { a: { a: { a: { a: {} } } } } } } };
assertEquals(S("%v", a), "[object Object]");
assertEquals(S("%#v", a), "{ a: { a: { a: { a: [Object] } } } }");
assertEquals(
S("%#.8v", a),
"{ a: { a: { a: { a: { a: { a: { a: {} } } } } } } }"
);
assertEquals(S("%#.1v", a), "{ a: [Object] }");
});
test(function formatJ(): void {
let a = { a: { a: { a: { a: { a: { a: { a: {} } } } } } } };
assertEquals(S("%j", a), `{"a":{"a":{"a":{"a":{"a":{"a":{"a":{}}}}}}}}`);
});
test(function flagLessThan(): void {
let a = { a: { a: { a: { a: { a: { a: { a: {} } } } } } } };
let aArray = [a, a, a];
assertEquals(
S("%<#.1v", aArray),
"[ { a: [Object] }, { a: [Object] }, { a: [Object] } ]"
);
let fArray = [1.2345, 0.98765, 123456789.5678];
assertEquals(S("%<.2f", fArray), "[ 1.23, 0.99, 123456789.57 ]");
});
test(function testErrors(): void {
// wrong type : TODO strict mode ...
//assertEquals(S("%f", "not a number"), "%!(BADTYPE flag=f type=string)")
assertEquals(S("A %h", ""), "A %!(BAD VERB 'h')");
assertEquals(S("%J", ""), "%!(BAD VERB 'J')");
assertEquals(S("bla%J", ""), "bla%!(BAD VERB 'J')");
assertEquals(S("%Jbla", ""), "%!(BAD VERB 'J')bla");
assertEquals(S("%d"), "%!(MISSING 'd')");
assertEquals(S("%d %d", 1), "1 %!(MISSING 'd')");
assertEquals(S("%d %f A", 1), "1 %!(MISSING 'f') A");
assertEquals(S("%*.2f", "a", 1.1), "%!(BAD WIDTH 'a')");
assertEquals(S("%.*f", "a", 1.1), "%!(BAD PREC 'a')");
assertEquals(S("%.[2]*f", 1.23, "p"), "%!(BAD PREC 'p')%!(EXTRA '1.23')");
assertEquals(S("%.[2]*[1]f Yippie!", 1.23, "p"), "%!(BAD PREC 'p') Yippie!");
assertEquals(S("%[1]*.2f", "a", "p"), "%!(BAD WIDTH 'a')");
assertEquals(S("A", "a", "p"), "A%!(EXTRA 'a' 'p')");
assertEquals(S("%[2]s %[2]s", "a", "p"), "p p%!(EXTRA 'a')");
// remains to be determined how to handle bad indices ...
// (realistically) the entire error handling is still up for grabs.
assertEquals(S("%[hallo]s %d %d %d", 1, 2, 3, 4), "%!(BAD INDEX) 2 3 4");
assertEquals(S("%[5]s", 1, 2, 3, 4), "%!(BAD INDEX)%!(EXTRA '2' '3' '4')");
assertEquals(S("%[5]f"), "%!(BAD INDEX)");
assertEquals(S("%.[5]f"), "%!(BAD INDEX)");
assertEquals(S("%.[5]*f"), "%!(BAD INDEX)");
});
runTests();