initial commit

This commit is contained in:
SheetJS 2016-09-19 02:33:23 -04:00
commit d217d96d5c
55 changed files with 93822 additions and 0 deletions

26
.flowconfig Normal file

@ -0,0 +1,26 @@
[ignore]
.*/node_modules/.*
.*/dist/.*
.*/test.js
.*/printj.js
.*/bits/.*
.*/ctest/.*
.*/misc/.*
.*/tests/.*
.*/stress/.*
.*/demo/browser.js
[include]
printj.flow.js
lib/loop_code.js
lib/loop_char.js
lib/index_code.js
lib/index_char.js
lib/regex.js
[libs]
bits/flow.js
[options]

3
.gitignore vendored Normal file

@ -0,0 +1,3 @@
node_modules
misc/coverage.html

6
.jscs.json Normal file

@ -0,0 +1,6 @@
{
"requireCommaBeforeLineBreak": true,
"disallowTrailingWhitespace": true,
"disallowTrailingComma": true
}

16
.travis.yml Normal file

@ -0,0 +1,16 @@
language: node_js
node_js:
- "6"
- "5"
- "4"
- "0.12"
- "0.10"
- "0.8"
before_install:
- "npm install -g npm@next"
- "npm install -g mocha"
- "npm install codepage"
- "npm install blanket"
- "npm install coveralls mocha-lcov-reporter"
after_success:
- "make coveralls-spin"

14
LICENSE Normal file

@ -0,0 +1,14 @@
Copyright (C) 2016-present SheetJS
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

80
Makefile Normal file

