| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- // from https://gist.github.com/msteen/e4828fbf25d6efef73576fc43ac479d2
- // https://discuss.codemirror.net/t/whats-the-best-to-test-and-debug-grammars/2542/5
- // MIT License
- //
- // Copyright (c) 2021 Matthijs Steen
- //
- // 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.
- import { Text } from '@codemirror/state'
- import { Tree, TreeCursor } from '@lezer/common'
- class StringInput {
- constructor(input) {
- this.input = input
- this.lineChunks = false
- }
- get length() {
- return this.input.length
- }
- chunk(from) {
- return this.input.slice(from)
- }
- read(from, to) {
- return this.input.slice(from, to)
- }
- }
- function cursorNode({ type, from, to }, isLeaf = false) {
- return { type, from, to, isLeaf }
- }
- function traverseTree(
- cursor,
- {
- from = -Infinity,
- to = Infinity,
- includeParents = false,
- beforeEnter,
- onEnter,
- onLeave,
- }
- ) {
- if (!(cursor instanceof TreeCursor))
- cursor = cursor instanceof Tree ? cursor.cursor() : cursor.cursor()
- for (;;) {
- let node = cursorNode(cursor)
- let leave = false
- if (node.from <= to && node.to >= from) {
- const enter =
- !node.type.isAnonymous &&
- (includeParents || (node.from >= from && node.to <= to))
- if (enter && beforeEnter) beforeEnter(cursor)
- node.isLeaf = !cursor.firstChild()
- if (enter) {
- leave = true
- if (onEnter(node) === false) return
- }
- if (!node.isLeaf) continue
- }
- for (;;) {
- node = cursorNode(cursor, node.isLeaf)
- if (leave && onLeave) if (onLeave(node) === false) return
- leave = cursor.type.isAnonymous
- node.isLeaf = false
- if (cursor.nextSibling()) break
- if (!cursor.parent()) return
- leave = true
- }
- }
- }
- function isChildOf(child, parent) {
- return (
- child.from >= parent.from &&
- child.from <= parent.to &&
- child.to <= parent.to &&
- child.to >= parent.from
- )
- }
- function validatorTraversal(input, { fullMatch = true } = {}) {
- if (typeof input === 'string') input = new StringInput(input)
- const state = {
- valid: true,
- parentNodes: [],
- lastLeafTo: 0,
- }
- return {
- state,
- traversal: {
- onEnter(node) {
- state.valid = true
- if (!node.isLeaf) state.parentNodes.unshift(node)
- if (node.from > node.to || node.from < state.lastLeafTo) {
- state.valid = false
- } else if (node.isLeaf) {
- if (
- state.parentNodes.length &&
- !isChildOf(node, state.parentNodes[0])
- )
- state.valid = false
- state.lastLeafTo = node.to
- } else {
- if (state.parentNodes.length) {
- if (!isChildOf(node, state.parentNodes[0])) state.valid = false
- } else if (
- fullMatch &&
- (node.from !== 0 || node.to !== input.length)
- ) {
- state.valid = false
- }
- }
- },
- onLeave(node) {
- if (!node.isLeaf) state.parentNodes.shift()
- },
- },
- }
- }
- let Color
- ;(function (Color) {
- Color[(Color.Red = 31)] = 'Red'
- Color[(Color.Green = 32)] = 'Green'
- Color[(Color.Yellow = 33)] = 'Yellow'
- })(Color || (Color = {}))
- function colorize(value, color) {
- return '\u001b[' + color + 'm' + String(value) + '\u001b[39m'
- }
- function printTree(
- cursor,
- input,
- { from, to, start = 0, includeParents } = {}
- ) {
- const inp = typeof input === 'string' ? new StringInput(input) : input
- const text = Text.of(inp.read(0, inp.length).split('\n'))
- const state = {
- output: '',
- prefixes: [],
- hasNextSibling: false,
- }
- const validator = validatorTraversal(inp)
- traverseTree(cursor, {
- from,
- to,
- includeParents,
- beforeEnter(cursor) {
- state.hasNextSibling = cursor.nextSibling() && cursor.prevSibling()
- },
- onEnter(node) {
- validator.traversal.onEnter(node)
- const isTop = state.output === ''
- const hasPrefix = !isTop || node.from > 0
- if (hasPrefix) {
- state.output += (!isTop ? '\n' : '') + state.prefixes.join('')
- if (state.hasNextSibling) {
- state.output += ' ├─ '
- state.prefixes.push(' │ ')
- } else {
- state.output += ' └─ '
- state.prefixes.push(' ')
- }
- }
- const hasRange = node.from !== node.to
- state.output +=
- (node.type.isError || !validator.state.valid
- ? colorize('ERROR ' + node.type.name, Color.Red)
- : node.type.name) +
- ' ' +
- (hasRange
- ? '[' +
- colorize(locAt(text, start + node.from), Color.Yellow) +
- '..' +
- colorize(locAt(text, start + node.to), Color.Yellow) +
- ']'
- : colorize(locAt(text, start + node.from), Color.Yellow))
- if (hasRange && node.isLeaf) {
- state.output +=
- ': ' +
- colorize(JSON.stringify(inp.read(node.from, node.to)), Color.Green)
- }
- },
- onLeave(node) {
- validator.traversal.onLeave(node)
- state.prefixes.pop()
- },
- })
- return state.output
- }
- function locAt(text, pos) {
- const line = text.lineAt(pos)
- return line.number + ':' + (pos - line.from)
- }
- export function logTree(tree, input, options) {
- console.warn(printTree(tree, input, options))
- }
|