blob: ac8506a47f6900664d89aa0bd488334796665a11 [file]
/*
* 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,
};
}
`;
}
}