enso/app/gui2/shared/ast/parserSupport.ts
Kaz Wesley 343a644051
Syntactic synchronization, automatic parentheses, metadata in Ast (#8893)
- Synchronize Y.Js clients by AST (implements #8237).
- Before committing an edit, insert any parentheses-nodes needed for the concrete syntax to reflect tree structure (fixes #8884).
- Move `externalId` and all node metadata into a Y.Map owned by each `Ast`. This allows including metadata changes in an edit, enables Y.Js merging of changes to different metadata fields, and will enable the use of Y.Js objects in metadata. (Implements #8804.)

### Important Notes

- Metadata is now set and retrieved through accessors on the `Ast` objects.
- Since some metadata edits need to take effect in real time (e.g. node dragging), new lower-overhead APIs (`commitDirect`, `skipTreeRepair`) are provided for careful use in certain cases.
- The client is now bundled as ESM.
- The build script cleans up git-untracked generated files in an outdated location, which fixes lint errors related to `src/generated` that may occur when switching branches.
2024-02-02 10:22:18 +01:00

206 lines
5.3 KiB
TypeScript

/** This file supports the module in `generated/ast.ts` that is produced by `parser-codegen`. */
export { type Result } from '../util/data/result'
import { bail } from '../util/assert'
import { Err, Ok, type Result } from '../util/data/result'
export type ObjectVisitor = (object: LazyObject) => boolean | void
export type ObjectAddressVisitor = (view: DataView, address: number) => boolean | void
/** Base class for objects that lazily deserialize fields when accessed. */
export abstract class LazyObject {
protected readonly _v: DataView
protected constructor(view: DataView) {
if (view == null) throw new Error('WTF?')
this._v = view
}
visitChildren(_visitor: ObjectVisitor): boolean {
return false
}
children(): LazyObject[] {
const children: LazyObject[] = []
this.visitChildren((child) => {
children.push(child)
})
return children
}
}
type Reader<T> = (view: DataView, address: number) => T
function makeDataView(buffer: ArrayBuffer, address: number) {
return new DataView(buffer, address)
}
export function readU8(view: DataView, address: number) {
return view.getUint8(address)
}
export function readU32(view: DataView, address: number) {
return view.getUint32(address, true)
}
export function readI32(view: DataView, address: number) {
return view.getInt32(address, true)
}
export function readU64(view: DataView, address: number) {
return view.getBigUint64(address, true)
}
export function readI64(view: DataView, address: number) {
return view.getBigInt64(address, true)
}
export function readBool(view: DataView, address: number) {
return readU8(view, address) !== 0
}
export function readOffset(view: DataView, offset: number) {
return makeDataView(view.buffer, view.byteOffset + offset)
}
export function readPointer(view: DataView, address: number): DataView {
return makeDataView(view.buffer, readU32(view, address))
}
const textDecoder = new TextDecoder()
export function readOption<T>(
view: DataView,
address: number,
readElement: Reader<T>,
): T | undefined {
let result = undefined
visitOption(view, address, (view, address) => {
result = readElement(view, address)
})
return result
}
export function visitOption(
view: DataView,
address: number,
visitor: ObjectAddressVisitor,
): boolean {
const discriminant = readU8(view, address)
switch (discriminant) {
case 0:
return false
case 1:
return !!visitor(readPointer(view, address + 1), 0)
default:
throw new Error(`Invalid Option discriminant: 0x${discriminant.toString(16)}.`)
}
}
export function readResult<Ok, Err>(
view: DataView,
address: number,
readOk: Reader<Ok>,
readErr: Reader<Err>,
): Result<Ok, Err> {
const data = readPointer(view, address)
const discriminant = readU32(data, 0)
switch (discriminant) {
case 0:
return Ok(readOk(data, 4))
case 1:
return Err(readErr(data, 4))
default:
throw new Error(`Invalid Result discriminant: 0x${discriminant.toString(16)}.`)
}
}
export function visitResult(
view: DataView,
address: number,
visitOk: ObjectAddressVisitor | null,
visitErr: ObjectAddressVisitor | null,
): boolean {
const data = readPointer(view, address)
const discriminant = readU32(data, 0)
switch (discriminant) {
case 0:
if (visitOk?.(data, 4)) return true
return false
case 1:
if (visitErr?.(data, 4)) return true
return false
default:
throw new Error(`Invalid Result discriminant: 0x${discriminant.toString(16)}.`)
}
}
export function visitSequence(
view: DataView,
address: number,
size: number,
visitor: ObjectAddressVisitor,
): boolean {
const data = readPointer(view, address)
let offset = 4
const end = offset + size * readU32(data, 0)
while (offset != end) {
if (visitor(data, offset) === true) return true
offset += size
}
return false
}
export function readSequence<T>(
view: DataView,
address: number,
size: number,
reader: Reader<T>,
): IterableIterator<T> {
const data = readPointer(view, address)
const offset = 4
const end = offset + size * readU32(data, 0)
return new LazySequence(offset, size, end, (offset: number) => reader(data, offset))
}
export class LazySequence<T> implements IterableIterator<T> {
private offset: number
private readonly step: number
private readonly end: number
private readonly read: (address: number) => T
constructor(offset: number, step: number, end: number, read: (address: number) => T) {
this.read = read
this.offset = offset
this.step = step
this.end = end
}
[Symbol.iterator]() {
return this
}
public next(): IteratorResult<T> {
if (this.offset >= this.end) {
return { done: true, value: undefined }
}
const value = this.read(this.offset)
this.offset += this.step
return { done: false, value: value }
}
}
export function readString(view: DataView, address: number): string {
const data = readPointer(view, address)
const len = readU32(data, 0)
const bytes = new Uint8Array(data.buffer, data.byteOffset + 4, len)
return textDecoder.decode(bytes)
}
export function readEnum<T>(readers: Reader<T>[], view: DataView, address: number): T {
const data = readPointer(view, address)
const discriminant = readU32(data, 0)
const reader = readers[discriminant] ?? bail(`Invalid enum discriminant: ${discriminant}`)
return reader(data, 4)
}