Skip to content

Commit c46ae6a

Browse files
authored
perf(parser-adapter-yaml-1-2): use tree-sitter cursor for CST traversal (#2955)
Refs #691
1 parent e6a3d80 commit c46ae6a

File tree

12 files changed

+923
-115
lines changed

12 files changed

+923
-115
lines changed

packages/apidom-parser-adapter-yaml-1-2/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@
3939
"typescript:declaration": "tsc -p declaration.tsconfig.json && rollup -c config/rollup/types.dist.js",
4040
"test": "cross-env NODE_ENV=test BABEL_ENV=cjs mocha",
4141
"perf": "cross-env NODE_ENV=test BABEL_ENV=cjs node ./test/perf/index.cjs",
42-
"perf:parse": "cross-env NODE_ENV=test BABEL_ENV=cjs node ./test/perf/parse.cjs",
4342
"perf:lexical-analysis": "cross-env NODE_ENV=test BABEL_ENV=cjs node ./test/perf/lexical-analysis.cjs",
43+
"perf:parse-syntactic-analysis-indirect": "cross-env NODE_ENV=test BABEL_ENV=cjs node ./test/perf/parse-syntactic-analysis-indirect.cjs",
4444
"prepack": "copyfiles -u 3 ../../LICENSES/* LICENSES && copyfiles -u 2 ../../NOTICE .",
4545
"postpack": "rimraf NOTICE LICENSES"
4646
},

packages/apidom-parser-adapter-yaml-1-2/src/adapter-browser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ParseResultElement } from '@swagger-api/apidom-core';
22

33
import lexicalAnalysis from './lexical-analysis/browser';
4-
import syntacticAnalysis from './syntactic-analysis/index';
4+
import syntacticAnalysis from './syntactic-analysis/indirect/index';
55

66
export { mediaTypes, namespace } from './adapter';
77
export { lexicalAnalysis, syntacticAnalysis };

packages/apidom-parser-adapter-yaml-1-2/src/adapter-node.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ParseResultElement } from '@swagger-api/apidom-core';
22

33
import lexicalAnalysis from './lexical-analysis/node';
4-
import syntacticAnalysis from './syntactic-analysis/index';
4+
import syntacticAnalysis from './syntactic-analysis/indirect/index';
55

