enso/app/gui2/shared/ast/index.ts
Kaz Wesley 97033a2ff4
Represent documentation in AST, load into Node (#9205)
Part of #9162.

- Add support for representing and interpreting the full text-literal/documentation syntax (escape codes, platform-independent newlines, string interpolations); build on generalized operations for structured-fields that will simplify future representation of other types like `Vector`.
- Load parsed and interpreted node documentation into `Node`s.
2024-02-28 20:43:23 +01:00

105 lines
3.2 KiB
TypeScript

import * as random from 'lib0/random'
import type { ExternalId } from '../yjsModel'
import type { Module } from './mutableModule'
import type { SyncTokenId } from './token'
import type { AstId } from './tree'
import { App, Ast, Group, MutableAst, OprApp, Wildcard } from './tree'
export * from './mutableModule'
export * from './parse'
export * from './token'
export * from './tree'
declare const brandOwned: unique symbol
/** Used to mark references required to be unique.
*
* Note that the typesystem cannot stop you from copying an `Owned`,
* but that is an easy mistake to see (because it occurs locally).
*
* We can at least require *obtaining* an `Owned`,
* which statically prevents the otherwise most likely usage errors when rearranging ASTs.
*/
export type Owned<T = MutableAst> = T & { [brandOwned]: never }
/** @internal */
export function asOwned<T>(t: T): Owned<T> {
return t as Owned<T>
}
export type NodeChild<T> = { whitespace?: string | undefined; node: T }
export type RawNodeChild = NodeChild<AstId> | NodeChild<SyncTokenId>
export function newExternalId(): ExternalId {
return random.uuidv4() as ExternalId
}
/** @internal */
export function parentId(ast: Ast): AstId | undefined {
return ast.fields.get('parent')
}
/** Returns the given IDs, and the IDs of all their ancestors. */
export function subtrees(module: Module, ids: Iterable<AstId>) {
const subtrees = new Set<AstId>()
for (const id of ids) {
let ast = module.tryGet(id)
while (ast != null && !subtrees.has(ast.id)) {
subtrees.add(ast.id)
ast = ast.parent()
}
}
return subtrees
}
/** Returns the IDs of the ASTs that are not descendants of any others in the given set. */
export function subtreeRoots(module: Module, ids: Set<AstId>): Set<AstId> {
const roots = new Set<AstId>()
for (const id of ids) {
const astInModule = module.tryGet(id)
if (!astInModule) continue
let ast = astInModule.parent()
let hasParentInSet
while (ast != null) {
if (ids.has(ast.id)) {
hasParentInSet = true
break
}
ast = ast.parent()
}
if (!hasParentInSet) roots.add(id)
}
return roots
}
function unwrapGroups(ast: Ast) {
while (ast instanceof Group && ast.expression) ast = ast.expression
return ast
}
/** Tries to recognize inputs that are semantically-equivalent to a sequence of `App`s, and returns the arguments
* identified and LHS of the analyzable chain.
*
* In particular, this function currently recognizes syntax used in visualization-preprocessor expressions.
*/
export function analyzeAppLike(ast: Ast): { func: Ast; args: Ast[] } {
const deferredOperands = new Array<Ast>()
while (
ast instanceof OprApp &&
ast.operator.ok &&
ast.operator.value.code() === '<|' &&
ast.lhs &&
ast.rhs
) {
deferredOperands.push(unwrapGroups(ast.rhs))
ast = unwrapGroups(ast.lhs)
}
deferredOperands.reverse()
const args = new Array<Ast>()
while (ast instanceof App) {
const deferredOperand = ast.argument instanceof Wildcard ? deferredOperands.pop() : undefined
args.push(deferredOperand ?? unwrapGroups(ast.argument))
ast = ast.function
}
args.reverse()
return { func: ast, args }
}