1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-09 00:22:08 +03:00
Files
node-redis/lib/parser/javascript.js
Matt Ranney 36c40ee03d JavaScript parser passes all tests when returning strings.
JS is still way too slow for large mb replies.  Hiredis is fast for strings of large  replies,
but slow for buffers.
2010-12-07 00:23:31 -08:00

286 lines
10 KiB
JavaScript

/*global Buffer require exports console setTimeout */
var events = require("events"),
util = require("../util").util;
exports.debug_mode = false;
exports.name = "javascript";
function RedisReplyParser(options) {
this.name = exports.name;
this.options = options || {};
this.reset();
events.EventEmitter.call(this);
}
util.inherits(RedisReplyParser, events.EventEmitter);
exports.Parser = RedisReplyParser;
// Buffer.toString() is quite slow for small strings
function small_toString(buf, len) {
var tmp = "", i;
for (i = 0; i < len; i += 1) {
tmp += String.fromCharCode(buf[i]);
}
return tmp;
}
// Reset parser to it's original state.
RedisReplyParser.prototype.reset = function () {
this.state = "type";
this.return_buffer = new Buffer(16384); // for holding replies, might grow
this.return_string = "";
this.tmp_string = ""; // for holding size fields
this.multi_bulk_length = 0;
this.multi_bulk_replies = null;
this.multi_bulk_nested_length = 0;
this.multi_bulk_nested_replies = null;
};
RedisReplyParser.prototype.parser_error = function (message) {
this.emit("error", message);
this.reset();
};
RedisReplyParser.prototype.execute = function (incoming_buf) {
var pos = 0, bd_tmp, bd_str, i, il;
//, state_times = {}, start_execute = new Date(), start_switch, end_switch, old_state;
//start_switch = new Date();
while (pos < incoming_buf.length) {
// old_state = this.state;
// console.log("execute: " + this.state + ", " + pos + "/" + incoming_buf.length + ", " + String.fromCharCode(incoming_buf[pos]));
switch (this.state) {
case "type":
this.type = incoming_buf[pos];
pos += 1;
switch (this.type) {
case 43: // +
this.state = "single line";
this.return_buffer.end = 0;
this.return_string = "";
break;
case 42: // *
this.state = "multi bulk count";
this.tmp_string = "";
break;
case 58: // :
this.state = "integer line";
this.return_buffer.end = 0;
this.return_string = "";
break;
case 36: // $
this.state = "bulk length";
this.tmp_string = "";
break;
case 45: // -
this.state = "error line";
this.return_buffer.end = 0;
this.return_string = "";
break;
default:
this.state = "unknown type";
}
break;
case "integer line":
if (incoming_buf[pos] === 13) {
this.send_reply(+small_toString(this.return_buffer, this.return_buffer.end));
this.state = "final lf";
} else {
this.return_buffer[this.return_buffer.end] = incoming_buf[pos];
this.return_buffer.end += 1;
}
pos += 1;
break;
case "error line":
if (incoming_buf[pos] === 13) {
this.send_error(this.return_buffer.toString("ascii", 0, this.return_buffer.end));
this.state = "final lf";
} else {
this.return_buffer[this.return_buffer.end] = incoming_buf[pos];
this.return_buffer.end += 1;
}
pos += 1;
break;
case "single line":
if (incoming_buf[pos] === 13) {
this.send_reply(this.return_string);
this.state = "final lf";
} else {
this.return_string += String.fromCharCode(incoming_buf[pos]);
}
pos += 1;
break;
case "multi bulk count":
if (incoming_buf[pos] === 13) { // \r
this.state = "multi bulk count lf";
} else {
this.tmp_string += String.fromCharCode(incoming_buf[pos]);
}
pos += 1;
break;
case "multi bulk count lf":
if (incoming_buf[pos] === 10) { // \n
if (this.multi_bulk_length) { // nested multi-bulk
this.multi_bulk_nested_length = this.multi_bulk_length;
this.multi_bulk_nested_replies = this.multi_bulk_replies;
}
this.multi_bulk_length = +this.tmp_string;
this.multi_bulk_replies = [];
this.state = "type";
if (this.multi_bulk_length < 0) {
this.send_reply(null);
this.multi_bulk_length = 0;
} else if (this.multi_bulk_length === 0) {
this.multi_bulk_replies = null;
this.send_reply([]);
}
} else {
this.parser_error(new Error("didn't see LF after NL reading multi bulk count"));
return;
}
pos += 1;
break;
case "bulk length":
if (incoming_buf[pos] === 13) { // \r
this.state = "bulk lf";
} else {
this.tmp_string += String.fromCharCode(incoming_buf[pos]);
}
pos += 1;
break;
case "bulk lf":
if (incoming_buf[pos] === 10) { // \n
this.bulk_length = +this.tmp_string;
if (this.bulk_length === -1) {
this.send_reply(null);
this.state = "type";
} else if (this.bulk_length === 0) {
this.send_reply(new Buffer(""));
this.state = "final cr";
} else {
this.state = "bulk data";
if (this.bulk_length > this.return_buffer.length) {
if (exports.debug_mode) {
console.log("Growing return_buffer from " + this.return_buffer.length + " to " + this.bulk_length);
}
this.return_buffer = new Buffer(this.bulk_length);
}
this.return_buffer.end = 0;
}
} else {
this.parser_error(new Error("didn't see LF after NL while reading bulk length"));
return;
}
pos += 1;
break;
case "bulk data":
this.return_buffer[this.return_buffer.end] = incoming_buf[pos];
this.return_buffer.end += 1;
pos += 1;
if (this.return_buffer.end === this.bulk_length) {
bd_tmp = new Buffer(this.bulk_length);
// When the response is small, Buffer.copy() is a lot slower.
if (this.bulk_length > 10) {
this.return_buffer.copy(bd_tmp, 0, 0, this.bulk_length);
} else {
for (i = 0, il = this.bulk_length; i < il; i += 1) {
bd_tmp[i] = this.return_buffer[i];
}
}
this.send_reply(bd_tmp);
this.state = "final cr";
}
break;
case "final cr":
if (incoming_buf[pos] === 13) { // \r
this.state = "final lf";
pos += 1;
} else {
this.parser_error(new Error("saw " + incoming_buf[pos] + " when expecting final CR"));
return;
}
break;
case "final lf":
if (incoming_buf[pos] === 10) { // \n
this.state = "type";
pos += 1;
} else {
this.parser_error(new Error("saw " + incoming_buf[pos] + " when expecting final LF"));
return;
}
break;
default:
this.parser_error(new Error("invalid state " + this.state));
}
// end_switch = new Date();
// if (state_times[old_state] === undefined) {
// state_times[old_state] = 0;
// }
// state_times[old_state] += (end_switch - start_switch);
// start_switch = end_switch;
}
// console.log("execute ran for " + (Date.now() - start_execute) + " ms, on " + incoming_buf.length + " Bytes. ");
// Object.keys(state_times).forEach(function (state) {
// console.log(" " + state + ": " + state_times[state]);
// });
};
RedisReplyParser.prototype.send_error = function (reply) {
if (this.multi_bulk_length > 0 || this.multi_bulk_nested_length > 0) {
// TODO - can this happen? Seems like maybe not.
this.add_multi_bulk_reply(reply);
} else {
this.emit("reply error", reply);
}
};
RedisReplyParser.prototype.send_reply = function (reply) {
if (this.multi_bulk_length > 0 || this.multi_bulk_nested_length > 0) {
if (!this.options.return_buffers && Buffer.isBuffer(reply)) {
this.add_multi_bulk_reply(reply.toString("utf8"));
} else {
this.add_multi_bulk_reply(reply);
}
} else {
if (!this.options.return_buffers && Buffer.isBuffer(reply)) {
this.emit("reply", reply.toString("utf8"));
} else {
this.emit("reply", reply);
}
}
};
RedisReplyParser.prototype.add_multi_bulk_reply = function (reply) {
if (this.multi_bulk_replies) {
this.multi_bulk_replies.push(reply);
if (this.multi_bulk_replies.length < this.multi_bulk_length) {
return;
}
} else {
this.multi_bulk_replies = reply;
}
if (this.multi_bulk_nested_length > 0) {
this.multi_bulk_nested_replies.push(this.multi_bulk_replies);
this.multi_bulk_length = 0;
delete this.multi_bulk_replies;
if (this.multi_bulk_nested_length === this.multi_bulk_nested_replies.length) {
this.emit("reply", this.multi_bulk_nested_replies);
this.multi_bulk_nested_length = 0;
this.multi_bulk_nested_replies = null;
}
} else {
this.emit("reply", this.multi_bulk_replies);
this.multi_bulk_length = 0;
this.multi_bulk_replies = null;
}
};