66
export { mediaTypes, namespace } from './adapter';
77
export { lexicalAnalysis, syntacticAnalysis };
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { TreeCursor as NodeTreeCursor } from 'tree-sitter';
2+
import { TreeCursor as WebTreeCursor } from 'web-tree-sitter';
3+
4+
import TreeCursorSyntaxNode from './TreeCursorSyntaxNode';
5+
6+
class TreeCursorIterator {
7+
protected readonly cursor;
8+
9+
constructor(cursor: NodeTreeCursor | WebTreeCursor) {
10+
this.cursor = cursor;
11+
}
12+
13+
stream() {
14+
return new TreeCursorSyntaxNode(this.cursor);
15+
}
16+
17+
yaml_directive() {
18+
return new TreeCursorSyntaxNode(this.cursor);
19+
}
20+
21+
tag_directive() {
22+
return new TreeCursorSyntaxNode(this.cursor);
23+
}
24+
25+
reserved_directive() {
26+
return new TreeCursorSyntaxNode(this.cursor);
27+
}
28+
29+
document() {
30+
return new TreeCursorSyntaxNode(this.cursor);
31+
}
32+
33+
block_node() {
34+
return new TreeCursorSyntaxNode(this.cursor).setFieldName(this.cursor);
35+
}
36+
37+
flow_node() {
38+
return new TreeCursorSyntaxNode(this.cursor).setFieldName(this.cursor);
39+
}
40+
41+
block_mapping() {
42+
return new TreeCursorSyntaxNode(this.cursor);
43+
}
44+
45+
block_mapping_pair() {
46+
return new TreeCursorSyntaxNode(this.cursor);
47+
}
48+
49+
flow_mapping() {
50+
return new TreeCursorSyntaxNode(this.cursor);
51+
}
52+
53+
flow_pair() {
54+
return new TreeCursorSyntaxNode(this.cursor);
55+
}
56+
57+
block_sequence() {
58+
return new TreeCursorSyntaxNode(this.cursor);
59+
}
60+
61+
block_sequence_item() {
62+
return new TreeCursorSyntaxNode(this.cursor);
63+
}
64+
65+
flow_sequence() {
66+
return new TreeCursorSyntaxNode(this.cursor);
67+
}
68+
69+
plain_scalar() {
70+
return new TreeCursorSyntaxNode(this.cursor);
71+
}
72+
73+
single_quote_scalar() {
74+
return new TreeCursorSyntaxNode(this.cursor);
75+
}
76+
77+
double_quote_scalar() {
78+
return new TreeCursorSyntaxNode(this.cursor);
79+
}
80+
81+
block_scalar() {
82+
return new TreeCursorSyntaxNode(this.cursor);
83+
}
84+
85+
ERROR() {
86+
return new TreeCursorSyntaxNode(this.cursor).setHasError(this.cursor);
87+
}
88+
89+
public *[Symbol.iterator]() {
90+
let node: TreeCursorSyntaxNode;
91+
92+
if (this.cursor.nodeType in this) {
93+
// @ts-ignore
94+
node = this[this.cursor.nodeType]() as TreeCursorSyntaxNode;
95+
} else {
96+
node = new TreeCursorSyntaxNode(this.cursor);
97+
}
98+
99+
if (this.cursor.gotoFirstChild()) {
100+
const [firstChild] = new TreeCursorIterator(this.cursor);
101+
102+
node.pushChildren(firstChild);
103+
104+
while (this.cursor.gotoNextSibling()) {
105+
const firstChildSiblings = Array.from(new TreeCursorIterator(this.cursor));
106+
node.pushChildren(...firstChildSiblings);
107+
}
108+
109+
node.children.reduce((previousNode: TreeCursorSyntaxNode | undefined, currentNode) => {
110+
currentNode.setPreviousSibling(previousNode);
111+
return currentNode;
112+
}, undefined);
113+
114+
this.cursor.gotoParent();
115+
}
116+
117+
yield node;
118+
}
119+
}
120+
121+
export default TreeCursorIterator;
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { TreeCursor as NodeTreeCursor, Point as NodePoint } from 'tree-sitter';
2+
import { TreeCursor as WebTreeCursor, Point as WebPoint } from 'web-tree-sitter';
3+
4+
class TreeCursorSyntaxNode {
5+
public readonly type: string;
6+
7+
public readonly startPosition: NodePoint | WebPoint;
8+
9+
public readonly endPosition: NodePoint | WebPoint;
10+
11+
public readonly startIndex: number;
12+
13+
public readonly endIndex: number;
14+
15+
public readonly text: string;
16+
17+
public readonly isNamed: boolean;
18+
19+
public readonly isMissing: boolean;
20+
21+
public fieldName: string | undefined;
22+
23+
public hasError = false;
24+
25+
public readonly children: TreeCursorSyntaxNode[] = [];
26+
27+
public previousSibling: TreeCursorSyntaxNode | undefined;
28+
29+
constructor(cursor: NodeTreeCursor | WebTreeCursor) {
30+
this.type = cursor.nodeType;
31+
this.startPosition = cursor.startPosition;
32+
this.endPosition = cursor.endPosition;
33+
this.startIndex = cursor.startIndex;
34+
this.endIndex = cursor.endIndex;
35+
this.text = cursor.nodeText;
36+
this.isNamed = cursor.nodeIsNamed;
37+
this.isMissing = cursor.nodeIsMissing;
38+
}
39+
40+
get keyNode(): TreeCursorSyntaxNode | undefined {
41+
if (this.type === 'flow_pair' || this.type === 'block_mapping_pair') {
42+
return this.children.find((node) => node.fieldName === 'key');
43+
}
44+
return undefined;
45+
}
46+
47+
get valueNode(): TreeCursorSyntaxNode | undefined {
48+
if (this.type === 'flow_pair' || this.type === 'block_mapping_pair') {
49+
return this.children.find((node) => node.fieldName === 'value');
50+
}
51+
return undefined;
52+
}
53+
54+
get tag(): TreeCursorSyntaxNode | undefined {
55+
let { previousSibling } = this;
56+
57+
while (typeof previousSibling !== 'undefined' && previousSibling.type !== 'tag') {
58+
({ previousSibling } = previousSibling);
59+
}
60+
61+
return previousSibling;
62+
}
63+
64+
get anchor(): TreeCursorSyntaxNode | undefined {
65+
let { previousSibling } = this;
66+
67+
while (typeof previousSibling !== 'undefined' && previousSibling.type !== 'anchor') {
68+
({ previousSibling } = previousSibling);
69+
}
70+
71+
return previousSibling;
72+
}
73+
74+
get firstNamedChild(): TreeCursorSyntaxNode | undefined {
75+
return this.children.find((node) => node.isNamed);
76+
}
77+
78+
setFieldName(cursor: NodeTreeCursor | WebTreeCursor) {
79+
if (typeof cursor.currentFieldName === 'function') {
80+
this.fieldName = cursor.currentFieldName();
81+
} else {
82+
this.fieldName = cursor.currentFieldName;
83+
}
84+
return this;
85+
}
86+
87+
setHasError(cursor: NodeTreeCursor | WebTreeCursor) {
88+
if (typeof cursor.currentNode === 'function') {
89+
this.hasError = cursor.currentNode().hasError();
90+
} else {
91+
this.hasError = cursor.currentNode.hasError();
92+
}
93+
return this;
94+
}
95+
96+
setPreviousSibling(previousSibling: TreeCursorSyntaxNode | undefined) {
97+
this.previousSibling = previousSibling;
98+
}
99+
100+
pushChildren(...children: TreeCursorSyntaxNode[]) {
101+
this.children.push(...children);
102+
}
103+
}
104+
105+
export default TreeCursorSyntaxNode;

