Skip to content

Commit

Permalink
fix: micro-optimizations for parser
Browse files Browse the repository at this point in the history
  • Loading branch information
andris9 committed Mar 7, 2024
1 parent 0a40e19 commit 3451ad2
Showing 1 changed file with 48 additions and 37 deletions.
85 changes: 48 additions & 37 deletions lib/handler/token-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@

const imapFormalSyntax = require('./imap-formal-syntax');

const STATE_ATOM = 0x001;
const STATE_LITERAL = 0x002;
const STATE_NORMAL = 0x003;
const STATE_PARTIAL = 0x004;
const STATE_SEQUENCE = 0x005;
const STATE_STRING = 0x006;
const STATE_TEXT = 0x007;

const RE_DIGITS = /^\d+$/;
const RE_SINGLE_DIGIT = /^\d$/;

class TokenParser {
constructor(parent, startPos, str, options) {
this.str = (str || '').toString();
Expand All @@ -15,7 +26,7 @@ class TokenParser {

this.currentNode.type = 'TREE';

this.state = 'NORMAL';
this.state = STATE_NORMAL;
}

async getAttributes() {
Expand Down Expand Up @@ -132,13 +143,13 @@ class TokenParser {
chr = this.str.charAt(i);

switch (this.state) {
case 'NORMAL':
case STATE_NORMAL:
switch (chr) {
// DQUOTE starts a new string
case '"':
this.currentNode = this.createNode(this.currentNode, this.pos + i);
this.currentNode.type = 'string';
this.state = 'STRING';
this.state = STATE_STRING;
this.currentNode.isClosed = false;
break;

Expand Down Expand Up @@ -186,11 +197,11 @@ class TokenParser {
this.currentNode = this.createNode(this.currentNode, this.pos + i);
this.currentNode.type = 'ATOM';
this.currentNode.value = chr;
this.state = 'ATOM';
this.state = STATE_ATOM;
} else {
this.currentNode = this.createNode(this.currentNode, this.pos + i);
this.currentNode.type = 'PARTIAL';
this.state = 'PARTIAL';
this.state = STATE_PARTIAL;
this.currentNode.isClosed = false;
}
break;
Expand All @@ -204,7 +215,7 @@ class TokenParser {
this.currentNode = this.createNode(this.currentNode, this.pos + i);
this.currentNode.type = 'ATOM';
this.currentNode.value = chr;
this.state = 'ATOM';
this.state = STATE_ATOM;
break;
}

Expand All @@ -223,7 +234,7 @@ class TokenParser {
this.currentNode.type = 'LITERAL';
this.currentNode.literalType = this.expectedLiteralType || 'literal';
this.expectedLiteralType = false;
this.state = 'LITERAL';
this.state = STATE_LITERAL;
this.currentNode.isClosed = false;
break;

Expand All @@ -233,7 +244,7 @@ class TokenParser {
this.currentNode.type = 'SEQUENCE';
this.currentNode.value = chr;
this.currentNode.isClosed = false;
this.state = 'SEQUENCE';
this.state = STATE_SEQUENCE;
break;

// normally a space should never occur
Expand All @@ -253,7 +264,7 @@ class TokenParser {
this.currentNode = this.createNode(this.currentNode, this.pos + i);
this.currentNode.type = 'SECTION';
this.currentNode.isClosed = false;
this.state = 'NORMAL';
this.state = STATE_NORMAL;

// RFC2221 defines a response code REFERRAL whose payload is an
// RFC2192/RFC5092 imapurl that we will try to parse as an ATOM but
Expand Down Expand Up @@ -303,17 +314,17 @@ class TokenParser {
this.currentNode = this.createNode(this.currentNode, this.pos + i);
this.currentNode.type = 'ATOM';
this.currentNode.value = chr;
this.state = 'ATOM';
this.state = STATE_ATOM;
break;
}
break;

case 'ATOM':
case STATE_ATOM:
// space finishes an atom
if (chr === ' ') {
this.currentNode.endPos = this.pos + i - 1;
this.currentNode = this.currentNode.parentNode;
this.state = 'NORMAL';
this.state = STATE_NORMAL;
break;
}

Expand All @@ -328,16 +339,16 @@ class TokenParser {
this.currentNode.isClosed = true;
this.currentNode.endPos = this.pos + i;
this.currentNode = this.currentNode.parentNode;
this.state = 'NORMAL';
this.state = STATE_NORMAL;
checkSP();

break;
}

if ((chr === ',' || chr === ':') && this.currentNode.value.match(/^\d+$/)) {
if ((chr === ',' || chr === ':') && RE_DIGITS.test(this.currentNode.value)) {
this.currentNode.type = 'SEQUENCE';
this.currentNode.isClosed = true;
this.state = 'SEQUENCE';
this.state = STATE_SEQUENCE;
}

// [ starts a section group for this element
Expand All @@ -353,7 +364,7 @@ class TokenParser {
this.currentNode = this.createNode(this.currentNode.parentNode, this.pos + i);
this.currentNode.type = 'SECTION';
this.currentNode.isClosed = false;
this.state = 'NORMAL';
this.state = STATE_NORMAL;
break;
}

Expand Down Expand Up @@ -386,13 +397,13 @@ class TokenParser {
this.currentNode.value += chr;
break;

case 'STRING':
case STATE_STRING:
// DQUOTE ends the string sequence
if (chr === '"') {
this.currentNode.endPos = this.pos + i;
this.currentNode.isClosed = true;
this.currentNode = this.currentNode.parentNode;
this.state = 'NORMAL';
this.state = STATE_NORMAL;

checkSP();
break;
Expand All @@ -413,9 +424,9 @@ class TokenParser {
this.currentNode.value += chr;
break;

case 'PARTIAL':
case STATE_PARTIAL:
if (chr === '>') {
if (this.currentNode.value.substr(-1) === '.') {
if (this.currentNode.value.at(-1) === '.') {
let error = new Error(`Unexpected end of partial at position ${this.pos + 1} [E19]`);
error.code = 'ParserError19';
error.parserContext = { input: this.str, pos: this.pos + i, chr };
Expand All @@ -424,7 +435,7 @@ class TokenParser {
this.currentNode.endPos = this.pos + i;
this.currentNode.isClosed = true;
this.currentNode = this.currentNode.parentNode;
this.state = 'NORMAL';
this.state = STATE_NORMAL;
checkSP();
break;
}
Expand Down Expand Up @@ -453,7 +464,7 @@ class TokenParser {
this.currentNode.value += chr;
break;

case 'LITERAL':
case STATE_LITERAL:
if (this.currentNode.started) {
// only relevant if literals are not already parsed out from input

Expand All @@ -473,7 +484,7 @@ class TokenParser {
this.currentNode.value = this.currentNode.chBuffer.toString('binary');
this.currentNode.chBuffer = Buffer.alloc(0);
this.currentNode = this.currentNode.parentNode;
this.state = 'NORMAL';
this.state = STATE_NORMAL;
checkSP();
}
break;
Expand Down Expand Up @@ -510,7 +521,7 @@ class TokenParser {
this.currentNode.endPos = this.pos + i;
this.currentNode.isClosed = true;
this.currentNode = this.currentNode.parentNode;
this.state = 'NORMAL';
this.state = STATE_NORMAL;
checkSP();
} else if (this.options.literals) {
// use the next precached literal values
Expand All @@ -528,7 +539,7 @@ class TokenParser {
this.currentNode.started = false;
this.currentNode.isClosed = true;
this.currentNode = this.currentNode.parentNode;
this.state = 'NORMAL';
this.state = STATE_NORMAL;
checkSP();
} else {
this.currentNode.started = true;
Expand All @@ -554,17 +565,17 @@ class TokenParser {
this.currentNode.literalLength = (this.currentNode.literalLength || '') + chr;
break;

case 'SEQUENCE':
case STATE_SEQUENCE:
// space finishes the sequence set
if (chr === ' ') {
if (!this.currentNode.value.substr(-1).match(/\d/) && this.currentNode.value.substr(-1) !== '*') {
if (!RE_SINGLE_DIGIT.test(this.currentNode.value.at(-1)) && this.currentNode.value.at(-1) !== '*') {
let error = new Error(`Unexpected whitespace at position ${this.pos + i} [E27]`);
error.code = 'ParserError27';
error.parserContext = { input: this.str, pos: this.pos + i, chr };
throw error;
}

if (this.currentNode.value !== '*' && this.currentNode.value.substr(-1) === '*' && this.currentNode.value.substr(-2, 1) !== ':') {
if (this.currentNode.value !== '*' && this.currentNode.value.at(-1) === '*' && this.currentNode.value.at(-2) !== ':') {
let error = new Error(`Unexpected whitespace at position ${this.pos + i} [E28]`);
error.code = 'ParserError28';
error.parserContext = { input: this.str, pos: this.pos + i, chr };
Expand All @@ -574,7 +585,7 @@ class TokenParser {
this.currentNode.isClosed = true;
this.currentNode.endPos = this.pos + i - 1;
this.currentNode = this.currentNode.parentNode;
this.state = 'NORMAL';
this.state = STATE_NORMAL;
break;
} else if (this.currentNode.parentNode && chr === ']' && this.currentNode.parentNode.type === 'SECTION') {
this.currentNode.endPos = this.pos + i - 1;
Expand All @@ -583,47 +594,47 @@ class TokenParser {
this.currentNode.isClosed = true;
this.currentNode.endPos = this.pos + i;
this.currentNode = this.currentNode.parentNode;
this.state = 'NORMAL';
this.state = STATE_NORMAL;

checkSP();
break;
}

if (chr === ':') {
if (!this.currentNode.value.substr(-1).match(/\d/) && this.currentNode.value.substr(-1) !== '*') {
if (!RE_SINGLE_DIGIT.test(this.currentNode.value.at(-1)) && this.currentNode.value.at(-1) !== '*') {
let error = new Error(`Unexpected range separator : at position ${this.pos + i} [E29]`);
error.code = 'ParserError29';
error.parserContext = { input: this.str, pos: this.pos + i, chr };
throw error;
}
} else if (chr === '*') {
if ([',', ':'].indexOf(this.currentNode.value.substr(-1)) < 0) {
if ([',', ':'].indexOf(this.currentNode.value.at(-1)) < 0) {
let error = new Error(`Unexpected range wildcard at position ${this.pos + i} [E30]`);
error.code = 'ParserError30';
error.parserContext = { input: this.str, pos: this.pos + i, chr };
throw error;
}
} else if (chr === ',') {
if (!this.currentNode.value.substr(-1).match(/\d/) && this.currentNode.value.substr(-1) !== '*') {
if (!RE_SINGLE_DIGIT.test(this.currentNode.value.at(-1)) && this.currentNode.value.at(-1) !== '*') {
let error = new Error(`Unexpected sequence separator , at position ${this.pos + i} [E31]`);
error.code = 'ParserError31';
error.parserContext = { input: this.str, pos: this.pos + i, chr };
throw error;
}
if (this.currentNode.value.substr(-1) === '*' && this.currentNode.value.substr(-2, 1) !== ':') {
if (this.currentNode.value.at(-1) === '*' && this.currentNode.value.at(-2) !== ':') {
let error = new Error(`Unexpected sequence separator , at position ${this.pos + i} [E32]`);
error.code = 'ParserError32';
error.parserContext = { input: this.str, pos: this.pos + i, chr };
throw error;
}
} else if (!chr.match(/\d/)) {
} else if (!RE_SINGLE_DIGIT.test(chr)) {
let error = new Error(`Unexpected char at position ${this.pos + i} [E33: ${JSON.stringify(chr)}]`);
error.code = 'ParserError33';
error.parserContext = { input: this.str, pos: this.pos + i, chr };
throw error;
}

if (chr.match(/\d/) && this.currentNode.value.substr(-1) === '*') {
if (RE_SINGLE_DIGIT.test(chr) && this.currentNode.value.at(-1) === '*') {
let error = new Error(`Unexpected number at position ${this.pos + i} [E34: ${JSON.stringify(chr)}]`);
error.code = 'ParserError34';
error.parserContext = { input: this.str, pos: this.pos + i, chr };
Expand All @@ -633,7 +644,7 @@ class TokenParser {
this.currentNode.value += chr;
break;

case 'TEXT':
case STATE_TEXT:
this.currentNode.value += chr;
break;
}
Expand Down

0 comments on commit 3451ad2

Please sign in to comment.