Skip to content

Commit bb34da1

Browse files
committed
New: add visitor
1 parent 0dbc2d3 commit bb34da1

File tree

8 files changed

+7236
-8
lines changed

8 files changed

+7236
-8
lines changed

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@
3838
"lint": "eslint scripts src test --ext .ts",
3939
"pretest": "run-s build lint",
4040
"test": "_mocha --require ts-node/register --reporter dot --timeout 10000 \"test/*.ts\"",
41-
"update:test": "ts-node tools/update-fixtures.ts",
42-
"update:ids": "ts-node tools/update-unicode-ids.ts",
41+
"update:test": "ts-node scripts/update-fixtures.ts",
42+
"update:ids": "ts-node scripts/update-unicode-ids.ts",
4343
"preversion": "npm test",
4444
"version": "npm run -s build",
4545
"postversion": "git push && git push --tags",

scripts/update-fixtures.ts

+55-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { parseRegExpLiteral } from "../src/index"
2-
import { Fixtures, save } from "../test/fixtures/parser/literal"
1+
import { AST, parseRegExpLiteral, visitRegExpAST } from "../src/index"
2+
import * as Parser from "../test/fixtures/parser/literal"
3+
import * as Visitor from "../test/fixtures/visitor"
34
import { cloneWithoutCircular } from "./clone-without-circular"
45

