| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import { CodecBuilder } from "./builder"; |
| import { RefFlags, TypeId } from "../type"; |
| import { Scope } from "./scope"; |
| import { TypeInfo } from "../typeInfo"; |
| import { refTrackingUnableTypeId } from "../meta/TypeMeta"; |
| import { BinaryWriter } from "../writer"; |
| |
| export const makeHead = (flag: RefFlags, typeId: number) => { |
| const writer = new BinaryWriter(); |
| writer.writeUint8(flag); |
| writer.writeUint8(typeId); |
| const buffer = writer.dump(); |
| return buffer; |
| }; |
| |
| export interface SerializerGenerator { |
| writeRef(accessor: string): string; |
| writeNoRef(accessor: string): string; |
| writeRefOrNull(accessor: string, assignStmt: (v: string) => string): string; |
| writeTypeInfo(accessor: string): string; |
| write(accessor: string): string; |
| writeEmbed(): any; |
| |
| toSerializer(): string; |
| getFixedSize(): number; |
| needToWriteRef(): boolean; |
| |
| readRef(assignStmt: (v: string) => string): string; |
| readRefWithoutTypeInfo(assignStmt: (v: string) => string): string; |
| readNoRef(assignStmt: (v: string) => string, refState: string): string; |
| readWithDepth(assignStmt: (v: string) => string, refState: string): string; |
| readTypeInfo(): string; |
| read(assignStmt: (v: string) => string, refState: string): string; |
| readEmbed(): any; |
| getHash(): string; |
| |
| getType(): number; |
| getTypeId(): number | undefined; |
| } |
| |
| export enum RefStateType { |
| Condition = "condition", |
| True = "true", |
| False = "false", |
| } |
| export abstract class BaseSerializerGenerator implements SerializerGenerator { |
| constructor( |
| protected typeInfo: TypeInfo, |
| protected builder: CodecBuilder, |
| protected scope: Scope, |
| ) { |
| |
| } |
| |
| abstract getFixedSize(): number; |
| |
| needToWriteRef(): boolean { |
| if (refTrackingUnableTypeId(this.getTypeId())) { |
| return false; |
| } |
| if (!this.builder.resolver.trackingRef) { |
| return false; |
| } |
| if (typeof this.typeInfo.trackingRef === "boolean") { |
| return this.typeInfo.trackingRef; |
| } |
| return true; |
| } |
| |
| abstract write(accessor: string): string; |
| |
| writeEmbed() { |
| const obj = {}; |
| return new Proxy(obj, { |
| get: (target, prop) => { |
| return (...args: any[]) => { |
| return (this as any)[prop](...args); |
| }; |
| }, |
| }); |
| } |
| |
| readEmbed() { |
| const obj = {}; |
| return new Proxy(obj, { |
| get: (target, prop) => { |
| return (...args: any[]) => { |
| return (this as any)[prop](...args); |
| }; |
| }, |
| }); |
| } |
| |
| writeRef(accessor: string) { |
| const noneedWrite = this.scope.uniqueName("noneedWrite"); |
| return ` |
| let ${noneedWrite} = false; |
| ${this.writeRefOrNull(accessor, expr => `${noneedWrite} = ${expr}`)} |
| if (!${noneedWrite}) { |
| ${this.writeNoRef(accessor)} |
| } |
| `; |
| } |
| |
| writeNoRef(accessor: string) { |
| return ` |
| ${this.writeTypeInfo(accessor)}; |
| ${this.write(accessor)}; |
| `; |
| } |
| |
| writeRefOrNull(accessor: string, assignStmt: (expr: string) => string) { |
| let refFlagStmt = ""; |
| if (this.needToWriteRef()) { |
| const existsId = this.scope.uniqueName("existsId"); |
| refFlagStmt = ` |
| const ${existsId} = ${this.builder.referenceResolver.getWrittenRefId(accessor)}; |
| if (typeof ${existsId} === "number") { |
| ${this.builder.writer.writeInt8(RefFlags.RefFlag)} |
| ${this.builder.writer.writeVarUInt32(existsId)} |
| ${assignStmt("true")}; |
| } else { |
| ${this.builder.writer.writeInt8(RefFlags.RefValueFlag)} |
| ${this.builder.referenceResolver.writeRef(accessor)} |
| } |
| `; |
| } else { |
| refFlagStmt = this.builder.writer.writeInt8(RefFlags.NotNullValueFlag); |
| } |
| return ` |
| if (${accessor} === null || ${accessor} === undefined) { |
| ${this.builder.writer.writeInt8(RefFlags.NullFlag)}; |
| ${assignStmt("true")}; |
| } else { |
| ${refFlagStmt} |
| } |
| `; |
| } |
| |
| writeTypeInfo(accessor: string) { |
| void accessor; |
| const typeId = this.getTypeId(); |
| const userTypeId = this.typeInfo.userTypeId; |
| const userTypeStmt = TypeId.needsUserTypeId(typeId) && typeId !== TypeId.COMPATIBLE_STRUCT |
| ? this.builder.writer.writeVarUint32Small7(userTypeId) |
| : ""; |
| return ` |
| ${this.builder.writer.writeUint8(typeId)}; |
| ${userTypeStmt} |
| `; |
| } |
| |
| getType() { |
| return this.getTypeId(); |
| } |
| |
| getTypeId() { |
| return this.builder.resolver.computeTypeId(this.typeInfo); |
| } |
| |
| getUserTypeId() { |
| return this.typeInfo.userTypeId; |
| } |
| |
| getInternalTypeId() { |
| return this.getTypeId(); |
| } |
| |
| abstract read(assignStmt: (v: string) => string, refState: string): string; |
| |
| readWithDepth(assignStmt: (v: string) => string, refState: string): string { |
| const result = this.scope.uniqueName("result"); |
| return ` |
| ${this.builder.getReadContextName()}.incReadDepth(); |
| let ${result}; |
| ${this.read(v => `${result} = ${v}`, refState)}; |
| ${this.builder.getReadContextName()}.decReadDepth(); |
| ${assignStmt(result)}; |
| `; |
| } |
| |
| readTypeInfo(): string { |
| const typeId = this.getTypeId(); |
| const readUserTypeStmt = TypeId.needsUserTypeId(typeId) && typeId !== TypeId.COMPATIBLE_STRUCT |
| ? `${this.builder.reader.readVarUint32Small7()};` |
| : ""; |
| return ` |
| ${this.builder.reader.readUint8()}; |
| ${readUserTypeStmt} |
| `; |
| } |
| |
| readNoRef(assignStmt: (v: string) => string, refState: string): string { |
| return ` |
| ${this.readTypeInfo()} |
| ${this.readWithDepth(assignStmt, refState)} |
| `; |
| } |
| |
| readRefWithoutTypeInfo(assignStmt: (v: string) => string): string { |
| const refFlag = this.scope.uniqueName("refFlag"); |
| const result = this.scope.uniqueName("result"); |
| return ` |
| const ${refFlag} = ${this.builder.reader.readInt8()}; |
| let ${result}; |
| switch (${refFlag}) { |
| case ${RefFlags.NotNullValueFlag}: |
| case ${RefFlags.RefValueFlag}: |
| ${this.readWithDepth(v => `${result} = ${v}`, `${refFlag} === ${RefFlags.RefValueFlag}`)} |
| break; |
| case ${RefFlags.RefFlag}: |
| ${result} = ${this.builder.referenceResolver.getReadRef(this.builder.reader.readVarUInt32())}; |
| break; |
| case ${RefFlags.NullFlag}: |
| ${result} = null; |
| break; |
| } |
| ${assignStmt(result)}; |
| `; |
| } |
| |
| readRef(assignStmt: (v: string) => string): string { |
| const refFlag = this.scope.uniqueName("refFlag"); |
| return ` |
| const ${refFlag} = ${this.builder.reader.readInt8()}; |
| switch (${refFlag}) { |
| case ${RefFlags.NotNullValueFlag}: |
| case ${RefFlags.RefValueFlag}: |
| ${this.readNoRef(assignStmt, `${refFlag} === ${RefFlags.RefValueFlag}`)} |
| break; |
| case ${RefFlags.RefFlag}: |
| ${assignStmt(this.builder.referenceResolver.getReadRef(this.builder.reader.readVarUInt32()))} |
| break; |
| case ${RefFlags.NullFlag}: |
| ${assignStmt("null")} |
| break; |
| } |
| `; |
| } |
| |
| protected maybeReference(accessor: string, refState: string) { |
| if (refState === "false") { |
| return ""; |
| } |
| if (refState === "true") { |
| return this.builder.referenceResolver.reference(accessor); |
| } |
| return ` |
| if (${refState}) { |
| ${this.builder.referenceResolver.reference(accessor)} |
| } |
| `; |
| } |
| |
| getHash(): string { |
| return "0"; |
| } |
| |
| toSerializer() { |
| this.scope.assertNameNotDuplicate("read"); |
| this.scope.assertNameNotDuplicate("readInner"); |
| this.scope.assertNameNotDuplicate("write"); |
| this.scope.assertNameNotDuplicate("writeInner"); |
| this.scope.assertNameNotDuplicate("typeResolver"); |
| this.scope.assertNameNotDuplicate("external"); |
| this.scope.assertNameNotDuplicate("options"); |
| this.scope.assertNameNotDuplicate("typeInfo"); |
| |
| const declare = ` |
| const getHash = () => { |
| return ${this.getHash()}; |
| } |
| const write = (v) => { |
| ${this.write("v")} |
| }; |
| const writeRef = (v) => { |
| ${this.writeRef("v")} |
| }; |
| const writeNoRef = (v) => { |
| ${this.writeNoRef("v")} |
| }; |
| const writeRefOrNull = (v) => { |
| ${this.writeRefOrNull("v", expr => `return ${expr};`)} |
| }; |
| const writeTypeInfo = (v) => { |
| ${this.writeTypeInfo("v")} |
| }; |
| const read = (fromRef) => { |
| ${this.read(assignStmt => `return ${assignStmt}`, "fromRef")} |
| }; |
| const readRef = () => { |
| ${this.readRef(assignStmt => `return ${assignStmt}`)} |
| }; |
| const readRefWithoutTypeInfo = () => { |
| ${this.readRefWithoutTypeInfo(assignStmt => `return ${assignStmt}`)} |
| }; |
| const readNoRef = (fromRef) => { |
| ${this.readNoRef(assignStmt => `return ${assignStmt}`, "fromRef")} |
| }; |
| const readTypeInfo = () => { |
| ${this.readTypeInfo()} |
| }; |
| `; |
| return ` |
| return function (typeResolver, external, typeInfo, options) { |
| ${this.scope.generate()} |
| ${declare} |
| return { |
| _initialized: true, |
| fixedSize: ${this.getFixedSize()}, |
| needToWriteRef: () => ${this.needToWriteRef()}, |
| getTypeId: () => ${this.getTypeId()}, |
| getUserTypeId: () => ${this.getUserTypeId()}, |
| getTypeInfo: () => typeInfo, |
| getHash, |
| |
| write, |
| writeRef, |
| writeNoRef, |
| writeRefOrNull, |
| writeTypeInfo, |
| |
| read, |
| readRef, |
| readRefWithoutTypeInfo, |
| readNoRef, |
| readTypeInfo, |
| }; |
| } |
| `; |
| } |
| } |