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 & { [brandOwned]: never } /** @internal */ export function asOwned(t: T): Owned { return t as Owned } export type NodeChild = { whitespace?: string | undefined; node: T } export type RawNodeChild = NodeChild | NodeChild 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) { const subtrees = new Set() 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): Set { const roots = new Set() 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() 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() 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 } }