@ -0,0 +1,80 @@
LIB=printj
REQS=loop_code
ADDONS=
AUXTARGETS=lib/loop_char.js lib/loop_code.js lib/index_char.js lib/index_code.js lib/regex.js
HTMLLINT=index.html
ULIB=$(shell echo $(LIB) | tr a-z A-Z)
DEPS=$(sort $(wildcard bits/*.js))
TARGET=$(LIB).js
FLOWTARGET=$(LIB).flow.js
## Main Targets
.PHONY: all
all: $(TARGET) ## Build library and auxiliary scripts
.PHONY: lib
lib:
OUTDIR=$(PWD)/lib make -C bits
$(TARGET): %.js : %.flow.js
node -e 'process.stdout.write(require("fs").readFileSync("$<","utf8").replace(/^[ \t]*\/\*[:#][^*]*\*\/\s*(\n)?/gm,"").replace(/\/\*[:#][^*]*\*\//gm,""))' > $@
$(FLOWTARGET): $(DEPS) lib
cp lib/$(REQS).js $(FLOWTARGET)
bits/01_version.js: package.json
echo "PRINTJ.version = '"`grep version package.json | awk '{gsub(/[^0-9a-z\.-]/,"",$$2); print $$2}'`"';" > $@
.PHONY: clean
clean: clean-stress ## Remove targets and build artifacts
@OUTDIR=$(PWD)/lib make -C bits clean
rm -f $(TARGET) $(FLOWTARGET)
## Testing
.PHONY: test mocha
test mocha: test.js ## Run test suite
mocha -R spec -t 20000
.PHONY: stress ## Run stress tests
stress:
@make -C stress clean
@make -C stress
@make -C stress test
.PHONY: clean-stress
clean-stress: ## Remove stress tests
@make -C stress clean
## Code Checking
.PHONY: lint
lint: ## Run jshint and jscs checks
@jshint --show-non-errors $(TARGET) $(AUXTARGETS)
@jshint --show-non-errors package.json
@jshint --show-non-errors --extract=always $(HTMLLINT)
@jscs lib/*.js
.PHONY: flow
flow: lint ## Run flow checker
@flow check --all --show-all-errors
.PHONY: cov
cov: misc/coverage.html ## Run coverage test
misc/coverage.html: $(TARGET) test.js
mocha --require blanket -R html-cov -t 20000 > $@
.PHONY: coveralls
coveralls: ## Coverage Test + Send to coveralls.io
mocha --require blanket --reporter mocha-lcov-reporter -t 20000 | node ./node_modules/coveralls/bin/coveralls.js
.PHONY: help
help:
@grep -hE '(^[a-zA-Z_-][ a-zA-Z_-]*:.*?|^#[#*])' $(MAKEFILE_LIST) | bash misc/help.sh
#* To show a spinner, append "-spin" to any target e.g. cov-spin
%-spin:
@make $* & bash misc/spin.sh $$!

976
README.md Normal file

@ -0,0 +1,976 @@
# printj
Extended `sprintf` implementation (for the browser and nodejs). Emphasis on
compliance and performance.
```JS
PRINTJ.sprintf("Hello %s!", "World");
```
A self-contained specification of the printf format string is included below in [this README](#printf-format-string-specification), as well as a summary of the
[support against various printf implementations](#support-summary)
## Installation
With [npm](https://www.npmjs.org/package/printj):
$ npm install printj
In the browser:
<script src="printj.js"></script>
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`
## Usage
In all cases, the relevant function takes a format and arguments to be rendered.
The return value is a JS string.
- `PRINTJ.sprintf(format, ...args)` assumes the arguments are passed directly
- `PRINTJ.vsprintf(format, argv)` assumes the arguments are passed in an array
For example:
```js
> // var PRINTJ = require('printj'); // uncomment this line if in node
> var sprintf = PRINTJ.sprintf, vsprintf = PRINTJ.vsprintf;
> sprintf("Hello %s", "SheetJS") // 'Hello SheetJS'
> sprintf("%d + %d = %d", 2,3,2+3) // '2 + 3 = 5'
> vsprintf("%d + %d = %d", [2,3,5]) // '2 + 3 = 5'
> sprintf("%1$02hhx %1$u %1$i %1$o", -69) // 'bb 4294967227 -69 37777777673'
```
The command line script takes a format and arguments:
```
usage: printj [options] <format> [args...]
Options:
-h, --help output usage information
-d, --dump print debug information about format string
Arguments are treated as strings unless prefaced by a type indicator:
n:<integer> call parseInt (ex. n:3 -> 3)
f:<float> call parseFloat (ex. f:3.1 -> 3.1)
b:<boolean> false when lowercase value is "FALSE" or "0", else true
s:<string> interpret as string (ex. s:n:3 -> "n:3")
j:<JSON> interpret as an object using JSON.parse
e:<JS> evaluate argument (ex. e:1+1 -> 2, e:"1"+1 -> "11")
samples:
$ printj '|%02hhx%d|' n:50 e:0x7B # |32123|
$ printj '|%2$d + %3$d is %1$d|' e:1+2 n:1 n:2 # |1 + 2 is 3|
$ printj '|%s is %s|' s:1+2 e:1+2 # |1+2 is 3|
$ printj '|%c %c|' s:69 n:69 # |6 E|
```
## Testing
`make test` will run the nodejs-based test.
`make stress` will run a larger test encompassing every possible conversion. It
requires access to a C compiler.
## License
Please consult the attached LICENSE file for details. All rights not explicitly
granted by the Apache 2.0 license are reserved by the Original Author.
## Badges
[![Build Status](https://travis-ci.org/SheetJS/printj.svg?branch=master)](https://travis-ci.org/SheetJS/printj)
[![Coverage Status](http://img.shields.io/coveralls/SheetJS/printj/master.svg)](https://coveralls.io/r/SheetJS/printj?branch=master)
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/printj?pixel)](https://github.com/SheetJS/printj)
# printf format string specification
The `printf` family of functions attempt to generate and output a string of
characters from a series of arguments, following a user-supplied "format string"
specification. The format string contains normal characters that are written to
the output string as well as specifiers that describe which parameter to insert
and how to render the parameter. This specification describes how a conformant
implementation should process the format string and generate an output string.
Any discrepancies between this document and the reference implementation are
considered bugs in the implementation.
### Original C Interface
Every function in the `printf` family follows the same logic to generate strings
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 |
|------------|------------|-----------------------|---------|------------|
| `printf` | unbounded | standard output | K&R | `wprintf` |
| `fprintf` | unbounded | stream (`FILE *`) | K&R | `fwprintf` |
| `sprintf` | unbounded | string (`char *`) | K&R | `swprintf` |
| `snprintf` | parameter | string (`char *`) | C99 | |
| `dprintf` | unbounded | POSIX file descriptor | POSIX | |
Each function has a dual function, whose name begins with `v`, that accepts the
parameters as a `va_list` rather than formal parameters. In all cases, they
return the number of characters written or a negative value to indicate error:
```C
int sprintf(char *ostr, const char *fmt, ...);
int vsprintf(char *ostr, const char *fmt, va_list arg_list);
int swprintf(wchar_t *ostr, const wchar_t *fmt, ...);
int vswprintf(wchar_t *ostr, const wchar_t *fmt, va_list arg_list);
```
### JS and C strings
C "strings" are really just arrays of numbers. An external code page (such as
ASCII) maps those numbers to characters. K&R defines two types of strings:
basic character set strings (`char *`) and extended character set strings
(`wchar_t *`). In contrast, JS has a true string value type.
Unlike in C, JS strings do not treat the null character as an end-of-string
marker. As a result, characters beyond the first null character will be used.
The JS equivalent of a C extended string would be an array of the individual
character codes. The C basic string equivalent would involve specifying a code
page and mapping back. The `codepage` JS library supports common codepages.
While capturing the essence of C strings, using arrays of character codes is not
idiomatic JS. Few developers leverage this and the downsides far exceed the
benefits of a more direct translation. The effect can be feigned, as shown in
the `js2c` code sample at the end of the document.
### JS Interface
In the absence of a standard output or even a standard concept of a stream, the
non-string outputs are irrelevant. Similarly there is no JS analogue of wide
characters. While useful, lack of direct memory management obviates `snprintf`.
This implementation exports the remaining functions, `sprintf` and `vsprintf`.
Instead of replicating the original C signature and `errno`, functions directly
return the output string and throw Errors:
```typescript
function sprintf(fmt:string, ...args):string;
function vsprintf(fmt:string, args:Array<any>):string;
```
The C functions return the number of characters written to the string, which is
directly accessible in JS via the `length` property. A direct replica of the
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
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:
```js
regex_string.replace(/#.*$/gm,"").replace(/^\s*/gm,"").replace(/\s*\n/gm,"");
```
Based on K&R, conversions originally followed the format:
- required: leading `%`
- optional: `-` (POSIX refers to this as the "flags")
- optional: positive number or `*` (POSIX "width")
- optional: period followed by positive number or `*` (POSIX "precision")
- optional: an `h` or `l` to indicate size of data (POSIX "length")
- required: character describing output behavior (POSIX "conversion specifier")
This is captured by the regular expression:
```perl
/%(?:
([-])? # flags (only minus sign)
(\d+|\*)? # width
(?:\.(\d+|\*))? # period + precision
([hl])? # length
([dioxXucsfeEgGp%]) # conversion specifier
)/x
```
Various implementations of `printf` have added different functionality.
ANSI standards up through C99:
- more flags `"+"` `" "` `"0"` `"#"`
- more lengths `"L"` `"hh"` `"ll"` `"j"` `"z"` `"t"`
- more conversions `"F"` `"a"` `"A"` `"n"`
The POSIX specification of `printf` added:
- positional parameters to identify argument indices
- more flags `"'"`
- more conversions `"C"` `"S"`
- clarifications regarding corner cases and "undefined behavior"
BSD implementations added:
- more lengths `"q"`
- more conversions `"D"` `"U"` `"O"`
glibc (GNU) added:
- more lengths `"Z"`
- more conversions `"m"`
Windows C Runtime (CRT) added:
- more lengths `"I"` `"I32"` `"I64"` `"w"`
glibc and CRT both added `Z`. glibc uses `Z` for the length `size_t`. CRT uses
`Z` as a conversion for length-prefixed strings. This implementation takes the
former approach, handling `Z` in the same way as `z`.
BSD and IBM C library both added `D`. BSD uses `D` as a conversion, namely as
an alias of `ld`. IBM uses `D` for the length for `_Decimal64`, a decimal
floating point type, in accordance with ISO/IEC TR 24732. This implementation
takes the former approach.
This implementation also adds new conversions:
- `"b"` and `"B"` for binary (base-2) integer renderings
- `"y"` and `"Y"` for true/false and yes/no Boolean conversions
- `"J"` for JSON
- `"T"` and `"V"` for JS typeof and valueOf inspection
Combining the various extensions yields the following regular expression:
```perl
/%(?:
%| # literal %% (flags etc prohibited)
(\d+\$)? # positional parameter
([-+ 0\x23\x27]*)? # flags
(\d*|\*(?:\d+\$)?) # width
(?:\.(\d+|\*(?:\d+\$)?))? # precision
(hh?|ll?|[LzjtqZIw])? # length
([diouxXfFeEgGaAcCsSpnDUOmbByYJVT]) # conversion specifier
)/x
```
This implementation explicitly does not support certain non-standard extensions:
- AltiVec vector length extensions (`v` with `h`/`l`/`ll`):
- CRT fixed width lengths `I32` and `I64`
## Conversion Specifier Quick Reference Table
| C | Type | Summary |
|-----|:--------:|-------------------------------------------------------------|
| `a` | floating | base-2 exp form w/ hex mantissa and dec exponent, lowercase |
| `A` | floating | base-2 exp form w/ hex mantissa and dec exponent, uppercase |
| `b` | extended | cast to C `unsigned int`, standard form binary |
| `B` | extended | cast to C `unsigned long`, standard form binary |
| `c` | text | print `latin-1` char from number OR first char of string |
| `C` | text | print `UCS-2` char from number OR first char of string |
| `d` | integral | cast to C `int`, standard form decimal |
| `D` | integral | cast to C `long`, standard form decimal |
| `e` | floating | base-10 exp form w/dec mantissa and dec exponent, lowercase |
| `E` | floating | base-10 exp form w/dec mantissa and dec exponent, uppercase |
| `f` | floating | base-10 decimal form, lowercase extended values |
| `F` | floating | base-10 decimal form, uppercase extended values |
| `g` | floating | print using `e` or `f` conversion based on value/precision |
| `G` | floating | print using `E` or `F` conversion based on value/precision |
| `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` |
| `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) |
| `s` | text | print string argument |
| `S` | text | print string argument (alias of `"s"`) |
| `T` | extended | print type information (`typeof` or `Object toString`) |
| `u` | integral | cast to C `unsigned int`, standard form decimal |
| `U` | integral | cast to C `unsigned long`, standard form decimal |
| `V` | extended | print primitive value (`valueOf`) |
| `x` | integral | cast to C `unsigned int`, standard form hex, lowercase |
| `X` | integral | cast to C `unsigned long`, standard form hex, uppercase |
| `y` | extended | prints `true`/`false` or `yes`/`no` based on Boolean value |
| `Y` | extended | prints `TRUE`/`FALSE` or `YES`/`NO` based on Boolean value |
| `%` | misc | print the literal `%` character |
## Parameter Selection
The default behavior is to consume arguments in order:
```C
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:
```C
printf("%d %d %d", 1, 2, 3); // 1 2 3 (implicit order 1, 2, 3 )
printf("%1$s %2$s %3$s", "a", "b", "c"); // a b c (explicit order 1, 2, 3 )
printf("%1$s %3$s %2$s", "a", "b", "c"); // a c b (explicit order 1, 3, 2 )
```
The POSIX standard asserts that mixing positional and non-positional conversions
is undefined behavior. This implementation handles mixing by tracking the index
for non-positional conversions:
```C
printf("%s %4$s %s %5$s %s", "a", "b", "c", "d", "e"); // a d b e c
```
The POSIX standard requires that if an argument is used in the format, every
preceding argument must be used. This implementation relaxes that requirement:
```C
printf("%3$s", "a", "b", "c"); // c (technically invalid since "a"/"b" unused)
```
## Dynamic Specifiers
The width and precision specifiers may include the dynamic specifier `*` which
instructs the engine to read the next argument (assumed to be an integer). Just
as with the positional parameter, `idx$` immediately after the `*` token selects
the numeric argument.
For example:
```C
printf("|%5s|", "sheetjs"); // |sheetjs| (width = 5)
printf("|%*s|", 5, "sheetjs"); // |sheetjs| (width first argument)
printf("|%2$*1$s|", 5, "sheetjs", 10); // |sheetjs| (width is argument #1)
printf("|%10s|", "sheetjs"); // | sheetjs| (width = 10)
printf("|%2$*3$s|", 5, "sheetjs", 10); // | sheetjs| (width is argument #3)
```
Arguments are generally consumed in order as presented in the format string:
```C
printf("|%s|", val);
printf("|%*s|", width, val);
printf("|%.*s|", prec, val);
printf("|%*.*s|", width, prec, val);
printf("|%0*.*d|", 4, 2, 1); // | 01| width=4 prec=2 value=1
```
Positional arguments can be applied to width and precision:
```C
printf("|%*.*d|", width, prec, val);
printf("|%2$0*3$.*1$d|", prec, val, width);
printf("|%0*.*d|", 4, 2, 1); // | 01| width=4 prec=2 value=1 flags='0'
printf("|%1$0*3$.*2$d|", 1, 2, 4); // | 01| width=4 prec=2 value=1 flags='0'
```
A negative width is interpreted as the `-` flag with a positive width:
```C
printf("|%*.*d|", 4, 2, 1); // | 01| width=4 prec=2 value=1 flags=''
printf("|%-*.*d|", 4, 2, 1); // |01 | width=4 prec=2 value=1 flags='-'
printf("|%*.*d|", -4, 2, 1); // |01 | width=4 prec=2 value=1 flags='-'
printf("|%-*.*d|", -4, 2, 1); // |01 | width=4 prec=2 value=1 flags='-'
```
A negative precision is discarded:
```C
printf("|%*s|\n", 4, "sheetjs"); // |sheetjs| width=4
printf("|%*.*s|\n", 4, 3, "sheetjs"); // | she| width=4 prec=3
printf("|%*.*s|\n", 4, 2, "sheetjs"); // | sh| width=4 prec=2
printf("|%*.*s|\n", 4, 1, "sheetjs"); // | s| width=4 prec=1
printf("|%*.*s|\n", 4, 0, "sheetjs"); // | | width=4 prec=0
printf("|%*.*s|\n", 4, -1, "sheetjs"); // |sheetjs| width=4 (prec ignored)
```
# C Data Model
JS has one numeric type `Number` which represents an IEEE754 double-precision
(64-bit) floating point number. C has a multitude of numeric types, including
floating point as well as integer types. The sizes of those data types are
implementation-dependent. A "C data model" specifies the sizes of the core C
data types.
### Integer Types
POSIX `printf` specification references 8 integer types in integer conversions:
| C data type | fmt | unsigned type | fmt | signed type | fmt |
|-------------|------:|----------------------|------:|---------------|------:|
| `char` | | `unsigned char` | `hhu` | `signed char` | `hhd` |
| `short` | `hd` | `unsigned short` | `hu` | | |
| `int` | `d` | `unsigned int` | `u` | | |
| `long` | `ld` | `unsigned long` | `lu` | | |
| `long long` | `lld` | `unsigned long long` | `llu` | | |
| `size_t` | `zu` | | | `ssize_t` | `zd` |
| `intmax_t` | `jd` | `uintmax_t` | `ju` | | |
| `ptrdiff_t` | `td` | | | | |
C99 does not officially define a signed `size_t` or unsigned `ptrdiff_t` type.
POSIX does define `ssize_t` but no equivalent `uptrdiff_t`.
BSD additionally recognizes the types `quad_t` and `u_quad_t`, which this
implementation treats as `long long int` and `unsigned long long int`.
### Character and String Types
Two integer types are used in character and string conversions:
| type | fmt |
|-------------|------:|
| `wchar_t` | `ls` |
| `wint_t` | `lc` |
Both wide types `wchar_t` and `wint_t` can be signed or unsigned according to
C99. Both types are used only in character and string conversions. Based on
K&R "printable characters are always positive", the types are assumed unsigned.
### Floating Point Number Types
K&R recognizes 3 floating point types. C99 later tied it to IEC 60559:
| C data type | precision | total bits | exponent | mantissa | fmt |
|:--------------|:----------|:----------:|:--------:|:--------:|------:|
| `float` | single | `32` | `8` | `23` | |
| `double` | double | `64` | `11` | `52` | `f` |
| `long double` | extended | `80` | `15` | `64` | `Lf` |
## Implementation
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
in the `Models` object as per the following table:
| type | ctypes.json | LP64 | ILP32 | LLP64 |
|-------------|-------------|-----:|------:|------:|
| `char` | `char` | 1 | 1 | 1 |
| `short` | `short` | 2 | 2 | 2 |
| `int` | `int` | 4 | 4 | 4 |
| `long` | `long` | 8 | 4 | 4 |
| `long long` | `longlong` | 8 | 8 | 8 |
| `wchar_t` | `wchar_t` | 4 | 4 | 2 |
| `wint_t` | `wint_t` | 4 | 4 | 2 |
| `size_t` | `size_t` | 8 | 4 | 8 |
| `intmax_t` | `intmax_t` | 8 | 8 | 8 |
| `ptrdiff_t` | `ptrdiff_t` | 8 | 4 | 8 |
By default the source assumes the LP64 data model. Other data models are
supported in the source tree, controlled by the JSFLAGS variable in the build
process. Set the `JS_MODEL` variable to the desired index as specified in the
`ModelNames` array in `bits/ctype.json`:
```bash
$ <bits/ctypes.json jq -r '.ModelNames|.[]' # LP64 ILP32 LLP64
$ JSFLAGS=-DJS_MODEL=0 make # LP64
$ JSFLAGS=-DJS_MODEL=1 make # ILP32
$ JSFLAGS=-DJS_MODEL=2 make # LLP64
```
To create a custom model, add the spec to `bits/ctypes.json` by appending the
model name to the end of the `ModelNames` array and adding an entry to the
`Models` object. The current models are defined as follows:
```json
{
"ModelNames":["LP64", "ILP32", "LLP64"],
"Models": {
"LP64": { "char":1, "short":2, "int":4, "long":8, "longlong":8, "wint_t":4, "wchar_t":4, "size_t":8, "intmax_t":8, "ptrdiff_t":8 },
"ILP32": { "char":1, "short":2, "int":4, "long":4, "longlong":8, "wint_t":4, "wchar_t":4, "size_t":4, "intmax_t":8, "ptrdiff_t":4 },
"LLP64": { "char":1, "short":2, "int":4, "long":4, "longlong":8, "wint_t":2, "wchar_t":2, "size_t":8, "intmax_t":8, "ptrdiff_t":8 }
}
}
```
# Integer Conversions
This section covers the conversions `diouxXDUO`. The base-2 conversions `bB`
are an extension and are discussed at the end, but the same basic rules apply.
JS has one Number type (representing an IEEE754 8-byte floating point number)
that is capable of representing a 32-bit integer. It cannot represent the full
range of 64-bit integers exactly. Care is taken to avoid operations that may
inadvertently result in a conversion to a smaller integral type.
## Restricting Integer Values
JS Bitwise operations convert numbers to 32-bit integers before performing
operations. With the exception of the unsigned right shift operator `>>>`, all
operations act on signed integers. For example:
```js
Math.pow(2,31) | 0; // -2147483648 == -Math.pow(2,31)
(Math.pow(2,32)-2) ^ 0; // -2
-1 >>> 0 // 4294967295 == Math.pow(2,32) - 1
```
JS Number can exactly represent every integer in the range `-2^53 .. 2^53`. For
lengths exceeding 32 bits, `Math.round` is appropriate.
| bits | unsigned | signed |
|------|---------------------------|-------------------------------------------|
| 8 | `V & 0xFF` | `V &= 0xFF; if(V > 0x7F) V-= 0x100` |
| 16 | `V & 0xFFFF` | `V &= 0xFFFF; if(V > 0x7FFF) V-= 0x10000` |
| 32 | `V >>> 0` | `V | 0` |
| 64 | `Math.abs(Math.round(V))` | `Math.round(V)` |
## Length Specifiers for Integer Conversions
When a length specifier implies a certain size (such as `hh` for a single-byte
integer), the number will be converted before rendering strings. For example:
```C
printf("%1$02hhx %1$02hx %1$02lx %1$02llx\n", 256); // 00 100 100 100
printf("%1$02hhx %1$02hx %1$02lx %1$02llx\n", 4096); // 00 1000 1000 1000
printf("%1$02hhx %1$02hx %1$02lx %1$02llx\n", 65536); // 00 00 10000 10000
```
Values are restricted by first limiting the result to a specified number of
bytes (appropriate bit-and) and then adding or subtracting to ensure the value
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 |
## Rendering Unsigned Integers in Base 10 ("u" and "U" conversions)
`num.toString(10)` produces the correct result for exact integers.
`"u"` conversion restricts values to `int`; `"U"` restricts to `long`.
## Rendering Unsigned Integers in Base 8 ("o" and "O" conversions)
Even though `num.toString(8)` is implementation-dependent, all browser
implementations use standard form for integers in the exact range.
The alternate form (`#`) prints a `"0"` prefix.
`"o"` conversion restricts values to `int`; `"O"` restricts to `long`.
## Rendering Unsigned Integers in Base 16 ("x" and "X" conversions)
Even though `num.toString(16)` is implementation-dependent, all browser
implementations use standard form for integers in the exact range.
The alternate form (`#`) prints a `"0x"` or `"0X"` prefix.
Unlike `"U" "O" "D"`, `"X"` conversion uses `A-F` instead of `a-f` in hex.
## Rendering Signed Integers in Base 10 ("d" "i" and "D" conversions)
`num.toString(10)` produces the correct result for exact integers. The flags
`" +"` control prefixes for positive integers.
`"di"` conversions restrict values to `int`; `"D"` restricts to `long`.
# Floating Point Conversions
This section covers the conversions `fFeEgGaA`.
Due to C variadic argument promotion rules, `float` types are always promoted to
`double`. None of the conversions or length specifiers signal that an argument
is to be interpreted as a `float`. There is no JS canonical representation of
an extended floating point number, so JS `Number` suffices.
## Infinity, NaN, and Negative Zero
JS recognizes a few special IEEE754 values, as described in the following table:
| JS value | JS Expression | Description |
|------------:|:--------------|:-----------------------------------------------|
| `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.` |
| `-0.` | `-1/Infinity` | Negative limiting value `lim{x->0-} x` |
JS `Number` methods render different strings from the POSIX spec:
| JS value | POSIX string | JS string |
|------------:|:----------------------------------------------|--------------:|
| `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.` |
This implementation performs the required adjustments.
## Exponential Form ("e" and "E" conversions)
Aside from the special cases discussed above, JS `num.toExponential(prec)`
differs from POSIX `printf("%1$.*2$e", num, prec)` in the exponent field: JS
writes exponents with the fewest digits (POSIX requires 2+ digits). This is
easily fixed by inspecting the output string and inserting a "0" when needed.
The optional `#` flag forces the decimal point to appear when precision is 0.
This is also easily corrected by adding a decimal point just before the "e".
## Standard Form ("f" and "F" conversions)
The POSIX spec only requires that the number of digits after the decimal point
is equal to the precision. It does not specify how many digits appear before
the decimal point, nor does it specify how to handle numbers that cannot be
exactly represented.
For values less than `1e21` the JS `num.toFixed(n)` generally matches `%f` with
the specified precision. However, for larger values `toFixed` defaults to the
exponential form.
## Value-dependent Form ("g" and "G" conversions)
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` |
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`.
## Hex-Mantissa Decimal-Binary-Exponent Form ("a" and "A" conversions)
A general exponential form involves 3 parameters: radix of the mantissa, base of
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` |
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
(`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
exact standard hexadecimal form. Both forms are easily converted to `"%a"` by
calculating and dividing by the appropriate power of 2.
For each non-zero normal floating point value, there are 4 acceptable strings
that represent the value, derived by multiplying the normalized value by powers
of 2 and adjusting the exponent accordingly:
| Value | Normalized | Alternate `*2` | Alternate `*4` | Alternate `*8` |
|:--------|:---------------|:---------------|:---------------|:---------------|
| `1` | `1p+0` | `2p-1` | `4p-2` | `8p-3` |
| `.2` | `1.9999999p-3` | `3.3333333p-4` | `6.6666666p-5` | `c.cccccccp-6` |
| `.69` | `1.6147ae1p-1` | `2.c28f5c2p-2` | `5.851eb85p-3` | `b.0a3d70ap-4` |
| `6.e20` | `1.043561p+69` | `2.086ac3p+68` | `4.10d586p+67` | `8.21ab0dp+66` |
JS engines follow the glibc model: multiply by a suitable power of 16 so that
the mantissa is between 1 and 16, render left to right one digit at a time, then
fix the result at the end. FreeBSD and OSX always show the normalized form.
This implementation defaults to the normalized form. To switch to the glibc
form, define `DO_NOT_NORMALIZE` in the `JSFLAGS` variable when building:
```bash
$ JSFLAGS=-DDO_NOT_NORMALIZE make
```
# Character Conversions
This section covers the conversions `sScC`.
## Rendering Strings ("s" and "S" conversions)
JS has no concept of "wide strings" (`wchar_t *` in C), so the length modifiers
are ignored. `s` and `S` are treated as equivalent.
Arguments are first interpreted as strings by calling the `String` function.
Implementing `toString` on the argument to be converted may lead to unexpected
results:
```C
var O = {valueOf:function() {return 456;}, toString:function() {return "123"}};
printf("%1$s %1$d", O); // "123 456"
```
If a positive precision is specified, up to that many characters will be taken
from the string. Otherwise the entire string will be used:
```C
printf("|%s|", "sheetjs"); // '|sheetjs|' (no precision)
printf("|%.9s|", "sheetjs"); // '|sheetjs|' (string shorter than precision)
printf("|%.5s|", "sheetjs"); // '|sheet|' (string truncated)
```
Lengths are measured using the JS string length accessor. Since there is no
attempt to correct for multi-character sequences like combining marks, the
results may be unexpected:
```C
printf("%.1s","ñ"); // 'n' not "ñ"
```
If the width is specified and is greater than the width of the string to be
rendered, padding will be applied. If the `"-"` flag is specified, then the
string will be right-padded, otherwise it will be left-padded. If the `"0"`
flag is specified, the final string is left-padded with zeroes. The `"-"` flag
takes precedence over `0`.
```C
printf( "|%s|", "sheetjs"); // '|sheetjs|' (no width)
printf( "|%5s|", "sheetjs"); // '|sheetjs|' (string longer than width)
printf( "|%9s|", "sheetjs"); // '| sheetjs|' (no flag = left pad spaces)
printf( "|%09s|", "sheetjs"); // '|00sheetjs|' ("0" = left pad "0")
printf( "|%-9s|", "sheetjs"); // '|sheetjs |' ("-" = right pad space)
printf("|%-09s|", "sheetjs"); // '|sheetjs |' ("0" ignored)
```
## Rendering Characters ("c" and "C" conversions)
JS has no concept of "wide characters" (`wchar_t` in C). The length modifier is
used in determining whether the number should be interpreted as one or two
16-bit character codes (when the "C" format or the "l" or "ll" specifiers are
used) or a single 8-bit char code. Precision and flags are ignored.
# Non-Numeric Conversions
## The literal "%" symbol ("%" conversion)
All other parameters are ignored.
## Interpreting and Rendering Pointers ("p" conversion)
JS has no true concept of pointers. In array and typed array contexts, it is
common to associate a position object that stores the address relative to the
start of the array. This implementation reads the `l` key and interprets as a
32-bit or 52-bit unsigned integer depending on `size_t` in the data model.
The normal output format is equivalent to `"%#x"` but the alternate form emits
using the `"%d"` format. When the pointer is invalid, `-1` is rendered. Only
the `"#"` flag is interpreted.
```js
var x = {}, y = {l:3};
printf("%1$p %1$#p", y); // 0x3 3
printf("%1$p %1$#p", x); // 0xFFFFFFFF -1
```
## Extracting length of a partial conversion ("n" conversion)
C `printf` permits a special `n` conversion which interprets the argument as an
integral pointer (interpreted size controlled by the length specifier) and
writes the number of characters printed to that pointer.
JS has no true concept of pointers in the C sense. The library works around
the limitation by interpreting the argument as an object and assigning to the
`len` key. The conversion does not write any characters to the output string:
```js
var x = {};
printf("%1$s %2$J%2$n abc", "foo", x); // "foo {} abc", also sets x.len = 6
// |........| |......| (6 chars at that point)
```
This implementation mutates the object while processing:
```js
var x = {};
printf("%1$s %2$J%2$n %3$s %2$J", "foo", x, "bar"); // 'foo {} bar {"len":6}'
```
## Error messages ("m" conversion)
glibc supports an `m` conversion that does not consume arguments. It renders
the string `strerror(errno)` where `strerror` is the libc function and `errno`
is the global error number.
JS has no equivalent of `errno` and no standard JS runtime exposes a similar
global error variable, so `%m` will write the default message `"Success"`. A
positional parameter or `#` flag changes the behavior:
| form | position | behavior |
|:------------:|:--------:|---------------------------------------|
| main | no | do not read argument, emit "Success" |
| alt (flag #) | no | read and process next argument |
| main or alt | yes | read and process specified argument |
In all forms other than `"%m"`, an argument will be processed as follows:
- If the argument is not an instance of an `Error`, emit "Success"
- If the `message` field is set, emit the error message.
- If the `errno` field is set, emit "Error number " followed by the errno
- Otherwise emit "Error " followed by the error interpreted as a String
```
var x = new Error("sheetjs");
x.errno = 69; x.toString = function() { return "SHEETJS"; };
printf("|%#m|", x); // |sheetjs|
delete x.message;
printf("|%#m|", x); // |Error number 69|
delete x.errno;
printf("|%#m|", x); // |Error SHEETJS|
```
# Extensions
These additional conversions take advantage of unused format characters:
## Rendering Boolean Values ("y" and "Y" conversions)
Values are converted to Boolean and tested for truthiness. The `Y` rendering
is the uppercase version of the equivalent rendering with format `y`.
| form | truthy value `y` (`Y`) | falsy value `y` (`Y`) |
|:------------:|:-----------------------:|:---------------------:|
| main | `true` (`TRUE`) | `false` (`FALSE`) |
| alt (flag #) | `yes` (`YES`) | `no` (`NO`) |
Width and precision are applied in the same manner as the `s` conversion.
```js
printf("|%1$y|%2$Y|%1$#Y|%2$#y|%2$.1y|", 1, 0); // |true|FALSE|YES|no|f|
printf("|%05.2Y|%-5.2y|", 1, 0); // |000TR|fa |
```
## Rendering JSON ("J" conversion)
The default rendering is the standard output from `JSON.stringify`. Alternate
form (`"#"` flag) renders using `util.inspect` if available.
```js
var x = {
a: [1,[2,3,4],5,6,7],
b: {
c: {
d: { e:"f" },
g:"h",
i:"j"
},
k:"l",
m:"n",
o:"p"},
q: "r"
};
printf("%J", x) // '{"a":[1,[2,3,4],5,6,7],"b":{"c":{"d":{"e":"f"}, ..(ctnd)..
printf("%#J", x) // '{ a: [ 1, [ 2, 3, 4 ], 5, 6, 7 ],\n b: { c: { ..(ctnd)..
```
Width, precision and other flags are ignored.
## JS typeof and valueOf ("T" and "V" conversion)
Under the "T" conversion, the result of `typeof arg` is rendered. If the `#`
flag is specified, the type is derived from `Object.prototype.toString`:
```
printf("%1$T %1$#T", 1); // 'number Number'
printf("%1$T %1$#T", 'foo'); // 'string String'
printf("%1$T %1$#T", [1,2,3]); // 'object Array'
printf("%1$T %1$#T", null); // 'object Null'
printf("%1$T %1$#T", undefined); // 'undefined Undefined'
```
Under the "V" conversion, the result of `arg.valueOf()` is rendered:
```
var _f = function() { return "f"; };
var _3 = function() { return 3; };
printf("%1$d %1$s %1$V", {toString:_f}); // '0 f f'
printf("%1$d %1$s %1$V", {valueOf:_3}); // '3 [object Object] 3'
printf("%1$d %1$s %1$V", {valueOf:_3, toString:_f}); // '3 f 3'
```
## Rendering Unsigned Integers in Base 2 ("b" and "B" conversions)
The implementation is similar to the octal `"o"` and `"O"` conversions, except
for the radix (2 for `"b"` and `"B"`) and the alternate-form prefix (`"0b"`)
# Miscellaneous Notes
## Format Characters
For compatibility purposes, format characters must be printable ASCII characters
(ASCII codes `0x20 - 0x7E`). The 95 eligible characters are listed below:
| C | Type | C | Type | C | Type | C | Type |
|-----|:----------:|-----|:----------:|-----|:----------:|-----|:----------:|
| `a` | conversion | `A` | conversion | ` ` | flag | `!` | |
| `b` | conversion | `B` | conversion | `"` | | `#` | flag |
| `c` | conversion | `C` | conversion | `$` | other | `%` | conversion |
| `d` | conversion | `D` | conversion | `&` | | `'` | flag |
| `e` | conversion | `E` | conversion | `(` | | `)` | |
| `f` | conversion | `F` | conversion | `*` | other | `+` | flag |
| `g` | conversion | `G` | conversion | `,` | | `-` | flag |
| `h` | length | `H` | | `.` | other | `/` | |
| `i` | conversion | `I` | length | `0` | digit | `1` | digit |
| `j` | length | `J` | conversion | `2` | digit | `3` | digit |
| `k` | | `K` | | `4` | digit | `5` | digit |
| `l` | length | `L` | length | `6` | digit | `7` | digit |
| `m` | conversion | `M` | | `8` | digit | `9` | digit |
| `n` | conversion | `N` | | `:` | | `;` | |
| `o` | conversion | `O` | conversion | `<` | | `=` | |
| `p` | conversion | `P` | | `>` | | `?` | |
| `q` | length | `Q` | | `@` | | `[` | |
| `r` | | `R` | | `\` | | `]` | |
| `s` | conversion | `S` | conversion | `^` | | `_` | |
| `t` | length | `T` | conversion | `~` | | `{` | |
| `u` | conversion | `U` | conversion | `|` | | `}` | |
| `v` | | `V` | conversion | `` ` `` | |
| `w` | length | `W` | |
| `x` | conversion | `X` | conversion |
| `y` | conversion | `Y` | conversion |
| `z` | length | `Z` | length |
## JS and C strings
C provides no guidance on the actual character set. According to K&R all valid
characters in source code must be in a character set that is a subset of the
7-bit ASCII set. This implementation falls back on the UTF-16 base required by
JS. When converting C literal strings, there are a few differences in escaping:
| C escape sequence | Equivalent JS | Notes |
|:------------------|:--------------|:---------------------------------------|
| `"\a"` | `"\007"` | BEL character will not ring in browser |
| `"\?"` | `"?"` | JS does not handle trigraphs |
| `"\ooo"` (octal) | `"\ooo"` | JS uses Latin-1 for non-ASCII codes |
| `"\xhh"` (hex) | `"\xhh"` | JS uses Latin-1 for non-ASCII codes |
## Support Summary
- Full [POSIX](http://pubs.opengroup.org/onlinepubs/9699919799/functions/printf.html) conversion support with extensions!
[Conversion Specifier Table](#conversion-specifier-quick-reference-table)
- Full support for POSIX flags and positional parameters
- Emulation of BSD `quad_t` and `u_quad_t` conversion
- Parser accepts but does not emulate CRT wide and unicode character conversions
- glibc `Z` length conversion and extended `m` error support
- CRT `I/w` length but no `I32/I64`
- Default `LP64` data model but can be configured to support `ILP32` or `LLP64`

62
bin/printj.njs Executable file

@ -0,0 +1,62 @@
#!/usr/bin/env node
/* printj.js (C) 2016-present SheetJS -- http://sheetjs.com */
/* vim: set ts=2 ft=javascript: */
var X = require("../"), argv = process.argv;
function help() {
[
"usage: printj [options] <format> [args...]",
"",
"Options:",
" -h, --help output usage information",
" -d, --dump print debug information about format string",
"",
"Arguments are treated as strings unless prefaced by a type indicator:",
" n:<integer> call parseInt (ex. n:3 -> 3)",
" f:<float> call parseFloat (ex. f:3.1 -> 3.1)",
' b:<boolean> false when lowercase value is "FALSE" or "0", else true',
" s:<string> interpret as string (ex. s:n:3 -> \"n:3\")",
" j:<JSON> interpret as an object using JSON.parse",
" e:<JS> evaluate argument (ex. e:1+1 -> 2, e:\"1\"+1 -> \"11\")",
"",
"samples:",
" $ printj '|%02hhx%d|' n:50 e:0x7B # |32123|",
" $ printj '|%2$d + %3$d is %1$d|' e:1+2 n:1 n:2 # |1 + 2 is 3| ",
" $ printj '|%s is %s|' s:1+2 e:1+2 # |1+2 is 3|",
" $ printj '|%c %c|' s:69 n:69 # |6 E|",
"",
"Support email: dev@sheetjs.com",
"Web Demo: http://oss.sheetjs.com/printj/"
].forEach(function(l) { console.log(l); });
return 0;
}
function parse_arg(arg/*:string*/)/*:any*/ {
var m = arg.substr(2), p/*:number*/;
if(arg.charCodeAt(1) == 58) switch((p = arg.charCodeAt(0))) {
case /*n*/ 110: return parseInt(m);
case /*f*/ 102: return parseFloat(m);
case /*b*/ 98: return !(m.toUpperCase() == "FALSE" || m == "0");
case /*j*/ 106: return JSON.parse(m);
case /*e*/ 101: return eval(m);
case /*s*/ 115: return m;
}
return arg;
}
var args/*:Array<any>*/ = [];
var fmt = "", n = 0;
for(var i = 2; i < argv.length; ++i) switch(argv[i]) {
case "--help": case "-h": return help();
case "--dump": case "-d": if(fmt.length==0) fmt = argv[++i]; return dump(fmt);
default: if(n++ == 0) fmt = argv[i]; else args.push(parse_arg(argv[i]));
}
console.log(X.vsprintf(fmt, args));
return 0;
function dump(fmt) {
if(!fmt) { console.error("printj: missing format argument"); return 1; }
X._tokenize(fmt).forEach(function(x){console.log(x);});
return 0;
}

1
bits/.gitignore vendored Normal file

@ -0,0 +1 @@
30_ctypes.js

27
bits/00_header.js Normal file

@ -0,0 +1,27 @@
/* 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: any; */
/*:: declare var define: any; */
var PRINTJ/*:any*/;
(function (factory/*:(a:any)=>void*/)/*:void*/ {
/*jshint ignore:start */
if(typeof DO_NOT_EXPORT_PRINTJ === 'undefined') {
if('object' === typeof exports) {
factory(exports);
} else if ('function' === typeof define && define.amd) {
define(function () {
var module/*:any*/ = {};
factory(module);
return module;
});
} else {
factory(PRINTJ = {});
}
} else {
factory(PRINTJ = {});
}
/*jshint ignore:end */
}(function(PRINTJ) {
#include "01_version.js"

1
bits/01_version.js Normal file

@ -0,0 +1 @@
PRINTJ.version = '0.1.0';

78
bits/10_tokenize.js Normal file

@ -0,0 +1,78 @@
#ifdef USE_CHAR
#define GETCHAR C = fmt.charAt(i);
#else
#define GETCHAR c = fmt.charCodeAt(i);
#endif
#define DRAIN(idx) if(start < idx) out.push(["L", fmt.substring(start, idx)]);
function tokenize(fmt/*:string*/)/*:ParsedFmt*/ {
var out/*:ParsedFmt*/ = [];
var start/*:number*/ = 0;
#ifdef USE_REGEX
var m/*:?Array<string>*/;
while((m = REGEX.exec(fmt))) {
DRAIN(m.index)
start = m.index + m[0].length;
if(m[0] === "%%") out.push(["%","%"]);
else out.push([m[6], m[0], m[1], m[2], m[3], m[4], m[5]]);
}
#else /* NOT USE_REGEX */
var i/*:number*/ = 0;
var infmt/*:boolean*/ = false;
var fmtparam/*:string*/ = "", fmtflags/*:string*/ = "", fmtwidth/*:string*/ = "", fmtprec/*:string*/ = "", fmtlen/*:string*/ = "";
#if defined(USE_CHAR)
var C/*:string*/ = '?';
#else /* USE_CODE */
var c/*:number*/ = 0;
#endif /* USE_CHAR */
#if defined(USE_INDEX)
while((i=fmt.indexOf("%", start)) != -1) {
DRAIN(i)
start = i++;
infmt = true;
while(infmt) {
GETCHAR
#ifdef USE_CHAR
#include "12_switchchar.js"
#else /* USE_CODE */
#include "11_switchcode.js"
#endif /* USE_CHAR */
++i;
}
}
#elif defined(USE_LOOP)
var L/*:number*/ = fmt.length;
for(; i < L; ++i) {
GETCHAR
if(!infmt) {
#ifdef USE_CHAR
if(C !== "%") continue;
#else /* USE_CODE */
if(c !== 37) continue;
#endif /* USE_CHAR */
DRAIN(i)
start = i;
infmt = true;
continue;
}
#ifdef USE_CHAR
#include "12_switchchar.js"
#else /* USE_CODE */
#include "11_switchcode.js"
#endif /* USE_CHAR */
}
#endif /* USE_INDEX / USE_LOOP */
#endif /* USE_REGEX */
if(start < fmt.length) out.push(["L", fmt.substring(start)]);
return out;
}

91
bits/11_switchcode.js Normal file

@ -0,0 +1,91 @@
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) + "|");
}

3
bits/12_regexp.js Normal file

@ -0,0 +1,3 @@
var REGEX/*:RegExp*/ = /%(?:%|(\d+\$)?([-+ 0#']*)?(\d*|\*(?:\d+\$)?)(?:\.(\d+|\*(?:\d+\$)?))?(hh?|ll?|[LzjtqZIw])?([diouxXfFeEgGaAcCsSpnDUOmbByYJVT]))/g;
/* |A------AB----------BC----------------CD-----------------------DE-------------------EF---------------------------------F| */

91
bits/12_switchchar.js Normal file

@ -0,0 +1,91 @@
if(C >= "0" && C <= "9") {
if(fmtprec.length) fmtprec += C;
else if(C == "0" && !fmtwidth.length) fmtflags += C;
else fmtwidth += C;
} else switch(C) {
/* positional */
case '$':
if(fmtprec.length) fmtprec += "$";
else if(fmtwidth.charAt(0) == "*") fmtwidth += "$";
else { fmtparam = fmtwidth + "$"; fmtwidth = ""; }
break;
/* flags */
case "'": fmtflags += "'"; break;
case '-': fmtflags += "-"; break;
case '+': fmtflags += "+"; break;
case ' ': fmtflags += " "; break;
case '#': fmtflags += "#"; break;
/* width and precision */
case '.': fmtprec = "."; break;
case '*':
if(fmtprec.charAt(0) == ".") fmtprec += "*";
else fmtwidth += "*";
break;
/* length */
case 'h':
case 'l':
if(fmtlen !== "" && fmtlen !== C) throw "bad length " + fmtlen + C;
fmtlen += C;
break;
case 'L':
case 'j':
case 'z':
case 't':
case 'q':
case 'Z':
case 'w':
if(fmtlen !== "") throw "bad length " + fmtlen + C;
fmtlen = C;
break;
case 'I':
if(fmtlen !== "") throw "bad length " + fmtlen + 'I';
fmtlen = 'I';
break;
/* conversion */
case 'd':
case 'i':
case 'o':
case 'u':
case 'x':
case 'X':
case 'f':
case 'F':
case 'e':
case 'E':
case 'g':
case 'G':
case 'a':
case 'A':
case 'c':
case 'C':
case 's':
case 'S':
case 'p':
case 'n':
case 'D':
case 'U':
case 'O':
case 'm':
case 'b':
case 'B':
case 'y':
case 'Y':
case 'J':
case 'V':
case 'T':
case '%':
infmt = false;
if(fmtprec.length > 1) fmtprec = fmtprec.substr(1);
out.push([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) + "|");
}

61
bits/30_ctypes.njs Normal file

@ -0,0 +1,61 @@
/* vim: set ts=2 ft=javascript: */
var ctypes = require("./ctypes.json");
var models = ctypes.Models;
var modelnames = ctypes.ModelNames;
console.log("#ifndef CTYPES_JS_");
console.log("#define CTYPES_JS_");
console.log("");
modelnames.forEach(function(m, i) { console.log("#define JS_MODEL_" + m + " " + i); });
console.log("");
console.log("#ifndef JS_MODEL\n#define JS_MODEL JS_MODEL_" + modelnames[0] + "\n#endif\n");
function print_model(model) {
[
"#define SIZEOF_CHAR " + model.char,
"#define SIZEOF_WCHAR_T " + model.wchar_t,
"",
"#define SIZEOF_SHORT " + model.short,
"#define SIZEOF_INT " + model.int,
"#define SIZEOF_WINT_T " + model.wint_t,
"#define SIZEOF_LONG " + model.long,
"#define SIZEOF_LONG_LONG " + model.longlong,
"",
"#define SIZEOF_SIZE_T " + model.size_t,
"#define SIZEOF_INTMAX_T " + model.intmax_t,
"#define SIZEOF_PTRDIFF_T " + model.ptrdiff_t,
].forEach(function(l) { console.log(l); });
}
modelnames.forEach(function(m,i) {
console.log("#" + (i == 0 ? "" : "el") + "if JS_MODEL == JS_MODEL_" + m);
print_model(models[m]);
});
console.log("#else");
var warn = modelnames.map(function(m, i) { return i + " (" + m + ")"; }).join(" or ");
console.log("#error 'Unsupported JS_MODEL, should be " + warn + "'\n#endif\n");
function make_124_mask(T) {
var name = "SIZEOF_" + T, mask = "MASK_" + T;
return [
"#ifdef " + name,
"#if " + name + " == 1",
"#define " + mask + " 0xFF",
"#elif " + name + " == 2",
"#define " + mask + " 0xFFFF",
"#elif " + name + " == 4",
"#define " + mask + " 0xFFFFFFFF",
"#endif",
"#endif /*" + name + "*/"
].join("\n");
}
console.log(make_124_mask("CHAR"));
console.log("");
console.log(make_124_mask("WCHAR_T"));
console.log("");
console.log("#endif /*CTYPES_JS_*/");

55
bits/40_macros.js Normal file

@ -0,0 +1,55 @@
#define isnan isNaN
//#define PAD_(x,c) (x >= 0 ? new Array(((x)|0) + 1).join((c)) : "")
var padstr = {
" ": " ",
"0": "000000000000000000000000000000000",
"7": "777777777777777777777777777777777",
"f": "fffffffffffffffffffffffffffffffff"
};
#define PAD_(x,c) (x >= 0 ? padstr[c].substr(0,x) : "")
#ifdef DO_NOT_INLINE
function pads(x/*:number*/, c/*:string*/)/*:string*/ { return PAD_(x,c); }
#define PADS(x,c) pads(x,c)
#else
#define PADS(x,c) PAD_(x,c)
#endif
#define PAD(x) pad = PADS(x, " ")
#define PREC_STR(O, prec) if(prec >= 0) O = O.substr(0, prec);
#define WIDTH(O, width, flags) \
if(width > O.length || -width > O.length) { \
if((flags.indexOf("-") == -1 || width < 0) && flags.indexOf("0") != -1) { \
pad = PADS(width - O.length, "0"); \
O = pad + O; \
} else { \
PAD(width - O.length); \
O = flags.indexOf("-") > -1 ? O + pad : pad + O; \
} \
}
#ifndef SIZEOF_WCHAR_T
#error SIZEOF_WCHAR_T must be 1, 2, or 4
#elif SIZEOF_WCHAR_T == 1 || SIZEOF_WCHAR_T == 2 || SIZEOF_WCHAR_T == 4
#define WCHAR_TO_STR(O,cc) { cc &= MASK_WCHAR_T; O = String.fromCharCode(cc); }
#else
#error SIZEOF_WCHAR_T must be 1, 2, or 4
#endif
#define CHAR_TO_STR(O,cc) cc &= MASK_CHAR; O = String.fromCharCode(cc);
#if SIZEOF_SIZE_T > 4 /* TODO: negative ptrs? */
#define CONV_SIZE_T(x) x = Math.abs(x);
#define SIZE_T_TO_HEX(n) n.toString(16)
#else
#define CONV_SIZE_T(x) x = (x>>>0);
#define SIZE_T_TO_HEX(n) n.toString(16)
#endif
#define IDX_POS 2
#define IDX_FLAGS 3
#define IDX_WIDTH 4
#define IDX_PREC 5
#define IDX_LEN 6

82
bits/50_doit.js Normal file

@ -0,0 +1,82 @@
#include "30_ctypes.js"
#include "40_macros.js"
/*:: declare var util:any; */
/*:: declare var require: any; */
if(typeof util=='undefined' && typeof require!=='undefined')util=require("util");
var u_inspect/*:(o:any)=>string*/ = (typeof util != 'undefined') ? util.inspect : JSON.stringify;
function doit(t/*:ParsedFmt*/, args/*:Array<any>*/)/*:string*/ {
var o/*:Array<string>*/ = [];
var argidx/*:number*/ = 0, idx/*:number*/ = 0;
var Vnum/*:number*/ = 0;
var pad/*:string*/ = "";
for(var i = 0; i < t.length; ++i) {
var m/*:Array<any>*/ = 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*/ = SIZEOF_INT, sign/*:boolean*/ = false;
/* flags */
var flags/*:string*/ = m[IDX_FLAGS]||"";
var alt = flags.indexOf("#") > -1;
/* position */
if(m[IDX_POS]) argidx = parseInt(m[IDX_POS])-1;
/* %m special case */
else if(c === /*m*/ 109 && !alt) { o.push("Success"); continue; }
#define GRAB_INT(NAME, IDX, DFL) \
var NAME/*:number*/ = DFL; \
if(m[IDX] != null && m[IDX].length > 0) { \
if(m[IDX].charAt(0) !== '*') NAME = parseInt(m[IDX], 10); \
else if(m[IDX].length === 1) NAME = args[idx++]; \
else NAME = args[parseInt(m[IDX].substr(1), 10)-1]; \
}
/* grab width */
GRAB_INT(width, IDX_WIDTH, 0)
/* grab precision */
GRAB_INT(prec, IDX_PREC, -1)
/* position not specified */
if(!m[IDX_POS]) argidx = idx++;
/* grab argument */
var arg/*:any*/ = args[argidx];
/* grab length */
var len/*:string*/ = m[IDX_LEN] || "";
#define INT isnum = -1
#define SGN(s) sign = s
#define LONG bytes = SIZEOF_LONG
#define RADIX(n) radix = (n)
#define NUM(n) isnum = (n)
switch(c) {
/* str cCsS */
#include "51_convstr.js"
/* int diDuUoOxXbB */
#include "52_convint.js"
/* flt fegFEGaA */
#include "53_convflt.js"
/* misc pnmJVTyY */
#include "54_convmisc.js"
}
if(isnum == -1) {
#include "60_integer.js"
} else if(isnum > 0) {
#include "70_float.js"
}
o.push(O);
}
return o.join("");
}

22
bits/51_convstr.js Normal file

@ -0,0 +1,22 @@
case /*S*/ 83:
case /*s*/ 115:
/* only valid flag is "-" for left justification */
O = String(arg);
PREC_STR(O, prec)
WIDTH(O, width, flags)
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) WCHAR_TO_STR(O, cc)
else CHAR_TO_STR(O, cc)
break;
case "string": O = arg.charAt(0); break;
default: O = String(arg).charAt(0);
}
WIDTH(O, width, flags)
break;

24
bits/52_convint.js Normal file

@ -0,0 +1,24 @@
/* signed integer */
case /*D*/ 68: LONG;
/* falls through */
case /*d*/ 100:
case /*i*/ 105: INT; SGN(true); break;
/* unsigned integer */
case /*U*/ 85: LONG;
/* falls through */
case /*u*/ 117: INT; break;
/* unsigned octal */
case /*O*/ 79: LONG;
/* falls through */
case /*o*/ 111: INT; RADIX(8); break;
/* unsigned hex */
case /*x*/ 120: INT; RADIX(-16); break;
case /*X*/ 88: INT; RADIX(16); break;
/* unsigned binary (extension) */
case /*B*/ 66: LONG;
/* falls through */
case /*b*/ 98: INT; RADIX(2); break;

13
bits/53_convflt.js Normal file

@ -0,0 +1,13 @@
/* floating point logic */
case /*F*/ 70:
case /*f*/ 102: NUM(1); break;
case /*E*/ 69:
case /*e*/ 101: NUM(2); break;
case /*G*/ 71:
case /*g*/ 103: NUM(3); break;
/* floating hex */
case /*A*/ 65:
case /*a*/ 97: NUM(4); break;

42
bits/54_convmisc.js Normal file

@ -0,0 +1,42 @@
/* JS has no concept of pointers so interpret the `l` key as an address */
case /*p*/ 112:
Vnum = typeof arg == "number" ? arg : Number(arg.l);
if(isnan(Vnum)) Vnum = -1;
if(alt) O = Vnum.toString(10);
else {
CONV_SIZE_T(Vnum)
O = "0x" + SIZE_T_TO_HEX(Vnum).toLowerCase();
}
break;
/* store length in the `len` key */
case /*n*/ 110:
if(arg) { arg.len=0; for(var oo = 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 = 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 = Boolean(arg) ? (alt ? "yes" : "true") : (alt ? "no" : "false");
if(c == /*Y*/ 89) O = O.toUpperCase();
PREC_STR(O, prec)
WIDTH(O, width, flags)
break;

39
bits/55_bytelen.js Normal file

@ -0,0 +1,39 @@
#define SETBYTES(n) { bytes = n; }
#define SETBYTESC(n) { if(bytes == SIZEOF_INT) bytes = n; }
switch(len) {
/* char */
case "hh": SETBYTES(SIZEOF_CHAR) break;
/* short */
case "h": SETBYTES(SIZEOF_SHORT) break;
/* long */
case "l": SETBYTESC(SIZEOF_LONG) break;
/* long long */
case "L":
case "q":
case "ll": SETBYTESC(SIZEOF_LONG_LONG) break;
/* intmax_t */
case "j": SETBYTESC(SIZEOF_INTMAX_T) break;
/* ptrdiff_t */
case "t": SETBYTESC(SIZEOF_PTRDIFF_T) break;
/* size_t */
case "z":
case "Z": SETBYTESC(SIZEOF_SIZE_T) break;
/* CRT size_t or ptrdiff_t */
case "I":
#if SIZEOF_PTRDIFF_T == SIZEOF_SIZE_T
SETBYTESC(SIZEOF_SIZE_T)
#else
if(sign) SETBYTESC(SIZEOF_PTRDIFF_T)
else SETBYTESC(SIZEOF_SIZE_T)
#endif
break;
/* CRT wchar_t */
case "w": break;
}

87
bits/60_integer.js Normal file

@ -0,0 +1,87 @@
Vnum = Number(arg);
/* parse byte length field */
#include "55_bytelen.js"
/* restrict value */
#define _DOIT(msk, max) Vnum = (Vnum & msk); if(sign && (Vnum > max)) Vnum -= (msk + 1)
switch(bytes) {
case 1: _DOIT(0xFF, 0x7F); break;
case 2: _DOIT(0xFFFF, 0x7FFF); break;
case 4: Vnum = sign ? (Vnum | 0) : (Vnum >>> 0); break;
default: Vnum = Math.round(Vnum); break;
}
#undef _DOIT
/* 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) + PADS(8 - O.length, "0") + O;
O = PADS(16 - O.length, "f") + O;
if(radix == 16) O = O.toUpperCase();
} else if(radix == 8) {
O = (Vnum>>>0).toString(8);
O = PADS(10 - O.length, "0") + 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" + PADS(21 - O.length, "7") + O;
} else {
Vnum = (-Vnum) % 1e16;
var d1/*:Array<number>*/ = [1,8,4,4,6,7,4,4,0,7,3,7,0,9,5,5,1,6,1,6];
var di = 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 = PADS(prec - O.length, "0") + O;
else O = O.substr(0,1) + PADS(prec + 1 - O.length, "0") + 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 + PADS((width - O.length), " ");
} else if(flags.indexOf("0") > -1 && prec < 0 && O.length > 0) {
if(prec > O.length) O = PADS((prec - O.length), "0") + O;
pad = PADS((width - O.length), (prec > 0 ? " " : "0"));
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 = PADS((width - O.length), " ") + O;
}
}
}

137
bits/70_float.js Normal file

@ -0,0 +1,137 @@
Vnum = Number(arg);
if(len == "L") bytes = 12;
var isf = 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) { NUM(11); prec = prec -(E + 1); }
else { NUM(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 < Math.pow(10,21)) {
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 + PADS(E - O.length + 1, "0");
if(alt || (prec > 0 && isnum !== 11)) O = O + "." + PADS(prec, "0");
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)?"."+PADS(prec,"0"):"")+"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<any>*/ = 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 */
#ifndef DO_NOT_NORMALIZE
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 */
}
#endif
O = Vnum.toString(16);
if(O.length > 1) {
if(O.length > prec+2 && O.charCodeAt(prec+2) >= 56) {
var _f = 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) + PADS((prec + 2 - O.length), "0") + O.substr(1);
else O += PADS((prec + 2 - O.length), "0");
}
} else if(prec === 0) O = O.charAt(0) + (alt ? "." : "");
} else if(prec > 0) O = O + "." + PADS(prec,"0");
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 + PADS((width - O.length), " ");
} else if(flags.indexOf("0") > -1 && O.length > 0 && isf) {
pad = PADS((width - O.length), "0");
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 = PADS((width - O.length), " ") + O;
}
}
if(c < 96) O = O.toUpperCase();

8
bits/80_wrapper.js Normal file

@ -0,0 +1,8 @@
function vsprintf(fmt/*:string*/, args/*:Array<any>*/) { return doit(tokenize(fmt), args); }
//function sprintf() { return doit(tokenize(arguments[0]), Array.prototype.slice.call(arguments, 1)); }
function sprintf()/*:string*/ {
var args/*:Array<any>*/ = new Array(arguments.length - 1);
for(var i = 0; i < args.length; ++i) args[i] = arguments[i+1];
return doit(tokenize(arguments[0]), args);
}

4
bits/90_exports.js Normal file

@ -0,0 +1,4 @@
PRINTJ.sprintf = sprintf;
PRINTJ.vsprintf = vsprintf;
PRINTJ._doit = doit;
PRINTJ._tokenize = tokenize;

2
bits/99_footer.js Normal file

@ -0,0 +1,2 @@
#include "90_exports.js"
}));

24
bits/Makefile Normal file

@ -0,0 +1,24 @@
ifndef OUTDIR
OUTDIR=$(PWD)/lib
endif
JSFILES=$(wildcard *.js)
LIBS=$(filter-out $(wildcard [0-9]*_*.js),$(wildcard *.js))
OUTLIBS=$(patsubst %,$(OUTDIR)/%,$(LIBS))
CPPFLAGS=-P -C -Wno-invalid-pp-token -fdollars-in-identifiers -nostdinc -Wundef -undef $(JSFLAGS)
.PHONY: lib
lib: init $(JSFILES)
for i in $(LIBS); do cpp $(CPPFLAGS) $$i | uniq > $(OUTDIR)/$$i; done
.PHONY: init
init: 30_ctypes.js
30_ctypes.js: 30_ctypes.njs
node 30_ctypes.njs > 30_ctypes.js
.PHONY: clean
clean:
rm $(OUTLIBS) 30_ctypes.js

