src/ctype.js
/**
* @file src/ctype.js
* @module ctype
*/
"use strict";
/**
* Tests an variable is being an JavaScript object type
* @param {Object} object Testing object value
* @return {Boolean} Is a variable a JavaScript object
*/
function isObject(object)
{
return (typeof object === "object");
}
/**
* Does deep copy of an object
* @param {Object} destObj Destination object
* @param {Object} srcObj Source object
*/
function copyObject(destObj, srcObj)
{
if(destObj)
{
if(!isObject(destObj) || destObj === null)
{
throw new Error("[CType] 'copyObject' function: " + "a destination object '" + destObj.toString() + "' must have an object type");
}
for(let it in srcObj)
{
if(!isObject(srcObj[it]) || srcObj[it] === null)
{
destObj[it] = srcObj[it];
}
if(isObject(srcObj[it]) && srcObj[it] !== null && srcObj[it].length !== undefined)
{
destObj[it] = new window[srcObj[it].constructor.name](srcObj[it].length);
allocateArray(destObj[it], srcObj[it]);
continue;
}
if(isObject(srcObj[it]) && srcObj[it] !== null)
{
destObj[it] = {};
copyObject(destObj[it], srcObj[it]);
}
}
}
else
{
throw new Error("[CType] 'copyObject' function: set a non-empty parameter: [object]");
}
}
function allocateArray(destArr, srcArr)
{
let l = srcArr.length;
if(destArr)
{
if(!isObject(destArr) || destArr.length === undefined || destArr === null)
{
throw new Error("[CType] 'allocateArray' function: " + "a destination object '" + destArr.toString() + "' must have an array type");
}
for(let it = 0; it < l; ++it)
{
if(isObject(srcArr[it]) && srcArr[it] !== null && srcArr[it].length !== undefined)
{
destArr[it] = new window[srcArr[it].constructor.name](srcArr[it].length);
allocateArray(destArr[it], srcArr[it]);
continue;
}
if(isObject(srcArr[it]) && srcArr[it] !== null)
{
destArr[it] = {};
copyObject(destArr[it], srcArr[it]);
}
}
}
else
{
throw new Error("[CType] 'allocateArray' function: set a non-empty parameter: [array]");
}
}
/**
* Gets a size of source structure
* @param {Object} srcStruct Source structure
* @param {Number} totalSize Total size in bytes
*/
function getStructSize(srcStruct, totalSize)
{
let isEmpty = false;
for(let field in srcStruct)
{
let fieldValue = srcStruct[field];
isEmpty = false;
if(!isObject(fieldValue) && !fieldValue.BYTES_PER_ELEMENT && !srcStruct.byteLength)
{
throw new Error("[ctype] 'struct' function: invalid structure field '" + field + ":" + fieldValue + "'");
}
if(!fieldValue.BYTES_PER_ELEMENT)
{
if(fieldValue.length)
{
for(let i = 0; i < fieldValue.length; ++i)
{
if(isObject(fieldValue[i]))
{
getStructSize(fieldValue[i], totalSize);
}
}
}
else
{
if(isObject(fieldValue))
{
getStructSize(fieldValue, totalSize);
}
}
}
else
{
totalSize.value += fieldValue.byteLength;
}
}
if(isEmpty)
{
throw new Error("[ctype] 'struct' function: invalid structure field - an empty object");
}
}
/**
* uint8(Uint8Array) type byte length
* @type {Number}
*/
export const UINT8_SIZE = Uint8Array.BYTES_PER_ELEMENT;
/**
* uint16(Uint16Array) type byte length
* @type {Number}
*/
export const UINT16_SIZE = Uint16Array.BYTES_PER_ELEMENT;
/**
* uint32(Uint32Array) type byte length
* @type {Number}
*/
export const UINT32_SIZE = Uint32Array.BYTES_PER_ELEMENT;
/**
* int8(Int8Array) type byte length
* @type {Number}
*/
export const INT8_SIZE = Int8Array.BYTES_PER_ELEMENT;
/**
* int16(Int16Array) type byte length
* @type {Number}
*/
export const INT16_SIZE = Int16Array.BYTES_PER_ELEMENT;
/**
* int32(Uint32Array) type byte length
* @type {Number}
*/
export const INT32_SIZE = Int32Array.BYTES_PER_ELEMENT;
/**
* float32(Float32Array) type byte length
* @type {Number}
*/
export const FLOAT32_SIZE = Float32Array.BYTES_PER_ELEMENT;
/**
* float64(Float64Array) type byte length
* @type {Number}
*/
export const FLOAT64_SIZE = Float64Array.BYTES_PER_ELEMENT;
/**
* Returns new 'unsigned char array[size]' C equivalent
* @param {Number} size=1 Array length
* @return {Uint8Array} Unsigned 8-byte integer array
*/
export function uint8(size = 1)
{
let ctype = new Uint8Array(size);
return ctype;
}
/**
* Returns new 'unsigned short array[size]' C equivalent
* @param {Number} size=1 Array length
* @return {Uint16Array} Unsigned 16-byte integer array
*/
export function uint16(size = 1)
{
let ctype = new Uint16Array(size);
return ctype;
}
/**
* Returns new 'unsigned int array[size]' C equivalent
* @param {Number} size=1 Array length
* @return {Uint32Array} Unsigned 32-byte integer array
*/
export function uint32(size = 1)
{
let ctype = new Uint32Array(size);
return ctype;
}
/**
* Returns new 'char array[size]' C equivalent
* @param {Number} size=1 Array length
* @return {Int8Array} Signed 8-byte integer array
*/
export function int8(size = 1)
{
let ctype = new Int8Array(size);
return ctype;
}
/**
* Returns new 'short array[size]' C equivalent
* @param {Number} size=1 Array length
* @return {Int16Array} Signed 16-byte integer array
*/
export function int16(size = 1)
{
let ctype = new Int16Array(size);
return ctype;
}
/**
* Returns new 'int array[size]' C equivalent
* @param {Number} size=1 Array length
* @return {Int32Array} Signed 32-byte integer array
*/
export function int32(size = 1)
{
let ctype = new Int32Array(size);
return ctype;
}
/**
* Returns new 'float array[size]' C equivalent
* @param {Number} size=1 Array length
* @return {Float32Array} Signed 32-byte floating point array
*/
export function float32(size = 1)
{
let ctype = new Float32Array(size);
return ctype;
}
/**
* Returns new 'double array[size]' C equivalent
* @param {Number} size=1 Array length
* @return {Float64Array} Signed 64-byte floating point array
*/
export function float64(size = 1)
{
let ctype = new Float64Array(size);
return ctype;
}
/**
* Returns new 'struct s[size]' C equivalent with 'byteLength' field is a total size of structure
* @param {Object} srcStruct Empty source object
* @param {Number} size=1 Array length
* @return {Object} Object structure with typed fields
*/
export function struct(srcStruct, size = 1)
{
if(!isObject(srcStruct) || (typeof size !== "number"))
{
throw new Error("[ctype] 'struct' function: invalid arguments (Object srcStruct, Number size)");
}
let totalSize = { value: 0 };
getStructSize(srcStruct, totalSize);
if(size > 1)
{
let dstStructs = [];
for(let i = 0; i < size; ++i)
{
dstStructs[i] = {};
copyObject(dstStructs[i], srcStruct);
Object.defineProperty(dstStructs[i], "byteLength",
{
value : totalSize.value,
writable : false,
enumerable : true,
configurable: false
});
}
return dstStructs;
}
else
{
let dstStruct = {};
copyObject(dstStruct, srcStruct);
Object.defineProperty(dstStruct, "byteLength",
{
value : totalSize.value,
writable : false,
enumerable : true,
configurable: false
});
return dstStruct;
}
return null;
}
/**
* Sets data from a source buffer to a destination structure
* @param {Object} dstStruct Destination structure
* @param {ArrayBuffer} srcBuffer Source buffer
* @param {Number} totalOffset Total offset in bytes
* @param {Boolean} littleEndian Little-endian bytes order flag
*/
function setBufferToStruct(dstStruct, srcBuffer, totalOffset, littleEndian)
{
for(let field in dstStruct)
{
let fieldValue = dstStruct[field];
if(fieldValue.constructor.name === "Array")
{
let l = fieldValue.length;
for(let i = 0; i < l; ++i)
{
setBufferToStruct(fieldValue[i], srcBuffer, totalOffset, littleEndian);
}
}
else
{
if(fieldValue.constructor.name === "Object")
{
setBufferToStruct(fieldValue, srcBuffer, totalOffset, littleEndian);
}
else
{
let l = fieldValue.length;
switch(fieldValue.constructor.name)
{
case "Uint8Array":
for(let i = 0; i < l; ++i)
{
fieldValue[i] = srcBuffer.getUint8(totalOffset.value, littleEndian && true);
totalOffset.value += fieldValue.BYTES_PER_ELEMENT;
}
break;
case "Uint16Array":
for(let i = 0; i < l; ++i)
{
fieldValue[i] = srcBuffer.getUint16(totalOffset.value, littleEndian && true);
totalOffset.value += fieldValue.BYTES_PER_ELEMENT;
}
break;
case "Uint32Array":
for(let i = 0; i < l; ++i)
{
fieldValue[i] = srcBuffer.getUint32(totalOffset.value, littleEndian && true);
totalOffset.value += fieldValue.BYTES_PER_ELEMENT;
}
break;
case "Int8Array":
for(let i = 0; i < l; ++i)
{
fieldValue[i] = srcBuffer.getInt8(totalOffset.value, littleEndian && true);
totalOffset.value += fieldValue.BYTES_PER_ELEMENT;
}
break;
case "Int16Array":
for(let i = 0; i < l; ++i)
{
fieldValue[i] = srcBuffer.getInt16(totalOffset.value, littleEndian && true);
totalOffset.value += fieldValue.BYTES_PER_ELEMENT;
}
break;
case "Int32Array":
for(let i = 0; i < l; ++i)
{
fieldValue[i] = srcBuffer.getInt32(totalOffset.value, littleEndian && true);
totalOffset.value += fieldValue.BYTES_PER_ELEMENT;
}
break;
case "Float32Array":
for(let i = 0; i < l; ++i)
{
fieldValue[i] = srcBuffer.getFloat32(totalOffset.value, littleEndian && true);
totalOffset.value += fieldValue.BYTES_PER_ELEMENT;
}
break;
case "Float64Array":
for(let i = 0; i < l; ++i)
{
fieldValue[i] = srcBuffer.getFloat64(totalOffset.value, littleEndian && true);
totalOffset.value += fieldValue.BYTES_PER_ELEMENT;
}
break;
}
}
}
}
}
/**
* Sets data from source structure to destination buffer
* @param {ArrayBuffer} dstBuffer Destination buffer
* @param {Object} srcStruct Source structure
* @param {Number} totalOffset Total offset in bytes
* @param {Boolean} littleEndian Little-endian bytes order flag
*/
function setStructToBuffer(dstBuffer, srcStruct, totalOffset, littleEndian)
{
for(let field in srcStruct)
{
let fieldValue = srcStruct[field];
if(fieldValue.constructor.name === "Array")
{
let l = fieldValue.length;
for(let i = 0; i < l; ++i)
{
setStructToBuffer(dstBuffer, fieldValue[i], totalOffset, littleEndian);
}
}
else
{
if(fieldValue.constructor.name === "Object")
{
setStructToBuffer(dstBuffer, fieldValue, totalOffset, littleEndian);
}
else
{
let l = fieldValue.length;
switch(fieldValue.constructor.name)
{
case "Uint8Array":
for(let i = 0; i < l; ++i)
{
dstBuffer.setUint8(totalOffset.value, fieldValue[i], littleEndian && true);
totalOffset.value += fieldValue.BYTES_PER_ELEMENT;
}
break;
case "Uint16Array":
for(let i = 0; i < l; ++i)
{
dstBuffer.setUint16(totalOffset.value, fieldValue[i], littleEndian && true);
totalOffset.value += fieldValue.BYTES_PER_ELEMENT;
}
break;
case "Uint32Array":
for(let i = 0; i < l; ++i)
{
dstBuffer.setUint32(totalOffset.value, fieldValue[i], littleEndian && true);
totalOffset.value += fieldValue.BYTES_PER_ELEMENT;
}
break;
case "Int8Array":
for(let i = 0; i < l; ++i)
{
dstBuffer.setInt8(totalOffset.value, fieldValue[i], littleEndian && true);
totalOffset.value += fieldValue.BYTES_PER_ELEMENT;
}
break;
case "Int16Array":
for(let i = 0; i < l; ++i)
{
dstBuffer.setInt16(totalOffset.value, fieldValue[i], littleEndian && true);
totalOffset.value += fieldValue.BYTES_PER_ELEMENT;
}
break;
case "Int32Array":
for(let i = 0; i < l; ++i)
{
dstBuffer.setInt32(totalOffset.value, fieldValue[i], littleEndian && true);
totalOffset.value += fieldValue.BYTES_PER_ELEMENT;
}
break;
case "Float32Array":
for(let i = 0; i < l; ++i)
{
dstBuffer.setFloat32(totalOffset.value, fieldValue[i], littleEndian && true);
totalOffset.value += fieldValue.BYTES_PER_ELEMENT;
}
break;
case "Float64Array":
for(let i = 0; i < l; ++i)
{
dstBuffer.setFloat64(totalOffset.value, fieldValue[i], littleEndian && true);
totalOffset.value += fieldValue.BYTES_PER_ELEMENT;
}
break;
}
}
}
}
}
/**
* Copies a source buffer to a destination structure
* @param {ArrayBuffer} srcBuffer Source buffer
* @param {Object|Object[]} dstStruct Destination structure or array of structures
* @param {Number} byteOffset=0 Byte offset from a start of a source buffer
* @param {Boolean} littleEndian=true Little-endian bytes order flag
* @return {Object} Destination structure reference
*/
export function bufferToStruct(srcBuffer, dstStruct, byteOffset = 0, littleEndian = true)
{
if(!isObject(dstStruct) || !(srcBuffer instanceof ArrayBuffer) || (typeof byteOffset !== "number") || (typeof littleEndian !== "boolean"))
{
throw new Error("[ctype] 'bufferToStruct' function: invalid arguments in the signature (ArrayBuffer srcBuffer, Object dstStruct, Number byteOffset = 0, Boolean littleEndian = true)");
}
let srcBuf;
try
{
srcBuf = new DataView(srcBuffer, byteOffset);
}
catch(e)
{
console.log(e);
return;
}
let totalOffset = { value: 0 };
setBufferToStruct(dstStruct, srcBuf, totalOffset, littleEndian);
return dstStruct;
}
/**
* Copies a source structure to a destination buffer
* @param {Object|Object[]} srcStruct Source structure or array of structures
* @param {ArrayBuffer} existedBuffer=null Existed buffer
* @param {Number} byteOffset=0 Byte offset from a start of a source buffer
* @param {Number} littleEndian=true Little-endian bytes order flag
* @return {ArrayBuffer} Destination buffer reference
*/
export function structToBuffer(srcStruct, existedBuffer = null, byteOffset = 0, littleEndian = true)
{
if(!isObject(srcStruct) ||
(!(existedBuffer instanceof ArrayBuffer) && existedBuffer !== null) ||
(typeof byteOffset !== "number") ||
(typeof littleEndian !== "boolean"))
{
throw new Error("[ctype] 'structToBuffer' function: invalid arguments in the signature (Object srcStruct, ArrayBuffer existedBuffer = null, Number byteOffset = 0, Boolean littleEndian = true)");
}
let totalOffset = { value: 0 };
let arrayBuffer, dstBuffer;
if(existedBuffer === null)
{
if(srcStruct instanceof Array)
{
let l = srcStruct.length;
arrayBuffer = new ArrayBuffer(srcStruct[0].byteLength * l);
dstBuffer = new DataView (arrayBuffer);
}
else
{
arrayBuffer = new ArrayBuffer(srcStruct.byteLength);
dstBuffer = new DataView (arrayBuffer);
}
setStructToBuffer(dstBuffer, srcStruct, totalOffset, littleEndian);
}
else
{
dstBuffer = new DataView(existedBuffer, byteOffset);
setStructToBuffer(dstBuffer, srcStruct, totalOffset, littleEndian);
}
return dstBuffer.buffer;
}
/**
* Sets data from a source typed array to a destination buffer
* @param {Array} srcArray Source typed array
* @param {ArrayBuffer} dstBuffer Destination buffer
* @param {Number} length Byte length for copying from a source typed array
* @param {Number} byteOffset Byte offset from a start of a source typed array
* @param {Number} totalOffset Total offset in bytes
* @param {Boolean} littleEndian Little-endian bytes order flag
*/
function setArrayToBuffer(srcArray, dstBuffer, length, totalOffset, littleEndian)
{
let l;
let i = totalOffset.value / srcArray.BYTES_PER_ELEMENT;
if(isNaN(length))
{
if(dstBuffer.byteLength > srcArray.byteLength ||
dstBuffer.byteLength === srcArray.byteLength)
{
l = srcArray.length;
}
else
{
l = dstBuffer.byteLength / srcArray.BYTES_PER_ELEMENT;
}
}
else
{
l = length / srcArray.BYTES_PER_ELEMENT + totalOffset.value / srcArray.BYTES_PER_ELEMENT;
}
switch(srcArray.constructor.name)
{
case "Uint8Array":
for(; i < l; ++i)
{
dstBuffer.setUint8(totalOffset.value, srcArray[i], littleEndian && true);
totalOffset.value += srcArray.BYTES_PER_ELEMENT;
}
break;
case "Uint16Array":
for(; i < l; ++i)
{
dstBuffer.setUint16(totalOffset.value, srcArray[i], littleEndian && true);
totalOffset.value += srcArray.BYTES_PER_ELEMENT;
}
break;
case "Uint32Array":
for(; i < l; ++i)
{
dstBuffer.setUint32(totalOffset.value, srcArray[i], littleEndian && true);
totalOffset.value += srcArray.BYTES_PER_ELEMENT;
}
break;
case "Int8Array":
for(; i < l; ++i)
{
dstBuffer.setInt8(totalOffset.value, srcArray[i], littleEndian && true);
totalOffset.value += srcArray.BYTES_PER_ELEMENT;
}
break;
case "Int16Array":
for(; i < l; ++i)
{
dstBuffer.setInt16(totalOffset.value, srcArray[i], littleEndian && true);
totalOffset.value += srcArray.BYTES_PER_ELEMENT;
}
break;
case "Int32Array":
for(; i < l; ++i)
{
dstBuffer.setInt32(totalOffset.value, srcArray[i], littleEndian && true);
totalOffset.value += srcArray.BYTES_PER_ELEMENT;
}
break;
case "Float32Array":
for(; i < l; ++i)
{
dstBuffer.setFloat32(totalOffset.value, srcArray[i], littleEndian && true);
totalOffset.value += srcArray.BYTES_PER_ELEMENT;
}
break;
case "Float64Array":
for(; i < l; ++i)
{
dstBuffer.setFloat64(totalOffset.value, srcArray[i], littleEndian && true);
totalOffset.value += srcArray.BYTES_PER_ELEMENT;
}
break;
}
}
/**
* Sets data from a source buffer array to a destination typed array
* @param {ArrayBuffer} srcBuffer Sorce buffer
* @param {Array} dstArray Destination typed array
* @param {Number} length Byte length for copying from a source buffer
* @param {Number} totalOffset Total offset in bytes
* @param {Boolean} littleEndian Little-endian bytes order flag
*/
function setBufferToArray(srcBuffer, dstArray, length, totalOffset, littleEndian)
{
let l;
if(isNaN(length))
{
if(srcBuffer.byteLength > dstArray.byteLength ||
srcBuffer.byteLength === dstArray.byteLength)
{
l = dstArray.length;
}
else
{
l = srcBuffer.byteLength / dstArray.BYTES_PER_ELEMENT;
}
}
else
{
l = length / dstArray.BYTES_PER_ELEMENT;
}
switch(dstArray.constructor.name)
{
case "Uint8Array":
for(let i = 0; i < l; ++i)
{
dstArray[i] = srcBuffer.getUint8(totalOffset.value, littleEndian && true);
totalOffset.value += dstArray.BYTES_PER_ELEMENT;
}
break;
case "Uint16Array":
for(let i = 0; i < l; ++i)
{
dstArray[i] = srcBuffer.getUint16(totalOffset.value, littleEndian && true);
totalOffset.value += dstArray.BYTES_PER_ELEMENT;
}
break;
case "Uint32Array":
for(let i = 0; i < l; ++i)
{
dstArray[i] = srcBuffer.getUint32(totalOffset.value, littleEndian && true);
totalOffset.value += dstArray.BYTES_PER_ELEMENT;
}
break;
case "Int8Array":
for(let i = 0; i < l; ++i)
{
dstArray[i] = srcBuffer.getInt8(totalOffset.value, littleEndian && true);
totalOffset.value += dstArray.BYTES_PER_ELEMENT;
}
break;
case "Int16Array":
for(let i = 0; i < l; ++i)
{
dstArray[i] = srcBuffer.getInt16(totalOffset.value, littleEndian && true);
totalOffset.value += dstArray.BYTES_PER_ELEMENT;
}
break;
case "Int32Array":
for(let i = 0; i < l; ++i)
{
dstArray[i] = srcBuffer.getInt32(totalOffset.value, littleEndian && true);
totalOffset.value += dstArray.BYTES_PER_ELEMENT;
}
break;
case "Float32Array":
for(let i = 0; i < l; ++i)
{
dstArray[i] = srcBuffer.getFloat32(totalOffset.value, littleEndian && true);
totalOffset.value += dstArray.BYTES_PER_ELEMENT;
}
break;
case "Float64Array":
for(let i = 0; i < l; ++i)
{
dstArray[i] = srcBuffer.getFloat64(totalOffset.value, littleEndian && true);
totalOffset.value += dstArray.BYTES_PER_ELEMENT;
}
break;
}
}
/**
* Copies a source buffer to a destination typed array
* @param {ArrayBuffer} srcBuffer Source buffer
* @param {Array} dstArray Destination typed array
* @param {Number} byteOffset=0 Byte offset from a start of a source buffer
* @param {Number} length=NaN Byte length for copying from a source buffer
* @param {Boolean} littleEndian=true Little-endian bytes order flag
* @return {Array} Destination array reference
*/
export function bufferToArray(srcBuffer, dstArray, byteOffset = 0, length = NaN, littleEndian = true)
{
if(!dstArray.BYTES_PER_ELEMENT || !(srcBuffer instanceof ArrayBuffer) ||
(typeof length !== "number" && !isNaN(length)) ||
(typeof byteOffset !== "number") || (typeof littleEndian !== "boolean"))
{
throw new Error("[ctype] 'bufferToArray' function: invalid arguments in the signature (ArrayBuffer srcBuffer, TypedArray dstArray, Number length = NaN, NumberNumber offset = 0, Boolean littleEndian = true)");
}
if(length < 0)
{
throw new Error("[ctype] 'bufferToArray' function: the copying byte length must be a positive value");
}
let srcBuf = new DataView(srcBuffer, byteOffset);
let totalOffset = { value: 0 };
setBufferToArray(srcBuf, dstArray, length, totalOffset, littleEndian);
return dstArray;
}
/**
* Copies a source typed array to a destination buffer
* @param {Array} srcArray Source typed array
* @param {ArrayBuffer} existedBuffer=null DesExisted buffer
* @param {Number} byteOffset=0 Byte offset from a start of a source typed array
* @param {Number} length=NaN Byte length for copying from a source typed array
* @param {Boolean} littleEndian=true Little-endian bytes order flag
* @return {ArrayBuffer} Destination buffer reference
*/
export function arrayToBuffer(srcArray, existedBuffer = null, byteOffset = 0, length = NaN, littleEndian = true)
{
if(!srcArray.BYTES_PER_ELEMENT ||
(!(existedBuffer instanceof ArrayBuffer) && existedBuffer !== null) ||
(typeof length !== "number") ||
(typeof byteOffset !== "number") ||
(typeof littleEndian !== "boolean"))
{
throw new Error("[ctype] 'arrayToBuffer' function: invalid arguments in the signature (TypedArray srcArray, ArrayBuffer existedBuffer = null, Number length = NaN, Number byteOffset = 0, Boolean littleEndian = true)");
}
if(length < 0)
{
throw new Error("[ctype] 'arrayToBuffer' function: the copying byte length must be a positive value");
}
let totalOffset = { value: byteOffset };
let arrayBuffer, dstBuffer;
if(existedBuffer === null)
{
arrayBuffer = new ArrayBuffer(srcArray.byteLength);
dstBuffer = new DataView (arrayBuffer);
setArrayToBuffer(srcArray, dstBuffer, length, totalOffset, littleEndian);
}
else
{
dstBuffer = new DataView(existedBuffer);
setArrayToBuffer(srcArray, dstBuffer, length, totalOffset, littleEndian);
}
return dstBuffer.buffer;
}