mirror of https://github.com/pyodide/pyodide.git
Add buffer format string function and tests (#1411)
This commit is contained in:
parent
5123ee976d
commit
c8f3971120
|
@ -143,6 +143,115 @@ EM_JS_NUM(int, hiwire_init, (), {
|
||||||
return (!!obj) && typeof obj.then === 'function';
|
return (!!obj) && typeof obj.then === 'function';
|
||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine type and endianness of data from format. This is a helper
|
||||||
|
* function for converting buffers from Python to Javascript, used in
|
||||||
|
* PyProxyBufferMethods and in `toJs` on a buffer.
|
||||||
|
*
|
||||||
|
* To understand this function it will be helpful to look at the tables here:
|
||||||
|
* https://docs.python.org/3/library/struct.html#format-strings
|
||||||
|
*
|
||||||
|
* @arg format {String} A Python format string (caller must convert it to a
|
||||||
|
* Javascript string).
|
||||||
|
* @arg errorMessage {String} Extra stuff to append to an error message if
|
||||||
|
* thrown. Should be a complete sentence.
|
||||||
|
* @returns A pair, an appropriate TypedArray constructor and a boolean which
|
||||||
|
* is true if the format suggests a big endian array.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Module.processBufferFormatString = function(formatStr, errorMessage = "")
|
||||||
|
{
|
||||||
|
if (formatStr.length > 2) {
|
||||||
|
throw new Error(
|
||||||
|
"Expected format string to have length <= 2, " +
|
||||||
|
`got '${formatStr}'.` + errorMessage);
|
||||||
|
}
|
||||||
|
let formatChar = formatStr.slice(-1);
|
||||||
|
let alignChar = formatStr.slice(0, -1);
|
||||||
|
let bigEndian;
|
||||||
|
switch (alignChar) {
|
||||||
|
case "!":
|
||||||
|
case ">":
|
||||||
|
bigEndian = true;
|
||||||
|
break;
|
||||||
|
case "<":
|
||||||
|
case "@":
|
||||||
|
case "=":
|
||||||
|
case "":
|
||||||
|
bigEndian = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unrecognized alignment character ${ alignChar }.` +
|
||||||
|
errorMessage);
|
||||||
|
}
|
||||||
|
let arrayType;
|
||||||
|
switch (formatChar) {
|
||||||
|
case 'b':
|
||||||
|
arrayType = Int8Array;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
case 'p':
|
||||||
|
case 'c':
|
||||||
|
case 'B':
|
||||||
|
case '?':
|
||||||
|
arrayType = Uint8Array;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
arrayType = Int16Array;
|
||||||
|
break;
|
||||||
|
case 'H':
|
||||||
|
arrayType = Uint16Array;
|
||||||
|
break;
|
||||||
|
case 'i':
|
||||||
|
case 'l':
|
||||||
|
case 'n':
|
||||||
|
arrayType = Int32Array;
|
||||||
|
break;
|
||||||
|
case 'I':
|
||||||
|
case 'L':
|
||||||
|
case 'N':
|
||||||
|
case 'P':
|
||||||
|
arrayType = Uint32Array;
|
||||||
|
break;
|
||||||
|
case 'q':
|
||||||
|
// clang-format off
|
||||||
|
if (globalThis.BigInt64Array === undefined) {
|
||||||
|
// clang-format on
|
||||||
|
throw new Error("BigInt64Array is not supported on this browser." +
|
||||||
|
errorMessage);
|
||||||
|
}
|
||||||
|
arrayType = BigInt64Array;
|
||||||
|
break;
|
||||||
|
case 'Q':
|
||||||
|
// clang-format off
|
||||||
|
if (globalThis.BigUint64Array === undefined) {
|
||||||
|
// clang-format on
|
||||||
|
throw new Error("BigUint64Array is not supported on this browser." +
|
||||||
|
errorMessage);
|
||||||
|
}
|
||||||
|
arrayType = BigUint64Array;
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
arrayType = Float32Array;
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
arrayType = Float64Array;
|
||||||
|
break;
|
||||||
|
case "e":
|
||||||
|
// clang-format off
|
||||||
|
throw new Error(
|
||||||
|
"Javascript has no Float16 support. Consider converting the data to " +
|
||||||
|
"float32 before using it from JavaScript. If you are using a webgl " +
|
||||||
|
"float16 texture then just use `getBuffer('u8')`.");
|
||||||
|
// clang-format on
|
||||||
|
default:
|
||||||
|
throw new Error(`Unrecognized format character '${formatChar}'.` +
|
||||||
|
errorMessage);
|
||||||
|
}
|
||||||
|
return [ arrayType, bigEndian ];
|
||||||
|
};
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -675,3 +675,72 @@ def test_pyimport_deprecation(selenium):
|
||||||
selenium.run_js("pyodide.runPython('x = 1')")
|
selenium.run_js("pyodide.runPython('x = 1')")
|
||||||
assert selenium.run_js("return pyodide.pyimport('x') === 1")
|
assert selenium.run_js("return pyodide.pyimport('x') === 1")
|
||||||
assert "pyodide.pyimport is deprecated and will be removed" in selenium.logs
|
assert "pyodide.pyimport is deprecated and will be removed" in selenium.logs
|
||||||
|
|
||||||
|
|
||||||
|
def test_buffer_format_string(selenium):
|
||||||
|
errors = [
|
||||||
|
["aaa", "Expected format string to have length <= 2, got 'aaa'"],
|
||||||
|
["II", "Unrecognized alignment character I."],
|
||||||
|
["x", "Unrecognized format character 'x'."],
|
||||||
|
["x", "Unrecognized format character 'x'."],
|
||||||
|
["e", "Javascript has no Float16 support."],
|
||||||
|
]
|
||||||
|
for fmt, msg in errors:
|
||||||
|
with pytest.raises(selenium.JavascriptException, match=msg):
|
||||||
|
selenium.run_js(
|
||||||
|
f"""
|
||||||
|
pyodide._module.processBufferFormatString({fmt!r});
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
format_tests = [
|
||||||
|
["c", "Uint8"],
|
||||||
|
["b", "Int8"],
|
||||||
|
["B", "Uint8"],
|
||||||
|
["?", "Uint8"],
|
||||||
|
["h", "Int16"],
|
||||||
|
["H", "Uint16"],
|
||||||
|
["i", "Int32"],
|
||||||
|
["I", "Uint32"],
|
||||||
|
["l", "Int32"],
|
||||||
|
["L", "Uint32"],
|
||||||
|
["n", "Int32"],
|
||||||
|
["N", "Uint32"],
|
||||||
|
["q", "BigInt64"],
|
||||||
|
["Q", "BigUint64"],
|
||||||
|
["f", "Float32"],
|
||||||
|
["d", "Float64"],
|
||||||
|
["s", "Uint8"],
|
||||||
|
["p", "Uint8"],
|
||||||
|
["P", "Uint32"],
|
||||||
|
]
|
||||||
|
|
||||||
|
def process_fmt_string(fmt):
|
||||||
|
return selenium.run_js(
|
||||||
|
f"""
|
||||||
|
let [array, is_big_endian] = pyodide._module.processBufferFormatString({fmt!r});
|
||||||
|
if(!array || typeof array.name !== "string" || !array.name.endsWith("Array")){{
|
||||||
|
throw new Error("Unexpected output on input {fmt}: " + array);
|
||||||
|
}}
|
||||||
|
let arrayName = array.name.slice(0, -"Array".length);
|
||||||
|
return [arrayName, is_big_endian];
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
for fmt, expected_array_name in format_tests:
|
||||||
|
[array_name, is_big_endian] = process_fmt_string(fmt)
|
||||||
|
assert not is_big_endian
|
||||||
|
assert array_name == expected_array_name
|
||||||
|
|
||||||
|
endian_tests = [
|
||||||
|
["@h", "Int16", False],
|
||||||
|
["=H", "Uint16", False],
|
||||||
|
["<i", "Int32", False],
|
||||||
|
[">I", "Uint32", True],
|
||||||
|
["!l", "Int32", True],
|
||||||
|
]
|
||||||
|
|
||||||
|
for fmt, expected_array_name, expected_is_big_endian in endian_tests:
|
||||||
|
[array_name, is_big_endian] = process_fmt_string(fmt)
|
||||||
|
assert is_big_endian == expected_is_big_endian
|
||||||
|
assert array_name == expected_array_name
|
||||||
|
|
Loading…
Reference in New Issue