8
bits/ctypes.json Normal file

@ -0,0 +1,8 @@
{
"ModelNames":["LP64", "ILP32", "LLP64"],
"Models": {
"LP64": { "char":1, "short":2, "int":4, "long":8, "longlong":8, "wint_t":4, "wchar_t":4, "size_t":8, "intmax_t":8, "ptrdiff_t":8 },
"ILP32": { "char":1, "short":2, "int":4, "long":4, "longlong":8, "wint_t":4, "wchar_t":4, "size_t":4, "intmax_t":8, "ptrdiff_t":4 },
"LLP64": { "char":1, "short":2, "int":4, "long":4, "longlong":8, "wint_t":2, "wchar_t":2, "size_t":8, "intmax_t":8, "ptrdiff_t":8 }
}
}

11
bits/flow.js Normal file

@ -0,0 +1,11 @@
/*::
type ParsedFmt = Array<Array<any>>;
type Args = Array<any>;
declare module "exit-on-epipe" { };
declare module "./" {
declare function sprintf(fmt:string, ...args:any):string;
declare function vsprintf(fmt:string, args:Args):string;
declare function _doit(t:ParsedFmt, args:Args):string;
declare function _tokenize(fmt:string):ParsedFmt;
};
*/

7
bits/index_char.js Normal file

