; | |
module.exports = Writer; | |
var util = require("./util/minimal"); | |
var BufferWriter; // cyclic | |
var LongBits = util.LongBits, | |
base64 = util.base64, | |
utf8 = util.utf8; | |
/** | |
* Constructs a new writer operation instance. | |
* @classdesc Scheduled writer operation. | |
* @constructor | |
* @param {function(*, Uint8Array, number)} fn Function to call | |
* @param {number} len Value byte length | |
* @param {*} val Value to write | |
* @ignore | |
*/ | |
function Op(fn, len, val) { | |
/** | |
* Function to call. | |
* @type {function(Uint8Array, number, *)} | |
*/ | |
this.fn = fn; | |
/** | |
* Value byte length. | |
* @type {number} | |
*/ | |
this.len = len; | |
/** | |
* Next operation. | |
* @type {Writer.Op|undefined} | |
*/ | |
this.next = undefined; | |
/** | |
* Value to write. | |
* @type {*} | |
*/ | |
this.val = val; // type varies | |
} | |
/* istanbul ignore next */ | |
function noop() {} // eslint-disable-line no-empty-function | |
/** | |
* Constructs a new writer state instance. | |
* @classdesc Copied writer state. | |
* @memberof Writer | |
* @constructor | |
* @param {Writer} writer Writer to copy state from | |
* @ignore | |
*/ | |
function State(writer) { | |
/** | |
* Current head. | |
* @type {Writer.Op} | |
*/ | |
this.head = writer.head; | |
/** | |
* Current tail. | |
* @type {Writer.Op} | |
*/ | |
this.tail = writer.tail; | |
/** | |
* Current buffer length. | |
* @type {number} | |
*/ | |
this.len = writer.len; | |
/** | |
* Next state. | |
* @type {State|null} | |
*/ | |
this.next = writer.states; | |
} | |
/** | |
* Constructs a new writer instance. | |
* @classdesc Wire format writer using `Uint8Array` if available, otherwise `Array`. | |
* @constructor | |
*/ | |
function Writer() { | |
/** | |
* Current length. | |
* @type {number} | |
*/ | |
this.len = 0; | |
/** | |
* Operations head. | |
* @type {Object} | |
*/ | |
this.head = new Op(noop, 0, 0); | |
/** | |
* Operations tail | |
* @type {Object} | |
*/ | |
this.tail = this.head; | |
/** | |
* Linked forked states. | |
* @type {Object|null} | |
*/ | |
this.states = null; | |
// When a value is written, the writer calculates its byte length and puts it into a linked | |
// list of operations to perform when finish() is called. This both allows us to allocate | |
// buffers of the exact required size and reduces the amount of work we have to do compared | |
// to first calculating over objects and then encoding over objects. In our case, the encoding | |
// part is just a linked list walk calling operations with already prepared values. | |
} | |
var create = function create() { | |
return util.Buffer | |
? function create_buffer_setup() { | |
return (Writer.create = function create_buffer() { | |
return new BufferWriter(); | |
})(); | |
} | |
/* istanbul ignore next */ | |
: function create_array() { | |
return new Writer(); | |
}; | |
}; | |
/** | |
* Creates a new writer. | |
* @function | |
* @returns {BufferWriter|Writer} A {@link BufferWriter} when Buffers are supported, otherwise a {@link Writer} | |
*/ | |
Writer.create = create(); | |
/** | |
* Allocates a buffer of the specified size. | |
* @param {number} size Buffer size | |
* @returns {Uint8Array} Buffer | |
*/ | |
Writer.alloc = function alloc(size) { | |
return new util.Array(size); | |
}; | |
// Use Uint8Array buffer pool in the browser, just like node does with buffers | |
/* istanbul ignore else */ | |
if (util.Array !== Array) | |
Writer.alloc = util.pool(Writer.alloc, util.Array.prototype.subarray); | |
/** | |
* Pushes a new operation to the queue. | |
* @param {function(Uint8Array, number, *)} fn Function to call | |
* @param {number} len Value byte length | |
* @param {number} val Value to write | |
* @returns {Writer} `this` | |
* @private | |
*/ | |
Writer.prototype._push = function push(fn, len, val) { | |
this.tail = this.tail.next = new Op(fn, len, val); | |
this.len += len; | |
return this; | |
}; | |
function writeByte(val, buf, pos) { | |
buf[pos] = val & 255; | |
} | |
function writeVarint32(val, buf, pos) { | |
while (val > 127) { | |
buf[pos++] = val & 127 | 128; | |
val >>>= 7; | |
} | |
buf[pos] = val; | |
} | |
/** | |
* Constructs a new varint writer operation instance. | |
* @classdesc Scheduled varint writer operation. | |
* @extends Op | |
* @constructor | |
* @param {number} len Value byte length | |
* @param {number} val Value to write | |
* @ignore | |
*/ | |
function VarintOp(len, val) { | |
this.len = len; | |
this.next = undefined; | |
this.val = val; | |
} | |
VarintOp.prototype = Object.create(Op.prototype); | |
VarintOp.prototype.fn = writeVarint32; | |
/** | |
* Writes an unsigned 32 bit value as a varint. | |
* @param {number} value Value to write | |
* @returns {Writer} `this` | |
*/ | |
Writer.prototype.uint32 = function write_uint32(value) { | |
// here, the call to this.push has been inlined and a varint specific Op subclass is used. | |
// uint32 is by far the most frequently used operation and benefits significantly from this. | |
this.len += (this.tail = this.tail.next = new VarintOp( | |
(value = value >>> 0) | |
< 128 ? 1 | |
: value < 16384 ? 2 | |
: value < 2097152 ? 3 | |
: value < 268435456 ? 4 | |
: 5, | |
value)).len; | |
return this; | |
}; | |
/** | |
* Writes a signed 32 bit value as a varint. | |
* @function | |
* @param {number} value Value to write | |
* @returns {Writer} `this` | |
*/ | |
Writer.prototype.int32 = function write_int32(value) { | |
return value < 0 | |
? this._push(writeVarint64, 10, LongBits.fromNumber(value)) // 10 bytes per spec | |
: this.uint32(value); | |
}; | |
/** | |
* Writes a 32 bit value as a varint, zig-zag encoded. | |
* @param {number} value Value to write | |
* @returns {Writer} `this` | |
*/ | |
Writer.prototype.sint32 = function write_sint32(value) { | |
return this.uint32((value << 1 ^ value >> 31) >>> 0); | |
}; | |
function writeVarint64(val, buf, pos) { | |
while (val.hi) { | |
buf[pos++] = val.lo & 127 | 128; | |
val.lo = (val.lo >>> 7 | val.hi << 25) >>> 0; | |
val.hi >>>= 7; | |
} | |
while (val.lo > 127) { | |
buf[pos++] = val.lo & 127 | 128; | |
val.lo = val.lo >>> 7; | |
} | |
buf[pos++] = val.lo; | |
} | |
/** | |
* Writes an unsigned 64 bit value as a varint. | |
* @param {Long|number|string} value Value to write | |
* @returns {Writer} `this` | |
* @throws {TypeError} If `value` is a string and no long library is present. | |
*/ | |
Writer.prototype.uint64 = function write_uint64(value) { | |
var bits = LongBits.from(value); | |
return this._push(writeVarint64, bits.length(), bits); | |
}; | |
/** | |
* Writes a signed 64 bit value as a varint. | |
* @function | |
* @param {Long|number|string} value Value to write | |
* @returns {Writer} `this` | |
* @throws {TypeError} If `value` is a string and no long library is present. | |
*/ | |
Writer.prototype.int64 = Writer.prototype.uint64; | |
/** | |
* Writes a signed 64 bit value as a varint, zig-zag encoded. | |
* @param {Long|number|string} value Value to write | |
* @returns {Writer} `this` | |
* @throws {TypeError} If `value` is a string and no long library is present. | |
*/ | |
Writer.prototype.sint64 = function write_sint64(value) { | |
var bits = LongBits.from(value).zzEncode(); | |
return this._push(writeVarint64, bits.length(), bits); | |
}; | |
/** | |
* Writes a boolish value as a varint. | |
* @param {boolean} value Value to write | |
* @returns {Writer} `this` | |
*/ | |
Writer.prototype.bool = function write_bool(value) { | |
return this._push(writeByte, 1, value ? 1 : 0); | |
}; | |
function writeFixed32(val, buf, pos) { | |
buf[pos ] = val & 255; | |
buf[pos + 1] = val >>> 8 & 255; | |
buf[pos + 2] = val >>> 16 & 255; | |
buf[pos + 3] = val >>> 24; | |
} | |
/** | |
* Writes an unsigned 32 bit value as fixed 32 bits. | |
* @param {number} value Value to write | |
* @returns {Writer} `this` | |
*/ | |
Writer.prototype.fixed32 = function write_fixed32(value) { | |
return this._push(writeFixed32, 4, value >>> 0); | |
}; | |
/** | |
* Writes a signed 32 bit value as fixed 32 bits. | |
* @function | |
* @param {number} value Value to write | |
* @returns {Writer} `this` | |
*/ | |
Writer.prototype.sfixed32 = Writer.prototype.fixed32; | |
/** | |
* Writes an unsigned 64 bit value as fixed 64 bits. | |
* @param {Long|number|string} value Value to write | |
* @returns {Writer} `this` | |
* @throws {TypeError} If `value` is a string and no long library is present. | |
*/ | |
Writer.prototype.fixed64 = function write_fixed64(value) { | |
var bits = LongBits.from(value); | |
return this._push(writeFixed32, 4, bits.lo)._push(writeFixed32, 4, bits.hi); | |
}; | |
/** | |
* Writes a signed 64 bit value as fixed 64 bits. | |
* @function | |
* @param {Long|number|string} value Value to write | |
* @returns {Writer} `this` | |
* @throws {TypeError} If `value` is a string and no long library is present. | |
*/ | |
Writer.prototype.sfixed64 = Writer.prototype.fixed64; | |
/** | |
* Writes a float (32 bit). | |
* @function | |
* @param {number} value Value to write | |
* @returns {Writer} `this` | |
*/ | |
Writer.prototype.float = function write_float(value) { | |
return this._push(util.float.writeFloatLE, 4, value); | |
}; | |
/** | |
* Writes a double (64 bit float). | |
* @function | |
* @param {number} value Value to write | |
* @returns {Writer} `this` | |
*/ | |
Writer.prototype.double = function write_double(value) { | |
return this._push(util.float.writeDoubleLE, 8, value); | |
}; | |
var writeBytes = util.Array.prototype.set | |
? function writeBytes_set(val, buf, pos) { | |
buf.set(val, pos); // also works for plain array values | |
} | |
/* istanbul ignore next */ | |
: function writeBytes_for(val, buf, pos) { | |
for (var i = 0; i < val.length; ++i) | |
buf[pos + i] = val[i]; | |
}; | |
/** | |
* Writes a sequence of bytes. | |
* @param {Uint8Array|string} value Buffer or base64 encoded string to write | |
* @returns {Writer} `this` | |
*/ | |
Writer.prototype.bytes = function write_bytes(value) { | |
var len = value.length >>> 0; | |
if (!len) | |
return this._push(writeByte, 1, 0); | |
if (util.isString(value)) { | |
var buf = Writer.alloc(len = base64.length(value)); | |
base64.decode(value, buf, 0); | |
value = buf; | |
} | |
return this.uint32(len)._push(writeBytes, len, value); | |
}; | |
/** | |
* Writes a string. | |
* @param {string} value Value to write | |
* @returns {Writer} `this` | |
*/ | |
Writer.prototype.string = function write_string(value) { | |
var len = utf8.length(value); | |
return len | |
? this.uint32(len)._push(utf8.write, len, value) | |
: this._push(writeByte, 1, 0); | |
}; | |
/** | |
* Forks this writer's state by pushing it to a stack. | |
* Calling {@link Writer#reset|reset} or {@link Writer#ldelim|ldelim} resets the writer to the previous state. | |
* @returns {Writer} `this` | |
*/ | |
Writer.prototype.fork = function fork() { | |
this.states = new State(this); | |
this.head = this.tail = new Op(noop, 0, 0); | |
this.len = 0; | |
return this; | |
}; | |
/** | |
* Resets this instance to the last state. | |
* @returns {Writer} `this` | |
*/ | |
Writer.prototype.reset = function reset() { | |
if (this.states) { | |
this.head = this.states.head; | |
this.tail = this.states.tail; | |
this.len = this.states.len; | |
this.states = this.states.next; | |
} else { | |
this.head = this.tail = new Op(noop, 0, 0); | |
this.len = 0; | |
} | |
return this; | |
}; | |
/** | |
* Resets to the last state and appends the fork state's current write length as a varint followed by its operations. | |
* @returns {Writer} `this` | |
*/ | |
Writer.prototype.ldelim = function ldelim() { | |
var head = this.head, | |
tail = this.tail, | |
len = this.len; | |
this.reset().uint32(len); | |
if (len) { | |
this.tail.next = head.next; // skip noop | |
this.tail = tail; | |
this.len += len; | |
} | |
return this; | |
}; | |
/** | |
* Finishes the write operation. | |
* @returns {Uint8Array} Finished buffer | |
*/ | |
Writer.prototype.finish = function finish() { | |
var head = this.head.next, // skip noop | |
buf = this.constructor.alloc(this.len), | |
pos = 0; | |
while (head) { | |
head.fn(head.val, buf, pos); | |
pos += head.len; | |
head = head.next; | |
} | |
// this.head = this.tail = null; | |
return buf; | |
}; | |
Writer._configure = function(BufferWriter_) { | |
BufferWriter = BufferWriter_; | |
Writer.create = create(); | |
BufferWriter._configure(); | |
}; | |