What's the best to test and debug grammars?

Whenever I develop a grammar I find it useful to pretty print the parse tree. I did not find anything for this in the API, so I ported some code I have used in other parser generators:

import * as _inspect from "browser-util-inspect"
import { Input, stringInput, Tree } from "lezer-tree"
function inspect(arg: any): string {
  return _inspect(arg, {
    depth: null,
    colors: true,
  })
}
function printTree(tree: Tree, input: Input | string, from = 0, to = input.length): string {
  if (typeof input === "string") input = stringInput(input)
  let out = ""
  const c = tree.cursor()
  const childPrefixes: string[] = []
  for (;;) {
    const { type } = c
    const cfrom = c.from
    const cto = c.to
    let leave = false
    if (cfrom <= to && cto >= from) {
      if (!type.isAnonymous) {
        leave = true
        if (!type.isTop) {
          out += "\n" + childPrefixes.join("")
          if (c.nextSibling() && c.prevSibling()) {
            out += " ├─ "
            childPrefixes.push(" │  ")
          } else {
            out += " └─ "
            childPrefixes.push("    ")
          }
        }
        out += type.name
      }
      const isLeaf = !c.firstChild()
      if (!type.isAnonymous) {
        const hasRange = cfrom !== cto
        out += ` ${hasRange ? `[${inspect(cfrom)}..${inspect(cto)}]` : inspect(cfrom)}`
        if (isLeaf && hasRange) {
          out += `: ${inspect(input.read(cfrom, cto))}`
        }
      }
      if (!isLeaf || type.isTop) continue
    }
    for (;;) {
      if (leave) childPrefixes.pop()
      leave = c.type.isAnonymous
      if (c.nextSibling()) break
      if (!c.parent()) return out
      leave = true
    }
  }
}
console.log(printTree(tree, input))

Example output would be:

Contents [0..8]
 ├─ TagName [0..1]: 't'
 ├─ Tag [1..7]
 │   ├─ TagStart [1..2]: '{'
 │   ├─ TagName [2..3]: 'a'
 │   ├─ ContentsStart [3..4]: ':'
 │   ├─ TagName [4..6]: 'es'
 │   └─ TagEnd [6..7]: '}'
 └─ TagName [7..8]: 't'
2 Likes