@ -0,0 +1,7 @@
#include "00_header.js"
#define USE_INDEX
#define USE_CHAR
#include "10_tokenize.js"
#include "50_doit.js"
#include "80_wrapper.js"
#include "99_footer.js"

7
bits/index_code.js Normal file

@ -0,0 +1,7 @@
#include "00_header.js"
#define USE_INDEX
#define USE_CODE
#include "10_tokenize.js"
#include "50_doit.js"
#include "80_wrapper.js"
#include "99_footer.js"

7
bits/loop_char.js Normal file

@ -0,0 +1,7 @@
#include "00_header.js"
#define USE_LOOP
#define USE_CHAR
#include "10_tokenize.js"
#include "50_doit.js"
#include "80_wrapper.js"
#include "99_footer.js"

7
bits/loop_code.js Normal file

@ -0,0 +1,7 @@
#include "00_header.js"
#define USE_LOOP
#define USE_CODE
#include "10_tokenize.js"
#include "50_doit.js"
#include "80_wrapper.js"
#include "99_footer.js"

7
bits/regex.js Normal file

@ -0,0 +1,7 @@
#include "00_header.js"
#include "12_regexp.js"
#define USE_REGEX
#include "10_tokenize.js"
#include "50_doit.js"
#include "80_wrapper.js"
#include "99_footer.js"

