initial commit
This commit is contained in:
commit
d217d96d5c
26
.flowconfig
Normal file
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
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
misc/coverage.html
|
||||
|
6
.jscs.json
Normal file
6
.jscs.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"requireCommaBeforeLineBreak": true,
|
||||
"disallowTrailingWhitespace": true,
|
||||
"disallowTrailingComma": true
|
||||
}
|
||||
|
16
.travis.yml
Normal file
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
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
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
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
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
1
bits/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
30_ctypes.js
|
27
bits/00_header.js
Normal file
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
1
bits/01_version.js
Normal file
@ -0,0 +1 @@
|
||||
PRINTJ.version = '0.1.0';
|
78
bits/10_tokenize.js
Normal file
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
2
bits/99_footer.js
Normal file
@ -0,0 +1,2 @@
|
||||
#include "90_exports.js"
|
||||
}));
|
24
bits/Makefile
Normal file
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
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
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
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
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
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
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
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
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
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
5
lib/impl.json
Normal file
@ -0,0 +1,5 @@
|
||||
[
|
||||
"loop_char",
|
||||
"loop_code",
|
||||
"regex"
|
||||
]
|
42
misc/help.sh
Executable file
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
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
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
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
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
6
stress/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
stress
|
||||
stress.h
|
||||
stress.njs
|
||||
tests.h
|
||||
t1
|
||||
t2
|
31
stress/Makefile
Normal file
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
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
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
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
88
stress/generate_testcase.njs
Normal file
88
stress/generate_testcase.njs
Normal file
@ -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
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
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
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
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
89926
tests/printf.js
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user