Skip to content

Commit

Permalink
perf(adapter-json): optimize indirect syntactic analysis
Browse files Browse the repository at this point in the history
Refs #691
  • Loading branch information
char0n committed Oct 31, 2021
1 parent 423f2ec commit e6ccf3a
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 78 deletions.
10 changes: 8 additions & 2 deletions packages/apidom-ast/src/json/nodes/JsonArray.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import stampit from 'stampit';
import { anyPass } from 'ramda';

import JsonNode from './JsonNode';
import { isFalse, isTrue, isNull, isNumber, isString, isArray, isObject } from './predicates';
Expand All @@ -14,7 +13,14 @@ const JsonArray: stampit.Stamp<JsonArray> = stampit(JsonNode, {
get items(): unknown[] {
// @ts-ignore
return this.children.filter(
anyPass([isFalse, isTrue, isNull, isNumber, isString, isArray, isObject]),
(node: any) =>
isFalse(node) ||
isTrue(node) ||
isNull(node) ||
isNumber(node) ||
isString(node) ||
isArray(node) ||
isObject,
);
},
},
Expand Down
10 changes: 8 additions & 2 deletions packages/apidom-ast/src/json/nodes/JsonProperty.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import stampit from 'stampit';
import { anyPass } from 'ramda';

import JsonNode from './JsonNode';
import JsonKey from './JsonKey';
Expand Down Expand Up @@ -33,7 +32,14 @@ const JsonProperty: stampit.Stamp<JsonProperty> = stampit(JsonNode, {
get value(): unknown {
// @ts-ignore
return this.children.find(
anyPass([isFalse, isTrue, isNull, isNumber, isString, isArray, isObject]),
(node: any) =>
isFalse(node) ||
isTrue(node) ||
isNull(node) ||
isNumber(node) ||
isString(node) ||
isArray(node) ||
isObject(node),
);
},
},
Expand Down
9 changes: 7 additions & 2 deletions packages/apidom-ast/src/json/nodes/JsonString.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import stampit from 'stampit';
import { either } from 'ramda';

import JsonNode from './JsonNode';
import JsonStringContent from './JsonStringContent';
Expand All @@ -16,10 +15,16 @@ const JsonString: stampit.Stamp<JsonString> = stampit(JsonNode, {
},
methods: {
get value(): string {
// @ts-ignore
if (this.children.length === 1) {
// @ts-ignore
return this.children[0].value;
}

return (
this.children
// @ts-ignore
.filter(either(isStringContent, isEscapeSequence))
.filter((node: any) => isStringContent(node) || isEscapeSequence(node))
.reduce(
(acc: string, cur: JsonStringContent | JsonEscapeSequence): string => acc + cur.value,
'',
Expand Down
24 changes: 12 additions & 12 deletions packages/apidom-ast/src/json/nodes/predicates.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import { isNodeType } from '../../predicates';

export const isDocument = isNodeType('document');
export const isDocument = isNodeType.bind(undefined, 'document');

export const isString = isNodeType('string');
export const isString = isNodeType.bind(undefined, 'string');

export const isFalse = isNodeType('false');
export const isFalse = isNodeType.bind(undefined, 'false');

export const isTrue = isNodeType('true');
export const isTrue = isNodeType.bind(undefined, 'true');

export const isNull = isNodeType('null');
export const isNull = isNodeType.bind(undefined, 'null');

export const isNumber = isNodeType('number');
export const isNumber = isNodeType.bind(undefined, 'number');

export const isArray = isNodeType('array');
export const isArray = isNodeType.bind(undefined, 'array');

export const isObject = isNodeType('object');
export const isObject = isNodeType.bind(undefined, 'object');

export const isStringContent = isNodeType('stringContent');
export const isStringContent = isNodeType.bind(undefined, 'stringContent');

export const isEscapeSequence = isNodeType('escapeSequence');
export const isEscapeSequence = isNodeType.bind(undefined, 'escapeSequence');

export const isProperty = isNodeType('property');
export const isProperty = isNodeType.bind(undefined, 'property');

export const isKey = isNodeType('key');
export const isKey = isNodeType.bind(undefined, 'key');
12 changes: 5 additions & 7 deletions packages/apidom-ast/src/predicates.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { pathEq } from 'ramda';
export const isNodeType = (type: string, node: any): boolean => node?.type === type;

export const isNodeType = pathEq(['type']);
export const isLiteral = isNodeType.bind(undefined, 'literal');

export const isLiteral = isNodeType('literal');
export const isPosition = isNodeType.bind(undefined, 'position');

export const isPosition = isNodeType('position');
export const isPoint = isNodeType.bind(undefined, 'point');

export const isPoint = isNodeType('point');

export const isParseResult = isNodeType('parseResult');
export const isParseResult = isNodeType.bind(undefined, 'parseResult');
20 changes: 10 additions & 10 deletions packages/apidom-ast/src/yaml/nodes/predicates.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { isNodeType } from '../../predicates';

export const isStream = isNodeType('stream');
export const isStream = isNodeType.bind(undefined, 'stream');

export const isDocument = isNodeType('document');
export const isDocument = isNodeType.bind(undefined, 'document');

export const isMapping = isNodeType('mapping');
export const isMapping = isNodeType.bind(undefined, 'mapping');

export const isSequence = isNodeType('sequence');
export const isSequence = isNodeType.bind(undefined, 'sequence');

export const isKeyValuePair = isNodeType('keyValuePair');
export const isKeyValuePair = isNodeType.bind(undefined, 'keyValuePair');

export const isTag = isNodeType('tag');
export const isTag = isNodeType.bind(undefined, 'tag');

export const isScalar = isNodeType('scalar');
export const isScalar = isNodeType.bind(undefined, 'scalar');

export const isAlias = isNodeType('alias');
export const isAlias = isNodeType.bind(undefined, 'alias');

export const isDirective = isNodeType('directive');
export const isDirective = isNodeType.bind(undefined, 'directive');

export const isComment = isNodeType('comment');
export const isComment = isNodeType.bind(undefined, 'comment');
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import stampit from 'stampit';
import { tail } from 'ramda';
import { isFalse, isFunction } from 'ramda-adjunct';
import { SyntaxNode as NodeSyntaxNode } from 'tree-sitter';
import { SyntaxNode as WebSyntaxNode } from 'web-tree-sitter';
import {
Expand All @@ -14,7 +12,6 @@ import {
JsonProperty,
JsonString,
JsonStringContent,
JsonEscapeSequence,
JsonTrue,
ParseResult,
Position,
Expand Down Expand Up @@ -60,6 +57,15 @@ const CstVisitor = stampit({
return Position({ start, end });
};

const getFieldFromNode = (fieldName: string, node: SyntaxNode): SyntaxNode | null => {
return `${fieldName}Node` in node
? // @ts-ignore
node[`${fieldName}Node`]
: 'childForFieldName' in node
? node.childForFieldName?.(fieldName)
: null;
};

/**
* Public API.
*/
Expand All @@ -70,7 +76,7 @@ const CstVisitor = stampit({
// 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)) {
if ((typeof node.isNamed === 'function' && !node.isNamed()) || node.isNamed === false) {
const position = toPosition(node);
const value = node.type || node.text;
const isMissing = node.isMissing();
Expand Down Expand Up @@ -104,20 +110,15 @@ const CstVisitor = stampit({

this.pair = function pair(node: SyntaxNode) {
const position = toPosition(node);
const children = tail<SyntaxNode | JsonKey>(node.children);
const keyValuePairNodeCount = 3;

if (node.childCount >= keyValuePairNodeCount && node.firstChild !== null) {
const key = JsonKey({
children: node.firstChild.children,
position: toPosition(node.firstChild),
isMissing: node.firstChild.isMissing(),
});

children.unshift(key);
}
const children = node.children.slice(1);
const keyNode = getFieldFromNode('key', node);
const key = JsonKey({
children: keyNode?.children || [],
position: toPosition(keyNode),
isMissing: keyNode?.isMissing() || false,
});

return JsonProperty({ children, position, isMissing: node.isMissing() });
return JsonProperty({ children: [key, ...children], position, isMissing: node.isMissing() });
};

this.array = function array(node: SyntaxNode) {
Expand All @@ -128,22 +129,9 @@ const CstVisitor = stampit({

this.string = function string(node: SyntaxNode) {
const position = toPosition(node);
const content = JsonStringContent({ value: node.text.slice(1, -1) });

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

// eslint-disable-next-line @typescript-eslint/naming-convention
this.string_content = function string_content(node: SyntaxNode) {
const position = toPosition(node);

return JsonStringContent({ value: node.text, position, isMissing: node.isMissing() });
};

// eslint-disable-next-line @typescript-eslint/naming-convention
this.escape_sequence = function escape_sequence(node: SyntaxNode) {
const position = toPosition(node);

return JsonEscapeSequence({ value: node.text, position, isMissing: node.isMissing() });
return JsonString({ children: [content], position, isMissing: node.isMissing() });
};

this.number = function number(node: SyntaxNode) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import stampit from 'stampit';
import { either } from 'ramda';
import {
JsonArray,
JsonDocument,
Expand Down Expand Up @@ -63,8 +62,7 @@ export const getNodeType = (node: any) => {
return getCSTNodeType(node);
};

// @ts-ignore
export const isNode = either(isElement, isCSTNode);
export const isNode = (element: any) => isElement(element) || isCSTNode(element);

/* eslint-disable no-underscore-dangle */

Expand Down Expand Up @@ -139,12 +137,17 @@ const JsonAstVisitor = stampit({
element.content.value = node.value;
maybeAddSourceMap(node, element);

// process possible errors here that may be present in property node children as we're using direct field access
node.children
.filter((child: any) => child.type === 'error')
.forEach((errorNode: any) => {
this.error(errorNode, node, [], [node]);
});
/**
* Process possible errors here that may be present in pair node children as we're using direct field access.
* There are usually 3 children here found: "key", ":", "value".
*/
if (node.children.length > 3) {
node.children
.filter((child: any) => child.type === 'error')
.forEach((errorNode: any) => {
this.error(errorNode, node, [], [node]);
});
}

return element;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const source = fs.readFileSync(fixturePath).toString();
const options = {
name: 'parse-syntactic-analysis-direct',
defer: true,
minSamples: 200,
minSamples: 600,
expected: '45.50 ops/sec ±1.23% (669 runs sampled)',
async fn(deferred) {
await parse(source, { syntacticAnalysis: 'direct' });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const options = {
name: 'parse-syntactic-analysis-indirect',
defer: true,
minSamples: 600,
expected: '4.45 ops/sec ±0.92% (621 runs sampled)',
expected: '11.07 ops/sec ±0.90% (650 runs sampled)',
async fn(deferred) {
await parse(source, { syntacticAnalysis: 'indirect' });
deferred.resolve();
Expand Down

0 comments on commit e6ccf3a

Please sign in to comment.