mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
216 lines
6.1 KiB
JavaScript
216 lines
6.1 KiB
JavaScript
|
// 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.log(printTree(tree, input, options))
|
||
|
}
|