summaryrefslogtreecommitdiff
path: root/std/fmt/printf.ts
diff options
context:
space:
mode:
authorCasper Beyer <caspervonb@pm.me>2021-02-02 19:05:46 +0800
committerGitHub <noreply@github.com>2021-02-02 12:05:46 +0100
commit6abf126c2a7a451cded8c6b5e6ddf1b69c84055d (patch)
treefd94c013a19fcb38954844085821ec1601c20e18 /std/fmt/printf.ts
parenta2b5d44f1aa9d64f448a2a3cc2001272e2f60b98 (diff)
chore: remove std directory (#9361)
This removes the std folder from the tree. Various parts of the tests are pretty tightly dependent on std (47 direct imports and 75 indirect imports, not counting the cli tests that use them as fixtures) so I've added std as a submodule for now.
Diffstat (limited to 'std/fmt/printf.ts')
-rw-r--r--std/fmt/printf.ts757
1 files changed, 0 insertions, 757 deletions
diff --git a/std/fmt/printf.ts b/std/fmt/printf.ts
deleted file mode 100644
index 6a821f208..000000000
--- a/std/fmt/printf.ts
+++ /dev/null
@@ -1,757 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-/**
- * This implementation is inspired by POSIX and Golang but does not port
- * implementation code. */
-
-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 = -1;
- precision = -1;
-}
-
-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;
- args: unknown[];
- i: number;
-
- state: State = State.PASSTHROUGH;
- verb = "";
- buf = "";
- argNum = 0;
- flags: Flags = new Flags();
-
- haveSeen: boolean[];
-
- // barf, store precision and width errors for later processing ...
- tmpError?: string;
-
- constructor(format: string, ...args: unknown[]) {
- 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) {
- const 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();
- const flags = this.flags;
- for (; this.i < this.format.length; ++this.i) {
- const 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:
- // TODO(bartlomieju): either a verb or * only verb for now
- if (c === "*") {
- const 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
- }
- }
-
- /**
- * Handle width or precision
- * @param wOrP
- */
- handleWidthOrPrecisionRef(wOrP: WorP): void {
- if (this.argNum >= this.args.length) {
- // handle Positional should have already taken care of it...
- return;
- }
- const 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 {
- const tmp = wOrP === WorP.WIDTH ? "WIDTH" : "PREC";
- this.tmpError = `%!(BAD ${tmp} '${this.args[this.argNum]}')`;
- }
- this.argNum++;
- }
-
- /**
- * Handle width and precision
- * @param flags
- */
- 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 unusable 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
- }
- }
-
- /** Handle positional */
- 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;
- }
-
- /** Handle less than */
- handleLessThan(): string {
- // deno-lint-ignore no-explicit-any
- const arg = this.args[this.argNum] as any;
- 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 + " ]";
- }
-
- /** Handle verb */
- 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 {
- const 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;
- }
-
- // deno-lint-ignore no-explicit-any
- _handleVerb(arg: any): string {
- switch (this.verb) {
- case "t":
- return this.pad(arg.toString());
- case "b":
- return this.fmtNumber(arg as number, 2);
- case "c":
- return this.fmtNumberCodePoint(arg as number);
- case "d":
- return this.fmtNumber(arg as number, 10);
- case "o":
- return this.fmtNumber(arg as number, 8);
- case "x":
- return this.fmtHex(arg);
- case "X":
- return this.fmtHex(arg, true);
- case "e":
- return this.fmtFloatE(arg as number);
- case "E":
- return this.fmtFloatE(arg as number, true);
- case "f":
- case "F":
- return this.fmtFloatF(arg as number);
- case "g":
- return this.fmtFloatG(arg as number);
- case "G":
- return this.fmtFloatG(arg as number, true);
- case "s":
- return this.fmtString(arg as string);
- case "T":
- return this.fmtString(typeof arg);
- case "v":
- return this.fmtV(arg);
- case "j":
- return this.fmtJ(arg);
- default:
- return `%!(BAD VERB '${this.verb}')`;
- }
- }
-
- /**
- * Pad a string
- * @param s text to pad
- */
- pad(s: string): string {
- const padding = this.flags.zero ? "0" : " ";
-
- if (this.flags.dash) {
- return s.padEnd(this.flags.width, padding);
- }
-
- return s.padStart(this.flags.width, padding);
- }
-
- /**
- * Pad a number
- * @param nStr
- * @param neg
- */
- 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;
-
- if (this.flags.dash) {
- nStr = nStr.padEnd(len, pad);
- } else {
- nStr = nStr.padStart(len, pad);
- }
-
- if (zero) {
- // see above
- nStr = sign + nStr;
- }
- return nStr;
- }
-
- /**
- * Format a number
- * @param n
- * @param radix
- * @param upcase
- */
- fmtNumber(n: number, radix: number, upcase = 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);
- }
-
- /**
- * Format number with code points
- * @param n
- */
- fmtNumberCodePoint(n: number): string {
- let s = "";
- try {
- s = String.fromCodePoint(n);
- } catch (RangeError) {
- s = UNICODE_REPLACEMENT_CHARACTER;
- }
- return this.pad(s);
- }
-
- /**
- * Format special float
- * @param n
- */
- 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 "";
- }
-
- /**
- * Round fraction to precision
- * @param fractional
- * @param precision
- */
- 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;
- }
-
- /**
- * Format float E
- * @param n
- * @param upcase
- */
- fmtFloatE(n: number, upcase = 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);
- }
-
- /**
- * Format float F
- * @param n
- */
- 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);
- }
-
- /**
- * Format float G
- * @param n
- * @param upcase
- */
- fmtFloatG(n: number, upcase = 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");
- }
-
- const 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;
- }
-
- /**
- * Format string
- * @param s
- */
- fmtString(s: string): string {
- if (this.flags.precision !== -1) {
- s = s.substr(0, this.flags.precision);
- }
- return this.pad(s);
- }
-
- /**
- * Format hex
- * @param val
- * @param upper
- */
- fmtHex(val: string | number, upper = false): string {
- // allow others types ?
- switch (typeof val) {
- case "number":
- return this.fmtNumber(val as number, 16, upper);
- case "string": {
- const 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(bartlomieju): 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
- const c = (val.charCodeAt(i) & 0xff).toString(16);
- hex += c.length === 1 ? `0${c}` : c;
- }
- if (upper) {
- hex = hex.toUpperCase();
- }
- return this.pad(hex);
- }
- default:
- throw new Error(
- "currently only number and string are implemented for hex",
- );
- }
- }
-
- /**
- * Format value
- * @param val
- */
- fmtV(val: Record<string, unknown>): string {
- if (this.flags.sharp) {
- const 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);
- }
- }
-
- /**
- * Format JSON
- * @param val
- */
- fmtJ(val: unknown): string {
- return JSON.stringify(val);
- }
-}
-
-/**
- * Converts and format a variable number of `args` as is specified by `format`.
- * `sprintf` returns the formatted string.
- *
- * @param format
- * @param args
- */
-export function sprintf(format: string, ...args: unknown[]): string {
- const printf = new Printf(format, ...args);
- return printf.doPrintf();
-}
-
-/**
- * Converts and format a variable number of `args` as is specified by `format`.
- * `printf` writes the formatted string to standard output.
- * @param format
- * @param args
- */
-export function printf(format: string, ...args: unknown[]): void {
- const s = sprintf(format, ...args);
- Deno.stdout.writeSync(new TextEncoder().encode(s));
-}