emscripten/src/library_formatString.js

502 строки
18 KiB
JavaScript

/**
* @license
* Copyright 2015 The Emscripten Authors
* SPDX-License-Identifier: MIT
*/
mergeInto(LibraryManager.library, {
$reallyNegative: function(x) {
return x < 0 || (x === 0 && (1/x) === -Infinity);
},
// Converts a value we have as signed, into an unsigned value. For
// example, -1 in int32 would be a very large number as unsigned.
$unSign: function(value, bits) {
if (value >= 0) {
return value;
}
// Need some trickery, since if bits == 32, we are right at the limit of the
// bits JS uses in bitshifts
return bits <= 32 ? 2*Math.abs(1 << (bits-1)) + value
: Math.pow(2, bits) + value;
},
// Converts a value we have as unsigned, into a signed value. For
// example, 200 in a uint8 would be a negative number.
$reSign: function(value, bits) {
if (value <= 0) {
return value;
}
var half = bits <= 32 ? Math.abs(1 << (bits-1)) // abs is needed if bits == 32
: Math.pow(2, bits-1);
// for huge values, we can hit the precision limit and always get true here.
// so don't do that but, in general there is no perfect solution here. With
// 64-bit ints, we get rounding and errors
// TODO: In i64 mode 1, resign the two parts separately and safely
if (value >= half && (bits <= 32 || value > half)) {
// Cannot bitshift half, as it may be at the limit of the bits JS uses in
// bitshifts
value = -2*half + value;
}
return value;
},
// Performs printf-style formatting.
// format: A pointer to the format string.
// varargs: A pointer to the start of the arguments list.
// Returns the resulting string string as a character array.
$formatString__deps: ['$reallyNegative', '$convertI32PairToI53', '$convertU32PairToI53',
'$reSign', '$unSign', 'strlen'
#if MINIMAL_RUNTIME
, '$intArrayFromString'
#endif
],
$formatString: function(format, varargs) {
#if ASSERTIONS
assert((varargs & 3) === 0);
#endif
var textIndex = format;
var argIndex = varargs;
// This must be called before reading a double or i64 vararg. It will bump the pointer properly.
// It also does an assert on i32 values, so it's nice to call it before all varargs calls.
function prepVararg(ptr, type) {
if (type === 'double' || type === 'i64') {
// move so the load is aligned
if (ptr & 7) {
#if ASSERTIONS
assert((ptr & 7) === 4);
#endif
ptr += 4;
}
} else {
#if ASSERTIONS
assert((ptr & 3) === 0);
#endif
}
return ptr;
}
function getNextArg(type) {
// NOTE: Explicitly ignoring type safety. Otherwise this fails:
// int x = 4; printf("%c\n", (char)x);
var ret;
argIndex = prepVararg(argIndex, type);
if (type === 'double') {
ret = {{{ makeGetValue('argIndex', 0, 'double', undefined, undefined, true) }}};
argIndex += 8;
} else if (type == 'i64') {
ret = [{{{ makeGetValue('argIndex', 0, 'i32', undefined, undefined, true, 4) }}},
{{{ makeGetValue('argIndex', 4, 'i32', undefined, undefined, true, 4) }}}];
argIndex += 8;
} else {
#if ASSERTIONS
assert((argIndex & 3) === 0);
#endif
type = 'i32'; // varargs are always i32, i64, or double
ret = {{{ makeGetValue('argIndex', 0, 'i32', undefined, undefined, true) }}};
argIndex += 4;
}
return ret;
}
var ret = [];
var curr, next, currArg;
while (1) {
var startTextIndex = textIndex;
curr = {{{ makeGetValue(0, 'textIndex', 'i8') }}};
if (curr === 0) break;
next = {{{ makeGetValue(0, 'textIndex+1', 'i8') }}};
if (curr == {{{ charCode('%') }}}) {
// Handle flags.
var flagAlwaysSigned = false;
var flagLeftAlign = false;
var flagAlternative = false;
var flagZeroPad = false;
var flagPadSign = false;
flagsLoop: while (1) {
switch (next) {
case {{{ charCode('+') }}}:
flagAlwaysSigned = true;
break;
case {{{ charCode('-') }}}:
flagLeftAlign = true;
break;
case {{{ charCode('#') }}}:
flagAlternative = true;
break;
case {{{ charCode('0') }}}:
if (flagZeroPad) {
break flagsLoop;
} else {
flagZeroPad = true;
break;
}
case {{{ charCode(' ') }}}:
flagPadSign = true;
break;
default:
break flagsLoop;
}
textIndex++;
next = {{{ makeGetValue(0, 'textIndex+1', 'i8') }}};
}
// Handle width.
var width = 0;
if (next == {{{ charCode('*') }}}) {
width = getNextArg('i32');
textIndex++;
next = {{{ makeGetValue(0, 'textIndex+1', 'i8') }}};
} else {
while (next >= {{{ charCode('0') }}} && next <= {{{ charCode('9') }}}) {
width = width * 10 + (next - {{{ charCode('0') }}});
textIndex++;
next = {{{ makeGetValue(0, 'textIndex+1', 'i8') }}};
}
}
// Handle precision.
var precisionSet = false, precision = -1;
if (next == {{{ charCode('.') }}}) {
precision = 0;
precisionSet = true;
textIndex++;
next = {{{ makeGetValue(0, 'textIndex+1', 'i8') }}};
if (next == {{{ charCode('*') }}}) {
precision = getNextArg('i32');
textIndex++;
} else {
while (1) {
var precisionChr = {{{ makeGetValue(0, 'textIndex+1', 'i8') }}};
if (precisionChr < {{{ charCode('0') }}} ||
precisionChr > {{{ charCode('9') }}}) break;
precision = precision * 10 + (precisionChr - {{{ charCode('0') }}});
textIndex++;
}
}
next = {{{ makeGetValue(0, 'textIndex+1', 'i8') }}};
}
if (precision < 0) {
precision = 6; // Standard default.
precisionSet = false;
}
// Handle integer sizes. WARNING: These assume a 32-bit architecture!
var argSize;
switch (String.fromCharCode(next)) {
case 'h':
var nextNext = {{{ makeGetValue(0, 'textIndex+2', 'i8') }}};
if (nextNext == {{{ charCode('h') }}}) {
textIndex++;
argSize = 1; // char (actually i32 in varargs)
} else {
argSize = 2; // short (actually i32 in varargs)
}
break;
case 'l':
var nextNext = {{{ makeGetValue(0, 'textIndex+2', 'i8') }}};
if (nextNext == {{{ charCode('l') }}}) {
textIndex++;
argSize = 8; // long long
} else {
argSize = 4; // long
}
break;
case 'L': // long long
case 'q': // int64_t
case 'j': // intmax_t
argSize = 8;
break;
case 'z': // size_t
case 't': // ptrdiff_t
case 'I': // signed ptrdiff_t or unsigned size_t
argSize = 4;
break;
default:
argSize = null;
}
if (argSize) textIndex++;
next = {{{ makeGetValue(0, 'textIndex+1', 'i8') }}};
// Handle type specifier.
switch (String.fromCharCode(next)) {
case 'd': case 'i': case 'u': case 'o': case 'x': case 'X': case 'p': {
// Integer.
var signed = next == {{{ charCode('d') }}} || next == {{{ charCode('i') }}};
argSize = argSize || 4;
currArg = getNextArg('i' + (argSize * 8));
var argText;
// Flatten i64-1 [low, high] into a (slightly rounded) double
if (argSize == 8) {
currArg = next == {{{ charCode('u') }}} ? convertU32PairToI53(currArg[0], currArg[1]) : convertI32PairToI53(currArg[0], currArg[1]);
}
// Truncate to requested size.
if (argSize <= 4) {
var limit = Math.pow(256, argSize) - 1;
currArg = (signed ? reSign : unSign)(currArg & limit, argSize * 8);
}
// Format the number.
var currAbsArg = Math.abs(currArg);
var prefix = '';
if (next == {{{ charCode('d') }}} || next == {{{ charCode('i') }}}) {
argText = reSign(currArg, 8 * argSize, 1).toString(10);
} else if (next == {{{ charCode('u') }}}) {
argText = unSign(currArg, 8 * argSize, 1).toString(10);
currArg = Math.abs(currArg);
} else if (next == {{{ charCode('o') }}}) {
argText = (flagAlternative ? '0' : '') + currAbsArg.toString(8);
} else if (next == {{{ charCode('x') }}} || next == {{{ charCode('X') }}}) {
prefix = (flagAlternative && currArg != 0) ? '0x' : '';
if (currArg < 0) {
// Represent negative numbers in hex as 2's complement.
currArg = -currArg;
argText = (currAbsArg - 1).toString(16);
var buffer = [];
for (var i = 0; i < argText.length; i++) {
buffer.push((0xF - parseInt(argText[i], 16)).toString(16));
}
argText = buffer.join('');
while (argText.length < argSize * 2) argText = 'f' + argText;
} else {
argText = currAbsArg.toString(16);
}
if (next == {{{ charCode('X') }}}) {
prefix = prefix.toUpperCase();
argText = argText.toUpperCase();
}
} else if (next == {{{ charCode('p') }}}) {
if (currAbsArg === 0) {
argText = '(nil)';
} else {
prefix = '0x';
argText = currAbsArg.toString(16);
}
}
if (precisionSet) {
while (argText.length < precision) {
argText = '0' + argText;
}
}
// Add sign if needed
if (currArg >= 0) {
if (flagAlwaysSigned) {
prefix = '+' + prefix;
} else if (flagPadSign) {
prefix = ' ' + prefix;
}
}
// Move sign to prefix so we zero-pad after the sign
if (argText.charAt(0) == '-') {
prefix = '-' + prefix;
argText = argText.substr(1);
}
// Add padding.
while (prefix.length + argText.length < width) {
if (flagLeftAlign) {
argText += ' ';
} else {
if (flagZeroPad) {
argText = '0' + argText;
} else {
prefix = ' ' + prefix;
}
}
}
// Insert the result into the buffer.
argText = prefix + argText;
argText.split('').forEach(function(chr) {
ret.push(chr.charCodeAt(0));
});
break;
}
case 'f': case 'F': case 'e': case 'E': case 'g': case 'G': {
// Float.
currArg = getNextArg('double');
var argText;
if (isNaN(currArg)) {
argText = 'nan';
flagZeroPad = false;
} else if (!isFinite(currArg)) {
argText = (currArg < 0 ? '-' : '') + 'inf';
flagZeroPad = false;
} else {
var isGeneral = false;
var effectivePrecision = Math.min(precision, 20);
// Convert g/G to f/F or e/E, as per:
// http://pubs.opengroup.org/onlinepubs/9699919799/functions/printf.html
if (next == {{{ charCode('g') }}} || next == {{{ charCode('G') }}}) {
isGeneral = true;
precision = precision || 1;
var exponent = parseInt(currArg.toExponential(effectivePrecision).split('e')[1], 10);
if (precision > exponent && exponent >= -4) {
next = ((next == {{{ charCode('g') }}}) ? 'f' : 'F').charCodeAt(0);
precision -= exponent + 1;
} else {
next = ((next == {{{ charCode('g') }}}) ? 'e' : 'E').charCodeAt(0);
precision--;
}
effectivePrecision = Math.min(precision, 20);
}
if (next == {{{ charCode('e') }}} || next == {{{ charCode('E') }}}) {
argText = currArg.toExponential(effectivePrecision);
// Make sure the exponent has at least 2 digits.
if (/[eE][-+]\d$/.test(argText)) {
argText = argText.slice(0, -1) + '0' + argText.slice(-1);
}
} else if (next == {{{ charCode('f') }}} || next == {{{ charCode('F') }}}) {
argText = currArg.toFixed(effectivePrecision);
if (currArg === 0 && reallyNegative(currArg)) {
argText = '-' + argText;
}
}
var parts = argText.split('e');
if (isGeneral && !flagAlternative) {
// Discard trailing zeros and periods.
while (parts[0].length > 1 && parts[0].includes('.') &&
(parts[0].slice(-1) == '0' || parts[0].slice(-1) == '.')) {
parts[0] = parts[0].slice(0, -1);
}
} else {
// Make sure we have a period in alternative mode.
if (flagAlternative && argText.indexOf('.') == -1) parts[0] += '.';
// Zero pad until required precision.
while (precision > effectivePrecision++) parts[0] += '0';
}
argText = parts[0] + (parts.length > 1 ? 'e' + parts[1] : '');
// Capitalize 'E' if needed.
if (next == {{{ charCode('E') }}}) argText = argText.toUpperCase();
// Add sign.
if (currArg >= 0) {
if (flagAlwaysSigned) {
argText = '+' + argText;
} else if (flagPadSign) {
argText = ' ' + argText;
}
}
}
// Add padding.
while (argText.length < width) {
if (flagLeftAlign) {
argText += ' ';
} else {
if (flagZeroPad && (argText[0] == '-' || argText[0] == '+')) {
argText = argText[0] + '0' + argText.slice(1);
} else {
argText = (flagZeroPad ? '0' : ' ') + argText;
}
}
}
// Adjust case.
if (next < {{{ charCode('a') }}}) argText = argText.toUpperCase();
// Insert the result into the buffer.
argText.split('').forEach(function(chr) {
ret.push(chr.charCodeAt(0));
});
break;
}
case 's': {
// String.
var arg = getNextArg('i8*');
var argLength = arg ? _strlen(arg) : '(null)'.length;
if (precisionSet) argLength = Math.min(argLength, precision);
if (!flagLeftAlign) {
while (argLength < width--) {
ret.push({{{ charCode(' ') }}});
}
}
if (arg) {
for (var i = 0; i < argLength; i++) {
ret.push({{{ makeGetValue('arg++', 0, 'i8', null, true) }}});
}
} else {
ret = ret.concat(intArrayFromString('(null)'.substr(0, argLength), true));
}
if (flagLeftAlign) {
while (argLength < width--) {
ret.push({{{ charCode(' ') }}});
}
}
break;
}
case 'c': {
// Character.
if (flagLeftAlign) ret.push(getNextArg('i8'));
while (--width > 0) {
ret.push({{{ charCode(' ') }}});
}
if (!flagLeftAlign) ret.push(getNextArg('i8'));
break;
}
case 'n': {
// Write the length written so far to the next parameter.
var ptr = getNextArg('i32*');
{{{ makeSetValue('ptr', '0', 'ret.length', 'i32') }}};
break;
}
case '%': {
// Literal percent sign.
ret.push(curr);
break;
}
default: {
// Unknown specifiers remain untouched.
for (var i = startTextIndex; i < textIndex + 2; i++) {
ret.push({{{ makeGetValue(0, 'i', 'i8') }}});
}
}
}
textIndex += 2;
// TODO: Support a/A (hex float) and m (last error) specifiers.
// TODO: Support %1${specifier} for arg selection.
} else {
ret.push(curr);
textIndex += 1;
}
}
return ret;
},
// printf/puts/strlen implementations for when musl is not pulled in - very
// partial. useful for tests, and when bootstrapping structInfo
strlen: function(ptr) {
var end = ptr;
while (HEAPU8[end]) ++end;
return end - ptr;
},
printf__deps: ['$formatString'
#if MINIMAL_RUNTIME
, '$intArrayToString'
#endif
],
printf: function(format, varargs) {
// int printf(const char *restrict format, ...);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/printf.html
// extra effort to support printf, even without a filesystem. very partial, very hackish
var result = formatString(format, varargs);
var string = intArrayToString(result);
if (string[string.length-1] === '\n') string = string.substr(0, string.length-1); // remove a final \n, as Module.print will do that
out(string);
return result.length;
},
puts: function(s) {
// extra effort to support puts, even without a filesystem. very partial, very hackish
var result = UTF8ToString(s);
var string = result.substr(0);
if (string[string.length-1] === '\n') string = string.substr(0, string.length-1); // remove a final \n, as Module.print will do that
out(string);
return result.length;
},
});