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
JavaScript
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
}
}