reified
Version:
JS Binary Data API. Structs, arrays, bitfields, and numbers. Reify and Reference like nobody's business.
158 lines (136 loc) • 4.11 kB
JavaScript
"use strict";
var utility = require('./utility');
var genesis = require('./genesis');
var bytesFor = utility.bytes;
var bits = utility.bits;
var BitfieldSubtype = genesis.Subtype.bind(BitfieldType);
var powers = Array.apply(null, Array(32)).map(Function.call.bind(Number)).map(Math.pow.bind(null, 2));
module.exports = BitfieldType;
// ################################
// ### BitfieldType Constructor ###
// ################################
function BitfieldType(name, flags, bytes){
if (typeof name !== 'string') {
bytes = flags;
flags = name;
name = '';
}
if (typeof flags === 'number') {
bytes = flags;
flags = [];
}
if (Array.isArray(flags)) {
flags = flags.reduce(function(ret, name, index){
ret[name] = 1 << index;
return ret;
}, {});
}
if (!(bytes > 0)) {
bytes = bytesFor(max(flags)) ;
}
// #############################
// ### BitfieldT Constructor ###
// #############################
function BitfieldT(data, offset, values) {
if (!genesis.isBuffer(data)) {
values = data || 0;
data = null;
}
this.rebase(data);
genesis.api(this, '_offset', +offset || 0);
if (Array.isArray(values)) {
values.forEach(function(flag){ this[flag] = true }, this);
} else if (typeof values === 'number') {
this.write(values);
} else if (Object(values) === values){
Object.keys(values).forEach(function(key){ this[key] = values[key] }, this);
}
return this;
};
BitfieldT.keys = flags;
// ######################
// ### BitfieldT Data ###
// ######################
BitfieldT.prototype = {
flags: flags,
length: bytes * 8,
toString: function toString(){
return this === BitfieldT.prototype
? '[object '+name+']'
: this.map(function(v){ return +v }).join('');
}
};
var out = BitfieldSubtype(name, bytes, BitfieldT);
return defineFlags(out);
}
function defineFlags(target) {
var largest = 0;
Object.keys(target.keys).forEach(function(flag){
var val = target.keys[flag];
largest = Math.max(largest, val);
Object.defineProperty(target.prototype, flag, {
configurable: true,
enumerable: true,
get: function( ){ return (this.read() & val) > 0 },
set: function(v){ this.write(v ? this.read() | val : this.read() & ~val) }
})
});
Array.apply(null, Array(target.bytes * 8)).forEach(function(n, i){
var power = powers[i];
if (power > largest) return;
Object.defineProperty(target.prototype, i, {
configurable: true,
enumerable: true,
get: function( ){ return (this.read() & power) > 0 },
set: function(v){ this.write(v ? this.read() | power : this.read() & ~power) }
});
});
return target;
}
// #########################
// ### BitfieldType Data ###
// #########################
genesis.Type(BitfieldType, {
DataType: 'bitfield',
forEach: Array.prototype.forEach,
reduce: Array.prototype.reduce,
map: Array.prototype.map,
get: function get(i){
return (this.read() & powers[i]) > 0;
},
set: function get(i){
this.write(this.read() | powers[i]);
return this;
},
unset: function unset(i){
this.write(this.read() & ~powers[i]);
return this;
},
write: function write(v){
this._data['writeUint'+this.length](this._offset, v);
return this;
},
read: function read(){
return this._data['readUint'+this.length](this._offset);
},
reify: function reify(deallocate){
var flags = Object.keys(this.flags);
if (flags.length) {
var val = flags.reduce(function(ret, flag, i){
if (this[flag]) ret.push(flag);
return ret;
}.bind(this), []);
} else {
var val = this.map(function(v){ return v });
}
if (deallocate) {
delete this._data;
delete this._offset;
}
return val;
}
});
function max(arr){
if (Array.isArray(arr)) return arr.reduce(function(r,s){ return Math.max(s, r) }, 0);
else return Object.keys(arr).reduce(function(r,s){ return Math.max(arr[s], r) }, 0);
}