scratch-sb1-converter
Version:
Scratch 1 (.sb) to Scratch 2 (.sb2) conversion library for Scratch 3.0
373 lines (339 loc) • 9.58 kB
JavaScript
import {assert} from '../util/assert';
const notImplemented = () => {
throw new Error('Not implemented');
};
/**
* Is the host computer little or big endian.
* @const IS_HOST_LITTLE_ENDIAN
* @type {boolean}
*/
const IS_HOST_LITTLE_ENDIAN = (() => {
const ab16 = new Uint16Array(1);
const ab8 = new Uint8Array(ab16.buffer);
ab16[0] = 0xaabb;
return ab8[0] === 0xbb;
})();
/**
* @callback BytePrimitive~sizeOfCallback
* @param {Uint8Array} uint8a
* @param {number} position
* @returns {number}
*/
/**
* @callback BytePrimitive~writeSizeOfCallback
* @param {Uint8Array} uint8a
* @param {number} position
* @param {*} value
* @returns {number}
*/
/**
* @callback BytePrimitive~writeCallback
* @param {Uint8Array} uint8a
* @param {number} position
* @param {*} value
*/
/**
* An interface for reading and writing binary values to typed arrays.
*
* Combined with {@link Packet Packet} this makes reading and writing packets
* of binary values easy.
*/
class BytePrimitive {
/**
* @constructor
* @param {object} options - Options to initialize BytePrimitive instance
* with.
* @param {number} [options.size=0] - Fixed size of the BytePrimitive.
* Should be 0 if the primitive has a variable size.
* @param {BytePrimitive~sizeOfCallback} [options.sizeOf=() => size] -
* Variable size of the primitive depending on its value stored in the
* array.
* @param {BytePrimitive~writeSizeOfCallback} [options.writeSizeOf] -
* Variable size of the primitive depending on the value being written.
* @param {TypedArray} [options.toBytes=new Uint8Array(1)] - Temporary
* space to copy bytes to/from to translate between a value and its
* representative byte set.
* @param {BytePrimitive#read} options.read - How is a value read
* at the given position of the array.
* @param {BytePrimitive~writeCallback} [options.write] - How to write a
* value to the array at the given position.
*/
constructor ({
size = 0,
sizeOf = () => size,
writeSizeOf = notImplemented,
toBytes = new Uint8Array(1),
read,
write = notImplemented
}) {
this.size = size;
this.sizeOf = sizeOf;
this.writeSizeOf = writeSizeOf;
this.toBytes = toBytes;
this.bytes = new Uint8Array(toBytes.buffer);
this.read = read;
this.write = write;
}
/**
* Create an object that can be used with `Object.defineProperty` to read
* and write values offset by `position` and the object's `this.offset`
* from `this.uint8a` by getting or setting the property.
* @param {number} position - Additional offset with `this.offset` to read
* from or write to.
* @returns {object} - A object that can be passed as the third argument to
* `Object.defineProperty`.
*/
asPropertyObject (position) {
const _this = this;
return {
get () {
return _this.read(this.uint8a, position + this.offset);
},
set (value) {
return _this.write(this.uint8a, position + this.offset, value);
},
enumerable: true
};
}
/**
* Read a value from `position` in `uint8a`.
* @param {Uint8Array} uint8a - Array to read from.
* @param {number} position - Position in `uint8a` to read from.
* @returns {*} - Value read from `uint8a` at `position`.
*/
read () {
return null;
}
}
/**
* @const Uint8
* @type {BytePrimitive}
*/
const Uint8 = new BytePrimitive({
size: 1,
read (uint8a, position) {
return uint8a[position];
},
write (uint8a, position, value) {
uint8a[position] = value;
return value;
}
});
const HOSTLE_BE16 = {
size: 2,
// toBytes: Defined by instance.
read (uint8a, position) {
this.bytes[1] = uint8a[position + 0];
this.bytes[0] = uint8a[position + 1];
return this.toBytes[0];
},
write (uint8a, position, value) {
this.toBytes[0] = value;
uint8a[position + 0] = this.bytes[1];
uint8a[position + 1] = this.bytes[0];
return value;
}
};
const HOSTBE_BE16 = {
size: 2,
// toBytes: Defined by instance.
read (uint8a, position) {
this.bytes[0] = uint8a[position + 0];
this.bytes[1] = uint8a[position + 1];
return this.toBytes[0];
},
write (uint8a, position, value) {
this.toBytes[0] = value;
uint8a[position + 0] = this.bytes[0];
uint8a[position + 1] = this.bytes[1];
return value;
}
};
let BE16;
if (IS_HOST_LITTLE_ENDIAN) {
BE16 = HOSTLE_BE16;
} else {
BE16 = HOSTBE_BE16;
}
/**
* @const Uint16BE
* @type {BytePrimitive}
*/
const Uint16BE = new BytePrimitive(Object.assign({}, BE16, {
toBytes: new Uint16Array(1)
}));
/**
* @const Int16BE
* @type {BytePrimitive}
*/
const Int16BE = new BytePrimitive(Object.assign({}, BE16, {
toBytes: new Int16Array(1)
}));
const HOSTLE_BE32 = {
size: 4,
// toBytes: Defined by instance.
read (uint8a, position) {
this.bytes[3] = uint8a[position + 0];
this.bytes[2] = uint8a[position + 1];
this.bytes[1] = uint8a[position + 2];
this.bytes[0] = uint8a[position + 3];
return this.toBytes[0];
},
write (uint8a, position, value) {
this.toBytes[0] = value;
uint8a[position + 0] = this.bytes[3];
uint8a[position + 1] = this.bytes[2];
uint8a[position + 2] = this.bytes[1];
uint8a[position + 3] = this.bytes[0];
return value;
}
};
const HOSTBE_BE32 = {
size: 4,
// toBytes: Defined by instance.
read (uint8a, position) {
this.bytes[0] = uint8a[position + 0];
this.bytes[1] = uint8a[position + 1];
this.bytes[2] = uint8a[position + 2];
this.bytes[3] = uint8a[position + 3];
return this.toBytes[0];
},
write (uint8a, position, value) {
this.toBytes[0] = value;
uint8a[position + 0] = this.bytes[0];
uint8a[position + 1] = this.bytes[1];
uint8a[position + 2] = this.bytes[2];
uint8a[position + 3] = this.bytes[3];
return value;
}
};
let BE32;
if (IS_HOST_LITTLE_ENDIAN) {
BE32 = HOSTLE_BE32;
} else {
BE32 = HOSTBE_BE32;
}
/**
* @const Int32BE
* @type {BytePrimitive}
*/
const Int32BE = new BytePrimitive(Object.assign({}, BE32, {
toBytes: new Int32Array(1)
}));
/**
* @const Uint32BE
* @type {BytePrimitive}
*/
const Uint32BE = new BytePrimitive(Object.assign({}, BE32, {
toBytes: new Uint32Array(1)
}));
let LE16;
if (IS_HOST_LITTLE_ENDIAN) {
LE16 = HOSTBE_BE16;
} else {
LE16 = HOSTLE_BE16;
}
/**
* @const Uint16LE
* @type {BytePrimitive}
*/
const Uint16LE = new BytePrimitive(Object.assign({}, LE16, {
toBytes: new Uint16Array(1)
}));
let LE32;
if (IS_HOST_LITTLE_ENDIAN) {
LE32 = HOSTBE_BE32;
} else {
LE32 = HOSTLE_BE32;
}
/**
* @const Uint32LE
* @type {BytePrimitive}
*/
const Uint32LE = new BytePrimitive(Object.assign({}, LE32, {
toBytes: new Uint32Array(1)
}));
const HOSTLE_BEDOUBLE = {
size: 8,
read (uint8a, position) {
this.bytes[7] = uint8a[position + 0];
this.bytes[6] = uint8a[position + 1];
this.bytes[5] = uint8a[position + 2];
this.bytes[4] = uint8a[position + 3];
this.bytes[3] = uint8a[position + 4];
this.bytes[2] = uint8a[position + 5];
this.bytes[1] = uint8a[position + 6];
this.bytes[0] = uint8a[position + 7];
return this.toBytes[0];
}
};
const HOSTBE_BEDOUBLE = {
size: 8,
read (uint8a, position) {
this.bytes[7] = uint8a[position + 0];
this.bytes[6] = uint8a[position + 1];
this.bytes[5] = uint8a[position + 2];
this.bytes[4] = uint8a[position + 3];
this.bytes[3] = uint8a[position + 4];
this.bytes[2] = uint8a[position + 5];
this.bytes[1] = uint8a[position + 6];
this.bytes[0] = uint8a[position + 7];
return this.toBytes[0];
}
};
let BEDOUBLE;
if (IS_HOST_LITTLE_ENDIAN) {
BEDOUBLE = HOSTLE_BEDOUBLE;
} else {
BEDOUBLE = HOSTBE_BEDOUBLE;
}
/**
* @const DoubleBE
* @type {BytePrimitive}
*/
const DoubleBE = new BytePrimitive(Object.assign({}, BEDOUBLE, {
toBytes: new Float64Array(1)
}));
/**
* @extends BytePrimitive
*/
class FixedAsciiString extends BytePrimitive {
/**
* @param {number} size - Number of bytes the FixedAsciiString uses.
*/
constructor (size) {
super({
size,
read (uint8a, position) {
let str = '';
for (let i = 0; i < size; i++) {
const code = uint8a[position + i];
assert(code <= 127, 'Non-ascii character in FixedAsciiString');
str += String.fromCharCode(code);
}
return str;
},
write (uint8a, position, value) {
for (let i = 0; i < size; i++) {
const code = value.charCodeAt(i);
assert(code <= 127, 'Non-ascii character in FixedAsciiString');
uint8a[position + i] = code;
}
return value;
}
});
}
}
export {
IS_HOST_LITTLE_ENDIAN,
BytePrimitive,
Uint8,
Uint16BE,
Int16BE,
Int32BE,
Uint32BE,
Uint16LE,
Uint32LE,
DoubleBE,
FixedAsciiString
};