Skip to content

Commit

Permalink
perf(lexer): improve tokenizer efficiency
Browse files Browse the repository at this point in the history
Signed-off-by: Lexus Drumgold <[email protected]>
  • Loading branch information
unicornware committed May 24, 2024
1 parent b64cb0d commit df7b1b6
Show file tree
Hide file tree
Showing 19 changed files with 952 additions and 538 deletions.
11 changes: 6 additions & 5 deletions .dprint.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"!**/typings/**/dist/",
"**/*.patch",
"**/*.snap",
"**/*.ts.txt",
"**/*config.*.timestamp*",
"**/.temp/",
"**/.vercel/",
Expand Down Expand Up @@ -81,9 +82,9 @@
},
"newLineKind": "lf",
"plugins": [
"https://plugins.dprint.dev/typescript-0.88.10.wasm",
"https://plugins.dprint.dev/json-0.19.1.wasm",
"https://plugins.dprint.dev/markdown-0.16.3.wasm",
"https://plugins.dprint.dev/typescript-0.90.5.wasm",
"https://plugins.dprint.dev/json-0.19.2.wasm",
"https://plugins.dprint.dev/markdown-0.17.0.wasm",
"https://plugins.dprint.dev/exec-0.4.4.json@c207bf9b9a4ee1f0ecb75c594f774924baf62e8e53a2ce9d873816a408cecbf7"
],
"typescript": {
Expand All @@ -98,7 +99,7 @@
"constructorType.spaceAfterNewKeyword": true,
"doWhileStatement.spaceAfterWhileKeyword": true,
"enumDeclaration.memberSpacing": "maintain",
"exportDeclaration.forceMultiLine": false,
"exportDeclaration.forceMultiLine": "never",
"exportDeclaration.forceSingleLine": false,
"exportDeclaration.sortNamedExports": "maintain",
"exportDeclaration.spaceSurroundingNamedExports": true,
Expand All @@ -113,7 +114,7 @@
"ifStatement.spaceAfterIfKeyword": true,
"ignoreFileCommentText": "dprint-ignore-file",
"ignoreNodeCommentText": "dprint-ignore",
"importDeclaration.forceMultiLine": false,
"importDeclaration.forceMultiLine": "never",
"importDeclaration.forceSingleLine": false,
"importDeclaration.sortNamedImports": "maintain",
"importDeclaration.spaceSurroundingNamedImports": true,
Expand Down
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
**/node_modules/
**/tsconfig*temp.json
Brewfile
__fixtures__/visit.ts.txt
yarn.lock

# NEGATED PATTERNS
Expand Down
8 changes: 7 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@
"emmet.triggerExpansionOnTab": true,
"eslint.enable": true,
"eslint.lintTask.enable": true,
"eslint.nodePath": "node_modules/.bin/eslint",
"eslint.options": {
"extensions": [
"css",
Expand All @@ -137,6 +136,7 @@
"overrideConfigFile": ".eslintrc.cjs"
},
"eslint.runtime": "node",
"eslint.trace.server": "messages",
"eslint.validate": [
"css",
"github-actions-workflow",
Expand All @@ -154,6 +154,7 @@
"files.associations": {
"*.log": "log",
"*.snap": "jest-snapshot",
"*.ts.txt": "assemblyscript",
".env.zsh": "shellscript",
".markdownlintignore": "ignore",
".npmrc": "ini",
Expand Down Expand Up @@ -287,6 +288,11 @@
"format": "svg",
"icon": "testts"
},
{
"extensions": ["ts.txt"],
"format": "svg",
"icon": "typescript"
},
{
"extensions": ["yarnrc.yml"],
"format": "svg",
Expand Down
299 changes: 299 additions & 0 deletions __fixtures__/visit.ts.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
/**
* @file visit
* @module unist-util-visit/visit
*/

import {
define,
isArray,
isBoolean,
isFalsy,
isFunction,
isNIL,
isNumber,
sift,
type EmptyArray,
type Fn,
type Nullable,
type Optional
} from '@flex-development/tutils'
import type { Index, Test } from '@flex-development/unist-util-types'
import color from '@flex-development/unist-util-visit/color'
import type { Node, Parent } from 'unist'
import { convert, type Check } from 'unist-util-is'
import { CONTINUE, EXIT, SKIP } from './actions'
import type { ActionTuple, Visitor, VisitorResult, Visitors } from './types'
import { nodelike, nodename, parentlike } from './utils'

/**
* Visit nodes, with ancestral information.
*
* This algorithm performs [*depth-first tree traversal*][dft] in
* [*preorder*][preorder] (**NLR**) and/or [*postorder*][postorder] (**LRN**),
* or if `reverse` is given, *reverse preorder* (**NRL**) and/or *reverse
* postorder* (**RLN**). Nodes are handled on [*enter*][enter] during *preorder*
* traversals and on [*exit*][exit] during *postorder* traversals.
*
* Walking the `tree` is an intensive task. Make use of `visitor` return values
* whenever possible. Instead of walking `tree` multiple times, walk it once,
* use [`unist-util-is`][unist-util-is] to check if a node matches, and then
* perform different operations.
*
* You can change `tree`. See {@linkcode Visitor} for more info.
*
* [dft]: https://github.com/syntax-tree/unist#depth-first-traversal
* [enter]: https://github.com/syntax-tree/unist#enter
* [exit]: https://github.com/syntax-tree/unist#exit
* [postorder]: https://github.com/syntax-tree/unist#postorder
* [preorder]: https://github.com/syntax-tree/unist#preorder
* [unist-util-is]: https://github.com/syntax-tree/unist-util-is
*
* @see {@linkcode Node}
* @see {@linkcode Visitors}
*
* @template {Node} [Tree=Node] - Tree to traverse
*
* @param {Tree} tree - Tree to traverse
* @param {Visitor<Tree> | Visitors<Tree>} visitor - A function to handle nodes
* when entering (*preorder*), or an object to handle nodes when entering and
* leaving (*preorder* and *postorder*)
* @param {(boolean | null)?} [reverse] - Traverse in reverse preorder (NRL)
* and/or postorder (RLN) instead of default traversal order(s)
* @return {void} Nothing
*/
function visit<Tree extends Node = Node>(
this: void,
tree: Tree,
visitor: Visitor<Tree> | Visitors<Tree>,
reverse?: boolean | null | undefined
): void

/**
* Visit nodes, with ancestral information.
*
* This algorithm performs [*depth-first tree traversal*][dft] in
* [*preorder*][preorder] (**NLR**) and/or [*postorder*][postorder] (**LRN**),
* or if `reverse` is given, *reverse preorder* (**NRL**) and/or *reverse
* postorder* (**RLN**). Nodes are handled on [*enter*][enter] during *preorder*
* traversals and on [*exit*][exit] during *postorder* traversals.
*
* You can choose which nodes visitor functions handle by passing a
* [`test`][test]. For complex tests, you should test yourself in `visitor`
* instead, as it will be faster and also have improved type information.
*
* Walking the `tree` is an intensive task. Make use of visitor return values
* whenever possible. Instead of walking the `tree` multiple times, walk it
* once, use [`unist-util-is`][unist-util-is] to check if a node matches, and
* then perform different operations.
*
* You can change `tree`. See {@linkcode Visitor} for more info.
*
* [dft]: https://github.com/syntax-tree/unist#depth-first-traversal
* [enter]: https://github.com/syntax-tree/unist#enter
* [exit]: https://github.com/syntax-tree/unist#exit
* [postorder]: https://github.com/syntax-tree/unist#postorder
* [preorder]: https://github.com/syntax-tree/unist#preorder
* [test]: https://github.com/syntax-tree/unist-util-is#test
* [unist-util-is]: https://github.com/syntax-tree/unist-util-is
*
* @see {@linkcode Node}
* @see {@linkcode Test}
* @see {@linkcode Visitors}
*
* @template {Node} [Tree=Node] - Tree to traverse
* @template {Test} [Check=Test] - Visited node test
*
* @param {Tree} tree - Tree to traverse
* @param {Check} test - [`unist-util-is`][unist-util-is]-compatible test
* @param {Visitor<Tree, Check> | Visitors<Tree, Check>} visitor - A function to
* handle nodes passing `test` when entering (*preorder*), or an object to
* handle passing nodes when entering and leaving (*preorder* and *postorder*)
* @param {(boolean | null)?} [reverse] - Traverse in reverse preorder (NRL)
* and/or postorder (RLN) instead of default traversal order(s)
* @return {void} Nothing
*/
function visit<Tree extends Node = Node, Check extends Test = Test>(
this: void,
tree: Tree,
test: Check,
visitor: Visitor<Tree, Check> | Visitors<Tree, Check>,
reverse?: boolean | null
): void

/**
* Visit nodes, with ancestral information.
*
* @see {@linkcode Node}
* @see {@linkcode Test}
* @see {@linkcode Visitor}
* @see {@linkcode Visitors}
*
* @param {Node} tree - Tree to traverse
* @param {Test | Visitor | Visitors} test - Visited node test or `visitor`
* @param {(Visitor | Visitors | boolean | null)?} [visitor] - A function to
* handle entering nodes, an object containing functions to handle entering and
* leaving nodes, or `reverse`
* @param {(boolean | null)?} [reverse] - Traverse in reverse order
* @return {void} Nothing
*/
function visit(
tree: Node,
test: Test | Visitor | Visitors,
visitor?: Visitor | Visitors | boolean | null,
reverse?: boolean | null
): void {
if (isBoolean(visitor) || isNIL(visitor)) {
reverse = visitor
visitor = test
if (isFunction(visitor)) visitor = { enter: visitor }
test = null
} else {
if (isFunction(visitor)) visitor = { enter: visitor }
test = <Test>test
}

/**
* Node checker.
*
* @const {Check} check
*/
const check: Check = convert(<Test>test)

/**
* Default value used to move between child nodes.
*
* @const {number} step
*/
const step: number = reverse ? -1 : 1

/**
* Convert a visitor `result` to an {@linkcode ActionTuple}.
*
* @this {void}
*
* @param {VisitorResult} result - Result returned from visitor function
* @return {ActionTuple} Visitor result as action tuple
*/
function cleanResult(this: void, result: VisitorResult): ActionTuple {
switch (true) {
case isArray(result):
return result
case isNIL(result):
return []
case isNumber(result):
return [CONTINUE, result]
default:
return [result]
}
}

/**
* Build a function to visit a node.
*
* @see {@linkcode ActionTuple}
* @see {@linkcode Index}
* @see {@linkcode Node}
* @see {@linkcode Parent}
*
* @this {void}
*
* @param {Node} node - Found node
* @param {Optional<Index>} index - Index of `node` in `parent.children`
* @param {Optional<Parent>} parent - Parent of `node`
* @param {Parent[]} ancestors - Ancestors of node
* @return {Fn<EmptyArray, Readonly<ActionTuple>>} Visitor function
*/
function factory(
this: void,
node: Node,
index: Optional<Index>,
parent: Optional<Parent>,
ancestors: Parent[]
): Fn<EmptyArray, Readonly<ActionTuple>> {
if (nodelike(node)) {
/**
* Node name.
*
* @const {Nullable<string>} name
*/
const name: Nullable<string> = nodename(node)

// set name of factory visit function to display name for node
define(visit, 'name', {
value: `node(${color(`${node.type}${name ? `<${name}>` : ''}`)})`
})
}

return visit

/**
* Visit `node`.
*
* @see {@linkcode ActionTuple}
*
* @return {Readonly<ActionTuple>} Clean visitor result
*/
function visit(): Readonly<ActionTuple> {
const { enter, leave } = <Visitors>visitor

/**
* Index of current child node.
*
* @var {number} offset
*/
let offset: number = 0

/**
* Clean visitor result.
*
* @var {Readonly<ActionTuple>} result
*/
let result: Readonly<ActionTuple> = cleanResult(null)

// visit node on enter
if (isFalsy(test) || check(node, index, parent)) {
result = cleanResult(enter?.(node, index, parent, ancestors))
if (result[0] === EXIT) return result
}

// visit each child in node.children
if (parentlike(node) && result[0] !== SKIP) {
offset = (reverse ? node.children.length : -1) + step
while (offset > -1 && offset < node.children.length) {
/**
* Child node.
*
* @const {Node} child
*/
const child: Node = node.children[offset]

/**
* Clean visitor result for {@linkcode child}.
*
* @const {Readonly<ActionTuple>} subresult
*/
const subresult: Readonly<ActionTuple> = factory(
child,
offset,
node,
sift([...ancestors, parent])
)()

if (subresult[0] === EXIT) return subresult
offset = isNumber(subresult[1]) ? subresult[1] : offset + step
}
}

// visit node on leave
if (leave && (isFalsy(test) || check(node, index, parent))) {
result = cleanResult(leave(node, index, parent, ancestors))
}

return result
}
}

return void factory(tree, undefined, undefined, [])()
}

export default visit
Loading

0 comments on commit df7b1b6

Please sign in to comment.