Skip to content

Commit

Permalink
feat: implement YAML CST to AST transformer
Browse files Browse the repository at this point in the history
Refs #1
  • Loading branch information
char0n committed Sep 30, 2020
1 parent 18b8960 commit ee3db2f
Show file tree
Hide file tree
Showing 10 changed files with 324 additions and 10 deletions.
8 changes: 7 additions & 1 deletion apidom/packages/apidom-ast/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export { default as YamlScalar } from './nodes/yaml/YamlScalar';
export { default as YamlSequence } from './nodes/yaml/YamlSequence';
export { default as YamlStream } from './nodes/yaml/YamlStream';
export { default as YamlTag } from './nodes/yaml/YamlTag';
export { default as YamlAnchor } from './nodes/yaml/YamlAnchor';
export {
isAlias as isYamlAlias,
isKeyValuePair as isYamlKeyValuePair,
Expand All @@ -57,8 +58,13 @@ export { default as Error } from './Error';
export { default as ParseResult } from './ParseResult';
// AST traversal related exports
export { getVisitFn, BREAK, visit } from './visitor';
// CST/AST transformers related exports
// JSON CST/AST transformers related exports
export {
transform as transformTreeSitterJsonCST,
keyMap as treeSitterJsonKeyMap,
} from './transformers/tree-sitter-json';
// YAML CST/AST transformers related exports
export {
transform as transformTreeSitterYamlCST,
keyMap as treeSitterYamlKeyMap,
} from './transformers/tree-sitter-yaml';
22 changes: 22 additions & 0 deletions apidom/packages/apidom-ast/src/nodes/yaml/YamlAnchor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import stampit from 'stampit';

import Node from '../../Node';

interface YamlAnchor extends Node {
type: 'anchor';
name: string | null;
}

const YamlAnchor: stampit.Stamp<YamlAnchor> = stampit(Node, {
statics: {
type: 'anchor',
},
props: {
name: null,
},
init({ name = null } = {}) {
this.name = name;
},
});

export default YamlAnchor;
4 changes: 1 addition & 3 deletions apidom/packages/apidom-ast/src/nodes/yaml/YamlCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import stampit from 'stampit';

import YamlNode from './YamlNode';

interface YamlCollection extends YamlNode {
readonly children: Array<unknown>;
}
type YamlCollection = YamlNode;

const YamlCollection: stampit.Stamp<YamlCollection> = stampit(YamlNode, {});

Expand Down
6 changes: 4 additions & 2 deletions apidom/packages/apidom-ast/src/nodes/yaml/YamlNode.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import stampit from 'stampit';

import Node from '../../Node';
import YamlTag from './YamlTag';
import YamlAnchor from './YamlAnchor';
import { YamlStyle, YamlStyleGroup } from './YamlStyle';

interface YamlNode extends Node {
content: unknown | null;
anchor: unknown | null;
tag: unknown | null;
anchor: YamlAnchor | null;
tag: YamlTag | null;
style: YamlStyle;
styleGroup: YamlStyleGroup;
}
Expand Down
8 changes: 8 additions & 0 deletions apidom/packages/apidom-ast/src/nodes/yaml/YamlStream.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import stampit from 'stampit';
import { isArray } from 'ramda-adjunct';

import Node from '../../Node';
import YamlDocument from './YamlDocument';
import { isDocument } from './predicates';

interface YamlStream extends Node {
type: 'stream';
readonly content: Array<YamlDocument>;
children: Array<YamlDocument>;
}

const YamlStream: stampit.Stamp<YamlStream> = stampit(Node, {
statics: {
type: 'stream',
},
methods: {
get content(): Array<YamlDocument> {
return isArray(this.children) ? this.children.filter(isDocument) : [];
},
},
});

export default YamlStream;
4 changes: 2 additions & 2 deletions apidom/packages/apidom-ast/src/nodes/yaml/YamlTag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import stampit from 'stampit';

import Node from '../../Node';

enum YamlNodeKind {
export enum YamlNodeKind {
Scalar = 'Scalar',
Sequence = 'Sequence',
Mapping = 'Mapping',
}

interface YamlTag {
interface YamlTag extends Node {
type: 'tag';
name: string | null;
kind: YamlNodeKind | null;
Expand Down
248 changes: 248 additions & 0 deletions apidom/packages/apidom-ast/src/transformers/tree-sitter-yaml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import stampit from 'stampit';
import { either, flatten, lensProp, over } from 'ramda';
import { isArray, isFunction, isFalse } from 'ramda-adjunct';
import { SyntaxNode, Tree } from 'tree-sitter';

import YamlStream from '../nodes/yaml/YamlStream';
import YamlDocument from '../nodes/yaml/YamlDocument';
import YamlSequence from '../nodes/yaml/YamlSequence';
import YamlMapping from '../nodes/yaml/YamlMapping';
import YamlKeyValuePair from '../nodes/yaml/YamlKeyValuePair';
import YamlTag, { YamlNodeKind } from '../nodes/yaml/YamlTag';
import YamlAnchor from '../nodes/yaml/YamlAnchor';
import YamlScalar from '../nodes/yaml/YamlScalar';
import { YamlStyle, YamlStyleGroup } from '../nodes/yaml/YamlStyle';
import ParseResult from '../ParseResult';
import Position, { Point } from '../Position';
import Literal from '../Literal';
import { isNode, visit } from '../visitor';

export const keyMap = {
stream: ['children'],
document: ['children'],
mapping: ['children'],
keyValuePair: ['children'],
sequence: ['children'],
};

const Visitor = stampit({
init() {
/**
* Private API.
*/

const toPosition = (node: SyntaxNode | null): Position | null => {
if (node === null) {
return null;
}

const start = Point({
row: node.startPosition.row,
column: node.startPosition.column,
char: node.startIndex,
});
const end = Point({
row: node.endPosition.row,
column: node.endPosition.column,
char: node.endIndex,
});

return Position({ start, end });
};

const toTag = (node: SyntaxNode): YamlTag | null => {
let { previousSibling } = node;

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

if (previousSibling === null) {
return null;
}

// eslint-disable-next-line no-nested-ternary
const kind = node.type.endsWith('mapping')
? YamlNodeKind.Mapping
: node.type.endsWith('sequence')
? YamlNodeKind.Sequence
: YamlNodeKind.Scalar;
const position = toPosition(previousSibling);

return YamlTag({ name: previousSibling.text, kind, position });
};

const toAnchor = (node: SyntaxNode): YamlAnchor | null => {
let { previousSibling } = node;

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

if (previousSibling === null) {
return null;
}

return YamlAnchor({ name: previousSibling.text, position: toPosition(previousSibling) });
};

const flattenChildren = over(lensProp('children'), flatten);

/**
* Public API.
*/

this.enter = function enter(node: SyntaxNode) {
// missing anonymous literals from CST transformed into AST literal nodes
// WARNING: be aware that web-tree-sitter and tree-sitter node bindings have inconsistency
// in `SyntaxNode.isNamed` property. web-tree-sitter has it defined as method
// whether tree-sitter node binding has it defined as a boolean property.
// @ts-ignore
if ((isFunction(node.isNamed) && !node.isNamed()) || isFalse(node.isNamed)) {
const position = toPosition(node);
const value = node.type || node.text;
const isMissing = node.isMissing();

return Literal({ value, position, isMissing });
}

return undefined;
};

this.stream = {
enter(node: SyntaxNode) {
const position = toPosition(node);

return YamlStream({
children: node.children,
position,
isMissing: node.isMissing(),
});
},
};

this.document = {
enter(node: SyntaxNode) {
const position = toPosition(node);

return YamlDocument({
children: node.children,
position,
isMissing: node.isMissing(),
});
},
leave(node: YamlDocument) {
return flattenChildren(node);
},
};

this.block_node = {
enter(node: SyntaxNode) {
return node.children;
},
};

this.flow_node = {
enter(node: SyntaxNode) {
return node.children;
},
};

this.tag = {
enter() {
return null;
},
};

this.anchor = {
enter() {
return null;
},
};

this.block_mapping = {
enter(node: SyntaxNode) {
const position = toPosition(node);
const tag = toTag(node);
const anchor = toAnchor(node);

return YamlMapping({
children: node.children,
position,
anchor,
tag,
styleGroup: YamlStyleGroup.Block,
style: YamlStyle.NextLine,
isMissing: node.isMissing(),
});
},
};

this.block_mapping_pair = {
enter(node: SyntaxNode) {
const position = toPosition(node);

return YamlKeyValuePair({
children: node.children,
position,
isMissing: node.isMissing(),
});
},
};

this.keyValuePair = {
leave(node: YamlKeyValuePair) {
return flattenChildren(node);
},
};

this.flow_sequence = {
enter(node: SyntaxNode) {
const position = toPosition(node);
const tag = toTag(node);
const anchor = toAnchor(node);

return YamlSequence({
children: node.children,
position,
anchor,
tag,
styleGroup: YamlStyleGroup.Flow,
style: YamlStyle.Explicit,
});
},
};

this.sequence = {
leave(node: YamlSequence) {
return flattenChildren(node);
},
};

this.plain_scalar = {
enter(node: SyntaxNode) {
const position = toPosition(node);
const tag = toTag(node);
const anchor = toAnchor(node);

return YamlScalar({
content: node.text,
anchor,
tag,
position,
styleGroup: YamlStyleGroup.Flow,
style: YamlStyle.Plain,
});
},
};
},
});

export const transform = (cst: Tree): ParseResult => {
const visitor = Visitor();
const nodePredicate = either(isArray, isNode);
// @ts-ignore
const rootNode = visit(cst.rootNode, visitor, { keyMap, nodePredicate });

return ParseResult({ children: [rootNode] });
};
4 changes: 2 additions & 2 deletions apidom/packages/apidom-ast/src/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ export const getVisitFn = (visitor, type: string, isLeaving: boolean) => {
export const BREAK = {};

// getNodeType :: Node -> String
const getNodeType = prop('type');
export const getNodeType = prop('type');

// isNode :: Node -> Boolean
const isNode = curryN(1, pipe(getNodeType, isString));
export const isNode = curryN(1, pipe(getNodeType, isString));

/* eslint-disable no-continue, no-nested-ternary, no-param-reassign */
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-ignore
import Parser from 'tree-sitter';
import { assert } from 'chai';
// @ts-ignore
Expand Down
Loading

0 comments on commit ee3db2f

Please sign in to comment.