mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 15:21:48 +03:00
6881bc844e
When using `npm run lint`, there are a lot of warnings for the last couple of weeks, and it is really annoying because I keep skipping important warnings for my files.
2147 lines
69 KiB
TypeScript
2147 lines
69 KiB
TypeScript
import type {
|
|
Identifier,
|
|
IdentifierOrOperatorIdentifier,
|
|
IdentifierOrOperatorIdentifierToken,
|
|
IdentifierToken,
|
|
Module,
|
|
NodeChild,
|
|
Owned,
|
|
SpanMap,
|
|
SyncTokenId,
|
|
} from '.'
|
|
import {
|
|
MutableModule,
|
|
Token,
|
|
asOwned,
|
|
isIdentifier,
|
|
isToken,
|
|
isTokenId,
|
|
newExternalId,
|
|
parentId,
|
|
parse,
|
|
parseBlock,
|
|
print,
|
|
printAst,
|
|
printBlock,
|
|
} from '.'
|
|
import { assert, assertDefined, assertEqual, bail } from '../util/assert'
|
|
import type { Result } from '../util/data/result'
|
|
import { Err, Ok } from '../util/data/result'
|
|
import type { ExternalId, VisualizationMetadata } from '../yjsModel'
|
|
import * as RawAst from './generated/ast'
|
|
|
|
declare const brandAstId: unique symbol
|
|
export type AstId = string & { [brandAstId]: never }
|
|
|
|
/** @internal */
|
|
export interface MetadataFields {
|
|
externalId: ExternalId
|
|
}
|
|
export interface NodeMetadataFields {
|
|
position?: { x: number; y: number } | undefined
|
|
visualization?: VisualizationMetadata | undefined
|
|
}
|
|
export type NodeMetadata = FixedMapView<NodeMetadataFields>
|
|
export type MutableNodeMetadata = FixedMap<NodeMetadataFields>
|
|
export function asNodeMetadata(map: Map<string, unknown>): NodeMetadata {
|
|
return map as unknown as NodeMetadata
|
|
}
|
|
/** @internal */
|
|
export interface AstFields {
|
|
id: AstId
|
|
type: string
|
|
parent: AstId | undefined
|
|
metadata: FixedMap<MetadataFields>
|
|
}
|
|
export abstract class Ast {
|
|
readonly module: Module
|
|
/** @internal */
|
|
readonly fields: FixedMapView<AstFields>
|
|
|
|
get id(): AstId {
|
|
return this.fields.get('id')
|
|
}
|
|
|
|
get externalId(): ExternalId {
|
|
const id = this.fields.get('metadata').get('externalId')
|
|
assert(id != null)
|
|
return id
|
|
}
|
|
|
|
get nodeMetadata(): NodeMetadata {
|
|
const metadata = this.fields.get('metadata')
|
|
return metadata as FixedMapView<NodeMetadataFields>
|
|
}
|
|
|
|
typeName(): string {
|
|
return this.fields.get('type')
|
|
}
|
|
|
|
/**
|
|
* Return whether `this` and `other` are the same object, possibly in different modules.
|
|
*/
|
|
is<T extends Ast>(other: T): boolean {
|
|
return this.id === other.id
|
|
}
|
|
|
|
innerExpression(): Ast {
|
|
// TODO: Override this in `Documented`, `Annotated`, `AnnotatedBuiltin`
|
|
return this
|
|
}
|
|
|
|
code(): string {
|
|
return print(this).code
|
|
}
|
|
|
|
visitRecursive(visit: (node: Ast | Token) => void): void {
|
|
visit(this)
|
|
for (const child of this.children()) {
|
|
if (isToken(child)) {
|
|
visit(child)
|
|
} else {
|
|
child.visitRecursive(visit)
|
|
}
|
|
}
|
|
}
|
|
|
|
visitRecursiveAst(visit: (ast: Ast) => void): void {
|
|
visit(this)
|
|
for (const child of this.children()) {
|
|
if (!isToken(child)) child.visitRecursiveAst(visit)
|
|
}
|
|
}
|
|
|
|
printSubtree(
|
|
info: SpanMap,
|
|
offset: number,
|
|
parentIndent: string | undefined,
|
|
verbatim?: boolean,
|
|
): string {
|
|
return printAst(this, info, offset, parentIndent, verbatim)
|
|
}
|
|
|
|
/** Returns child subtrees, without information about the whitespace between them. */
|
|
*children(): IterableIterator<Ast | Token> {
|
|
for (const child of this.concreteChildren()) {
|
|
if (isTokenId(child.node)) {
|
|
yield this.module.getToken(child.node)
|
|
} else {
|
|
const node = this.module.checkedGet(child.node)
|
|
if (node) yield node
|
|
}
|
|
}
|
|
}
|
|
|
|
get parentId(): AstId | undefined {
|
|
const parentId = this.fields.get('parent')
|
|
if (parentId !== 'ROOT_ID') return parentId
|
|
}
|
|
|
|
parent(): Ast | undefined {
|
|
return this.module.checkedGet(this.parentId)
|
|
}
|
|
|
|
static parseBlock(source: string, inModule?: MutableModule) {
|
|
return parseBlock(source, inModule)
|
|
}
|
|
|
|
static parse(source: string, module?: MutableModule) {
|
|
return parse(source, module)
|
|
}
|
|
|
|
////////////////////
|
|
|
|
protected constructor(module: Module, fields: FixedMapView<AstFields>) {
|
|
this.module = module
|
|
this.fields = fields
|
|
}
|
|
|
|
/** @internal
|
|
* Returns child subtrees, including information about the whitespace between them.
|
|
*/
|
|
abstract concreteChildren(verbatim?: boolean): IterableIterator<NodeChild>
|
|
}
|
|
export interface MutableAst {}
|
|
export abstract class MutableAst extends Ast {
|
|
declare readonly module: MutableModule
|
|
declare readonly fields: FixedMap<AstFields>
|
|
|
|
setExternalId(id: ExternalId) {
|
|
this.fields.get('metadata').set('externalId', id)
|
|
}
|
|
|
|
mutableNodeMetadata(): MutableNodeMetadata {
|
|
const metadata = this.fields.get('metadata')
|
|
return metadata as FixedMap<NodeMetadataFields>
|
|
}
|
|
|
|
setNodeMetadata(nodeMeta: NodeMetadataFields) {
|
|
const metadata = this.fields.get('metadata') as unknown as Map<string, unknown>
|
|
for (const [key, value] of Object.entries(nodeMeta))
|
|
if (value !== undefined) metadata.set(key, value)
|
|
}
|
|
|
|
/** Modify the parent of this node to refer to a new object instead. Return the object, which now has no parent. */
|
|
replace<T extends MutableAst>(replacement: Owned<T>): Owned<typeof this> {
|
|
const parentId = this.fields.get('parent')
|
|
if (parentId) {
|
|
const parent = this.module.checkedGet(parentId)
|
|
parent.replaceChild(this.id, replacement)
|
|
this.fields.set('parent', undefined)
|
|
}
|
|
return asOwned(this)
|
|
}
|
|
|
|
/** Change the value of the object referred to by the `target` ID. (The initial ID of `replacement` will be ignored.)
|
|
* Returns the old value, with a new (unreferenced) ID.
|
|
*/
|
|
replaceValue<T extends MutableAst>(replacement: Owned<T>): Owned<typeof this> {
|
|
const replacement_ = this.module.copyIfForeign(replacement)
|
|
const old = this.replace(replacement_)
|
|
replacement_.fields.set('metadata', old.fields.get('metadata').clone())
|
|
old.setExternalId(newExternalId())
|
|
return old
|
|
}
|
|
|
|
replaceValueChecked<T extends MutableAst>(replacement: Owned<T>): Owned<typeof this> {
|
|
const parentId = this.fields.get('parent')
|
|
assertDefined(parentId)
|
|
return this.replaceValue(replacement)
|
|
}
|
|
|
|
/** Replace the parent of this object with a reference to a new placeholder object.
|
|
* Returns the object, now parentless, and the placeholder. */
|
|
takeToReplace(): Removed<this> {
|
|
if (parentId(this)) {
|
|
const placeholder = Wildcard.new(this.module)
|
|
const node = this.replace(placeholder)
|
|
return { node, placeholder }
|
|
} else {
|
|
return { node: asOwned(this), placeholder: undefined }
|
|
}
|
|
}
|
|
|
|
/** Replace the parent of this object with a reference to a new placeholder object.
|
|
* Returns the object, now parentless. */
|
|
take(): Owned<this> {
|
|
return this.replace(Wildcard.new(this.module))
|
|
}
|
|
|
|
takeIfParented(): Owned<typeof this> {
|
|
const parent = parentId(this)
|
|
if (parent) {
|
|
const parentAst = this.module.checkedGet(parent)
|
|
const placeholder = Wildcard.new(this.module)
|
|
parentAst.replaceChild(this.id, placeholder)
|
|
this.fields.set('parent', undefined)
|
|
}
|
|
return asOwned(this)
|
|
}
|
|
|
|
/** Replace the value assigned to the given ID with a placeholder.
|
|
* Returns the removed value, with a new unreferenced ID.
|
|
**/
|
|
takeValue(): Removed<typeof this> {
|
|
const placeholder = Wildcard.new(this.module)
|
|
const node = this.replaceValue(placeholder)
|
|
return { node, placeholder }
|
|
}
|
|
|
|
/** Take this node from the tree, and replace it with the result of applying the given function to it.
|
|
*
|
|
* Note that this is a modification of the *parent* node. Any `Ast` objects or `AstId`s that pointed to the old value
|
|
* will still point to the old value.
|
|
*/
|
|
update<T extends MutableAst>(f: (x: Owned<typeof this>) => Owned<T>): T {
|
|
const taken = this.takeToReplace()
|
|
assertDefined(taken.placeholder, 'To replace an `Ast`, it must have a parent.')
|
|
const replacement = f(taken.node)
|
|
taken.placeholder.replace(replacement)
|
|
return replacement
|
|
}
|
|
|
|
/** Take this node from the tree, and replace it with the result of applying the given function to it; transfer the
|
|
* metadata from this node to the replacement.
|
|
*
|
|
* Note that this is a modification of the *parent* node. Any `Ast` objects or `AstId`s that pointed to the old value
|
|
* will still point to the old value.
|
|
*/
|
|
updateValue<T extends MutableAst>(f: (x: Owned<typeof this>) => Owned<T>): T {
|
|
const taken = this.takeValue()
|
|
assertDefined(taken.placeholder, 'To replace an `Ast`, it must have a parent.')
|
|
const replacement = f(taken.node)
|
|
taken.placeholder.replaceValue(replacement)
|
|
return replacement
|
|
}
|
|
|
|
mutableParent(): MutableAst | undefined {
|
|
const parentId = this.fields.get('parent')
|
|
if (parentId === 'ROOT_ID') return
|
|
return this.module.checkedGet(parentId)
|
|
}
|
|
|
|
///////////////////
|
|
|
|
/** @internal */
|
|
importReferences(module: Module) {
|
|
if (module === this.module) return
|
|
for (const child of this.concreteChildren()) {
|
|
if (!isTokenId(child.node)) {
|
|
const childInForeignModule = module.checkedGet(child.node)
|
|
assert(childInForeignModule !== undefined)
|
|
const importedChild = this.module.copy(childInForeignModule)
|
|
importedChild.fields.set('parent', undefined)
|
|
this.replaceChild(child.node, asOwned(importedChild))
|
|
}
|
|
}
|
|
}
|
|
|
|
/** @internal */
|
|
abstract replaceChild<T extends MutableAst>(target: AstId, replacement: Owned<T>): void
|
|
|
|
protected claimChild<T extends MutableAst>(child: Owned<T>): AstId
|
|
protected claimChild<T extends MutableAst>(child: Owned<T> | undefined): AstId | undefined
|
|
protected claimChild<T extends MutableAst>(child: Owned<T> | undefined): AstId | undefined {
|
|
return child ? claimChild(this.module, child, this.id) : undefined
|
|
}
|
|
}
|
|
|
|
function applyMixins(derivedCtor: any, constructors: any[]) {
|
|
constructors.forEach((baseCtor) => {
|
|
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
|
|
Object.defineProperty(
|
|
derivedCtor.prototype,
|
|
name,
|
|
Object.getOwnPropertyDescriptor(baseCtor.prototype, name) || Object.create(null),
|
|
)
|
|
})
|
|
})
|
|
}
|
|
|
|
interface AppFields {
|
|
function: NodeChild<AstId>
|
|
parens: { open: NodeChild<SyncTokenId>; close: NodeChild<SyncTokenId> } | undefined
|
|
nameSpecification: { name: NodeChild<SyncTokenId>; equals: NodeChild<SyncTokenId> } | undefined
|
|
argument: NodeChild<AstId>
|
|
}
|
|
export class App extends Ast {
|
|
declare fields: FixedMap<AstFields & AppFields>
|
|
constructor(module: Module, fields: FixedMapView<AstFields & AppFields>) {
|
|
super(module, fields)
|
|
}
|
|
|
|
static tryParse(source: string, module?: MutableModule): Owned<MutableApp> | undefined {
|
|
const parsed = parse(source, module)
|
|
if (parsed instanceof MutableApp) return parsed
|
|
}
|
|
|
|
static concrete(
|
|
module: MutableModule,
|
|
func: NodeChild<Owned>,
|
|
parens: { open: NodeChild<Token>; close: NodeChild<Token> } | undefined,
|
|
nameSpecification: { name: NodeChild<Token>; equals: NodeChild<Token> } | undefined,
|
|
argument: NodeChild<Owned>,
|
|
) {
|
|
const base = module.baseObject('App')
|
|
const id_ = base.get('id')
|
|
const fields = setAll(base, {
|
|
function: concreteChild(module, func, id_),
|
|
parens,
|
|
nameSpecification,
|
|
argument: concreteChild(module, argument, id_),
|
|
})
|
|
return asOwned(new MutableApp(module, fields))
|
|
}
|
|
|
|
static new(
|
|
module: MutableModule,
|
|
func: Owned,
|
|
argumentName: StrictIdentLike | undefined,
|
|
argument: Owned,
|
|
) {
|
|
return App.concrete(
|
|
module,
|
|
unspaced(func),
|
|
undefined,
|
|
nameSpecification(argumentName),
|
|
autospaced(argument),
|
|
)
|
|
}
|
|
|
|
get function(): Ast {
|
|
return this.module.checkedGet(this.fields.get('function').node)
|
|
}
|
|
get argumentName(): Token | undefined {
|
|
return this.module.getToken(this.fields.get('nameSpecification')?.name.node)
|
|
}
|
|
get argument(): Ast {
|
|
return this.module.checkedGet(this.fields.get('argument').node)
|
|
}
|
|
|
|
*concreteChildren(verbatim?: boolean): IterableIterator<NodeChild> {
|
|
const { function: function_, parens, nameSpecification, argument } = getAll(this.fields)
|
|
yield function_
|
|
if (parens) yield parens.open
|
|
const spacedEquals = !!parens && !!nameSpecification?.equals.whitespace
|
|
if (nameSpecification) {
|
|
yield ensureSpacedIf(nameSpecification.name, !parens, verbatim)
|
|
yield ensureSpacedOnlyIf(nameSpecification.equals, spacedEquals, verbatim)
|
|
}
|
|
yield ensureSpacedOnlyIf(argument, !nameSpecification || spacedEquals, verbatim)
|
|
if (parens) yield parens.close
|
|
}
|
|
|
|
printSubtree(
|
|
info: SpanMap,
|
|
offset: number,
|
|
parentIndent: string | undefined,
|
|
verbatim?: boolean,
|
|
): string {
|
|
const verbatim_ =
|
|
verbatim ?? (this.function instanceof Invalid || this.argument instanceof Invalid)
|
|
return super.printSubtree(info, offset, parentIndent, verbatim_)
|
|
}
|
|
}
|
|
function ensureSpacedIf<T>(
|
|
child: NodeChild<T>,
|
|
condition: boolean,
|
|
verbatim: boolean | undefined,
|
|
): NodeChild<T> {
|
|
return condition ? ensureSpaced(child, verbatim) : child
|
|
}
|
|
function ensureSpacedOnlyIf<T>(
|
|
child: NodeChild<T>,
|
|
condition: boolean,
|
|
verbatim: boolean | undefined,
|
|
): NodeChild<T> {
|
|
return condition ? ensureSpaced(child, verbatim) : ensureUnspaced(child, verbatim)
|
|
}
|
|
function ensureSpaced<T>(child: NodeChild<T>, verbatim: boolean | undefined): NodeChild<T> {
|
|
if (verbatim && child.whitespace != null) return child
|
|
return child.whitespace ? child : { whitespace: ' ', ...child }
|
|
}
|
|
function ensureUnspaced<T>(child: NodeChild<T>, verbatim: boolean | undefined): NodeChild<T> {
|
|
if (verbatim && child.whitespace != null) return child
|
|
return child.whitespace === '' ? child : { whitespace: '', ...child }
|
|
}
|
|
export class MutableApp extends App implements MutableAst {
|
|
declare readonly module: MutableModule
|
|
declare readonly fields: FixedMap<AstFields & AppFields>
|
|
|
|
setFunction<T extends MutableAst>(value: Owned<T>) {
|
|
setNode(this.fields, 'function', this.claimChild(value))
|
|
}
|
|
setArgumentName(name: StrictIdentLike | undefined) {
|
|
this.fields.set('nameSpecification', nameSpecification(name))
|
|
}
|
|
setArgument<T extends MutableAst>(value: Owned<T>) {
|
|
setNode(this.fields, 'argument', this.claimChild(value))
|
|
}
|
|
|
|
replaceChild<T extends MutableAst>(target: AstId, replacement: Owned<T>) {
|
|
if (this.fields.get('function').node === target) {
|
|
this.setFunction(replacement)
|
|
} else if (this.fields.get('argument').node === target) {
|
|
this.setArgument(replacement)
|
|
}
|
|
}
|
|
}
|
|
export interface MutableApp extends App, MutableAst {
|
|
get function(): MutableAst
|
|
get argument(): MutableAst
|
|
}
|
|
applyMixins(MutableApp, [MutableAst])
|
|
|
|
interface UnaryOprAppFields {
|
|
operator: NodeChild<SyncTokenId>
|
|
argument: NodeChild<AstId> | undefined
|
|
}
|
|
export class UnaryOprApp extends Ast {
|
|
declare fields: FixedMapView<AstFields & UnaryOprAppFields>
|
|
constructor(module: Module, fields: FixedMapView<AstFields & UnaryOprAppFields>) {
|
|
super(module, fields)
|
|
}
|
|
|
|
static tryParse(source: string, module?: MutableModule): Owned<MutableUnaryOprApp> | undefined {
|
|
const parsed = parse(source, module)
|
|
if (parsed instanceof MutableUnaryOprApp) return parsed
|
|
}
|
|
|
|
static concrete(
|
|
module: MutableModule,
|
|
operator: NodeChild<Token>,
|
|
argument: NodeChild<Owned> | undefined,
|
|
) {
|
|
const base = module.baseObject('UnaryOprApp')
|
|
const id_ = base.get('id')
|
|
const fields = setAll(base, {
|
|
operator,
|
|
argument: concreteChild(module, argument, id_),
|
|
})
|
|
return asOwned(new MutableUnaryOprApp(module, fields))
|
|
}
|
|
|
|
static new(module: MutableModule, operator: Token, argument: Owned | undefined) {
|
|
return this.concrete(module, unspaced(operator), argument ? autospaced(argument) : undefined)
|
|
}
|
|
|
|
get operator(): Token {
|
|
return this.module.getToken(this.fields.get('operator').node)
|
|
}
|
|
get argument(): Ast | undefined {
|
|
return this.module.checkedGet(this.fields.get('argument')?.node)
|
|
}
|
|
|
|
*concreteChildren(_verbatim?: boolean): IterableIterator<NodeChild> {
|
|
const { operator, argument } = getAll(this.fields)
|
|
yield operator
|
|
if (argument) yield argument
|
|
}
|
|
}
|
|
export class MutableUnaryOprApp extends UnaryOprApp implements MutableAst {
|
|
declare readonly module: MutableModule
|
|
declare readonly fields: FixedMap<AstFields & UnaryOprAppFields>
|
|
|
|
setOperator(value: Token) {
|
|
this.fields.set('operator', unspaced(value))
|
|
}
|
|
setArgument<T extends MutableAst>(argument: Owned<T> | undefined) {
|
|
setNode(this.fields, 'argument', this.claimChild(argument))
|
|
}
|
|
|
|
replaceChild<T extends MutableAst>(target: AstId, replacement: Owned<T>) {
|
|
if (this.fields.get('argument')?.node === target) {
|
|
this.setArgument(replacement)
|
|
}
|
|
}
|
|
}
|
|
export interface MutableUnaryOprApp extends UnaryOprApp, MutableAst {
|
|
get argument(): MutableAst | undefined
|
|
}
|
|
applyMixins(MutableUnaryOprApp, [MutableAst])
|
|
|
|
interface NegationAppFields {
|
|
operator: NodeChild<SyncTokenId>
|
|
argument: NodeChild<AstId>
|
|
}
|
|
export class NegationApp extends Ast {
|
|
declare fields: FixedMapView<AstFields & NegationAppFields>
|
|
constructor(module: Module, fields: FixedMapView<AstFields & NegationAppFields>) {
|
|
super(module, fields)
|
|
}
|
|
|
|
static tryParse(source: string, module?: MutableModule): Owned<MutableNegationApp> | undefined {
|
|
const parsed = parse(source, module)
|
|
if (parsed instanceof MutableNegationApp) return parsed
|
|
}
|
|
|
|
static concrete(module: MutableModule, operator: NodeChild<Token>, argument: NodeChild<Owned>) {
|
|
const base = module.baseObject('NegationApp')
|
|
const id_ = base.get('id')
|
|
const fields = setAll(base, {
|
|
operator,
|
|
argument: concreteChild(module, argument, id_),
|
|
})
|
|
return asOwned(new MutableNegationApp(module, fields))
|
|
}
|
|
|
|
static new(module: MutableModule, operator: Token, argument: Owned) {
|
|
return this.concrete(module, unspaced(operator), autospaced(argument))
|
|
}
|
|
|
|
get operator(): Token {
|
|
return this.module.getToken(this.fields.get('operator').node)
|
|
}
|
|
get argument(): Ast {
|
|
return this.module.checkedGet(this.fields.get('argument').node)
|
|
}
|
|
|
|
*concreteChildren(_verbatim?: boolean): IterableIterator<NodeChild> {
|
|
const { operator, argument } = getAll(this.fields)
|
|
yield operator
|
|
if (argument) yield argument
|
|
}
|
|
}
|
|
export class MutableNegationApp extends NegationApp implements MutableAst {
|
|
declare readonly module: MutableModule
|
|
declare readonly fields: FixedMap<AstFields & NegationAppFields>
|
|
|
|
setArgument<T extends MutableAst>(value: Owned<T>) {
|
|
setNode(this.fields, 'argument', this.claimChild(value))
|
|
}
|
|
|
|
replaceChild<T extends MutableAst>(target: AstId, replacement: Owned<T>) {
|
|
if (this.fields.get('argument')?.node === target) {
|
|
this.setArgument(replacement)
|
|
}
|
|
}
|
|
}
|
|
export interface MutableNegationApp extends NegationApp, MutableAst {
|
|
get argument(): MutableAst
|
|
}
|
|
applyMixins(MutableNegationApp, [MutableAst])
|
|
|
|
interface OprAppFields {
|
|
lhs: NodeChild<AstId> | undefined
|
|
operators: NodeChild<SyncTokenId>[]
|
|
rhs: NodeChild<AstId> | undefined
|
|
}
|
|
export class OprApp extends Ast {
|
|
declare fields: FixedMapView<AstFields & OprAppFields>
|
|
constructor(module: Module, fields: FixedMapView<AstFields & OprAppFields>) {
|
|
super(module, fields)
|
|
}
|
|
|
|
static tryParse(source: string, module?: MutableModule): Owned<MutableOprApp> | undefined {
|
|
const parsed = parse(source, module)
|
|
if (parsed instanceof MutableOprApp) return parsed
|
|
}
|
|
|
|
static concrete(
|
|
module: MutableModule,
|
|
lhs: NodeChild<Owned> | undefined,
|
|
operators: NodeChild<Token>[],
|
|
rhs: NodeChild<Owned> | undefined,
|
|
) {
|
|
const base = module.baseObject('OprApp')
|
|
const id_ = base.get('id')
|
|
const fields = setAll(base, {
|
|
lhs: concreteChild(module, lhs, id_),
|
|
operators,
|
|
rhs: concreteChild(module, rhs, id_),
|
|
})
|
|
return asOwned(new MutableOprApp(module, fields))
|
|
}
|
|
|
|
static new(
|
|
module: MutableModule,
|
|
lhs: Owned | undefined,
|
|
operator: Token,
|
|
rhs: Owned | undefined,
|
|
) {
|
|
return OprApp.concrete(module, unspaced(lhs), [autospaced(operator)], autospaced(rhs))
|
|
}
|
|
|
|
get lhs(): Ast | undefined {
|
|
return this.module.checkedGet(this.fields.get('lhs')?.node)
|
|
}
|
|
get operator(): Result<Token, NodeChild<Token>[]> {
|
|
const operators = this.fields.get('operators')
|
|
const operators_ = operators.map((child) => ({
|
|
...child,
|
|
node: this.module.getToken(child.node),
|
|
}))
|
|
const [opr] = operators_
|
|
return opr ? Ok(opr.node) : Err(operators_)
|
|
}
|
|
get rhs(): Ast | undefined {
|
|
return this.module.checkedGet(this.fields.get('rhs')?.node)
|
|
}
|
|
|
|
*concreteChildren(_verbatim?: boolean): IterableIterator<NodeChild> {
|
|
const { lhs, operators, rhs } = getAll(this.fields)
|
|
if (lhs) yield lhs
|
|
yield* operators
|
|
if (rhs) yield rhs
|
|
}
|
|
}
|
|
export class MutableOprApp extends OprApp implements MutableAst {
|
|
declare readonly module: MutableModule
|
|
declare readonly fields: FixedMap<AstFields & OprAppFields>
|
|
|
|
setLhs<T extends MutableAst>(value: Owned<T>) {
|
|
setNode(this.fields, 'lhs', this.claimChild(value))
|
|
}
|
|
setOperator(value: Token) {
|
|
this.fields.set('operators', [unspaced(value)])
|
|
}
|
|
setRhs<T extends MutableAst>(value: Owned<T>) {
|
|
setNode(this.fields, 'rhs', this.claimChild(value))
|
|
}
|
|
|
|
replaceChild<T extends MutableAst>(target: AstId, replacement: Owned<T>) {
|
|
if (this.fields.get('lhs')?.node === target) {
|
|
this.setLhs(replacement)
|
|
} else if (this.fields.get('rhs')?.node === target) {
|
|
this.setRhs(replacement)
|
|
}
|
|
}
|
|
}
|
|
export interface MutableOprApp extends OprApp, MutableAst {
|
|
get lhs(): MutableAst | undefined
|
|
get rhs(): MutableAst | undefined
|
|
}
|
|
applyMixins(MutableOprApp, [MutableAst])
|
|
|
|
interface PropertyAccessFields {
|
|
lhs: NodeChild<AstId> | undefined
|
|
operator: NodeChild<SyncTokenId>
|
|
rhs: NodeChild<AstId>
|
|
}
|
|
export class PropertyAccess extends Ast {
|
|
declare fields: FixedMapView<AstFields & PropertyAccessFields>
|
|
constructor(module: Module, fields: FixedMapView<AstFields & PropertyAccessFields>) {
|
|
super(module, fields)
|
|
}
|
|
|
|
static tryParse(
|
|
source: string,
|
|
module?: MutableModule,
|
|
): Owned<MutablePropertyAccess> | undefined {
|
|
const parsed = parse(source, module)
|
|
if (parsed instanceof MutablePropertyAccess) return parsed
|
|
}
|
|
|
|
static new(module: MutableModule, lhs: Owned, rhs: IdentLike) {
|
|
const dot = unspaced(Token.new('.', RawAst.Token.Type.Operator))
|
|
return this.concrete(
|
|
module,
|
|
unspaced(lhs),
|
|
dot,
|
|
unspaced(Ident.newAllowingOperators(module, toIdent(rhs))),
|
|
)
|
|
}
|
|
|
|
static Sequence(
|
|
segments: [StrictIdentLike, ...StrictIdentLike[]],
|
|
module: MutableModule,
|
|
): Owned<MutablePropertyAccess> | Owned<MutableIdent>
|
|
static Sequence(
|
|
segments: [StrictIdentLike, ...StrictIdentLike[], IdentLike],
|
|
module: MutableModule,
|
|
): Owned<MutablePropertyAccess> | Owned<MutableIdent>
|
|
static Sequence(
|
|
segments: IdentLike[],
|
|
module: MutableModule,
|
|
): Owned<MutablePropertyAccess> | Owned<MutableIdent> | undefined
|
|
static Sequence(
|
|
segments: IdentLike[],
|
|
module: MutableModule,
|
|
): Owned<MutablePropertyAccess> | Owned<MutableIdent> | undefined {
|
|
let path: Owned<MutablePropertyAccess> | Owned<MutableIdent> | undefined
|
|
let operatorInNonFinalSegment = false
|
|
segments.forEach((s, i) => {
|
|
const t = toIdent(s)
|
|
if (i !== segments.length - 1 && !isIdentifier(t.code())) operatorInNonFinalSegment = true
|
|
path = path ? this.new(module, path, t) : Ident.newAllowingOperators(module, t)
|
|
})
|
|
if (!operatorInNonFinalSegment) return path
|
|
}
|
|
|
|
static concrete(
|
|
module: MutableModule,
|
|
lhs: NodeChild<Owned> | undefined,
|
|
operator: NodeChild<Token>,
|
|
rhs: NodeChild<Owned<MutableIdent>>,
|
|
) {
|
|
const base = module.baseObject('PropertyAccess')
|
|
const id_ = base.get('id')
|
|
const fields = setAll(base, {
|
|
lhs: concreteChild(module, lhs, id_),
|
|
operator,
|
|
rhs: concreteChild(module, rhs, id_),
|
|
})
|
|
return asOwned(new MutablePropertyAccess(module, fields))
|
|
}
|
|
|
|
get lhs(): Ast | undefined {
|
|
return this.module.checkedGet(this.fields.get('lhs')?.node)
|
|
}
|
|
get operator(): Token {
|
|
return this.module.getToken(this.fields.get('operator').node)
|
|
}
|
|
get rhs(): IdentifierOrOperatorIdentifierToken {
|
|
const ast = this.module.checkedGet(this.fields.get('rhs').node)
|
|
assert(ast instanceof Ident)
|
|
return ast.token as IdentifierOrOperatorIdentifierToken
|
|
}
|
|
|
|
*concreteChildren(_verbatim?: boolean): IterableIterator<NodeChild> {
|
|
const { lhs, operator, rhs } = getAll(this.fields)
|
|
if (lhs) yield lhs
|
|
yield operator
|
|
yield rhs
|
|
}
|
|
}
|
|
export class MutablePropertyAccess extends PropertyAccess implements MutableAst {
|
|
declare readonly module: MutableModule
|
|
declare readonly fields: FixedMap<AstFields & PropertyAccessFields>
|
|
|
|
setLhs<T extends MutableAst>(value: Owned<T> | undefined) {
|
|
setNode(this.fields, 'lhs', this.claimChild(value))
|
|
}
|
|
setRhs(ident: IdentLike) {
|
|
const node = this.claimChild(Ident.newAllowingOperators(this.module, ident))
|
|
const old = this.fields.get('rhs')
|
|
this.fields.set('rhs', old ? { ...old, node } : unspaced(node))
|
|
}
|
|
|
|
replaceChild<T extends MutableAst>(target: AstId, replacement: Owned<T>) {
|
|
if (this.fields.get('lhs')?.node === target) {
|
|
this.setLhs(replacement)
|
|
} else if (this.fields.get('rhs')?.node === target) {
|
|
assert(replacement instanceof MutableIdent)
|
|
this.setRhs(replacement.token)
|
|
}
|
|
}
|
|
}
|
|
export interface MutablePropertyAccess extends PropertyAccess, MutableAst {
|
|
get lhs(): MutableAst | undefined
|
|
}
|
|
applyMixins(MutablePropertyAccess, [MutableAst])
|
|
|
|
interface GenericFields {
|
|
children: NodeChild[]
|
|
}
|
|
export class Generic extends Ast {
|
|
declare fields: FixedMapView<AstFields & GenericFields>
|
|
constructor(module: Module, fields: FixedMapView<AstFields & GenericFields>) {
|
|
super(module, fields)
|
|
}
|
|
|
|
static concrete(module: MutableModule, children: NodeChild<Owned | Token>[]) {
|
|
const base = module.baseObject('Generic')
|
|
const id_ = base.get('id')
|
|
const fields = setAll(base, {
|
|
children: children.map((child) => concreteChild(module, child, id_)),
|
|
})
|
|
return asOwned(new MutableGeneric(module, fields))
|
|
}
|
|
|
|
concreteChildren(_verbatim?: boolean): IterableIterator<NodeChild> {
|
|
return this.fields.get('children')[Symbol.iterator]()
|
|
}
|
|
}
|
|
export class MutableGeneric extends Generic implements MutableAst {
|
|
declare readonly module: MutableModule
|
|
declare readonly fields: FixedMap<AstFields & GenericFields>
|
|
|
|
replaceChild<T extends MutableAst>(target: AstId, replacement: Owned<T>) {
|
|
const replacement_ = autospaced(this.claimChild(replacement))
|
|
this.fields.set(
|
|
'children',
|
|
this.fields.get('children').map((child) => (child.node === target ? replacement_ : child)),
|
|
)
|
|
}
|
|
}
|
|
export interface MutableGeneric extends Generic, MutableAst {}
|
|
applyMixins(MutableGeneric, [MutableAst])
|
|
|
|
interface RawMultiSegmentAppSegment {
|
|
header: NodeChild<Token>
|
|
body: NodeChild<AstId> | undefined
|
|
}
|
|
interface OwnedMultiSegmentAppSegment {
|
|
header: NodeChild<Token>
|
|
body: NodeChild<Owned> | undefined
|
|
}
|
|
function multiSegmentAppSegment<T extends MutableAst>(
|
|
header: string,
|
|
body: Owned<T>,
|
|
): OwnedMultiSegmentAppSegment
|
|
function multiSegmentAppSegment<T extends MutableAst>(
|
|
header: string,
|
|
body: Owned<T> | undefined,
|
|
): OwnedMultiSegmentAppSegment | undefined
|
|
function multiSegmentAppSegment<T extends MutableAst>(
|
|
header: string,
|
|
body: Owned<T> | undefined,
|
|
): OwnedMultiSegmentAppSegment | undefined {
|
|
return {
|
|
header: { node: Token.new(header, RawAst.Token.Type.Ident) },
|
|
body: spaced(body ? (body as any) : undefined),
|
|
}
|
|
}
|
|
|
|
function multiSegmentAppSegmentToRaw(
|
|
module: MutableModule,
|
|
msas: OwnedMultiSegmentAppSegment,
|
|
parent: AstId,
|
|
): RawMultiSegmentAppSegment
|
|
function multiSegmentAppSegmentToRaw(
|
|
module: MutableModule,
|
|
msas: OwnedMultiSegmentAppSegment,
|
|
parent: AstId,
|
|
): RawMultiSegmentAppSegment
|
|
function multiSegmentAppSegmentToRaw(
|
|
module: MutableModule,
|
|
msas: OwnedMultiSegmentAppSegment | undefined,
|
|
parent: AstId,
|
|
): RawMultiSegmentAppSegment | undefined
|
|
function multiSegmentAppSegmentToRaw(
|
|
module: MutableModule,
|
|
msas: OwnedMultiSegmentAppSegment | undefined,
|
|
parent: AstId,
|
|
): RawMultiSegmentAppSegment | undefined {
|
|
if (!msas) return undefined
|
|
return {
|
|
...msas,
|
|
body: concreteChild(module, msas.body, parent),
|
|
}
|
|
}
|
|
interface ImportFields {
|
|
polyglot: RawMultiSegmentAppSegment | undefined
|
|
from: RawMultiSegmentAppSegment | undefined
|
|
import: RawMultiSegmentAppSegment
|
|
all: NodeChild<SyncTokenId> | undefined
|
|
as: RawMultiSegmentAppSegment | undefined
|
|
hiding: RawMultiSegmentAppSegment | undefined
|
|
}
|
|
export class Import extends Ast {
|
|
declare fields: FixedMapView<AstFields & ImportFields>
|
|
constructor(module: Module, fields: FixedMapView<AstFields & ImportFields>) {
|
|
super(module, fields)
|
|
}
|
|
|
|
static tryParse(source: string, module?: MutableModule): Owned<MutableImport> | undefined {
|
|
const parsed = parse(source, module)
|
|
if (parsed instanceof MutableImport) return parsed
|
|
}
|
|
|
|
get polyglot(): Ast | undefined {
|
|
return this.module.checkedGet(this.fields.get('polyglot')?.body?.node)
|
|
}
|
|
get from(): Ast | undefined {
|
|
return this.module.checkedGet(this.fields.get('from')?.body?.node)
|
|
}
|
|
get import_(): Ast | undefined {
|
|
return this.module.checkedGet(this.fields.get('import').body?.node)
|
|
}
|
|
get all(): Token | undefined {
|
|
return this.module.getToken(this.fields.get('all')?.node)
|
|
}
|
|
get as(): Ast | undefined {
|
|
return this.module.checkedGet(this.fields.get('as')?.body?.node)
|
|
}
|
|
get hiding(): Ast | undefined {
|
|
return this.module.checkedGet(this.fields.get('hiding')?.body?.node)
|
|
}
|
|
|
|
static concrete(
|
|
module: MutableModule,
|
|
polyglot: OwnedMultiSegmentAppSegment | undefined,
|
|
from: OwnedMultiSegmentAppSegment | undefined,
|
|
import_: OwnedMultiSegmentAppSegment,
|
|
all: NodeChild<Token> | undefined,
|
|
as: OwnedMultiSegmentAppSegment | undefined,
|
|
hiding: OwnedMultiSegmentAppSegment | undefined,
|
|
) {
|
|
const base = module.baseObject('Import')
|
|
const id_ = base.get('id')
|
|
const fields = setAll(base, {
|
|
polyglot: multiSegmentAppSegmentToRaw(module, polyglot, id_),
|
|
from: multiSegmentAppSegmentToRaw(module, from, id_),
|
|
import: multiSegmentAppSegmentToRaw(module, import_, id_),
|
|
all,
|
|
as: multiSegmentAppSegmentToRaw(module, as, id_),
|
|
hiding: multiSegmentAppSegmentToRaw(module, hiding, id_),
|
|
})
|
|
return asOwned(new MutableImport(module, fields))
|
|
}
|
|
|
|
static Qualified(path: IdentLike[], module: MutableModule): Owned<MutableImport> | undefined {
|
|
const path_ = PropertyAccess.Sequence(path, module)
|
|
if (!path_) return
|
|
return MutableImport.concrete(
|
|
module,
|
|
undefined,
|
|
undefined,
|
|
multiSegmentAppSegment('import', path_),
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
)
|
|
}
|
|
|
|
static Unqualified(
|
|
path: IdentLike[],
|
|
name: IdentLike,
|
|
module: MutableModule,
|
|
): Owned<MutableImport> | undefined {
|
|
const path_ = PropertyAccess.Sequence(path, module)
|
|
if (!path_) return
|
|
const name_ = Ident.newAllowingOperators(module, name)
|
|
return MutableImport.concrete(
|
|
module,
|
|
undefined,
|
|
multiSegmentAppSegment('from', path_),
|
|
multiSegmentAppSegment('import', name_),
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
)
|
|
}
|
|
|
|
*concreteChildren(_verbatim?: boolean): IterableIterator<NodeChild> {
|
|
const segment = (segment: RawMultiSegmentAppSegment | undefined) => {
|
|
const parts = []
|
|
if (segment) parts.push(segment.header)
|
|
if (segment?.body) parts.push(segment.body)
|
|
return parts
|
|
}
|
|
const { polyglot, from, import: import_, all, as, hiding } = getAll(this.fields)
|
|
yield* segment(polyglot)
|
|
yield* segment(from)
|
|
yield* segment(import_)
|
|
if (all) yield all
|
|
yield* segment(as)
|
|
yield* segment(hiding)
|
|
}
|
|
}
|
|
export class MutableImport extends Import implements MutableAst {
|
|
declare readonly module: MutableModule
|
|
declare readonly fields: FixedMap<AstFields & ImportFields>
|
|
|
|
private toRaw(msas: OwnedMultiSegmentAppSegment): RawMultiSegmentAppSegment
|
|
private toRaw(
|
|
msas: OwnedMultiSegmentAppSegment | undefined,
|
|
): RawMultiSegmentAppSegment | undefined
|
|
private toRaw(
|
|
msas: OwnedMultiSegmentAppSegment | undefined,
|
|
): RawMultiSegmentAppSegment | undefined {
|
|
return multiSegmentAppSegmentToRaw(this.module, msas, this.id)
|
|
}
|
|
|
|
setPolyglot<T extends MutableAst>(value: Owned<T> | undefined) {
|
|
this.fields.set(
|
|
'polyglot',
|
|
value ? this.toRaw(multiSegmentAppSegment('polyglot', value)) : undefined,
|
|
)
|
|
}
|
|
setFrom<T extends MutableAst>(value: Owned<T> | undefined) {
|
|
this.fields.set('from', value ? this.toRaw(multiSegmentAppSegment('from', value)) : value)
|
|
}
|
|
setImport<T extends MutableAst>(value: Owned<T>) {
|
|
this.fields.set('import', this.toRaw(multiSegmentAppSegment('import', value)))
|
|
}
|
|
setAll(value: Token | undefined) {
|
|
this.fields.set('all', spaced(value))
|
|
}
|
|
setAs<T extends MutableAst>(value: Owned<T> | undefined) {
|
|
this.fields.set('as', this.toRaw(multiSegmentAppSegment('as', value)))
|
|
}
|
|
setHiding<T extends MutableAst>(value: Owned<T> | undefined) {
|
|
this.fields.set('hiding', this.toRaw(multiSegmentAppSegment('hiding', value)))
|
|
}
|
|
|
|
replaceChild<T extends MutableAst>(target: AstId, replacement: Owned<T>) {
|
|
const { polyglot, from, import: import_, as, hiding } = getAll(this.fields)
|
|
polyglot?.body?.node === target
|
|
? this.setPolyglot(replacement)
|
|
: from?.body?.node === target
|
|
? this.setFrom(replacement)
|
|
: import_.body?.node === target
|
|
? this.setImport(replacement)
|
|
: as?.body?.node === target
|
|
? this.setAs(replacement)
|
|
: hiding?.body?.node === target
|
|
? this.setHiding(replacement)
|
|
: bail(`Failed to find child ${target} in node ${this.externalId}.`)
|
|
}
|
|
}
|
|
export interface MutableImport extends Import, MutableAst {
|
|
get polyglot(): MutableAst | undefined
|
|
get from(): MutableAst | undefined
|
|
get import_(): MutableAst | undefined
|
|
get as(): MutableAst | undefined
|
|
get hiding(): MutableAst | undefined
|
|
}
|
|
applyMixins(MutableImport, [MutableAst])
|
|
|
|
const mapping: Record<string, string> = {
|
|
'\b': '\\b',
|
|
'\f': '\\f',
|
|
'\n': '\\n',
|
|
'\r': '\\r',
|
|
'\t': '\\t',
|
|
'\v': '\\v',
|
|
'"': '\\"',
|
|
"'": "\\'",
|
|
'`': '``',
|
|
}
|
|
|
|
/** Escape a string so it can be safely spliced into an interpolated (`''`) Enso string.
|
|
* NOT USABLE to insert into raw strings. Does not include quotes. */
|
|
function escape(string: string) {
|
|
return string.replace(/[\0\b\f\n\r\t\v"'`]/g, (match) => mapping[match]!)
|
|
}
|
|
|
|
interface TextLiteralFields {
|
|
open: NodeChild<SyncTokenId> | undefined
|
|
newline: NodeChild<SyncTokenId> | undefined
|
|
elements: NodeChild[]
|
|
close: NodeChild<SyncTokenId> | undefined
|
|
}
|
|
export class TextLiteral extends Ast {
|
|
declare fields: FixedMapView<AstFields & TextLiteralFields>
|
|
constructor(module: Module, fields: FixedMapView<AstFields & TextLiteralFields>) {
|
|
super(module, fields)
|
|
}
|
|
|
|
static tryParse(source: string, module?: MutableModule): Owned<MutableTextLiteral> | undefined {
|
|
const parsed = parse(source, module)
|
|
if (parsed instanceof MutableTextLiteral) return parsed
|
|
}
|
|
|
|
static concrete(
|
|
module: MutableModule,
|
|
open: NodeChild<Token> | undefined,
|
|
newline: NodeChild<Token> | undefined,
|
|
elements: NodeChild<Owned | Token>[],
|
|
close: NodeChild<Token> | undefined,
|
|
) {
|
|
const base = module.baseObject('TextLiteral')
|
|
const id_ = base.get('id')
|
|
const fields = setAll(base, {
|
|
open,
|
|
newline,
|
|
elements: elements.map((elem) => concreteChild(module, elem, id_)),
|
|
close,
|
|
})
|
|
return asOwned(new MutableTextLiteral(module, fields))
|
|
}
|
|
|
|
static new(rawText: string, module: MutableModule) {
|
|
const open = unspaced(Token.new("'"))
|
|
const elements = [unspaced(Token.new(escape(rawText)))]
|
|
const close = unspaced(Token.new("'"))
|
|
return this.concrete(module, open, undefined, elements, close)
|
|
}
|
|
|
|
*concreteChildren(_verbatim?: boolean): IterableIterator<NodeChild> {
|
|
const { open, newline, elements, close } = getAll(this.fields)
|
|
if (open) yield open
|
|
if (newline) yield newline
|
|
yield* elements
|
|
if (close) yield close
|
|
}
|
|
}
|
|
export class MutableTextLiteral extends TextLiteral implements MutableAst {
|
|
declare readonly module: MutableModule
|
|
declare readonly fields: FixedMap<AstFields & TextLiteralFields>
|
|
|
|
replaceChild<T extends MutableAst>(target: AstId, replacement: Owned<T>) {
|
|
const replacement_ = autospaced(this.claimChild(replacement))
|
|
this.fields.set(
|
|
'elements',
|
|
this.fields.get('elements').map((child) => (child.node === target ? replacement_ : child)),
|
|
)
|
|
}
|
|
}
|
|
export interface MutableTextLiteral extends TextLiteral, MutableAst {}
|
|
applyMixins(MutableTextLiteral, [MutableAst])
|
|
|
|
interface DocumentedFields {
|
|
open: NodeChild<SyncTokenId> | undefined
|
|
elements: NodeChild[]
|
|
newlines: NodeChild<SyncTokenId>[]
|
|
expression: NodeChild<AstId> | undefined
|
|
}
|
|
export class Documented extends Ast {
|
|
declare fields: FixedMapView<AstFields & DocumentedFields>
|
|
constructor(module: Module, fields: FixedMapView<AstFields & DocumentedFields>) {
|
|
super(module, fields)
|
|
}
|
|
|
|
static tryParse(source: string, module?: MutableModule): Owned<MutableDocumented> | undefined {
|
|
const parsed = parse(source, module)
|
|
if (parsed instanceof MutableDocumented) return parsed
|
|
}
|
|
|
|
static concrete(
|
|
module: MutableModule,
|
|
open: NodeChild<Token> | undefined,
|
|
elements: NodeChild<Owned | Token>[],
|
|
newlines: NodeChild<Token>[],
|
|
expression: NodeChild<Owned> | undefined,
|
|
) {
|
|
const base = module.baseObject('Documented')
|
|
const id_ = base.get('id')
|
|
const fields = setAll(base, {
|
|
open,
|
|
elements: elements.map((elem) => concreteChild(module, elem, id_)),
|
|
newlines,
|
|
expression: concreteChild(module, expression, id_),
|
|
})
|
|
return asOwned(new MutableDocumented(module, fields))
|
|
}
|
|
|
|
get expression(): Ast | undefined {
|
|
return this.module.checkedGet(this.fields.get('expression')?.node)
|
|
}
|
|
|
|
*concreteChildren(_verbatim?: boolean): IterableIterator<NodeChild> {
|
|
const { open, elements, newlines, expression } = getAll(this.fields)
|
|
if (open) yield open
|
|
yield* elements
|
|
yield* newlines
|
|
if (expression) yield expression
|
|
}
|
|
}
|
|
export class MutableDocumented extends Documented implements MutableAst {
|
|
declare readonly module: MutableModule
|
|
declare readonly fields: FixedMap<AstFields & DocumentedFields>
|
|
|
|
setExpression<T extends MutableAst>(value: Owned<T> | undefined) {
|
|
this.fields.set('expression', unspaced(this.claimChild(value)))
|
|
}
|
|
|
|
replaceChild<T extends MutableAst>(target: AstId, replacement: Owned<T>) {
|
|
if (this.fields.get('expression')?.node === target) {
|
|
this.setExpression(replacement)
|
|
} else {
|
|
const replacement_ = unspaced(this.claimChild(replacement))
|
|
this.fields.set(
|
|
'elements',
|
|
this.fields.get('elements').map((child) => (child.node === target ? replacement_ : child)),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
export interface MutableDocumented extends Documented, MutableAst {
|
|
get expression(): MutableAst | undefined
|
|
}
|
|
applyMixins(MutableDocumented, [MutableAst])
|
|
|
|
interface InvalidFields {
|
|
expression: NodeChild<AstId>
|
|
}
|
|
export class Invalid extends Ast {
|
|
declare fields: FixedMapView<AstFields & InvalidFields>
|
|
constructor(module: Module, fields: FixedMapView<AstFields & InvalidFields>) {
|
|
super(module, fields)
|
|
}
|
|
|
|
static concrete(module: MutableModule, expression: NodeChild<Owned>) {
|
|
const base = module.baseObject('Invalid')
|
|
return asOwned(new MutableInvalid(module, invalidFields(module, base, expression)))
|
|
}
|
|
|
|
get expression(): Ast {
|
|
return this.module.checkedGet(this.fields.get('expression').node)
|
|
}
|
|
|
|
*concreteChildren(_verbatim?: boolean): IterableIterator<NodeChild> {
|
|
yield this.fields.get('expression')
|
|
}
|
|
|
|
printSubtree(
|
|
info: SpanMap,
|
|
offset: number,
|
|
parentIndent: string | undefined,
|
|
_verbatim?: boolean,
|
|
): string {
|
|
return super.printSubtree(info, offset, parentIndent, true)
|
|
}
|
|
}
|
|
export function invalidFields(
|
|
module: MutableModule,
|
|
base: FixedMap<AstFields>,
|
|
expression: NodeChild<Owned>,
|
|
): FixedMap<AstFields & InvalidFields> {
|
|
const id_ = base.get('id')
|
|
return setAll(base, { expression: concreteChild(module, expression, id_) })
|
|
}
|
|
export class MutableInvalid extends Invalid implements MutableAst {
|
|
declare readonly module: MutableModule
|
|
declare readonly fields: FixedMap<AstFields & InvalidFields>
|
|
|
|
/** Private, because it makes more sense to `.replace` the `Invalid` node. */
|
|
private setExpression<T extends MutableAst>(value: Owned<T>) {
|
|
this.fields.set('expression', unspaced(this.claimChild(value)))
|
|
}
|
|
|
|
replaceChild<T extends MutableAst>(target: AstId, replacement: Owned<T>) {
|
|
assertEqual(this.fields.get('expression').node, target)
|
|
this.setExpression(replacement)
|
|
}
|
|
}
|
|
export interface MutableInvalid extends Invalid, MutableAst {
|
|
/** The `expression` getter is intentionally not narrowed to provide mutable access:
|
|
* It makes more sense to `.replace` the `Invalid` node. */
|
|
}
|
|
applyMixins(MutableInvalid, [MutableAst])
|
|
|
|
interface GroupFields {
|
|
open: NodeChild<SyncTokenId> | undefined
|
|
expression: NodeChild<AstId> | undefined
|
|
close: NodeChild<SyncTokenId> | undefined
|
|
}
|
|
export class Group extends Ast {
|
|
declare fields: FixedMapView<AstFields & GroupFields>
|
|
constructor(module: Module, fields: FixedMapView<AstFields & GroupFields>) {
|
|
super(module, fields)
|
|
}
|
|
|
|
static tryParse(source: string, module?: MutableModule): Owned<MutableGroup> | undefined {
|
|
const parsed = parse(source, module)
|
|
if (parsed instanceof MutableGroup) return parsed
|
|
}
|
|
|
|
static concrete(
|
|
module: MutableModule,
|
|
open: NodeChild<Token> | undefined,
|
|
expression: NodeChild<Owned> | undefined,
|
|
close: NodeChild<Token> | undefined,
|
|
) {
|
|
const base = module.baseObject('Group')
|
|
const id_ = base.get('id')
|
|
const fields = setAll(base, { open, expression: concreteChild(module, expression, id_), close })
|
|
return asOwned(new MutableGroup(module, fields))
|
|
}
|
|
|
|
static new(module: MutableModule, expression: Owned) {
|
|
const open = unspaced(Token.new('(', RawAst.Token.Type.OpenSymbol))
|
|
const close = unspaced(Token.new(')', RawAst.Token.Type.CloseSymbol))
|
|
return this.concrete(module, open, unspaced(expression), close)
|
|
}
|
|
|
|
get expression(): Ast | undefined {
|
|
return this.module.checkedGet(this.fields.get('expression')?.node)
|
|
}
|
|
|
|
*concreteChildren(_verbatim?: boolean): IterableIterator<NodeChild> {
|
|
const { open, expression, close } = getAll(this.fields)
|
|
if (open) yield open
|
|
if (expression) yield expression
|
|
if (close) yield close
|
|
}
|
|
}
|
|
export class MutableGroup extends Group implements MutableAst {
|
|
declare readonly module: MutableModule
|
|
declare readonly fields: FixedMap<AstFields & GroupFields>
|
|
|
|
setExpression<T extends MutableAst>(value: Owned<T> | undefined) {
|
|
this.fields.set('expression', unspaced(this.claimChild(value)))
|
|
}
|
|
|
|
replaceChild<T extends MutableAst>(target: AstId, replacement: Owned<T>) {
|
|
assertEqual(this.fields.get('expression')?.node, target)
|
|
this.setExpression(replacement)
|
|
}
|
|
}
|
|
export interface MutableGroup extends Group, MutableAst {
|
|
get expression(): MutableAst | undefined
|
|
}
|
|
applyMixins(MutableGroup, [MutableAst])
|
|
|
|
interface NumericLiteralFields {
|
|
tokens: NodeChild<SyncTokenId>[]
|
|
}
|
|
export class NumericLiteral extends Ast {
|
|
declare fields: FixedMapView<AstFields & NumericLiteralFields>
|
|
constructor(module: Module, fields: FixedMapView<AstFields & NumericLiteralFields>) {
|
|
super(module, fields)
|
|
}
|
|
|
|
static tryParse(
|
|
source: string,
|
|
module?: MutableModule,
|
|
): Owned<MutableNumericLiteral> | undefined {
|
|
const parsed = parse(source, module)
|
|
if (parsed instanceof MutableNumericLiteral) return parsed
|
|
}
|
|
|
|
static concrete(module: MutableModule, tokens: NodeChild<Token>[]) {
|
|
const base = module.baseObject('NumericLiteral')
|
|
const fields = setAll(base, { tokens })
|
|
return asOwned(new MutableNumericLiteral(module, fields))
|
|
}
|
|
|
|
concreteChildren(_verbatim?: boolean): IterableIterator<NodeChild> {
|
|
return this.fields.get('tokens')[Symbol.iterator]()
|
|
}
|
|
}
|
|
export class MutableNumericLiteral extends NumericLiteral implements MutableAst {
|
|
declare readonly module: MutableModule
|
|
declare readonly fields: FixedMap<AstFields & NumericLiteralFields>
|
|
|
|
replaceChild<T extends MutableAst>(_target: AstId, _replacement: Owned<T>) {}
|
|
}
|
|
export interface MutableNumericLiteral extends NumericLiteral, MutableAst {}
|
|
applyMixins(MutableNumericLiteral, [MutableAst])
|
|
|
|
/** The actual contents of an `ArgumentDefinition` are complex, but probably of more interest to the compiler than the
|
|
* GUI. We just need to represent them faithfully and create the simple cases. */
|
|
type ArgumentDefinition = NodeChild<Ast | Token>[]
|
|
type RawArgumentDefinition = NodeChild[]
|
|
type OwnedArgumentDefinition = NodeChild<Owned | Token>[]
|
|
|
|
function argumentDefinitionsToRaw(
|
|
module: MutableModule,
|
|
defs: OwnedArgumentDefinition[],
|
|
parent: AstId,
|
|
): RawArgumentDefinition[] {
|
|
return defs.map((def) =>
|
|
def.map((part) => ({
|
|
...part,
|
|
node: part.node instanceof Token ? part.node : claimChild(module, part.node, parent),
|
|
})),
|
|
)
|
|
}
|
|
|
|
interface FunctionFields {
|
|
name: NodeChild<AstId>
|
|
argumentDefinitions: RawArgumentDefinition[]
|
|
equals: NodeChild<SyncTokenId>
|
|
body: NodeChild<AstId> | undefined
|
|
}
|
|
export class Function extends Ast {
|
|
declare fields: FixedMapView<AstFields & FunctionFields>
|
|
constructor(module: Module, fields: FixedMapView<AstFields & FunctionFields>) {
|
|
super(module, fields)
|
|
}
|
|
|
|
static tryParse(source: string, module?: MutableModule): Owned<MutableFunction> | undefined {
|
|
const parsed = parse(source, module)
|
|
if (parsed instanceof MutableFunction) return parsed
|
|
}
|
|
|
|
get name(): Ast {
|
|
return this.module.checkedGet(this.fields.get('name').node)
|
|
}
|
|
get body(): Ast | undefined {
|
|
return this.module.checkedGet(this.fields.get('body')?.node)
|
|
}
|
|
get argumentDefinitions(): ArgumentDefinition[] {
|
|
return this.fields.get('argumentDefinitions').map((raw) =>
|
|
raw.map((part) => ({
|
|
...part,
|
|
node: this.module.getAny(part.node),
|
|
})),
|
|
)
|
|
}
|
|
|
|
static concrete(
|
|
module: MutableModule,
|
|
name: NodeChild<Owned>,
|
|
argumentDefinitions: OwnedArgumentDefinition[],
|
|
equals: NodeChild<Token>,
|
|
body: NodeChild<Owned> | undefined,
|
|
) {
|
|
const base = module.baseObject('Function')
|
|
const id_ = base.get('id')
|
|
const fields = setAll(base, {
|
|
name: concreteChild(module, name, id_),
|
|
argumentDefinitions: argumentDefinitionsToRaw(module, argumentDefinitions, id_),
|
|
equals,
|
|
body: concreteChild(module, body, id_),
|
|
})
|
|
return asOwned(new MutableFunction(module, fields))
|
|
}
|
|
|
|
static new(
|
|
module: MutableModule,
|
|
name: IdentLike,
|
|
argumentDefinitions: OwnedArgumentDefinition[],
|
|
body: Owned,
|
|
): Owned<MutableFunction> {
|
|
// Note that a function name may not be an operator if the function is not in the body of a type definition, but we
|
|
// can't easily enforce that because we don't currently make a syntactic distinction between top-level functions and
|
|
// type methods.
|
|
return MutableFunction.concrete(
|
|
module,
|
|
unspaced(Ident.newAllowingOperators(module, name)),
|
|
argumentDefinitions,
|
|
spaced(makeEquals()),
|
|
autospaced(body),
|
|
)
|
|
}
|
|
|
|
/** Construct a function with simple (name-only) arguments and a body block. */
|
|
static fromStatements(
|
|
module: MutableModule,
|
|
name: IdentLike,
|
|
argumentNames: StrictIdentLike[],
|
|
statements: Owned[],
|
|
): Owned<MutableFunction> {
|
|
const statements_: OwnedBlockLine[] = statements.map((statement) => ({
|
|
expression: unspaced(statement),
|
|
}))
|
|
const argumentDefinitions = argumentNames.map((name) => [spaced(Ident.new(module, name))])
|
|
const body = BodyBlock.new(statements_, module)
|
|
return MutableFunction.new(module, name, argumentDefinitions, body)
|
|
}
|
|
|
|
*bodyExpressions(): IterableIterator<Ast> {
|
|
const body = this.body
|
|
if (body instanceof BodyBlock) {
|
|
yield* body.statements()
|
|
} else if (body) {
|
|
yield body
|
|
}
|
|
}
|
|
|
|
*concreteChildren(verbatim?: boolean): IterableIterator<NodeChild> {
|
|
const { name, argumentDefinitions, equals, body } = getAll(this.fields)
|
|
yield name
|
|
for (const def of argumentDefinitions) yield* def
|
|
yield { whitespace: equals.whitespace ?? ' ', node: this.module.getToken(equals.node) }
|
|
if (body)
|
|
yield ensureSpacedOnlyIf(body, !(this.module.get(body.node) instanceof BodyBlock), verbatim)
|
|
}
|
|
}
|
|
export class MutableFunction extends Function implements MutableAst {
|
|
declare readonly module: MutableModule
|
|
declare readonly fields: FixedMap<AstFields & FunctionFields>
|
|
|
|
setName<T extends MutableAst>(value: Owned<T>) {
|
|
this.fields.set('name', unspaced(this.claimChild(value)))
|
|
}
|
|
setBody<T extends MutableAst>(value: Owned<T> | undefined) {
|
|
this.fields.set('body', unspaced(this.claimChild(value)))
|
|
}
|
|
setArgumentDefinitions(defs: OwnedArgumentDefinition[]) {
|
|
this.fields.set('argumentDefinitions', argumentDefinitionsToRaw(this.module, defs, this.id))
|
|
}
|
|
|
|
/** Returns the body, after converting it to a block if it was empty or an inline expression. */
|
|
bodyAsBlock(): MutableBodyBlock {
|
|
const oldBody = this.body
|
|
if (oldBody instanceof MutableBodyBlock) return oldBody
|
|
const newBody = BodyBlock.new([], this.module)
|
|
if (oldBody) newBody.push(oldBody.take())
|
|
return newBody
|
|
}
|
|
|
|
replaceChild<T extends MutableAst>(target: AstId, replacement: Owned<T>) {
|
|
const { name, argumentDefinitions, body } = getAll(this.fields)
|
|
if (name.node === target) {
|
|
this.setName(replacement)
|
|
} else if (body?.node === target) {
|
|
this.setBody(replacement)
|
|
} else {
|
|
const replacement_ = this.claimChild(replacement)
|
|
const replaceChild = (child: NodeChild) =>
|
|
child.node === target ? { ...child, node: replacement_ } : child
|
|
this.fields.set(
|
|
'argumentDefinitions',
|
|
argumentDefinitions.map((def) => def.map(replaceChild)),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
export interface MutableFunction extends Function, MutableAst {
|
|
get name(): MutableAst
|
|
get body(): MutableAst | undefined
|
|
}
|
|
applyMixins(MutableFunction, [MutableAst])
|
|
|
|
interface AssignmentFields {
|
|
pattern: NodeChild<AstId>
|
|
equals: NodeChild<SyncTokenId>
|
|
expression: NodeChild<AstId>
|
|
}
|
|
export class Assignment extends Ast {
|
|
declare fields: FixedMapView<AstFields & AssignmentFields>
|
|
constructor(module: Module, fields: FixedMapView<AstFields & AssignmentFields>) {
|
|
super(module, fields)
|
|
}
|
|
|
|
static tryParse(source: string, module?: MutableModule): Owned<MutableAssignment> | undefined {
|
|
const parsed = parse(source, module)
|
|
if (parsed instanceof MutableAssignment) return parsed
|
|
}
|
|
|
|
static concrete(
|
|
module: MutableModule,
|
|
pattern: NodeChild<Owned>,
|
|
equals: NodeChild<Token>,
|
|
expression: NodeChild<Owned>,
|
|
) {
|
|
const base = module.baseObject('Assignment')
|
|
const id_ = base.get('id')
|
|
const fields = setAll(base, {
|
|
pattern: concreteChild(module, pattern, id_),
|
|
equals,
|
|
expression: concreteChild(module, expression, id_),
|
|
})
|
|
return asOwned(new MutableAssignment(module, fields))
|
|
}
|
|
|
|
static new(module: MutableModule, ident: StrictIdentLike, expression: Owned) {
|
|
return Assignment.concrete(
|
|
module,
|
|
unspaced(Ident.new(module, ident)),
|
|
spaced(makeEquals()),
|
|
spaced(expression),
|
|
)
|
|
}
|
|
|
|
get pattern(): Ast {
|
|
return this.module.checkedGet(this.fields.get('pattern').node)
|
|
}
|
|
get expression(): Ast {
|
|
return this.module.checkedGet(this.fields.get('expression').node)
|
|
}
|
|
|
|
*concreteChildren(verbatim?: boolean): IterableIterator<NodeChild> {
|
|
const { pattern, equals, expression } = getAll(this.fields)
|
|
yield pattern
|
|
yield ensureSpacedOnlyIf(equals, expression.whitespace !== '', verbatim)
|
|
yield expression
|
|
}
|
|
}
|
|
export class MutableAssignment extends Assignment implements MutableAst {
|
|
declare readonly module: MutableModule
|
|
declare readonly fields: FixedMap<AstFields & AssignmentFields>
|
|
|
|
setPattern<T extends MutableAst>(value: Owned<T>) {
|
|
this.fields.set('pattern', unspaced(this.claimChild(value)))
|
|
}
|
|
setExpression<T extends MutableAst>(value: Owned<T>) {
|
|
setNode(this.fields, 'expression', this.claimChild(value))
|
|
}
|
|
|
|
replaceChild<T extends MutableAst>(target: AstId, replacement: Owned<T>) {
|
|
const { pattern, expression } = getAll(this.fields)
|
|
if (pattern.node === target) {
|
|
this.setPattern(replacement)
|
|
} else if (expression.node === target) {
|
|
this.setExpression(replacement)
|
|
}
|
|
}
|
|
}
|
|
export interface MutableAssignment extends Assignment, MutableAst {
|
|
get pattern(): MutableAst
|
|
get expression(): MutableAst
|
|
}
|
|
applyMixins(MutableAssignment, [MutableAst])
|
|
|
|
interface BodyBlockFields {
|
|
lines: RawBlockLine[]
|
|
}
|
|
export class BodyBlock extends Ast {
|
|
declare fields: FixedMapView<AstFields & BodyBlockFields>
|
|
constructor(module: Module, fields: FixedMapView<AstFields & BodyBlockFields>) {
|
|
super(module, fields)
|
|
}
|
|
|
|
static tryParse(source: string, module?: MutableModule): Owned<MutableBodyBlock> | undefined {
|
|
const parsed = parse(source, module)
|
|
if (parsed instanceof MutableBodyBlock) return parsed
|
|
}
|
|
|
|
static concrete(module: MutableModule, lines: OwnedBlockLine[]) {
|
|
const base = module.baseObject('BodyBlock')
|
|
const id_ = base.get('id')
|
|
const fields = setAll(base, {
|
|
lines: lines.map((line) => lineToRaw(line, module, id_)),
|
|
})
|
|
return asOwned(new MutableBodyBlock(module, fields))
|
|
}
|
|
|
|
static new(lines: OwnedBlockLine[], module: MutableModule) {
|
|
return BodyBlock.concrete(module, lines)
|
|
}
|
|
|
|
get lines(): BlockLine[] {
|
|
return this.fields.get('lines').map((line) => lineFromRaw(line, this.module))
|
|
}
|
|
|
|
*statements(): IterableIterator<Ast> {
|
|
for (const line of this.lines) {
|
|
if (line.expression) yield line.expression.node
|
|
}
|
|
}
|
|
|
|
*concreteChildren(_verbatim?: boolean): IterableIterator<NodeChild> {
|
|
for (const line of this.fields.get('lines')) {
|
|
yield line.newline ?? { node: Token.new('\n', RawAst.Token.Type.Newline) }
|
|
if (line.expression) yield line.expression
|
|
}
|
|
}
|
|
|
|
printSubtree(
|
|
info: SpanMap,
|
|
offset: number,
|
|
parentIndent: string | undefined,
|
|
verbatim?: boolean,
|
|
): string {
|
|
return printBlock(this, info, offset, parentIndent, verbatim)
|
|
}
|
|
}
|
|
export class MutableBodyBlock extends BodyBlock implements MutableAst {
|
|
declare readonly module: MutableModule
|
|
declare readonly fields: FixedMap<AstFields & BodyBlockFields>
|
|
|
|
updateLines(map: (lines: OwnedBlockLine[]) => OwnedBlockLine[]) {
|
|
return this.setLines(map(this.takeLines()))
|
|
}
|
|
takeLines(): OwnedBlockLine[] {
|
|
return this.fields.get('lines').map((line) => ownedLineFromRaw(line, this.module))
|
|
}
|
|
setLines(lines: OwnedBlockLine[]) {
|
|
this.fields.set(
|
|
'lines',
|
|
lines.map((line) => lineToRaw(line, this.module, this.id)),
|
|
)
|
|
}
|
|
|
|
/** Insert the given statement(s) starting at the specified line index. */
|
|
insert(index: number, ...statements: (Owned | undefined)[]) {
|
|
const before = this.fields.get('lines').slice(0, index)
|
|
const insertions = statements.map((statement) => ({
|
|
newline: unspaced(Token.new('\n', RawAst.Token.Type.Newline)),
|
|
expression: statement && unspaced(this.claimChild(statement)),
|
|
}))
|
|
const after = this.fields.get('lines').slice(index)
|
|
this.fields.set('lines', [...before, ...insertions, ...after])
|
|
}
|
|
|
|
push(statement: Owned) {
|
|
const oldLines = this.fields.get('lines')
|
|
const newLine = {
|
|
newline: unspaced(Token.new('\n', RawAst.Token.Type.Newline)),
|
|
expression: unspaced(this.claimChild(statement)),
|
|
}
|
|
this.fields.set('lines', [...oldLines, newLine])
|
|
}
|
|
|
|
filter(keep: (ast: MutableAst) => boolean) {
|
|
const oldLines = this.fields.get('lines')
|
|
const filteredLines = oldLines.filter((line) => {
|
|
if (!line.expression) return true
|
|
return keep(this.module.checkedGet(line.expression.node))
|
|
})
|
|
this.fields.set('lines', filteredLines)
|
|
}
|
|
|
|
replaceChild<T extends MutableAst>(target: AstId, replacement: Owned<T>) {
|
|
const replacement_ = this.claimChild(replacement)
|
|
const updateLine = (line: RawBlockLine) =>
|
|
line.expression?.node === target
|
|
? { ...line, expression: { ...line.expression, node: replacement_ } }
|
|
: line
|
|
this.fields.set('lines', this.fields.get('lines').map(updateLine))
|
|
}
|
|
}
|
|
export interface MutableBodyBlock extends BodyBlock, MutableAst {
|
|
statements(): IterableIterator<MutableAst>
|
|
}
|
|
applyMixins(MutableBodyBlock, [MutableAst])
|
|
|
|
interface RawLine<T> {
|
|
newline: NodeChild<SyncTokenId>
|
|
expression: NodeChild<T> | undefined
|
|
}
|
|
interface Line<T> {
|
|
newline?: NodeChild<Token> | undefined
|
|
expression: NodeChild<T> | undefined
|
|
}
|
|
|
|
type RawBlockLine = RawLine<AstId>
|
|
export type BlockLine = Line<Ast>
|
|
export type OwnedBlockLine = Line<Owned>
|
|
|
|
function lineFromRaw(raw: RawBlockLine, module: Module): BlockLine {
|
|
const expression = raw.expression ? module.checkedGet(raw.expression.node) : undefined
|
|
return {
|
|
newline: { ...raw.newline, node: module.getToken(raw.newline.node) },
|
|
expression: expression
|
|
? {
|
|
whitespace: raw.expression?.whitespace,
|
|
node: expression,
|
|
}
|
|
: undefined,
|
|
}
|
|
}
|
|
|
|
function ownedLineFromRaw(raw: RawBlockLine, module: MutableModule): OwnedBlockLine {
|
|
const expression = raw.expression
|
|
? module.checkedGet(raw.expression.node).takeIfParented()
|
|
: undefined
|
|
return {
|
|
newline: { ...raw.newline, node: module.getToken(raw.newline.node) },
|
|
expression: expression
|
|
? {
|
|
whitespace: raw.expression?.whitespace,
|
|
node: expression,
|
|
}
|
|
: undefined,
|
|
}
|
|
}
|
|
|
|
function lineToRaw(line: OwnedBlockLine, module: MutableModule, block: AstId): RawBlockLine {
|
|
return {
|
|
newline: line.newline ?? unspaced(Token.new('\n', RawAst.Token.Type.Newline)),
|
|
expression: line.expression
|
|
? {
|
|
whitespace: line.expression?.whitespace,
|
|
node: claimChild(module, line.expression.node, block),
|
|
}
|
|
: undefined,
|
|
}
|
|
}
|
|
|
|
interface IdentFields {
|
|
token: NodeChild<SyncTokenId>
|
|
}
|
|
export class Ident extends Ast {
|
|
declare fields: FixedMapView<AstFields & IdentFields>
|
|
constructor(module: Module, fields: FixedMapView<AstFields & IdentFields>) {
|
|
super(module, fields)
|
|
}
|
|
|
|
static tryParse(source: string, module?: MutableModule): Owned<MutableIdent> | undefined {
|
|
const parsed = parse(source, module)
|
|
if (parsed instanceof MutableIdent) return parsed
|
|
}
|
|
|
|
get token(): IdentifierToken {
|
|
return this.module.getToken(this.fields.get('token').node) as IdentifierToken
|
|
}
|
|
|
|
static concrete(module: MutableModule, token: NodeChild<Token>) {
|
|
const base = module.baseObject('Ident')
|
|
const fields = setAll(base, { token })
|
|
return asOwned(new MutableIdent(module, fields))
|
|
}
|
|
|
|
static new(module: MutableModule, ident: StrictIdentLike) {
|
|
return Ident.concrete(module, unspaced(toIdentStrict(ident)))
|
|
}
|
|
|
|
/** @internal */
|
|
static newAllowingOperators(module: MutableModule, ident: IdentLike) {
|
|
return Ident.concrete(module, unspaced(toIdent(ident)))
|
|
}
|
|
|
|
*concreteChildren(_verbatim?: boolean): IterableIterator<NodeChild> {
|
|
yield this.fields.get('token')
|
|
}
|
|
|
|
code(): Identifier {
|
|
return this.token.code() as Identifier
|
|
}
|
|
}
|
|
export class MutableIdent extends Ident implements MutableAst {
|
|
declare readonly module: MutableModule
|
|
declare readonly fields: FixedMap<AstFields & IdentFields>
|
|
|
|
setToken(ident: IdentLike) {
|
|
this.fields.set('token', unspaced(toIdent(ident)))
|
|
}
|
|
|
|
replaceChild<T extends MutableAst>(_target: AstId, _replacement: Owned<T>) {}
|
|
|
|
code(): Identifier {
|
|
return this.token.code()
|
|
}
|
|
}
|
|
export interface MutableIdent extends Ident, MutableAst {}
|
|
applyMixins(MutableIdent, [MutableAst])
|
|
|
|
interface WildcardFields {
|
|
token: NodeChild<SyncTokenId>
|
|
}
|
|
export class Wildcard extends Ast {
|
|
declare fields: FixedMapView<AstFields & WildcardFields>
|
|
constructor(module: Module, fields: FixedMapView<AstFields & WildcardFields>) {
|
|
super(module, fields)
|
|
}
|
|
|
|
static tryParse(source: string, module?: MutableModule): Owned<MutableWildcard> | undefined {
|
|
const parsed = parse(source, module)
|
|
if (parsed instanceof MutableWildcard) return parsed
|
|
}
|
|
|
|
get token(): Token {
|
|
return this.module.getToken(this.fields.get('token').node)
|
|
}
|
|
|
|
static concrete(module: MutableModule, token: NodeChild<Token>) {
|
|
const base = module.baseObject('Wildcard')
|
|
const fields = setAll(base, { token })
|
|
return asOwned(new MutableWildcard(module, fields))
|
|
}
|
|
|
|
static new(module: MutableModule) {
|
|
const token = Token.new('_', RawAst.Token.Type.Wildcard)
|
|
return this.concrete(module, unspaced(token))
|
|
}
|
|
|
|
*concreteChildren(_verbatim?: boolean): IterableIterator<NodeChild> {
|
|
yield this.fields.get('token')
|
|
}
|
|
}
|
|
|
|
export class MutableWildcard extends Wildcard implements MutableAst {
|
|
declare readonly module: MutableModule
|
|
declare readonly fields: FixedMap<AstFields & WildcardFields>
|
|
|
|
replaceChild<T extends MutableAst>(_target: AstId, _replacement: Owned<T>) {}
|
|
}
|
|
export interface MutableWildcard extends Wildcard, MutableAst {}
|
|
applyMixins(MutableWildcard, [MutableAst])
|
|
|
|
export type Mutable<T extends Ast = Ast> = T extends App
|
|
? MutableApp
|
|
: T extends Assignment
|
|
? MutableAssignment
|
|
: T extends BodyBlock
|
|
? MutableBodyBlock
|
|
: T extends Documented
|
|
? MutableDocumented
|
|
: T extends Function
|
|
? MutableFunction
|
|
: T extends Generic
|
|
? MutableGeneric
|
|
: T extends Group
|
|
? MutableGroup
|
|
: T extends Ident
|
|
? MutableIdent
|
|
: T extends Import
|
|
? MutableImport
|
|
: T extends Invalid
|
|
? MutableInvalid
|
|
: T extends NegationApp
|
|
? MutableNegationApp
|
|
: T extends NumericLiteral
|
|
? MutableNumericLiteral
|
|
: T extends OprApp
|
|
? MutableOprApp
|
|
: T extends PropertyAccess
|
|
? MutablePropertyAccess
|
|
: T extends TextLiteral
|
|
? MutableTextLiteral
|
|
: T extends UnaryOprApp
|
|
? MutableUnaryOprApp
|
|
: T extends Wildcard
|
|
? MutableWildcard
|
|
: MutableAst
|
|
|
|
export function materializeMutable(module: MutableModule, fields: FixedMap<AstFields>): MutableAst {
|
|
const type = fields.get('type')
|
|
const fieldsForType = fields as FixedMap<any>
|
|
switch (type) {
|
|
case 'App':
|
|
return new MutableApp(module, fieldsForType)
|
|
case 'UnaryOprApp':
|
|
return new MutableUnaryOprApp(module, fieldsForType)
|
|
case 'NegationApp':
|
|
return new MutableNegationApp(module, fieldsForType)
|
|
case 'OprApp':
|
|
return new MutableOprApp(module, fieldsForType)
|
|
case 'PropertyAccess':
|
|
return new MutablePropertyAccess(module, fieldsForType)
|
|
case 'Generic':
|
|
return new MutableGeneric(module, fieldsForType)
|
|
case 'Import':
|
|
return new MutableImport(module, fieldsForType)
|
|
case 'TextLiteral':
|
|
return new MutableTextLiteral(module, fieldsForType)
|
|
case 'Documented':
|
|
return new MutableDocumented(module, fieldsForType)
|
|
case 'Invalid':
|
|
return new MutableInvalid(module, fieldsForType)
|
|
case 'Group':
|
|
return new MutableGroup(module, fieldsForType)
|
|
case 'NumericLiteral':
|
|
return new MutableNumericLiteral(module, fieldsForType)
|
|
case 'Function':
|
|
return new MutableFunction(module, fieldsForType)
|
|
case 'Assignment':
|
|
return new MutableAssignment(module, fieldsForType)
|
|
case 'BodyBlock':
|
|
return new MutableBodyBlock(module, fieldsForType)
|
|
case 'Ident':
|
|
return new MutableIdent(module, fieldsForType)
|
|
case 'Wildcard':
|
|
return new MutableWildcard(module, fieldsForType)
|
|
}
|
|
bail(`Invalid type: ${type}`)
|
|
}
|
|
|
|
export function materialize(module: Module, fields: FixedMapView<AstFields>): Ast {
|
|
const type = fields.get('type')
|
|
const fields_ = fields as FixedMapView<any>
|
|
switch (type) {
|
|
case 'App':
|
|
return new App(module, fields_)
|
|
case 'UnaryOprApp':
|
|
return new UnaryOprApp(module, fields_)
|
|
case 'NegationApp':
|
|
return new NegationApp(module, fields_)
|
|
case 'OprApp':
|
|
return new OprApp(module, fields_)
|
|
case 'PropertyAccess':
|
|
return new PropertyAccess(module, fields_)
|
|
case 'Generic':
|
|
return new Generic(module, fields_)
|
|
case 'Import':
|
|
return new Import(module, fields_)
|
|
case 'TextLiteral':
|
|
return new TextLiteral(module, fields_)
|
|
case 'Documented':
|
|
return new Documented(module, fields_)
|
|
case 'Invalid':
|
|
return new Invalid(module, fields_)
|
|
case 'Group':
|
|
return new Group(module, fields_)
|
|
case 'NumericLiteral':
|
|
return new NumericLiteral(module, fields_)
|
|
case 'Function':
|
|
return new Function(module, fields_)
|
|
case 'Assignment':
|
|
return new Assignment(module, fields_)
|
|
case 'BodyBlock':
|
|
return new BodyBlock(module, fields_)
|
|
case 'Ident':
|
|
return new Ident(module, fields_)
|
|
case 'Wildcard':
|
|
return new Wildcard(module, fields_)
|
|
}
|
|
bail(`Invalid type: ${type}`)
|
|
}
|
|
|
|
export interface FixedMapView<Fields> {
|
|
get<Key extends string & keyof Fields>(key: Key): Fields[Key]
|
|
entries(): IterableIterator<readonly [string, unknown]>
|
|
clone(): FixedMap<Fields>
|
|
has(key: string): boolean
|
|
}
|
|
|
|
export interface FixedMap<Fields> extends FixedMapView<Fields> {
|
|
set<Key extends string & keyof Fields>(key: Key, value: Fields[Key]): void
|
|
}
|
|
|
|
function getAll<Fields extends object>(map: FixedMapView<Fields>): Fields {
|
|
return Object.fromEntries(map.entries()) as Fields
|
|
}
|
|
|
|
/** Modifies the input `map`. Returns the same object with an extended type. */
|
|
export function setAll<Fields1, Fields2 extends object>(
|
|
map: FixedMap<Fields1>,
|
|
fields: Fields2,
|
|
): FixedMap<Fields1 & Fields2> {
|
|
const map_ = map as FixedMap<Fields1 & Fields2>
|
|
for (const [k, v] of Object.entries(fields)) {
|
|
const k_ = k as string & (keyof Fields1 | keyof Fields2)
|
|
map_.set(k_, v)
|
|
}
|
|
return map_
|
|
}
|
|
|
|
function claimChild<T extends MutableAst>(
|
|
module: MutableModule,
|
|
child: Owned<T>,
|
|
parent: AstId,
|
|
): AstId {
|
|
if (child.module === module) assertEqual(child.fields.get('parent'), undefined)
|
|
const child_ = module.copyIfForeign(child)
|
|
child_.fields.set('parent', parent)
|
|
return child_.id
|
|
}
|
|
|
|
function concreteChild(
|
|
module: MutableModule,
|
|
child: NodeChild<Owned>,
|
|
parent: AstId,
|
|
): NodeChild<AstId>
|
|
function concreteChild(
|
|
module: MutableModule,
|
|
child: NodeChild<Owned> | undefined,
|
|
parent: AstId,
|
|
): NodeChild<AstId> | undefined
|
|
function concreteChild(
|
|
module: MutableModule,
|
|
child: NodeChild<Owned | Token>,
|
|
parent: AstId,
|
|
): NodeChild<AstId | Token>
|
|
function concreteChild(
|
|
module: MutableModule,
|
|
child: NodeChild<Owned | Token> | undefined,
|
|
parent: AstId,
|
|
): NodeChild<AstId | Token> | undefined
|
|
function concreteChild(
|
|
module: MutableModule,
|
|
child: NodeChild<Owned | Token> | undefined,
|
|
parent: AstId,
|
|
): NodeChild<AstId | Token> | undefined {
|
|
if (!child) return undefined
|
|
if (isTokenId(child.node)) return child as NodeChild<Token>
|
|
return { ...child, node: claimChild(module, child.node, parent) }
|
|
}
|
|
|
|
type StrictIdentLike = Identifier | IdentifierToken
|
|
function toIdentStrict(ident: StrictIdentLike): IdentifierToken
|
|
function toIdentStrict(ident: StrictIdentLike | undefined): IdentifierToken | undefined
|
|
function toIdentStrict(ident: StrictIdentLike | undefined): IdentifierToken | undefined {
|
|
return ident
|
|
? isToken(ident)
|
|
? ident
|
|
: (Token.new(ident, RawAst.Token.Type.Ident) as IdentifierToken)
|
|
: undefined
|
|
}
|
|
|
|
type IdentLike = IdentifierOrOperatorIdentifier | IdentifierOrOperatorIdentifierToken
|
|
function toIdent(ident: IdentLike): IdentifierOrOperatorIdentifierToken
|
|
function toIdent(ident: IdentLike | undefined): IdentifierOrOperatorIdentifierToken | undefined
|
|
function toIdent(ident: IdentLike | undefined): IdentifierOrOperatorIdentifierToken | undefined {
|
|
return ident
|
|
? isToken(ident)
|
|
? ident
|
|
: (Token.new(ident, RawAst.Token.Type.Ident) as IdentifierOrOperatorIdentifierToken)
|
|
: undefined
|
|
}
|
|
|
|
function makeEquals(): Token {
|
|
return Token.new('=', RawAst.Token.Type.Operator)
|
|
}
|
|
|
|
function nameSpecification(
|
|
name: StrictIdentLike | undefined,
|
|
): { name: NodeChild<Token>; equals: NodeChild<Token> } | undefined {
|
|
return name && { name: autospaced(toIdentStrict(name)), equals: unspaced(makeEquals()) }
|
|
}
|
|
|
|
type KeysOfFieldType<Fields, T> = {
|
|
[K in keyof Fields]: Fields[K] extends T ? K : never
|
|
}[keyof Fields]
|
|
function setNode<Fields, Key extends string & KeysOfFieldType<Fields, NodeChild<AstId>>>(
|
|
map: FixedMap<Fields>,
|
|
key: Key,
|
|
node: AstId,
|
|
): void
|
|
function setNode<
|
|
Fields,
|
|
Key extends string & KeysOfFieldType<Fields, NodeChild<AstId> | undefined>,
|
|
>(map: FixedMap<Fields>, key: Key, node: AstId | undefined): void
|
|
function setNode<
|
|
Fields,
|
|
Key extends string & KeysOfFieldType<Fields, NodeChild<AstId> | undefined>,
|
|
>(map: FixedMap<Fields>, key: Key, node: AstId | undefined): void {
|
|
// The signature correctly only allows this function to be called if `Fields[Key] instanceof NodeChild<SyncId>`,
|
|
// but it doesn't prove that property to TSC, so we have to cast here.
|
|
const old = map.get(key as string & keyof Fields)
|
|
const updated = old ? { ...old, node } : autospaced(node)
|
|
map.set(key, updated as Fields[Key])
|
|
}
|
|
|
|
function spaced<T extends object | string>(node: T): NodeChild<T>
|
|
function spaced<T extends object | string>(node: T | undefined): NodeChild<T> | undefined
|
|
function spaced<T extends object | string>(node: T | undefined): NodeChild<T> | undefined {
|
|
if (node === undefined) return node
|
|
return { whitespace: ' ', node }
|
|
}
|
|
|
|
function unspaced<T extends object | string>(node: T): NodeChild<T>
|
|
function unspaced<T extends object | string>(node: T | undefined): NodeChild<T> | undefined
|
|
function unspaced<T extends object | string>(node: T | undefined): NodeChild<T> | undefined {
|
|
if (node === undefined) return node
|
|
return { whitespace: '', node }
|
|
}
|
|
|
|
function autospaced<T extends object | string>(node: T): NodeChild<T>
|
|
function autospaced<T extends object | string>(node: T | undefined): NodeChild<T> | undefined
|
|
function autospaced<T extends object | string>(node: T | undefined): NodeChild<T> | undefined {
|
|
if (node === undefined) return node
|
|
return { node }
|
|
}
|
|
|
|
export interface Removed<T extends MutableAst> {
|
|
node: Owned<T>
|
|
placeholder: MutableWildcard | undefined
|
|
}
|