Skip to content

Commit ff7d458

Browse files
AntonPiepermarcbachmann
authored andcommitted
perf(parser): reduce range bookkeeping overhead
1 parent 76184ed commit ff7d458

1 file changed

Lines changed: 76 additions & 29 deletions

File tree

lib/parser.js

Lines changed: 76 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,30 @@ export class ASTNode {
8282
this.args = args
8383
}
8484

85+
static infix(input, pos, op, left, right) {
86+
return new ASTNode(input, pos, left.#meta.start, right.#meta.end, op, [left, right])
87+
}
88+
89+
static ternary(input, pos, op, expression, consequent, alternate) {
90+
return new ASTNode(input, pos, expression.#meta.start, alternate.#meta.end, op, [
91+
expression,
92+
consequent,
93+
alternate
94+
])
95+
}
96+
97+
static unary(input, pos, op, arg) {
98+
return new ASTNode(input, pos, pos, arg.#meta.end, op, arg)
99+
}
100+
101+
static access(input, pos, op, left, right, end) {
102+
return new ASTNode(input, pos, left.#meta.start, end, op, [left, right])
103+
}
104+
105+
static receiverCallStart(args) {
106+
return args[1].#meta.start
107+
}
108+
85109
clone(op, args) {
86110
const meta = this.#meta
87111
return new ASTNode(meta.input, meta.pos, meta.start, meta.end, op, args)
@@ -195,7 +219,6 @@ class Lexer {
195219
length
196220

197221
tokenPos
198-
tokenEnd
199222
tokenType
200223
tokenValue
201224

@@ -206,9 +229,8 @@ class Lexer {
206229
return input
207230
}
208231

209-
token(pos, type, value, end = this.pos) {
232+
token(pos, type, value) {
210233
this.tokenPos = pos
211-
this.tokenEnd = end
212234
this.tokenType = type
213235
this.tokenValue = value
214236
return this
@@ -595,7 +617,6 @@ export class Parser {
595617

596618
type = null
597619
pos = null
598-
end = null
599620

600621
constructor(limits, registry) {
601622
this.limits = limits
@@ -617,10 +638,33 @@ export class Parser {
617638
return node
618639
}
619640

641+
#infixNode(pos, op, left, right) {
642+
const node = ASTNode.infix(this.input, pos, op, left, right)
643+
if (!this.astNodesRemaining--) this.#limitExceeded('maxAstNodes', pos)
644+
return node
645+
}
646+
647+
#ternaryNode(pos, expression, consequent, alternate) {
648+
const node = ASTNode.ternary(this.input, pos, OPS.ternary, expression, consequent, alternate)
649+
if (!this.astNodesRemaining--) this.#limitExceeded('maxAstNodes', pos)
650+
return node
651+
}
652+
653+
#unaryNode(pos, op, arg) {
654+
const node = ASTNode.unary(this.input, pos, op, arg)
655+
if (!this.astNodesRemaining--) this.#limitExceeded('maxAstNodes', pos)
656+
return node
657+
}
658+
659+
#accessNode(pos, op, left, right, end) {
660+
const node = ASTNode.access(this.input, pos, op, left, right, end)
661+
if (!this.astNodesRemaining--) this.#limitExceeded('maxAstNodes', pos)
662+
return node
663+
}
664+
620665
#advanceToken(returnValue = this.pos) {
621666
const l = this.lexer.nextToken()
622667
this.pos = l.tokenPos
623-
this.end = l.tokenEnd
624668
this.type = l.tokenType
625669
return returnValue
626670
}
@@ -636,7 +680,7 @@ export class Parser {
636680
throw parseError(
637681
'expected_token',
638682
`Expected ${TOKEN_BY_NUMBER[expectedType]}, got ${TOKEN_BY_NUMBER[this.type]}`,
639-
{pos: this.pos, start: this.pos, end: this.end, input: this.input}
683+
{pos: this.pos, start: this.pos, end: this.lexer.pos, input: this.input}
640684
)
641685
}
642686

@@ -660,13 +704,16 @@ export class Parser {
660704
throw parseError('unexpected_character', `Unexpected character: '${this.input[this.lexer.pos - 1]}'`, {
661705
pos: this.pos,
662706
start: this.pos,
663-
end: this.end,
707+
end: this.lexer.pos,
664708
input: this.input
665709
})
666710
}
667711

668-
#expandMacro(pos, op, args, start = pos, end = start) {
712+
#expandMacro(pos, op, args, start, end) {
669713
const [methodName, receiver, fnArgs] = op === OPS.rcall ? args : [args[0], null, args[1]]
714+
if (start === undefined && op === OPS.rcall) start = ASTNode.receiverCallStart(args)
715+
if (start === undefined) start = pos
716+
if (end === undefined) end = start
670717
const decl = this.registry.findMacro(methodName, !!receiver, fnArgs.length)
671718
const ast = this.#node(pos, op, args, start, end)
672719
if (!decl) return ast
@@ -686,7 +733,7 @@ export class Parser {
686733
this.consume(TOKEN.COLON)
687734
const alternate = this.parseExpression()
688735
this.maxDepthRemaining++
689-
return this.#node(questionPos, OPS.ternary, [expr, consequent, alternate], expr.start, alternate.end)
736+
return this.#ternaryNode(questionPos, expr, consequent, alternate)
690737
}
691738

692739
// LogicalOr ::= LogicalAnd ('||' LogicalAnd)*
@@ -695,7 +742,7 @@ export class Parser {
695742
while (this.match(TOKEN.OR)) {
696743
const opPos = this.#advanceToken()
697744
const right = this.parseLogicalAnd()
698-
expr = this.#node(opPos, OPS['||'], [expr, right], expr.start, right.end)
745+
expr = this.#infixNode(opPos, OPS['||'], expr, right)
699746
}
700747
return expr
701748
}
@@ -706,7 +753,7 @@ export class Parser {
706753
while (this.match(TOKEN.AND)) {
707754
const opPos = this.#advanceToken()
708755
const right = this.parseEquality()
709-
expr = this.#node(opPos, OPS['&&'], [expr, right], expr.start, right.end)
756+
expr = this.#infixNode(opPos, OPS['&&'], expr, right)
710757
}
711758
return expr
712759
}
@@ -718,7 +765,7 @@ export class Parser {
718765
const op = OP_FOR_TOKEN[this.type]
719766
const opPos = this.#advanceToken()
720767
const right = this.parseRelational()
721-
expr = this.#node(opPos, op, [expr, right], expr.start, right.end)
768+
expr = this.#infixNode(opPos, op, expr, right)
722769
}
723770
return expr
724771
}
@@ -736,7 +783,7 @@ export class Parser {
736783
const op = OP_FOR_TOKEN[this.type]
737784
const opPos = this.#advanceToken()
738785
const right = this.parseAdditive()
739-
expr = this.#node(opPos, op, [expr, right], expr.start, right.end)
786+
expr = this.#infixNode(opPos, op, expr, right)
740787
}
741788
return expr
742789
}
@@ -748,7 +795,7 @@ export class Parser {
748795
const op = OP_FOR_TOKEN[this.type]
749796
const opPos = this.#advanceToken()
750797
const right = this.parseMultiplicative()
751-
expr = this.#node(opPos, op, [expr, right], expr.start, right.end)
798+
expr = this.#infixNode(opPos, op, expr, right)
752799
}
753800
return expr
754801
}
@@ -760,7 +807,7 @@ export class Parser {
760807
const op = OP_FOR_TOKEN[this.type]
761808
const opPos = this.#advanceToken()
762809
const right = this.parseUnary()
763-
expr = this.#node(opPos, op, [expr, right], expr.start, right.end)
810+
expr = this.#infixNode(opPos, op, expr, right)
764811
}
765812
return expr
766813
}
@@ -770,12 +817,12 @@ export class Parser {
770817
if (this.type === TOKEN.NOT) {
771818
const pos = this.#advanceToken()
772819
const arg = this.parseUnary()
773-
return this.#node(pos, OPS.unaryNot, arg, pos, arg.end)
820+
return this.#unaryNode(pos, OPS.unaryNot, arg)
774821
}
775822
if (this.type === TOKEN.MINUS) {
776823
const pos = this.#advanceToken()
777824
const arg = this.parseUnary()
778-
return this.#node(pos, OPS.unaryMinus, arg, pos, arg.end)
825+
return this.#unaryNode(pos, OPS.unaryMinus, arg)
779826
}
780827
return this.parsePostfix()
781828
}
@@ -796,15 +843,15 @@ export class Parser {
796843

797844
const propertyValue = this.value
798845
const propertyPos = this.pos
799-
const propertyEnd = this.end
846+
const propertyEnd = this.lexer.pos
800847
this.consume(TOKEN.IDENTIFIER)
801848
if (op === OPS.fieldAccess && this.match(TOKEN.LPAREN) && this.#advanceToken()) {
802849
const args = this.parseArgumentList()
803-
const closeEnd = this.end
850+
const closeEnd = this.lexer.pos
804851
this.consume(TOKEN.RPAREN)
805-
expr = this.#expandMacro(propertyPos, OPS.rcall, [propertyValue, expr, args], expr.start, closeEnd)
852+
expr = this.#expandMacro(propertyPos, OPS.rcall, [propertyValue, expr, args], undefined, closeEnd)
806853
} else {
807-
expr = this.#node(propertyPos, op, [expr, propertyValue], expr.start, propertyEnd)
854+
expr = this.#accessNode(propertyPos, op, expr, propertyValue, propertyEnd)
808855
}
809856
continue
810857
}
@@ -819,9 +866,9 @@ export class Parser {
819866
: OPS.bracketAccess
820867

821868
const index = this.parseExpression()
822-
const closeEnd = this.end
869+
const closeEnd = this.lexer.pos
823870
this.consume(TOKEN.RBRACKET)
824-
expr = this.#node(bracket, op, [expr, index], expr.start, closeEnd)
871+
expr = this.#accessNode(bracket, op, expr, index, closeEnd)
825872
continue
826873
}
827874
break
@@ -852,19 +899,19 @@ export class Parser {
852899
throw parseError('unexpected_token', `Unexpected token: ${TOKEN_BY_NUMBER[this.type]}`, {
853900
pos: this.pos,
854901
start: this.pos,
855-
end: this.end,
902+
end: this.lexer.pos,
856903
input: this.input
857904
})
858905
}
859906

