reified
Version:
JS Binary Data API. Structs, arrays, bitfields, and numbers. Reify and Reference like nobody's business.
330 lines (276 loc) • 8.39 kB
JavaScript
"use strict";
var utility = require('./utility');
var isObject = utility.isObject;
var hasProto = !!Function.__proto__;
var types = {};
var exp = exports = module.exports = {
Type: Type,
Subtype: Subtype,
OpaqueType: OpaqueType,
lookupType: lookupType,
registerType: registerType,
DataBuffer: require('./buffer'),
types: types,
isBuffer: isBuffer,
api: api,
nullable: function(object, key){
Object.defineProperty(object, key, nullable);
delete object[key];
}
};
var nullable = { value: undefined, writable: true, configurable: true };
var hidden = { configurable: true, writable: true, value: 0 };
function isBuffer(o){
return exp.DataBuffer.isBuffer(o) || exp.DataBuffer.isDataBuffer(o);
}
function api(o, n, v){
if (Object(n) === n) {
Object.keys(n).forEach(function(k){
api(o, k, n[k]);
});
} else {
hidden.value = v;
Object.defineProperty(o, n, hidden);
}
}
function registerType(name, type){
if (name in types) return types[name];
if (name.length) return types[name] = type;
return type;
}
function lookupType(name, label){
if (typeof name !== 'string') {
return name;
}
// pointer type
if (name[0] === '*') {
name = name.slice(1);
var type = lookupType(name);
if (typeof type !== 'string') {
if (typeof label === 'string') {
name = label;
}
return createType('pointer', name, type);
}
}
if (name[name.length-1] === ']') {
var count = name.match(/(.*)\[(\d+)\]$/);
if (!count) {
return name in types ? types[name] : name;
}
name = count[1];
count = +count[2];
if (name === 'Char') {
return createType('string', count);
}
if (typeof label === 'string') {
return createType('array', label, lookupType(name), count);
} else {
if (type === 'Char') {
return createType('string', count);
}
var type = lookupType(name);
if (type === name) {
return createType('array', name, count);
} else {
name = type.name + 'x' + count;
return createType('array', name, type, count);
}
}
}
return name in types ? types[name] : name;
}
function createType(name, a, b, c){
var type = require('./'+name);
return new type(a, b, c);
}
// ########################
// ### Genesis for Type ###
// ########################
function Type(ctor, proto){
ctor.prototype = Super(eval('(function Empty'+ctor.name.replace(/Type$/,'T')+'(){})'), Type);
ctor.prototype.Type = ctor.name;
ctor.prototype.prototype = copy(proto, Object.create(Data));
types[ctor.name.replace(/Type$/,'')] = ctor;
inspectors(ctor.prototype, ctor.name);
}
function Super(ctor, superctor, proto){
if (hasProto) {
ctor.__proto__ = superctor;
} else {
copy(superctor, ctor);
}
ctor.prototype = proto || Object.create(superctor.prototype);
ctor.prototype.constructor = ctor;
return ctor;
}
function inspectors(obj, name){
if (typeof imports === 'undefined') {
obj.inspect = require('./inspect')('Type', name);
obj.prototype.inspect = require('./inspect')('Data', name);
}
}
copy({
Class: 'Type',
//toString: function toString(){ return '[object '+this.name+'Type]' },
isInstance: function isInstance(o){ return this.prototype.isPrototypeOf(o) },
array: function array(n){ return createType('array', this, n) },
typeDef: function typeDef(name, reifier){
var iface = Super(createInterface(name, ifaceMap.get(this)), this);
if (typeof reifier === 'function') {
iface.reifier(reifier);
}
return iface;
},
reifier: function reifier(handler){
var oldReifier = this.prototype.reify;
if (!handler) {
return oldReifier;
} else {
this.prototype.reify = function reify(){
var self = this;
return handler.call(this, function(){ return oldReifier.call(self) });
}
return this;
}
}
}, Type);
Array.apply(null, Array(20)).forEach(function(n, i){
Object.defineProperty(Type, i, {
configurable: true,
get: function(){ return this.array(i) }
});
});
Object.defineProperty(Type, 'ptr', {
configurable: true,
get: function(){ return createType('pointer', this.displayName, this) }
});
var ifaceMap = function(){
var ifaces = [];
var ctors = [];
return {
set: function(iface, ctor){
ifaces.push(iface);
ctors.push(ctor);
return iface;
},
get: function(iface){
var index = ifaces.indexOf(iface);
return ~index ? ctors[index] : null;
}
}
}();
function createInterface(name, ctor, type){
var fnName = name.replace(/[^\w0-9_$]/g, '');
if (name[0] === '*') {
var count = name.match(/^[*]+/)[0].length;
fnName = Array(count + 1).join('Ptr_') + fnName;
}
var src = 'return function '+fnName+'(data, offset, values){ return Ctor.call(Object.create('+fnName+'.prototype), data, offset, values) }';
var iface = Function('Ctor', src)(ctor);
ifaceMap.set(iface, ctor);
if (type) {
if (hasProto) iface.__proto__ = type.prototype;
else copy(type.prototype, iface);
}
iface.prototype = ctor.prototype;
api(iface, 'displayName', name);
if (name) registerType(name, iface);
return copy(ctor, iface);
}
function Subtype(name, bytes, ctor){
ctor.bytes = bytes;
ctor.prototype.bytes = bytes;
ctor.prototype = copy(ctor.prototype, Object.create(this.prototype.prototype));
return ctor.prototype.constructor = createInterface(name, ctor, this);
}
// ########################
// ### Genesis for Data ###
// ########################
var Data = Type.prototype = {
//__proto__: EventEmitter.prototype,
Class: 'Data',
toString: function toString(){ return '[object '+this.constructor.displayName+']' },
rebase: function rebase(data){
if (data == null) {
data = new exp.DataBuffer(this.bytes);
data.fill(0);
} else if (data._data) {
data = data._data;
} else if (exp.DataBuffer.isBuffer(data)) {
data = new exp.DataBuffer(data);
}
api(this, '_data', data);
},
realign: function realign(offset){
this._offset = +offset || 0;
},
clone: function clone(){
return new this.constructor(this._data, this._offset);
},
copy: function copy(data, offset){
return new this.constructor(this._data.clone());
},
cast: function cast(type, align){
if (typeof (type = lookupType(type)) === 'string') throw new TypeError('Unknown type "'+type+'"');
if (this instanceof Opaque) {
return new type(this._data, this._offset);
}
if (type.bytes < this.bytes) throw new RangeError('Tried to cast to a smaller size "'+type.name+'"');
if (this._data.length < type.bytes) throw new RangeError('Type is bigger than this buffer: "'+type.name+'"');
align = (type.bytes === this.bytes || !align) ? 0 : align < 0 ? this.bytes-type.bytes : +align;
return new type(this._data, this._offset + align);
},
pointer: function pointer(){
var PtrType = lookupType('*'+this.constructor.displayName);
return new PtrType(this);
}
};
function Opaque(data, offset, size){
if (isFinite(data)) {
size = data;
data = null;
if (!isFinite(size)) {
throw new Error('Opaque types must be given a size or buffer');
}
}
this.bytes = size || 0;
this.rebase(data);
api(this, '_offset', +offset || 0);
return this;
}
Super(Opaque, Type);
ifaceMap.set(Opaque, Opaque);
registerType('Opaque', Opaque);
inspectors(Opaque, 'Opaque');
api(Opaque, 'displayName', 'Opaque');
Opaque.bytes = Opaque.prototype.bytes = 0;
Opaque.prototype.DataType = 'opaque';
Opaque.prototype.reify = function reify(){
//return this._data;
return null;
}
Opaque.prototype.write = function write(){
throw new Error('Opaque data must be cast to a specific type before it can be written to');
}
Opaque.prototype.rebase = function rebase(buffer){
if (buffer != null) {
Data.rebase.call(this, buffer);
}
}
function OpaqueType(size){
function OpaqueT(data, offset){
Opaque.call(this, data, offset, size);
}
Super(OpaqueT, Opaque);
OpaqueT.bytes = size;
return OpaqueT;
}
function copy(from, to, hidden){
Object[hidden ? 'getOwnPropertyNames' : 'keys'](from).forEach(function(key){
var desc = Object.getOwnPropertyDescriptor(from, key);
desc.enumerable = false;
Object.defineProperty(to, key, desc);
});
return to;
}