From 731d1f5d635f469003949fc52a6717dd1e5d35a6 Mon Sep 17 00:00:00 2001 From: Vladimir Gorej Date: Sun, 31 Oct 2021 10:30:50 +0100 Subject: [PATCH] perf(adapter-json): optimize indirect syntactic analysis Refs #691 --- .../apidom-ast/src/json/nodes/JsonArray.ts | 10 +++- .../apidom-ast/src/json/nodes/JsonProperty.ts | 10 +++- .../apidom-ast/src/json/nodes/JsonString.ts | 9 +++- .../apidom-ast/src/json/nodes/predicates.ts | 24 ++++----- packages/apidom-ast/src/predicates.ts | 12 ++--- .../apidom-ast/src/yaml/nodes/predicates.ts | 20 +++---- .../indirect/visitors/CstVisitor.ts | 52 +++++++------------ .../indirect/visitors/JsonAstVisitor.ts | 21 ++++---- .../perf/parse-syntactic-analysis-direct.js | 2 +- .../perf/parse-syntactic-analysis-indirect.js | 2 +- 10 files changed, 84 insertions(+), 78 deletions(-) diff --git a/packages/apidom-ast/src/json/nodes/JsonArray.ts b/packages/apidom-ast/src/json/nodes/JsonArray.ts index bd8a77a2b..1cc76d1a7 100644 --- a/packages/apidom-ast/src/json/nodes/JsonArray.ts +++ b/packages/apidom-ast/src/json/nodes/JsonArray.ts @@ -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'; @@ -14,7 +13,14 @@ const JsonArray: stampit.Stamp = 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, ); }, }, diff --git a/packages/apidom-ast/src/json/nodes/JsonProperty.ts b/packages/apidom-ast/src/json/nodes/JsonProperty.ts index fcd1269d5..5d4ddf86a 100644 --- a/packages/apidom-ast/src/json/nodes/JsonProperty.ts +++ b/packages/apidom-ast/src/json/nodes/JsonProperty.ts @@ -1,5 +1,4 @@ import stampit from 'stampit'; -import { anyPass } from 'ramda'; import JsonNode from './JsonNode'; import JsonKey from './JsonKey'; @@ -33,7 +32,14 @@ const JsonProperty: stampit.Stamp = 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), ); }, }, diff --git a/packages/apidom-ast/src/json/nodes/JsonString.ts b/packages/apidom-ast/src/json/nodes/JsonString.ts index 73d910805..81e08fcf4 100644 --- a/packages/apidom-ast/src/json/nodes/JsonString.ts +++ b/packages/apidom-ast/src/json/nodes/JsonString.ts @@ -1,5 +1,4 @@ import stampit from 'stampit'; -import { either } from 'ramda'; import JsonNode from './JsonNode'; import JsonStringContent from './JsonStringContent'; @@ -16,10 +15,16 @@ const JsonString: stampit.Stamp = 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, '', diff --git a/packages/apidom-ast/src/json/nodes/predicates.ts b/packages/apidom-ast/src/json/nodes/predicates.ts index 24c2b1dc4..ea61737c1 100644 --- a/packages/apidom-ast/src/json/nodes/predicates.ts +++ b/packages/apidom-ast/src/json/nodes/predicates.ts @@ -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'); diff --git a/packages/apidom-ast/src/predicates.ts b/packages/apidom-ast/src/predicates.ts index 266a6a8e3..a086eb9e0 100644 --- a/packages/apidom-ast/src/predicates.ts +++ b/packages/apidom-ast/src/predicates.ts @@ -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'); diff --git a/packages/apidom-ast/src/yaml/nodes/predicates.ts b/packages/apidom-ast/src/yaml/nodes/predicates.ts index 266f3f5e2..7756b9b17 100644 --- a/packages/apidom-ast/src/yaml/nodes/predicates.ts +++ b/packages/apidom-ast/src/yaml/nodes/predicates.ts @@ -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'); diff --git a/packages/apidom-parser-adapter-json/src/syntactic-analysis/indirect/visitors/CstVisitor.ts b/packages/apidom-parser-adapter-json/src/syntactic-analysis/indirect/visitors/CstVisitor.ts index fee86c169..1887ec3a8 100644 --- a/packages/apidom-parser-adapter-json/src/syntactic-analysis/indirect/visitors/CstVisitor.ts +++ b/packages/apidom-parser-adapter-json/src/syntactic-analysis/indirect/visitors/CstVisitor.ts @@ -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 { @@ -14,7 +12,6 @@ import { JsonProperty, JsonString, JsonStringContent, - JsonEscapeSequence, JsonTrue, ParseResult, Position, @@ -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. */ @@ -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(); @@ -104,20 +110,15 @@ const CstVisitor = stampit({ this.pair = function pair(node: SyntaxNode) { const position = toPosition(node); - const children = tail(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) { @@ -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) { diff --git a/packages/apidom-parser-adapter-json/src/syntactic-analysis/indirect/visitors/JsonAstVisitor.ts b/packages/apidom-parser-adapter-json/src/syntactic-analysis/indirect/visitors/JsonAstVisitor.ts index 4e7ecd463..7287d23bb 100644 --- a/packages/apidom-parser-adapter-json/src/syntactic-analysis/indirect/visitors/JsonAstVisitor.ts +++ b/packages/apidom-parser-adapter-json/src/syntactic-analysis/indirect/visitors/JsonAstVisitor.ts @@ -1,5 +1,4 @@ import stampit from 'stampit'; -import { either } from 'ramda'; import { JsonArray, JsonDocument, @@ -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 */ @@ -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; }; diff --git a/packages/apidom-parser-adapter-json/test/perf/parse-syntactic-analysis-direct.js b/packages/apidom-parser-adapter-json/test/perf/parse-syntactic-analysis-direct.js index 1220e9f57..418ad5e04 100644 --- a/packages/apidom-parser-adapter-json/test/perf/parse-syntactic-analysis-direct.js +++ b/packages/apidom-parser-adapter-json/test/perf/parse-syntactic-analysis-direct.js @@ -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' }); diff --git a/packages/apidom-parser-adapter-json/test/perf/parse-syntactic-analysis-indirect.js b/packages/apidom-parser-adapter-json/test/perf/parse-syntactic-analysis-indirect.js index d92d6183a..399411d1a 100644 --- a/packages/apidom-parser-adapter-json/test/perf/parse-syntactic-analysis-indirect.js +++ b/packages/apidom-parser-adapter-json/test/perf/parse-syntactic-analysis-indirect.js @@ -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();