5-
for (const filename of Object.keys(Fixtures)) {
6-
const fixture = Fixtures[filename]
6+
for (const filename of Object.keys(Parser.Fixtures)) {
7+
const fixture = Parser.Fixtures[filename]
78
const options = fixture.options
89

910
for (const pattern of Object.keys(fixture.patterns)) {
@@ -17,5 +18,54 @@ for (const filename of Object.keys(Fixtures)) {
1718
}
1819
}
1920

20-
save()
21+
Parser.save()
22+
}
23+
24+
for (const filename of Object.keys(Visitor.Fixtures)) {
25+
const fixture = Visitor.Fixtures[filename]
26+
const options = fixture.options
27+
28+
for (const pattern of Object.keys(fixture.patterns)) {
29+
const ast = parseRegExpLiteral(pattern, options)
30+
const history = []
31+
const enter = (node: AST.Node): void => {
32+
history.push(`enter:${node.type}:${node.raw}`)
33+
}
34+
const leave = (node: AST.Node): void => {
35+
history.push(`leave:${node.type}:${node.raw}`)
36+
}
37+
38+
visitRegExpAST(ast, {
39+
onAssertionEnter: enter,
40+
onBackreferenceEnter: enter,
41+
onCapturingGroupEnter: enter,
42+
onCharacterEnter: enter,
43+
onCharacterClassEnter: enter,
44+
onCharacterClassRangeEnter: enter,
45+
onCharacterSetEnter: enter,
46+
onDisjunctionEnter: enter,
47+
onFlagsEnter: enter,
48+
onGroupEnter: enter,
49+
onPatternEnter: enter,
50+
onQuantifierEnter: enter,
51+
onRegExpLiteralEnter: enter,
52+
onAssertionLeave: leave,
53+
onBackreferenceLeave: leave,
54+
onCapturingGroupLeave: leave,
55+
onCharacterLeave: leave,
56+
onCharacterClassLeave: leave,
57+
onCharacterClassRangeLeave: leave,
58+
onCharacterSetLeave: leave,
59+
onDisjunctionLeave: leave,
60+
onFlagsLeave: leave,
61+
onGroupLeave: leave,
62+
onPatternLeave: leave,
63+
onQuantifierLeave: leave,
64+
onRegExpLiteralLeave: leave,
65+
})
66+
67+
fixture.patterns[pattern] = history
68+
}
69+
70+
Visitor.save()
2171
}

src/index.ts

+8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as AST from "./ast"
22
import { RegExpParser } from "./parser"
33
import { RegExpValidator } from "./validator"
4+
import { RegExpVisitor } from "./visitor"
45

56
export { AST, RegExpParser, RegExpValidator }
67

@@ -28,3 +29,10 @@ export function validateRegExpLiteral(
2829
): void {
2930
return new RegExpValidator(options).validateLiteral(source)
3031
}
32+
33+
export function visitRegExpAST(
34+
node: AST.Node,
35+
handlers: RegExpVisitor.Handlers,
36+
): void {
37+
new RegExpVisitor(handlers).visit(node)
38+
}

src/visitor.ts

+232
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
import {
2+
Assertion,
3+
Backreference,
4+
CapturingGroup,
5+
Character,
6+
CharacterClass,
7+
CharacterClassRange,
8+
CharacterSet,
9+
Disjunction,
10+
Flags,
11+
Group,
12+
Node,
13+
Pattern,
14+
Quantifier,
15+
RegExpLiteral,
16+
} from "./ast"
17+
18+
/**
19+
* The visitor to walk on AST.
20+
*/
21+
export class RegExpVisitor {
22+
private readonly _handlers: RegExpVisitor.Handlers
23+
24+
/**
25+
* Initialize this visitor.
26+
* @param handlers Callbacks for each node.
27+
*/
28+
public constructor(handlers: RegExpVisitor.Handlers) {
29+
this._handlers = handlers
30+
}
31+
32+
/**
33+
* Visit a given node and descendant nodes.
34+
* @param node The root node to visit tree.
35+
*/
36+
public visit(node: Node): void {
37+
switch (node.type) {
38+
case "Assertion":
39+
this.visitAssertion(node)
40+
break
41+
case "Backreference":
42+
this.visitBackreference(node)
43+
break
44+
case "CapturingGroup":
45+
this.visitCapturingGroup(node)
46+
break
47+
case "Character":
48+
this.visitCharacter(node)
49+
break
50+
case "CharacterClass":
51+
this.visitCharacterClass(node)
52+
break
53+
case "CharacterClassRange":
54+
this.visitCharacterClassRange(node)
55+
break
56+
case "CharacterSet":
57+
this.visitCharacterSet(node)
58+
break
59+
case "Disjunction":
60+
this.visitDisjunction(node)
61+
break
62+
case "Flags":
63+
this.visitFlags(node)
64+
break
65+
case "Group":
66+
this.visitGroup(node)
67+
break
68+
case "Pattern":
69+
this.visitPattern(node)
70+
break
71+
case "Quantifier":
72+
this.visitQuantifier(node)
73+
break
74+
case "RegExpLiteral":
75+
this.visitRegExpLiteral(node)
76+
break
77+
default:
78+
throw new Error(`Unknown type: ${(node as any).type}`)
79+
}
80+
}
81+
82+
private visitAssertion(node: Assertion): void {
83+
if (this._handlers.onAssertionEnter) {
84+
this._handlers.onAssertionEnter(node)
85+
}
86+
if (node.kind === "lookahead" || node.kind === "lookbehind") {
87+
node.elements.forEach(this.visit, this)
88+
}
89+
if (this._handlers.onAssertionLeave) {
90+
this._handlers.onAssertionLeave(node)
91+
}
92+
}
93+
private visitBackreference(node: Backreference): void {
94+
if (this._handlers.onBackreferenceEnter) {
95+
this._handlers.onBackreferenceEnter(node)
96+
}
97+
if (this._handlers.onBackreferenceLeave) {
98+
this._handlers.onBackreferenceLeave(node)
99+
}
100+
}
101+
private visitCapturingGroup(node: CapturingGroup): void {
102+
if (this._handlers.onCapturingGroupEnter) {
103+
this._handlers.onCapturingGroupEnter(node)
104+
}
105+
node.elements.forEach(this.visit, this)
106+
if (this._handlers.onCapturingGroupLeave) {
107+
this._handlers.onCapturingGroupLeave(node)
108+
}
109+
}
110+
private visitCharacter(node: Character): void {
111+
if (this._handlers.onCharacterEnter) {
112+
this._handlers.onCharacterEnter(node)
113+
}
114+
if (this._handlers.onCharacterLeave) {
115+
this._handlers.onCharacterLeave(node)
116+
}
117+
}
118+
private visitCharacterClass(node: CharacterClass): void {
119+
if (this._handlers.onCharacterClassEnter) {
120+
this._handlers.onCharacterClassEnter(node)
121+
}
122+
node.elements.forEach(this.visit, this)
123+
if (this._handlers.onCharacterClassLeave) {
124+
this._handlers.onCharacterClassLeave(node)
125+
}
126+
}
127+
private visitCharacterClassRange(node: CharacterClassRange): void {
128+
if (this._handlers.onCharacterClassRangeEnter) {
129+
this._handlers.onCharacterClassRangeEnter(node)
130+
}
131+
this.visitCharacter(node.min)
132+
this.visitCharacter(node.max)
133+
if (this._handlers.onCharacterClassRangeLeave) {
134+
this._handlers.onCharacterClassRangeLeave(node)
135+
}
136+
}
137+
private visitCharacterSet(node: CharacterSet): void {
138+
if (this._handlers.onCharacterSetEnter) {
139+
this._handlers.onCharacterSetEnter(node)
140+
}
141+
if (this._handlers.onCharacterSetLeave) {
142+
this._handlers.onCharacterSetLeave(node)
143+
}
144+
}
145+
private visitDisjunction(node: Disjunction): void {
146+
if (this._handlers.onDisjunctionEnter) {
147+
this._handlers.onDisjunctionEnter(node)
148+
}
149+
for (const elements of node.alternatives) {
150+
elements.forEach(this.visit, this)
151+
}
152+
if (this._handlers.onDisjunctionLeave) {
153+
this._handlers.onDisjunctionLeave(node)
154+
}
155+
}
156+
private visitFlags(node: Flags): void {
157+
if (this._handlers.onFlagsEnter) {
158+
this._handlers.onFlagsEnter(node)
159+
}
160+
if (this._handlers.onFlagsLeave) {
161+
this._handlers.onFlagsLeave(node)
162+
}
163+
}
164+
private visitGroup(node: Group): void {
165+
if (this._handlers.onGroupEnter) {
166+
this._handlers.onGroupEnter(node)
167+
}
168+
node.elements.forEach(this.visit, this)
169+
if (this._handlers.onGroupLeave) {
170+
this._handlers.onGroupLeave(node)
171+
}
172+
}
173+
private visitPattern(node: Pattern): void {
174+
if (this._handlers.onPatternEnter) {
175+
this._handlers.onPatternEnter(node)
176+
}
177+
node.elements.forEach(this.visit, this)
178+
if (this._handlers.onPatternLeave) {
179+
this._handlers.onPatternLeave(node)
180+
}
181+
}
182+
private visitQuantifier(node: Quantifier): void {
183+
if (this._handlers.onQuantifierEnter) {
184+
this._handlers.onQuantifierEnter(node)
185+
}
186+
this.visit(node.element)
187+
if (this._handlers.onQuantifierLeave) {
188+
this._handlers.onQuantifierLeave(node)
189+
}
190+
}
191+
private visitRegExpLiteral(node: RegExpLiteral): void {
192+
if (this._handlers.onRegExpLiteralEnter) {
193+
this._handlers.onRegExpLiteralEnter(node)
194+
}
195+
this.visitPattern(node.pattern)
196+
this.visitFlags(node.flags)
197+
if (this._handlers.onRegExpLiteralLeave) {
198+
this._handlers.onRegExpLiteralLeave(node)
199+
}
200+
}
201+
}
202+
203+
export namespace RegExpVisitor {
204+
export interface Handlers {
205+
onAssertionEnter?(node: Assertion): void
206+
onAssertionLeave?(node: Assertion): void
207+
onBackreferenceEnter?(node: Backreference): void
208+
onBackreferenceLeave?(node: Backreference): void
209+
onCapturingGroupEnter?(node: CapturingGroup): void
210+
onCapturingGroupLeave?(node: CapturingGroup): void
211+
onCharacterEnter?(node: Character): void
212+
onCharacterLeave?(node: Character): void
213+
onCharacterClassEnter?(node: CharacterClass): void
214+
onCharacterClassLeave?(node: CharacterClass): void
215+
onCharacterClassRangeEnter?(node: CharacterClassRange): void
216+
onCharacterClassRangeLeave?(node: CharacterClassRange): void
217+
onCharacterSetEnter?(node: CharacterSet): void
218+
onCharacterSetLeave?(node: CharacterSet): void
219+
onDisjunctionEnter?(node: Disjunction): void
220+
onDisjunctionLeave?(node: Disjunction): void
221+
onFlagsEnter?(node: Flags): void
222+
onFlagsLeave?(node: Flags): void
223+
onGroupEnter?(node: Group): void
224+
onGroupLeave?(node: Group): void
225+
onPatternEnter?(node: Pattern): void
226+
onPatternLeave?(node: Pattern): void
227+
onQuantifierEnter?(node: Quantifier): void
228+
onQuantifierLeave?(node: Quantifier): void
229+
onRegExpLiteralEnter?(node: RegExpLiteral): void
230+
onRegExpLiteralLeave?(node: RegExpLiteral): void
231+
}
232+
}

test/fixtures/parser/literal.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import assert from "assert"
21
import fs from "fs"
32
import path from "path"
43

0 commit comments

Comments
 (0)