mirror of
https://github.com/enso-org/enso.git
synced 2024-11-26 08:52:58 +03:00
[GUI2] use DataView directly in generated parser code (#8028)
- Removed `Cursor` and moved all accessors to standalone functions, reducing the generated code size significantly (especially after minification). - Removed unnecessary `seek`s from everywhere. Now the offseting is part of each `read` implementation. - Removed unnecessary differences between `read`s on abstract and concrete types. Now abstract types do the pointer access internally in their `read`.
This commit is contained in:
parent
34f34cf0ad
commit
bf76be6e6b
@ -12,9 +12,10 @@ import * as Schema from './schema.js'
|
||||
import {
|
||||
Type,
|
||||
abstractTypeDeserializer,
|
||||
abstractTypeVariants,
|
||||
fieldDeserializer,
|
||||
fieldDynValue,
|
||||
seekCursor,
|
||||
seekViewDyn,
|
||||
support,
|
||||
supportImports,
|
||||
} from './serialization.js'
|
||||
@ -29,11 +30,17 @@ import {
|
||||
} from './util.js'
|
||||
const tsf = ts.factory
|
||||
|
||||
const addressIdent = tsf.createIdentifier('address')
|
||||
const viewIdent = tsf.createIdentifier('view')
|
||||
|
||||
// === Public API ===
|
||||
|
||||
export function implement(schema: Schema.Schema): string {
|
||||
const file = ts.createSourceFile('source.ts', '', ts.ScriptTarget.ESNext, false, ts.ScriptKind.TS)
|
||||
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed })
|
||||
const printer = ts.createPrinter({
|
||||
newLine: ts.NewLineKind.LineFeed,
|
||||
omitTrailingSemicolon: true,
|
||||
})
|
||||
let output = '// *** THIS FILE GENERATED BY `parser-codegen` ***\n'
|
||||
|
||||
function emit(data: ts.Node) {
|
||||
@ -72,10 +79,6 @@ export function implement(schema: Schema.Schema): string {
|
||||
// Ignore child types; they are generated when `makeAbstractType` processes the parent.
|
||||
}
|
||||
}
|
||||
output += `export function deserializeTree(data: ArrayBuffer): Tree {
|
||||
const cursor = new Cursor(data, data.byteLength - 4)
|
||||
return Tree.read(cursor.readPointer())
|
||||
}`
|
||||
return output
|
||||
}
|
||||
|
||||
@ -158,31 +161,16 @@ function makeGetter(field: Field): ts.GetAccessorDeclaration {
|
||||
|
||||
function makeConcreteType(id: string, schema: Schema.Schema): ts.ClassDeclaration {
|
||||
const ident = tsf.createIdentifier(toPascal(schema.types[id]!.name))
|
||||
const paramIdent = tsf.createIdentifier('cursor')
|
||||
const cursorParam = tsf.createParameterDeclaration(
|
||||
[],
|
||||
undefined,
|
||||
paramIdent,
|
||||
undefined,
|
||||
support.Cursor,
|
||||
undefined,
|
||||
)
|
||||
return makeClass(
|
||||
[modifiers.export],
|
||||
ident,
|
||||
[
|
||||
forwardToSuper(paramIdent, support.Cursor),
|
||||
tsf.createMethodDeclaration(
|
||||
[modifiers.static],
|
||||
undefined,
|
||||
'read',
|
||||
undefined,
|
||||
[],
|
||||
[cursorParam],
|
||||
tsf.createTypeReferenceNode(ident),
|
||||
tsf.createBlock([
|
||||
tsf.createReturnStatement(tsf.createNewExpression(ident, [], [paramIdent])),
|
||||
]),
|
||||
forwardToSuper(viewIdent, support.DataView),
|
||||
makeReadMethod(
|
||||
ident,
|
||||
addressIdent,
|
||||
viewIdent,
|
||||
tsf.createNewExpression(ident, [], [seekViewDyn(viewIdent, addressIdent)]),
|
||||
),
|
||||
],
|
||||
id,
|
||||
@ -190,6 +178,73 @@ function makeConcreteType(id: string, schema: Schema.Schema): ts.ClassDeclaratio
|
||||
)
|
||||
}
|
||||
|
||||
function makeReadMethod(
|
||||
typeIdent: ts.Identifier,
|
||||
addressIdent: ts.Identifier,
|
||||
viewIdent: ts.Identifier,
|
||||
returnValue: ts.Expression,
|
||||
): ts.MethodDeclaration {
|
||||
const offsetParam = tsf.createParameterDeclaration(
|
||||
[],
|
||||
undefined,
|
||||
addressIdent,
|
||||
undefined,
|
||||
tsf.createTypeReferenceNode('number'),
|
||||
undefined,
|
||||
)
|
||||
const cursorParam = tsf.createParameterDeclaration(
|
||||
[],
|
||||
undefined,
|
||||
viewIdent,
|
||||
undefined,
|
||||
support.DataView,
|
||||
undefined,
|
||||
)
|
||||
return tsf.createMethodDeclaration(
|
||||
[modifiers.static],
|
||||
undefined,
|
||||
'read',
|
||||
undefined,
|
||||
[],
|
||||
[cursorParam, offsetParam],
|
||||
tsf.createTypeReferenceNode(typeIdent),
|
||||
tsf.createBlock([tsf.createReturnStatement(returnValue)]),
|
||||
)
|
||||
}
|
||||
|
||||
function makeReadFunction(
|
||||
typeIdent: ts.Identifier,
|
||||
addressIdent: ts.Identifier,
|
||||
viewIdent: ts.Identifier,
|
||||
returnValue: ts.Expression,
|
||||
): ts.FunctionDeclaration {
|
||||
const offsetParam = tsf.createParameterDeclaration(
|
||||
[],
|
||||
undefined,
|
||||
addressIdent,
|
||||
undefined,
|
||||
tsf.createTypeReferenceNode('number'),
|
||||
undefined,
|
||||
)
|
||||
const cursorParam = tsf.createParameterDeclaration(
|
||||
[],
|
||||
undefined,
|
||||
viewIdent,
|
||||
undefined,
|
||||
support.DataView,
|
||||
undefined,
|
||||
)
|
||||
return tsf.createFunctionDeclaration(
|
||||
[modifiers.export],
|
||||
undefined,
|
||||
'read',
|
||||
[],
|
||||
[cursorParam, offsetParam],
|
||||
tsf.createTypeReferenceNode(typeIdent),
|
||||
tsf.createBlock([tsf.createReturnStatement(returnValue)]),
|
||||
)
|
||||
}
|
||||
|
||||
function makeDebugFunction(fields: Field[], typeName?: string): ts.MethodDeclaration {
|
||||
const ident = tsf.createIdentifier('fields')
|
||||
const fieldAssignments = fields.map((field) =>
|
||||
@ -219,16 +274,19 @@ function makeDebugFunction(fields: Field[], typeName?: string): ts.MethodDeclara
|
||||
tsf.createTypeReferenceNode(`[string, ${support.DynValue}][]`),
|
||||
tsf.createBlock([
|
||||
tsf.createReturnStatement(
|
||||
tsf.createArrayLiteralExpression([
|
||||
tsf.createSpreadElement(
|
||||
tsf.createCallExpression(
|
||||
tsf.createPropertyAccessExpression(tsf.createSuper(), ident),
|
||||
undefined,
|
||||
undefined,
|
||||
tsf.createArrayLiteralExpression(
|
||||
[
|
||||
tsf.createSpreadElement(
|
||||
tsf.createCallExpression(
|
||||
tsf.createPropertyAccessExpression(tsf.createSuper(), ident),
|
||||
undefined,
|
||||
undefined,
|
||||
),
|
||||
),
|
||||
),
|
||||
...fieldAssignments,
|
||||
]),
|
||||
...fieldAssignments,
|
||||
],
|
||||
true,
|
||||
),
|
||||
),
|
||||
]),
|
||||
)
|
||||
@ -268,7 +326,7 @@ function makeClass(
|
||||
|
||||
type ChildType = {
|
||||
definition: ts.ClassDeclaration
|
||||
reference: ts.TypeNode
|
||||
name: ts.Identifier
|
||||
enumMember: ts.EnumMember
|
||||
case: ts.CaseClause
|
||||
}
|
||||
@ -283,15 +341,9 @@ function makeChildType(
|
||||
if (ty == null) throw new Error(`Invalid type id: ${id}`)
|
||||
const name = toPascal(ty.name)
|
||||
const ident = tsf.createIdentifier(name)
|
||||
const cursorIdent = tsf.createIdentifier('cursor')
|
||||
const cursorParam = tsf.createParameterDeclaration(
|
||||
[],
|
||||
undefined,
|
||||
cursorIdent,
|
||||
undefined,
|
||||
support.Cursor,
|
||||
undefined,
|
||||
)
|
||||
const typeIdent = tsf.createIdentifier('Type')
|
||||
const addressIdent = tsf.createIdentifier('address')
|
||||
const viewIdent = tsf.createIdentifier('view')
|
||||
const discriminantInt = tsf.createNumericLiteral(parseInt(discriminant, 10))
|
||||
return {
|
||||
definition: tsf.createClassDeclaration(
|
||||
@ -308,7 +360,7 @@ function makeChildType(
|
||||
[modifiers.readonly],
|
||||
'type',
|
||||
undefined,
|
||||
tsf.createTypeReferenceNode('Type.' + name),
|
||||
tsf.createTypeReferenceNode(tsf.createQualifiedName(typeIdent, name)),
|
||||
undefined,
|
||||
),
|
||||
tsf.createConstructorDeclaration(
|
||||
@ -317,42 +369,34 @@ function makeChildType(
|
||||
tsf.createParameterDeclaration(
|
||||
[],
|
||||
undefined,
|
||||
cursorIdent,
|
||||
viewIdent,
|
||||
undefined,
|
||||
support.Cursor,
|
||||
support.DataView,
|
||||
undefined,
|
||||
),
|
||||
],
|
||||
tsf.createBlock([
|
||||
tsf.createExpressionStatement(
|
||||
tsf.createCallExpression(tsf.createIdentifier('super'), [], [cursorIdent]),
|
||||
tsf.createCallExpression(tsf.createSuper(), [], [viewIdent]),
|
||||
),
|
||||
assignmentStatement(
|
||||
tsf.createPropertyAccessExpression(tsf.createIdentifier('this'), 'type'),
|
||||
tsf.createPropertyAccessExpression(tsf.createIdentifier('Type'), name),
|
||||
tsf.createPropertyAccessExpression(tsf.createThis(), 'type'),
|
||||
tsf.createPropertyAccessExpression(typeIdent, name),
|
||||
),
|
||||
]),
|
||||
),
|
||||
tsf.createMethodDeclaration(
|
||||
[modifiers.static],
|
||||
undefined,
|
||||
'read',
|
||||
undefined,
|
||||
[],
|
||||
[cursorParam],
|
||||
tsf.createTypeReferenceNode(ident),
|
||||
tsf.createBlock([
|
||||
tsf.createReturnStatement(tsf.createNewExpression(ident, [], [cursorIdent])),
|
||||
]),
|
||||
makeReadMethod(
|
||||
ident,
|
||||
addressIdent,
|
||||
viewIdent,
|
||||
tsf.createNewExpression(ident, [], [seekViewDyn(viewIdent, addressIdent)]),
|
||||
),
|
||||
...makeGetters(id, schema, name),
|
||||
],
|
||||
),
|
||||
reference: tsf.createTypeReferenceNode(name),
|
||||
name: tsf.createIdentifier(name),
|
||||
enumMember: tsf.createEnumMember(toPascal(ty.name), discriminantInt),
|
||||
case: tsf.createCaseClause(discriminantInt, [
|
||||
tsf.createReturnStatement(tsf.createNewExpression(ident, [], [seekCursor(cursorIdent, 4)])),
|
||||
]),
|
||||
case: tsf.createCaseClause(discriminantInt, [tsf.createReturnStatement(viewIdent)]),
|
||||
}
|
||||
}
|
||||
|
||||
@ -373,7 +417,7 @@ function makeAbstractType(
|
||||
const childTypes = Array.from(Object.entries(discriminants), ([discrim, id]: [string, string]) =>
|
||||
makeChildType(baseIdent, id, discrim, schema),
|
||||
)
|
||||
const cursorIdent = tsf.createIdentifier('cursor')
|
||||
|
||||
const moduleDecl = tsf.createModuleDeclaration(
|
||||
[modifiers.export],
|
||||
ident,
|
||||
@ -381,7 +425,7 @@ function makeAbstractType(
|
||||
makeClass(
|
||||
[modifiers.abstract],
|
||||
baseIdent,
|
||||
[forwardToSuper(cursorIdent, support.Cursor, [modifiers.protected])],
|
||||
[forwardToSuper(viewIdent, support.DataView, [modifiers.protected])],
|
||||
id,
|
||||
schema,
|
||||
),
|
||||
@ -395,11 +439,14 @@ function makeAbstractType(
|
||||
[modifiers.export],
|
||||
ident,
|
||||
undefined,
|
||||
tsf.createUnionTypeNode(childTypes.map((child) => child.reference)),
|
||||
tsf.createUnionTypeNode(childTypes.map((child) => tsf.createTypeReferenceNode(child.name))),
|
||||
),
|
||||
abstractTypeDeserializer(
|
||||
abstractTypeVariants(childTypes.map((child) => child.name)),
|
||||
makeReadFunction(
|
||||
ident,
|
||||
childTypes.map((child) => child.case),
|
||||
addressIdent,
|
||||
viewIdent,
|
||||
abstractTypeDeserializer(ident, viewIdent, addressIdent),
|
||||
),
|
||||
]),
|
||||
)
|
||||
@ -407,7 +454,7 @@ function makeAbstractType(
|
||||
[modifiers.export],
|
||||
ident,
|
||||
undefined,
|
||||
tsf.createTypeReferenceNode(name + '.' + name),
|
||||
tsf.createTypeReferenceNode(tsf.createQualifiedName(tsf.createIdentifier(name), name)),
|
||||
)
|
||||
return { module: moduleDecl, export: abstractTypeExport }
|
||||
}
|
||||
|
@ -1,43 +1,69 @@
|
||||
/** Generates code lazily deserializing from an application-specific binary format. */
|
||||
|
||||
import ts from 'typescript'
|
||||
import { casesOrThrow, modifiers } from './util.js'
|
||||
import { makeArrow } from './util'
|
||||
|
||||
const { factory: tsf } = ts
|
||||
|
||||
// === Definitions ===
|
||||
|
||||
const noneType = tsf.createTypeReferenceNode('undefined')
|
||||
const cursorFieldIdent = tsf.createIdentifier('lazyObjectData')
|
||||
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,
|
||||
Cursor: false,
|
||||
Result: true,
|
||||
DynValue: true,
|
||||
Dyn: false,
|
||||
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,
|
||||
} as const
|
||||
export const support = {
|
||||
LazyObject: tsf.createIdentifier('LazyObject'),
|
||||
Cursor: tsf.createTypeReferenceNode(tsf.createIdentifier('Cursor')),
|
||||
DataView: tsf.createTypeReferenceNode(tsf.createIdentifier('DataView')),
|
||||
Result: (t0: ts.TypeNode, t1: ts.TypeNode) =>
|
||||
tsf.createTypeReferenceNode(tsf.createIdentifier('Result'), [t0, t1]),
|
||||
DynValue: 'DynValue',
|
||||
Dyn: tsf.createIdentifier('Dyn'),
|
||||
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'),
|
||||
} as const
|
||||
|
||||
const cursorMethods = {
|
||||
readString: primitiveReader('readString'),
|
||||
readBool: primitiveReader('readBool'),
|
||||
readU32: primitiveReader('readU32'),
|
||||
readI32: primitiveReader('readI32'),
|
||||
readU64: primitiveReader('readU64'),
|
||||
readI64: primitiveReader('readI64'),
|
||||
readPointer: primitiveReader('readPointer'),
|
||||
readSequence: readerTransformerSized('readSequence'),
|
||||
readOption: readerTransformer('readOption'),
|
||||
readResult: readerTransformerTwoTyped('readResult'),
|
||||
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
|
||||
const dynBuilders = {
|
||||
Primitive: dynReader('Primitive'),
|
||||
@ -47,20 +73,20 @@ const dynBuilders = {
|
||||
Object: dynReader('Object'),
|
||||
} as const
|
||||
|
||||
type ExpressionTransformer = (expression: ts.Expression) => ts.Expression
|
||||
type ReadApplicator = (cursor: ts.Expression, offset: AccessOffset) => ts.Expression
|
||||
|
||||
// === Public API ===
|
||||
|
||||
export class Type {
|
||||
readonly type: ts.TypeNode
|
||||
readonly reader: ExpressionTransformer
|
||||
readonly dynReader: ExpressionTransformer
|
||||
readonly reader: ReadApplicator
|
||||
readonly dynReader: ReadApplicator
|
||||
readonly size: number
|
||||
|
||||
private constructor(
|
||||
type: ts.TypeNode,
|
||||
reader: ExpressionTransformer,
|
||||
dynReader: ExpressionTransformer,
|
||||
reader: ReadApplicator,
|
||||
dynReader: ReadApplicator,
|
||||
size: number,
|
||||
) {
|
||||
this.type = type
|
||||
@ -70,7 +96,7 @@ export class Type {
|
||||
}
|
||||
|
||||
static Abstract(name: string): Type {
|
||||
const valueReader = abstractTypeReader(name)
|
||||
const valueReader = callRead(name)
|
||||
return new Type(
|
||||
tsf.createTypeReferenceNode(name),
|
||||
valueReader,
|
||||
@ -80,7 +106,7 @@ export class Type {
|
||||
}
|
||||
|
||||
static Concrete(name: string, size: number): Type {
|
||||
const valueReader = concreteTypeReader(name)
|
||||
const valueReader = callRead(name)
|
||||
return new Type(
|
||||
tsf.createTypeReferenceNode(name),
|
||||
valueReader,
|
||||
@ -92,8 +118,8 @@ export class Type {
|
||||
static Sequence(element: Type): Type {
|
||||
return new Type(
|
||||
tsf.createTypeReferenceNode('Iterable', [element.type]),
|
||||
cursorMethods.readSequence(element.reader, element.size),
|
||||
dynBuilders.Sequence(cursorMethods.readSequence(element.dynReader, element.size)),
|
||||
createSequenceReader(element.size, element.reader),
|
||||
dynBuilders.Sequence(createSequenceReader(element.size, element.dynReader)),
|
||||
POINTER_SIZE,
|
||||
)
|
||||
}
|
||||
@ -101,8 +127,8 @@ export class Type {
|
||||
static Option(element: Type): Type {
|
||||
return new Type(
|
||||
tsf.createUnionTypeNode([element.type, noneType]),
|
||||
cursorMethods.readOption(element.reader),
|
||||
dynBuilders.Option(cursorMethods.readOption(element.dynReader)),
|
||||
baseReaders.readOption(element.reader),
|
||||
dynBuilders.Option(baseReaders.readOption(element.dynReader)),
|
||||
POINTER_SIZE + 1,
|
||||
)
|
||||
}
|
||||
@ -110,102 +136,102 @@ export class Type {
|
||||
static Result(ok: Type, err: Type): Type {
|
||||
return new Type(
|
||||
support.Result(ok.type, err.type),
|
||||
cursorMethods.readResult(ok.reader, err.reader),
|
||||
dynBuilders.Result(cursorMethods.readResult(ok.dynReader, err.dynReader)),
|
||||
baseReaders.readResult(ok.reader, err.reader),
|
||||
dynBuilders.Result(baseReaders.readResult(ok.dynReader, err.dynReader)),
|
||||
POINTER_SIZE,
|
||||
)
|
||||
}
|
||||
|
||||
static Boolean: Type = new Type(
|
||||
tsf.createTypeReferenceNode('boolean'),
|
||||
cursorMethods.readBool,
|
||||
dynBuilders.Primitive(cursorMethods.readBool),
|
||||
baseReaders.readBool,
|
||||
dynBuilders.Primitive(baseReaders.readBool),
|
||||
1,
|
||||
)
|
||||
static UInt32: Type = new Type(
|
||||
tsf.createTypeReferenceNode('number'),
|
||||
cursorMethods.readU32,
|
||||
dynBuilders.Primitive(cursorMethods.readU32),
|
||||
baseReaders.readU32,
|
||||
dynBuilders.Primitive(baseReaders.readU32),
|
||||
4,
|
||||
)
|
||||
static Int32: Type = new Type(
|
||||
tsf.createTypeReferenceNode('number'),
|
||||
cursorMethods.readI32,
|
||||
dynBuilders.Primitive(cursorMethods.readI32),
|
||||
baseReaders.readI32,
|
||||
dynBuilders.Primitive(baseReaders.readI32),
|
||||
4,
|
||||
)
|
||||
static UInt64: Type = new Type(
|
||||
tsf.createTypeReferenceNode('bigint'),
|
||||
cursorMethods.readU64,
|
||||
dynBuilders.Primitive(cursorMethods.readU64),
|
||||
baseReaders.readU64,
|
||||
dynBuilders.Primitive(baseReaders.readU64),
|
||||
8,
|
||||
)
|
||||
static Int64: Type = new Type(
|
||||
tsf.createTypeReferenceNode('bigint'),
|
||||
cursorMethods.readI64,
|
||||
dynBuilders.Primitive(cursorMethods.readI64),
|
||||
baseReaders.readI64,
|
||||
dynBuilders.Primitive(baseReaders.readI64),
|
||||
8,
|
||||
)
|
||||
static Char: Type = new Type(
|
||||
tsf.createTypeReferenceNode('number'),
|
||||
cursorMethods.readU32,
|
||||
dynBuilders.Primitive(cursorMethods.readU32),
|
||||
baseReaders.readU32,
|
||||
dynBuilders.Primitive(baseReaders.readU32),
|
||||
4,
|
||||
)
|
||||
static String: Type = new Type(
|
||||
tsf.createTypeReferenceNode('string'),
|
||||
cursorMethods.readString,
|
||||
dynBuilders.Primitive(cursorMethods.readString),
|
||||
baseReaders.readString,
|
||||
dynBuilders.Primitive(baseReaders.readString),
|
||||
POINTER_SIZE,
|
||||
)
|
||||
}
|
||||
|
||||
export function seekCursor(cursor: ts.Expression, offset: number): ts.Expression {
|
||||
if (offset === 0) {
|
||||
return cursor
|
||||
export function seekView(view: ts.Expression, address: number): ts.Expression {
|
||||
if (address === 0) {
|
||||
return view
|
||||
} else {
|
||||
return tsf.createCallExpression(
|
||||
tsf.createPropertyAccessExpression(cursor, 'seek'),
|
||||
[],
|
||||
[tsf.createNumericLiteral(offset)],
|
||||
)
|
||||
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,
|
||||
cases: ts.CaseClause[],
|
||||
): ts.FunctionDeclaration {
|
||||
const cursorIdent = tsf.createIdentifier('cursor')
|
||||
return tsf.createFunctionDeclaration(
|
||||
[modifiers.export],
|
||||
undefined,
|
||||
'read',
|
||||
[],
|
||||
[
|
||||
tsf.createParameterDeclaration(
|
||||
[],
|
||||
undefined,
|
||||
cursorIdent,
|
||||
undefined,
|
||||
support.Cursor,
|
||||
undefined,
|
||||
),
|
||||
],
|
||||
tsf.createTypeReferenceNode(ident),
|
||||
tsf.createBlock([
|
||||
tsf.createSwitchStatement(
|
||||
cursorMethods.readU32(cursorIdent),
|
||||
casesOrThrow(cases, 'Unexpected discriminant while deserializing.'),
|
||||
),
|
||||
]),
|
||||
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,
|
||||
offset: number,
|
||||
address: number,
|
||||
): ts.GetAccessorDeclaration {
|
||||
return tsf.createGetAccessorDeclaration(
|
||||
[],
|
||||
@ -214,30 +240,26 @@ export function fieldDeserializer(
|
||||
type.type,
|
||||
tsf.createBlock([
|
||||
tsf.createReturnStatement(
|
||||
type.reader(
|
||||
seekCursor(
|
||||
tsf.createPropertyAccessExpression(tsf.createThis(), cursorFieldIdent),
|
||||
offset,
|
||||
),
|
||||
),
|
||||
type.reader(thisAccess(viewFieldIdent), makeConstantAddress(address)),
|
||||
),
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
export function fieldDynValue(type: Type, offset: number): ts.Expression {
|
||||
return type.dynReader(
|
||||
seekCursor(tsf.createPropertyAccessExpression(tsf.createThis(), cursorFieldIdent), offset),
|
||||
)
|
||||
export function fieldDynValue(type: Type, address: number): ts.Expression {
|
||||
return type.dynReader(thisAccess(viewFieldIdent), makeConstantAddress(address))
|
||||
}
|
||||
|
||||
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(name: string): ExpressionTransformer {
|
||||
return (cursor) =>
|
||||
tsf.createCallExpression(tsf.createPropertyAccessExpression(cursor, name), [], [])
|
||||
function primitiveReader(func: ts.Identifier): ReadApplicator {
|
||||
return (view, address) => tsf.createCallExpression(func, [], [view, materializeAddress(address)])
|
||||
}
|
||||
|
||||
/**
|
||||
@ -250,123 +272,121 @@ function primitiveReader(name: string): ExpressionTransformer {
|
||||
* 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(
|
||||
name: string,
|
||||
): (readElement: ExpressionTransformer) => ExpressionTransformer {
|
||||
const innerParameter = tsf.createIdentifier('element')
|
||||
return (readElement: ExpressionTransformer) => (cursor: ts.Expression) => {
|
||||
function readerTransformer(func: ts.Identifier): (readElement: ReadApplicator) => ReadApplicator {
|
||||
return (readElement) => (view, offset) => {
|
||||
return tsf.createCallExpression(
|
||||
tsf.createPropertyAccessExpression(cursor, name),
|
||||
func,
|
||||
[],
|
||||
[
|
||||
tsf.createArrowFunction(
|
||||
[],
|
||||
[],
|
||||
[
|
||||
tsf.createParameterDeclaration(
|
||||
[],
|
||||
undefined,
|
||||
innerParameter,
|
||||
undefined,
|
||||
support.Cursor,
|
||||
undefined,
|
||||
),
|
||||
],
|
||||
undefined,
|
||||
undefined,
|
||||
readElement(innerParameter),
|
||||
),
|
||||
],
|
||||
[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(
|
||||
name: string,
|
||||
): (readOk: ExpressionTransformer, readErr: ExpressionTransformer) => ExpressionTransformer {
|
||||
function makeArrow(reader: ExpressionTransformer, data: ts.Identifier) {
|
||||
return tsf.createArrowFunction(
|
||||
[],
|
||||
[],
|
||||
[tsf.createParameterDeclaration([], undefined, data, undefined, support.Cursor, undefined)],
|
||||
undefined,
|
||||
undefined,
|
||||
reader(data),
|
||||
)
|
||||
}
|
||||
|
||||
const okData = tsf.createIdentifier('okData')
|
||||
const errData = tsf.createIdentifier('errData')
|
||||
return (readOk: ExpressionTransformer, readErr: ExpressionTransformer) =>
|
||||
(cursor: ts.Expression) => {
|
||||
return tsf.createCallExpression(
|
||||
tsf.createPropertyAccessExpression(cursor, name),
|
||||
[],
|
||||
[makeArrow(readOk, okData), makeArrow(readErr, errData)],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Similar to [`readerTransformer`], but for deserialization-transformers are parameterized by the size of their
|
||||
* element. */
|
||||
function readerTransformerSized(
|
||||
name: string,
|
||||
): (readElement: ExpressionTransformer, size: number) => ExpressionTransformer {
|
||||
const innerParameter = tsf.createIdentifier('element')
|
||||
return (readElement: ExpressionTransformer, size: number) => (cursor: ts.Expression) => {
|
||||
func: ts.Identifier,
|
||||
): (readOk: ReadApplicator, readErr: ReadApplicator) => ReadApplicator {
|
||||
return (readOk: ReadApplicator, readErr: ReadApplicator) => (view, offset) => {
|
||||
return tsf.createCallExpression(
|
||||
tsf.createPropertyAccessExpression(cursor, name),
|
||||
func,
|
||||
[],
|
||||
[
|
||||
tsf.createArrowFunction(
|
||||
[],
|
||||
[],
|
||||
[
|
||||
tsf.createParameterDeclaration(
|
||||
[],
|
||||
undefined,
|
||||
innerParameter,
|
||||
undefined,
|
||||
support.Cursor,
|
||||
undefined,
|
||||
),
|
||||
],
|
||||
undefined,
|
||||
undefined,
|
||||
readElement(innerParameter),
|
||||
),
|
||||
tsf.createNumericLiteral(size),
|
||||
],
|
||||
[view, materializeAddress(offset), readerClosure(readOk), readerClosure(readErr)],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function dynReader(name: string): (readValue: ExpressionTransformer) => ExpressionTransformer {
|
||||
return (readValue: ExpressionTransformer) => (cursor: ts.Expression) => {
|
||||
function dynReader(name: string): (readValue: ReadApplicator) => ReadApplicator {
|
||||
return (readValue) => (view, address) => {
|
||||
return tsf.createCallExpression(
|
||||
tsf.createPropertyAccessExpression(support.Dyn, name),
|
||||
[],
|
||||
[readValue(cursor)],
|
||||
[readValue(view, address)],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function abstractTypeReader(name: string): ExpressionTransformer {
|
||||
return (cursor: ts.Expression) =>
|
||||
export function callRead(ident: string): ReadApplicator {
|
||||
return (view, address) =>
|
||||
tsf.createCallExpression(
|
||||
tsf.createPropertyAccessExpression(tsf.createIdentifier(name), 'read'),
|
||||
tsf.createPropertyAccessExpression(tsf.createIdentifier(ident), 'read'),
|
||||
[],
|
||||
[cursorMethods.readPointer(cursor)],
|
||||
[view, materializeAddress(address)],
|
||||
)
|
||||
}
|
||||
|
||||
function concreteTypeReader(name: string): ExpressionTransformer {
|
||||
return (cursor: ts.Expression) =>
|
||||
export function createSequenceReader(size: number, reader: ReadApplicator): ReadApplicator {
|
||||
const sizeLiteral = tsf.createNumericLiteral(size)
|
||||
const closure = readerClosure(reader)
|
||||
return (view, address) =>
|
||||
tsf.createCallExpression(
|
||||
tsf.createPropertyAccessExpression(tsf.createIdentifier(name), 'read'),
|
||||
support.readSequence,
|
||||
[],
|
||||
[cursor],
|
||||
[view, materializeAddress(address), sizeLiteral, closure],
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -76,16 +76,22 @@ export function forwardToSuper(
|
||||
}
|
||||
|
||||
export function casesOrThrow(cases: ts.CaseClause[], error: string): ts.CaseBlock {
|
||||
return tsf.createCaseBlock([
|
||||
...cases,
|
||||
tsf.createDefaultClause([
|
||||
tsf.createThrowStatement(
|
||||
tsf.createNewExpression(
|
||||
tsf.createIdentifier('Error'),
|
||||
[],
|
||||
[tsf.createStringLiteral(error)],
|
||||
),
|
||||
),
|
||||
]),
|
||||
])
|
||||
return tsf.createCaseBlock([...cases, tsf.createDefaultClause([throwError(error)])])
|
||||
}
|
||||
|
||||
export function throwError(error: string): ts.Statement {
|
||||
return tsf.createThrowStatement(
|
||||
tsf.createNewExpression(tsf.createIdentifier('Error'), [], [tsf.createStringLiteral(error)]),
|
||||
)
|
||||
}
|
||||
|
||||
export function makeArrow(params: ts.BindingName[], expr: ts.Expression) {
|
||||
return tsf.createArrowFunction(
|
||||
[],
|
||||
[],
|
||||
params.map((ident) => tsf.createParameterDeclaration([], undefined, ident)),
|
||||
undefined,
|
||||
undefined,
|
||||
expr,
|
||||
)
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ export { Ast }
|
||||
|
||||
export function parseEnso(code: string): Ast.Tree {
|
||||
const blob = parse(code)
|
||||
return Ast.deserializeTree(blob.buffer)
|
||||
return Ast.Tree.read(new DataView(blob.buffer), blob.byteLength - 4)
|
||||
}
|
||||
|
||||
if (import.meta.vitest) {
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
export { type Result } from '@/util/result'
|
||||
import { Err, Error, Ok, type Result } from '@/util/result'
|
||||
import { bail } from './assert'
|
||||
|
||||
export type Primitive = {
|
||||
type: 'primitive'
|
||||
@ -24,6 +25,7 @@ export type DynObject = {
|
||||
type: 'object'
|
||||
getFields: () => [string, DynValue][]
|
||||
}
|
||||
|
||||
export const Dyn = {
|
||||
Primitive: (value: boolean | number | bigint | string): DynValue => ({
|
||||
type: 'primitive',
|
||||
@ -40,10 +42,10 @@ export const Dyn = {
|
||||
|
||||
/** Base class for objects that lazily deserialize fields when accessed. */
|
||||
export abstract class LazyObject {
|
||||
protected readonly lazyObjectData: Cursor
|
||||
protected readonly _v: DataView
|
||||
|
||||
protected constructor(data: Cursor) {
|
||||
this.lazyObjectData = data
|
||||
protected constructor(view: DataView) {
|
||||
this._v = view
|
||||
}
|
||||
|
||||
fields(): [string, DynValue][] {
|
||||
@ -51,109 +53,102 @@ export abstract class LazyObject {
|
||||
}
|
||||
}
|
||||
|
||||
export const builtin = {
|
||||
Array: Array,
|
||||
} as const
|
||||
type Reader<T> = (view: DataView, address: number) => T
|
||||
|
||||
export class Cursor {
|
||||
private readonly blob: DataView
|
||||
function makeDataView(buffer: ArrayBuffer, address: number) {
|
||||
return new DataView(buffer, address)
|
||||
}
|
||||
|
||||
constructor(buffer: ArrayBuffer, address: number) {
|
||||
this.blob = 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 {
|
||||
const discriminant = readU8(view, address)
|
||||
switch (discriminant) {
|
||||
case 0:
|
||||
return undefined
|
||||
case 1:
|
||||
return readElement(readPointer(view, address + 1), 0)
|
||||
default:
|
||||
throw new Error(`Invalid Option discriminant: 0x${discriminant.toString(16)}.`)
|
||||
}
|
||||
}
|
||||
|
||||
*readSequence<T>(readElement: (cursor: Cursor) => T, elementSize: number): Iterable<T> {
|
||||
const data = this.readPointer()
|
||||
let count = data.readU32()
|
||||
let offset = 4
|
||||
while (count > 0) {
|
||||
yield readElement(data.seek(offset))
|
||||
count--
|
||||
offset += elementSize
|
||||
}
|
||||
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* readSequence<T>(view: DataView, address: number, size: number, reader: Reader<T>) {
|
||||
const data = readPointer(view, address)
|
||||
let count = readU32(data, 0)
|
||||
let offset = 4
|
||||
while (count > 0) {
|
||||
yield reader(data, offset)
|
||||
count--
|
||||
offset += size
|
||||
}
|
||||
}
|
||||
|
||||
readOption<T>(readElement: (cursor: Cursor) => T): T | undefined {
|
||||
const discriminant = this.readU8()
|
||||
switch (discriminant) {
|
||||
case 0:
|
||||
return undefined
|
||||
case 1:
|
||||
return readElement(this.seek(1).readPointer())
|
||||
default:
|
||||
throw new Error(`Invalid Option discriminant: 0x${discriminant.toString(16)}.`)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
readResult<Ok, Err>(
|
||||
readOk: (cursor: Cursor) => Ok,
|
||||
readErr: (cursor: Cursor) => Err,
|
||||
): Result<Ok, Err> {
|
||||
const data = this.readPointer()
|
||||
const discriminant = data.readU32()
|
||||
switch (discriminant) {
|
||||
case 0:
|
||||
return Ok(readOk(data.seek(4)))
|
||||
case 1:
|
||||
return Err(readErr(data.seek(4)))
|
||||
default:
|
||||
throw new Error(`Invalid Result discriminant: 0x${discriminant.toString(16)}.`)
|
||||
}
|
||||
}
|
||||
|
||||
readPointer(): Cursor {
|
||||
const pointee = this.readU32()
|
||||
return new Cursor(this.blob.buffer, pointee)
|
||||
}
|
||||
|
||||
readU8(): number {
|
||||
return this.blob.getUint8(0)
|
||||
}
|
||||
|
||||
readU32(): number {
|
||||
return this.blob.getUint32(0, true)
|
||||
}
|
||||
|
||||
readI32(): number {
|
||||
return this.blob.getInt32(0, true)
|
||||
}
|
||||
|
||||
readU64(): bigint {
|
||||
return this.blob.getBigUint64(0, true)
|
||||
}
|
||||
|
||||
readI64(): bigint {
|
||||
return this.blob.getBigInt64(0, true)
|
||||
}
|
||||
|
||||
readBool(): boolean {
|
||||
const value = this.readU8()
|
||||
switch (value) {
|
||||
case 0:
|
||||
return false
|
||||
case 1:
|
||||
return true
|
||||
default:
|
||||
throw new Error(
|
||||
`Invalid boolean: 0x${value.toString(16)} @ 0x${this.blob.byteOffset.toString(16)}.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
readString(): string {
|
||||
const data = this.readPointer()
|
||||
const len = data.readU32()
|
||||
const bytes = data.blob.buffer.slice(data.blob.byteOffset + 4, data.blob.byteOffset + 4 + len)
|
||||
return new TextDecoder().decode(bytes)
|
||||
}
|
||||
|
||||
seek(offset: number): Cursor {
|
||||
return new Cursor(this.blob.buffer, this.blob.byteOffset + offset)
|
||||
}
|
||||
|
||||
address(): number {
|
||||
return this.blob.byteOffset
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
export function debug(obj: LazyObject): any {
|
||||
|
Loading…
Reference in New Issue
Block a user