Skip to content

perf(parser-adapter-yaml-1-2): use tree-sitter cursor for CST traversal #2955

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/apidom-parser-adapter-yaml-1-2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@
"typescript:declaration": "tsc -p declaration.tsconfig.json && rollup -c config/rollup/types.dist.js",
"test": "cross-env NODE_ENV=test BABEL_ENV=cjs mocha",
"perf": "cross-env NODE_ENV=test BABEL_ENV=cjs node ./test/perf/index.cjs",
"perf:parse": "cross-env NODE_ENV=test BABEL_ENV=cjs node ./test/perf/parse.cjs",
"perf:lexical-analysis": "cross-env NODE_ENV=test BABEL_ENV=cjs node ./test/perf/lexical-analysis.cjs",
"perf:parse-syntactic-analysis-indirect": "cross-env NODE_ENV=test BABEL_ENV=cjs node ./test/perf/parse-syntactic-analysis-indirect.cjs",
"prepack": "copyfiles -u 3 ../../LICENSES/* LICENSES && copyfiles -u 2 ../../NOTICE .",
"postpack": "rimraf NOTICE LICENSES"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ParseResultElement } from '@swagger-api/apidom-core';

import lexicalAnalysis from './lexical-analysis/browser';
import syntacticAnalysis from './syntactic-analysis/index';
import syntacticAnalysis from './syntactic-analysis/indirect/index';

export { mediaTypes, namespace } from './adapter';
export { lexicalAnalysis, syntacticAnalysis };
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ParseResultElement } from '@swagger-api/apidom-core';

import lexicalAnalysis from './lexical-analysis/node';
import syntacticAnalysis from './syntactic-analysis/index';
import syntacticAnalysis from './syntactic-analysis/indirect/index';

export { mediaTypes, namespace } from './adapter';
export { lexicalAnalysis, syntacticAnalysis };
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { TreeCursor as NodeTreeCursor } from 'tree-sitter';
import { TreeCursor as WebTreeCursor } from 'web-tree-sitter';

import TreeCursorSyntaxNode from './TreeCursorSyntaxNode';

class TreeCursorIterator {
protected readonly cursor;

constructor(cursor: NodeTreeCursor | WebTreeCursor) {
this.cursor = cursor;
}

stream() {
return new TreeCursorSyntaxNode(this.cursor);
}

yaml_directive() {
return new TreeCursorSyntaxNode(this.cursor);
}

tag_directive() {
return new TreeCursorSyntaxNode(this.cursor);
}

reserved_directive() {
return new TreeCursorSyntaxNode(this.cursor);
}

document() {
return new TreeCursorSyntaxNode(this.cursor);
}

block_node() {
return new TreeCursorSyntaxNode(this.cursor).setFieldName(this.cursor);
}

flow_node() {
return new TreeCursorSyntaxNode(this.cursor).setFieldName(this.cursor);
}

block_mapping() {
return new TreeCursorSyntaxNode(this.cursor);
}

block_mapping_pair() {
return new TreeCursorSyntaxNode(this.cursor);
}

flow_mapping() {
return new TreeCursorSyntaxNode(this.cursor);
}

flow_pair() {
return new TreeCursorSyntaxNode(this.cursor);
}

block_sequence() {
return new TreeCursorSyntaxNode(this.cursor);
}

block_sequence_item() {
return new TreeCursorSyntaxNode(this.cursor);
}

flow_sequence() {
return new TreeCursorSyntaxNode(this.cursor);
}

plain_scalar() {
return new TreeCursorSyntaxNode(this.cursor);
}

single_quote_scalar() {
return new TreeCursorSyntaxNode(this.cursor);
}

double_quote_scalar() {
return new TreeCursorSyntaxNode(this.cursor);
}

block_scalar() {
return new TreeCursorSyntaxNode(this.cursor);
}

ERROR() {
return new TreeCursorSyntaxNode(this.cursor).setHasError(this.cursor);
}

public *[Symbol.iterator]() {
let node: TreeCursorSyntaxNode;

if (this.cursor.nodeType in this) {
// @ts-ignore
node = this[this.cursor.nodeType]() as TreeCursorSyntaxNode;
} else {
node = new TreeCursorSyntaxNode(this.cursor);
}

if (this.cursor.gotoFirstChild()) {
const [firstChild] = new TreeCursorIterator(this.cursor);

node.pushChildren(firstChild);

while (this.cursor.gotoNextSibling()) {
const firstChildSiblings = Array.from(new TreeCursorIterator(this.cursor));
node.pushChildren(...firstChildSiblings);
}

node.children.reduce((previousNode: TreeCursorSyntaxNode | undefined, currentNode) => {
currentNode.setPreviousSibling(previousNode);
return currentNode;
}, undefined);

this.cursor.gotoParent();
}

yield node;
}
}