70
index.html Normal file

@ -0,0 +1,70 @@
<!DOCTYPE html>
<!-- printj.js (C) 2016-present SheetJS http://sheetjs.com -->
<!-- vim: set ts=2: -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>PRINTJ Live Demo</title>
<style>
table, td {
border: 1px dotted black;
text-align: left;
}
</style>
</head>
<body>
<b>PRINTJ Live Demo</b><br />
<a href="https://git.io/printj">Source Code Repo</a><br />
<a href="https://git.io/printj_issues">Issues? Something look weird? Click here and report an issue</a><br />
<br />
<pre>
usage: var printf = PRINTJ.sprintf;
This implementation supports the full POSIX set of conversions. Consult the enclosed README for full details.
</pre>
<div><a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/printf.html">POSIX documentation</a></div><br />
<table id="data">
<tr><th>example</th><th>result</th></tr>
</table>
<script type="text/javascript">/* jshint browser:true, evil:true */</script>
<script src="printj.js"></script>
<script type="text/javascript">
var table = document.getElementById("data");
var ex = [
'printf("|Hello %s|", "SheetJS")',
'printf("|%1$d %1$o %1$u %1$x %1$X|", 1234)',
'printf("|%1$d %1$o %1$u %1$x %1$X|", -1234)',
'printf("|%1$d %1$o %1$u %1$x %1$X %1$lld|", 2813308004)',
'printf("|%1$f %1$F %1$e %1$E %1$g %1$G %1$a %1$A|", 1.2)',
'printf("|%1$f %1$F %1$e %1$E %1$g %1$G %1$a %1$A|", Math.PI)',
'printf("|%1$f %1$F %1$e %1$E %1$g %1$G %1$a %1$A|", Math.LOG2E)',
'printf("|%1$f %1$F %1$e %1$E %1$g %1$G %1$a %1$A|", -Math.LOG10E)',
'printf("|%1$f %1$F %1$e %1$E %1$g %1$G %1$a %1$A|", Infinity)',
'printf("|%1$f %1$F %1$e %1$E %1$g %1$G %1$a %1$A|", -0)',
'printf("|%012s|", "SheetJS")',
'printf("|%-12s|", "SheetJS")',
'printf("|%1$f %1$F %1$e %1$E %1$g %1$G %1$a %1$A|", 1.4142E69)',
'printf("%%")'
];
window.onload = function() {
var printf = PRINTJ.sprintf;
ex.forEach(function(m) {
var row = table.insertRow(table.rows.length);
var c1 = row.insertCell(0); c1.innerHTML = "<pre>" + m + "</pre>";
var c2 = row.insertCell(1); c2.innerHTML = "<pre>" + eval(m) + "</pre>";
});
};
</script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-36810333-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</body>
</html>

6
lib/.gitignore vendored Normal file

@ -0,0 +1,6 @@
flow.js
index_char.js
index_code.js
loop_char.js
loop_code.js
regex.js

5
lib/impl.json Normal file

@ -0,0 +1,5 @@
[
"loop_char",
"loop_code",
"regex"
]

42
misc/help.sh Executable file

