vapid
Version:
Vapid, a vacuous Redis implementation for connection tests, with a fully functional PubSub system for multiple clients.
197 lines (191 loc) • 6.59 kB
JavaScript
/*
* VapidParser, based on Boris.
*
* Copyright(c) 2014 Guglielmo Ferri <44gatti@gmail.com>
* MIT Licensed
*/
exports.VapidParser = ( function () {
var bconcat = Buffer.concat
, util = require( 'util' )
, emitter = require( 'events' ).EventEmitter
, Bolgia = require( 'bolgia' )
, Peela = require( 'peela' )
// require parser rules
, Rules = require( './rules/' ).Rules
// build lookup table for rules chars
, ltable = Rules.lookupTable( '*$' )
/*
* An utility fn to convert a nested array
* with Buffer results to strings and numbers.
*/
, reveal = Bolgia.reveal
, clone = Bolgia.clone
, improve = Bolgia.improve
// emit Error event
, emitError = function ( ccode ) {
var me = this
, format = util.format
, fcc = String.fromCharCode
, msg = 'VapidParser parser error, current char: %s (code: %d), pos: %d.'
, emsg = format( msg, fcc( ccode ), ccode, me.cpos )
;
me.emit( 'error', emsg, me.cdata.slice( 0, me.cpos + 2 ) );
}
// get results
, collectResult = function ( rule, error, data ) {
var me = this
, arr = null
, peela = me.peela
, head = peela.head()
, slen = peela.size()
, IaN = typeof data === 'number'
, return_buffers = me.options.return_buffers
/** /
, debug = function ( op ) {
var msg = '\n- %s stack head (size %d):'
, stack = peela.stack
, inspect = util.inspect( stack, false, Infinity, true )
;
log( msg, op, peela.size(), inspect );
}
/**/
;
if ( rule.isMultiBulk && IaN ) {
// some elements are expected
arr = [];
if ( slen ) {
head.list.push( arr );
--head.left;
}
peela.push( {
list : arr
, left : data
} );
// debug( 'new' );
return;
}
// check data, "*0\r\n" returns [], "*-1\r\n" returns [ null ]
if ( slen === 0 ) return me.emit( 'match', error, data && data.length ? return_buffers ? [ data ] : reveal( [ data ] ) : [], reveal );
// add result to the head of stack
head.list.push( data );
// debug( 'update' );
--head.left;
// pop current stack head if done.
while ( slen ) {
head = peela.head();
if ( head.left ) break;
peela.pop();
// debug( 'pop' );
if ( --slen === 0 ) {
me.emit( 'match', false, return_buffers ? head.list : reveal( head.list ), reveal );
break;
}
}
}
, scanData = function ( me ) {
var rpos = 0
, i = null
, result = me.crule.parse( me.cdata, me.cpos )
;
if ( result ) {
me.mchk = false;
collectResult.apply( me, result );
// get result index, then update position
i = result[ 3 ];
rpos = ( i < me.cdata.length ) ? i : -1;
if ( ~ rpos ) {
me.cpos = rpos;
// me.emit( 'miss', me.crule, me.cpos );
return true;
}
// buffer ends
me.cdata = null;
me.cpos = 0;
// me.emit( 'end' );
return;
}
me.mchk = true;
// me.emit( 'miss', me.crule, me.cpos );
return;
}
, vapid_parser_opt = {
return_buffers : true
}
// VapidParser Parser
, VapidParser = function ( opt ) {
var me = this
, is = me instanceof VapidParser
;
if ( ! is ) return new VapidParser( opt );
me.options = improve( clone( opt ), vapid_parser_opt );
me.cdata = null;
me.cpos = 0;
me.crule = null;
me.mchk = false;
/*
* init stack to collect multibulk
* and nested multibulk replies
*/
me.peela = Peela();
/*
* init lookup table for parser rules,
* using the result of charCodeAt( 0 ).
*/
me.rules = {};
me.rules[ 36 ] = Rules.bulk( '$' );
me.rules[ 42 ] = Rules.mbulk( '*' );
}
, bproto = null
;
util.inherits( VapidParser, emitter );
bproto = VapidParser.prototype;
bproto.reset = function () {
var me = this
, rules = me.rules
;
// reset on nextTick
process.nextTick( function () {
var r = null
;
me.crule = me.cdata = null;
me.mchk = false;
me.cpos = 0;
me.peela.flush();
for ( r in rules ) rules[ r ].reset();
} );
};
bproto.parse = function ( data ) {
var me = this
, ccode = null
, val = null
;
while ( true ) {
if ( me.mchk ) {
/*
* multi chunk reply, a parser rule needs further data,
* join current data buffer with the received one.
*/
me.cdata = bconcat( [ me.cdata, data ], me.cdata.length + data.length );
if ( scanData( me ) ) continue;
break;
}
/*
* set current data, get current char code, then
* check data if there is a rule that matches the
* first char.
*/
me.cdata = me.cdata || data;
ccode = me.cdata[ me.cpos ];
val = ltable[ ccode ];
if ( val ) {
me.crule = me.rules[ val ];
if ( scanData( me ) ) continue;
break;
}
emitError.call( me, ccode );
me.reset();
break;
}
};
return VapidParser;
} )();