summaryrefslogtreecommitdiff
path: root/std/fmt/printf.ts
diff options
context:
space:
mode:
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));
-}