@ -0,0 +1,42 @@
#!/bin/bash
# make_help.sh -- process listing of targets and special items in Makefile
# Copyright (C) 2016-present SheetJS
#
# usage in makefile: pipe the output of the following command:
# @grep -hE '(^[a-zA-Z_-][ a-zA-Z_-]*:.*?|^#[#*])' $(MAKEFILE_LIST)
#
# lines starting with "## " are treated as subtitles
# lines starting with "#* " are treated as plaintext comments
# multiple targets with "## " after the ":" are rendered as separate targets
# if the presumed default target is labeled, it will be assigned a unique color
awk '
BEGIN{recipes=0;}
!/#[#*] .*$/ {next;}
{multi=0; isrecipe=0;}
/^[^#]*:/ {isrecipe=1; ++recipes;}
/^[^ :]* .*:/ {multi=1}
multi==0 && isrecipe>0 { if(recipes > 1) print; else print $0, "[default]"; next}
isrecipe == 0 {print; next}
multi>0 {
k=split($0, msg, "##"); m=split($0, a, ":"); n=split(a[1], b, " ");
for(i=1; i<=n; ++i) print b[i] ":", "##" msg[2], (recipes==1 && i==1 ? "[default]" : "")
}
END {}
' | if [[ -t 1 ]]; then
awk '
BEGIN {FS = ":.*?## "}
{color=36}
/\[default\]/ {color=35}
NF==1 && /^##/ {color=34}
NF==1 && /^#\*/ {color=20; $1 = substr($1, 4)}
{printf "\033[" color "m%-20s\033[0m %s\n", $1, $2;}
END{}' -
else
awk '
BEGIN {FS = ":.*?## "}
/^#\* / {$1 = substr($1, 4)}
{printf "%-20s %s\n", $1, $2;}
END{}' -
fi

14
misc/spin.sh Executable file

@ -0,0 +1,14 @@
#!/bin/bash
# spin.sh -- show a spinner (for coverage test)
# Copyright (C) 2014-present SheetJS
wpid=$1
delay=1
str="|/-\\"
while [ $(ps -a|awk '$1=='$wpid' {print $1}') ]; do
t=${str#?}
printf " [%c]" "$str"
str=$t${str%"$t"}
sleep $delay
printf "\b\b\b\b"
done

29
package.json Normal file

@ -0,0 +1,29 @@
{
"name": "printj",
"version": "0.1.0",
"author": "sheetjs",
"description": "Pure-JS printf",
"keywords": [ "printf", "sprintf", "format", "string" ],
"bin": {
"printj": "./bin/printj.njs"
},
"main": "./printj",
"dependencies": {
},
"devDependencies": {
"mocha":""
},
"repository": { "type":"git", "url":"git://github.com/SheetJS/printj.git" },
"scripts": {
"test": "make test"
},
"config": {
"blanket": {
"pattern": "printj.js"
}
},
"files": ["printj.js", "bin/printj.njs", "LICENSE", "README.md"],
"bugs": { "url": "https://github.com/SheetJS/printj/issues" },
"license": "Apache-2.0",
"engines": { "node": ">=0.8" }
}

605
printj.flow.js Normal file

@ -0,0 +1,605 @@
/* 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: any; */
/*:: declare var define: any; */
var PRINTJ/*:any*/;
(function (factory/*:(a:any)=>void*/)/*:void*/ {
/*jshint ignore:start */
if(typeof DO_NOT_EXPORT_PRINTJ === 'undefined') {
if('object' === typeof exports) {
factory(exports);
} else if ('function' === typeof define && define.amd) {
define(function () {
var module/*:any*/ = {};
factory(module);
return module;
});
} else {
factory(PRINTJ = {});
}
} else {
factory(PRINTJ = {});
}
/*jshint ignore:end */
}(function(PRINTJ) {
PRINTJ.version = '0.1.0';
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 = {
" ": " ",
"0": "000000000000000000000000000000000",
"7": "777777777777777777777777777777777",
"f": "fffffffffffffffffffffffffffffffff"
};
/*:: declare var util:any; */
/*:: declare var require: any; */
if(typeof util=='undefined' && typeof require!=='undefined')util=require("util");
var u_inspect/*:(o:any)=>string*/ = (typeof util != 'undefined') ? util.inspect : JSON.stringify;
function doit(t/*:ParsedFmt*/, args/*:Array<any>*/)/*:string*/ {
var o/*:Array<string>*/ = [];
var argidx/*:number*/ = 0, idx/*:number*/ = 0;
var Vnum/*:number*/ = 0;
var pad/*:string*/ = "";
for(var i = 0; i < t.length; ++i) {
var m/*:Array<any>*/ = 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 = 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.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 : Number(arg.l);
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 = 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 = 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 = Boolean(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(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 = 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<number>*/ = [1,8,4,4,6,7,4,4,0,7,3,7,0,9,5,5,1,6,1,6];
var di = 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(len == "L") bytes = 12;
var isf = 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 < Math.pow(10,21)) {
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<any>*/ = 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 = 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/*:Array<any>*/) { return doit(tokenize(fmt), args); }
//function sprintf() { return doit(tokenize(arguments[0]), Array.prototype.slice.call(arguments, 1)); }
function sprintf()/*:string*/ {
var args/*:Array<any>*/ = new Array(arguments.length - 1);
for(var i = 0; i < args.length; ++i) args[i] = arguments[i+1];
return doit(tokenize(arguments[0]), args);
}
PRINTJ.sprintf = sprintf;
PRINTJ.vsprintf = vsprintf;
PRINTJ._doit = doit;
PRINTJ._tokenize = tokenize;
}));

601
printj.js Normal file

@ -0,0 +1,601 @@
/* printj.js (C) 2016-present SheetJS -- http://sheetjs.com */
/* vim: set ts=2: */
/*jshint sub:true, eqnull:true */
/*exported PRINTJ */
var PRINTJ;
(function (factory) {
/*jshint ignore:start */
if(typeof DO_NOT_EXPORT_PRINTJ === 'undefined') {
if('object' === typeof exports) {
factory(exports);
} else if ('function' === typeof define && define.amd) {
define(function () {
var module = {};
factory(module);
return module;
});
} else {
factory(PRINTJ = {});
}
} else {
factory(PRINTJ = {});
}
/*jshint ignore:end */
}(function(PRINTJ) {
PRINTJ.version = '0.1.0';
function tokenize(fmt) {
var out = [];
var start = 0;
var i = 0;
var infmt = false;
var fmtparam = "", fmtflags = "", fmtwidth = "", fmtprec = "", fmtlen = "";
var c = 0;
var L = 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 = {
" ": " ",
"0": "000000000000000000000000000000000",
"7": "777777777777777777777777777777777",
"f": "fffffffffffffffffffffffffffffffff"
};
if(typeof util=='undefined' && typeof require!=='undefined')util=require("util");
var u_inspect = (typeof util != 'undefined') ? util.inspect : JSON.stringify;
function doit(t, args) {
var o = [];
var argidx = 0, idx = 0;
var Vnum = 0;
var pad = "";
for(var i = 0; i < t.length; ++i) {
var m = t[i], c = (m[0]).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 = "";
var isnum = 0, radix = 10, bytes = 4, sign = false;
/* flags */
var flags = m[3]||"";
var alt = 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 = args[argidx];
/* grab length */
var len = 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 = 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.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 : Number(arg.l);
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 = 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 = 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 = Boolean(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(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 = 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 = [1,8,4,4,6,7,4,4,0,7,3,7,0,9,5,5,1,6,1,6];
var di = 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(len == "L") bytes = 12;
var isf = 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 = 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 = (Vnum < 0 || 1/Vnum == -Infinity) ? "-" : "";
if(Vnum < 0) Vnum = -Vnum;
switch(isnum) {
/* f/F standard */
case 1: case 11:
if(Vnum < Math.pow(10,21)) {
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 = 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 = O.indexOf(".");
if(O.indexOf("(") > -1) {
/* IE exponential form */
var am = O.match(/\(e(.*)\)/);
var ae = 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 = 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, args) { return doit(tokenize(fmt), args); }
//function sprintf() { return doit(tokenize(arguments[0]), Array.prototype.slice.call(arguments, 1)); }
function sprintf() {
var args = new Array(arguments.length - 1);
for(var i = 0; i < args.length; ++i) args[i] = arguments[i+1];
return doit(tokenize(arguments[0]), args);
}
PRINTJ.sprintf = sprintf;
PRINTJ.vsprintf = vsprintf;
PRINTJ._doit = doit;
PRINTJ._tokenize = tokenize;
}));

6
stress/.gitignore vendored Normal file

@ -0,0 +1,6 @@
stress
stress.h
stress.njs
tests.h
t1
t2

31
stress/Makefile Normal file

@ -0,0 +1,31 @@
CPPFLAGS=-Wno-format -Wno-constant-conversion
FLAGS=-P -C -Wno-invalid-pp-token -Wno-format
NODEOPTS=--max_old_space_size=4096
.PHONY: all
all: stress stress.njs
.PHONY: tests
tests: tests.h
tests.h: generate_testcase.njs
node generate_testcase.njs > tests.h
stress.h: generate_stress.njs
node $< > $@
stress: stress.c stress.h tests.h
gcc $(CPPFLAGS) -o $@ $<
stress.njs: stress.js stress.h tests.h
cpp -DJAVASCRIPT $(FLAGS) $< > $@
.PHONY: test
test:
./stress | bash ./fix.sh > t1
node $(NODEOPTS) stress.njs | bash ./fix.sh > t2
diff -q t1 t2
.PHONY: clean
clean:
@rm -f stress stress.njs tests.h stress.h t1 t2

44
stress/common.h Normal file

@ -0,0 +1,44 @@
#define SUFFIX(arg, suffix) arg ## suffix
#define PREFIX(arg, prefix) prefix ## arg
#ifdef JAVASCRIPT
#define INFINITY Infinity
#define CAST_U(x) x
#define CAST_LD(x) x
#define CAST_WSTR(x) x
#define CAST_CHAR(x) (typeof (x) == 'string' ? (x).charCodeAt(0) : x)
#define CAST_UCHAR(x) (typeof (x) == 'string' ? (x).charCodeAt(0) : x)
#define CAST_SHORT(x) x
#define CAST_USHORT(x) x
#define CAST_L(x) x
#define CAST_UL(x) x
#define CAST_LL(x) x
#define CAST_ULL(x) x
#define CAST_INTMAX_T(x) x
#define CAST_UINTMAX_T(x) x
#define CAST_SIZE_T(x) x
#define CAST_SSIZE_T(x) x
#define CAST_PTRDIFF_T(x) x
#define CAST_UINTPTR_T(x) x
#else
#define CAST_U(x) SUFFIX(x, u)
#define CAST_LD(x) SUFFIX(x, L)
#define CAST_WSTR(x) PREFIX(x, L)
#define CAST_CHAR(x) x
#define CAST_UCHAR(x) (unsigned char)x
#define CAST_SHORT(x) (short)x
#define CAST_USHORT(x) (unsigned short)CAST_U(x)
#define CAST_L(x) SUFFIX(x, l)
#define CAST_UL(x) SUFFIX(x, ul)
#define CAST_LL(x) SUFFIX(x, ll)
#define CAST_ULL(x) SUFFIX(x, ull)
#define CAST_INTMAX_T(x) (intmax_t)x
#define CAST_UINTMAX_T(x) (uintmax_t)x
#define CAST_SIZE_T(x) (size_t)x
#define CAST_SSIZE_T(x) (ssize_t)x
#define CAST_PTRDIFF_T(x) (ptrdiff_t)x
#define CAST_UINTPTR_T(x) (uintptr_t)x
#endif

3
stress/fix.sh Executable file

@ -0,0 +1,3 @@
#!/bin/bash
# remove last digit from %a %A outputs due to implementation-dependent rounding
cat - | sed 's/.p/0p/; s/.P/0P/'

127
stress/generate_stress.njs Normal file

@ -0,0 +1,127 @@
/* vim: set ft=javascript: */
var S = {
/* valid formats */
"formats":"diouxXfeEgGaAcsCS".split(""),
/* flags and applicable convs */
"flags": [
["'", "diufFgG"],
["-", "diouxXfeEgGaAcsCS"],
["+", "diaAeEfFgG"],
[" ", "diaAeEfFgG"],
["#", "oxXaAeEfFgG"],
["0", "diouxXaAeEfFgG"]
],
/* lengths and applicable convs */
"lengths": [
["hh", "diouxX"],
["h", "diouxX"],
["l", "diouxXcs"],
["ll", "diouxX"],
["j", "diouxX"],
["z", "diouxX"],
["t", "diouxX"],
["L", "aAeEfFgG"]
],
"widths": [ "", "0", "1", "2", "4", "6", "8", "12" ],
"precs": [ "", "0", "1", "2", "4", "6", "8", "12" ].map(function(x) { return x == "" ? "" : "." + x; }),
/* implied data types */
"types": [
["i", "dicC"],
["u", "ouxX"],
["d", "feEfFgGaA"],
["s", "sS"],
],
/* override for lengths */
"typelen": { "C": "l", "S": "l" }
};
/*
int i; unsigned int u;
double d; long double ld;
char *s; wchar_t *ws;
signed char hhi; unsigned char hhu; short hi; unsigned short hu;
long li; unsigned long lu; long long lli; unsigned long long llu;
intmax_t ji; uintmax_t ju; size_t zi, ssize_t zu; ptrdiff_t ti; uintptr_t tu;
*/
function getarg(format/*:string*/, length/*:string*/)/*:string*/ {
var type = types[format];
if(!length) length = S.typelen[format] || "";
if(!length) return type;
switch(type) {
case 's': return "ws";
case 'd': if(length == "L") return "ld";
case 'i': case 'u': return length + type;
}
return type;
}
var platform = require("os").platform();
function doit(format/*:string*/, flag/*:string*/, length/*:string*/, width/*:string*/, prec/*:string*/) {
/* skip wide char */
if(format == "C" || (format == "c" && length == "l")) return;
/* skip char */
if(format == "c") return;
/* skip precision-less long double */
if(format.toLowerCase() == "a" && length == "L" && prec == "") return;
var fmt = "%" + flag + width + prec + length + format;
var printf_stmt = 'printf("' + fmt + '\\n", ' + getarg(format, length) + ');';
console.log(printf_stmt);
}
function pick(arr/*:Array<string>*/)/*:Array<string>*/ {
var O = [];
for(var i = 0; i < (1<<arr.length); ++i) {
var o = "";
for(var j = 0; j < arr.length; ++j) if(i & (1<<j)) o += arr[j];
O.push(o);
}
return O;
}
var flags = {}, lengths = {}, types = {};
S.formats.forEach(function(f) { flags[f]=[]; lengths[f]=[""]; types[f]=""; });
S.types.forEach(function(f) { var g = f[1].split(""); g.forEach(function(h) { types[h] = f[0]; }); });
S.lengths.forEach(function(f) {
var g = f[1].split("");
g.forEach(function(h) { if(lengths[h]) lengths[h].push(f[0]); });
});
S.flags.forEach(function(f) {
var g = f[1].split("");
g.forEach(function(h) { if(flags[h]) flags[h].push(f[0]); });
});
S.formats.forEach(function(f) { flags[f] = pick(flags[f]); });
var filters = {
dint: "diu",
oint: "o",
hint: "xX",
ddbl: "feEgG",
hdbl: "aA",
chr: "cC",
str: "sS"
};
filters.int = filters.dint + filters.hint + filters.oint;
filters.dbl = filters.ddbl + filters.hdbl;
filters.all = filters.int + filters.dbl + filters.chr + filters.str;
var filter = filters.all;
S.formats.forEach(function(format) {
if(filter && filter.indexOf(format) == -1) return;
flags[format].forEach(function(flag) {
lengths[format].forEach(function(length) {
S.widths.forEach(function(width) {
S.precs.forEach(function(prec) {
doit(format, flag, length, width, prec);
});
});
});
});
});

@ -0,0 +1,88 @@
/* unsigned int */
var uints = [ "0", "1", "2", "4", "8", "16", "32", "64", "69", "128", "256", "259", "512", "1024", "1559","2048", "3333", "4096", "5678", "8192", "10111", "16384", "24681", "32768", "45678", "65536", "121211", "131072", "141427", "262144", "314159", "524288", "888888", "1048576", "1515151", "2097152", "3333333", "4194304", "4565456", "8388608", "13245125", "16777216", "27182818", "33554432", "34567654", "67108864", "99999999", "134217728", "201201201", "268435456", "298929892","536870912", "1000000000", "1073741824", "2011021011", "2147483648", "3743743743", "4294967296", "7876787678", "8589934592", "11111111111", "17179869184", "21347111829", "34359738368", "45678901234", "68719476736", "78987898789", "137438953472", "137438953473", "274877906944", "274877906945", "549755813888", "549755813889", "1099511627776", "1099511627777", "2199023255552", "2199023255553", "4398046511104", "4398046511105", "8796093022208", "8796093022209", "17592186044416", "17592186044417", "35184372088832", "35184372088833", "70368744177664", "70368744177665", "140737488355328", "140737488355329", "281474976710656", "281494978750657", "562949953421312", "562949953421313","1125899906842624","1125899906843625", "2251799813685248" ]
/* int */
var ints = []; for(var i = 0; i < uints.length; ++i) ints.push((i % 2 ? "-" : "") + uints[i]);
function posneg(o,i) { i.forEach(function(x) { o.push(x); o.push("-" + x); }); }
/* double / long double */
var pdbl = [
"0.", "INFINITY", "1.", "500.", ".0003",
"1.2", ".000000000069", "69000000000.",
"2.51", "4.37e19", "8.17e-19",
"3.141", "2.718e19", "5.772e-19"
];
var doubles = []; posneg(doubles, pdbl);
/* char * / wchar_t * */
var strings = ['"v"', '"pq"', '"var"', '"rama"', '"sheet"', '"sheets"', '"sheetjs"', '"somberi"', '"function"', '"variadics"', '"javascript"', '"sesquipedalian"'];
/* signed char */
var schar = ["'\\0'", "'A'", "'z'", "'q'", "-1", "-127", "-128", "12345", "-12345"];
/* unsigned char */
var uchar = ["'\\0'", "'A'", "'z'", "'Q'", "-1", "-127", "-128", "12345", "45678"];
/* unsigned long */
var ulongs = ["10", "30", "100", "300", "1000", "1234", "3000", "4321"];
/* long */
var longs = []; posneg(longs, ulongs);
/* unsigned long long */
var ullongs = ["123", "456", "4543216", "1123412343", "100000000000", "2131000000000", "987654321", "1000123000000", "1234567890", "2718281828", "31415926535", "677215664901", "16180339887", "4669201609", "2813308004"];
for(var i = 1; i < uints.length; i+=2) ullongs.push(uints[i]);
/* long long */
var llongs = []; posneg(llongs, ullongs);
var tests = [
ints,
uints,
doubles,
doubles,
strings, strings,
schar, uchar,
ints,
uints,
longs,
ulongs,
llongs,
ullongs,
llongs,
ullongs,
ullongs,
llongs,
llongs,
ullongs
]
var maxlen = 0;
for(var i = 0; i < tests.length; ++i) maxlen = Math.max(maxlen,tests[i].length);
for(var j = 0; j < maxlen; ++j) {
var args = [];
for(i=0; i < tests.length; ++i) args.push(tests[i][j % tests[i].length]);
[ "stress(",
" " + args[0] + ",",
" CAST_U(" + args[1] + "),",
" " + args[2] + ",",
" " + (args[3].match(/INF/) ? "" : "CAST_LD") + "(" + args[3] + "),",
" " + args[4] + ",",
" CAST_WSTR(" + args[5] + "),",
" CAST_CHAR(" + args[6] + "),",
" CAST_UCHAR(" + args[7] + "),",
" CAST_SHORT(" + args[8] + "),",
" CAST_USHORT(" + args[9] + "),",
" CAST_L(" + args[10] + "),",
" CAST_UL(" + args[11] + "),",
" CAST_LL(" + args[12] + "),",
" CAST_ULL(" + args[13] + "),",
" CAST_INTMAX_T(" + args[14] + "),",
" CAST_UINTMAX_T(" + args[15] + "),",
" CAST_SIZE_T(" + args[16] + "),",
" CAST_SSIZE_T(" + args[17] + "),",
" CAST_PTRDIFF_T(" + args[18] + "),",
" CAST_UINTPTR_T(" + args[19] + ")",
");\n"].forEach(function(x) { console.log(x); });
}

22
stress/stress.c Normal file

@ -0,0 +1,22 @@
#include <wchar.h>
#include <stdint.h>
#include <string.h>
#include <stddef.h>
#include <math.h>
#include "common.h"
void stress(
int i, unsigned int u,
double d, long double ld,
char *s, wchar_t *ws,
signed char hhi, unsigned char hhu, short hi, unsigned short hu,
long li, unsigned long lu, long long lli, unsigned long long llu,
intmax_t ji, uintmax_t ju, size_t zi, ssize_t zu, ptrdiff_t ti, uintptr_t tu
) {
#include "stress.h"
}
int main() {
#include "tests.h"
}

23
stress/stress.js Normal file

@ -0,0 +1,23 @@
#ifndef JAVASCRIPT
#define JAVASCRIPT
#endif
var sprintf = require("../");
var printf = function() {
var o = sprintf.sprintf.apply(null, arguments);
process.stdout.write(o);
}
function stress(
i, u,
d, ld,
s, ws,
hhi, hhu, hi, hu,
li, lu, lli, llu,
ji, ju, zi, zu, ti, tu
) {
#include "stress.h"
}
#include "common.h"
#include "tests.h"

39
test.js Normal file

@ -0,0 +1,39 @@
var assert = require("assert");
var IMPL = require("./lib/impl.json");
var COMPARE = require("./tests/compare.json");
var PRINTF = require("./tests/printf");
var IMPLS = {}, IMPLA = [];
IMPL.forEach(function(impl, i) { IMPLS[impl] = IMPLA[i] = require("./lib/" + impl); });
IMPL.push("base"); IMPLS["base"] = IMPLA[IMPL.length-1] = require("./");
function compare_parse(a,b) {
assert.equal(a.length, b.length);
for(var i = 0; i < a.length; ++i) {
for(var j = 0; j < a[i].length; ++j) {
if((a[i][j] || "") != (b[i][j] || "")) {
throw i + "," + j + " " + a[i] + " " + b[i];
}
}
}
}
describe('consensus', function() {
it('tokenizer', function() {
COMPARE.forEach(function(m) {
var base = IMPLA[0]._tokenize(m);
for(var i = 1; i < IMPLA.length; ++i) compare_parse(base, IMPLA[i]._tokenize(m));
});
});
});
describe('correctness', function() {
IMPL.forEach(function(n,i) {
it(n, function() {
PRINTF.forEach(function(v) {
assert.equal(IMPLA[i].sprintf.apply(IMPLA[i], v[0]), v[1]);
});
});
});
});

12
tests/compare.json Normal file

@ -0,0 +1,12 @@
[
"Hello World!",
"Hello %s!",
"Hello %s %s%s!",
"Hello %s %s%s %s%s%s :(",
"Hello %s %s%s %s%s%s %s%s%s%s",
"Hello %x %s%02hhx %s%X%s %s%X%s%X",
"Hello %s %s%s %s%s%s %s%s%s%s %s%s%s%s%102f %f",
"Hello %1$s!",
"Hello %1$*.*s!",
"Hello %1$*2$.*3$s!"
]

89926
tests/printf.js Normal file

File diff suppressed because it is too large Load Diff