src/base.js
import VariableStack from './variable-stack';
import BufferList from 'bl';
import { Transform } from 'readable-stream';
import { cloneDeep, isPlainObject } from 'lodash';
const LITTLE_ENDIAN = 'LE';
const BIG_ENDIAN = 'BE';
const POW_32 = Math.pow(2, 32);
const LOOP_VAR_SYMBOL = Symbol('loop-variable');
/**
* This class lies at the very base of this library.
*
* It's responsible for reading variables, looping and tapping values and other
* more or less low-level stuff. It extends the {Transform}-stream, so you can
* pipe other streams into your corrode instance. This makes corrode an efficient
* library for even huge files. Unless necessary or explicitly wanted by the user,
* corrode flushes it's internal buffer {@link Corrode#streamBuffer} asap.
*
* The library doesn't work directly when called. If you call e.g.
* {@link Corrode#uint32be} there won't be a direct read from a given buffer.
* Instead there will be a new *job*, added to {@link Corrode#jobs}.
* This array of jobs is responsible for managing the work to be done
* once data flows. This way, it's possible to insert new jobs on the fly
* and making for an imperative way of using this library.
*
* Corrode will process the given stream as long as long as there's new chunks
* and jobs. Once either of these drain, corrode will stop and clean possibly
* remaining jobs (i.e. remove all read-jobs).
*
* In fact CorrodeBase can be used as a standalone-library, if low-level is your thing.
*/
export default class CorrodeBase extends Transform {
/**
* Defaults Object for available options
* {@link CorrodeBase#constructor}
* @access public
* @type {Object}
*/
static defaults = {
endianness: LITTLE_ENDIAN,
loopIdentifier: LOOP_VAR_SYMBOL,
encoding: 'utf8',
finishJobsOnEOF: true,
anonymousLoopDiscardDeep: false,
strictObjectMode: true
};
/**
* static constant for little endian
* @access public
* @type {string}
*/
static LITTLE_ENDIAN = LITTLE_ENDIAN;
/**
* static constant for big endian
* @access public
* @type {string}
*/
static BIG_ENDIAN = BIG_ENDIAN;
/**
* array holding the jobs which are to be processed
* @access public
* @type {Array<Object>}
*/
jobs = [];
/**
* the VariableStack holding the layers and variables of this instance
* @access public
* @type {VariableStack}
*/
varStack = new VariableStack();
/**
* internal buffer for stream-chunks not yet processable
* @access public
* @type {BufferList}
*/
streamBuffer = new BufferList();
/**
* offset in the whole piped stream
* @access public
* @type {Number}
*/
streamOffset = 0;
/**
* offset in the current running chunk ({@link CorrodeBase#streamBuffer})
* @access public
* @type {Number}
*/
chunkOffset = 0;
/**
* indicates whether only pops are getting processed
* @access public
* @type {Boolean}
*/
isUnwinding = false;
/**
* indicates whether automatic flushes are disabled
* @access public
* @type {Boolean}
*/
isSeeking = false;
/**
* available primitve jobs. these are the functions really reading data from
* the buffer.
* @access public
* @type {Object}
*/
primitveMap = {
int8: () => this.streamBuffer.readInt8(this.chunkOffset),
uint8: () => this.streamBuffer.readUInt8(this.chunkOffset),
int16: job => this.streamBuffer[`readInt16${job.endianness}`](this.chunkOffset),
uint16: job => this.streamBuffer[`readUInt16${job.endianness}`](this.chunkOffset),
int32: job => this.streamBuffer[`readInt32${job.endianness}`](this.chunkOffset),
uint32: job => this.streamBuffer[`readUInt32${job.endianness}`](this.chunkOffset),
float: job => this.streamBuffer[`readFloat${job.endianness}`](this.chunkOffset),
double: job => this.streamBuffer[`readDouble${job.endianness}`](this.chunkOffset),
int64: job => {
const lo = this.streamBuffer[`readUInt32${job.endianness}`](this.chunkOffset + (job.endianness === LITTLE_ENDIAN ? 0 : 4));
const hi = this.streamBuffer[`read${job.type === 'uint64' ? 'U' : ''}Int32${job.endianness}`](this.chunkOffset + (job.endianness === LITTLE_ENDIAN ? 4 : 0));
return (POW_32 * hi) + lo;
},
uint64: job => this.primitveMap.int64(job)
};
/**
* get variables of the current stack
* @return {Object|*} topmost value
*/
get vars(){
return this.varStack.value;
}
/**
* replace the current value
* @param {Object|*} val new value
*/
set vars(val){
this.varStack.value = val;
}
/**
* options default to {@link Corrode.defaults}, also accepts options
* for {@link Transform#constructor}.
* @param {string} options.endianness=CorrodeBase#LITTLE_ENDIAN endianness, when none is explicitly given by the job
* @param {*} options.loopIdentifier=Symbol(loop-variable) identifier of the temporary variable used internally for loops
* @param {string} options.encoding='utf8' default encoding, when none is explicitly given by the job
* @param {boolean} options.finishJobsOnEOF=true whether to finish all remaining jobs on stream-end or leave the corrode-instance in an unfinished state with possibly many unresolved functions and unpredictable state (see tests)
* @param {boolean} options.anonymousLoopDiscardDeep=false when anonymous loop-jobs get discarded, the original state which gets restored is either a shallow copy or a deep copy of the original object (see tests)
* @param {boolean} options.strictObjectMode=true whether to prevent none-object values from being pushed onto {@link CorrodeBase#vars} (catches mistakes)
*/
constructor(options){
super({ ...options, objectMode: true, encoding: null });
/** @type {Object} options default-options overwritten by user options */
this.options = {
...CorrodeBase.defaults,
...options
};
}
/**
* add a new chunk to the internal buffer and process remaining jobs
* this TransformStream won't call the push-method as it is not able
* to guarantee correct ouptut while still reading data. this depends not on
* corrode, but on the way you use it.
* @implements {Transform#_transform}
* @access private
*/
_transform(chunk, encoding, done){
this.streamBuffer.append(chunk);
this.jobLoop();
// isSeeking prevents internal flushes
// (useful for going back to a previous chunk)
if(!this.isSeeking){
this.streamBuffer.consume(this.chunkOffset);
/**
* offset in the current running chunk ({@link CorrodeBase#streamBuffer})
* @access public
* @type {Number}
*/
this.chunkOffset = 0;
}
// the callback is called synchronously, meaning any errors
// working up in the jobLoop (asserts, etc) get thrown in the main
// event-loop. that's a bug:
// @link https://github.com/screeny05/corrode/issues/24
return done();
}
/**
* finish remaining jobs if finishJobsOnEOF is enabled
* @implements {Transform#_flush}
* @access private
*/
_flush(done){
if(this.options.finishJobsOnEOF){
this.finishRemainingJobs();
}
setImmediate(done);
}
/**
* internal function to process the array of jobs still remaing.
* this function is synchronous. this is due to the fact of keeping things
* more simple. if you intend to use this library expecting taps and loops
* to work async you're invited to create an issue to inform me of the
* need for this kind of behaviour. Until then corrode should satisfy most use cases.
*
* if you desperately need async behaviour when parsing data, i'd recommend
* doing that after corrode has finished parsing the buffer, separately.
*
* @throws {Error} assertion-errors, runtime-errors
*/
jobLoop(){
// {@link CorrodeBase#jobs} get's manipulated by {@link CorrodeBase#jobLoop}
while(this.jobs.length > 0){
const job = this.jobs[0];
const remainingBuffer = this.streamBuffer.length - this.chunkOffset;
if(job.type === 'push'){
// in strictObjectMode the variable being pushed has to be a real object.
// this prevents accidentaly pushing numbers, strings, etc.
if(this.options.strictObjectMode && typeof this.vars[job.name] !== 'undefined' && !isPlainObject(this.vars[job.name])){
throw new TypeError(`Can't push into a non-object value (${job.name}) in strictObjectMode`);
}
this.jobs.shift();
this.varStack.push(job.name, job.value);
continue;
} else if(job.type === 'pop'){
this.jobs.shift();
this.varStack.pop();
continue;
} else if(job.type === 'tap'){
this.jobs.shift();
const unqueue = this.queueJobs();
if(typeof job.name !== 'undefined'){
// if the tap has a name, push a new var-layer
this
.push(job.name)
.tap(job.callback, job.args)
.pop();
} else {
// otherwise we continue working on the current layer
job.callback.apply(this, job.args);
}
unqueue();
continue;
} else if(job.type === 'loop'){
// wait for more data before executing a loop on an empty buffer.
// this way empty objects are not being added when the stream finishes
if(remainingBuffer === 0){
break;
}
if(job.finished){
this.jobs.shift();
continue;
}
const loopIdentifier = this.options.loopIdentifier;
const unqueue = this.queueJobs();
if(typeof job.name !== 'undefined'){
if(typeof this.vars[job.name] === 'undefined'){
this.vars[job.name] = [];
}
this
.tap(loopIdentifier, job.callback, [job.finish, job.discard, job.iteration++])
.tap(function(){
const loopResult = this.vars[loopIdentifier];
// push vars only if job isn't discarded and yielded vars
// (no empty objects this way)
if(!job.discarded && (!isPlainObject(loopResult) || Object.keys(loopResult).length > 0)){
this.vars[job.name].push(loopResult);
}
job.discarded = false;
delete this.vars[loopIdentifier];
});
} else {
// make copy, in case the user discards the result
// {@link CorrodeBase#options.anonymousLoopDiscardDeep}
if(this.options.anonymousLoopDiscardDeep){
job[loopIdentifier] = cloneDeep(this.vars);
} else {
job[loopIdentifier] = { ...this.vars };
}
this
.tap(job.callback, [job.finish, job.discard, job.iteration++])
.tap(function(){
if(job.discarded && typeof job[loopIdentifier] !== 'undefined'){
this.vars = job[loopIdentifier];
}
job.discarded = false;
delete job[loopIdentifier];
});
}
unqueue();
continue;
}
// determine length of next job
const length = typeof job.length === 'string' ? this.vars[job.length] : job.length;
// only valid numbers can be used as length
if(typeof length !== 'number'){
throw new TypeError(`Cannot find a valid length for job ${job.name}, dereferenced length is ${JSON.stringify(length)}`);
}
// break on unsufficient streamBuffer-length (wait if not unwinding yet)
if(this.streamBuffer.length - this.chunkOffset < length){
if(this.isUnwinding && this.jobs.length > 0){
// unwind loop, by removing the loop job
this.removeReadJobs();
continue;
}
break;
}
if(job.type === 'buffer'){
this.jobs.shift();
this.vars[job.name] = this.streamBuffer.slice(this.chunkOffset, this.chunkOffset + length);
this._moveOffset(length);
continue;
} else if(job.type === 'string'){
this.jobs.shift();
this.vars[job.name] = this.streamBuffer.toString(job.encoding, this.chunkOffset, this.chunkOffset + length);
this._moveOffset(length);
continue;
} else if(job.type === 'skip'){
this.jobs.shift();
if(this.streamOffset + length < 0){
throw new RangeError('cannot skip below 0');
}
this._moveOffset(length);
continue;
} else if(typeof this.primitveMap[job.type] === 'function'){
this.vars[job.name] = this.primitveMap[job.type](job);
this.jobs.shift();
this._moveOffset(length);
} else {
throw new Error(`invalid job type '${job.type}'`);
}
}
}
/**
* re-starts the jobLoop with the job-list cleaned from any read jobs
* {@link CorrodeBase#removeReadJobs}
* {@link CorrodeBase#isUnwinding}
*/
finishRemainingJobs(){
/**
* indicates whether only pops are getting processed
* @access public
* @type {Boolean}
*/
this.isUnwinding = true;
this.removeReadJobs();
this.jobLoop();
this.varStack.popAll();
}
/**
* purges all jobs from the job-queue, which need to read from the stream
*/
removeReadJobs(){
const filteredJobs = this.jobs
.slice()
.filter(job => job.type === 'pop' || (job.type === 'tap' && !job.name));
this.jobs.splice(0);
this.jobs.push(...filteredJobs);
}
/**
* utility method to move both {@link CorrodeBase#chunkOffset} and
* {@link CorrodeBase#streamOffset} by a given amount of bytes
* @access private
*/
_moveOffset(by){
this.chunkOffset += by;
/**
* offset in the whole piped stream
* @access public
* @type {Number}
*/
this.streamOffset += by;
}
/**
* remove all jobs from {@link CorrodeBase#jobs} with the ability to re-add them back later.
* @returns {function} function to append all queued jobs back onto the jobs-array (borrowed from redux)
* @example
* + [
* | { type: 'push', name: 'struct', fn },
* | { type: 'uint8', name: 'var' ' }
* | ]
* |
* | jobLoop - iteration
* |
* | var unqueueJobs = queJobs();
* |
* | []
* |
* | fn();
* |
* | [
* | { type: 'uint8', name: 'strvar' }
* | ]
* |
* | unqueueJobs();
* |
* | [
* | { type: 'uint8', name: 'strvar' },
* | { type: 'uint8', name: 'var' }
* v ]
*/
queueJobs(){
const queuedJobs = this.jobs;
// empty jobs
this.jobs = [];
// unqueue-method
return () => this.jobs = this.jobs.concat(queuedJobs);
}
/**
* utility method to push a new job onto the job-array
* @access private
* @return {CorrodeBase} this
*/
_pushJob(name, type, length, endianness){
this.jobs.push(typeof name === 'object' ? name : {
name,
type,
length,
endianness
});
return this;
}
/**
* push a tap-job onto the job-array
* a tap-job is a special kind of job allowing the developer to peek into
* the current variables and creating more complex structures and behaviour
* based on available information.
* @param {string} [name] name of the new variable-layer. if none is provided, the current layer will be used
* @param {function(...args: *)} callback called when this job is reached
* @param {Array} args array of possible arguments being passed to the callback
* @return {CorrodeBase} this
*/
tap(name, callback, args){
if(typeof name === 'function'){
args = callback;
callback = name;
name = undefined;
}
return this._pushJob({
name,
type: 'tap',
args,
callback
});
}
/**
* push a loop-job onto the job-array
* a loop-job is a special kind of job allowing the developer to create loops
* in the current job-array. this allows for iteration, seeking and more
* complex behaviours
* @param {string} [name] name of the new variable-layer. if none is provided, the current layer will be used
* @param {function(end: function, discard: function, i: number)} callback called until `end()` is called.
* `end(true)` can be used to end and discard the current loop.
* `discard()` can be used to reset the current layer ({@link CorrodeBase#options.anonymousLoopDiscardDeep}).
* `i` is the current iteration count
* @return {CorrodeBase} this
*/
loop(name, callback){
if(typeof name === 'function'){
callback = name;
name = undefined;
}
const loopJob = {
name,
type: 'loop',
callback,
finished: false,
discarded: false,
iteration: 0
};
loopJob.finish = function(discard){
loopJob.finished = true;
loopJob.discarded = Boolean(discard);
};
loopJob.discard = function(){
loopJob.discarded = true;
};
return this._pushJob(loopJob);
}
/**
* push a skip-job onto the job-array
* skip-jobs allow the developer to skip a given number of bytes.
* If the amount of bytes exceeds the current available byte-count in the
* internal buffer {@link CorrodeBase#streamBuffer} the job will wait for enough
* data. If this data won't come the job gets aborted and corrode ends.
* If you want to skip a negative amount of bytes you have to disable auto-flushing.
* this can be done by setting {@link CorrodeBase#isSeeking} to `true`.
* @param {number|string} length how many bytes to skip. Given a string, corrode will try to find a variable with the given string in the current layer
* @return {CorrodeBase} this
*/
skip(length){
return this._pushJob({
type: 'skip',
length
});
}
/**
* push a pop-job onto the job-array
* the pop-job pops a layer from {@link CorrodeBase#varStack}.
* this most probably doesn't have to get called manually, as
* {@link CorrodeBase#tap} and {@link CorrodeBase#loop} will do this automatically
* @return {CorrodeBase} this
*/
pop(){
return this._pushJob({
type: 'pop'
});
}
/**
* push a push-job onto the job-array
* the push-job pushes a new layer onto {@link CorrodeBase#varStack}.
* this most probably doesn't have to get called manually, as
* {@link CorrodeBase#tap} and {@link CorrodeBase#loop} will do this automatically
* @param {string} name name of the new layer
* @param {*} [value={}] value of the new layer (default is from {@link VariableStack})
* @return {CorrodeBase} this
*/
push(name, value){
return this._pushJob({
type: 'push',
name,
value
});
}
/**
* pushes a buffer-job onto the job-array
* the buffer-job will read a buffer with the given length starting at the
* current offset {@link CorrodeBase#bufferOffset}.
* The returned Buffer will be a slice (not a copy) of the underlying {@link CorrodeBase#streamBuffer}
* @param {string} name name of the buffer-variable
* @param {number|string} length length of the buffer in bytes. Given a string, corrode will try to find a variable with the given string in the current layer
* @return {CorrodeBase} this
*/
buffer(name, length){
return this._pushJob({
name,
type: 'buffer',
length
});
}
/**
* pushes a string-job onto the job-array
* the string-job will read a string with the given length starting at the
* current offset {@link CorrodeBase#bufferOffset}
* @param {string} name name of the string-variable
* @param {number|string} length length of the string in bytes (_not characters_). Given a string, corrode will try to find a variable with the given string in the current layer
* @param {string} [encoding=CorrodeBase#options.encoding] encoding encoding used to decode the string, defaults to 'utf8'.
* available encodings can be found here https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings
* @return {CorrodeBase} this
*/
string(name, length, encoding = this.options.encoding){
return this._pushJob({
name,
type: 'string',
length,
encoding
});
}
/**
* push a int8 (signed) job onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
int8(name){ return this._pushJob(name, 'int8', 1, this.options.endianness); }
/**
* push a int8 (signed) job onto the job-array
* @deprecated int8 needs no endianness, use int8() instead
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
int8le(name){ return this._pushJob(name, 'int8', 1, LITTLE_ENDIAN); }
/**
* push a int8 (signed) job onto the job-array
* @deprecated int8 needs no endianness, use int8() instead
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
int8be(name){ return this._pushJob(name, 'int8', 1, BIG_ENDIAN); }
/**
* push a uint8 (unsigned) job onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
uint8(name){ return this._pushJob(name, 'uint8', 1, this.options.endianness); }
/**
* push a uint8 (unsigned) job onto the job-array
* @deprecated uint8 needs no endianness, use uint8() instead
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
uint8le(name){ return this._pushJob(name, 'uint8', 1, LITTLE_ENDIAN); }
/**
* push a uint8 (unsigned) job onto the job-array
* @deprecated uint8 needs no endianness, use uint8() instead
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
uint8be(name){ return this._pushJob(name, 'uint8', 1, BIG_ENDIAN); }
/**
* push a int16 (signed) job with default endianness ({@link CorrodeBase#options.endianness}) onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
int16(name){ return this._pushJob(name, 'int16', 2, this.options.endianness); }
/**
* push a int16 (signed) job with little endianness onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
int16le(name){ return this._pushJob(name, 'int16', 2, LITTLE_ENDIAN); }
/**
* push a int16 (signed) job with big endianness onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
int16be(name){ return this._pushJob(name, 'int16', 2, BIG_ENDIAN); }
/**
* push a uint16 (unsigned) job with default endianness ({@link CorrodeBase#options.endianness}) onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
uint16(name){ return this._pushJob(name, 'uint16', 2, this.options.endianness); }
/**
* push a uint16 (unsigned) job with little endianness onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
uint16le(name){ return this._pushJob(name, 'uint16', 2, LITTLE_ENDIAN); }
/**
* push a uint16 (unsigned) job with big endianness onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
uint16be(name){ return this._pushJob(name, 'uint16', 2, BIG_ENDIAN); }
/**
* push a int32 (signed) job with default endianness ({@link CorrodeBase#options.endianness}) onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
int32(name){ return this._pushJob(name, 'int32', 4, this.options.endianness); }
/**
* push a int32 (signed) job with little endianness onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
int32le(name){ return this._pushJob(name, 'int32', 4, LITTLE_ENDIAN); }
/**
* push a int32 (signed) job with big endianness onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
int32be(name){ return this._pushJob(name, 'int32', 4, BIG_ENDIAN); }
/**
* push a uint32 (unsigned) job with default endianness ({@link CorrodeBase#options.endianness}) onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
uint32(name){ return this._pushJob(name, 'uint32', 4, this.options.endianness); }
/**
* push a uint32 (unsigned) job with little endianness onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
uint32le(name){ return this._pushJob(name, 'uint32', 4, LITTLE_ENDIAN); }
/**
* push a uint32 (unsigned) job with big endianness onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
uint32be(name){ return this._pushJob(name, 'uint32', 4, BIG_ENDIAN); }
/**
* push a int64 (signed) job with default endianness ({@link CorrodeBase#options.endianness}) onto the job-array
* note that in64 may be unprecise, due to number-values being double in js
* @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
int64(name){ return this._pushJob(name, 'int64', 8, this.options.endianness); }
/**
* push a int64 (signed) job with little endianness onto the job-array
* note that in64 may be unprecise, due to number-values being double in js
* @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
int64le(name){ return this._pushJob(name, 'int64', 8, LITTLE_ENDIAN); }
/**
* push a int64 (signed) job with big endianness onto the job-array
* note that in64 may be unprecise, due to number-values being double in js
* @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
int64be(name){ return this._pushJob(name, 'int64', 8, BIG_ENDIAN); }
/**
* push a uint64 (unsigned) job with default endianness ({@link CorrodeBase#options.endianness}) onto the job-array
* note that in64 may be unprecise, due to number-values being double in js
* @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
uint64(name){ return this._pushJob(name, 'uint64', 8, this.options.endianness); }
/**
* push a uint64 (unsigned) job with little endianness onto the job-array
* note that in64 may be unprecise, due to number-values being double in js
* @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
uint64le(name){ return this._pushJob(name, 'uint64', 8, LITTLE_ENDIAN); }
/**
* push a uint64 (unsigned) job with big endianness onto the job-array
* note that in64 may be unprecise, due to number-values being double in js
* @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
uint64be(name){ return this._pushJob(name, 'uint64', 8, BIG_ENDIAN); }
/**
* push a float job with default endianness ({@link CorrodeBase#options.endianness}) onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
float(name){ return this._pushJob(name, 'float', 4, this.options.endianness); }
/**
* push a float job with little endianness onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
floatle(name){ return this._pushJob(name, 'float', 4, LITTLE_ENDIAN); }
/**
* push a float job with big endianness onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
floatbe(name){ return this._pushJob(name, 'float', 4, BIG_ENDIAN); }
/**
* push a double job with default endianness ({@link CorrodeBase#options.endianness}) onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
double(name){return this._pushJob(name, 'double', 8, this.options.endianness); }
/**
* push a double job with little endianness onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
doublele(name){return this._pushJob(name, 'double', 8, LITTLE_ENDIAN); }
/**
* push a double job with big endianness onto the job-array
* @param {string} name name of the variable to be created
* @return {CorrodeBase} this
*/
doublebe(name){return this._pushJob(name, 'double', 8, BIG_ENDIAN); }
}