export default TreeCursorIterator;
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { TreeCursor as NodeTreeCursor, Point as NodePoint } from 'tree-sitter';
import { TreeCursor as WebTreeCursor, Point as WebPoint } from 'web-tree-sitter';

class TreeCursorSyntaxNode {
public readonly type: string;

public readonly startPosition: NodePoint | WebPoint;

public readonly endPosition: NodePoint | WebPoint;

public readonly startIndex: number;

public readonly endIndex: number;

public readonly text: string;

public readonly isNamed: boolean;

public readonly isMissing: boolean;

public fieldName: string | undefined;

public hasError = false;

public readonly children: TreeCursorSyntaxNode[] = [];

public previousSibling: TreeCursorSyntaxNode | undefined;

constructor(cursor: NodeTreeCursor | WebTreeCursor) {
this.type = cursor.nodeType;
this.startPosition = cursor.startPosition;
this.endPosition = cursor.endPosition;
this.startIndex = cursor.startIndex;
this.endIndex = cursor.endIndex;
this.text = cursor.nodeText;
this.isNamed = cursor.nodeIsNamed;
this.isMissing = cursor.nodeIsMissing;
}

get keyNode(): TreeCursorSyntaxNode | undefined {
if (this.type === 'flow_pair' || this.type === 'block_mapping_pair') {
return this.children.find((node) => node.fieldName === 'key');
}
return undefined;
}

get valueNode(): TreeCursorSyntaxNode | undefined {
if (this.type === 'flow_pair' || this.type === 'block_mapping_pair') {
return this.children.find((node) => node.fieldName === 'value');
}
return undefined;
}

get tag(): TreeCursorSyntaxNode | undefined {
let { previousSibling } = this;

while (typeof previousSibling !== 'undefined' && previousSibling.type !== 'tag') {
({ previousSibling } = previousSibling);
}

return previousSibling;
}

get anchor(): TreeCursorSyntaxNode | undefined {
let { previousSibling } = this;

while (typeof previousSibling !== 'undefined' && previousSibling.type !== 'anchor') {
({ previousSibling } = previousSibling);
}

return previousSibling;
}

get firstNamedChild(): TreeCursorSyntaxNode | undefined {
return this.children.find((node) => node.isNamed);
}

setFieldName(cursor: NodeTreeCursor | WebTreeCursor) {
if (typeof cursor.currentFieldName === 'function') {
this.fieldName = cursor.currentFieldName();
} else {
this.fieldName = cursor.currentFieldName;
}
return this;
}

setHasError(cursor: NodeTreeCursor | WebTreeCursor) {
if (typeof cursor.currentNode === 'function') {
this.hasError = cursor.currentNode().hasError();
} else {
this.hasError = cursor.currentNode.hasError();
}
return this;
}

setPreviousSibling(previousSibling: TreeCursorSyntaxNode | undefined) {
this.previousSibling = previousSibling;
}

pushChildren(...children: TreeCursorSyntaxNode[]) {
this.children.push(...children);
}
}

export default TreeCursorSyntaxNode;
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import YamlAstVisitor, {
isNode as isAstNode,
getNodeType as getAstNodeType,
} from './visitors/YamlAstVisitor';
import TreeCursorIterator from '../TreeCursorIterator';
import TreeCursorSyntaxNode from '../TreeCursorSyntaxNode';

type Tree = WebTree | NodeTree;

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

const yamlAst = visit(cst.rootNode, cstVisitor, {
const yamlAst = visit(rootNode, cstVisitor, {
// @ts-ignore
keyMap: cstKeyMap,
nodePredicate: isCstNode,
Expand All @@ -35,9 +40,7 @@ const analyze = (cst: Tree, { sourceMap = false } = {}): ParseResultElement => {
return visit(yamlAst.rootNode, astVisitor, {
// @ts-ignore
keyMap: astKeyMap,
// @ts-ignore
nodeTypeGetter: getAstNodeType,
// @ts-ignore
nodePredicate: isAstNode,
state: {
sourceMap,
Expand Down
Loading