/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const accessors = require("./accessors")
const assert = require("./assert")
const BinaryRecord = require("./binary-record").BinaryRecord
const getArrayRecord = require("./array-record").getArrayRecord
const getObjectRecord = require("./object-record").getObjectRecord
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const NUMBER_ACCESSOR_TYPE = /^(?:u?int(?:8|16|32)|bit[1-7]|float(?:32|64)?|double)$/
const STRING_TYPE = /^([a-zA-Z0-9_-]+)\(([1-9][0-9]*)\)$/
const ARRAY_BRACKET = /^([a-zA-Z0-9_-]+(?:\[[1-9][0-9]*\])*)\[([1-9][0-9]*)\]$/
/**
* Parse bracket notations in the given type name.
*
* e.g.
* - `"int8[4]" → ["int8", 4]`
* - `"int32[3][4]" → ["int32", 3, 4]`
* - `"string(16)[4]" → ["string(16)", 4]`
*
* @param {string} type - The type name to parse.
* @returns {any[]} Parsed information. This is an array.
* 1st element is originated type name.
* 2nd and later elements are array lengths.
* @private
*/
function parseArrayBrackets(type) {
if (!(typeof type === "string")) {
return type
}
const m = ARRAY_BRACKET.exec(type)
if (m == null) {
return type
}
const length = parseInt(m[2], 10)
const elementType = parseArrayBrackets(m[1])
if (Array.isArray(elementType)) {
elementType.push(length)
return elementType
}
return [elementType, length]
}
/**
* Gets the accessor implementation of the given type name.
*
* If the accessor implementation of the type had not found, this will throw an
* error.
*
* @param {string} type - The type name to get.
* @returns {bre/accessors~Accessor} Found accessor.
* @private
*/
function getAccessor(type) {
const strippedType = parseArrayBrackets(type)
// Address arrays.
if (Array.isArray(strippedType)) {
let accessor = getAccessor(strippedType[0])
for (let i = strippedType.length - 1; i >= 1; --i) {
const length = strippedType[i]
accessor = accessors.record(getArrayRecord(accessor, length))
}
return accessor
}
// Embedded types and array shorthands.
if (typeof strippedType === "string") {
// Number types.
if (NUMBER_ACCESSOR_TYPE.test(strippedType)) {
return accessors[strippedType]
}
// String types.
let m = null
if ((m = STRING_TYPE.exec(strippedType)) != null) {
const byteLength = parseInt(m[2], 10)
const encoding = (m[1] === "string") ? "utf8" : m[1]
return accessors.string(encoding, byteLength)
}
}
// Record types.
if (typeof strippedType === "function" &&
strippedType.prototype instanceof BinaryRecord
) {
return accessors.record(strippedType)
}
throw new Error(`invalid type: ${strippedType}`)
}
//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------
/**
* Defines an object record.
*
* Object records are similar to C `struct`.
* It has several properties and it can map to binary blocks.
*
* const ExampleRecord = bre.defineObjectRecord("ExampleRecord", [
* {type: "uint8", name: "a"},
* {type: "uint8", name: "b"},
* ])
*
* const buffer = Buffer.from([0x01, 0x02])
* const record = ExampleRecord.view(buffer)
*
* console.log("a =", record.a) // => "a = 1"
* console.log("buffer[0] =", buffer[0]) // => "buffer[0] = 1"
* record.a = 7
* console.log("a =", record.a) // => "a = 7"
* console.log("buffer[0] =", buffer[0]) // => "buffer[0] = 7"
*
* @param {string} className - The class name of new record.
*
* - This requires [a valid identifier of ECMAScript].
* - This requires an upper case character at the head of the name.
*
* [a valid identifier of ECMAScript]: http://www.ecma-international.org/ecma-262/7.0/#sec-identifiers
*
* @method module:bre.defineObjectRecord
* @param {Array<module:bre.FieldDefinition|module:bre.SkipDefinition>} fields
* The list of field definition.
* @returns {class} The defined record class.
*/
exports.defineObjectRecord = function defineObjectRecord(className, fields) {
assert.instanceOf(fields, Array, "fields")
const definition = fields.reduce(
(d, f, i) => {
if ("skip" in f) {
assert.integer(f.skip, `fields[${i}].skip`)
assert.gte(f.skip, 1, `fields[${i}].skip`)
d.bitLength += f.skip
}
else {
const name = f.name
const accessor = getAccessor(f.type)
const offset = d.bitLength
d.fields.push({name, accessor, offset})
d.bitLength += accessor.bits
}
return d
},
{className, fields: [], bitLength: 0}
)
return getObjectRecord(definition)
}
/**
* Defines an array record.
*
* The class this method defined is array-like.
* It has the ability to access by indexes and iterators.
*
* const ListRecord = bre.defineArrayRecord("uint8", 3)
*
* const buffer = Buffer.from([0x01, 0x02, 0x03])
* const record = ListRecord.view(buffer)
*
* console.log(record.length) // => 3
* console.log(record[0]) // => 1
* console.log(record[1]) // => 2
* console.log(record[2]) // => 3
*
* for (const value of record) {
* console.log(value) // => 1 2 3
* }
*
* And it has several methods of `Array.prototype`:
*
* - `concat`
* - `entries`
* - `every`
* - `filter`
* - `find`
* - `findIndex`
* - `forEach`
* - `includes`
* - `indexOf`
* - `join`
* - `keys`
* - `lastIndexOf`
* - `map`
* - `reduce`
* - `reduceRight`
* - `slice`
* - `some`
* - `values`
*
* @method module:bre.defineArrayRecord
* @param {string|class|Array} type - The type of the elements.
* See {@link module:bre.FieldDefinition}'s `type` property for details.
* @param {number} length - The count of elements.
* @returns {class} The defined record class.
*/
exports.defineArrayRecord = function defineArrayRecord(type, length) {
return getArrayRecord(getAccessor(type), length)
}
/**
* The definition of a field.
*
* This type is used to define object records.
* A field defines a getter/setter pair of the record.
*
* const SubRecord = bre.defineObjectRecord("SubRecord", [
* {type: "uint8", name: "a"},
* {type: "uint8", name: "b"},
* ])
*
* const Record = bre.defineObjectRecord("Record", [
* {type: "uint8[4]", name: "list"},
* {type: SubRecord, name: "sub"},
* {type: [SubRecord, 8], name: "subList"},
* ])
*
* @memberof module:bre
* @interface FieldDefinition
*/
/**
* The name of this field.
*
* - This requires [a valid identifier of ECMAScript].
* - The following names are forbidden:
* - `__defineGetter__`
* - `__defineSetter__`
* - `__lookupGetter__`
* - `__lookupSetter__`
* - `__proto__`
* - `__noSuchMethod__`
* - `constructor`
* - `hasOwnProperty`
* - `isPrototypeOf`
* - `propertyIsEnumerable`
* - `toJSON`
* - `toLocaleString`
* - `toString`
* - `valueOf`
*
* [a valid identifier of ECMAScript]: http://www.ecma-international.org/ecma-262/7.0/#sec-identifiers
*
* @name module:bre.FieldDefinition#name
* @type {string}
*/
/**
* The type name of this field.
*
* - **Signed integer types:**
* - `"int8"` - 8 bits signed integer (`-128..127`).
* - `"int16"` - 16 bits signed integer (`-32768..32767`).
* - `"int32"` - 32 bits signed integer (`-2147483648..2147483647`).
* - **Unsigned integer types:**
* - `"uint8"` - 8 bits unsigned integer (`0..255`).
* - `"uint16"` - 16 bits unsigned integer (`0..65535`).
* - `"uint32"` - 32 bits unsigned integer (`0..4294967295`).
* - **Small unsigned integer types:**
* - `"bit1"` - 1 bits unsigned integer (`0..1`).
* - `"bit2"` - 2 bits unsigned integer (`0..3`).
* - `"bit3"` - 3 bits unsigned integer (`0..7`).
* - `"bit4"` - 4 bits unsigned integer (`0..15`).
* - `"bit5"` - 5 bits unsigned integer (`0..31`).
* - `"bit6"` - 6 bits unsigned integer (`0..63`).
* - `"bit7"` - 7 bits unsigned integer (`0..127`).
* - **Floating point number types:**
* - `"float32"` - 32 bits floating point number (a.k.a. `float`).
* - `"float64"` - 64 bits floating point number (a.k.a. `double`).
* - **String types:**
* - `"string(N)"` - fixed length (N bytes) string (e.g. `"string(32)"`).
* The default encoding of this type is `utf8`. It can use a specific
* encoding type instead of `"string"`. For examples:
* - `"utf8(N)"` - fixed length (N bytes) string.
* - `"utf16be(N)"` - fixed length (N bytes) string.
* - `"shift_jis(N)"` - fixed length (N bytes) string.
* - **Note:** It needs to set
* {@link module:bre/lib/text-encoders.TextEncoder text encoder}
* before use string types.
* - **Record types:**
* - Another record class (e.g. `ExampleRecord`).
* - **Array types:**
* - `"T[N]"` - fixed length (count N) array. T is another type
* (e.g. `"uint8[8]"`). An array type can be T (e.g. `"uint8[2][3]"`).
* - `[T, N]` - fixed length (count N) array. T is another type
* (e.g. `["uint8", 8]`, this is same as `"uint8[8]"`). This notation
* is for record types (e.g. `[ExampleRecord, 8]`).
*
* @name module:bre.FieldDefinition#type
* @type {string|class|Array}
*/
/**
* The definition of skipping for object records.
*
* This type is used to define object records.
*
* const SubRecord = bre.defineObjectRecord("SubRecord", [
* {type: "bit4", name: "a"},
* {skip: 4},
* {type: "uint8", name: "b"},
* ])
*
* @interface module:bre.SkipDefinition
*/
/**
* The size in bits to skip.
*
* @name module:bre.SkipDefinition#skip
* @type {number}
*/