packages/apidom-parser-adapter-yaml-1-2/src/syntactic-analysis/index.ts renamed to packages/apidom-parser-adapter-yaml-1-2/src/syntactic-analysis/indirect/index.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import YamlAstVisitor, {
99
isNode as isAstNode,
1010
getNodeType as getAstNodeType,
1111
} from './visitors/YamlAstVisitor';
12+
import TreeCursorIterator from '../TreeCursorIterator';
13+
import TreeCursorSyntaxNode from '../TreeCursorSyntaxNode';
1214

1315
type Tree = WebTree | NodeTree;
1416

@@ -18,11 +20,14 @@ type Tree = WebTree | NodeTree;
1820
* Two traversals passes are needed to get from CST to ApiDOM.
1921
*/
2022
const analyze = (cst: Tree, { sourceMap = false } = {}): ParseResultElement => {
23+
const cursor = cst.walk();
24+
const iterator = new TreeCursorIterator(cursor);
25+
const rootNode = [...iterator].at(0) as TreeCursorSyntaxNode;
2126
const cstVisitor = CstVisitor();
2227
const astVisitor = YamlAstVisitor();
2328
const schema = JsonSchema();
2429

25-
const yamlAst = visit(cst.rootNode, cstVisitor, {
30+
const yamlAst = visit(rootNode, cstVisitor, {
2631
// @ts-ignore
2732
keyMap: cstKeyMap,
2833
nodePredicate: isCstNode,
@@ -35,9 +40,7 @@ const analyze = (cst: Tree, { sourceMap = false } = {}): ParseResultElement => {
3540
return visit(yamlAst.rootNode, astVisitor, {
3641
// @ts-ignore
3742
keyMap: astKeyMap,
38-
// @ts-ignore
3943
nodeTypeGetter: getAstNodeType,
40-
// @ts-ignore
4144
nodePredicate: isAstNode,
4245
state: {
4346
sourceMap,

0 commit comments

Comments
 (0)