Last active
February 19, 2024 11:18
-
-
Save VICTORVICKIE/faa7a7252607f600fb114781fd4d1ade to your computer and use it in GitHub Desktop.
sprintf in js wasm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
> | |
<script> | |
let wasm; | |
function make_environment(...envs) { | |
return new Proxy(envs, { | |
get(target, prop, receiver) { | |
for (let env of envs) { | |
if (env.hasOwnProperty(prop)) { | |
return env[prop]; | |
} | |
} | |
return (...args) => { | |
console.error("NOT IMPLEMENTED: " + prop, args); | |
}; | |
}, | |
}); | |
} | |
function cstrlen(mem, ptr) { | |
let len = 0; | |
while (mem[ptr] != 0) { | |
len++; | |
ptr++; | |
} | |
return len; | |
} | |
function cstr_by_ptr(mem_buffer, ptr) { | |
const mem = new Uint8Array(mem_buffer); | |
const len = cstrlen(mem, ptr); | |
const bytes = new Uint8Array(mem_buffer, ptr, len); | |
return new TextDecoder().decode(bytes); | |
} | |
function cstr_fmt_by_ptr(mem_buffer, fmt_ptr, arg_ptr) { | |
const fmt = cstr_by_ptr(mem_buffer, fmt_ptr); | |
// VarArgs Chunk | |
let args = mem_buffer.slice(arg_ptr); | |
let result = ""; | |
let fmt_buffer = fmt.split(""); | |
let fmt_cur = 0; | |
let pad_width, | |
precision, | |
pad_with_zero, | |
pad_with_space, | |
justify_right, | |
pre_with_polarity, | |
pre_with_space, | |
pre_with_format, | |
capitalize, | |
precise, | |
bit_size, | |
radix; | |
reset_state(); | |
function reset_state() { | |
bit_size = 32; | |
pad_width = 0; | |
precision = 0; | |
radix = 10; | |
pad_with_zero = false; | |
pad_with_space = false; | |
justify_right = true; | |
pre_with_polarity = false; | |
pre_with_space = false; | |
pre_with_format = false; | |
capitalize = false; | |
precise = false; | |
} | |
function parse_num(cursor) { | |
let width_end = cursor; | |
while (fmt_buffer[width_end]) { | |
if (/\d/.test(fmt_buffer[width_end])) width_end++; | |
else break; | |
} | |
let num = fmt_buffer.splice(cursor, width_end - cursor).join(""); | |
return parseInt(num); | |
} | |
// Grab the view of args based on specifier and shift the args | |
function shift_args(view) { | |
args = args.slice(view.BYTES_PER_ELEMENT); | |
return view.at(0); | |
} | |
while (fmt_cur < fmt_buffer.length) { | |
if (fmt_buffer[fmt_cur] !== "%") { | |
// Normal character, copy it to the temp string | |
const str = fmt_buffer[fmt_cur++]; | |
result += str; | |
continue; | |
} | |
// Peek only next character and splice the modifiers. | |
// So we can always simply look one char ahead | |
const peek_idx = fmt_cur + 1; | |
const peek_char = fmt_buffer[peek_idx]; | |
switch (peek_char) { | |
case undefined: { | |
fmt_cur++; | |
break; | |
} | |
case "%": { | |
result += "%"; | |
fmt_cur += 2; | |
break; | |
} | |
case "+": { | |
pre_with_polarity = true; | |
fmt_buffer.splice(peek_idx, 1); | |
break; | |
} | |
case "-": { | |
justify_right = false; | |
fmt_buffer.splice(peek_idx, 1); | |
break; | |
} | |
case " ": { | |
pre_with_space = !pre_with_polarity; | |
fmt_buffer.splice(peek_idx, 1); | |
break; | |
} | |
case ".": { | |
precise = true; | |
fmt_buffer.splice(peek_idx, 1); | |
break; | |
} | |
case "#": { | |
pre_with_format = true; | |
fmt_buffer.splice(peek_idx, 1); | |
break; | |
} | |
case "0": { | |
if (precise) { | |
precision = parse_num(peek_idx); | |
} else { | |
pad_with_zero = true; | |
pad_width = parse_num(peek_idx); | |
} | |
break; | |
} | |
case "1": | |
case "2": | |
case "3": | |
case "4": | |
case "5": | |
case "6": | |
case "7": | |
case "8": | |
case "9": { | |
if (precise) { | |
precision = parse_num(peek_idx); | |
} else { | |
pad_with_space = true; | |
pad_width = parse_num(peek_idx); | |
} | |
break; | |
} | |
// Modifiers | |
case "l": { | |
bit_size = bit_size === 32 ? 64 : 32; | |
fmt_buffer.splice(peek_idx, 1); | |
} | |
// In JS there are no number below 32 bit, so we are ignoring | |
// also clang bakes the overflown and underflown values as it is | |
case "h": { | |
fmt_buffer.splice(peek_idx, 1); | |
break; | |
} | |
case "c": { | |
const char_code = shift_args(new Uint32Array(args)); | |
let str = String.fromCharCode(char_code); | |
let pad_char; | |
if (pad_with_zero || pad_with_space) pad_char = " "; | |
else pad_char = ""; | |
if (justify_right) str = str.padStart(pad_width, pad_char); | |
else str = str.padEnd(pad_width, pad_char); | |
result += str; | |
fmt_cur += 2; | |
reset_state(); | |
break; | |
} | |
case "s": { | |
const str_ptr = shift_args(new Uint32Array(args)); | |
let str = cstr_by_ptr(mem_buffer, str_ptr); | |
let pad_char; | |
if (pad_with_zero || pad_with_space) pad_char = " "; | |
else pad_char = ""; | |
if (justify_right) str = str.padStart(pad_width, pad_char); | |
else str = str.padEnd(pad_width, pad_char); | |
if (precise) str = str.substring(0, precision); | |
result += str; | |
fmt_cur += 2; | |
reset_state(); | |
break; | |
} | |
case "o": | |
case "x": | |
case "X": | |
case "u": { | |
let num; | |
let pad_char, pre_char; | |
if (peek_char === "o") { | |
radix = 8; | |
} else if (peek_char === "x") { | |
radix = 16; | |
} else if (peek_char === "X") { | |
radix = 16; | |
capitalize = true; | |
} else { | |
console.error("radix unreachable"); | |
} | |
if (bit_size === 32) { | |
num = shift_args(new Uint32Array(args)); | |
} else { | |
unaligned = args.byteLength % 8; | |
args.slice(unaligned); | |
num = shift_args(new BigUint64Array(args)); | |
} | |
if (pad_with_zero) pad_char = "0"; | |
else if (pad_with_space) pad_char = " "; | |
else pad_char = ""; | |
if (pre_with_format && radix === 8) pre_char = "0"; | |
else if (pre_with_format && radix === 16) pre_char = "0x"; | |
else pre_char = ""; | |
if (precise) { | |
pad_char = " "; | |
num = num.toString(radix).padStart(precision, "0"); | |
} | |
if (justify_right) { | |
num = num.toString(radix).padStart(pad_width, pad_char); | |
} else { | |
pad_char = " "; | |
num = num.toString(radix).padEnd(pad_width, pad_char); | |
} | |
num = pre_char + num; | |
if (capitalize) num = num.toUpperCase(); | |
result += num; | |
fmt_cur += 2; | |
reset_state(); | |
break; | |
} | |
case "i": | |
case "d": { | |
let num; | |
let pad_char, pre_char; | |
if (bit_size === 32) { | |
num = shift_args(new Int32Array(args)); | |
} else { | |
unaligned = args.byteLength % 8; | |
args = args.slice(unaligned); | |
num = shift_args(new BigInt64Array(args)); | |
} | |
if (pad_with_zero) pad_char = "0"; | |
else if (pad_with_space) pad_char = " "; | |
else pad_char = ""; | |
if (pre_with_polarity && num > 0) pre_char = "+"; | |
else if (pre_with_space && num > 0) pre_char = " "; | |
else pre_char = ""; | |
if (precise) { | |
pad_char = " "; | |
num = num.toString().padStart(precision, "0"); | |
} | |
if (justify_right) { | |
num = num.toString().padStart(pad_width, pad_char); | |
} else { | |
pad_char = " "; | |
num = num.toString().padEnd(pad_width, pad_char); | |
} | |
num = pre_char + num; | |
result += num; | |
fmt_cur += 2; | |
reset_state(); | |
break; | |
} | |
case "F": { | |
capitalize = true; | |
} | |
case "f": { | |
// Align Bytes by 8 | |
unaligned = args.byteLength % 8; | |
args = args.slice(unaligned); | |
let num = shift_args(new Float64Array(args)); | |
let pad_char, pre_char; | |
if (pad_with_zero) pad_char = "0"; | |
else if (pad_with_space) pad_char = " "; | |
else pad_char = ""; | |
if (pre_with_polarity && num > 0) pre_char = "+"; | |
else if (pre_with_space && num > 0) pre_char = " "; | |
else pre_char = ""; | |
if (precise) num = num.toFixed(precision); | |
if (justify_right) { | |
num = num.toString().padStart(pad_width, pad_char); | |
} else { | |
pad_char = " "; | |
num = num.toString().padEnd(pad_width, pad_char); | |
} | |
if (capitalize) num = num.toUpperCase(); | |
num = pre_char + num; | |
result += num; | |
fmt_cur += 2; | |
reset_state(); | |
break; | |
} | |
// unsupported format specifiers, copy it to the temp string | |
// Flags => # * | |
// Len Mod => j z t L | |
// Specify => x X e E a A g G n p | |
default: { | |
const str = fmt_buffer[fmt_cur++]; | |
result += str; | |
break; | |
} | |
} | |
} | |
return result; | |
} | |
WebAssembly.instantiateStreaming(fetch("./main.wasm"), { | |
env: make_environment({ | |
add: (a, b) => { | |
return a + b; | |
}, | |
console_fmt: (fmt_ptr, arg_ptr) => { | |
console.log(cstr_fmt_by_ptr(wasm.exports.memory.buffer, fmt_ptr, arg_ptr)); | |
}, | |
}), | |
}) | |
.then((w) => { | |
wasm = w.instance; | |
wasm.exports.main(); | |
}) | |
.catch((err) => console.log(err)); | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void console_fmt(char *fmt, ...); | |
int main(void) { | |
const char *s = "Hello"; | |
console_fmt("Strings:\n"); // same as puts("Strings"); | |
console_fmt(" padding:\n"); | |
console_fmt("\t[%10s]\n", s); | |
console_fmt("\t[%-10s]\n", s); | |
console_fmt("\t[%*s]\n", 10, s); | |
console_fmt(" truncating:\n"); | |
console_fmt("\t%.4s\n", s); | |
console_fmt("\t%.*s\n", 3, s); | |
console_fmt("Characters:\t%c %%\n", 'A'); | |
console_fmt("Integers:\n"); | |
console_fmt("\tDecimal:\t%i %d %.6i %i %.0i %+i %i\n", 1, 2, 3, 0, 0, 4, | |
-4); | |
console_fmt("\tHexadecimal:\t%x %x %X %#x\n", 5, 10, 10, 6); | |
console_fmt("\tOctal:\t\t%o %#o %#o\n", 10, 10, 4); | |
console_fmt("Floating point:\n"); | |
console_fmt("\tRounding:\t%f %.0f %.32f\n", 1.5, 1.5, 1.3); | |
console_fmt("\tPadding:\t%05.2f %.2f %5.2f\n", 1.5, 1.5, 1.5); | |
console_fmt("\tScientific:\t%E %e\n", 1.5, 1.5); | |
console_fmt("\tHexadecimal:\t%a %A\n", 1.5, 1.5); | |
console_fmt("\tSpecial values:\t0/0=%g 1/0=%g\n", 0.0 / 0.0, 1.0 / 0.0); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment