mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 20:16:47 +03:00
e492d3f260
Fixes #8256 #8257 Implemented displaying node argument placeholders. Refactored widgets to allow arbitrary input types, and split widget matching into two phases, separating input type validation step and allowing it to influence the derived TypeScript type of widget component props. Connecting an edge to the placeholder is not implemented yet. Only connections to existing arguments are supported for now. https://github.com/enso-org/enso/assets/919491/ad3bf254-5f56-4f2f-8c0d-616a7195c6d8 Nested argument lists are also supported: <img width="396" alt="image" src="https://github.com/enso-org/enso/assets/919491/b61f6870-0f01-48c3-9b45-c7c6bc464638">
451 lines
14 KiB
TypeScript
451 lines
14 KiB
TypeScript
/** Generates code lazily deserializing from an application-specific binary format. */
|
|
|
|
import ts from 'typescript'
|
|
import { makeArrow } from './util'
|
|
|
|
const { factory: tsf } = ts
|
|
|
|
// === Definitions ===
|
|
|
|
const noneType = tsf.createTypeReferenceNode('undefined')
|
|
const viewFieldIdent = tsf.createIdentifier('_v')
|
|
const variantReadersIdent = tsf.createIdentifier('VARIANT_READERS')
|
|
const POINTER_SIZE: number = 4
|
|
// Symbols exported by the `parserSupport` module.
|
|
export const supportImports = {
|
|
LazyObject: false,
|
|
ObjectVisitor: true,
|
|
ObjectAddressVisitor: true,
|
|
Result: true,
|
|
readU8: false,
|
|
readU32: false,
|
|
readI32: false,
|
|
readU64: false,
|
|
readI64: false,
|
|
readBool: false,
|
|
readOffset: false,
|
|
readPointer: false,
|
|
readOption: false,
|
|
readResult: false,
|
|
readEnum: false,
|
|
readSequence: false,
|
|
readString: false,
|
|
visitSequence: false,
|
|
visitOption: false,
|
|
visitResult: false,
|
|
} as const
|
|
export const support = {
|
|
LazyObject: tsf.createIdentifier('LazyObject'),
|
|
ObjectVisitor: tsf.createTypeReferenceNode(tsf.createIdentifier('ObjectVisitor')),
|
|
ObjectAddressVisitor: tsf.createTypeReferenceNode(tsf.createIdentifier('ObjectAddressVisitor')),
|
|
DataView: tsf.createTypeReferenceNode(tsf.createIdentifier('DataView')),
|
|
Result: (t0: ts.TypeNode, t1: ts.TypeNode) =>
|
|
tsf.createTypeReferenceNode(tsf.createIdentifier('Result'), [t0, t1]),
|
|
readU8: tsf.createIdentifier('readU8'),
|
|
readU32: tsf.createIdentifier('readU32'),
|
|
readI32: tsf.createIdentifier('readI32'),
|
|
readU64: tsf.createIdentifier('readU64'),
|
|
readI64: tsf.createIdentifier('readI64'),
|
|
readBool: tsf.createIdentifier('readBool'),
|
|
readOffset: tsf.createIdentifier('readOffset'),
|
|
readPointer: tsf.createIdentifier('readPointer'),
|
|
readOption: tsf.createIdentifier('readOption'),
|
|
readResult: tsf.createIdentifier('readResult'),
|
|
readEnum: tsf.createIdentifier('readEnum'),
|
|
readSequence: tsf.createIdentifier('readSequence'),
|
|
readString: tsf.createIdentifier('readString'),
|
|
visitSequence: tsf.createIdentifier('visitSequence'),
|
|
visitOption: tsf.createIdentifier('visitOption'),
|
|
visitResult: tsf.createIdentifier('visitResult'),
|
|
} as const
|
|
|
|
const baseReaders = {
|
|
readString: primitiveReader(support.readString),
|
|
readBool: primitiveReader(support.readBool),
|
|
readU32: primitiveReader(support.readU32),
|
|
readI32: primitiveReader(support.readI32),
|
|
readU64: primitiveReader(support.readU64),
|
|
readI64: primitiveReader(support.readI64),
|
|
readPointer: primitiveReader(support.readPointer),
|
|
readOffset: primitiveReader(support.readOffset),
|
|
readOption: readerTransformer(support.readOption),
|
|
readResult: readerTransformerTwoTyped(support.readResult),
|
|
} as const
|
|
|
|
type ReadApplicator = (cursor: ts.Expression, offset: AccessOffset) => ts.Expression
|
|
type VisitorApplicator = (cursor: ts.Expression, offset: AccessOffset) => ts.Expression
|
|
|
|
// === Public API ===
|
|
|
|
export class Type {
|
|
readonly type: ts.TypeNode
|
|
readonly reader: ReadApplicator
|
|
readonly visitor: VisitorApplicator | undefined | 'visitValue'
|
|
readonly size: number
|
|
|
|
private constructor(
|
|
type: ts.TypeNode,
|
|
reader: ReadApplicator,
|
|
visitor: VisitorApplicator | undefined | 'visitValue',
|
|
size: number,
|
|
) {
|
|
this.type = type
|
|
this.reader = reader
|
|
this.visitor = visitor
|
|
this.size = size
|
|
}
|
|
|
|
static Abstract(name: string): Type {
|
|
const valueReader = callRead(name)
|
|
return new Type(tsf.createTypeReferenceNode(name), valueReader, 'visitValue', POINTER_SIZE)
|
|
}
|
|
|
|
static Concrete(name: string, size: number): Type {
|
|
const valueReader = callRead(name)
|
|
return new Type(tsf.createTypeReferenceNode(name), valueReader, 'visitValue', size)
|
|
}
|
|
|
|
static Sequence(element: Type): Type {
|
|
return new Type(
|
|
tsf.createTypeReferenceNode('IterableIterator', [element.type]),
|
|
createSequenceReader(element.size, element.reader),
|
|
createSequenceVisitor(element.size, visitorClosure(element.visitor, element.reader)),
|
|
POINTER_SIZE,
|
|
)
|
|
}
|
|
|
|
static Option(element: Type): Type {
|
|
return new Type(
|
|
tsf.createUnionTypeNode([element.type, noneType]),
|
|
baseReaders.readOption(element.reader),
|
|
createOptionVisitor(visitorClosure(element.visitor, element.reader)),
|
|
POINTER_SIZE + 1,
|
|
)
|
|
}
|
|
|
|
static Result(ok: Type, err: Type): Type {
|
|
return new Type(
|
|
support.Result(ok.type, err.type),
|
|
baseReaders.readResult(ok.reader, err.reader),
|
|
createResultVisitor(
|
|
visitorClosure(ok.visitor, ok.reader),
|
|
visitorClosure(err.visitor, err.reader),
|
|
),
|
|
POINTER_SIZE,
|
|
)
|
|
}
|
|
|
|
static Boolean: Type = new Type(
|
|
tsf.createTypeReferenceNode('boolean'),
|
|
baseReaders.readBool,
|
|
undefined,
|
|
1,
|
|
)
|
|
static UInt32: Type = new Type(
|
|
tsf.createTypeReferenceNode('number'),
|
|
baseReaders.readU32,
|
|
undefined,
|
|
4,
|
|
)
|
|
static Int32: Type = new Type(
|
|
tsf.createTypeReferenceNode('number'),
|
|
baseReaders.readI32,
|
|
undefined,
|
|
4,
|
|
)
|
|
static UInt64: Type = new Type(
|
|
tsf.createTypeReferenceNode('bigint'),
|
|
baseReaders.readU64,
|
|
undefined,
|
|
8,
|
|
)
|
|
static Int64: Type = new Type(
|
|
tsf.createTypeReferenceNode('bigint'),
|
|
baseReaders.readI64,
|
|
undefined,
|
|
8,
|
|
)
|
|
static Char: Type = new Type(
|
|
tsf.createTypeReferenceNode('number'),
|
|
baseReaders.readU32,
|
|
undefined,
|
|
4,
|
|
)
|
|
static String: Type = new Type(
|
|
tsf.createTypeReferenceNode('string'),
|
|
baseReaders.readString,
|
|
undefined,
|
|
POINTER_SIZE,
|
|
)
|
|
}
|
|
|
|
export function seekView(view: ts.Expression, address: number): ts.Expression {
|
|
if (address === 0) {
|
|
return view
|
|
} else {
|
|
return seekViewDyn(view, tsf.createNumericLiteral(address))
|
|
}
|
|
}
|
|
|
|
export function seekViewDyn(view: ts.Expression, address: ts.Expression): ts.Expression {
|
|
return tsf.createCallExpression(support.readOffset, [], [view, address])
|
|
}
|
|
|
|
export function abstractTypeVariants(cases: ts.Identifier[]): ts.Statement {
|
|
const reads = cases.map((c) => tsf.createPropertyAccessChain(c, undefined, 'read'))
|
|
return tsf.createVariableStatement(
|
|
[],
|
|
tsf.createVariableDeclarationList(
|
|
[
|
|
tsf.createVariableDeclaration(
|
|
variantReadersIdent,
|
|
undefined,
|
|
undefined,
|
|
tsf.createArrayLiteralExpression(reads),
|
|
),
|
|
],
|
|
ts.NodeFlags.Const,
|
|
),
|
|
)
|
|
}
|
|
|
|
export function abstractTypeDeserializer(
|
|
ident: ts.Identifier,
|
|
cursorIdent: ts.Identifier,
|
|
offsetIdent: ts.Identifier,
|
|
): ts.Expression {
|
|
return tsf.createCallExpression(
|
|
support.readEnum,
|
|
[tsf.createTypeReferenceNode(ident)],
|
|
[variantReadersIdent, cursorIdent, offsetIdent],
|
|
)
|
|
}
|
|
|
|
export function fieldDeserializer(
|
|
ident: ts.Identifier,
|
|
type: Type,
|
|
address: number,
|
|
): ts.GetAccessorDeclaration {
|
|
return tsf.createGetAccessorDeclaration(
|
|
[],
|
|
ident,
|
|
[],
|
|
type.type,
|
|
tsf.createBlock([
|
|
tsf.createReturnStatement(
|
|
type.reader(thisAccess(viewFieldIdent), makeConstantAddress(address)),
|
|
),
|
|
]),
|
|
)
|
|
}
|
|
|
|
export function fieldVisitor(
|
|
ident: ts.Identifier,
|
|
type: Type,
|
|
address: number,
|
|
): ts.ClassElement | undefined {
|
|
if (type.visitor == null || type.visitor === 'visitValue') return undefined
|
|
const value = type.visitor(thisAccess(viewFieldIdent), makeConstantAddress(address))
|
|
return tsf.createMethodDeclaration(
|
|
undefined,
|
|
undefined,
|
|
ident,
|
|
undefined,
|
|
undefined,
|
|
[
|
|
tsf.createParameterDeclaration(
|
|
undefined,
|
|
undefined,
|
|
'visitor',
|
|
undefined,
|
|
support.ObjectVisitor,
|
|
undefined,
|
|
),
|
|
],
|
|
tsf.createTypeReferenceNode('boolean'),
|
|
tsf.createBlock([tsf.createReturnStatement(value)]),
|
|
)
|
|
}
|
|
|
|
function thisAccess(ident: ts.Identifier): ts.PropertyAccessExpression {
|
|
return tsf.createPropertyAccessExpression(tsf.createThis(), ident)
|
|
}
|
|
|
|
// === Implementation ===
|
|
|
|
/** Returns a function that, given an expression evaluating to a [`Cursor`], returns an expression applying a
|
|
* deserialization method with the given name to the cursor. */
|
|
function primitiveReader(func: ts.Identifier): ReadApplicator {
|
|
return (view, address) => tsf.createCallExpression(func, [], [view, materializeAddress(address)])
|
|
}
|
|
|
|
/**
|
|
* Given the name of a runtime `Cursor` method that deserializes a derived type given a function to deserialize a
|
|
* base type, return a codegen-time function that generates a *reader* for a derived type from a *reader* for the base
|
|
* type, where a *reader* is a function producing a deserialization expression from an expression that evaluates to a
|
|
* `Cursor`.
|
|
*
|
|
* For example, if we have a reader produced by `primitiveReader('readU32')`, we can use it to create an expression
|
|
* representing the deserialization of a number from an expression that will evaluate to a location in the input. If we
|
|
* create a `readerTransformer('readOption')`, we can apply it to the number reader to yield an optional-number reader.
|
|
*/
|
|
function readerTransformer(func: ts.Identifier): (readElement: ReadApplicator) => ReadApplicator {
|
|
return (readElement) => (view, offset) => {
|
|
return tsf.createCallExpression(
|
|
func,
|
|
[],
|
|
[view, materializeAddress(offset), readerClosure(readElement)],
|
|
)
|
|
}
|
|
}
|
|
|
|
interface AccessOffset {
|
|
expression: ts.Expression | null
|
|
constant: number
|
|
}
|
|
|
|
function makeConstantAddress(constant: number): AccessOffset {
|
|
return { expression: null, constant }
|
|
}
|
|
|
|
function makeDynAddress(expression: ts.Expression, constant = 0): AccessOffset {
|
|
return { expression, constant }
|
|
}
|
|
|
|
function materializeAddress(offset: AccessOffset): ts.Expression {
|
|
if (offset.expression == null) {
|
|
return tsf.createNumericLiteral(offset.constant)
|
|
} else if (offset.constant == 0) {
|
|
return offset.expression
|
|
} else {
|
|
return tsf.createAdd(offset.expression, tsf.createNumericLiteral(offset.constant))
|
|
}
|
|
}
|
|
|
|
/** Similar to [`readerTransformer`], but for deserialization-transformers that produce a reader by combining two input
|
|
* readers. */
|
|
function readerTransformerTwoTyped(
|
|
func: ts.Identifier,
|
|
): (readOk: ReadApplicator, readErr: ReadApplicator) => ReadApplicator {
|
|
return (readOk: ReadApplicator, readErr: ReadApplicator) => (view, offset) => {
|
|
return tsf.createCallExpression(
|
|
func,
|
|
[],
|
|
[view, materializeAddress(offset), readerClosure(readOk), readerClosure(readErr)],
|
|
)
|
|
}
|
|
}
|
|
|
|
export function callRead(ident: string): ReadApplicator {
|
|
return (view, address) =>
|
|
tsf.createCallExpression(
|
|
tsf.createPropertyAccessExpression(tsf.createIdentifier(ident), 'read'),
|
|
[],
|
|
[view, materializeAddress(address)],
|
|
)
|
|
}
|
|
|
|
export function createSequenceReader(size: number, reader: ReadApplicator): ReadApplicator {
|
|
const sizeLiteral = tsf.createNumericLiteral(size)
|
|
const closure = readerClosure(reader)
|
|
return (view, address) =>
|
|
tsf.createCallExpression(
|
|
support.readSequence,
|
|
[],
|
|
[view, materializeAddress(address), sizeLiteral, closure],
|
|
)
|
|
}
|
|
|
|
function createSequenceVisitor(
|
|
size: number,
|
|
closure: ts.Expression | undefined,
|
|
): VisitorApplicator | undefined {
|
|
if (closure == null) return undefined
|
|
const sizeLiteral = tsf.createNumericLiteral(size)
|
|
return (view, address) =>
|
|
tsf.createCallExpression(
|
|
support.visitSequence,
|
|
[],
|
|
[thisAccess(viewFieldIdent), materializeAddress(address), sizeLiteral, closure],
|
|
)
|
|
}
|
|
|
|
function createOptionVisitor(closure: ts.Expression | undefined): VisitorApplicator | undefined {
|
|
if (closure == null) return undefined
|
|
return (view, address) =>
|
|
tsf.createCallExpression(support.visitOption, [], [view, materializeAddress(address), closure])
|
|
}
|
|
|
|
function createResultVisitor(
|
|
ok: ts.Expression | undefined,
|
|
err: ts.Expression | undefined,
|
|
): VisitorApplicator | undefined {
|
|
if (ok == null && err == null) return undefined
|
|
const none = tsf.createNull()
|
|
return (view, address) =>
|
|
tsf.createCallExpression(
|
|
support.visitResult,
|
|
[],
|
|
[view, materializeAddress(address), ok ?? none, err ?? none],
|
|
)
|
|
}
|
|
|
|
export function visitorClosure(
|
|
visitor: VisitorApplicator | 'visitValue' | undefined,
|
|
reader: ReadApplicator,
|
|
): ts.Expression | undefined {
|
|
if (visitor == null) return undefined
|
|
const view = tsf.createIdentifier('view')
|
|
const address = tsf.createIdentifier('address')
|
|
const addressValue = makeDynAddress(address)
|
|
if (visitor === 'visitValue') {
|
|
const read = reader(view, addressValue)
|
|
const readAndVisit = tsf.createCallExpression(tsf.createIdentifier('visitor'), [], [read])
|
|
return makeArrow([view, address], readAndVisit)
|
|
} else {
|
|
return makeArrow([view, address], visitor(view, addressValue))
|
|
}
|
|
}
|
|
|
|
export function readerClosure(reader: ReadApplicator): ts.Expression {
|
|
const view = tsf.createIdentifier('view')
|
|
const address = tsf.createIdentifier('address')
|
|
const read = reader(view, makeDynAddress(address))
|
|
if (isSimpleRead(read)) {
|
|
return read.expression
|
|
} else {
|
|
return makeArrow([view, address], read)
|
|
}
|
|
}
|
|
|
|
function isSimpleRead(reader: ts.Expression): reader is ts.CallExpression {
|
|
return (
|
|
ts.isCallExpression(reader) &&
|
|
ts.isPropertyAccessExpression(reader.expression) &&
|
|
reader.expression.name.text === 'read' &&
|
|
reader.arguments.length === 2
|
|
)
|
|
}
|
|
|
|
function _dbg<T extends ts.Node | undefined>(node: T): T {
|
|
if (node == null) {
|
|
console.log(node)
|
|
return node
|
|
}
|
|
const printer = ts.createPrinter({
|
|
newLine: ts.NewLineKind.LineFeed,
|
|
omitTrailingSemicolon: true,
|
|
})
|
|
|
|
console.log(
|
|
ts.SyntaxKind[node.kind],
|
|
':',
|
|
printer.printNode(
|
|
ts.EmitHint.Unspecified,
|
|
node,
|
|
ts.createSourceFile('dbg.ts', '', ts.ScriptTarget.Latest, false, ts.ScriptKind.TS),
|
|
),
|
|
)
|
|
return node
|
|
}
|