Skip to content

Commit e783fe4

Browse files
committed
refactor(parser): simplify code structure and improve performance
- Consolidate unary operator parsing into loop for typeof/void keywords - Replace switch statement with static ESCAPE_CHARS lookup table - Extract operators to static class member sorted by length - Use character code comparisons instead of regex for digit/identifier checks
1 parent 8970518 commit e783fe4

1 file changed

Lines changed: 82 additions & 90 deletions

File tree

src/parser.ts

Lines changed: 82 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -206,38 +206,29 @@ class Parser {
206206
this.skipWhitespace();
207207
const ch = this.peek();
208208

209+
// Single-character unary operators
209210
if (ch === "!" || ch === "~" || ch === "+" || ch === "-") {
210211
this.advance();
211212
this.skipWhitespace();
212-
const argument = this.parseUnary();
213213
return {
214214
type: "UnaryExpr",
215215
operator: ch,
216-
argument,
216+
argument: this.parseUnary(),
217217
prefix: true,
218218
};
219219
}
220220

221-
if (this.matchKeyword("typeof")) {
222-
this.skipWhitespace();
223-
const argument = this.parseUnary();
224-
return {
225-
type: "UnaryExpr",
226-
operator: "typeof",
227-
argument,
228-
prefix: true,
229-
};
230-
}
231-
232-
if (this.matchKeyword("void")) {
233-
this.skipWhitespace();
234-
const argument = this.parseUnary();
235-
return {
236-
type: "UnaryExpr",
237-
operator: "void",
238-
argument,
239-
prefix: true,
240-
};
221+
// Keyword unary operators
222+
for (const keyword of ["typeof", "void"] as const) {
223+
if (this.matchKeyword(keyword)) {
224+
this.skipWhitespace();
225+
return {
226+
type: "UnaryExpr",
227+
operator: keyword,
228+
argument: this.parseUnary(),
229+
prefix: true,
230+
};
231+
}
241232
}
242233

243234
return this.parsePostfix();
@@ -438,6 +429,16 @@ class Parser {
438429
};
439430
}
440431

432+
private static readonly ESCAPE_CHARS: Record<string, string> = {
433+
n: "\n",
434+
r: "\r",
435+
t: "\t",
436+
"\\": "\\",
437+
"'": "'",
438+
'"': '"',
439+
"`": "`",
440+
};
441+
441442
private parseString(): StringLiteral {
442443
const quote = this.peek() as "'" | '"' | "`";
443444
this.advance();
@@ -447,31 +448,7 @@ class Parser {
447448
if (this.peek() === "\\") {
448449
this.advance();
449450
const escaped = this.peek();
450-
switch (escaped) {
451-
case "n":
452-
value += "\n";
453-
break;
454-
case "r":
455-
value += "\r";
456-
break;
457-
case "t":
458-
value += "\t";
459-
break;
460-
case "\\":
461-
value += "\\";
462-
break;
463-
case "'":
464-
value += "'";
465-
break;
466-
case '"':
467-
value += '"';
468-
break;
469-
case "`":
470-
value += "`";
471-
break;
472-
default:
473-
value += escaped;
474-
}
451+
value += Parser.ESCAPE_CHARS[escaped] ?? escaped;
475452
this.advance();
476453
} else {
477454
value += this.peek();
@@ -585,47 +562,52 @@ class Parser {
585562
return args;
586563
}
587564

565+
// Operators sorted by length (longest first) to ensure correct matching
566+
private static readonly OPERATORS = [
567+
// 10 chars
568+
"instanceof",
569+
// 3 chars
570+
">>>",
571+
"===",
572+
"!==",
573+
// 2 chars
574+
"&&",
575+
"||",
576+
"??",
577+
"==",
578+
"!=",
579+
"<=",
580+
">=",
581+
"<<",
582+
">>",
583+
"**",
584+
"in",
585+
// 1 char
586+
"+",
587+
"-",
588+
"*",
589+
"/",
590+
"%",
591+
"<",
592+
">",
593+
"&",
594+
"|",
595+
"^",
596+
];
597+
598+
private static readonly KEYWORD_OPERATORS = new Set(["in", "instanceof"]);
599+
588600
private peekOperator(): string | null {
589-
// 按长度排序,先匹配长的运算符
590-
const ops = [
591-
">>>",
592-
"===",
593-
"!==",
594-
"instanceof",
595-
"&&",
596-
"||",
597-
"??",
598-
"==",
599-
"!=",
600-
"<=",
601-
">=",
602-
"<<",
603-
">>",
604-
"**",
605-
"in",
606-
"+",
607-
"-",
608-
"*",
609-
"/",
610-
"%",
611-
"<",
612-
">",
613-
"&",
614-
"|",
615-
"^",
616-
];
617-
618-
for (const op of ops) {
619-
if (this.source.startsWith(op, this.pos)) {
620-
// 对于 "in" 和 "instanceof",确保后面不是标识符字符
621-
if (op === "in" || op === "instanceof") {
622-
const nextChar = this.source[this.pos + op.length];
623-
if (nextChar && this.isIdentifierPart(nextChar)) {
624-
continue;
625-
}
626-
}
627-
return op;
601+
for (const op of Parser.OPERATORS) {
602+
if (!this.source.startsWith(op, this.pos)) continue;
603+
604+
// Keyword operators must not be followed by identifier characters
605+
if (Parser.KEYWORD_OPERATORS.has(op)) {
606+
const nextChar = this.source[this.pos + op.length];
607+
if (nextChar && this.isIdentifierPart(nextChar)) continue;
628608
}
609+
610+
return op;
629611
}
630612
return null;
631613
}
@@ -667,19 +649,29 @@ class Parser {
667649
}
668650

669651
private isDigit(ch: string): boolean {
670-
return /[0-9]/.test(ch);
652+
const code = ch.charCodeAt(0);
653+
return code >= 48 && code <= 57; // 0-9
671654
}
672655

673656
private isHexDigit(ch: string): boolean {
674-
return /[0-9a-fA-F]/.test(ch);
657+
const code = ch.charCodeAt(0);
658+
return (code >= 48 && code <= 57) || (code >= 65 && code <= 70) || (code >= 97 && code <= 102);
675659
}
676660

677661
private isIdentifierStart(ch: string): boolean {
678-
return /[a-zA-Z_$]/.test(ch);
662+
const code = ch.charCodeAt(0);
663+
return (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code === 95 || code === 36;
679664
}
680665

681666
private isIdentifierPart(ch: string): boolean {
682-
return /[a-zA-Z0-9_$]/.test(ch);
667+
const code = ch.charCodeAt(0);
668+
return (
669+
(code >= 65 && code <= 90) ||
670+
(code >= 97 && code <= 122) ||
671+
(code >= 48 && code <= 57) ||
672+
code === 95 ||
673+
code === 36
674+
);
683675
}
684676
}
685677

0 commit comments

Comments
 (0)