From e1e33f9cb6daeb055694631760d5e7e3cdd8dbcb Mon Sep 17 00:00:00 2001 From: SheetJS Date: Mon, 3 Sep 2018 23:35:36 -0400 Subject: [PATCH] version bump 1.2.0: ESM version Fixes #3 h/t @75lb --- .spelling | 40 +++ Makefile | 4 +- README.md | 136 ++++++++--- bits/00_header.js | 6 + bits/01_version.js | 2 +- bits/50_doit.js | 5 +- bits/54_convmisc.js | 2 +- bits/99_esmfoot.js | 1 + bits/Makefile | 4 +- bits/loop_code.mjs | 8 + lib/loop_code.mjs | 582 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 +- printj.flow.js | 5 +- printj.js | 4 +- printj.mjs | 582 ++++++++++++++++++++++++++++++++++++++++++++ tests/mjs.html | 70 ++++++ tests/node.mjs | 5 + tests/printj.mjs | 1 + 18 files changed, 1411 insertions(+), 50 deletions(-) create mode 100644 .spelling create mode 100644 bits/99_esmfoot.js create mode 100644 bits/loop_code.mjs create mode 100644 lib/loop_code.mjs create mode 100644 printj.mjs create mode 100644 tests/mjs.html create mode 100755 tests/node.mjs create mode 120000 tests/printj.mjs diff --git a/.spelling b/.spelling new file mode 100644 index 0000000..ce339f3 --- /dev/null +++ b/.spelling @@ -0,0 +1,40 @@ +# printj (C) 2016-present SheetJS -- http://sheetjs.com +SheetJS +printj +printf + +# printf-related terms +16-bit +32-bit +52-bit +64-bit +base-10 +fmt + +# Third-party +AltiVec +FreeBSD +glibc +libc +nodejs +npm +unicode + +# Other terms +CommonJS +NaN +UTF-16 +accessor +bitwise +codepages +conformant +errno +falsy +runtime +trigraphs +truthy +typeof +valueOf +variadic +whitespace + diff --git a/Makefile b/Makefile index f76305b..983d406 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ ULIB=$(shell echo $(LIB) | tr a-z A-Z) DEPS=$(sort $(wildcard bits/*.js)) TARGET=$(LIB).js FLOWTARGET=$(LIB).flow.js +MJSTARGET=$(LIB).mjs FLOWTGTS=$(TARGET) $(AUXTARGETS) CLOSURE=/usr/local/lib/node_modules/google-closure-compiler/compiler.jar @@ -26,6 +27,7 @@ $(FLOWTGTS): %.js : %.flow.js $(FLOWTARGET): $(DEPS) lib cp lib/$(REQS).js $(FLOWTARGET) + cp lib/$(REQS).mjs $(MJSTARGET) bits/01_version.js: package.json echo "$(ULIB).version = '"`grep version package.json | awk '{gsub(/[^0-9a-z\.-]/,"",$$2); print $$2}'`"';" > $@ @@ -75,7 +77,7 @@ fullint: lint old-lint tslint flow mdlint ## Run all checks .PHONY: lint lint: $(TARGET) ## Run eslint checks - @eslint --ext .js,.njs,.json,.html,.htm $(TARGET) $(AUXTARGETS) $(CMDS) $(HTMLLINT) package.json bower.json + @eslint --ext .js,.njs,.json,.html,.htm $(TARGET) $(AUXTARGETS) $(CMDS) $(HTMLLINT) package.json if [ -e $(CLOSURE) ]; then java -jar $(CLOSURE) $(FLOWTARGET) --jscomp_warning=reportUnknownTypes >/dev/null; fi .PHONY: old-lint diff --git a/README.md b/README.md index 53b9e88..68d4e43 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,67 @@ A self-contained specification of the printf format string is included below in [support against various printf implementations](#support-summary) +## Table of Contents + +
+ Table of Contents (click to show) + + + + * [Installation](#installation) + * [Usage](#usage) + * [Testing](#testing) + * [License](#license) + * [Badges](#badges) +- [printf format string specification](#printf-format-string-specification) + + [Original C Interface](#original-c-interface) + + [JS and C strings](#js-and-c-strings) + + [JS Interface](#js-interface) + * [Specifier heritage and regular expression](#specifier-heritage-and-regular-expression) + * [Conversion Specifier Quick Reference Table](#conversion-specifier-quick-reference-table) + * [Parameter Selection](#parameter-selection) + * [Dynamic Specifiers](#dynamic-specifiers) +- [C Data Model](#c-data-model) + + [Integer Types](#integer-types) + + [Character and String Types](#character-and-string-types) + + [Floating Point Number Types](#floating-point-number-types) + * [Implementation](#implementation) +- [Integer Conversions](#integer-conversions) + * [Restricting Integer Values](#restricting-integer-values) + * [Length Specifiers for Integer Conversions](#length-specifiers-for-integer-conversions) + * [Rendering Unsigned Integers in Base 10 ("u" and "U" conversions)](#rendering-unsigned-integers-in-base-10-u-and-u-conversions) + * [Rendering Unsigned Integers in Base 8 ("o" and "O" conversions)](#rendering-unsigned-integers-in-base-8-o-and-o-conversions) + * [Rendering Unsigned Integers in Base 16 ("x" and "X" conversions)](#rendering-unsigned-integers-in-base-16-x-and-x-conversions) + * [Rendering Signed Integers in Base 10 ("d" "i" and "D" conversions)](#rendering-signed-integers-in-base-10-d-i-and-d-conversions) +- [Floating Point Conversions](#floating-point-conversions) + * [Infinity, NaN, and Negative Zero](#infinity-nan-and-negative-zero) + * [Exponential Form ("e" and "E" conversions)](#exponential-form-e-and-e-conversions) + * [Standard Form ("f" and "F" conversions)](#standard-form-f-and-f-conversions) + * [Value-dependent Form ("g" and "G" conversions)](#value-dependent-form-g-and-g-conversions) + * [Hex-Mantissa Decimal-Binary-Exponent Form ("a" and "A" conversions)](#hex-mantissa-decimal-binary-exponent-form-a-and-a-conversions) +- [Character Conversions](#character-conversions) + * [Rendering Strings ("s" and "S" conversions)](#rendering-strings-s-and-s-conversions) + * [Rendering Characters ("c" and "C" conversions)](#rendering-characters-c-and-c-conversions) +- [Non-Numeric Conversions](#non-numeric-conversions) + * [The literal "%" symbol ("%" conversion)](#the-literal-%25-symbol-%25-conversion) + * [Interpreting and Rendering Pointers ("p" conversion)](#interpreting-and-rendering-pointers-p-conversion) + * [Extracting length of a partial conversion ("n" conversion)](#extracting-length-of-a-partial-conversion-n-conversion) + * [Error messages ("m" conversion)](#error-messages-m-conversion) +- [Extensions](#extensions) + * [Rendering Boolean Values ("y" and "Y" conversions)](#rendering-boolean-values-y-and-y-conversions) + * [Rendering JSON ("J" conversion)](#rendering-json-j-conversion) + * [JS typeof and valueOf ("T" and "V" conversion)](#js-typeof-and-valueof-t-and-v-conversion) + * [Rendering Unsigned Integers in Base 2 ("b" and "B" conversions)](#rendering-unsigned-integers-in-base-2-b-and-b-conversions) +- [Miscellaneous Notes](#miscellaneous-notes) + * [Format Characters](#format-characters) + * [JS and C strings](#js-and-c-strings-1) + * [Browser Deviations](#browser-deviations) + * [Support Summary](#support-summary) + + + +
+ ## Installation With [npm](https://www.npmjs.org/package/printj): @@ -30,9 +91,8 @@ The browser exposes a variable `PRINTJ` When installed globally, npm installs a script `printj` that renders the format string with the given arguments. Running the script with `-h` displays help. -The script will manipulate `module.exports` if available (e.g. in a CommonJS -`require` context). This is not always desirable. To prevent the behavior, -define `DO_NOT_EXPORT_PRINTJ` +The script will manipulate `module.exports` if available. This is not always +desirable. To prevent the behavior, define `DO_NOT_EXPORT_PRINTJ` ## Usage @@ -125,8 +185,8 @@ but have different interfaces reflecting different input and output behaviors. Some functions have wide variants that use wide `wchar_t *` strings rather than normal C `char *`. The following variants are required by the POSIX spec: -| function | max length | output destination | vintage | wide ver | -|------------|------------|-----------------------|---------|------------| +| function | max length | output destination | vintage | wide form | +|:-----------|:-----------|:----------------------|:--------|:-----------| | `printf` | unbounded | standard output | K&R | `wprintf` | | `fprintf` | unbounded | stream (`FILE *`) | K&R | `fwprintf` | | `sprintf` | unbounded | string (`char *`) | K&R | `swprintf` | @@ -185,7 +245,7 @@ various string functions are included at the end of the document. ## Specifier heritage and regular expression -Note: The regular expressions follow perl `/x` style. Whitespace characters +Note: The regular expressions follow Perl `/x` style. Whitespace characters outside of character classes are ignored. `#` is a comment character and every character until the end of the line is ignored. To convert to a standard regex: @@ -298,7 +358,7 @@ This implementation explicitly does not support certain non-standard extensions: | `i` | integral | cast to C `int`, standard form decimal (alias of `d`) | | `J` | extended | prints objects using JSON or `util.inspect` | | `m` | misc | prints info about Error objects (JS equivalent of `errno`) | -| `n` | misc | do not print! stores number of chars written to arg `.len` | +| `n` | misc | do not print! store number of chars written to `.len` field | | `o` | integral | cast to C `unsigned int`, standard form octal | | `O` | integral | cast to C `unsigned long`, standard form octal | | `p` | misc | print `"l"` field of object (fake pointer) | @@ -323,8 +383,8 @@ printf("Count to 3: %d %d %d", 1, 2, 3); // Count to 3: 1 2 3 ``` POSIX `printf` permits explicit argument selection, bypassing the standard -behavior of using the arguments in order. To select the `n`-th argument, use -`n$` immediately after the `%` token to select an argument for the conversion: +behavior of consuming arguments in order. To specify the argument at position +`n`, use `n$` immediately after the `%` token: ```C printf("%d %d %d", 1, 2, 3); // 1 2 3 (implicit order 1, 2, 3 ) @@ -463,11 +523,11 @@ Numerous "C data models", specifying the bit/byte sizes of the various types, have been and continue to be used. For example, OSX and other modern 64-bit UNIX flavors use the "LP64" C data model. 64-bit Windows currently uses the "LLP64" model. 32-bit systems generally use the "ILP32" model. The 8-bit byte -sizes for the various types under the various models are defined in ctypes.json +sizes for the data types under the various models are defined in `ctypes.json` in the `Models` object as per the following table: -| type | ctypes.json | LP64 | ILP32 | LLP64 | -|-------------|-------------|-----:|------:|------:| +| type | JSON key | LP64 | ILP32 | LLP64 | +|:------------|:------------|-----:|------:|------:| | `char` | `char` | 1 | 1 | 1 | | `short` | `short` | 2 | 2 | 2 | | `int` | `int` | 4 | 4 | 4 | @@ -555,16 +615,16 @@ is signed or unsigned according to the conversion specifier. If a length is specified, it overrides the implied length of the conversion. The following table describes the behavior of this implementation: -| implied C type | ctypes.json | length | conv default | -|:------------------------------------|:------------|:------:|:-------------| -| `int` or `unsigned int` | `int` | (none) | d i o u x X | -| `char` or `unsigned char` | `char` | hh | -| `short` or `unsigned short` | `short` | h | -| `long` or `unsigned long` | `long` | l | D U O | -| `long long` or `unsigned long long` | `longlong` | L ll q | -| `intmax_t` or `uintmax_t` | `intmax_t` | j | -| `size_t` or `ssize_t` | `size_t` | z Z | -| `ptrdiff_t` or unsigned variant | `ptrdiff_t` | t | +| implied C type | JSON key | length | conversion default | +|:-----------------------------|:------------|:--------:|:-------------------| +| `[unsigned] int` | `int` | (none) | `d i o u x X` | +| `[unsigned] char` | `char` | `hh` | +| `[unsigned] short` | `short` | `h` | +| `[unsigned] long` | `long` | `l` | `D U O` | +| `[unsigned] long long` | `longlong` | `L ll q` | +| `intmax_t` or `uintmax_t` | `intmax_t` | `j` | +| `size_t` or `ssize_t` | `size_t` | `z Z` | +| `ptrdiff_t` or unsigned form | `ptrdiff_t` | `t` | ## Rendering Unsigned Integers in Base 10 ("u" and "U" conversions) @@ -615,7 +675,7 @@ JS recognizes a few special IEEE754 values, as described in the following table: |------------:|:--------------|:-----------------------------------------------| | `Infinity` | `1./0.` | Positive limiting value `lim{x->0+} 1/x` | | `-Infinity` | `-1./0.` | Negative limiting value `lim{x->0+} -1/x` | -| `NaN` | `0./0.` | Placeholder for "not-a-number" e.g. `0./0.` | +| `NaN` | `0./0.` | Placeholder for "not-a-number" such as `0./0.` | | `-0.` | `-1/Infinity` | Negative limiting value `lim{x->0-} x` | JS `Number` methods render different strings from the POSIX spec: @@ -625,7 +685,7 @@ JS `Number` methods render different strings from the POSIX spec: | `Infinity` | `"inf" "INF"` or `"infinity" "INFINITY"` | `"Infinity"` | | `-Infinity` | `"-inf" "-INF"` or `"-infinity" "-INFINITY"` | `"-Infinity"` | | `NaN` | `"[-]nan" "[-]NAN"` w/opt parenthesized chars | `"NaN"` | -| `-0.` | uses negative sign (e.g. `"-0"` under `"%f"`) | same as `+0.` | +| `-0.` | uses negative sign (`"-0"` under `"%f"`) | same as `+0.` | This implementation performs the required adjustments. @@ -656,12 +716,12 @@ The final form (exponential or standard) is determined based on the value. The threshold is different from the JS `toString` / `toPrecision` thresholds and depends on the specified precision as well as the base-10 exponent: -| Value | `"%.3g"` | `toPrecision(3)` | -|----------:|:-----------|:-----------------| -| 1.2345e-4 | `0.000123` | `0.000123` | -| 1.2345e-5 | `1.23e-05` | `0.0000123` | -| 1.2345e-6 | `1.23e-06` | `0.00000123` | -| 1.2345e-7 | `1.23e-07` | `1.23e-7` | +| Value | `"%.3g"` | `toPrecision(3)` | +|------------:|:-----------|:-----------------| +| `1.2345e-4` | `0.000123` | `0.000123` | +| `1.2345e-5` | `1.23e-05` | `0.0000123` | +| `1.2345e-6` | `1.23e-06` | `0.00000123` | +| `1.2345e-7` | `1.23e-07` | `1.23e-7` | According to JS spec, `toPrecision` uses standard form when `precision > E` and `E >= -6`. For printf standard form is used when `precision > E` and `E >= -4`. @@ -673,20 +733,20 @@ the exponent expression, and radix of the exponent expression. The standard exponential form uses decimal for all three parts. For base 16, there are quite a few reasonable combinations. Consider the value `1.234567e-80`: -| Mant | Exp Base | Radix-10 (sigil `";"`) | Radix-16 (sigil `";"`) | -|:----:|:--------:|:-----------------------|:-----------------------| -| 10 | 10 | `1.234567;-80` | `1.234567;-50` | -| 16 | 10 | `1.3c0c9539b8887;-80` | `1.3c0c9539b8887;-50` | -| 16 | 16 | `5.daf8c8f5f4104;-67` | `5.daf8c8f5f4104;-43` | -| 16 | 4 | `1.76be323d7d041;-133` | `1.76be323d7d041;-85` | -| 16 | 2 | `1.76be323d7d041;-266` | `1.76be323d7d041;-10a` | +| Mantissa | Exp Base | Radix 10 (sigil `";"`) | Radix 16 (sigil `";"`) | +|:--------:|:--------:|:-----------------------|:-----------------------| +| 10 | 10 | `1.234567;-80` | `1.234567;-50` | +| 16 | 10 | `1.3c0c9539b8887;-80` | `1.3c0c9539b8887;-50` | +| 16 | 16 | `5.daf8c8f5f4104;-67` | `5.daf8c8f5f4104;-43` | +| 16 | 4 | `1.76be323d7d041;-133` | `1.76be323d7d041;-85` | +| 16 | 2 | `1.76be323d7d041;-266` | `1.76be323d7d041;-10a` | POSIX `"%a"` uses a hex mantissa (16), decimal exponent radix (10), and binary exponent base (2). The general normalized form requires that the integral part of the mantissa to exceed 0 and not to exceed `exponent base - 1` except in the special case of `0`. The sigil is `p` and exponent sign is always used. -JS `num.toString(radix)` is implementation-dependent for valid non-10 radices +JS `num.toString(radix)` is implementation-dependent for radices other than 10 (`2-9, 11-36`). IE uses hex-mantissa decimal-hex-exponent form when the absolute value of the base-2 exponent exceeds 60. Otherwise, IE uses an exact standard hexadecimal form. Chrome, Safari and other browsers always use the diff --git a/bits/00_header.js b/bits/00_header.js index a609499..2cd78d2 100644 --- a/bits/00_header.js +++ b/bits/00_header.js @@ -4,6 +4,11 @@ /*exported PRINTJ */ /*:: declare var DO_NOT_EXPORT_PRINTJ:?boolean; */ /*:: declare function define(cb:()=>any):void; */ +#ifdef USE_ESM +var PRINTJ/*:PRINTJModule*/ = /*::(*/{}/*:: :any)*/; +#include "01_version.js" +export const version = PRINTJ.version; +#else var PRINTJ/*:PRINTJModule*/; (function (factory/*:(a:any)=>void*/)/*:void*/ { /*jshint ignore:start */ @@ -27,3 +32,4 @@ var PRINTJ/*:PRINTJModule*/; /*jshint ignore:end */ }(function(PRINTJ/*:PRINTJModule*/) { #include "01_version.js" +#endif diff --git a/bits/01_version.js b/bits/01_version.js index 1a39861..eafee04 100644 --- a/bits/01_version.js +++ b/bits/01_version.js @@ -1 +1 @@ -PRINTJ.version = '1.1.2'; +PRINTJ.version = '1.2.0'; diff --git a/bits/50_doit.js b/bits/50_doit.js index a359aea..408cd03 100644 --- a/bits/50_doit.js +++ b/bits/50_doit.js @@ -1,10 +1,13 @@ #include "30_ctypes.js" #include "40_macros.js" +#ifdef USE_ESM +var u_inspect/*:(o:any)=>string*/ = JSON.stringify; +#else /*:: var util = require('util'); */ /*global process:true, util:true, require:true */ if(typeof process !== 'undefined' && !!process.versions && !!process.versions.node) util=require("util"); var u_inspect/*:(o:any)=>string*/ = (typeof util != 'undefined') ? util.inspect : JSON.stringify; - +#endif function doit(t/*:ParsedFmt*/, args/*:Array*/)/*:string*/ { var o/*:Array*/ = []; diff --git a/bits/54_convmisc.js b/bits/54_convmisc.js index fa479e9..a2fa44a 100644 --- a/bits/54_convmisc.js +++ b/bits/54_convmisc.js @@ -35,7 +35,7 @@ /* boolean (extension) */ case /*Y*/ 89: case /*y*/ 121: - O = Boolean(arg) ? (alt ? "yes" : "true") : (alt ? "no" : "false"); + O = (arg) ? (alt ? "yes" : "true") : (alt ? "no" : "false"); if(c == /*Y*/ 89) O = O.toUpperCase(); PREC_STR(O, prec) WIDTH(O, width, flags) diff --git a/bits/99_esmfoot.js b/bits/99_esmfoot.js new file mode 100644 index 0000000..54a466f --- /dev/null +++ b/bits/99_esmfoot.js @@ -0,0 +1 @@ +export { sprintf, vsprintf }; diff --git a/bits/Makefile b/bits/Makefile index f6986ac..b73e4a2 100644 --- a/bits/Makefile +++ b/bits/Makefile @@ -2,8 +2,8 @@ ifndef OUTDIR OUTDIR=$(PWD)/lib endif -JSFILES=$(wildcard *.js) -LIBS=$(filter-out $(wildcard [0-9]*_*.js),$(wildcard *.js)) +JSFILES=$(wildcard *.js) $(wildcard *.mjs) +LIBS=$(filter-out $(wildcard [0-9]*_*.js),$(JSFILES)) OUTLIBS=$(patsubst %,$(OUTDIR)/%,$(LIBS)) diff --git a/bits/loop_code.mjs b/bits/loop_code.mjs new file mode 100644 index 0000000..fb8179e --- /dev/null +++ b/bits/loop_code.mjs @@ -0,0 +1,8 @@ +#define USE_ESM +#include "00_header.js" +#define USE_LOOP +#define USE_CODE +#include "10_tokenize.js" +#include "50_doit.js" +#include "80_wrapper.js" +#include "99_esmfoot.js" diff --git a/lib/loop_code.mjs b/lib/loop_code.mjs new file mode 100644 index 0000000..decfc8c --- /dev/null +++ b/lib/loop_code.mjs @@ -0,0 +1,582 @@ +/* printj.js (C) 2016-present SheetJS -- http://sheetjs.com */ +/* vim: set ts=2: */ +/*jshint sub:true, eqnull:true */ +/*exported PRINTJ */ +/*:: declare var DO_NOT_EXPORT_PRINTJ:?boolean; */ +/*:: declare function define(cb:()=>any):void; */ + +var PRINTJ/*:PRINTJModule*/ = /*::(*/{}/*:: :any)*/; + +PRINTJ.version = '1.2.0'; + +export const version = PRINTJ.version; + +function tokenize(fmt/*:string*/)/*:ParsedFmt*/ { + var out/*:ParsedFmt*/ = []; + var start/*:number*/ = 0; + + var i/*:number*/ = 0; + var infmt/*:boolean*/ = false; + var fmtparam/*:string*/ = "", fmtflags/*:string*/ = "", fmtwidth/*:string*/ = "", fmtprec/*:string*/ = "", fmtlen/*:string*/ = ""; + + var c/*:number*/ = 0; + + var L/*:number*/ = fmt.length; + + for(; i < L; ++i) { + c = fmt.charCodeAt(i); + if(!infmt) { + + if(c !== 37) continue; + + if(start < i) out.push(["L", fmt.substring(start, i)]); + start = i; + infmt = true; + continue; + } + + if(c >= 48 && c < 58) { + if(fmtprec.length) fmtprec += String.fromCharCode(c); + else if(c == 48 && !fmtwidth.length) fmtflags += String.fromCharCode(c); + else fmtwidth += String.fromCharCode(c); + } else switch(c) { + /* positional */ + case 36: + if(fmtprec.length) fmtprec += "$"; + else if(fmtwidth.charAt(0) == "*") fmtwidth += "$"; + else { fmtparam = fmtwidth + "$"; fmtwidth = ""; } + break; + + /* flags */ + case 39: fmtflags += "'"; break; + case 45: fmtflags += "-"; break; + case 43: fmtflags += "+"; break; + case 32: fmtflags += " "; break; + case 35: fmtflags += "#"; break; + + /* width and precision */ + case 46: fmtprec = "."; break; + case 42: + if(fmtprec.charAt(0) == ".") fmtprec += "*"; + else fmtwidth += "*"; + break; + + /* length */ + case 104: + case 108: + if(fmtlen.length > 1) throw "bad length " + fmtlen + String(c); + fmtlen += String.fromCharCode(c); + break; + + case 76: + case 106: + case 122: + case 116: + case 113: + case 90: + case 119: + if(fmtlen !== "") throw "bad length " + fmtlen + String.fromCharCode(c); + fmtlen = String.fromCharCode(c); + break; + + case 73: + if(fmtlen !== "") throw "bad length " + fmtlen + 'I'; + fmtlen = 'I'; + break; + + /* conversion */ + case 100: + case 105: + case 111: + case 117: + case 120: + case 88: + case 102: + case 70: + case 101: + case 69: + case 103: + case 71: + case 97: + case 65: + case 99: + case 67: + case 115: + case 83: + case 112: + case 110: + case 68: + case 85: + case 79: + case 109: + case 98: + case 66: + case 121: + case 89: + case 74: + case 86: + case 84: + case 37: + infmt = false; + if(fmtprec.length > 1) fmtprec = fmtprec.substr(1); + out.push([String.fromCharCode(c), fmt.substring(start, i+1), fmtparam, fmtflags, fmtwidth, fmtprec, fmtlen]); + start = i+1; + fmtlen = fmtprec = fmtwidth = fmtflags = fmtparam = ""; + break; + default: + throw new Error("Invalid format string starting with |" + fmt.substring(start, i+1) + "|"); + } + + } + + if(start < fmt.length) out.push(["L", fmt.substring(start)]); + return out; +} + +//#define PAD_(x,c) (x >= 0 ? new Array(((x)|0) + 1).join((c)) : "") +var padstr/*:{[s:string]:string}*/ = { + " ": " ", + "0": "000000000000000000000000000000000", + "7": "777777777777777777777777777777777", + "f": "fffffffffffffffffffffffffffffffff" +}; + +var u_inspect/*:(o:any)=>string*/ = JSON.stringify; + +function doit(t/*:ParsedFmt*/, args/*:Array*/)/*:string*/ { + var o/*:Array*/ = []; + var argidx/*:number*/ = 0, idx/*:number*/ = 0; + var Vnum/*:number*/ = 0; + var pad/*:string*/ = ""; + for(var i/*:number*/ = 0; i < t.length; ++i) { + var m/*:ParsedEntry*/ = t[i], c/*:number*/ = (m[0]/*:string*/).charCodeAt(0); + /* m order: conv full param flags width prec length */ + + if(c === /*L*/ 76) { o.push(m[1]); continue; } + if(c === /*%*/ 37) { o.push("%"); continue; } + + var O/*:string*/ = ""; + var isnum/*:number*/ = 0, radix/*:number*/ = 10, bytes/*:number*/ = 4, sign/*:boolean*/ = false; + + /* flags */ + var flags/*:string*/ = m[3]||""; + var alt/*:boolean*/ = flags.indexOf("#") > -1; + + /* position */ + if(m[2]) argidx = parseInt(m[2])-1; + /* %m special case */ + else if(c === /*m*/ 109 && !alt) { o.push("Success"); continue; } + + /* grab width */ + var width = 0; if(m[ 4] != null && m[ 4].length > 0) { if(m[ 4].charAt(0) !== '*') width = parseInt(m[ 4], 10); else if(m[ 4].length === 1) width = args[idx++]; else width = args[parseInt(m[ 4].substr(1), 10)-1]; } + + /* grab precision */ + var prec = -1; if(m[ 5] != null && m[ 5].length > 0) { if(m[ 5].charAt(0) !== '*') prec = parseInt(m[ 5], 10); else if(m[ 5].length === 1) prec = args[idx++]; else prec = args[parseInt(m[ 5].substr(1), 10)-1]; } + + /* position not specified */ + if(!m[2]) argidx = idx++; + + /* grab argument */ + var arg/*:any*/ = args[argidx]; + + /* grab length */ + var len/*:string*/ = m[6] || ""; + + switch(c) { + /* str cCsS */ + + case /*S*/ 83: + case /*s*/ 115: + /* only valid flag is "-" for left justification */ + O = String(arg); + if( prec >= 0) O = O.substr(0, prec); + if( width > O.length || - width > O.length) { if(( flags.indexOf("-") == -1 || width < 0) && flags.indexOf("0") != -1) { pad = ( width - O.length >= 0 ? padstr["0"].substr(0, width - O.length) : ""); O = pad + O; } else { pad = ( width - O.length >= 0 ? padstr[" "].substr(0, width - O.length) : ""); O = flags.indexOf("-") > -1 ? O + pad : pad + O; } } + break; + + /* first char of string or convert */ + case /*C*/ 67: + case /*c*/ 99: + switch(typeof arg) { + case "number": + var cc/*:number*/ = arg; + if(c == 67 || len.charCodeAt(0) === /*l*/ 108) { cc &= 0xFFFFFFFF; O = String.fromCharCode( cc); } + else { cc &= 0xFF; O = String.fromCharCode( cc); } + break; + case "string": O = /*::(*/arg/*:: :string)*/.charAt(0); break; + default: O = String(arg).charAt(0); + } + if( width > O.length || - width > O.length) { if(( flags.indexOf("-") == -1 || width < 0) && flags.indexOf("0") != -1) { pad = ( width - O.length >= 0 ? padstr["0"].substr(0, width - O.length) : ""); O = pad + O; } else { pad = ( width - O.length >= 0 ? padstr[" "].substr(0, width - O.length) : ""); O = flags.indexOf("-") > -1 ? O + pad : pad + O; } } + break; + + /* int diDuUoOxXbB */ + + /* signed integer */ + case /*D*/ 68: bytes = 8; + /* falls through */ + case /*d*/ 100: + case /*i*/ 105: isnum = -1; sign = true; break; + + /* unsigned integer */ + case /*U*/ 85: bytes = 8; + /* falls through */ + case /*u*/ 117: isnum = -1; break; + + /* unsigned octal */ + case /*O*/ 79: bytes = 8; + /* falls through */ + case /*o*/ 111: isnum = -1; radix = (8); break; + + /* unsigned hex */ + case /*x*/ 120: isnum = -1; radix = (-16); break; + case /*X*/ 88: isnum = -1; radix = (16); break; + + /* unsigned binary (extension) */ + case /*B*/ 66: bytes = 8; + /* falls through */ + case /*b*/ 98: isnum = -1; radix = (2); break; + + /* flt fegFEGaA */ + + /* floating point logic */ + case /*F*/ 70: + case /*f*/ 102: isnum = (1); break; + + case /*E*/ 69: + case /*e*/ 101: isnum = (2); break; + + case /*G*/ 71: + case /*g*/ 103: isnum = (3); break; + + /* floating hex */ + case /*A*/ 65: + case /*a*/ 97: isnum = (4); break; + + /* misc pnmJVTyY */ + + /* JS has no concept of pointers so interpret the `l` key as an address */ + case /*p*/ 112: + Vnum = typeof arg == "number" ? arg : arg ? Number(arg.l) : -1; + if(isNaN(Vnum)) Vnum = -1; + if(alt) O = Vnum.toString(10); + else { + Vnum = Math.abs(Vnum); + O = "0x" + Vnum.toString(16).toLowerCase(); + } + break; + + /* store length in the `len` key */ + case /*n*/ 110: + if(arg) { arg.len=0; for(var oo/*:number*/ = 0; oo < o.length; ++oo) arg.len += o[oo].length; } + continue; + + /* process error */ + case /*m*/ 109: + if(!(arg instanceof Error)) O = "Success"; + else if(arg.message) O = arg.message; + else if(arg.errno) O = "Error number " + arg.errno; + else O = "Error " + String(arg); + break; + + /* JS-specific conversions (extension) */ + case /*J*/ 74: O = (alt ? u_inspect : JSON.stringify)(arg); break; + case /*V*/ 86: O = arg == null ? "null" : String(arg.valueOf()); break; + case /*T*/ 84: + if(alt) { /* from '[object %s]' extract %s */ + O = Object.prototype.toString.call(arg).substr(8); + O = O.substr(0, O.length - 1); + } else O = typeof arg; + break; + + /* boolean (extension) */ + case /*Y*/ 89: + case /*y*/ 121: + O = (arg) ? (alt ? "yes" : "true") : (alt ? "no" : "false"); + if(c == /*Y*/ 89) O = O.toUpperCase(); + if( prec >= 0) O = O.substr(0, prec); + if( width > O.length || - width > O.length) { if(( flags.indexOf("-") == -1 || width < 0) && flags.indexOf("0") != -1) { pad = ( width - O.length >= 0 ? padstr["0"].substr(0, width - O.length) : ""); O = pad + O; } else { pad = ( width - O.length >= 0 ? padstr[" "].substr(0, width - O.length) : ""); O = flags.indexOf("-") > -1 ? O + pad : pad + O; } } + break; + + } + + if(width < 0) { width = -width; flags += "-"; } + + if(isnum == -1) { + + Vnum = Number(arg); + + /* parse byte length field */ + + switch(len) { + /* char */ + case "hh": { bytes = 1; } break; + /* short */ + case "h": { bytes = 2; } break; + + /* long */ + case "l": { if(bytes == 4) bytes = 8; } break; + + /* long long */ + case "L": + case "q": + case "ll": { if(bytes == 4) bytes = 8; } break; + + /* intmax_t */ + case "j": { if(bytes == 4) bytes = 8; } break; + + /* ptrdiff_t */ + case "t": { if(bytes == 4) bytes = 8; } break; + + /* size_t */ + case "z": + case "Z": { if(bytes == 4) bytes = 8; } break; + + /* CRT size_t or ptrdiff_t */ + case "I": + + { if(bytes == 4) bytes = 8; } + + break; + + /* CRT wchar_t */ + case "w": break; + } + + /* restrict value */ + + switch(bytes) { + case 1: Vnum = (Vnum & 0xFF); if(sign && (Vnum > 0x7F)) Vnum -= (0xFF + 1); break; + case 2: Vnum = (Vnum & 0xFFFF); if(sign && (Vnum > 0x7FFF)) Vnum -= (0xFFFF + 1); break; + case 4: Vnum = sign ? (Vnum | 0) : (Vnum >>> 0); break; + default: Vnum = isNaN(Vnum) ? 0 : Math.round(Vnum); break; + } + + /* generate string */ + if(bytes > 4 && Vnum < 0 && !sign) { + if(radix == 16 || radix == -16) { + O = (Vnum>>>0).toString(16); + Vnum = Math.floor((Vnum - (Vnum >>> 0)) / Math.pow(2,32)); + O = (Vnum>>>0).toString(16) + (8 - O.length >= 0 ? padstr[ "0"].substr(0,8 - O.length) : "") + O; + O = (16 - O.length >= 0 ? padstr[ "f"].substr(0,16 - O.length) : "") + O; + if(radix == 16) O = O.toUpperCase(); + } else if(radix == 8) { + O = (Vnum>>>0).toString(8); + O = (10 - O.length >= 0 ? padstr[ "0"].substr(0,10 - O.length) : "") + O; + Vnum = Math.floor((Vnum - ((Vnum >>> 0)&0x3FFFFFFF)) / Math.pow(2,30)); + O = (Vnum>>>0).toString(8) + O.substr(O.length - 10); + O = O.substr(O.length - 20); + O = "1" + (21 - O.length >= 0 ? padstr[ "7"].substr(0,21 - O.length) : "") + O; + } else { + Vnum = (-Vnum) % 1e16; + var d1/*:Array*/ = [1,8,4,4,6,7,4,4,0,7,3,7,0,9,5,5,1,6,1,6]; + var di/*:number*/ = d1.length - 1; + while(Vnum > 0) { + if((d1[di] -= (Vnum % 10)) < 0) { d1[di] += 10; d1[di-1]--; } + --di; Vnum = Math.floor(Vnum / 10); + } + O = d1.join(""); + } + } else { + if(radix === -16) O = Vnum.toString(16).toLowerCase(); + else if(radix === 16) O = Vnum.toString(16).toUpperCase(); + else O = Vnum.toString(radix); + } + + /* apply precision */ + if(prec ===0 && O == "0" && !(radix == 8 && alt)) O = ""; /* bail out */ + else { + if(O.length < prec + (O.substr(0,1) == "-" ? 1 : 0)) { + if(O.substr(0,1) != "-") O = (prec - O.length >= 0 ? padstr[ "0"].substr(0,prec - O.length) : "") + O; + else O = O.substr(0,1) + (prec + 1 - O.length >= 0 ? padstr[ "0"].substr(0,prec + 1 - O.length) : "") + O.substr(1); + } + + /* add prefix for # form */ + if(!sign && alt && Vnum !== 0) switch(radix) { + case -16: O = "0x" + O; break; + case 16: O = "0X" + O; break; + case 8: if(O.charAt(0) != "0") O = "0" + O; break; + case 2: O = "0b" + O; break; + } + } + + /* add sign character */ + if(sign && O.charAt(0) != "-") { + if(flags.indexOf("+") > -1) O = "+" + O; + else if(flags.indexOf(" ") > -1) O = " " + O; + } + /* width */ + if(width > 0) { + if(O.length < width) { + if(flags.indexOf("-") > -1) { + O = O + ((width - O.length) >= 0 ? padstr[ " "].substr(0,(width - O.length)) : ""); + } else if(flags.indexOf("0") > -1 && prec < 0 && O.length > 0) { + if(prec > O.length) O = ((prec - O.length) >= 0 ? padstr[ "0"].substr(0,(prec - O.length)) : "") + O; + pad = ((width - O.length) >= 0 ? padstr[ (prec > 0 ? " " : "0")].substr(0,(width - O.length)) : ""); + if(O.charCodeAt(0) < 48) { + if(O.charAt(2).toLowerCase() == "x") O = O.substr(0,3) + pad + O.substring(3); + else O = O.substr(0,1) + pad + O.substring(1); + } + else if(O.charAt(1).toLowerCase() == "x") O = O.substr(0,2) + pad + O.substring(2); + else O = pad + O; + } else { + O = ((width - O.length) >= 0 ? padstr[ " "].substr(0,(width - O.length)) : "") + O; + } + } + } + + } else if(isnum > 0) { + + Vnum = Number(arg); + if(arg === null) Vnum = 0/0; + if(len == "L") bytes = 12; + var isf/*:boolean*/ = isFinite(Vnum); + if(!isf) { /* Infinity or NaN */ + if(Vnum < 0) O = "-"; + else if(flags.indexOf("+") > -1) O = "+"; + else if(flags.indexOf(" ") > -1) O = " "; + O += (isNaN(Vnum)) ? "nan" : "inf"; + } else { + var E/*:number*/ = 0; + + if(prec == -1 && isnum != 4) prec = 6; + + /* g/G conditional behavior */ + if(isnum == 3) { + O = Vnum.toExponential(1); + E = +O.substr(O.indexOf("e") + 1); + if(prec === 0) prec = 1; + if(prec > E && E >= -4) { isnum = (11); prec = prec -(E + 1); } + else { isnum = (12); prec = prec - 1; } + } + + /* sign: workaround for negative zero */ + var sg/*:string*/ = (Vnum < 0 || 1/Vnum == -Infinity) ? "-" : ""; + if(Vnum < 0) Vnum = -Vnum; + + switch(isnum) { + /* f/F standard */ + case 1: case 11: + if(Vnum < 1e21) { + O = Vnum.toFixed(prec); + if(isnum == 1) { if(prec===0 &&alt&& O.indexOf(".")==-1) O+="."; } + else if(!alt) O=O.replace(/(\.\d*[1-9])0*$/,"$1").replace(/\.0*$/,""); + else if(O.indexOf(".") == -1) O+= "."; + break; + } + O = Vnum.toExponential(20); + E = +O.substr(O.indexOf("e")+1); + O = O.charAt(0) + O.substr(2,O.indexOf("e")-2); + O = O + (E - O.length + 1 >= 0 ? padstr[ "0"].substr(0,E - O.length + 1) : ""); + if(alt || (prec > 0 && isnum !== 11)) O = O + "." + (prec >= 0 ? padstr[ "0"].substr(0,prec) : ""); + break; + + /* e/E exponential */ + case 2: case 12: + O = Vnum.toExponential(prec); + E = O.indexOf("e"); + if(O.length - E === 3) O = O.substr(0, E+2) + "0" + O.substr(E+2); + if(alt && O.indexOf(".") == -1) O = O.substr(0,E) +"."+ O.substr(E); + else if(!alt && isnum == 12) O = O.replace(/\.0*e/, "e").replace(/\.(\d*[1-9])0*e/, ".$1e"); + break; + + /* a/A hex */ + case 4: + if(Vnum===0){O= "0x0"+((alt||prec>0)?"."+(prec >= 0 ? padstr["0"].substr(0,prec) : ""):"")+"p+0"; break;} + O = Vnum.toString(16); + /* First char 0-9 */ + var ac/*:number*/ = O.charCodeAt(0); + if(ac == 48) { + ac = 2; E = -4; Vnum *= 16; + while(O.charCodeAt(ac++) == 48) { E -= 4; Vnum *= 16; } + O = Vnum.toString(16); + ac = O.charCodeAt(0); + } + + var ai/*:number*/ = O.indexOf("."); + if(O.indexOf("(") > -1) { + /* IE exponential form */ + var am/*:?Array*/ = O.match(/\(e(.*)\)/); + var ae/*:number*/ = am ? (+am[1]) : 0; + E += 4 * ae; Vnum /= Math.pow(16, ae); + } else if(ai > 1) { + E += 4 * (ai - 1); Vnum /= Math.pow(16, ai - 1); + } else if(ai == -1) { + E += 4 * (O.length - 1); Vnum /= Math.pow(16, O.length - 1); + } + + /* at this point 1 <= Vnum < 16 */ + + if(bytes > 8) { + if(ac < 50) { E -= 3; Vnum *= 8; } + else if(ac < 52) { E -= 2; Vnum *= 4; } + else if(ac < 56) { E -= 1; Vnum *= 2; } + /* at this point 8 <= Vnum < 16 */ + } else { + if(ac >= 56) { E += 3; Vnum /= 8; } + else if(ac >= 52) { E += 2; Vnum /= 4; } + else if(ac >= 50) { E += 1; Vnum /= 2; } + /* at this point 1 <= Vnum < 2 */ + } + + O = Vnum.toString(16); + if(O.length > 1) { + if(O.length > prec+2 && O.charCodeAt(prec+2) >= 56) { + var _f/*:boolean*/ = O.charCodeAt(0) == 102; + O = (Vnum + 8 * Math.pow(16, -prec-1)).toString(16); + if(_f && O.charCodeAt(0) == 49) E += 4; + } + if(prec > 0) { + O = O.substr(0, prec + 2); + if(O.length < prec + 2) { + if(O.charCodeAt(0) < 48) O = O.charAt(0) + ((prec + 2 - O.length) >= 0 ? padstr[ "0"].substr(0,(prec + 2 - O.length)) : "") + O.substr(1); + else O += ((prec + 2 - O.length) >= 0 ? padstr[ "0"].substr(0,(prec + 2 - O.length)) : ""); + } + } else if(prec === 0) O = O.charAt(0) + (alt ? "." : ""); + } else if(prec > 0) O = O + "." + (prec >= 0 ? padstr["0"].substr(0,prec) : ""); + else if(alt) O = O + "."; + O = "0x" + O + "p" + (E>=0 ? "+" + E : E); + break; + } + + if(sg === "") { + if(flags.indexOf("+") > -1) sg = "+"; + else if(flags.indexOf(" ") > -1) sg = " "; + } + + O = sg + O; + } + + /* width */ + if(width > O.length) { + if(flags.indexOf("-") > -1) { + O = O + ((width - O.length) >= 0 ? padstr[ " "].substr(0,(width - O.length)) : ""); + } else if(flags.indexOf("0") > -1 && O.length > 0 && isf) { + pad = ((width - O.length) >= 0 ? padstr[ "0"].substr(0,(width - O.length)) : ""); + if(O.charCodeAt(0) < 48) { + if(O.charAt(2).toLowerCase() == "x") O = O.substr(0,3) + pad + O.substring(3); + else O = O.substr(0,1) + pad + O.substring(1); + } + else if(O.charAt(1).toLowerCase() == "x") O = O.substr(0,2) + pad + O.substring(2); + else O = pad + O; + } else { + O = ((width - O.length) >= 0 ? padstr[ " "].substr(0,(width - O.length)) : "") + O; + } + } + if(c < 96) O = O.toUpperCase(); + + } + + o.push(O); + } + return o.join(""); +} + +function vsprintf(fmt/*:string*/, args/*:Args*/)/*:string*/ { return doit(tokenize(fmt), args); } + +function sprintf(/*:: ...argz*/)/*:string*/ { + var args/*:Array*/ = new Array(arguments.length - 1); + for(var i/*:number*/ = 0; i < args.length; ++i) args[i] = arguments[i+1]; + return doit(tokenize(arguments[0]), args); +} + +export { sprintf, vsprintf }; + diff --git a/package.json b/package.json index 34f019f..381c90d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "printj", - "version": "1.1.2", + "version": "1.2.0", "author": "sheetjs", "description": "Pure-JS printf", "keywords": [ @@ -49,7 +49,7 @@ ] }, "homepage": "http://sheetjs.com/opensource", - "files": ["printj.js", "bin/printj.njs", "LICENSE", "README.md", "dist/*.js", "dist/*.map", "dist/LICENSE", "types/index.d.ts", "types/*.json"], + "files": ["printj.js", "printj.mjs", "bin/printj.njs", "LICENSE", "README.md", "dist/*.js", "dist/*.map", "dist/LICENSE", "types/index.d.ts", "types/*.json"], "bugs": { "url": "https://github.com/SheetJS/printj/issues" }, diff --git a/printj.flow.js b/printj.flow.js index 8f0694c..f912739 100644 --- a/printj.flow.js +++ b/printj.flow.js @@ -4,6 +4,7 @@ /*exported PRINTJ */ /*:: declare var DO_NOT_EXPORT_PRINTJ:?boolean; */ /*:: declare function define(cb:()=>any):void; */ + var PRINTJ/*:PRINTJModule*/; (function (factory/*:(a:any)=>void*/)/*:void*/ { /*jshint ignore:start */ @@ -27,7 +28,7 @@ var PRINTJ/*:PRINTJModule*/; /*jshint ignore:end */ }(function(PRINTJ/*:PRINTJModule*/) { -PRINTJ.version = '1.1.2'; +PRINTJ.version = '1.2.0'; function tokenize(fmt/*:string*/)/*:ParsedFmt*/ { var out/*:ParsedFmt*/ = []; @@ -311,7 +312,7 @@ function doit(t/*:ParsedFmt*/, args/*:Array*/)/*:string*/ { /* boolean (extension) */ case /*Y*/ 89: case /*y*/ 121: - O = Boolean(arg) ? (alt ? "yes" : "true") : (alt ? "no" : "false"); + O = (arg) ? (alt ? "yes" : "true") : (alt ? "no" : "false"); if(c == /*Y*/ 89) O = O.toUpperCase(); if( prec >= 0) O = O.substr(0, prec); if( width > O.length || - width > O.length) { if(( flags.indexOf("-") == -1 || width < 0) && flags.indexOf("0") != -1) { pad = ( width - O.length >= 0 ? padstr["0"].substr(0, width - O.length) : ""); O = pad + O; } else { pad = ( width - O.length >= 0 ? padstr[" "].substr(0, width - O.length) : ""); O = flags.indexOf("-") > -1 ? O + pad : pad + O; } } diff --git a/printj.js b/printj.js index a2a38d6..f01f0bf 100644 --- a/printj.js +++ b/printj.js @@ -25,7 +25,7 @@ var PRINTJ; /*jshint ignore:end */ }(function(PRINTJ) { -PRINTJ.version = '1.1.2'; +PRINTJ.version = '1.2.0'; function tokenize(fmt) { var out = []; @@ -308,7 +308,7 @@ function doit(t, args) { /* boolean (extension) */ case /*Y*/ 89: case /*y*/ 121: - O = Boolean(arg) ? (alt ? "yes" : "true") : (alt ? "no" : "false"); + O = (arg) ? (alt ? "yes" : "true") : (alt ? "no" : "false"); if(c == /*Y*/ 89) O = O.toUpperCase(); if( prec >= 0) O = O.substr(0, prec); if( width > O.length || - width > O.length) { if(( flags.indexOf("-") == -1 || width < 0) && flags.indexOf("0") != -1) { pad = ( width - O.length >= 0 ? padstr["0"].substr(0, width - O.length) : ""); O = pad + O; } else { pad = ( width - O.length >= 0 ? padstr[" "].substr(0, width - O.length) : ""); O = flags.indexOf("-") > -1 ? O + pad : pad + O; } } diff --git a/printj.mjs b/printj.mjs new file mode 100644 index 0000000..decfc8c --- /dev/null +++ b/printj.mjs @@ -0,0 +1,582 @@ +/* printj.js (C) 2016-present SheetJS -- http://sheetjs.com */ +/* vim: set ts=2: */ +/*jshint sub:true, eqnull:true */ +/*exported PRINTJ */ +/*:: declare var DO_NOT_EXPORT_PRINTJ:?boolean; */ +/*:: declare function define(cb:()=>any):void; */ + +var PRINTJ/*:PRINTJModule*/ = /*::(*/{}/*:: :any)*/; + +PRINTJ.version = '1.2.0'; + +export const version = PRINTJ.version; + +function tokenize(fmt/*:string*/)/*:ParsedFmt*/ { + var out/*:ParsedFmt*/ = []; + var start/*:number*/ = 0; + + var i/*:number*/ = 0; + var infmt/*:boolean*/ = false; + var fmtparam/*:string*/ = "", fmtflags/*:string*/ = "", fmtwidth/*:string*/ = "", fmtprec/*:string*/ = "", fmtlen/*:string*/ = ""; + + var c/*:number*/ = 0; + + var L/*:number*/ = fmt.length; + + for(; i < L; ++i) { + c = fmt.charCodeAt(i); + if(!infmt) { + + if(c !== 37) continue; + + if(start < i) out.push(["L", fmt.substring(start, i)]); + start = i; + infmt = true; + continue; + } + + if(c >= 48 && c < 58) { + if(fmtprec.length) fmtprec += String.fromCharCode(c); + else if(c == 48 && !fmtwidth.length) fmtflags += String.fromCharCode(c); + else fmtwidth += String.fromCharCode(c); + } else switch(c) { + /* positional */ + case 36: + if(fmtprec.length) fmtprec += "$"; + else if(fmtwidth.charAt(0) == "*") fmtwidth += "$"; + else { fmtparam = fmtwidth + "$"; fmtwidth = ""; } + break; + + /* flags */ + case 39: fmtflags += "'"; break; + case 45: fmtflags += "-"; break; + case 43: fmtflags += "+"; break; + case 32: fmtflags += " "; break; + case 35: fmtflags += "#"; break; + + /* width and precision */ + case 46: fmtprec = "."; break; + case 42: + if(fmtprec.charAt(0) == ".") fmtprec += "*"; + else fmtwidth += "*"; + break; + + /* length */ + case 104: + case 108: + if(fmtlen.length > 1) throw "bad length " + fmtlen + String(c); + fmtlen += String.fromCharCode(c); + break; + + case 76: + case 106: + case 122: + case 116: + case 113: + case 90: + case 119: + if(fmtlen !== "") throw "bad length " + fmtlen + String.fromCharCode(c); + fmtlen = String.fromCharCode(c); + break; + + case 73: + if(fmtlen !== "") throw "bad length " + fmtlen + 'I'; + fmtlen = 'I'; + break; + + /* conversion */ + case 100: + case 105: + case 111: + case 117: + case 120: + case 88: + case 102: + case 70: + case 101: + case 69: + case 103: + case 71: + case 97: + case 65: + case 99: + case 67: + case 115: + case 83: + case 112: + case 110: + case 68: + case 85: + case 79: + case 109: + case 98: + case 66: + case 121: + case 89: + case 74: + case 86: + case 84: + case 37: + infmt = false; + if(fmtprec.length > 1) fmtprec = fmtprec.substr(1); + out.push([String.fromCharCode(c), fmt.substring(start, i+1), fmtparam, fmtflags, fmtwidth, fmtprec, fmtlen]); + start = i+1; + fmtlen = fmtprec = fmtwidth = fmtflags = fmtparam = ""; + break; + default: + throw new Error("Invalid format string starting with |" + fmt.substring(start, i+1) + "|"); + } + + } + + if(start < fmt.length) out.push(["L", fmt.substring(start)]); + return out; +} + +//#define PAD_(x,c) (x >= 0 ? new Array(((x)|0) + 1).join((c)) : "") +var padstr/*:{[s:string]:string}*/ = { + " ": " ", + "0": "000000000000000000000000000000000", + "7": "777777777777777777777777777777777", + "f": "fffffffffffffffffffffffffffffffff" +}; + +var u_inspect/*:(o:any)=>string*/ = JSON.stringify; + +function doit(t/*:ParsedFmt*/, args/*:Array*/)/*:string*/ { + var o/*:Array*/ = []; + var argidx/*:number*/ = 0, idx/*:number*/ = 0; + var Vnum/*:number*/ = 0; + var pad/*:string*/ = ""; + for(var i/*:number*/ = 0; i < t.length; ++i) { + var m/*:ParsedEntry*/ = t[i], c/*:number*/ = (m[0]/*:string*/).charCodeAt(0); + /* m order: conv full param flags width prec length */ + + if(c === /*L*/ 76) { o.push(m[1]); continue; } + if(c === /*%*/ 37) { o.push("%"); continue; } + + var O/*:string*/ = ""; + var isnum/*:number*/ = 0, radix/*:number*/ = 10, bytes/*:number*/ = 4, sign/*:boolean*/ = false; + + /* flags */ + var flags/*:string*/ = m[3]||""; + var alt/*:boolean*/ = flags.indexOf("#") > -1; + + /* position */ + if(m[2]) argidx = parseInt(m[2])-1; + /* %m special case */ + else if(c === /*m*/ 109 && !alt) { o.push("Success"); continue; } + + /* grab width */ + var width = 0; if(m[ 4] != null && m[ 4].length > 0) { if(m[ 4].charAt(0) !== '*') width = parseInt(m[ 4], 10); else if(m[ 4].length === 1) width = args[idx++]; else width = args[parseInt(m[ 4].substr(1), 10)-1]; } + + /* grab precision */ + var prec = -1; if(m[ 5] != null && m[ 5].length > 0) { if(m[ 5].charAt(0) !== '*') prec = parseInt(m[ 5], 10); else if(m[ 5].length === 1) prec = args[idx++]; else prec = args[parseInt(m[ 5].substr(1), 10)-1]; } + + /* position not specified */ + if(!m[2]) argidx = idx++; + + /* grab argument */ + var arg/*:any*/ = args[argidx]; + + /* grab length */ + var len/*:string*/ = m[6] || ""; + + switch(c) { + /* str cCsS */ + + case /*S*/ 83: + case /*s*/ 115: + /* only valid flag is "-" for left justification */ + O = String(arg); + if( prec >= 0) O = O.substr(0, prec); + if( width > O.length || - width > O.length) { if(( flags.indexOf("-") == -1 || width < 0) && flags.indexOf("0") != -1) { pad = ( width - O.length >= 0 ? padstr["0"].substr(0, width - O.length) : ""); O = pad + O; } else { pad = ( width - O.length >= 0 ? padstr[" "].substr(0, width - O.length) : ""); O = flags.indexOf("-") > -1 ? O + pad : pad + O; } } + break; + + /* first char of string or convert */ + case /*C*/ 67: + case /*c*/ 99: + switch(typeof arg) { + case "number": + var cc/*:number*/ = arg; + if(c == 67 || len.charCodeAt(0) === /*l*/ 108) { cc &= 0xFFFFFFFF; O = String.fromCharCode( cc); } + else { cc &= 0xFF; O = String.fromCharCode( cc); } + break; + case "string": O = /*::(*/arg/*:: :string)*/.charAt(0); break; + default: O = String(arg).charAt(0); + } + if( width > O.length || - width > O.length) { if(( flags.indexOf("-") == -1 || width < 0) && flags.indexOf("0") != -1) { pad = ( width - O.length >= 0 ? padstr["0"].substr(0, width - O.length) : ""); O = pad + O; } else { pad = ( width - O.length >= 0 ? padstr[" "].substr(0, width - O.length) : ""); O = flags.indexOf("-") > -1 ? O + pad : pad + O; } } + break; + + /* int diDuUoOxXbB */ + + /* signed integer */ + case /*D*/ 68: bytes = 8; + /* falls through */ + case /*d*/ 100: + case /*i*/ 105: isnum = -1; sign = true; break; + + /* unsigned integer */ + case /*U*/ 85: bytes = 8; + /* falls through */ + case /*u*/ 117: isnum = -1; break; + + /* unsigned octal */ + case /*O*/ 79: bytes = 8; + /* falls through */ + case /*o*/ 111: isnum = -1; radix = (8); break; + + /* unsigned hex */ + case /*x*/ 120: isnum = -1; radix = (-16); break; + case /*X*/ 88: isnum = -1; radix = (16); break; + + /* unsigned binary (extension) */ + case /*B*/ 66: bytes = 8; + /* falls through */ + case /*b*/ 98: isnum = -1; radix = (2); break; + + /* flt fegFEGaA */ + + /* floating point logic */ + case /*F*/ 70: + case /*f*/ 102: isnum = (1); break; + + case /*E*/ 69: + case /*e*/ 101: isnum = (2); break; + + case /*G*/ 71: + case /*g*/ 103: isnum = (3); break; + + /* floating hex */ + case /*A*/ 65: + case /*a*/ 97: isnum = (4); break; + + /* misc pnmJVTyY */ + + /* JS has no concept of pointers so interpret the `l` key as an address */ + case /*p*/ 112: + Vnum = typeof arg == "number" ? arg : arg ? Number(arg.l) : -1; + if(isNaN(Vnum)) Vnum = -1; + if(alt) O = Vnum.toString(10); + else { + Vnum = Math.abs(Vnum); + O = "0x" + Vnum.toString(16).toLowerCase(); + } + break; + + /* store length in the `len` key */ + case /*n*/ 110: + if(arg) { arg.len=0; for(var oo/*:number*/ = 0; oo < o.length; ++oo) arg.len += o[oo].length; } + continue; + + /* process error */ + case /*m*/ 109: + if(!(arg instanceof Error)) O = "Success"; + else if(arg.message) O = arg.message; + else if(arg.errno) O = "Error number " + arg.errno; + else O = "Error " + String(arg); + break; + + /* JS-specific conversions (extension) */ + case /*J*/ 74: O = (alt ? u_inspect : JSON.stringify)(arg); break; + case /*V*/ 86: O = arg == null ? "null" : String(arg.valueOf()); break; + case /*T*/ 84: + if(alt) { /* from '[object %s]' extract %s */ + O = Object.prototype.toString.call(arg).substr(8); + O = O.substr(0, O.length - 1); + } else O = typeof arg; + break; + + /* boolean (extension) */ + case /*Y*/ 89: + case /*y*/ 121: + O = (arg) ? (alt ? "yes" : "true") : (alt ? "no" : "false"); + if(c == /*Y*/ 89) O = O.toUpperCase(); + if( prec >= 0) O = O.substr(0, prec); + if( width > O.length || - width > O.length) { if(( flags.indexOf("-") == -1 || width < 0) && flags.indexOf("0") != -1) { pad = ( width - O.length >= 0 ? padstr["0"].substr(0, width - O.length) : ""); O = pad + O; } else { pad = ( width - O.length >= 0 ? padstr[" "].substr(0, width - O.length) : ""); O = flags.indexOf("-") > -1 ? O + pad : pad + O; } } + break; + + } + + if(width < 0) { width = -width; flags += "-"; } + + if(isnum == -1) { + + Vnum = Number(arg); + + /* parse byte length field */ + + switch(len) { + /* char */ + case "hh": { bytes = 1; } break; + /* short */ + case "h": { bytes = 2; } break; + + /* long */ + case "l": { if(bytes == 4) bytes = 8; } break; + + /* long long */ + case "L": + case "q": + case "ll": { if(bytes == 4) bytes = 8; } break; + + /* intmax_t */ + case "j": { if(bytes == 4) bytes = 8; } break; + + /* ptrdiff_t */ + case "t": { if(bytes == 4) bytes = 8; } break; + + /* size_t */ + case "z": + case "Z": { if(bytes == 4) bytes = 8; } break; + + /* CRT size_t or ptrdiff_t */ + case "I": + + { if(bytes == 4) bytes = 8; } + + break; + + /* CRT wchar_t */ + case "w": break; + } + + /* restrict value */ + + switch(bytes) { + case 1: Vnum = (Vnum & 0xFF); if(sign && (Vnum > 0x7F)) Vnum -= (0xFF + 1); break; + case 2: Vnum = (Vnum & 0xFFFF); if(sign && (Vnum > 0x7FFF)) Vnum -= (0xFFFF + 1); break; + case 4: Vnum = sign ? (Vnum | 0) : (Vnum >>> 0); break; + default: Vnum = isNaN(Vnum) ? 0 : Math.round(Vnum); break; + } + + /* generate string */ + if(bytes > 4 && Vnum < 0 && !sign) { + if(radix == 16 || radix == -16) { + O = (Vnum>>>0).toString(16); + Vnum = Math.floor((Vnum - (Vnum >>> 0)) / Math.pow(2,32)); + O = (Vnum>>>0).toString(16) + (8 - O.length >= 0 ? padstr[ "0"].substr(0,8 - O.length) : "") + O; + O = (16 - O.length >= 0 ? padstr[ "f"].substr(0,16 - O.length) : "") + O; + if(radix == 16) O = O.toUpperCase(); + } else if(radix == 8) { + O = (Vnum>>>0).toString(8); + O = (10 - O.length >= 0 ? padstr[ "0"].substr(0,10 - O.length) : "") + O; + Vnum = Math.floor((Vnum - ((Vnum >>> 0)&0x3FFFFFFF)) / Math.pow(2,30)); + O = (Vnum>>>0).toString(8) + O.substr(O.length - 10); + O = O.substr(O.length - 20); + O = "1" + (21 - O.length >= 0 ? padstr[ "7"].substr(0,21 - O.length) : "") + O; + } else { + Vnum = (-Vnum) % 1e16; + var d1/*:Array*/ = [1,8,4,4,6,7,4,4,0,7,3,7,0,9,5,5,1,6,1,6]; + var di/*:number*/ = d1.length - 1; + while(Vnum > 0) { + if((d1[di] -= (Vnum % 10)) < 0) { d1[di] += 10; d1[di-1]--; } + --di; Vnum = Math.floor(Vnum / 10); + } + O = d1.join(""); + } + } else { + if(radix === -16) O = Vnum.toString(16).toLowerCase(); + else if(radix === 16) O = Vnum.toString(16).toUpperCase(); + else O = Vnum.toString(radix); + } + + /* apply precision */ + if(prec ===0 && O == "0" && !(radix == 8 && alt)) O = ""; /* bail out */ + else { + if(O.length < prec + (O.substr(0,1) == "-" ? 1 : 0)) { + if(O.substr(0,1) != "-") O = (prec - O.length >= 0 ? padstr[ "0"].substr(0,prec - O.length) : "") + O; + else O = O.substr(0,1) + (prec + 1 - O.length >= 0 ? padstr[ "0"].substr(0,prec + 1 - O.length) : "") + O.substr(1); + } + + /* add prefix for # form */ + if(!sign && alt && Vnum !== 0) switch(radix) { + case -16: O = "0x" + O; break; + case 16: O = "0X" + O; break; + case 8: if(O.charAt(0) != "0") O = "0" + O; break; + case 2: O = "0b" + O; break; + } + } + + /* add sign character */ + if(sign && O.charAt(0) != "-") { + if(flags.indexOf("+") > -1) O = "+" + O; + else if(flags.indexOf(" ") > -1) O = " " + O; + } + /* width */ + if(width > 0) { + if(O.length < width) { + if(flags.indexOf("-") > -1) { + O = O + ((width - O.length) >= 0 ? padstr[ " "].substr(0,(width - O.length)) : ""); + } else if(flags.indexOf("0") > -1 && prec < 0 && O.length > 0) { + if(prec > O.length) O = ((prec - O.length) >= 0 ? padstr[ "0"].substr(0,(prec - O.length)) : "") + O; + pad = ((width - O.length) >= 0 ? padstr[ (prec > 0 ? " " : "0")].substr(0,(width - O.length)) : ""); + if(O.charCodeAt(0) < 48) { + if(O.charAt(2).toLowerCase() == "x") O = O.substr(0,3) + pad + O.substring(3); + else O = O.substr(0,1) + pad + O.substring(1); + } + else if(O.charAt(1).toLowerCase() == "x") O = O.substr(0,2) + pad + O.substring(2); + else O = pad + O; + } else { + O = ((width - O.length) >= 0 ? padstr[ " "].substr(0,(width - O.length)) : "") + O; + } + } + } + + } else if(isnum > 0) { + + Vnum = Number(arg); + if(arg === null) Vnum = 0/0; + if(len == "L") bytes = 12; + var isf/*:boolean*/ = isFinite(Vnum); + if(!isf) { /* Infinity or NaN */ + if(Vnum < 0) O = "-"; + else if(flags.indexOf("+") > -1) O = "+"; + else if(flags.indexOf(" ") > -1) O = " "; + O += (isNaN(Vnum)) ? "nan" : "inf"; + } else { + var E/*:number*/ = 0; + + if(prec == -1 && isnum != 4) prec = 6; + + /* g/G conditional behavior */ + if(isnum == 3) { + O = Vnum.toExponential(1); + E = +O.substr(O.indexOf("e") + 1); + if(prec === 0) prec = 1; + if(prec > E && E >= -4) { isnum = (11); prec = prec -(E + 1); } + else { isnum = (12); prec = prec - 1; } + } + + /* sign: workaround for negative zero */ + var sg/*:string*/ = (Vnum < 0 || 1/Vnum == -Infinity) ? "-" : ""; + if(Vnum < 0) Vnum = -Vnum; + + switch(isnum) { + /* f/F standard */ + case 1: case 11: + if(Vnum < 1e21) { + O = Vnum.toFixed(prec); + if(isnum == 1) { if(prec===0 &&alt&& O.indexOf(".")==-1) O+="."; } + else if(!alt) O=O.replace(/(\.\d*[1-9])0*$/,"$1").replace(/\.0*$/,""); + else if(O.indexOf(".") == -1) O+= "."; + break; + } + O = Vnum.toExponential(20); + E = +O.substr(O.indexOf("e")+1); + O = O.charAt(0) + O.substr(2,O.indexOf("e")-2); + O = O + (E - O.length + 1 >= 0 ? padstr[ "0"].substr(0,E - O.length + 1) : ""); + if(alt || (prec > 0 && isnum !== 11)) O = O + "." + (prec >= 0 ? padstr[ "0"].substr(0,prec) : ""); + break; + + /* e/E exponential */ + case 2: case 12: + O = Vnum.toExponential(prec); + E = O.indexOf("e"); + if(O.length - E === 3) O = O.substr(0, E+2) + "0" + O.substr(E+2); + if(alt && O.indexOf(".") == -1) O = O.substr(0,E) +"."+ O.substr(E); + else if(!alt && isnum == 12) O = O.replace(/\.0*e/, "e").replace(/\.(\d*[1-9])0*e/, ".$1e"); + break; + + /* a/A hex */ + case 4: + if(Vnum===0){O= "0x0"+((alt||prec>0)?"."+(prec >= 0 ? padstr["0"].substr(0,prec) : ""):"")+"p+0"; break;} + O = Vnum.toString(16); + /* First char 0-9 */ + var ac/*:number*/ = O.charCodeAt(0); + if(ac == 48) { + ac = 2; E = -4; Vnum *= 16; + while(O.charCodeAt(ac++) == 48) { E -= 4; Vnum *= 16; } + O = Vnum.toString(16); + ac = O.charCodeAt(0); + } + + var ai/*:number*/ = O.indexOf("."); + if(O.indexOf("(") > -1) { + /* IE exponential form */ + var am/*:?Array*/ = O.match(/\(e(.*)\)/); + var ae/*:number*/ = am ? (+am[1]) : 0; + E += 4 * ae; Vnum /= Math.pow(16, ae); + } else if(ai > 1) { + E += 4 * (ai - 1); Vnum /= Math.pow(16, ai - 1); + } else if(ai == -1) { + E += 4 * (O.length - 1); Vnum /= Math.pow(16, O.length - 1); + } + + /* at this point 1 <= Vnum < 16 */ + + if(bytes > 8) { + if(ac < 50) { E -= 3; Vnum *= 8; } + else if(ac < 52) { E -= 2; Vnum *= 4; } + else if(ac < 56) { E -= 1; Vnum *= 2; } + /* at this point 8 <= Vnum < 16 */ + } else { + if(ac >= 56) { E += 3; Vnum /= 8; } + else if(ac >= 52) { E += 2; Vnum /= 4; } + else if(ac >= 50) { E += 1; Vnum /= 2; } + /* at this point 1 <= Vnum < 2 */ + } + + O = Vnum.toString(16); + if(O.length > 1) { + if(O.length > prec+2 && O.charCodeAt(prec+2) >= 56) { + var _f/*:boolean*/ = O.charCodeAt(0) == 102; + O = (Vnum + 8 * Math.pow(16, -prec-1)).toString(16); + if(_f && O.charCodeAt(0) == 49) E += 4; + } + if(prec > 0) { + O = O.substr(0, prec + 2); + if(O.length < prec + 2) { + if(O.charCodeAt(0) < 48) O = O.charAt(0) + ((prec + 2 - O.length) >= 0 ? padstr[ "0"].substr(0,(prec + 2 - O.length)) : "") + O.substr(1); + else O += ((prec + 2 - O.length) >= 0 ? padstr[ "0"].substr(0,(prec + 2 - O.length)) : ""); + } + } else if(prec === 0) O = O.charAt(0) + (alt ? "." : ""); + } else if(prec > 0) O = O + "." + (prec >= 0 ? padstr["0"].substr(0,prec) : ""); + else if(alt) O = O + "."; + O = "0x" + O + "p" + (E>=0 ? "+" + E : E); + break; + } + + if(sg === "") { + if(flags.indexOf("+") > -1) sg = "+"; + else if(flags.indexOf(" ") > -1) sg = " "; + } + + O = sg + O; + } + + /* width */ + if(width > O.length) { + if(flags.indexOf("-") > -1) { + O = O + ((width - O.length) >= 0 ? padstr[ " "].substr(0,(width - O.length)) : ""); + } else if(flags.indexOf("0") > -1 && O.length > 0 && isf) { + pad = ((width - O.length) >= 0 ? padstr[ "0"].substr(0,(width - O.length)) : ""); + if(O.charCodeAt(0) < 48) { + if(O.charAt(2).toLowerCase() == "x") O = O.substr(0,3) + pad + O.substring(3); + else O = O.substr(0,1) + pad + O.substring(1); + } + else if(O.charAt(1).toLowerCase() == "x") O = O.substr(0,2) + pad + O.substring(2); + else O = pad + O; + } else { + O = ((width - O.length) >= 0 ? padstr[ " "].substr(0,(width - O.length)) : "") + O; + } + } + if(c < 96) O = O.toUpperCase(); + + } + + o.push(O); + } + return o.join(""); +} + +function vsprintf(fmt/*:string*/, args/*:Args*/)/*:string*/ { return doit(tokenize(fmt), args); } + +function sprintf(/*:: ...argz*/)/*:string*/ { + var args/*:Array*/ = new Array(arguments.length - 1); + for(var i/*:number*/ = 0; i < args.length; ++i) args[i] = arguments[i+1]; + return doit(tokenize(arguments[0]), args); +} + +export { sprintf, vsprintf }; + diff --git a/tests/mjs.html b/tests/mjs.html new file mode 100644 index 0000000..922a60f --- /dev/null +++ b/tests/mjs.html @@ -0,0 +1,70 @@ + + + + + + +PRINTJ Live Demo + + + +PRINTJ ESM Live Demo
+Source Code Repo
+Issues? Something look weird? Click here and report an issue
+
+
+usage: import { sprintf as printf } from './printj.mjs';
+
+This implementation supports the full POSIX set of conversions.  Consult the enclosed README for full details.
+
+
+ + +
exampleresult
+ + + + diff --git a/tests/node.mjs b/tests/node.mjs new file mode 100755 index 0000000..8f0b475 --- /dev/null +++ b/tests/node.mjs @@ -0,0 +1,5 @@ +#!/usr/bin/env node --experimental-modules + +import { version, sprintf } from '../printj.mjs' + +console.log(sprintf("PRINTJ version %s, 123 = 0x%02hhx", version, 123)); diff --git a/tests/printj.mjs b/tests/printj.mjs new file mode 120000 index 0000000..6a4bf14 --- /dev/null +++ b/tests/printj.mjs @@ -0,0 +1 @@ +../printj.mjs \ No newline at end of file