pax_global_header00006660000000000000000000000064145514575500014525gustar00rootroot0000000000000052 comment=76f22406bdf1606b2c2f634ae7d588ce4310d197 common-1.2.1/000077500000000000000000000000001455145755000130165ustar00rootroot00000000000000common-1.2.1/.gitignore000066400000000000000000000000651455145755000150070ustar00rootroot00000000000000/node_modules/ /dist/* /test/*.js .tern-* .rpt2_cachecommon-1.2.1/.mocharc.cjs000066400000000000000000000001551455145755000152120ustar00rootroot00000000000000module.exports = { extension: ["ts"], spec: ["test/test-*.ts"], loader: "ts-node/esm/transpile-only" } common-1.2.1/.npmignore000066400000000000000000000000161455145755000150120ustar00rootroot00000000000000/node_modules common-1.2.1/CHANGELOG.md000066400000000000000000000314601455145755000146330ustar00rootroot00000000000000## 1.2.1 (2024-01-16) ### Bug fixes Fix a bug where `getChild` and `getChildren` would, if the first and second arguments matched the same node, return incorrect results. ## 1.2.0 (2023-12-28) ### New features The new `NodeProp.isolate` can be used by parser to signal that a given node should be treated as isolated when it comes to bidirectional text rendering. ## 1.1.2 (2023-12-07) ### Bug fixes Fix a crash that could happen in mixed-language parsing when mounting a tree to a zero-length node. ## 1.1.1 (2023-11-10) ### Bug fixes Fix a bug where `resolveStack` could sometimes yield the same node multiple times. Allow mixed-parsing trees to be mounted for zero-length nodes. Fix a bug in mixed-language parsing that could some parts of an inner language to be missed when incrementally reparsing from a stopped parse. In `Tree.build`, when given an extremely deeply nested tree structure, flatten it instead of overflowing the stack. ## 1.1.0 (2023-09-19) ### New features The new `Tree.resolveStack` method returns an iterator through the nodes covering a position, including those from overlays not active at that point. ## 1.0.4 (2023-08-17) ### Bug fixes Make the package usable in TypeScript with node16/nodenext resolution. ## 1.0.3 (2023-06-02) ### Bug fixes `Tree.iterate` now properly includes anonymous nodes when `IterMode.IncludeAnonymous` is enabled. ## 1.0.2 (2022-11-23) ### Bug fixes Fix a bug in mixed parsing that would sometimes produce invalid trees. ## 1.0.1 (2022-08-30) ### Bug fixes Fix a bug that could cause incremental parsing to incorrectly reuse nodes inside nested mixed parses. ## 1.0.0 (2022-06-06) ### New features First stable version. ## 0.16.1 (2022-06-01) ### Bug fixes Declare `matchContext` as a property of `SytaxNodeRef`. ## 0.16.0 (2022-04-20) ### Breaking changes The mixed parsing interface now passes `SyntaxNodeRef` values instead of `TreeCursor` instances around. Creating a cursor at a given position is now done with `Tree.cursorAt`, not `cursor`. Getting a cursor from a syntax node is now done with a method (`cursor`) rather than a getter. `Tree.fullCursor` was removed and replaced by the `IncludeAnonymous` iteration mode. The optional arguments to `enter` tree traversal methods were replaced with a single `mode` argument. `Tree.iterate` now passes node objects, rather than type/from/to as separate arguments, to its callback functions. ### New features The new `SyntaxNodeRef` type provides an interface shared by `SyntaxNode` and `TreeCursor`. `TreeCursor` instances now how have an `iterate` method that calls a function for each descendant of the current node. The new `matchContext` method on `SyntaxNode` and `TreeCursor` provides a convenient way to match the names of direct parent nodes. Cursors can now be passed flags to control their behavior. ## 0.15.12 (2022-03-18) ### Bug fixes Work around a TypeScript issue that caused it to infer return type `any` for `resolve` and `resolveInner`. Fix a bug in incremental mixed-language parsing where it could incorrectly add a parse range twice, causing a crash in the inner parser. ## 0.15.11 (2021-12-16) ### Bug fixes Fix a bug where nested parsing would sometimes corrupt the length of parent nodes around the nesting point. ## 0.15.10 (2021-11-30) ### New features `SyntaxNode` now has a `resolveInner` method (analogous to `Tree.resolveInner`). ## 0.15.9 (2021-11-24) ### Bug fixes Full tree cursors no longer automatically enter mounted subtrees. Fix a bug where a nested parser would not re-parse inner sections when given fragments produced by a parse that finished the outer tree but was stopped before the inner trees were done. ## 0.15.8 (2021-11-10) ### Bug fixes Fix a bug that could cause incorrectly structured nodes to be created for repeat rules, breaking incremental parsing using the resulting tree. ## 0.15.7 (2021-10-05) ### Bug fixes Fix an issue in `parseMixed` where parses nested two or more levels deep would use the wrong document offsets. ## 0.15.6 (2021-09-30) ### Bug fixes Fix a null-dereference crash in mixed-language parsing. ## 0.15.5 (2021-09-09) ### New features Syntax node objects now have a method `enterUnfinishedNodesBefore` to scan down the tree for nodes that were broken off directly in front of a given position (which can provide a more accurate context that just resolving the position). ## 0.15.4 (2021-08-31) ### Bug fixes `parseMixed` will now scan children not covered by the ranges of an eagerly computed overlay for further nesting. ## 0.15.3 (2021-08-12) ### Bug fixes Fix an issue where `parseMixed` could create overlay mounts with zero ranges, which were useless and confused CodeMirror's highlighter. ## 0.15.2 (2021-08-12) ### Bug fixes Fix a bug that would cause `enter` to return incorrect results when called entering children in a buffer with . ## 0.15.1 (2021-08-12) ### Bug fixes Fix a bug where `parseMixed` could crash by dereferencing null. ## 0.15.0 (2021-08-11) ### Breaking changes The module name has changed from `lezer-tree` to `@lezer/common`. `TreeBuffer`s no longer accept a node type. `Tree.balance` no longer takes a buffer size as argument. `NodeProp.string`, `NodeProp.number`, and `NodeProp.flag` have been removed (the thing they provided is trivial to write by hand). A node's context hash is now stored in the `NodeProp.contextHash` prop. Reused nodes passed to `Tree.build` must now be `Tree` instances (not tree buffers). `Parser` is now an abstract class that all parser implementations must extend, implementing the `createParse` method. The `PartialParse` interface changed to make multi-pass parsers possible. `Parser.startParse` now takes different arguments `(input, fragments, ranges)` instead of `(input, startPos, context)`. The `Input` interface has changed (to become chunk-based and more low-level). A single `Input` object is now shared between outer and inner parses. `stringInput` is no longer exported (`Parser` methods will automatically wrap strings when appropriate). ### Bug fixes Fix a bug in `TreeFragment.applyChanges` that prevented some valid reuse of syntax nodes. Fix a bug where reused nodes could incorrectly be dropped by `Tree.build`. ### New features Node props can now be per-node, in which case they are stored on `Tree` instances rather than `NodeType`s. Tree nodes can now be replaced with other trees through `NodeProp.mountedTree`. `Tree.resolveInner` can now be used to resolve into overlay trees. `SyntaxNode` objects now have a `toTree` method to convert them to a stand-alone tree. `Tree.balance` now accepts a helper function to create the inner nodes. Tree cursors' `next`/`prev` methods now take an `enter` argument to control whether they enter the current node. `SyntaxNode` and `TreeCursor` now have an `enter` method to directly enter the child at the given position (if any). `Tree.iterate` callbacks now get an extra argument that allows them to create a `SyntaxNode` for the current node. The parsing interface now supports parsing specific, non-contiguous ranges of the input in a single parse. The module now exports a `parseMixed` helper function for creating mixed-language parsers. ## 0.13.2 (2021-02-17) ### New features Add support for context tracking. ## 0.13.1 (2021-02-11) ### Bug fixes Fix a bug where building a tree from a buffer would go wrong for repeat nodes whose children were all repeat nodes of the same type. ## 0.13.0 (2020-12-04) ### Breaking changes `NodeType.isRepeated` is now called `isAnonymous`, which more accurately describes what it means. `NodeGroup` has been renamed to `NodeSet` to avoid confusion with `NodeProp.group`. The `applyChanges` method on trees is no longer supported (`TreeFragment` is now used to track reusable content). Trees no longer have `cut` and `append` methods. ### New features It is now possible to pass a node ID to `SyntaxNode.getChild`/`getChildren` and `NodeType.is`. Allow specifying a tree length in Tree.build `Tree.build` now allows you to specify the length of the resulting tree. `Tree.fullCursor()` can now be used to get a cursor that includes anonymous nodes, rather than skipping them. Introduces `NodeType.define` to define node types. The new `TreeFragment` type is used to manage reusable subtrees for incremental parsing. `Tree.build` now accepts a `start` option indicating the start offset of the tree. The `Input` type, which used to be `InputStream` in the lezer package, is now exported from this package. This package now exports a `PartialParse` interface, which describes the interface used, for example, as return type from `Parser.startParse`. ## 0.12.3 (2020-11-02) ### New features Make `NodePropSource` a function type. ## 0.12.2 (2020-10-28) ### Bug fixes Fix a bug that made `SyntaxNode.prevSibling` fail in most cases when the node is part of a buffer. ## 0.12.1 (2020-10-26) ### Bug fixes Fix issue where using `Tree.append` with an empty tree as argument would return a tree with a nonsensical `length` property. ## 0.12.0 (2020-10-23) ### Breaking changes `Tree.iterate` no longer allows returning from inside the iteration (use cursors directly for that kind of use cases). `Subtree` has been renamed to `SyntaxNode` and narrowed in scope a little. The `top`, `skipped`, and `error` node props no longer exist. ### New features The package now offers a `TreeCursor` abstraction, which can be used for both regular iteration and for custom traversal of a tree. `SyntaxNode` instances have `nextSibling`/`prevSibling` getters that allow more direct navigation through the tree. Node types now expose `isTop`, `isSkipped`, `isError`, and `isRepeated` properties that indicate special status. Adds `NodeProp.group` to assign group names to node types. Syntax nodes now have helper functions `getChild` and `getChildren` to retrieve direct child nodes by type or group. `NodeType.match` (and thus `NodeProp.add`) now allows types to be targeted by group name. Node types have a new `is` method for checking whether their name or one of their groups matches a given string. ## 0.11.1 (2020-09-26) ### Bug fixes Fix lezer depencency versions ## 0.11.0 (2020-09-26) ### Breaking changes Adjust to new output format of repeat rules. ## 0.10.0 (2020-08-07) ### Breaking changes No longer list internal properties in the type definitions. ## 0.9.0 (2020-06-08) ### Breaking changes Drop `NodeProp.delim` in favor of `NodeProp.openedBy`/`closedBy`. ## 0.8.4 (2020-04-01) ### Bug fixes Make the package load as an ES module on node ## 0.8.3 (2020-02-28) ### New features The package now provides an ES6 module. ## 0.8.2 (2020-02-26) ### Bug fixes Fix a bug that caused `applyChanges` to include parts of the old tree that weren't safe to reuse. ## 0.8.1 (2020-02-14) ### Bug fixes Fix bug that would cause tree balancing of deep trees to produce corrupt output. ## 0.8.0 (2020-02-03) ### New features Bump version along with the rest of the lezer packages. ## 0.7.1 (2020-01-23) ### Bug fixes In `applyChanges`, make sure the tree is collapsed all the way to the nearest non-error node next to the change. ## 0.7.0 (2020-01-20) ### Bug fixes Fix a bug that prevented balancing of repeat nodes when there were skipped nodes present between the repeated elements (which ruined the efficiency of incremental parses). ### New features `TreeBuffer` objects now have an `iterate` function. Buffers can optionally be tagged with an (unnamed) node type to allow reusing them in an incremental parse without wrapping them in a tree. ### Breaking changes `Tree.build` now takes its arguments wrapped in an object. It also expects the buffer content to conform to from lezer 0.7.0's representation of repeated productions. The `repeated` node prop was removed (the parser generator now encodes repetition in the type ids). ## 0.5.1 (2019-10-22) ### New features `NodeProp.add` now also allows a selector object to be passed. ## 0.5.0 (2019-10-22) ### New features Adds `NodeProp.top`, which flags a grammar's outer node type. ### Breaking changes Drops the `NodeProp.lang` prop (superseded by `top`). ## 0.4.0 (2019-09-10) ### Bug fixes Export `BufferCursor` again, which was accidentally removed from the exports in 0.3.0. ### Breaking changes The `iterate` method now takes an object instead of separate parameters. ## 0.3.0 (2019-08-22) ### New features Introduces node props. Node types are now objects holding a name, id, and set of props. ### Breaking changes Tags are gone again, nodes have plain string names. ## 0.2.0 (2019-08-02) ### Bug fixes Fix incorrect node length calculation in `Tree.build`. ### New features Tree nodes are now identified with tags. New `Tag` data structure to represent node tags. ### Breaking changes Drop support for grammar ids and node types. ## 0.1.1 (2019-07-09) ### Bug Fixes Actually include the .d.ts file in the published package. ## 0.1.0 (2019-07-09) ### New Features First documented release. common-1.2.1/LICENSE000066400000000000000000000021311455145755000140200ustar00rootroot00000000000000MIT License Copyright (C) 2018 by Marijn Haverbeke and others Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. common-1.2.1/README.md000066400000000000000000000011701455145755000142740ustar00rootroot00000000000000# @lezer/common [ [**WEBSITE**](http://lezer.codemirror.net) | [**ISSUES**](https://github.com/lezer-parser/lezer/issues) | [**FORUM**](https://discuss.codemirror.net/c/lezer) | [**CHANGELOG**](https://github.com/lezer-parser/common/blob/master/CHANGELOG.md) ] [Lezer](https://lezer.codemirror.net/) is an incremental parser system intended for use in an editor or similar system. @lezer/common provides the syntax tree data structure and parser abstractions for Lezer parsers. Its programming interface is documented on [the website](https://lezer.codemirror.net/docs/ref/#common). This code is licensed under an MIT license. common-1.2.1/build.js000066400000000000000000000005551455145755000144600ustar00rootroot00000000000000import {build, watch} from "@marijn/buildtool" import {fileURLToPath} from "url" import {dirname, join} from "path" let tsOptions = { lib: ["es5", "es6"], target: "es6" } let main = join(dirname(fileURLToPath(import.meta.url)), "src", "index.ts") if (process.argv.includes("--watch")) { watch([main], [], {tsOptions}) } else { build(main, {tsOptions}) } common-1.2.1/package.json000066400000000000000000000014161455145755000153060ustar00rootroot00000000000000{ "name": "@lezer/common", "version": "1.2.1", "description": "Syntax tree data structure and parser interfaces for the lezer parser", "main": "dist/index.cjs", "type": "module", "exports": { "import": "./dist/index.js", "require": "./dist/index.cjs" }, "module": "dist/index.js", "types": "dist/index.d.ts", "author": "Marijn Haverbeke ", "license": "MIT", "devDependencies": { "ist": "^1.1.1", "@marijn/buildtool": "^0.1.5", "@types/mocha": "^5.2.6", "mocha": "^10.2.0" }, "files": ["dist"], "repository": { "type" : "git", "url" : "https://github.com/lezer-parser/common.git" }, "scripts": { "watch": "node build.js --watch", "prepare": "node build.js", "test": "mocha" } } common-1.2.1/src/000077500000000000000000000000001455145755000136055ustar00rootroot00000000000000common-1.2.1/src/README.md000066400000000000000000000023061455145755000150650ustar00rootroot00000000000000This package provides common data structures used by all Lezer-related parsing—those related to syntax trees and the generic interface of parsers. Their main use is the [LR](#lr) parsers generated by the [parser generator](#generator), but for example the [Markdown parser](https://github.com/lezer-parser/markdown) implements a different parsing algorithm using the same interfaces. ### Trees Lezer syntax trees are _not_ abstract, they just tell you which nodes were parsed where, without providing additional information about their role or relation (beyond parent-child relations). This makes them rather unsuited for some purposes, but quick to construct and cheap to store. @Tree @SyntaxNodeRef @SyntaxNode @NodeIterator @TreeCursor @IterMode @NodeWeakMap #### Node types @NodeType @NodeSet @NodeProp @NodePropSource #### Buffers Buffers are an optimization in the way Lezer trees are stored. @TreeBuffer @DefaultBufferLength @BufferCursor ### Parsing @Parser @Input @PartialParse @ParseWrapper ### Incremental Parsing Efficient reparsing happens by reusing parts of the original parsed structure. @TreeFragment @ChangedRange ### Mixed Parsing @parseMixed @NestedParse @MountedTree common-1.2.1/src/index.ts000066400000000000000000000005451455145755000152700ustar00rootroot00000000000000export {DefaultBufferLength, NodeProp, MountedTree, NodePropSource, NodeType, NodeSet, Tree, TreeBuffer, SyntaxNode, SyntaxNodeRef, TreeCursor, BufferCursor, NodeWeakMap, IterMode, NodeIterator} from "./tree" export {ChangedRange, TreeFragment, PartialParse, Parser, Input, ParseWrapper} from "./parse" export {NestedParse, parseMixed} from "./mix" common-1.2.1/src/mix.ts000066400000000000000000000413301455145755000147530ustar00rootroot00000000000000import {Tree, TreeBuffer, NodeType, SyntaxNodeRef, SyntaxNode, NodeProp, TreeCursor, MountedTree, Range, IterMode, TreeNode, BufferNode} from "./tree" import {Input, Parser, PartialParse, TreeFragment, ParseWrapper} from "./parse" /// Objects returned by the function passed to /// [`parseMixed`](#common.parseMixed) should conform to this /// interface. export interface NestedParse { /// The parser to use for the inner region. parser: Parser /// When this property is not given, the entire node is parsed with /// this parser, and it is [mounted](#common.NodeProp^mounted) as a /// non-overlay node, replacing its host node in tree iteration. /// /// When an array of ranges is given, only those ranges are parsed, /// and the tree is mounted as an /// [overlay](#common.MountedTree.overlay). /// /// When a function is given, that function will be called for /// descendant nodes of the target node, not including child nodes /// that are covered by another nested parse, to determine the /// overlay ranges. When it returns true, the entire descendant is /// included, otherwise just the range given. The mixed parser will /// optimize range-finding in reused nodes, which means it's a good /// idea to use a function here when the target node is expected to /// have a large, deep structure. overlay?: readonly {from: number, to: number}[] | ((node: SyntaxNodeRef) => {from: number, to: number} | boolean) } /// Create a parse wrapper that, after the inner parse completes, /// scans its tree for mixed language regions with the `nest` /// function, runs the resulting [inner parses](#common.NestedParse), /// and then [mounts](#common.NodeProp^mounted) their results onto the /// tree. export function parseMixed(nest: (node: SyntaxNodeRef, input: Input) => NestedParse | null): ParseWrapper { return (parse, input, fragments, ranges): PartialParse => new MixedParse(parse, nest, input, fragments, ranges) } class InnerParse { constructor( readonly parser: Parser, readonly parse: PartialParse, readonly overlay: readonly {from: number, to: number}[] | null, readonly target: Tree, readonly from: number ) {} } function checkRanges(ranges: readonly {from: number, to: number}[]) { if (!ranges.length || ranges.some(r => r.from >= r.to)) throw new RangeError("Invalid inner parse ranges given: " + JSON.stringify(ranges)) } class ActiveOverlay { depth = 0 readonly ranges: {from: number, to: number}[] = [] constructor( readonly parser: Parser, readonly predicate: (node: SyntaxNodeRef) => {from: number, to: number} | boolean, readonly mounts: readonly ReusableMount[], readonly index: number, readonly start: number, readonly target: Tree, readonly prev: ActiveOverlay | null, ) {} } type CoverInfo = null | {ranges: readonly {from: number, to: number}[], depth: number, prev: CoverInfo} const stoppedInner = new NodeProp({perNode: true}) class MixedParse implements PartialParse { baseParse: PartialParse | null inner: InnerParse[] = [] innerDone = 0 baseTree: Tree | null = null stoppedAt: number | null = null constructor( base: PartialParse, readonly nest: (node: SyntaxNodeRef, input: Input) => NestedParse | null, readonly input: Input, readonly fragments: readonly TreeFragment[], readonly ranges: readonly {from: number, to: number}[] ) { this.baseParse = base } advance() { if (this.baseParse) { let done = this.baseParse.advance() if (!done) return null this.baseParse = null this.baseTree = done this.startInner() if (this.stoppedAt != null) for (let inner of this.inner) inner.parse.stopAt(this.stoppedAt) } if (this.innerDone == this.inner.length) { let result = this.baseTree! if (this.stoppedAt != null) result = new Tree(result.type, result.children, result.positions, result.length, result.propValues.concat([[stoppedInner, this.stoppedAt]])) return result } let inner = this.inner[this.innerDone], done = inner.parse.advance() if (done) { this.innerDone++ // This is a somewhat dodgy but super helpful hack where we // patch up nodes created by the inner parse (and thus // presumably not aliased anywhere else) to hold the information // about the inner parse. let props = Object.assign(Object.create(null), inner.target.props) props[NodeProp.mounted.id] = new MountedTree(done, inner.overlay, inner.parser) ;(inner.target as any).props = props } return null } get parsedPos() { if (this.baseParse) return 0 let pos = this.input.length for (let i = this.innerDone; i < this.inner.length; i++) { if (this.inner[i].from < pos) pos = Math.min(pos, this.inner[i].parse.parsedPos) } return pos } stopAt(pos: number) { this.stoppedAt = pos if (this.baseParse) this.baseParse.stopAt(pos) else for (let i = this.innerDone; i < this.inner.length; i++) this.inner[i].parse.stopAt(pos) } startInner() { let fragmentCursor = new FragmentCursor(this.fragments) let overlay: ActiveOverlay | null = null let covered: CoverInfo = null let cursor = new TreeCursor(new TreeNode(this.baseTree!, this.ranges[0].from, 0, null), IterMode.IncludeAnonymous | IterMode.IgnoreMounts) scan: for (let nest, isCovered;;) { let enter = true, range if (this.stoppedAt != null && cursor.from >= this.stoppedAt) { enter = false } else if (fragmentCursor.hasNode(cursor)) { if (overlay) { let match = overlay.mounts.find(m => m.frag.from <= cursor.from && m.frag.to >= cursor.to && m.mount.overlay) if (match) for (let r of match.mount.overlay!) { let from = r.from + match.pos, to = r.to + match.pos if (from >= cursor.from && to <= cursor.to && !overlay.ranges.some(r => r.from < to && r.to > from)) overlay.ranges.push({from, to}) } } enter = false } else if (covered && (isCovered = checkCover(covered.ranges, cursor.from, cursor.to))) { enter = isCovered != Cover.Full } else if (!cursor.type.isAnonymous && (nest = this.nest(cursor, this.input)) && (cursor.from < cursor.to || !nest.overlay)) { if (!cursor.tree) materialize(cursor) let oldMounts = fragmentCursor.findMounts(cursor.from, nest.parser) if (typeof nest.overlay == "function") { overlay = new ActiveOverlay(nest.parser, nest.overlay, oldMounts, this.inner.length, cursor.from, cursor.tree!, overlay) } else { let ranges = punchRanges(this.ranges, nest.overlay || (cursor.from < cursor.to ? [new Range(cursor.from, cursor.to)] : [])) if (ranges.length) checkRanges(ranges) if (ranges.length || !nest.overlay) this.inner.push(new InnerParse( nest.parser, ranges.length ? nest.parser.startParse(this.input, enterFragments(oldMounts, ranges), ranges) : nest.parser.startParse(""), nest.overlay ? nest.overlay.map(r => new Range(r.from - cursor.from, r.to - cursor.from)) : null, cursor.tree!, ranges.length ? ranges[0].from : cursor.from, )) if (!nest.overlay) enter = false else if (ranges.length) covered = {ranges, depth: 0, prev: covered} } } else if (overlay && (range = overlay.predicate(cursor))) { if (range === true) range = new Range(cursor.from, cursor.to) if (range.from < range.to) overlay.ranges.push(range) } if (enter && cursor.firstChild()) { if (overlay) overlay.depth++ if (covered) covered.depth++ } else { for (;;) { if (cursor.nextSibling()) break if (!cursor.parent()) break scan if (overlay && !--overlay.depth) { let ranges = punchRanges(this.ranges, overlay.ranges) if (ranges.length) { checkRanges(ranges) this.inner.splice(overlay.index, 0, new InnerParse( overlay.parser, overlay.parser.startParse(this.input, enterFragments(overlay.mounts, ranges), ranges), overlay.ranges.map(r => new Range(r.from - overlay!.start, r.to - overlay!.start)), overlay.target, ranges[0].from )) } overlay = overlay.prev } if (covered && !--covered.depth) covered = covered.prev } } } } } const enum Cover { None = 0, Partial = 1, Full = 2 } function checkCover(covered: readonly {from: number, to: number}[], from: number, to: number) { for (let range of covered) { if (range.from >= to) break if (range.to > from) return range.from <= from && range.to >= to ? Cover.Full : Cover.Partial } return Cover.None } // Take a piece of buffer and convert it into a stand-alone // TreeBuffer. function sliceBuf(buf: TreeBuffer, startI: number, endI: number, nodes: (Tree | TreeBuffer)[], positions: number[], off: number) { if (startI < endI) { let from = buf.buffer[startI + 1] nodes.push(buf.slice(startI, endI, from)) positions.push(from - off) } } // This function takes a node that's in a buffer, and converts it, and // its parent buffer nodes, into a Tree. This is again acting on the // assumption that the trees and buffers have been constructed by the // parse that was ran via the mix parser, and thus aren't shared with // any other code, making violations of the immutability safe. function materialize(cursor: TreeCursor) { let {node} = cursor, stack: number[] = [] let buffer = (node as BufferNode).context.buffer // Scan up to the nearest tree do { stack.push(cursor.index); cursor.parent() } while (!cursor.tree) // Find the index of the buffer in that tree let base = cursor.tree!, i = base.children.indexOf(buffer) let buf = base.children[i] as TreeBuffer, b = buf.buffer, newStack: number[] = [i] // Split a level in the buffer, putting the nodes before and after // the child that contains `node` into new buffers. function split(startI: number, endI: number, type: NodeType, innerOffset: number, length: number, stackPos: number): Tree { let targetI = stack[stackPos] let children: (Tree | TreeBuffer)[] = [], positions: number[] = [] sliceBuf(buf, startI, targetI, children, positions, innerOffset) let from = b[targetI + 1], to = b[targetI + 2] newStack.push(children.length) let child = stackPos ? split(targetI + 4, b[targetI + 3], buf.set.types[b[targetI]], from, to - from, stackPos - 1) : node.toTree() children.push(child) positions.push(from - innerOffset) sliceBuf(buf, b[targetI + 3], endI, children, positions, innerOffset) return new Tree(type, children, positions, length) } // Overwrite (!) the child at the buffer's index with the split-up tree ;(base.children as any)[i] = split(0, b.length, NodeType.none, 0, buf.length, stack.length - 1) // Move the cursor back to the target node for (let index of newStack) { let tree = cursor.tree.children[index] as Tree, pos = cursor.tree.positions[index] cursor.yield(new TreeNode(tree, pos + cursor.from, index, cursor._tree)) } } class StructureCursor { cursor: TreeCursor done = false constructor( root: Tree, private offset: number ) { this.cursor = root.cursor(IterMode.IncludeAnonymous | IterMode.IgnoreMounts) } // Move to the first node (in pre-order) that starts at or after `pos`. moveTo(pos: number) { let {cursor} = this, p = pos - this.offset while (!this.done && cursor.from < p) { if (cursor.to >= pos && cursor.enter(p, 1, IterMode.IgnoreOverlays | IterMode.ExcludeBuffers)) {} else if (!cursor.next(false)) this.done = true } } hasNode(cursor: TreeCursor) { this.moveTo(cursor.from) if (!this.done && this.cursor.from + this.offset == cursor.from && this.cursor.tree) { for (let tree = this.cursor.tree!;;) { if (tree == cursor.tree) return true if (tree.children.length && tree.positions[0] == 0 && tree.children[0] instanceof Tree) tree = tree.children[0] else break } } return false } } class FragmentCursor { curFrag: TreeFragment | null curTo = 0 fragI = 0 inner: StructureCursor | null constructor(readonly fragments: readonly TreeFragment[]) { if (fragments.length) { let first = this.curFrag = fragments[0] this.curTo = first.tree.prop(stoppedInner) ?? first.to this.inner = new StructureCursor(first.tree, -first.offset) } else { this.curFrag = this.inner = null } } hasNode(node: TreeCursor) { while (this.curFrag && node.from >= this.curTo) this.nextFrag() return this.curFrag && this.curFrag.from <= node.from && this.curTo >= node.to && this.inner!.hasNode(node) } nextFrag() { this.fragI++ if (this.fragI == this.fragments.length) { this.curFrag = this.inner = null } else { let frag = this.curFrag = this.fragments[this.fragI] this.curTo = frag.tree.prop(stoppedInner) ?? frag.to this.inner = new StructureCursor(frag.tree, -frag.offset) } } findMounts(pos: number, parser: Parser) { let result: ReusableMount[] = [] if (this.inner) { this.inner.cursor.moveTo(pos, 1) for (let pos: SyntaxNode | null = this.inner.cursor.node; pos; pos = pos.parent) { let mount = pos.tree?.prop(NodeProp.mounted) if (mount && mount.parser == parser) { for (let i = this.fragI; i < this.fragments.length; i++) { let frag = this.fragments[i] if (frag.from >= pos.to) break if (frag.tree == this.curFrag!.tree) result.push({ frag, pos: pos.from - frag.offset, mount }) } } } } return result } } function punchRanges(outer: readonly {from: number, to: number}[], ranges: readonly {from: number, to: number}[]) { let copy: {from: number, to: number}[] | null = null, current = ranges for (let i = 1, j = 0; i < outer.length; i++) { let gapFrom = outer[i - 1].to, gapTo = outer[i].from for (; j < current.length; j++) { let r = current[j] if (r.from >= gapTo) break if (r.to <= gapFrom) continue if (!copy) current = copy = ranges.slice() if (r.from < gapFrom) { copy[j] = new Range(r.from, gapFrom) if (r.to > gapTo) copy.splice(j + 1, 0, new Range(gapTo, r.to)) } else if (r.to > gapTo) { copy[j--] = new Range(gapTo, r.to) } else { copy.splice(j--, 1) } } } return current } type ReusableMount = { frag: TreeFragment, mount: MountedTree, pos: number } function findCoverChanges(a: readonly {from: number, to: number}[], b: readonly {from: number, to: number}[], from: number, to: number) { let iA = 0, iB = 0, inA = false, inB = false, pos = -1e9 let result = [] for (;;) { let nextA = iA == a.length ? 1e9 : inA ? a[iA].to : a[iA].from let nextB = iB == b.length ? 1e9 : inB ? b[iB].to : b[iB].from if (inA != inB) { let start = Math.max(pos, from), end = Math.min(nextA, nextB, to) if (start < end) result.push(new Range(start, end)) } pos = Math.min(nextA, nextB) if (pos == 1e9) break if (nextA == pos) { if (!inA) inA = true else { inA = false; iA++ } } if (nextB == pos) { if (!inB) inB = true else { inB = false; iB++ } } } return result } // Given a number of fragments for the outer tree, and a set of ranges // to parse, find fragments for inner trees mounted around those // ranges, if any. function enterFragments(mounts: readonly ReusableMount[], ranges: readonly {from: number, to: number}[]) { let result: TreeFragment[] = [] for (let {pos, mount, frag} of mounts) { let startPos = pos + (mount.overlay ? mount.overlay[0].from : 0), endPos = startPos + mount.tree.length let from = Math.max(frag.from, startPos), to = Math.min(frag.to, endPos) if (mount.overlay) { let overlay = mount.overlay.map(r => new Range(r.from + pos, r.to + pos)) let changes = findCoverChanges(ranges, overlay, from, to) for (let i = 0, pos = from;; i++) { let last = i == changes.length, end = last ? to : changes[i].from if (end > pos) result.push(new TreeFragment(pos, end, mount.tree, -startPos, frag.from >= pos || frag.openStart, frag.to <= end || frag.openEnd)) if (last) break pos = changes[i].to } } else { result.push(new TreeFragment(from, to, mount.tree, -startPos, frag.from >= startPos || frag.openStart, frag.to <= endPos || frag.openEnd)) } } return result } common-1.2.1/src/parse.ts000066400000000000000000000202541455145755000152720ustar00rootroot00000000000000import {Tree, Range} from "./tree" /// The [`TreeFragment.applyChanges`](#common.TreeFragment^applyChanges) /// method expects changed ranges in this format. export interface ChangedRange { /// The start of the change in the start document fromA: number /// The end of the change in the start document toA: number /// The start of the replacement in the new document fromB: number /// The end of the replacement in the new document toB: number } const enum Open { Start = 1, End = 2 } /// Tree fragments are used during [incremental /// parsing](#common.Parser.startParse) to track parts of old trees /// that can be reused in a new parse. An array of fragments is used /// to track regions of an old tree whose nodes might be reused in new /// parses. Use the static /// [`applyChanges`](#common.TreeFragment^applyChanges) method to /// update fragments for document changes. export class TreeFragment { /// @internal open: Open /// Construct a tree fragment. You'll usually want to use /// [`addTree`](#common.TreeFragment^addTree) and /// [`applyChanges`](#common.TreeFragment^applyChanges) instead of /// calling this directly. constructor( /// The start of the unchanged range pointed to by this fragment. /// This refers to an offset in the _updated_ document (as opposed /// to the original tree). readonly from: number, /// The end of the unchanged range. readonly to: number, /// The tree that this fragment is based on. readonly tree: Tree, /// The offset between the fragment's tree and the document that /// this fragment can be used against. Add this when going from /// document to tree positions, subtract it to go from tree to /// document positions. readonly offset: number, openStart: boolean = false, openEnd: boolean = false ) { this.open = (openStart ? Open.Start : 0) | (openEnd ? Open.End : 0) } /// Whether the start of the fragment represents the start of a /// parse, or the end of a change. (In the second case, it may not /// be safe to reuse some nodes at the start, depending on the /// parsing algorithm.) get openStart() { return (this.open & Open.Start) > 0 } /// Whether the end of the fragment represents the end of a /// full-document parse, or the start of a change. get openEnd() { return (this.open & Open.End) > 0 } /// Create a set of fragments from a freshly parsed tree, or update /// an existing set of fragments by replacing the ones that overlap /// with a tree with content from the new tree. When `partial` is /// true, the parse is treated as incomplete, and the resulting /// fragment has [`openEnd`](#common.TreeFragment.openEnd) set to /// true. static addTree(tree: Tree, fragments: readonly TreeFragment[] = [], partial = false): readonly TreeFragment[] { let result = [new TreeFragment(0, tree.length, tree, 0, false, partial)] for (let f of fragments) if (f.to > tree.length) result.push(f) return result } /// Apply a set of edits to an array of fragments, removing or /// splitting fragments as necessary to remove edited ranges, and /// adjusting offsets for fragments that moved. static applyChanges(fragments: readonly TreeFragment[], changes: readonly ChangedRange[], minGap = 128) { if (!changes.length) return fragments let result: TreeFragment[] = [] let fI = 1, nextF = fragments.length ? fragments[0] : null for (let cI = 0, pos = 0, off = 0;; cI++) { let nextC = cI < changes.length ? changes[cI] : null let nextPos = nextC ? nextC.fromA : 1e9 if (nextPos - pos >= minGap) while (nextF && nextF.from < nextPos) { let cut: TreeFragment | null = nextF if (pos >= cut.from || nextPos <= cut.to || off) { let fFrom = Math.max(cut.from, pos) - off, fTo = Math.min(cut.to, nextPos) - off cut = fFrom >= fTo ? null : new TreeFragment(fFrom, fTo, cut.tree, cut.offset + off, cI > 0, !!nextC) } if (cut) result.push(cut) if (nextF.to > nextPos) break nextF = fI < fragments.length ? fragments[fI++] : null } if (!nextC) break pos = nextC.toA off = nextC.toA - nextC.toB } return result } } /// Interface used to represent an in-progress parse, which can be /// moved forward piece-by-piece. export interface PartialParse { /// Advance the parse state by some amount. Will return the finished /// syntax tree when the parse completes. advance(): Tree | null /// The position up to which the document has been parsed. Note /// that, in multi-pass parsers, this will stay back until the last /// pass has moved past a given position. readonly parsedPos: number /// Tell the parse to not advance beyond the given position. /// `advance` will return a tree when the parse has reached the /// position. Note that, depending on the parser algorithm and the /// state of the parse when `stopAt` was called, that tree may /// contain nodes beyond the position. It is an error to call /// `stopAt` with a higher position than it's [current /// value](#common.PartialParse.stoppedAt). stopAt(pos: number): void /// Reports whether `stopAt` has been called on this parse. readonly stoppedAt: number | null } /// A superclass that parsers should extend. export abstract class Parser { /// Start a parse for a single tree. This is the method concrete /// parser implementations must implement. Called by `startParse`, /// with the optional arguments resolved. abstract createParse( input: Input, fragments: readonly TreeFragment[], ranges: readonly {from: number, to: number}[] ): PartialParse /// Start a parse, returning a [partial parse](#common.PartialParse) /// object. [`fragments`](#common.TreeFragment) can be passed in to /// make the parse incremental. /// /// By default, the entire input is parsed. You can pass `ranges`, /// which should be a sorted array of non-empty, non-overlapping /// ranges, to parse only those ranges. The tree returned in that /// case will start at `ranges[0].from`. startParse( input: Input | string, fragments?: readonly TreeFragment[], ranges?: readonly {from: number, to: number}[] ): PartialParse { if (typeof input == "string") input = new StringInput(input) ranges = !ranges ? [new Range(0, input.length)] : ranges.length ? ranges.map(r => new Range(r.from, r.to)) : [new Range(0, 0)] return this.createParse(input, fragments || [], ranges) } /// Run a full parse, returning the resulting tree. parse( input: Input | string, fragments?: readonly TreeFragment[], ranges?: readonly {from: number, to: number}[] ) { let parse = this.startParse(input, fragments, ranges) for (;;) { let done = parse.advance() if (done) return done } } } /// This is the interface parsers use to access the document. To run /// Lezer directly on your own document data structure, you have to /// write an implementation of it. export interface Input { /// The length of the document. readonly length: number /// Get the chunk after the given position. The returned string /// should start at `from` and, if that isn't the end of the /// document, may be of any length greater than zero. chunk(from: number): string /// Indicates whether the chunks already end at line breaks, so that /// client code that wants to work by-line can avoid re-scanning /// them for line breaks. When this is true, the result of `chunk()` /// should either be a single line break, or the content between /// `from` and the next line break. readonly lineChunks: boolean /// Read the part of the document between the given positions. read(from: number, to: number): string } class StringInput implements Input { constructor(readonly string: string) {} get length() { return this.string.length } chunk(from: number) { return this.string.slice(from) } get lineChunks() { return false } read(from: number, to: number) { return this.string.slice(from, to) } } /// Parse wrapper functions are supported by some parsers to inject /// additional parsing logic. export type ParseWrapper = ( inner: PartialParse, input: Input, fragments: readonly TreeFragment[], ranges: readonly {from: number, to: number}[] ) => PartialParse common-1.2.1/src/tree.ts000066400000000000000000002005101455145755000151120ustar00rootroot00000000000000import {Parser} from "./parse" /// The default maximum length of a `TreeBuffer` node. export const DefaultBufferLength = 1024 let nextPropID = 0 export class Range { constructor(readonly from: number, readonly to: number) {} } /// Each [node type](#common.NodeType) or [individual tree](#common.Tree) /// can have metadata associated with it in props. Instances of this /// class represent prop names. export class NodeProp { /// @internal id: number /// Indicates whether this prop is stored per [node /// type](#common.NodeType) or per [tree node](#common.Tree). perNode: boolean /// A method that deserializes a value of this prop from a string. /// Can be used to allow a prop to be directly written in a grammar /// file. deserialize: (str: string) => T /// Create a new node prop type. constructor(config: { /// The [deserialize](#common.NodeProp.deserialize) function to /// use for this prop, used for example when directly providing /// the prop from a grammar file. Defaults to a function that /// raises an error. deserialize?: (str: string) => T, /// By default, node props are stored in the [node /// type](#common.NodeType). It can sometimes be useful to directly /// store information (usually related to the parsing algorithm) /// in [nodes](#common.Tree) themselves. Set this to true to enable /// that for this prop. perNode?: boolean } = {}) { this.id = nextPropID++ this.perNode = !!config.perNode this.deserialize = config.deserialize || (() => { throw new Error("This node type doesn't define a deserialize function") }) } /// This is meant to be used with /// [`NodeSet.extend`](#common.NodeSet.extend) or /// [`LRParser.configure`](#lr.ParserConfig.props) to compute /// prop values for each node type in the set. Takes a [match /// object](#common.NodeType^match) or function that returns undefined /// if the node type doesn't get this prop, and the prop's value if /// it does. add(match: {[selector: string]: T} | ((type: NodeType) => T | undefined)): NodePropSource { if (this.perNode) throw new RangeError("Can't add per-node props to node types") if (typeof match != "function") match = NodeType.match(match) return (type) => { let result = (match as (type: NodeType) => T | undefined)(type) return result === undefined ? null : [this, result] } } /// Prop that is used to describe matching delimiters. For opening /// delimiters, this holds an array of node names (written as a /// space-separated string when declaring this prop in a grammar) /// for the node types of closing delimiters that match it. static closedBy = new NodeProp({deserialize: str => str.split(" ")}) /// The inverse of [`closedBy`](#common.NodeProp^closedBy). This is /// attached to closing delimiters, holding an array of node names /// of types of matching opening delimiters. static openedBy = new NodeProp({deserialize: str => str.split(" ")}) /// Used to assign node types to groups (for example, all node /// types that represent an expression could be tagged with an /// `"Expression"` group). static group = new NodeProp({deserialize: str => str.split(" ")}) /// Attached to nodes to indicate these should be /// [displayed](https://codemirror.net/docs/ref/#language.syntaxTree) /// in a bidirectional text isolate, so that direction-neutral /// characters on their sides don't incorrectly get associated with /// surrounding text. You'll generally want to set this for nodes /// that contain arbitrary text, like strings and comments, and for /// nodes that appear _inside_ arbitrary text, like HTML tags. When /// not given a value, in a grammar declaration, defaults to /// `"auto"`. static isolate = new NodeProp<"rtl" | "ltr" | "auto">({deserialize: value => { if (value && value != "rtl" && value != "ltr" && value != "auto") throw new RangeError("Invalid value for isolate: " + value) return (value as any) || "auto" }}) /// The hash of the [context](#lr.ContextTracker.constructor) /// that the node was parsed in, if any. Used to limit reuse of /// contextual nodes. static contextHash = new NodeProp({perNode: true}) /// The distance beyond the end of the node that the tokenizer /// looked ahead for any of the tokens inside the node. (The LR /// parser only stores this when it is larger than 25, for /// efficiency reasons.) static lookAhead = new NodeProp({perNode: true}) /// This per-node prop is used to replace a given node, or part of a /// node, with another tree. This is useful to include trees from /// different languages in mixed-language parsers. static mounted = new NodeProp({perNode: true}) } /// A mounted tree, which can be [stored](#common.NodeProp^mounted) on /// a tree node to indicate that parts of its content are /// represented by another tree. export class MountedTree { constructor( /// The inner tree. readonly tree: Tree, /// If this is null, this tree replaces the entire node (it will /// be included in the regular iteration instead of its host /// node). If not, only the given ranges are considered to be /// covered by this tree. This is used for trees that are mixed in /// a way that isn't strictly hierarchical. Such mounted trees are /// only entered by [`resolveInner`](#common.Tree.resolveInner) /// and [`enter`](#common.SyntaxNode.enter). readonly overlay: readonly {from: number, to: number}[] | null, /// The parser used to create this subtree. readonly parser: Parser ) {} /// @internal static get(tree: Tree | null): MountedTree | null { return tree && tree.props && tree.props[NodeProp.mounted.id] } } /// Type returned by [`NodeProp.add`](#common.NodeProp.add). Describes /// whether a prop should be added to a given node type in a node set, /// and what value it should have. export type NodePropSource = (type: NodeType) => null | [NodeProp, any] // Note: this is duplicated in lr/src/constants.ts const enum NodeFlag { Top = 1, Skipped = 2, Error = 4, Anonymous = 8 } const noProps: {[propID: number]: any} = Object.create(null) /// Each node in a syntax tree has a node type associated with it. export class NodeType { /// @internal constructor( /// The name of the node type. Not necessarily unique, but if the /// grammar was written properly, different node types with the /// same name within a node set should play the same semantic /// role. readonly name: string, /// @internal readonly props: {readonly [prop: number]: any}, /// The id of this node in its set. Corresponds to the term ids /// used in the parser. readonly id: number, /// @internal readonly flags: number = 0) {} /// Define a node type. static define(spec: { /// The ID of the node type. When this type is used in a /// [set](#common.NodeSet), the ID must correspond to its index in /// the type array. id: number, /// The name of the node type. Leave empty to define an anonymous /// node. name?: string, /// [Node props](#common.NodeProp) to assign to the type. The value /// given for any given prop should correspond to the prop's type. props?: readonly ([NodeProp, any] | NodePropSource)[], /// Whether this is a [top node](#common.NodeType.isTop). top?: boolean, /// Whether this node counts as an [error /// node](#common.NodeType.isError). error?: boolean, /// Whether this node is a [skipped](#common.NodeType.isSkipped) /// node. skipped?: boolean }) { let props = spec.props && spec.props.length ? Object.create(null) : noProps let flags = (spec.top ? NodeFlag.Top : 0) | (spec.skipped ? NodeFlag.Skipped : 0) | (spec.error ? NodeFlag.Error : 0) | (spec.name == null ? NodeFlag.Anonymous : 0) let type = new NodeType(spec.name || "", props, spec.id, flags) if (spec.props) for (let src of spec.props) { if (!Array.isArray(src)) src = src(type)! if (src) { if (src[0].perNode) throw new RangeError("Can't store a per-node prop on a node type") props[src[0].id] = src[1] } } return type } /// Retrieves a node prop for this type. Will return `undefined` if /// the prop isn't present on this node. prop(prop: NodeProp): T | undefined { return this.props[prop.id] } /// True when this is the top node of a grammar. get isTop() { return (this.flags & NodeFlag.Top) > 0 } /// True when this node is produced by a skip rule. get isSkipped() { return (this.flags & NodeFlag.Skipped) > 0 } /// Indicates whether this is an error node. get isError() { return (this.flags & NodeFlag.Error) > 0 } /// When true, this node type doesn't correspond to a user-declared /// named node, for example because it is used to cache repetition. get isAnonymous() { return (this.flags & NodeFlag.Anonymous) > 0 } /// Returns true when this node's name or one of its /// [groups](#common.NodeProp^group) matches the given string. is(name: string | number) { if (typeof name == 'string') { if (this.name == name) return true let group = this.prop(NodeProp.group) return group ? group.indexOf(name) > -1 : false } return this.id == name } /// An empty dummy node type to use when no actual type is available. static none: NodeType = new NodeType("", Object.create(null), 0, NodeFlag.Anonymous) /// Create a function from node types to arbitrary values by /// specifying an object whose property names are node or /// [group](#common.NodeProp^group) names. Often useful with /// [`NodeProp.add`](#common.NodeProp.add). You can put multiple /// names, separated by spaces, in a single property name to map /// multiple node names to a single value. static match(map: {[selector: string]: T}): (node: NodeType) => T | undefined { let direct = Object.create(null) for (let prop in map) for (let name of prop.split(" ")) direct[name] = map[prop] return (node: NodeType) => { for (let groups = node.prop(NodeProp.group), i = -1; i < (groups ? groups.length : 0); i++) { let found = direct[i < 0 ? node.name : groups![i]] if (found) return found } } } } /// A node set holds a collection of node types. It is used to /// compactly represent trees by storing their type ids, rather than a /// full pointer to the type object, in a numeric array. Each parser /// [has](#lr.LRParser.nodeSet) a node set, and [tree /// buffers](#common.TreeBuffer) can only store collections of nodes /// from the same set. A set can have a maximum of 2**16 (65536) node /// types in it, so that the ids fit into 16-bit typed array slots. export class NodeSet { /// Create a set with the given types. The `id` property of each /// type should correspond to its position within the array. constructor( /// The node types in this set, by id. readonly types: readonly NodeType[] ) { for (let i = 0; i < types.length; i++) if (types[i].id != i) throw new RangeError("Node type ids should correspond to array positions when creating a node set") } /// Create a copy of this set with some node properties added. The /// arguments to this method can be created with /// [`NodeProp.add`](#common.NodeProp.add). extend(...props: NodePropSource[]): NodeSet { let newTypes: NodeType[] = [] for (let type of this.types) { let newProps: null | {[id: number]: any} = null for (let source of props) { let add = source(type) if (add) { if (!newProps) newProps = Object.assign({}, type.props) newProps[add[0].id] = add[1] } } newTypes.push(newProps ? new NodeType(type.name, newProps, type.id, type.flags) : type) } return new NodeSet(newTypes) } } const CachedNode = new WeakMap(), CachedInnerNode = new WeakMap() /// Options that control iteration. Can be combined with the `|` /// operator to enable multiple ones. export enum IterMode { /// When enabled, iteration will only visit [`Tree`](#common.Tree) /// objects, not nodes packed into /// [`TreeBuffer`](#common.TreeBuffer)s. ExcludeBuffers = 1, /// Enable this to make iteration include anonymous nodes (such as /// the nodes that wrap repeated grammar constructs into a balanced /// tree). IncludeAnonymous = 2, /// By default, regular [mounted](#common.NodeProp^mounted) nodes /// replace their base node in iteration. Enable this to ignore them /// instead. IgnoreMounts = 4, /// This option only applies in /// [`enter`](#common.SyntaxNode.enter)-style methods. It tells the /// library to not enter mounted overlays if one covers the given /// position. IgnoreOverlays = 8, } /// A piece of syntax tree. There are two ways to approach these /// trees: the way they are actually stored in memory, and the /// convenient way. /// /// Syntax trees are stored as a tree of `Tree` and `TreeBuffer` /// objects. By packing detail information into `TreeBuffer` leaf /// nodes, the representation is made a lot more memory-efficient. /// /// However, when you want to actually work with tree nodes, this /// representation is very awkward, so most client code will want to /// use the [`TreeCursor`](#common.TreeCursor) or /// [`SyntaxNode`](#common.SyntaxNode) interface instead, which provides /// a view on some part of this data structure, and can be used to /// move around to adjacent nodes. export class Tree { /// @internal props: null | {[id: number]: any} = null /// Construct a new tree. See also [`Tree.build`](#common.Tree^build). constructor( /// The type of the top node. readonly type: NodeType, /// This node's child nodes. readonly children: readonly (Tree | TreeBuffer)[], /// The positions (offsets relative to the start of this tree) of /// the children. readonly positions: readonly number[], /// The total length of this tree readonly length: number, /// Per-node [node props](#common.NodeProp) to associate with this node. props?: readonly [NodeProp | number, any][] ) { if (props && props.length) { this.props = Object.create(null) for (let [prop, value] of props) this.props![typeof prop == "number" ? prop : prop.id] = value } } /// @internal toString(): string { let mounted = MountedTree.get(this) if (mounted && !mounted.overlay) return mounted.tree.toString() let children = "" for (let ch of this.children) { let str = ch.toString() if (str) { if (children) children += "," children += str } } return !this.type.name ? children : (/\W/.test(this.type.name) && !this.type.isError ? JSON.stringify(this.type.name) : this.type.name) + (children.length ? "(" + children + ")" : "") } /// The empty tree static empty = new Tree(NodeType.none, [], [], 0) /// Get a [tree cursor](#common.TreeCursor) positioned at the top of /// the tree. Mode can be used to [control](#common.IterMode) which /// nodes the cursor visits. cursor(mode: IterMode = 0 as IterMode) { return new TreeCursor(this.topNode as TreeNode, mode) } /// Get a [tree cursor](#common.TreeCursor) pointing into this tree /// at the given position and side (see /// [`moveTo`](#common.TreeCursor.moveTo). cursorAt(pos: number, side: -1 | 0 | 1 = 0, mode: IterMode = 0 as IterMode): TreeCursor { let scope = CachedNode.get(this) || this.topNode let cursor = new TreeCursor(scope as TreeNode | BufferNode) cursor.moveTo(pos, side) CachedNode.set(this, cursor._tree) return cursor } /// Get a [syntax node](#common.SyntaxNode) object for the top of the /// tree. get topNode(): SyntaxNode { return new TreeNode(this, 0, 0, null) } /// Get the [syntax node](#common.SyntaxNode) at the given position. /// If `side` is -1, this will move into nodes that end at the /// position. If 1, it'll move into nodes that start at the /// position. With 0, it'll only enter nodes that cover the position /// from both sides. /// /// Note that this will not enter /// [overlays](#common.MountedTree.overlay), and you often want /// [`resolveInner`](#common.Tree.resolveInner) instead. resolve(pos: number, side: -1 | 0 | 1 = 0) { let node = resolveNode(CachedNode.get(this) || this.topNode, pos, side, false) CachedNode.set(this, node) return node } /// Like [`resolve`](#common.Tree.resolve), but will enter /// [overlaid](#common.MountedTree.overlay) nodes, producing a syntax node /// pointing into the innermost overlaid tree at the given position /// (with parent links going through all parent structure, including /// the host trees). resolveInner(pos: number, side: -1 | 0 | 1 = 0) { let node = resolveNode(CachedInnerNode.get(this) || this.topNode, pos, side, true) CachedInnerNode.set(this, node) return node } /// In some situations, it can be useful to iterate through all /// nodes around a position, including those in overlays that don't /// directly cover the position. This method gives you an iterator /// that will produce all nodes, from small to big, around the given /// position. resolveStack(pos: number, side: -1 | 0 | 1 = 0): NodeIterator { return stackIterator(this, pos, side) } /// Iterate over the tree and its children, calling `enter` for any /// node that touches the `from`/`to` region (if given) before /// running over such a node's children, and `leave` (if given) when /// leaving the node. When `enter` returns `false`, that node will /// not have its children iterated over (or `leave` called). iterate(spec: { enter(node: SyntaxNodeRef): boolean | void, leave?(node: SyntaxNodeRef): void, from?: number, to?: number, mode?: IterMode }) { let {enter, leave, from = 0, to = this.length} = spec let mode = spec.mode || 0, anon = (mode & IterMode.IncludeAnonymous) > 0 for (let c = this.cursor(mode | IterMode.IncludeAnonymous);;) { let entered = false if (c.from <= to && c.to >= from && (!anon && c.type.isAnonymous || enter(c) !== false)) { if (c.firstChild()) continue entered = true } for (;;) { if (entered && leave && (anon || !c.type.isAnonymous)) leave(c) if (c.nextSibling()) break if (!c.parent()) return entered = true } } } /// Get the value of the given [node prop](#common.NodeProp) for this /// node. Works with both per-node and per-type props. prop(prop: NodeProp): T | undefined { return !prop.perNode ? this.type.prop(prop) : this.props ? this.props[prop.id] : undefined } /// Returns the node's [per-node props](#common.NodeProp.perNode) in a /// format that can be passed to the [`Tree`](#common.Tree) /// constructor. get propValues(): readonly [NodeProp | number, any][] { let result: [NodeProp | number, any][] = [] if (this.props) for (let id in this.props) result.push([+id, this.props[id]]) return result } /// Balance the direct children of this tree, producing a copy of /// which may have children grouped into subtrees with type /// [`NodeType.none`](#common.NodeType^none). balance(config: { /// Function to create the newly balanced subtrees. makeTree?: (children: readonly (Tree | TreeBuffer)[], positions: readonly number[], length: number) => Tree } = {}) { return this.children.length <= Balance.BranchFactor ? this : balanceRange(NodeType.none, this.children, this.positions, 0, this.children.length, 0, this.length, (children, positions, length) => new Tree(this.type, children, positions, length, this.propValues), config.makeTree || ((children, positions, length) => new Tree(NodeType.none, children, positions, length))) } /// Build a tree from a postfix-ordered buffer of node information, /// or a cursor over such a buffer. static build(data: BuildData) { return buildTree(data) } } /// Represents a sequence of nodes. export type NodeIterator = {node: SyntaxNode, next: NodeIterator | null} type BuildData = { /// The buffer or buffer cursor to read the node data from. /// /// When this is an array, it should contain four values for every /// node in the tree. /// /// - The first holds the node's type, as a node ID pointing into /// the given `NodeSet`. /// - The second holds the node's start offset. /// - The third the end offset. /// - The fourth the amount of space taken up in the array by this /// node and its children. Since there's four values per node, /// this is the total number of nodes inside this node (children /// and transitive children) plus one for the node itself, times /// four. /// /// Parent nodes should appear _after_ child nodes in the array. As /// an example, a node of type 10 spanning positions 0 to 4, with /// two children, of type 11 and 12, might look like this: /// /// [11, 0, 1, 4, 12, 2, 4, 4, 10, 0, 4, 12] buffer: BufferCursor | readonly number[], /// The node types to use. nodeSet: NodeSet, /// The id of the top node type. topID: number, /// The position the tree should start at. Defaults to 0. start?: number, /// The position in the buffer where the function should stop /// reading. Defaults to 0. bufferStart?: number, /// The length of the wrapping node. The end offset of the last /// child is used when not provided. length?: number, /// The maximum buffer length to use. Defaults to /// [`DefaultBufferLength`](#common.DefaultBufferLength). maxBufferLength?: number, /// An optional array holding reused nodes that the buffer can refer /// to. reused?: readonly Tree[], /// The first node type that indicates repeat constructs in this /// grammar. minRepeatType?: number } /// This is used by `Tree.build` as an abstraction for iterating over /// a tree buffer. A cursor initially points at the very last element /// in the buffer. Every time `next()` is called it moves on to the /// previous one. export interface BufferCursor { /// The current buffer position (four times the number of nodes /// remaining). pos: number /// The node ID of the next node in the buffer. id: number /// The start position of the next node in the buffer. start: number /// The end position of the next node. end: number /// The size of the next node (the number of nodes inside, counting /// the node itself, times 4). size: number /// Moves `this.pos` down by 4. next(): void /// Create a copy of this cursor. fork(): BufferCursor } class FlatBufferCursor implements BufferCursor { constructor(readonly buffer: readonly number[], public index: number) {} get id() { return this.buffer[this.index - 4] } get start() { return this.buffer[this.index - 3] } get end() { return this.buffer[this.index - 2] } get size() { return this.buffer[this.index - 1] } get pos() { return this.index } next() { this.index -= 4 } fork() { return new FlatBufferCursor(this.buffer, this.index) } } /// Tree buffers contain (type, start, end, endIndex) quads for each /// node. In such a buffer, nodes are stored in prefix order (parents /// before children, with the endIndex of the parent indicating which /// children belong to it). export class TreeBuffer { /// Create a tree buffer. constructor( /// The buffer's content. readonly buffer: Uint16Array, /// The total length of the group of nodes in the buffer. readonly length: number, /// The node set used in this buffer. readonly set: NodeSet ) {} /// @internal get type() { return NodeType.none } /// @internal toString() { let result: string[] = [] for (let index = 0; index < this.buffer.length;) { result.push(this.childString(index)) index = this.buffer[index + 3] } return result.join(",") } /// @internal childString(index: number): string { let id = this.buffer[index], endIndex = this.buffer[index + 3] let type = this.set.types[id], result = type.name if (/\W/.test(result) && !type.isError) result = JSON.stringify(result) index += 4 if (endIndex == index) return result let children: string[] = [] while (index < endIndex) { children.push(this.childString(index)) index = this.buffer[index + 3] } return result + "(" + children.join(",") + ")" } /// @internal findChild(startIndex: number, endIndex: number, dir: 1 | -1, pos: number, side: Side) { let {buffer} = this, pick = -1 for (let i = startIndex; i != endIndex; i = buffer[i + 3]) { if (checkSide(side, pos, buffer[i + 1], buffer[i + 2])) { pick = i if (dir > 0) break } } return pick } /// @internal slice(startI: number, endI: number, from: number) { let b = this.buffer let copy = new Uint16Array(endI - startI), len = 0 for (let i = startI, j = 0; i < endI;) { copy[j++] = b[i++] copy[j++] = b[i++] - from let to = copy[j++] = b[i++] - from copy[j++] = b[i++] - startI len = Math.max(len, to) } return new TreeBuffer(copy, len, this.set) } } /// The set of properties provided by both [`SyntaxNode`](#common.SyntaxNode) /// and [`TreeCursor`](#common.TreeCursor). Note that, if you need /// an object that is guaranteed to stay stable in the future, you /// need to use the [`node`](#common.SyntaxNodeRef.node) accessor. export interface SyntaxNodeRef { /// The start position of the node. readonly from: number /// The end position of the node. readonly to: number /// The type of the node. readonly type: NodeType /// The name of the node (`.type.name`). readonly name: string /// Get the [tree](#common.Tree) that represents the current node, /// if any. Will return null when the node is in a [tree /// buffer](#common.TreeBuffer). readonly tree: Tree | null /// Retrieve a stable [syntax node](#common.SyntaxNode) at this /// position. readonly node: SyntaxNode /// Test whether the node matches a given context—a sequence of /// direct parent nodes. Empty strings in the context array act as /// wildcards, other strings must match the ancestor node's name. matchContext(context: readonly string[]): boolean } /// A syntax node provides an immutable pointer to a given node in a /// tree. When iterating over large amounts of nodes, you may want to /// use a mutable [cursor](#common.TreeCursor) instead, which is more /// efficient. export interface SyntaxNode extends SyntaxNodeRef { /// The node's parent node, if any. parent: SyntaxNode | null /// The first child, if the node has children. firstChild: SyntaxNode | null /// The node's last child, if available. lastChild: SyntaxNode | null /// The first child that ends after `pos`. childAfter(pos: number): SyntaxNode | null /// The last child that starts before `pos`. childBefore(pos: number): SyntaxNode | null /// Enter the child at the given position. If side is -1 the child /// may end at that position, when 1 it may start there. /// /// This will by default enter /// [overlaid](#common.MountedTree.overlay) /// [mounted](#common.NodeProp^mounted) trees. You can set /// `overlays` to false to disable that. /// /// Similarly, when `buffers` is false this will not enter /// [buffers](#common.TreeBuffer), only [nodes](#common.Tree) (which /// is mostly useful when looking for props, which cannot exist on /// buffer-allocated nodes). enter(pos: number, side: -1 | 0 | 1, mode?: IterMode): SyntaxNode | null /// This node's next sibling, if any. nextSibling: SyntaxNode | null /// This node's previous sibling. prevSibling: SyntaxNode | null /// A [tree cursor](#common.TreeCursor) starting at this node. cursor(mode?: IterMode): TreeCursor /// Find the node around, before (if `side` is -1), or after (`side` /// is 1) the given position. Will look in parent nodes if the /// position is outside this node. resolve(pos: number, side?: -1 | 0 | 1): SyntaxNode /// Similar to `resolve`, but enter /// [overlaid](#common.MountedTree.overlay) nodes. resolveInner(pos: number, side?: -1 | 0 | 1): SyntaxNode /// Move the position to the innermost node before `pos` that looks /// like it is unfinished (meaning it ends in an error node or has a /// child ending in an error node right at its end). enterUnfinishedNodesBefore(pos: number): SyntaxNode /// Get a [tree](#common.Tree) for this node. Will allocate one if it /// points into a buffer. toTree(): Tree /// Get the first child of the given type (which may be a [node /// name](#common.NodeType.name) or a [group /// name](#common.NodeProp^group)). If `before` is non-null, only /// return children that occur somewhere after a node with that name /// or group. If `after` is non-null, only return children that /// occur somewhere before a node with that name or group. getChild(type: string | number, before?: string | number | null, after?: string | number | null): SyntaxNode | null /// Like [`getChild`](#common.SyntaxNode.getChild), but return all /// matching children, not just the first. getChildren(type: string | number, before?: string | number | null, after?: string | number | null): SyntaxNode[] } const enum Side { Before = -2, AtOrBefore = -1, Around = 0, AtOrAfter = 1, After = 2, DontCare = 4 } function checkSide(side: Side, pos: number, from: number, to: number) { switch (side) { case Side.Before: return from < pos case Side.AtOrBefore: return to >= pos && from < pos case Side.Around: return from < pos && to > pos case Side.AtOrAfter: return from <= pos && to > pos case Side.After: return to > pos case Side.DontCare: return true } } function resolveNode(node: SyntaxNode, pos: number, side: -1 | 0 | 1, overlays: boolean): SyntaxNode { // Move up to a node that actually holds the position, if possible while (node.from == node.to || (side < 1 ? node.from >= pos : node.from > pos) || (side > -1 ? node.to <= pos : node.to < pos)) { let parent = !overlays && node instanceof TreeNode && node.index < 0 ? null : node.parent if (!parent) return node node = parent } let mode = overlays ? 0 : IterMode.IgnoreOverlays // Must go up out of overlays when those do not overlap with pos if (overlays) for (let scan: SyntaxNode | null = node, parent = scan.parent; parent; scan = parent, parent = scan.parent) { if (scan instanceof TreeNode && scan.index < 0 && parent.enter(pos, side, mode)?.from != scan.from) node = parent } for (;;) { let inner = node.enter(pos, side, mode) if (!inner) return node node = inner } } abstract class BaseNode implements SyntaxNode { abstract from: number abstract to: number abstract type: NodeType abstract name: string abstract tree: Tree | null abstract parent: SyntaxNode | null abstract firstChild: SyntaxNode | null abstract lastChild: SyntaxNode | null abstract childAfter(pos: number): SyntaxNode | null abstract childBefore(pos: number): SyntaxNode | null abstract enter(pos: number, side: -1 | 0 | 1, mode?: IterMode): SyntaxNode | null abstract nextSibling: SyntaxNode | null abstract prevSibling: SyntaxNode | null abstract toTree(): Tree cursor(mode: IterMode = 0 as IterMode) { return new TreeCursor(this as any, mode) } getChild(type: string | number, before: string | number | null = null, after: string | number | null = null) { let r = getChildren(this, type, before, after) return r.length ? r[0] : null } getChildren(type: string | number, before: string | number | null = null, after: string | number | null = null): SyntaxNode[] { return getChildren(this, type, before, after) } resolve(pos: number, side: -1 | 0 | 1 = 0): SyntaxNode { return resolveNode(this, pos, side, false) } resolveInner(pos: number, side: -1 | 0 | 1 = 0): SyntaxNode { return resolveNode(this, pos, side, true) } matchContext(context: readonly string[]): boolean { return matchNodeContext(this, context) } enterUnfinishedNodesBefore(pos: number) { let scan = this.childBefore(pos), node: SyntaxNode = this while (scan) { let last = scan.lastChild if (!last || last.to != scan.to) break if (last.type.isError && last.from == last.to) { node = scan scan = last.prevSibling } else { scan = last } } return node } get node() { return this } get next() { return this.parent } } export class TreeNode extends BaseNode implements SyntaxNode { constructor(readonly _tree: Tree, readonly from: number, // Index in parent node, set to -1 if the node is not a direct child of _parent.node (overlay) readonly index: number, readonly _parent: TreeNode | null) { super() } get type() { return this._tree.type } get name() { return this._tree.type.name } get to() { return this.from + this._tree.length } nextChild(i: number, dir: 1 | -1, pos: number, side: Side, mode: IterMode = 0 as IterMode): TreeNode | BufferNode | null { for (let parent: TreeNode = this;;) { for (let {children, positions} = parent._tree, e = dir > 0 ? children.length : -1; i != e; i += dir) { let next = children[i], start = positions[i] + parent.from if (!checkSide(side, pos, start, start + next.length)) continue if (next instanceof TreeBuffer) { if (mode & IterMode.ExcludeBuffers) continue let index = next.findChild(0, next.buffer.length, dir, pos - start, side) if (index > -1) return new BufferNode(new BufferContext(parent, next, i, start), null, index) } else if ((mode & IterMode.IncludeAnonymous) || (!next.type.isAnonymous || hasChild(next))) { let mounted if (!(mode & IterMode.IgnoreMounts) && (mounted = MountedTree.get(next)) && !mounted.overlay) return new TreeNode(mounted.tree, start, i, parent) let inner = new TreeNode(next, start, i, parent) return (mode & IterMode.IncludeAnonymous) || !inner.type.isAnonymous ? inner : inner.nextChild(dir < 0 ? next.children.length - 1 : 0, dir, pos, side) } } if ((mode & IterMode.IncludeAnonymous) || !parent.type.isAnonymous) return null if (parent.index >= 0) i = parent.index + dir else i = dir < 0 ? -1 : parent._parent!._tree.children.length parent = parent._parent! if (!parent) return null } } get firstChild() { return this.nextChild(0, 1, 0, Side.DontCare) } get lastChild() { return this.nextChild(this._tree.children.length - 1, -1, 0, Side.DontCare) } childAfter(pos: number) { return this.nextChild(0, 1, pos, Side.After) } childBefore(pos: number) { return this.nextChild(this._tree.children.length - 1, -1, pos, Side.Before) } enter(pos: number, side: -1 | 0 | 1, mode = 0) { let mounted if (!(mode & IterMode.IgnoreOverlays) && (mounted = MountedTree.get(this._tree)) && mounted.overlay) { let rPos = pos - this.from for (let {from, to} of mounted.overlay) { if ((side > 0 ? from <= rPos : from < rPos) && (side < 0 ? to >= rPos : to > rPos)) return new TreeNode(mounted.tree, mounted.overlay[0].from + this.from, -1, this) } } return this.nextChild(0, 1, pos, side, mode) } nextSignificantParent() { let val: TreeNode = this while (val.type.isAnonymous && val._parent) val = val._parent return val } get parent(): TreeNode | null { return this._parent ? this._parent.nextSignificantParent() : null } get nextSibling(): SyntaxNode | null { return this._parent && this.index >= 0 ? this._parent.nextChild(this.index + 1, 1, 0, Side.DontCare) : null } get prevSibling(): SyntaxNode | null { return this._parent && this.index >= 0 ? this._parent.nextChild(this.index - 1, -1, 0, Side.DontCare) : null } get tree() { return this._tree } toTree() { return this._tree } /// @internal toString() { return this._tree.toString() } } function getChildren(node: SyntaxNode, type: string | number, before: string | number | null, after: string | number | null): SyntaxNode[] { let cur = node.cursor(), result: SyntaxNode[] = [] if (!cur.firstChild()) return result if (before != null) for (let found = false; !found;) { found = cur.type.is(before) if (!cur.nextSibling()) return result } for (;;) { if (after != null && cur.type.is(after)) return result if (cur.type.is(type)) result.push(cur.node) if (!cur.nextSibling()) return after == null ? result : [] } } function matchNodeContext(node: SyntaxNode, context: readonly string[], i = context.length - 1): boolean { for (let p: SyntaxNode | null = node.parent; i >= 0; p = p.parent) { if (!p) return false if (!p.type.isAnonymous) { if (context[i] && context[i] != p.name) return false i-- } } return true } class BufferContext { constructor(readonly parent: TreeNode, readonly buffer: TreeBuffer, readonly index: number, readonly start: number) {} } export class BufferNode extends BaseNode { type: NodeType get name() { return this.type.name } get from() { return this.context.start + this.context.buffer.buffer[this.index + 1] } get to() { return this.context.start + this.context.buffer.buffer[this.index + 2] } constructor(readonly context: BufferContext, readonly _parent: BufferNode | null, readonly index: number) { super() this.type = context.buffer.set.types[context.buffer.buffer[index]] } child(dir: 1 | -1, pos: number, side: Side): BufferNode | null { let {buffer} = this.context let index = buffer.findChild(this.index + 4, buffer.buffer[this.index + 3], dir, pos - this.context.start, side) return index < 0 ? null : new BufferNode(this.context, this, index) } get firstChild() { return this.child(1, 0, Side.DontCare) } get lastChild() { return this.child(-1, 0, Side.DontCare) } childAfter(pos: number) { return this.child(1, pos, Side.After) } childBefore(pos: number) { return this.child(-1, pos, Side.Before) } enter(pos: number, side: -1 | 0 | 1, mode: IterMode = 0 as IterMode) { if (mode & IterMode.ExcludeBuffers) return null let {buffer} = this.context let index = buffer.findChild(this.index + 4, buffer.buffer[this.index + 3], side > 0 ? 1 : -1, pos - this.context.start, side) return index < 0 ? null : new BufferNode(this.context, this, index) } get parent(): SyntaxNode | null { return this._parent || this.context.parent.nextSignificantParent() } externalSibling(dir: 1 | -1) { return this._parent ? null : this.context.parent.nextChild(this.context.index + dir, dir, 0, Side.DontCare) } get nextSibling(): SyntaxNode | null { let {buffer} = this.context let after = buffer.buffer[this.index + 3] if (after < (this._parent ? buffer.buffer[this._parent.index + 3] : buffer.buffer.length)) return new BufferNode(this.context, this._parent, after) return this.externalSibling(1) } get prevSibling(): SyntaxNode | null { let {buffer} = this.context let parentStart = this._parent ? this._parent.index + 4 : 0 if (this.index == parentStart) return this.externalSibling(-1) return new BufferNode(this.context, this._parent, buffer.findChild(parentStart, this.index, -1, 0, Side.DontCare)) } get tree() { return null } toTree() { let children = [], positions = [] let {buffer} = this.context let startI = this.index + 4, endI = buffer.buffer[this.index + 3] if (endI > startI) { let from = buffer.buffer[this.index + 1] children.push(buffer.slice(startI, endI, from)) positions.push(0) } return new Tree(this.type, children, positions, this.to - this.from) } /// @internal toString() { return this.context.buffer.childString(this.index) } } function iterStack(heads: readonly SyntaxNode[]): NodeIterator | null { if (!heads.length) return null let pick = 0, picked = heads[0] for (let i = 1; i < heads.length; i++) { let node = heads[i] if (node.from > picked.from || node.to < picked.to) { picked = node; pick = i } } let next = picked instanceof TreeNode && picked.index < 0 ? null : picked.parent let newHeads = heads.slice() if (next) newHeads[pick] = next else newHeads.splice(pick, 1) return new StackIterator(newHeads, picked) } class StackIterator implements NodeIterator { constructor(readonly heads: readonly SyntaxNode[], readonly node: SyntaxNode) {} get next() { return iterStack(this.heads) } } function stackIterator(tree: Tree, pos: number, side: -1 | 0 | 1): NodeIterator { let inner = tree.resolveInner(pos, side), layers: SyntaxNode[] | null = null for (let scan: TreeNode | null = inner instanceof TreeNode ? inner : (inner as BufferNode).context.parent; scan; scan = scan.parent) { if (scan.index < 0) { // This is an overlay root let parent: TreeNode | null = scan.parent! ;(layers || (layers = [inner])).push(parent.resolve(pos, side)) scan = parent } else { let mount = MountedTree.get(scan.tree) // Relevant overlay branching off if (mount && mount.overlay && mount.overlay[0].from <= pos && mount.overlay[mount.overlay.length - 1].to >= pos) { let root = new TreeNode(mount.tree, mount.overlay[0].from + scan.from, -1, scan) ;(layers || (layers = [inner])).push(resolveNode(root, pos, side, false)) } } } return layers ? iterStack(layers) : inner as any } /// A tree cursor object focuses on a given node in a syntax tree, and /// allows you to move to adjacent nodes. export class TreeCursor implements SyntaxNodeRef { /// The node's type. type!: NodeType /// Shorthand for `.type.name`. get name() { return this.type.name } /// The start source offset of this node. from!: number /// The end source offset. to!: number /// @internal _tree!: TreeNode /// @internal buffer: BufferContext | null = null private stack: number[] = [] /// @internal index: number = 0 private bufferNode: BufferNode | null = null /// @internal constructor( node: TreeNode | BufferNode, /// @internal readonly mode = 0 ) { if (node instanceof TreeNode) { this.yieldNode(node) } else { this._tree = node.context.parent this.buffer = node.context for (let n: BufferNode | null = node._parent; n; n = n._parent) this.stack.unshift(n.index) this.bufferNode = node this.yieldBuf(node.index) } } private yieldNode(node: TreeNode | null) { if (!node) return false this._tree = node this.type = node.type this.from = node.from this.to = node.to return true } private yieldBuf(index: number, type?: NodeType) { this.index = index let {start, buffer} = this.buffer! this.type = type || buffer.set.types[buffer.buffer[index]] this.from = start + buffer.buffer[index + 1] this.to = start + buffer.buffer[index + 2] return true } /// @internal yield(node: TreeNode | BufferNode | null) { if (!node) return false if (node instanceof TreeNode) { this.buffer = null return this.yieldNode(node) } this.buffer = node.context return this.yieldBuf(node.index, node.type) } /// @internal toString() { return this.buffer ? this.buffer.buffer.childString(this.index) : this._tree.toString() } /// @internal enterChild(dir: 1 | -1, pos: number, side: Side) { if (!this.buffer) return this.yield(this._tree.nextChild(dir < 0 ? this._tree._tree.children.length - 1 : 0, dir, pos, side, this.mode)) let {buffer} = this.buffer let index = buffer.findChild(this.index + 4, buffer.buffer[this.index + 3], dir, pos - this.buffer.start, side) if (index < 0) return false this.stack.push(this.index) return this.yieldBuf(index) } /// Move the cursor to this node's first child. When this returns /// false, the node has no child, and the cursor has not been moved. firstChild() { return this.enterChild(1, 0, Side.DontCare) } /// Move the cursor to this node's last child. lastChild() { return this.enterChild(-1, 0, Side.DontCare) } /// Move the cursor to the first child that ends after `pos`. childAfter(pos: number) { return this.enterChild(1, pos, Side.After) } /// Move to the last child that starts before `pos`. childBefore(pos: number) { return this.enterChild(-1, pos, Side.Before) } /// Move the cursor to the child around `pos`. If side is -1 the /// child may end at that position, when 1 it may start there. This /// will also enter [overlaid](#common.MountedTree.overlay) /// [mounted](#common.NodeProp^mounted) trees unless `overlays` is /// set to false. enter(pos: number, side: -1 | 0 | 1, mode: IterMode = this.mode) { if (!this.buffer) return this.yield(this._tree.enter(pos, side, mode)) return mode & IterMode.ExcludeBuffers ? false : this.enterChild(1, pos, side) } /// Move to the node's parent node, if this isn't the top node. parent() { if (!this.buffer) return this.yieldNode((this.mode & IterMode.IncludeAnonymous) ? this._tree._parent : this._tree.parent) if (this.stack.length) return this.yieldBuf(this.stack.pop()!) let parent = (this.mode & IterMode.IncludeAnonymous) ? this.buffer.parent : this.buffer.parent.nextSignificantParent() this.buffer = null return this.yieldNode(parent) } /// @internal sibling(dir: 1 | -1) { if (!this.buffer) return !this._tree._parent ? false : this.yield(this._tree.index < 0 ? null : this._tree._parent.nextChild(this._tree.index + dir, dir, 0, Side.DontCare, this.mode)) let {buffer} = this.buffer, d = this.stack.length - 1 if (dir < 0) { let parentStart = d < 0 ? 0 : this.stack[d] + 4 if (this.index != parentStart) return this.yieldBuf(buffer.findChild(parentStart, this.index, -1, 0, Side.DontCare)) } else { let after = buffer.buffer[this.index + 3] if (after < (d < 0 ? buffer.buffer.length : buffer.buffer[this.stack[d] + 3])) return this.yieldBuf(after) } return d < 0 ? this.yield(this.buffer.parent.nextChild(this.buffer.index + dir, dir, 0, Side.DontCare, this.mode)) : false } /// Move to this node's next sibling, if any. nextSibling() { return this.sibling(1) } /// Move to this node's previous sibling, if any. prevSibling() { return this.sibling(-1) } private atLastNode(dir: 1 | -1) { let index, parent: TreeNode | null, {buffer} = this if (buffer) { if (dir > 0) { if (this.index < buffer.buffer.buffer.length) return false } else { for (let i = 0; i < this.index; i++) if (buffer.buffer.buffer[i + 3] < this.index) return false } ;({index, parent} = buffer) } else { ({index, _parent: parent} = this._tree) } for (; parent; {index, _parent: parent} = parent) { if (index > -1) for (let i = index + dir, e = dir < 0 ? -1 : parent._tree.children.length; i != e; i += dir) { let child = parent._tree.children[i] if ((this.mode & IterMode.IncludeAnonymous) || child instanceof TreeBuffer || !child.type.isAnonymous || hasChild(child)) return false } } return true } private move(dir: 1 | -1, enter: boolean) { if (enter && this.enterChild(dir, 0, Side.DontCare)) return true for (;;) { if (this.sibling(dir)) return true if (this.atLastNode(dir) || !this.parent()) return false } } /// Move to the next node in a /// [pre-order](https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR) /// traversal, going from a node to its first child or, if the /// current node is empty or `enter` is false, its next sibling or /// the next sibling of the first parent node that has one. next(enter = true) { return this.move(1, enter) } /// Move to the next node in a last-to-first pre-order traveral. A /// node is followed by its last child or, if it has none, its /// previous sibling or the previous sibling of the first parent /// node that has one. prev(enter = true) { return this.move(-1, enter) } /// Move the cursor to the innermost node that covers `pos`. If /// `side` is -1, it will enter nodes that end at `pos`. If it is 1, /// it will enter nodes that start at `pos`. moveTo(pos: number, side: -1 | 0 | 1 = 0) { // Move up to a node that actually holds the position, if possible while (this.from == this.to || (side < 1 ? this.from >= pos : this.from > pos) || (side > -1 ? this.to <= pos : this.to < pos)) if (!this.parent()) break // Then scan down into child nodes as far as possible while (this.enterChild(1, pos, side)) {} return this } /// Get a [syntax node](#common.SyntaxNode) at the cursor's current /// position. get node(): SyntaxNode { if (!this.buffer) return this._tree let cache = this.bufferNode, result: BufferNode | null = null, depth = 0 if (cache && cache.context == this.buffer) { scan: for (let index = this.index, d = this.stack.length; d >= 0;) { for (let c: BufferNode | null = cache; c; c = c._parent) if (c.index == index) { if (index == this.index) return c result = c depth = d + 1 break scan } index = this.stack[--d] } } for (let i = depth; i < this.stack.length; i++) result = new BufferNode(this.buffer, result, this.stack[i]) return this.bufferNode = new BufferNode(this.buffer, result, this.index) } /// Get the [tree](#common.Tree) that represents the current node, if /// any. Will return null when the node is in a [tree /// buffer](#common.TreeBuffer). get tree(): Tree | null { return this.buffer ? null : this._tree._tree } /// Iterate over the current node and all its descendants, calling /// `enter` when entering a node and `leave`, if given, when leaving /// one. When `enter` returns `false`, any children of that node are /// skipped, and `leave` isn't called for it. iterate(enter: (node: SyntaxNodeRef) => boolean | void, leave?: (node: SyntaxNodeRef) => void) { for (let depth = 0;;) { let mustLeave = false if (this.type.isAnonymous || enter(this) !== false) { if (this.firstChild()) { depth++; continue } if (!this.type.isAnonymous) mustLeave = true } for (;;) { if (mustLeave && leave) leave(this) mustLeave = this.type.isAnonymous if (this.nextSibling()) break if (!depth) return this.parent() depth-- mustLeave = true } } } /// Test whether the current node matches a given context—a sequence /// of direct parent node names. Empty strings in the context array /// are treated as wildcards. matchContext(context: readonly string[]): boolean { if (!this.buffer) return matchNodeContext(this.node, context) let {buffer} = this.buffer, {types} = buffer.set for (let i = context.length - 1, d = this.stack.length - 1; i >= 0; d--) { if (d < 0) return matchNodeContext(this.node, context, i) let type = types[buffer.buffer[this.stack[d]]] if (!type.isAnonymous) { if (context[i] && context[i] != type.name) return false i-- } } return true } } function hasChild(tree: Tree): boolean { return tree.children.some(ch => ch instanceof TreeBuffer || !ch.type.isAnonymous || hasChild(ch)) } const enum Balance { BranchFactor = 8 } const enum CutOff { Depth = 2500 } const enum SpecialRecord { Reuse = -1, ContextChange = -3, LookAhead = -4 } function buildTree(data: BuildData) { let {buffer, nodeSet, maxBufferLength = DefaultBufferLength, reused = [], minRepeatType = nodeSet.types.length} = data let cursor = Array.isArray(buffer) ? new FlatBufferCursor(buffer, buffer.length) : buffer as BufferCursor let types = nodeSet.types let contextHash = 0, lookAhead = 0 function takeNode(parentStart: number, minPos: number, children: (Tree | TreeBuffer)[], positions: number[], inRepeat: number, depth: number) { let {id, start, end, size} = cursor let lookAheadAtStart = lookAhead while (size < 0) { cursor.next() if (size == SpecialRecord.Reuse) { let node = reused[id] children.push(node) positions.push(start - parentStart) return } else if (size == SpecialRecord.ContextChange) { // Context change contextHash = id return } else if (size == SpecialRecord.LookAhead) { lookAhead = id return } else { throw new RangeError(`Unrecognized record size: ${size}`) } ;({id, start, end, size} = cursor) } let type = types[id], node, buffer: {size: number, start: number, skip: number} | undefined let startPos = start - parentStart if (end - start <= maxBufferLength && (buffer = findBufferSize(cursor.pos - minPos, inRepeat))) { // Small enough for a buffer, and no reused nodes inside let data = new Uint16Array(buffer.size - buffer.skip) let endPos = cursor.pos - buffer.size, index = data.length while (cursor.pos > endPos) index = copyToBuffer(buffer.start, data, index) node = new TreeBuffer(data, end - buffer.start, nodeSet) startPos = buffer.start - parentStart } else { // Make it a node let endPos = cursor.pos - size cursor.next() let localChildren: (Tree | TreeBuffer)[] = [], localPositions: number[] = [] let localInRepeat = id >= minRepeatType ? id : -1 let lastGroup = 0, lastEnd = end while (cursor.pos > endPos) { if (localInRepeat >= 0 && cursor.id == localInRepeat && cursor.size >= 0) { if (cursor.end <= lastEnd - maxBufferLength) { makeRepeatLeaf(localChildren, localPositions, start, lastGroup, cursor.end, lastEnd, localInRepeat, lookAheadAtStart) lastGroup = localChildren.length lastEnd = cursor.end } cursor.next() } else if (depth > CutOff.Depth) { takeFlatNode(start, endPos, localChildren, localPositions) } else { takeNode(start, endPos, localChildren, localPositions, localInRepeat, depth + 1) } } if (localInRepeat >= 0 && lastGroup > 0 && lastGroup < localChildren.length) makeRepeatLeaf(localChildren, localPositions, start, lastGroup, start, lastEnd, localInRepeat, lookAheadAtStart) localChildren.reverse(); localPositions.reverse() if (localInRepeat > -1 && lastGroup > 0) { let make = makeBalanced(type) node = balanceRange(type, localChildren, localPositions, 0, localChildren.length, 0, end - start, make, make) } else { node = makeTree(type, localChildren, localPositions, end - start, lookAheadAtStart - end) } } children.push(node) positions.push(startPos) } function takeFlatNode(parentStart: number, minPos: number, children: (Tree | TreeBuffer)[], positions: number[]) { let nodes = [] // Temporary, inverted array of leaf nodes found, with absolute positions let nodeCount = 0, stopAt = -1 while (cursor.pos > minPos) { let {id, start, end, size} = cursor if (size > 4) { // Not a leaf cursor.next() } else if (stopAt > -1 && start < stopAt) { break } else { if (stopAt < 0) stopAt = end - maxBufferLength nodes.push(id, start, end) nodeCount++ cursor.next() } } if (nodeCount) { let buffer = new Uint16Array(nodeCount * 4) let start = nodes[nodes.length - 2] for (let i = nodes.length - 3, j = 0; i >= 0; i -= 3) { buffer[j++] = nodes[i] buffer[j++] = nodes[i + 1] - start buffer[j++] = nodes[i + 2] - start buffer[j++] = j } children.push(new TreeBuffer(buffer, nodes[2] - start, nodeSet)) positions.push(start - parentStart) } } function makeBalanced(type: NodeType) { return (children: readonly (Tree | TreeBuffer)[], positions: readonly number[], length: number) => { let lookAhead = 0, lastI = children.length - 1, last, lookAheadProp if (lastI >= 0 && (last = children[lastI]) instanceof Tree) { if (!lastI && last.type == type && last.length == length) return last if (lookAheadProp = last.prop(NodeProp.lookAhead)) lookAhead = positions[lastI] + last.length + lookAheadProp } return makeTree(type, children, positions, length, lookAhead) } } function makeRepeatLeaf(children: (Tree | TreeBuffer)[], positions: number[], base: number, i: number, from: number, to: number, type: number, lookAhead: number) { let localChildren = [], localPositions = [] while (children.length > i) { localChildren.push(children.pop()!); localPositions.push(positions.pop()! + base - from) } children.push(makeTree(nodeSet.types[type], localChildren, localPositions, to - from, lookAhead - to)) positions.push(from - base) } function makeTree(type: NodeType, children: readonly (Tree | TreeBuffer)[], positions: readonly number[], length: number, lookAhead: number = 0, props?: readonly [number | NodeProp, any][]) { if (contextHash) { let pair: [number | NodeProp, any] = [NodeProp.contextHash, contextHash] props = props ? [pair].concat(props) : [pair] } if (lookAhead > 25) { let pair: [number | NodeProp, any] = [NodeProp.lookAhead, lookAhead] props = props ? [pair].concat(props) : [pair] } return new Tree(type, children, positions, length, props) } function findBufferSize(maxSize: number, inRepeat: number) { // Scan through the buffer to find previous siblings that fit // together in a TreeBuffer, and don't contain any reused nodes // (which can't be stored in a buffer). // If `inRepeat` is > -1, ignore node boundaries of that type for // nesting, but make sure the end falls either at the start // (`maxSize`) or before such a node. let fork = cursor.fork() let size = 0, start = 0, skip = 0, minStart = fork.end - maxBufferLength let result = {size: 0, start: 0, skip: 0} scan: for (let minPos = fork.pos - maxSize; fork.pos > minPos;) { let nodeSize = fork.size // Pretend nested repeat nodes of the same type don't exist if (fork.id == inRepeat && nodeSize >= 0) { // Except that we store the current state as a valid return // value. result.size = size; result.start = start; result.skip = skip skip += 4; size += 4 fork.next() continue } let startPos = fork.pos - nodeSize if (nodeSize < 0 || startPos < minPos || fork.start < minStart) break let localSkipped = fork.id >= minRepeatType ? 4 : 0 let nodeStart = fork.start fork.next() while (fork.pos > startPos) { if (fork.size < 0) { if (fork.size == SpecialRecord.ContextChange) localSkipped += 4 else break scan } else if (fork.id >= minRepeatType) { localSkipped += 4 } fork.next() } start = nodeStart size += nodeSize skip += localSkipped } if (inRepeat < 0 || size == maxSize) { result.size = size; result.start = start; result.skip = skip } return result.size > 4 ? result : undefined } function copyToBuffer(bufferStart: number, buffer: Uint16Array, index: number): number { let {id, start, end, size} = cursor cursor.next() if (size >= 0 && id < minRepeatType) { let startIndex = index if (size > 4) { let endPos = cursor.pos - (size - 4) while (cursor.pos > endPos) index = copyToBuffer(bufferStart, buffer, index) } buffer[--index] = startIndex buffer[--index] = end - bufferStart buffer[--index] = start - bufferStart buffer[--index] = id } else if (size == SpecialRecord.ContextChange) { contextHash = id } else if (size == SpecialRecord.LookAhead) { lookAhead = id } return index } let children: (Tree | TreeBuffer)[] = [], positions: number[] = [] while (cursor.pos > 0) takeNode(data.start || 0, data.bufferStart || 0, children, positions, -1, 0) let length = data.length ?? (children.length ? positions[0] + children[0].length : 0) return new Tree(types[data.topID], children.reverse(), positions.reverse(), length) } const nodeSizeCache: WeakMap = new WeakMap function nodeSize(balanceType: NodeType, node: Tree | TreeBuffer): number { if (!balanceType.isAnonymous || node instanceof TreeBuffer || node.type != balanceType) return 1 let size = nodeSizeCache.get(node) if (size == null) { size = 1 for (let child of node.children) { if (child.type != balanceType || !(child instanceof Tree)) { size = 1 break } size += nodeSize(balanceType, child) } nodeSizeCache.set(node, size) } return size } function balanceRange( // The type the balanced tree's inner nodes. balanceType: NodeType, // The direct children and their positions children: readonly (Tree | TreeBuffer)[], positions: readonly number[], // The index range in children/positions to use from: number, to: number, // The start position of the nodes, relative to their parent. start: number, // Length of the outer node length: number, // Function to build the top node of the balanced tree mkTop: ((children: readonly (Tree | TreeBuffer)[], positions: readonly number[], length: number) => Tree) | null, // Function to build internal nodes for the balanced tree mkTree: (children: readonly (Tree | TreeBuffer)[], positions: readonly number[], length: number) => Tree ): Tree { let total = 0 for (let i = from; i < to; i++) total += nodeSize(balanceType, children[i]) let maxChild = Math.ceil((total * 1.5) / Balance.BranchFactor) let localChildren: (Tree | TreeBuffer)[] = [], localPositions: number[] = [] function divide(children: readonly (Tree | TreeBuffer)[], positions: readonly number[], from: number, to: number, offset: number) { for (let i = from; i < to;) { let groupFrom = i, groupStart = positions[i], groupSize = nodeSize(balanceType, children[i]) i++ for (; i < to; i++) { let nextSize = nodeSize(balanceType, children[i]) if (groupSize + nextSize >= maxChild) break groupSize += nextSize } if (i == groupFrom + 1) { if (groupSize > maxChild) { let only = children[groupFrom] as Tree // Only trees can have a size > 1 divide(only.children, only.positions, 0, only.children.length, positions[groupFrom] + offset) continue } localChildren.push(children[groupFrom]) } else { let length = positions[i - 1] + children[i - 1].length - groupStart localChildren.push(balanceRange(balanceType, children, positions, groupFrom, i, groupStart, length, null, mkTree)) } localPositions.push(groupStart + offset - start) } } divide(children, positions, from, to, 0) return (mkTop || mkTree)(localChildren, localPositions, length) } /// Provides a way to associate values with pieces of trees. As long /// as that part of the tree is reused, the associated values can be /// retrieved from an updated tree. export class NodeWeakMap { private map = new WeakMap>() private setBuffer(buffer: TreeBuffer, index: number, value: T) { let inner = this.map.get(buffer) as Map | undefined if (!inner) this.map.set(buffer, inner = new Map) inner.set(index, value) } private getBuffer(buffer: TreeBuffer, index: number): T | undefined { let inner = this.map.get(buffer) as Map | undefined return inner && inner.get(index) } /// Set the value for this syntax node. set(node: SyntaxNode, value: T) { if (node instanceof BufferNode) this.setBuffer(node.context.buffer, node.index, value) else if (node instanceof TreeNode) this.map.set(node.tree, value) } /// Retrieve value for this syntax node, if it exists in the map. get(node: SyntaxNode): T | undefined { return node instanceof BufferNode ? this.getBuffer(node.context.buffer, node.index) : node instanceof TreeNode ? this.map.get(node.tree) as T | undefined : undefined } /// Set the value for the node that a cursor currently points to. cursorSet(cursor: TreeCursor, value: T) { if (cursor.buffer) this.setBuffer(cursor.buffer.buffer, cursor.index, value) else this.map.set(cursor.tree!, value) } /// Retrieve the value for the node that a cursor currently points /// to. cursorGet(cursor: TreeCursor): T | undefined { return cursor.buffer ? this.getBuffer(cursor.buffer.buffer, cursor.index) : this.map.get(cursor.tree!) as T | undefined } } common-1.2.1/test/000077500000000000000000000000001455145755000137755ustar00rootroot00000000000000common-1.2.1/test/test-tree.ts000066400000000000000000000223521455145755000162650ustar00rootroot00000000000000import {Tree, NodeSet, NodeType, SyntaxNode, NodeProp, IterMode} from "../dist/index.js" import ist from "ist" let types = "T a b c Pa Br".split(" ").map((s, i) => NodeType.define({ id: i, name: s, props: /^[abc]$/.test(s) ? [[NodeProp.group, ["atom"]]] : [] })) let repeat = NodeType.define({id: types.length}) types.push(repeat) let nodeSet = new NodeSet(types) function id(n: string) { return types.find(x => x.name == n)!.id } function mk(spec: string) { let starts: number[] = [], buffer: number[] = [] for (let pos = 0; pos < spec.length;) { let [m, letters, open, close] = /^(?:([abc])|([\[\(])|([\]\)]))/.exec(spec.slice(pos))! if (letters) { let bufStart = buffer.length for (let i = 0; i < letters.length; i++) { buffer.push(id(letters[i]), pos + i, pos + i + 1, 4) if (i) buffer.push(repeat.id, pos, pos + i + 1, (buffer.length + 4) - bufStart) } } else if (open) { starts.push(buffer.length, pos) } else { let start = starts.pop()!, startOff = starts.pop()! buffer.push(id(close == ")" ? "Pa" : "Br"), start, pos + 1, (buffer.length + 4) - startOff) } pos += m.length } return Tree.build({buffer, nodeSet, topID: 0, maxBufferLength: 10, minRepeatType: repeat.id}) } let _recur: Tree | null = null function recur() { return _recur || (_recur = mk(function build(depth: number): string { if (depth) { let inner = build(depth - 1) return "(" + inner + ")[" + inner + "]" } else { let result = "" for (let i = 0; i < 20; i++) result += "abc"[i % 3] return result } }(6))) } let _simple: Tree | null = null function simple() { return _simple || (_simple = mk("aaaa(bbb[ccc][aaa][()])")) } const anonTree = new Tree(nodeSet.types[0], [ new Tree(NodeType.none, [ new Tree(nodeSet.types[1], [], [], 1), new Tree(nodeSet.types[2], [], [], 1) ], [0, 1], 2), ], [0], 2) describe("SyntaxNode", () => { it("can resolve at the top level", () => { let c = simple().resolve(2, -1) ist(c.from, 1) ist(c.to, 2) ist(c.name, "a") ist(c.parent!.name, "T") ist(!c.parent!.parent) c = simple().resolve(2, 1) ist(c.from, 2) ist(c.to, 3) c = simple().resolve(2) ist(c.name, "T") ist(c.from, 0) ist(c.to, 23) }) it("can resolve deeper", () => { let c = simple().resolve(10, 1) ist(c.name, "c") ist(c.from, 10) ist(c.parent!.name, "Br") ist(c.parent!.parent!.name, "Pa") ist(c.parent!.parent!.parent!.name, "T") }) it("can resolve in a large tree", () => { let c: SyntaxNode | null = recur().resolve(10, 1), depth = 1 while (c = c && c.parent) depth++ ist(depth, 8) }) it("caches resolved parents", () => { let a = recur().resolve(3, 1), b = recur().resolve(3, 1) ist(a, b) }) describe("getChild", () => { function flat(children: readonly SyntaxNode[]) { return children.map(c => c.name).join(",") } it("can get children by group", () => { let tree = mk("aa(bb)[aabbcc]").topNode ist(flat(tree.getChildren("atom")), "a,a") ist(flat(tree.firstChild!.getChildren("atom")), "") ist(flat(tree.lastChild!.getChildren("atom")), "a,a,b,b,c,c") }) it("can get single children", () => { let tree = mk("abc()").topNode ist(tree.getChild("Br"), null) ist(tree.getChild("Pa")?.name, "Pa") }) it("can get children between others", () => { let tree = mk("aa(bb)[aabbcc]").topNode ist(tree.getChild("Pa", "atom", "Br")) ist(!tree.getChild("Pa", "atom", "atom")) let last = tree.lastChild! ist(flat(last.getChildren("b", "a", "c")), "b,b") ist(flat(last.getChildren("a", null, "c")), "a,a") ist(flat(last.getChildren("c", "b", null)), "c,c") ist(flat(last.getChildren("b", "c")), "") }) }) it("skips anonymous nodes", () => { ist(anonTree + "", "T(a,b)") ist(anonTree.resolve(1).name, "T") ist(anonTree.topNode.lastChild!.name, "b") ist(anonTree.topNode.firstChild!.name, "a") ist(anonTree.topNode.childAfter(1)!.name, "b") }) it("allows access to the underlying tree", () => { let tree = mk("aaa[bbbbb(bb)bbbbbbb]aaa") let node = tree.topNode.firstChild! while (node.name != "Br") node = node.nextSibling! ist(node.tree instanceof Tree) ist(node.tree!.type.name, "Br") node = node.firstChild! while (node.name != "Pa") node = node.nextSibling! ist(!node.tree) ist(node.toTree().toString(), "Pa(b,b)") node = node.firstChild! ist(node.name, "b") ist(node.toTree().toString(), "b") ist(node.toTree().children.length, 0) }) }) describe("TreeCursor", () => { const simpleCount: Record = {a: 7, b: 3, c: 3, Br: 3, Pa: 2, T: 1} it("iterates over all nodes", () => { let count: Record = Object.create(null) let pos = 0, cur = simple().cursor() do { ist(cur.from, pos, ">=") pos = cur.from count[cur.name] = (count[cur.name] || 0) + 1 } while (cur.next()) for (let k of Object.keys(simpleCount)) ist(count[k], simpleCount[k]) }) it("iterates over all nodes in reverse", () => { let count: Record = Object.create(null) let pos = 100, cur = simple().cursor() do { ist(cur.to, pos, "<=") pos = cur.to count[cur.name] = (count[cur.name] || 0) + 1 } while (cur.prev()) for (let k of Object.keys(simpleCount)) ist(count[k], simpleCount[k]) }) it("works with internal iteration", () => { let openCount: Record = Object.create(null) let closeCount: Record = Object.create(null) simple().iterate({ enter(t) { openCount[t.name] = (openCount[t.name] || 0) + 1 }, leave(t) { closeCount[t.name] = (closeCount[t.name] || 0) + 1 } }) for (let k of Object.keys(simpleCount)) { ist(openCount[k], simpleCount[k]) ist(closeCount[k], simpleCount[k]) } }) it("handles iterating out of bounds", () => { let hit = 0 Tree.empty.iterate({ from: 0, to: 200, enter() { hit++ }, leave() { hit++ } }) ist(hit, 0) }) it("internal iteration can be limited to a range", () => { let seen: string[] = [] simple().iterate({ enter(t) { seen.push(t.name); return t.name == "Br" ? false : undefined }, from: 3, to: 14 }) ist(seen.join(","), "T,a,a,Pa,b,b,b,Br,Br") }) it("can leave nodes", () => { let cur = simple().cursor() ist(!cur.parent()) cur.next(); cur.next() ist(cur.from, 1) ist(cur.parent()) ist(cur.from, 0) for (let j = 0; j < 6; j++) cur.next() ist(cur.from, 5) ist(cur.parent()) ist(cur.from, 4) ist(cur.parent()) ist(cur.from, 0) ist(!cur.parent()) }) it("can move to a given position", () => { let tree = recur(), start = tree.length >> 1, cursor = tree.cursorAt(start, 1) do { ist(cursor.from, start, ">=") } while (cursor.next()) }) it("can move into a parent node", () => { let c = simple().cursorAt(10).moveTo(2) ist(c.name, "T") }) it("can move to a specific sibling", () => { let cursor = simple().cursor() ist(cursor.childAfter(2)) ist(cursor.to, 3) cursor.parent() ist(cursor.childBefore(5)) ist(cursor.from, 4) ist(cursor.childAfter(11)) ist(cursor.from, 8) ist(cursor.childBefore(10)) ist(cursor.from, 9) ist(!simple().cursor().childBefore(0)) ist(!simple().cursor().childAfter(100)) }) it("isn't slow", () => { let tree = recur(), t0 = Date.now(), count = 0 for (let i = 0; i < 2000; i++) { let cur = tree.cursor() do { if (cur.from < 0 || !cur.name) throw new Error("BAD") count++ } while (cur.next()) } let perMS = count / (Date.now() - t0) ist(perMS, 10000, ">") }) it("can produce nodes", () => { let node = simple().cursorAt(8, 1).node ist(node.name, "Br") ist(node.from, 8) ist(node.parent!.name, "Pa") ist(node.parent!.from, 4) ist(node.parent!.parent!.name, "T") ist(node.parent!.parent!.from, 0) ist(node.parent!.parent!.parent, null) }) it("can produce node from cursors created from nodes", () => { let cur = simple().topNode.lastChild!.childAfter(8)!.childAfter(10)!.cursor() ist(cur.name, "c") ist(cur.from, 10) ist(cur.parent()) let node = cur.node ist(node.name, "Br") ist(node.from, 8) ist(node.parent!.name, "Pa") ist(node.parent!.from, 4) ist(node.parent!.parent!.name, "T") ist(node.parent!.parent!.parent, null) }) it("reuses nodes in buffers", () => { let cur = simple().cursorAt(10, 1) let n10 = cur.node ist(n10.name, "c") ist(n10.from, 10) ist(cur.node, n10) cur.nextSibling() ist(cur.node.parent, n10.parent) cur.parent() ist(cur.node, n10.parent) }) it("skips anonymous nodes", () => { let c = anonTree.cursor() c.moveTo(1) ist(c.name, "T") c.firstChild() ist(c.name, "a") c.nextSibling() ist(c.name, "b") ist(!c.next()) }) it("stops at anonymous nodes when configured as full", () => { let c = anonTree.cursor(IterMode.IncludeAnonymous) c.moveTo(1) ist(c.type, NodeType.none) ist(c.tree!.length, 2) c.firstChild() ist(c.name, "a") c.parent() ist(c.type, NodeType.none) }) }) common-1.2.1/test/tsconfig.json000066400000000000000000000003671455145755000165120ustar00rootroot00000000000000{ "compilerOptions": { "lib": ["es2017"], "noImplicitReturns": true, "noUnusedLocals": true, "sourceMap": true, "strict": true, "target": "es5", "newLine": "lf", "stripInternal": true }, "include": ["*.ts"] } common-1.2.1/tsconfig.json000066400000000000000000000004371455145755000155310ustar00rootroot00000000000000{ "compilerOptions": { "lib": ["es2017"], "noImplicitReturns": true, "noUnusedLocals": true, "strict": true, "target": "es2015", "module": "es2015", "newLine": "lf", "stripInternal": true, "moduleResolution": "node" }, "include": ["src/*.ts"] }