mirror of
https://github.com/overleaf/overleaf.git
synced 2025-03-22 02:04:31 +00:00
Merge pull request #14991 from overleaf/jpa-log-tree
[web] consolidate logTree helper into scripts folder GitOrigin-RevId: d98a04f1753ba485f835312198f4c79917f7566d
This commit is contained in:
parent
1213709578
commit
d76c0e2688
3 changed files with 2 additions and 286 deletions
services/web
frontend/js/features/source-editor/languages/latex
scripts/lezer-latex
|
@ -1,283 +0,0 @@
|
|||
// 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 { Input, NodeType, SyntaxNode, Tree, TreeCursor } from '@lezer/common'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
|
||||
class StringInput implements Input {
|
||||
private input: string
|
||||
|
||||
constructor(input: string) {
|
||||
this.input = input
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this.input.length
|
||||
}
|
||||
|
||||
chunk(from: number): string {
|
||||
return this.input.slice(from)
|
||||
}
|
||||
|
||||
lineChunks = false
|
||||
|
||||
read(from: number, to: number): string {
|
||||
return this.input.slice(from, to)
|
||||
}
|
||||
}
|
||||
|
||||
export function sliceType(
|
||||
cursor: TreeCursor,
|
||||
input: Input,
|
||||
type: number
|
||||
): string | null {
|
||||
if (cursor.type.id === type) {
|
||||
const s = input.read(cursor.from, cursor.to)
|
||||
cursor.nextSibling()
|
||||
return s
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function isType(cursor: TreeCursor, type: number): boolean {
|
||||
const cond = cursor.type.id === type
|
||||
if (cond) cursor.nextSibling()
|
||||
return cond
|
||||
}
|
||||
|
||||
export type CursorNode = {
|
||||
type: NodeType
|
||||
from: number
|
||||
to: number
|
||||
isLeaf: boolean
|
||||
}
|
||||
|
||||
function cursorNode(
|
||||
{ type, from, to }: TreeCursor,
|
||||
isLeaf = false
|
||||
): CursorNode {
|
||||
return { type, from, to, isLeaf }
|
||||
}
|
||||
|
||||
export type TreeTraversal = {
|
||||
beforeEnter?: (cursor: TreeCursor) => void
|
||||
onEnter: (node: CursorNode) => false | void
|
||||
onLeave?: (node: CursorNode) => false | void
|
||||
}
|
||||
|
||||
type TreeTraversalOptions = {
|
||||
from?: number
|
||||
to?: number
|
||||
includeParents?: boolean
|
||||
} & TreeTraversal
|
||||
|
||||
export function traverseTree(
|
||||
cursor: TreeCursor | Tree | SyntaxNode,
|
||||
{
|
||||
from = -Infinity,
|
||||
to = Infinity,
|
||||
includeParents = false,
|
||||
beforeEnter,
|
||||
onEnter,
|
||||
onLeave,
|
||||
}: TreeTraversalOptions
|
||||
): void {
|
||||
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: CursorNode, parent: CursorNode): boolean {
|
||||
return (
|
||||
child.from >= parent.from &&
|
||||
child.from <= parent.to &&
|
||||
child.to <= parent.to &&
|
||||
child.to >= parent.from
|
||||
)
|
||||
}
|
||||
|
||||
export function validatorTraversal(
|
||||
input: Input | string,
|
||||
{ fullMatch = true }: { fullMatch?: boolean } = {}
|
||||
) {
|
||||
if (typeof input === 'string') input = new StringInput(input)
|
||||
const state = {
|
||||
valid: true,
|
||||
parentNodes: [] as CursorNode[],
|
||||
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()
|
||||
},
|
||||
} as TreeTraversal,
|
||||
}
|
||||
}
|
||||
|
||||
export function validateTree(
|
||||
tree: TreeCursor | Tree | SyntaxNode,
|
||||
input: Input | string,
|
||||
options?: { fullMatch?: boolean }
|
||||
): boolean {
|
||||
const { state, traversal } = validatorTraversal(input, options)
|
||||
traverseTree(tree, traversal)
|
||||
return state.valid
|
||||
}
|
||||
|
||||
function colorize(value: any): string {
|
||||
return '' + value
|
||||
}
|
||||
|
||||
type PrintTreeOptions = {
|
||||
from?: number
|
||||
to?: number
|
||||
start?: number
|
||||
includeParents?: boolean
|
||||
}
|
||||
|
||||
export function printTree(
|
||||
cursor: TreeCursor | Tree | SyntaxNode,
|
||||
input: Input | string,
|
||||
{ from, to, start = 0, includeParents }: PrintTreeOptions = {}
|
||||
): string {
|
||||
const inp = typeof input === 'string' ? new StringInput(input) : input
|
||||
const text = Text.of(inp.read(0, inp.length).split('\n'))
|
||||
const state = {
|
||||
output: '',
|
||||
prefixes: [] as string[],
|
||||
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)
|
||||
: node.type.name) +
|
||||
' ' +
|
||||
(hasRange
|
||||
? '[' +
|
||||
colorize(locAt(text, start + node.from)) +
|
||||
'..' +
|
||||
colorize(locAt(text, start + node.to)) +
|
||||
']'
|
||||
: colorize(locAt(text, start + node.from)))
|
||||
if (hasRange && node.isLeaf) {
|
||||
state.output +=
|
||||
': ' + colorize(JSON.stringify(inp.read(node.from, node.to)))
|
||||
}
|
||||
},
|
||||
onLeave(node) {
|
||||
if (validator.traversal.onLeave) {
|
||||
validator.traversal.onLeave(node)
|
||||
}
|
||||
state.prefixes.pop()
|
||||
},
|
||||
})
|
||||
return state.output
|
||||
}
|
||||
|
||||
function locAt(text: Text, pos: number): string {
|
||||
const line = text.lineAt(pos)
|
||||
return line.number + ':' + (pos - line.from)
|
||||
}
|
||||
|
||||
export function logTree(
|
||||
tree: TreeCursor | Tree | SyntaxNode,
|
||||
input: string,
|
||||
options?: PrintTreeOptions
|
||||
): void {
|
||||
debugConsole.warn(printTree(tree, input, options))
|
||||
}
|
|
@ -23,7 +23,6 @@
|
|||
// SOFTWARE.
|
||||
import { Text } from '@codemirror/state'
|
||||
import { Tree, TreeCursor } from '@lezer/common'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
|
||||
class StringInput {
|
||||
constructor(input) {
|
||||
|
@ -212,5 +211,5 @@ function locAt(text, pos) {
|
|||
}
|
||||
|
||||
export function logTree(tree, input, options) {
|
||||
debugConsole.warn(printTree(tree, input, options))
|
||||
console.warn(printTree(tree, input, options))
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { readFileSync } from 'fs'
|
||||
import { logTree } from '../../frontend/js/features/source-editor/lezer-latex/print-tree.mjs'
|
||||
import { logTree } from './print-tree.mjs'
|
||||
import { parser as LaTeXParser } from '../../frontend/js/features/source-editor/lezer-latex/latex.mjs'
|
||||
import { parser as BibTeXParser } from '../../frontend/js/features/source-editor/lezer-bibtex/bibtex.mjs'
|
||||
|
||||
|
|
Loading…
Reference in a new issue