860907
#consumeLiteral() {
861-
return this.#advanceToken(this.#node(this.pos, OPS.value, this.value, this.pos, this.end))
908+
return this.#advanceToken(this.#node(this.pos, OPS.value, this.value, this.pos, this.lexer.pos))
862909
}
863910

864911
#parseIdentifierPrimary() {
865912
const value = this.value
866913
const pos = this.pos
867-
const end = this.end
914+
const end = this.lexer.pos
868915
this.consume(TOKEN.IDENTIFIER)
869916
if (RESERVED.has(value)) {
870917
throw parseError('reserved_identifier', `Reserved identifier: ${value}`, {
@@ -878,7 +925,7 @@ export class Parser {
878925
if (!this.match(TOKEN.LPAREN)) return this.#node(pos, OPS.id, value, pos, end)
879926
this.#advanceToken()
880927
const args = this.parseArgumentList()
881-
const closeEnd = this.end
928+
const closeEnd = this.lexer.pos
882929
this.consume(TOKEN.RPAREN)
883930
return this.#expandMacro(pos, OPS.call, [value, args], pos, closeEnd)
884931
}
@@ -906,7 +953,7 @@ export class Parser {
906953
}
907954
}
908955

909-
const closeEnd = this.end
956+
const closeEnd = this.lexer.pos
910957
this.consume(TOKEN.RBRACKET)
911958
return this.#node(token, OPS.list, elements, token, closeEnd)
912959
}
@@ -927,7 +974,7 @@ export class Parser {
927974
}
928975
}
929976

930-
const closeEnd = this.end
977+
const closeEnd = this.lexer.pos
931978
this.consume(TOKEN.RBRACE)
932979
return this.#node(token, OPS.map, props, token, closeEnd)
933980
}

0 commit comments

Comments
 (0)