Skip to content

Commit e8822ed

Browse files
committed
Lazy compute arithmetic expressions
1 parent de0b47d commit e8822ed

2 files changed

Lines changed: 110 additions & 32 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ Relative performance comparison (on Apple M1 Pro/32GB), unbash is x times faster
7777
| Parser | short | advanced | medium | large |
7878
| ---------------------------- | ----: | -------: | -----: | ----: |
7979
| tree-sitter-bash (native) | 14x | 9x | 4x | 4x |
80-
| tree-sitter-bash (WASM) | 14x | 10x | 7x | 7x |
81-
| sh-syntax | 2008x | 1296x | 7x | 4x |
82-
| bash-parser | 230x | N/A | N/A | N/A |
80+
| tree-sitter-bash (WASM) | 14x | 11x | 7x | 7x |
81+
| sh-syntax | 1998x | 1419x | 7x | 4x |
82+
| bash-parser | 236x | N/A | N/A | N/A |
8383
| @ericcornelissen/bash-parser | 247x | N/A | N/A | N/A |
8484

8585
Run the benchmarks using Node.js v22 or v24:

src/parser.ts

Lines changed: 107 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,110 @@ class WordImpl implements Word {
6868
}
6969
}
7070

71+
class ArithmeticCommandImpl implements ArithmeticCommand {
72+
type: "ArithmeticCommand" = "ArithmeticCommand";
73+
pos: number;
74+
end: number;
75+
body: string;
76+
#expression: ArithmeticExpression | undefined | null = null;
77+
78+
constructor(pos: number, end: number, body: string) {
79+
this.pos = pos;
80+
this.end = end;
81+
this.body = body;
82+
}
83+
84+
get expression(): ArithmeticExpression | undefined {
85+
if (this.#expression === null) {
86+
this.#expression = parseArithmeticExpression(this.body, this.pos + 2) ?? undefined;
87+
}
88+
return this.#expression;
89+
}
90+
set expression(v: ArithmeticExpression | undefined) {
91+
this.#expression = v ?? undefined;
92+
}
93+
}
94+
95+
class ArithmeticForImpl implements ArithmeticFor {
96+
type: "ArithmeticFor" = "ArithmeticFor";
97+
pos: number;
98+
end: number;
99+
body: CompoundList;
100+
#initStr: string;
101+
#testStr: string;
102+
#updateStr: string;
103+
#initPos: number;
104+
#testPos: number;
105+
#updatePos: number;
106+
#initialize: ArithmeticExpression | undefined | null = null;
107+
#test: ArithmeticExpression | undefined | null = null;
108+
#update: ArithmeticExpression | undefined | null = null;
109+
110+
constructor(
111+
pos: number, end: number, body: CompoundList,
112+
initStr: string, testStr: string, updateStr: string,
113+
initPos: number, testPos: number, updatePos: number,
114+
) {
115+
this.pos = pos;
116+
this.end = end;
117+
this.body = body;
118+
this.#initStr = initStr;
119+
this.#testStr = testStr;
120+
this.#updateStr = updateStr;
121+
this.#initPos = initPos;
122+
this.#testPos = testPos;
123+
this.#updatePos = updatePos;
124+
}
125+
126+
get initialize(): ArithmeticExpression | undefined {
127+
if (this.#initialize === null) {
128+
if (this.#initStr) {
129+
const expr = parseArithmeticExpression(this.#initStr);
130+
if (expr) offsetArith(expr, this.#initPos);
131+
this.#initialize = expr ?? undefined;
132+
} else {
133+
this.#initialize = undefined;
134+
}
135+
}
136+
return this.#initialize;
137+
}
138+
set initialize(v: ArithmeticExpression | undefined) {
139+
this.#initialize = v ?? undefined;
140+
}
141+
142+
get test(): ArithmeticExpression | undefined {
143+
if (this.#test === null) {
144+
if (this.#testStr) {
145+
const expr = parseArithmeticExpression(this.#testStr);
146+
if (expr) offsetArith(expr, this.#testPos);
147+
this.#test = expr ?? undefined;
148+
} else {
149+
this.#test = undefined;
150+
}
151+
}
152+
return this.#test;
153+
}
154+
set test(v: ArithmeticExpression | undefined) {
155+
this.#test = v ?? undefined;
156+
}
157+
158+
get update(): ArithmeticExpression | undefined {
159+
if (this.#update === null) {
160+
if (this.#updateStr) {
161+
const expr = parseArithmeticExpression(this.#updateStr);
162+
if (expr) offsetArith(expr, this.#updatePos);
163+
this.#update = expr ?? undefined;
164+
} else {
165+
this.#update = undefined;
166+
}
167+
}
168+
return this.#update;
169+
}
170+
set update(v: ArithmeticExpression | undefined) {
171+
this.#update = v ?? undefined;
172+
}
173+
}
174+
71175
const CASE_TERMINATORS: Record<number, CaseTerminator> = {
72176
[Token.DoubleSemi]: ";;",
73177
[Token.SemiAmp]: ";&",
@@ -469,12 +573,8 @@ class Parser {
469573
// arith_command := (( expr ))
470574
private arithCommand(): ArithmeticCommand {
471575
const tok = this.tok.next(LexContext.CommandStart);
472-
const body = tok.value;
473-
const tokPos = tok.pos;
474-
const tokEnd = tok.end;
475-
const expr = parseArithmeticExpression(body, tokPos + 2) ?? undefined;
476576
this._redirects = this.collectTrailingRedirects();
477-
return { type: "ArithmeticCommand", pos: tokPos, end: tokEnd, expression: expr, body };
577+
return new ArithmeticCommandImpl(tok.pos, tok.end, tok.value);
478578
}
479579

480580
// coproc := COPROC [name] command [redirections]
@@ -629,41 +729,19 @@ class Parser {
629729
// C-style for: (( expr; expr; expr )) [;|NL] do list done | { list }
630730
private cStyleFor(pos: number): ArithmeticFor {
631731
const [initStr, testStr, updateStr, initPos, testPos, updatePos] = this.tok.readCStyleForExprs();
632-
const init = initStr ? (parseArithmeticExpression(initStr) ?? undefined) : undefined;
633-
const test_ = testStr ? (parseArithmeticExpression(testStr) ?? undefined) : undefined;
634-
const update = updateStr ? (parseArithmeticExpression(updateStr) ?? undefined) : undefined;
635-
if (init) offsetArith(init, initPos);
636-
if (test_) offsetArith(test_, testPos);
637-
if (update) offsetArith(update, updatePos);
638732
if (this.tok.peek(LexContext.CommandStart).token === Token.Semi) this.tok.next(LexContext.CommandStart);
639733
this.skipNewlines(LexContext.CommandStart);
640734
if (this.tok.peek(LexContext.CommandStart).token === Token.LBrace) {
641735
const bg = this.braceGroup();
642-
return {
643-
type: "ArithmeticFor",
644-
pos,
645-
end: bg.end,
646-
initialize: init,
647-
test: test_,
648-
update,
649-
body: bg.body,
650-
} satisfies ArithmeticFor;
736+
return new ArithmeticForImpl(pos, bg.end, bg.body, initStr, testStr, updateStr, initPos, testPos, updatePos);
651737
}
652738
if (!this.accept(Token.Do, LexContext.CommandStart)) this.error("expected 'do'", this.tok.getPos());
653739
const body = this.list();
654740
const closeEnd = this.acceptEnd(Token.Done, LexContext.CommandStart);
655741
if (closeEnd < 0) this.error("expected 'done' to close 'for'", this.tok.getPos());
656742
const end = closeEnd >= 0 ? closeEnd : pos;
657743
this._redirects = this.collectTrailingRedirects();
658-
return {
659-
type: "ArithmeticFor",
660-
pos,
661-
end,
662-
initialize: init,
663-
test: test_,
664-
update,
665-
body: this.makeCompoundList(body),
666-
} satisfies ArithmeticFor;
744+
return new ArithmeticForImpl(pos, end, this.makeCompoundList(body), initStr, testStr, updateStr, initPos, testPos, updatePos);
667745
}
668746

669747
private whileClause(): While {

0 commit comments

Comments
 (0)