UNPKG

structjs

Version:

**Structjs** allows mapping of contiguously allocated binary data to JavaScript objects for both Node.js and the Browser.

228 lines (196 loc) 7.16 kB
var StructNumber = require('./types/number') , StructString = require('./types/string') , StructHash = require('./types/hash') , StructArray = require('./types/array') , StructReference = require('./types/reference') , StructStorage = require('./types/storage') , utils = require('./utils') var Struct = module.exports = function(definition, opts) { if (!opts) opts = {} var StructType = function(values) { Object.defineProperties(this, { _view: { writable: true, value: null }, _offset: { writable: true, value: null }, _definition: { writable: false, value: definition} }) for (var key in values) { if (key in definition) this[key] = cloneValue(values[key]) } var self = this extensions.forEach(function(extension) { for (var key in values) { if (key in extension.extension) self[key] = cloneValue(values[key]) } }) } Object.defineProperties(StructType, { _offset: { writable: true, value: null }, _definition: { writable: false, value: definition } }) StructType.storage = opts.storage StructType._offset = opts.offset utils.methodsFor(StructType, '_offset', 'offsetFor', 'setOffset') var extensions = [] StructType.conditional = function(condition, extension) { extensions.push({ condition: condition, extension: extension }) return this } StructType.prototype.unpack = function(view, offset) { if (!(view instanceof DataView)) throw new Error('DataView expected') if (!offset) offset = 0 this._view = view var self = this function apply(definition) { for (var prop in definition) { var type = definition[prop] definition[prop].prop = prop if (type.storage) continue self[prop] = type.read(view, offset) if (typeof type.$unpacked === 'function') self[prop] = type.$unpacked.call(self, self[prop]) if (self[prop] === undefined) delete self[prop] if (!type.external) offset += type.lengthFor(self) * type.sizeFor(self) } } apply.parent = this apply(definition) extensions.forEach(function(extension) { if (!extension.condition.call(self)) return apply(extension.extension) }) if (typeof this.$unpacked === 'function') this.$unpacked() return this } StructType.prototype.pack = function(view, offset) { // console.log('Size: %d, Length: %d', this.sizeFor(this, true), this.lengthFor(this, true)) if (typeof this.$packing === 'function') this.$packing() if (!view) view = new DataView(new ArrayBuffer(this.lengthFor(this, true) * this.sizeFor(this, true))) if (!offset) offset = 0 var self = this function apply(definition) { var start = offset // write Storages first for (var prop in definition) { var type = definition[prop] if (type.external || type.storage) continue if (type instanceof StructStorage) type.write(view, offset, self[prop], offset - start) offset += type.lengthFor(self, true) * type.sizeFor(self, true) } offset = start // write everything left other than StructNumber second for (var prop in definition) { var type = definition[prop] if (type.external || type.storage) continue if (!(type instanceof StructNumber) && !(type instanceof StructStorage)) type.write(view, offset, self[prop]) offset += type.lengthFor(self, true) * type.sizeFor(self, true) } // write StructNumber last offset = start for (var prop in definition) { var type = definition[prop] if (type.external || type.storage) continue var value = self[prop] if (typeof type.$packing === 'function') value = type.$packing.call(self, value) if (type instanceof StructNumber) type.write(view, offset, value) offset += type.lengthFor(self, true) * type.sizeFor(self, true) } } apply.parent = this apply(definition) extensions.forEach(function(extension) { if (!extension.condition.call(self)) return apply(extension.extension) }) return view.buffer } StructType.read = function read(buffer, offset) { var self = new this, parent = read.caller.parent , shift = this.storage ? this.offsetFor(parent) : offset self.unpack(buffer, shift) return self } StructType.write = function write(buffer, offset, value, relativeOffset) { var parent = write.caller.parent , shift = this.storage ? offset + this.offsetFor(parent) : offset this.setOffset(this.storage ? relativeOffset + this.offsetFor(parent) : offset, parent) value.pack(buffer, shift) } StructType.prototype.lengthFor = StructType.lengthFor = function() { return 1 } StructType.prototype.sizeFor = StructType.sizeFor = function(parent, writing) { var self = this function sizeOf(definition) { return Object.keys(definition) .filter(function(prop) { return !definition[prop].external && !definition[prop].storage }) .map(function(prop) { return definition[prop].lengthFor(parent, !!writing) * definition[prop].sizeFor(parent, !!writing) }) .reduce(function(lhs, rhs) { return lhs + rhs }, 0) } var size = sizeOf(definition) extensions.forEach(function(extension) { if (!extension.condition.call(parent)) return size += sizeOf(extension.extension) }) return size } StructType.prototype.clone = function() { var clone = new StructType(this) if (typeof clone.$unpacked === 'function') clone.$unpacked() return clone } return StructType } Struct.Int8 = new StructNumber('getInt8', 'setInt8', 1) Struct.Uint8 = new StructNumber('getUint8', 'setUint8', 1) Struct.Int16 = new StructNumber('getInt16', 'setInt16', 2) Struct.Uint16 = new StructNumber('getUint16', 'setUint16', 2) Struct.Int32 = new StructNumber('getInt32', 'setInt32', 4) Struct.Uint32 = new StructNumber('getUint32', 'setUint32', 4) Struct.Float32 = new StructNumber('getFloat32', 'setFloat32', 4) Struct.Float64 = new StructNumber('getFloat64', 'setFloat64', 8) Struct.String = function(length) { return new StructString(length) } Struct.Hash = function(struct, key, length) { return new StructHash(struct, key, length) } Struct.Array = function(struct, length) { return new StructArray(struct, length) } Struct.Reference = Struct.Ref = function(prop) { return new StructReference(prop) } Struct.Storage = function(path, opts) { return new StructStorage(path, opts) } // Helper function cloneValue(val) { if (val === undefined) { return undefined } else if (typeof val.clone === 'function') { return val.clone() } else if (Array.isArray(val)) { return [].concat(val) } else if (typeof val === 'object') { var clone = {} for (key in val) clone[key] = cloneValue(val[key]) return clone } else { return val } }