structjs
Version:
**Structjs** allows mapping of contiguously allocated binary data to JavaScript objects for both Node.js and the Browser.
565 lines (496 loc) • 19.3 kB
JavaScript
!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.Struct=e():"undefined"!=typeof global?global.Struct=e():"undefined"!=typeof self&&(self.Struct=e())}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
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
}
}
},{"./types/array":2,"./types/hash":3,"./types/number":4,"./types/reference":5,"./types/storage":6,"./types/string":7,"./utils":8}],2:[function(require,module,exports){
var utils = require('../utils')
, StructReference = require('./reference')
var StructArray = module.exports = function(struct, length) {
this.struct = struct
Object.defineProperties(this, {
_length: { value: length, writable: true }
})
}
StructArray.prototype.read = function read(buffer, offset) {
var arr = [], parent = read.caller.parent
for (var i = 0, len = this.lengthFor(parent); i < len; ++i) {
var child
if (typeof this.struct === 'function') {
child = new this.struct
child.unpack(buffer, offset)
offset += child.lengthFor(parent) * child.sizeFor(parent)
} else {
child = this.struct.read(buffer, offset)
offset += this.struct.lengthFor(parent) * this.struct.sizeFor(parent)
}
arr.push(child)
}
return arr
}
StructArray.prototype.write = function write(buffer, offset, arr) {
var parent = write.caller.parent, child
this.setLength(this.lengthFor(parent, true), parent)
for (var i = 0, len = this.lengthFor(parent, true); i < len; ++i) {
if ((child = arr[i]) === undefined) break
if (typeof this.struct === 'function') {
child.pack(buffer, offset)
offset += child.lengthFor(parent) * child.sizeFor(parent)
} else {
this.struct.write(buffer, offset, child)
offset += this.struct.lengthFor(parent) * this.struct.sizeFor(parent)
}
}
}
StructArray.prototype.sizeFor = function(parent) {
return (this.struct.sizeFor ? this.struct.sizeFor(parent) : this.struct.prototype.sizeFor(parent))
* (this.struct.lengthFor ? this.struct.lengthFor(parent) : this.struct.prototype.lengthFor(parent))
}
StructArray.prototype.lengthFor = function(parent, writing) {
if (!this._length) return 0
if (this._length instanceof StructReference) {
if (writing) return parent[this.prop].length
return parent[this._length.prop]
} else if (typeof this._length === 'function') {
return this._length.call(parent)
}
return this._length
}
StructArray.prototype.setLength = function(value, parent) {
if (this._length instanceof StructReference)
parent[this._length.prop] = value
else if (typeof this._length === 'function') {
return
}
else this._length = value
}
},{"../utils":8,"./reference":5}],3:[function(require,module,exports){
var utils = require('../utils')
, StructReference = require('./reference')
var StructHash = module.exports = function(struct, key, length) {
this.struct = struct
this.key = key
Object.defineProperties(this, {
_length: { value: length, writable: true }
})
}
StructHash.prototype.read = function read(buffer, offset) {
var hash = {}, parent = read.caller.parent
for (var i = 0, len = this.lengthFor(parent); i < len; ++i) {
var child = new this.struct
child.unpack(buffer, offset)
offset += child.lengthFor(parent) * child.sizeFor(parent)
hash[child[this.key]] = child
}
return hash
}
StructHash.prototype.write = function write(buffer, offset, hash) {
var keys = Object.keys(hash), parent = write.caller.parent, child
this.setLength(this.lengthFor(parent, true), parent)
for (var i = 0, len = this.lengthFor(parent); i < len; ++i) {
if (!(child = hash[keys[i]])) continue
child.pack(buffer, offset)
offset += child.lengthFor(parent) * child.sizeFor(parent)
}
}
StructHash.prototype.sizeFor = function(parent) {
return (this.struct.sizeFor ? this.struct.sizeFor(parent) : this.struct.prototype.sizeFor(parent))
* (this.struct.lengthFor ? this.struct.lengthFor(parent) : this.struct.prototype.lengthFor(parent))
}
StructHash.prototype.lengthFor = function(parent, writing) {
if (!this._length) return 0
if (this._length instanceof StructReference) {
if (writing) return Object.keys(parent[this.prop]).length
return parent[this._length.prop]
}
return this._length
}
StructHash.prototype.setLength = function(value, parent) {
if (this._length instanceof StructReference)
parent[this._length.prop] = value
else this._length = value
}
},{"../utils":8,"./reference":5}],4:[function(require,module,exports){
var utils = require('../utils')
var StructNumber = module.exports = function(read, write, length) {
this.methods = { read: read, write: write }
Object.defineProperties(this, {
_offset: { value: null, writable: true },
_length: { value: null, writable: true }
})
utils.options.call(this, length)
}
StructNumber.prototype.with = function(opts) {
if (!opts.length) opts.length = this._length
return new StructNumber(this.methods.read, this.methods.write, opts)
}
StructNumber.prototype.from = function(offset) {
return this.with({ external: true, offset: offset })
}
StructNumber.prototype.read = function read(buffer, offset) {
var parent = read.caller.parent
return buffer[this.methods.read](this.external ? this.offsetFor(parent) : offset)
}
StructNumber.prototype.write = function write(buffer, offset, value) {
var parent = write.caller.parent
buffer[this.methods.write](this.external ? this.offsetFor(parent) : offset, value)
}
StructNumber.prototype.lengthFor = function() {
return 1
}
StructNumber.prototype.sizeFor = function() {
return this._length
}
utils.methodsFor(StructNumber.prototype, '_offset', 'offsetFor', 'setOffset')
},{"../utils":8}],5:[function(require,module,exports){
var StructReference = module.exports = function(prop) {
this.prop = prop
}
},{}],6:[function(require,module,exports){
var utils = require('../utils')
, StructReference = require('./reference')
, StructArray = require('./array')
var StructStorage = module.exports = function(path, opts) {
this.path = path
if (opts instanceof StructReference)
opts = { offset: opts }
opts = opts || {}
Object.defineProperties(this, {
_offset: { value: opts.offset, writable: true }
})
}
StructStorage.prototype.read = function read(view, offset) {
var parent = read.caller.parent
, shift = this.offsetFor(parent) || offset
!function traverse(path, definition, target) {
var step = path.shift(), type = definition[step]
traverse.parent = target
if (!path.length) {
target[step] = type.read(view, shift)
} else if (type instanceof StructArray) {
target[step].forEach(function(target) {
traverse(path.concat([]), type.struct._definition, target)
})
} else {
traverse(path, type, target[step])
}
}(this.path.split('.'), parent._definition, parent)
}
StructStorage.prototype.write = function write(view, offset, _, relativeOffset) {
var parent = write.caller.parent, shift = 0
this.setOffset(relativeOffset, parent)
!function traverse(path, definition, target) {
var step = path.shift(), type = definition[step]
traverse.parent = target
if (!path.length) {
type.setOffset(shift, target)
var value = target[step], target = type.prototype ? target[step] : target
type.write(view, offset, value, relativeOffset)
shift += type.lengthFor(target, true) * type.sizeFor(target, true)
} else if (type instanceof StructArray) {
target[step].forEach(function(target) {
traverse(path.concat([]), type.struct._definition, target)
})
} else {
traverse(path, type, target[step])
}
}(this.path.split('.'), parent._definition, parent)
}
StructStorage.prototype.lengthFor = function() {
return 1
}
StructStorage.prototype.sizeFor = function(parent, writing) {
var size = 0
!function traverse(path, definition, target) {
var step = path.shift(), type = definition[step]
traverse.parent = target
if (!path.length) {
if (type.prototype) target = target[step]
size += type.lengthFor(target, writing) * type.sizeFor(target, writing)
} else if (type instanceof StructArray) {
target[step].forEach(function(target) {
traverse(path.concat([]), type.struct._definition, target)
})
} else {
traverse(path, type, target[step])
}
}(this.path.split('.'), parent._definition, parent)
return size
}
utils.methodsFor(StructStorage.prototype, '_offset', 'offsetFor', 'setOffset')
},{"../utils":8,"./array":2,"./reference":5}],7:[function(require,module,exports){
var utils = require('../utils')
, StructReference = require('./reference')
var StructString = module.exports = function(length) {
Object.defineProperties(this, {
_offset: { value: null, writable: true },
_length: { value: null, writable: true },
_size: { value: null, writable: true }
})
utils.options.call(this, length)
}
StructString.prototype.read = function read(buffer, offset) {
var str = [], storage, parent = read.caller.parent
, shift = this.external
? this.offsetFor(parent)
: (this.storage ? offset + this.offsetFor(parent) : offset)
for (var i = 0, len = this.lengthFor(parent), step = this.sizeFor() === 2 ? 2 : 1; i < len; ++i) {
str.push(buffer[this.sizeFor() === 2 ? 'getUint16' : 'getUint8'](shift + i * step, this.littleEndian))
}
return String.fromCharCode.apply(null, str)
}
StructString.prototype.write = function write(buffer, offset, value) {
var str = [], storage, parent = write.caller.parent
, shift = this.external
? this.offsetFor(parent)
: (this.storage ? offset + this.offsetFor(parent) : offset)
this.setLength(this.lengthFor(parent, true), parent)
for (var i = 0, len = this.lengthFor(parent), step = this.sizeFor() === 2 ? 2 : 1; i < len; ++i) {
var code = value.charCodeAt(i) || 0x00
buffer[this.sizeFor() === 2 ? 'setUint16' : 'setUint8'](shift + i * step, code, this.littleEndian)
}
}
StructString.prototype.sizeFor = function() {
return this._size || 1
}
StructString.prototype.lengthFor = function(parent, writing) {
if (this._length instanceof StructReference) {
if (writing) return parent[this.prop].length
return parent[this._length.prop]
}
return this._length || 0
}
StructString.prototype.setLength = function(value, parent) {
if (this._length instanceof StructReference)
parent[this._length.prop] = value
else this._length = value
}
utils.methodsFor(StructString.prototype, '_offset', 'offsetFor', 'setOffset')
},{"../utils":8,"./reference":5}],8:[function(require,module,exports){
var StructReference = require('./types/reference')
exports.methodsFor = function(obj, prop, get, set) {
obj[get] = function(parent) {
if (!this[prop]) return 0
if (this[prop] instanceof StructReference)
return parent[this[prop].prop]
else if (typeof this[prop] === 'function')
return this[prop].call(parent)
return this[prop]
}
if (!set) return
obj[set] = function(value, parent) {
if (this[prop] instanceof StructReference)
parent[this[prop].prop] = value
else this[prop] = value
}
}
exports.options = function(opts) {
if (typeof opts === 'object') {
this._offset = opts.offset
this._length = opts.length
this._size = opts.size
this.$unpacked = opts.$unpacked
this.$packing = opts.$packing
this.external = opts.external === true
this.storage = opts.storage
this.littleEndian = opts.littleEndian === true
} else {
this._length = opts
}
}
},{"./types/reference":5}]},{},[1])
(1)
});
;