Skip to content

Commit

Permalink
[JavaScript] Implements tuple serializer (#1216)
Browse files Browse the repository at this point in the history
  • Loading branch information
bytemain authored Dec 10, 2023
1 parent 84747cf commit 738a29f
Show file tree
Hide file tree
Showing 9 changed files with 440 additions and 25 deletions.
5 changes: 4 additions & 1 deletion javascript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@
"workspaces": [
"packages/hps",
"packages/fury"
]
],
"devDependencies": {
"prettier": "^3.1.0"
}
}
65 changes: 44 additions & 21 deletions javascript/packages/fury/lib/codeGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import { replaceBackslashAndQuote, safePropAccessor, safePropName } from './util
import mapSerializer from './internalSerializer/map';
import setSerializer from './internalSerializer/set';
import { arraySerializer } from './internalSerializer/array';
import { ArrayTypeDescription, Cast, MapTypeDescription, ObjectTypeDescription, SetTypeDescription, TypeDescription } from './description';
import { tupleSerializer } from './internalSerializer/tuple';
import { ArrayTypeDescription, Cast, MapTypeDescription, ObjectTypeDescription, SetTypeDescription, TupleTypeDescription, TypeDescription } from './description';

function computeFieldHash(hash: number, id: number): number {
let newHash = (hash) * 31 + (id);
Expand Down Expand Up @@ -72,13 +73,13 @@ function typeHandlerDeclaration(fury: Fury) {
const genBuiltinDeclaration = (type: number) => {
const name = `type_${type}`.replace('-', '_');
return addDeclar(name, `
const ${name} = classResolver.getSerializerById(${type})`);
const ${name} = classResolver.getSerializerById(${type})`);
}

const genTagDeclaration = (tag: string) => {
const name = `tag_${count++}`;
return addDeclar(name, `
const ${name} = classResolver.getSerializerByTag("${replaceBackslashAndQuote(tag)}")`, tag);
const ${name} = classResolver.getSerializerByTag("${replaceBackslashAndQuote(tag)}")`, tag);
}

const genDeclaration = (description: TypeDescription): string => {
Expand All @@ -87,17 +88,30 @@ function typeHandlerDeclaration(fury: Fury) {
return genTagDeclaration(Cast<ObjectTypeDescription>(description).options.tag);
}
if (description.type === InternalSerializerType.ARRAY) {
const tupleOptions = Cast<TupleTypeDescription>(description).options;
if (tupleOptions && tupleOptions.isTuple) {
const names = [] as string[];
Cast<TupleTypeDescription>(description).options.inner.forEach(v => {
names.push(genDeclaration(v));
})

const name = `tuple_${names.join('_')}`;
return addDeclar(name, `
const ${name} = tupleSerializer(fury, [${names.join(', ')}])`
)
}

const inner = genDeclaration(Cast<ArrayTypeDescription>(description).options.inner);
const name = `array_${inner}`;
return addDeclar(name, `
const ${name} = arraySerializer(fury, ${inner})`
const ${name} = arraySerializer(fury, ${inner})`
)
}
if (description.type === InternalSerializerType.FURY_SET) {
const inner = genDeclaration(Cast<SetTypeDescription>(description).options.key);
const name = `set_${inner}`;
return addDeclar(name, `
const ${name} = setSerializer(fury, ${inner})`
const ${name} = setSerializer(fury, ${inner})`
)
}
if (description.type === InternalSerializerType.MAP) {
Expand All @@ -106,7 +120,7 @@ function typeHandlerDeclaration(fury: Fury) {

const name = `map_${key}_${value}`;
return addDeclar(name, `
const ${name} = mapSerializer(fury, ${key}, ${value})`
const ${name} = mapSerializer(fury, ${key}, ${value})`
)
}
return genBuiltinDeclaration(description.type);
Expand All @@ -125,41 +139,37 @@ function typeHandlerDeclaration(fury: Fury) {
}
}

export const genSerializer = (fury: Fury, description: TypeDescription) => {
export const generateInlineCode = (fury: Fury, description: TypeDescription) => {
const options = Cast<ObjectTypeDescription>(description).options;
const tag = options?.tag;
const { genDeclaration, finish } = typeHandlerDeclaration(fury);
const tag = Cast<ObjectTypeDescription>(description).options?.tag;
if (fury.classResolver.getSerializerByTag(tag)) {
return fury.classResolver.getSerializerByTag(tag);
}

fury.classResolver.registerSerializerByTag(tag, fury.classResolver.getSerializerById(InternalSerializerType.ANY));
const expectHash = computeStructHash(description);
const read = `
// relation tag: ${Cast<ObjectTypeDescription>(description).options?.tag}
// relation tag: ${tag}
const result = {
${Object.entries(Cast<ObjectTypeDescription>(description).options.props).sort().map(([key]) => {
${Object.entries(options.props).sort().map(([key]) => {
return `${safePropName(key)}: null`
}).join(',\n')}
};
pushReadObject(result);
${Object.entries(Cast<ObjectTypeDescription>(description).options.props).sort().map(([key, value]) => {
${Object.entries(options.props).sort().map(([key, value]) => {
return `result${safePropAccessor(key)} = ${genDeclaration(value)}.read()`;
}).join(';\n')
}
return result;
`;
const write = Object.entries(Cast<ObjectTypeDescription>(description).options.props).sort().map(([key, value]) => {
const write = Object.entries(options.props).sort().map(([key, value]) => {
return `${genDeclaration(value)}.write(v${safePropAccessor(key)})`;
}).join(';\n');
const { names, declarations} = finish();
const validTag = replaceBackslashAndQuote(tag);
return fury.classResolver.registerSerializerByTag(tag, new Function(
return new Function(
`
return function (fury, scope) {
const { referenceResolver, binaryWriter, classResolver, binaryReader } = fury;
const { writeNullOrRef, pushReadObject } = referenceResolver;
const { RefFlags, InternalSerializerType, arraySerializer, mapSerializer, setSerializer } = scope;
${declarations.join('')}
const { RefFlags, InternalSerializerType, arraySerializer, tupleSerializer, mapSerializer, setSerializer } = scope;
${declarations.join('')}
const tagBuffer = classResolver.tagToBuffer("${validTag}");
const bufferLen = tagBuffer.byteLength;
Expand Down Expand Up @@ -188,10 +198,23 @@ return function (fury, scope) {
}
}
`
)()(fury, {
)
}

export const genSerializer = (fury: Fury, description: TypeDescription) => {
const tag = Cast<ObjectTypeDescription>(description).options?.tag;
if (fury.classResolver.getSerializerByTag(tag)) {
return fury.classResolver.getSerializerByTag(tag);
}

fury.classResolver.registerSerializerByTag(tag, fury.classResolver.getSerializerById(InternalSerializerType.ANY));

const func = generateInlineCode(fury, description);
return fury.classResolver.registerSerializerByTag(tag, func()(fury, {
InternalSerializerType,
RefFlags,
arraySerializer,
tupleSerializer,
mapSerializer,
setSerializer,
}));
Expand Down
29 changes: 29 additions & 0 deletions javascript/packages/fury/lib/description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ export interface ArrayTypeDescription extends TypeDescription {
}
}

export interface TupleTypeDescription extends TypeDescription {
options: {
isTuple: true,
inner: TypeDescription[];
}
}


export interface SetTypeDescription extends TypeDescription {
options: {
key: TypeDescription;
Expand Down Expand Up @@ -80,6 +88,14 @@ type MapProps<T> = T extends {
? Map<ToRecordType<T2>, ToRecordType<T3> | null>
: unknown;

type TupleProps<T> = T extends {
options: {
inner: infer T2 extends readonly[...TypeDescription[]];
};
}
? { [K in keyof T2]: ToRecordType<T2[K]> }
: unknown;

type SetProps<T> = T extends {
options: {
key: infer T2 extends TypeDescription;
Expand All @@ -96,6 +112,10 @@ export type ToRecordType<T> = T extends {
type: InternalSerializerType.STRING;
}
? string
: T extends {
type: InternalSerializerType.TUPLE;
}
? TupleProps<T>
: T extends {
type:
| InternalSerializerType.UINT8
Expand Down Expand Up @@ -164,6 +184,15 @@ export const Type = {
},
};
},
tuple<T1 extends readonly [...readonly TypeDescription[]]>(t1: T1) {
return {
type: InternalSerializerType.TUPLE as const,
options: {
isTuple: true,
inner: t1,
},
};
},
map<T1 extends TypeDescription, T2 extends TypeDescription>(
key: T1,
value: T2
Expand Down
54 changes: 54 additions & 0 deletions javascript/packages/fury/lib/internalSerializer/tuple.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2023 The Fury Authors
*
* Licensed 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 { InternalSerializerType, Serializer } from "../type";
import { Fury } from "../type";


export const tupleSerializer = (fury: Fury, serializers: Serializer[]) => {
const { binaryReader, binaryWriter, referenceResolver } = fury;

const { pushReadObject } = referenceResolver;
const { varInt32: writeVarInt32, reserve: reserves } = binaryWriter;
const { varInt32: readVarInt32 } = binaryReader;

return {
...referenceResolver.deref(() => {
const len = readVarInt32();
const result = new Array(len);
pushReadObject(result);
for (let i = 0; i < len; i++) {
const item = serializers[i];
result[i] = item.read();
}
return result;
}),
write: referenceResolver.withNullableOrRefWriter(InternalSerializerType.TUPLE, (v: any[]) => {
writeVarInt32(serializers.length);

for (let i = 0; i < serializers.length; i++) {
const item = serializers[i];
reserves(item.config().reserve);
item.write(v[i]);
}
}),
config: () => {
return {
reserve: 7,
}
}
}
}
7 changes: 4 additions & 3 deletions javascript/packages/fury/lib/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ export type BinaryWriter = ReturnType<typeof BinaryWriter>
export type BinaryReader = ReturnType<typeof BinaryReader>


export enum InternalSerializerType{
STRING = 13,
export enum InternalSerializerType {
STRING = 13,
ARRAY = 25,
TUPLE = 25,
MAP = 30,
BOOL = 1,
UINT8 = 2,
Expand All @@ -41,7 +42,7 @@ export enum InternalSerializerType{
BINARY = 14,
DATE = 16,
TIMESTAMP = 18,
FURY_TYPE_TAG = 256,
FURY_TYPE_TAG = 256,
FURY_SET = 257,
FURY_PRIMITIVE_BOOL_ARRAY = 258,
FURY_PRIMITIVE_SHORT_ARRAY = 259,
Expand Down
Loading

0 comments on commit 738a29f

Please sign in to comment.