UNPKG

@teradataprebuilt/fastcall-linux-node-v59

Version:

fastcall - Very fast, asynchronous, dyncall based foreign function interface library for Node.js

365 lines (311 loc) 10.7 kB
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /** * An interface for modeling and instantiating C-style data structures. This is * not a constructor per-say, but a constructor generator. It takes an array of * tuples, the left side being the type, and the right side being a field name. * The order should be the same order it would appear in the C-style struct * definition. It returns a function that can be used to construct an object that * reads and writes to the data structure using properties specified by the * initial field list. * * The only verboten field names are "ref", which is used used on struct * instances as a function to retrieve the backing Buffer instance of the * struct, and "ref.buffer" which contains the backing Buffer instance. * * * Example: * * ``` javascript * var ref = require('ref') * var Struct = require('ref-struct') * * // create the `char *` type * var charPtr = ref.refType(ref.types.char) * var int = ref.types.int * * // create the struct "type" / constructor * var PasswordEntry = Struct({ * 'username': 'string' * , 'password': 'string' * , 'salt': int * }) * * // create an instance of the struct, backed a Buffer instance * var pwd = new PasswordEntry() * pwd.username = 'ricky' * pwd.password = 'rbransonlovesnode.js' * pwd.salt = (Math.random() * 1000000) | 0 * * pwd.username // → 'ricky' * pwd.password // → 'rbransonlovesnode.js' * pwd.salt // → 820088 * ``` */ /** * Module dependencies. */ var ref = require('./ref'); var util = require('util'); var assert = require('assert'); var debug = require('debug')('ref:struct'); /** * Module exports. */ module.exports = Struct; /** * The Struct "type" meta-constructor. */ function Struct() { debug('defining new struct "type"'); /** * This is the "constructor" of the Struct type that gets returned. * * Invoke it with `new` to create a new Buffer instance backing the struct. * Pass it an existing Buffer instance to use that as the backing buffer. * Pass in an Object containing the struct fields to auto-populate the * struct with the data. */ function StructType(arg, data) { if (!(this instanceof StructType)) { return new StructType(arg, data); } debug('creating new struct instance'); var store; if (Buffer.isBuffer(arg)) { debug('using passed-in Buffer instance to back the struct', arg); assert(arg.length >= StructType.size, 'Buffer instance must be at least ' + StructType.size + ' bytes to back this struct type'); store = arg; arg = data; } else { debug('creating new Buffer instance to back the struct (size: %d)', StructType.size); store = new Buffer(StructType.size); } // set the backing Buffer store store.type = StructType; this['ref.buffer'] = store; if (arg) { for (var key in arg) { // hopefully hit the struct setters this[key] = arg[key]; } } StructType._instanceCreated = true; } // make instances inherit from the `proto` StructType.prototype = Object.create(proto, { constructor: { value: StructType, enumerable: false, writable: true, configurable: true } }); StructType.defineProperty = defineProperty; StructType.toString = toString; StructType.fields = {}; var opt = arguments.length > 0 && arguments[1] ? arguments[1] : {}; // Setup the ref "type" interface. The constructor doubles as the "type" object StructType.size = 0; StructType.alignment = 0; StructType.indirection = 1; StructType.isPacked = opt.packed ? Boolean(opt.packed) : false; StructType.get = get; StructType.set = set; // Read the fields list and apply all the fields to the struct // TODO: Better arg handling... (maybe look at ES6 binary data API?) var arg = arguments[0]; if (Array.isArray(arg)) { // legacy API arg.forEach(function (a) { var type = a[0]; var name = a[1]; StructType.defineProperty(name, type); }); } else if ((typeof arg === 'undefined' ? 'undefined' : _typeof(arg)) === 'object') { Object.keys(arg).forEach(function (name) { var type = arg[name]; StructType.defineProperty(name, type); }); } return StructType; } /** * The "get" function of the Struct "type" interface */ function get(buffer, offset) { debug('Struct "type" getter for buffer at offset', buffer, offset); if (offset > 0) { buffer = buffer.slice(offset); } return new this(buffer); } /** * The "set" function of the Struct "type" interface */ function set(buffer, offset, value) { debug('Struct "type" setter for buffer at offset', buffer, offset, value); var isStruct = value instanceof this; if (isStruct) { // optimization: copy the buffer contents directly rather // than going through the ref-struct constructor value['ref.buffer'].copy(buffer, offset, 0, this.size); } else { if (offset > 0) { buffer = buffer.slice(offset); } new this(buffer, value); } } /** * Custom `toString()` override for struct type instances. */ function toString() { return '[StructType]'; } /** * Adds a new field to the struct instance with the given name and type. * Note that this function will throw an Error if any instances of the struct * type have already been created, therefore this function must be called at the * beginning, before any instances are created. */ function defineProperty(name, type) { debug('defining new struct type field', name); // allow string types for convenience type = ref.coerceType(type); assert(!this._instanceCreated, 'an instance of this Struct type has already ' + 'been created, cannot add new "fields" anymore'); assert.equal('string', typeof name === 'undefined' ? 'undefined' : _typeof(name), 'expected a "string" field name'); assert(type && /object|function/i.test(typeof type === 'undefined' ? 'undefined' : _typeof(type)) && 'size' in type && 'indirection' in type, 'expected a "type" object describing the field type: "' + type + '"'); assert(type.indirection > 1 || type.size > 0, '"type" object must have a size greater than 0'); assert(!(name in this.prototype), 'the field "' + name + '" already exists in this Struct type'); var field = { type: type }; this.fields[name] = field; var cacheName = '_cache' + name; // define the getter/setter property var desc = { enumerable: true, configurable: true }; desc.get = function () { if (this[cacheName]) { return this[cacheName]; } debug('getting "%s" struct field (offset: %d)', name, field.offset); var got = ref.get(this['ref.buffer'], field.offset, type); if ((typeof got === 'undefined' ? 'undefined' : _typeof(got)) === 'object') { this[cacheName] = got; } return got; }; desc.set = function (value) { this[cacheName] = null; debug('setting "%s" struct field (offset: %d)', name, field.offset, value); return ref.set(this['ref.buffer'], field.offset, value, type); }; // calculate the new size and field offsets recalc(this); Object.defineProperty(this.prototype, name, desc); } function recalc(struct) { // reset size and alignment struct.size = 0; struct.alignment = 0; var fieldNames = Object.keys(struct.fields); // first loop through is to determine the `alignment` of this struct fieldNames.forEach(function (name) { var field = struct.fields[name]; var type = field.type; var alignment = type.alignment || ref.alignof.pointer; if (type.indirection > 1) { alignment = ref.alignof.pointer; } if (struct.isPacked) { struct.alignment = Math.min(struct.alignment || alignment, alignment); } else { struct.alignment = Math.max(struct.alignment, alignment); } }); // second loop through sets the `offset` property on each "field" // object, and sets the `struct.size` as we go along fieldNames.forEach(function (name) { var field = struct.fields[name]; var type = field.type; if (null != type.fixedLength) { // "ref-array" types set the "fixedLength" prop. don't treat arrays like one // contiguous entity. instead, treat them like individual elements in the // struct. doing this makes the padding end up being calculated correctly. field.offset = addType(type.type); for (var i = 1; i < type.fixedLength; i++) { addType(type.type); } } else { field.offset = addType(type); } }); function addType(type) { var offset = struct.size; var align = type.indirection === 1 ? type.alignment : ref.alignof.pointer; var padding = struct.isPacked ? 0 : (align - offset % align) % align; var size = type.indirection === 1 ? type.size : ref.sizeof.pointer; offset += padding; if (!struct.isPacked) { assert.equal(offset % align, 0, "offset should align"); } // adjust the "size" of the struct type struct.size = offset + size; // return the calulated offset return offset; } // any final padding? var left = struct.size % struct.alignment; if (left > 0) { debug('additional padding to the end of struct:', struct.alignment - left); struct.size += struct.alignment - left; } } /** * this is the custom prototype of Struct type instances. */ var proto = {}; /** * set a placeholder variable on the prototype so that defineProperty() will * throw an error if you try to define a struct field with the name "buffer". */ proto['ref.buffer'] = ref.NULL; /** * Flattens the Struct instance into a regular JavaScript Object. This function * "gets" all the defined properties. * * @api public */ proto.toObject = function toObject() { var obj = {}; Object.keys(this.constructor.fields).forEach(function (k) { obj[k] = this[k]; }, this); return obj; }; /** * Basic `JSON.stringify(struct)` support. */ proto.toJSON = function toJSON() { return this.toObject(); }; /** * `.inspect()` override. For the REPL. * * @api public */ proto.inspect = function inspect() { var obj = this.toObject(); // add instance's "own properties" Object.keys(this).forEach(function (k) { obj[k] = this[k]; }, this); return util.inspect(obj); }; /** * returns a Buffer pointing to this struct data structure. */ proto.ref = function ref() { return this['ref.buffer']; }; //# sourceMappingURL=struct.js.map