Skip to content

Commit 49b5831

Browse files
Copilotstreamich
andcommitted
feat: create centralized codegen router functions
Co-authored-by: streamich <[email protected]>
1 parent 61b1b84 commit 49b5831

File tree

5 files changed

+1571
-0
lines changed

5 files changed

+1571
-0
lines changed

src/codegen/binary/cborEncoders.ts

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
import type {CborEncoderCodegenContext} from './CborEncoderCodegenContext';
2+
import type {JsExpression} from '@jsonjoy.com/util/lib/codegen/util/JsExpression';
3+
import type {Type} from '../../type';
4+
import type {BinaryJsonEncoder} from '@jsonjoy.com/json-pack/lib/types';
5+
import type {BinaryEncoderCodegenContext} from './BinaryEncoderCodegenContext';
6+
import {normalizeAccessor} from '@jsonjoy.com/util/lib/codegen/util/normalizeAccessor';
7+
8+
type CborEncoderFunction = (ctx: CborEncoderCodegenContext, value: JsExpression, type: Type) => void;
9+
10+
const codegenBinaryEncoder = (ctx: BinaryEncoderCodegenContext<BinaryJsonEncoder>, value: JsExpression, type: Type): void => {
11+
const kind = type.getTypeName();
12+
const v = value.use();
13+
14+
switch (kind) {
15+
case 'str': {
16+
const strType = type as any; // StrType
17+
const {ascii, format} = strType.schema;
18+
// Use ASCII encoding if format is 'ascii' or ascii=true (backward compatibility)
19+
const useAscii = format === 'ascii' || ascii;
20+
if (useAscii) ctx.js(/* js */ `encoder.writeAsciiStr(${v});`);
21+
else ctx.js(/* js */ `encoder.writeStr(${v});`);
22+
break;
23+
}
24+
case 'bin': {
25+
ctx.js(/* js */ `encoder.writeBin(${v});`);
26+
break;
27+
}
28+
case 'num': {
29+
const numType = type as any; // NumType
30+
const {format, int} = numType.schema;
31+
if (format === 'u8') ctx.js(/* js */ `encoder.writeU8(${v});`);
32+
else if (format === 'u16') ctx.js(/* js */ `encoder.writeU16(${v});`);
33+
else if (format === 'u32') ctx.js(/* js */ `encoder.writeU32(${v});`);
34+
else if (format === 'i8') ctx.js(/* js */ `encoder.writeI8(${v});`);
35+
else if (format === 'i16') ctx.js(/* js */ `encoder.writeI16(${v});`);
36+
else if (format === 'i32') ctx.js(/* js */ `encoder.writeI32(${v});`);
37+
else if (format === 'f32') ctx.js(/* js */ `encoder.writeF32(${v});`);
38+
else if (format === 'f64') ctx.js(/* js */ `encoder.writeF64(${v});`);
39+
else if (int) ctx.js(/* js */ `encoder.writeUInt(${v});`);
40+
else ctx.js(/* js */ `encoder.writeF64(${v});`);
41+
break;
42+
}
43+
case 'bool': {
44+
ctx.js(/* js */ `encoder.writeBool(${v});`);
45+
break;
46+
}
47+
default: {
48+
ctx.js(/* js */ `encoder.writeAny(${v});`);
49+
break;
50+
}
51+
}
52+
};
53+
54+
export const any = (ctx: CborEncoderCodegenContext, value: JsExpression, type: Type): void => {
55+
const codegen = ctx.codegen;
56+
codegen.link('Value');
57+
const r = codegen.var(value.use());
58+
codegen.if(
59+
`${r} instanceof Value`,
60+
() => {
61+
codegen.if(
62+
`${r}.type`,
63+
() => {
64+
codegen.js(`${r}.type.encoder(encoder.format)(${r}.data, encoder);`);
65+
},
66+
() => {
67+
codegen.js(`encoder.writeAny(${r}.data);`);
68+
},
69+
);
70+
},
71+
() => {
72+
codegen.js(`encoder.writeAny(${r});`);
73+
},
74+
);
75+
};
76+
77+
export const bool = (ctx: CborEncoderCodegenContext, value: JsExpression): void => {
78+
codegenBinaryEncoder(ctx, value, { getTypeName: () => 'bool' } as Type);
79+
};
80+
81+
export const num = (ctx: CborEncoderCodegenContext, value: JsExpression, type: Type): void => {
82+
codegenBinaryEncoder(ctx, value, type);
83+
};
84+
85+
export const str = (ctx: CborEncoderCodegenContext, value: JsExpression, type: Type): void => {
86+
codegenBinaryEncoder(ctx, value, type);
87+
};
88+
89+
export const bin = (ctx: CborEncoderCodegenContext, value: JsExpression, type: Type): void => {
90+
codegenBinaryEncoder(ctx, value, type);
91+
};
92+
93+
export const const_ = (ctx: CborEncoderCodegenContext, value: JsExpression, type: Type): void => {
94+
const constType = type as any; // ConType
95+
const constValue = constType.value();
96+
ctx.js(/* js */ `encoder.writeAny(${JSON.stringify(constValue)});`);
97+
};
98+
99+
export const arr = (
100+
ctx: CborEncoderCodegenContext,
101+
value: JsExpression,
102+
type: Type,
103+
encodeFn: CborEncoderFunction,
104+
): void => {
105+
const arrType = type as any; // ArrType
106+
const codegen = ctx.codegen;
107+
const r = codegen.getRegister(); // array
108+
const rl = codegen.getRegister(); // array.length
109+
const ri = codegen.getRegister(); // index
110+
ctx.js(/* js */ `var ${r} = ${value.use()}, ${rl} = ${r}.length, ${ri} = 0;`);
111+
ctx.js(/* js */ `encoder.writeArrayHead(${rl});`);
112+
ctx.js(/* js */ `for(; ${ri} < ${rl}; ${ri}++) ` + '{');
113+
encodeFn(ctx, new JsExpression(() => `${r}[${ri}]`), arrType.type);
114+
ctx.js(`}`);
115+
};
116+
117+
export const tup = (
118+
ctx: CborEncoderCodegenContext,
119+
value: JsExpression,
120+
type: Type,
121+
encodeFn: CborEncoderFunction,
122+
): void => {
123+
const tupType = type as any; // TupType
124+
const codegen = ctx.codegen;
125+
const r = codegen.var(value.use());
126+
const types = tupType.types;
127+
ctx.js(/* js */ `encoder.writeArrayHead(${types.length});`);
128+
for (let i = 0; i < types.length; i++) {
129+
encodeFn(ctx, new JsExpression(() => `${r}[${i}]`), types[i]);
130+
}
131+
};
132+
133+
export const obj = (
134+
ctx: CborEncoderCodegenContext,
135+
value: JsExpression,
136+
type: Type,
137+
encodeFn: CborEncoderFunction,
138+
): void => {
139+
const objType = type as any; // ObjType
140+
const codegen = ctx.codegen;
141+
const r = codegen.var(value.use());
142+
const encodeUnknownFields = !!objType.schema.encodeUnknownFields;
143+
144+
if (encodeUnknownFields) {
145+
ctx.js(/* js */ `encoder.writeAny(${r});`);
146+
return;
147+
}
148+
149+
const fields = objType.fields;
150+
const requiredFields = fields.filter((f: any) => !f.optional && f.constructor?.name !== 'ObjectOptionalFieldType');
151+
const optionalFields = fields.filter((f: any) => f.optional || f.constructor?.name === 'ObjectOptionalFieldType');
152+
153+
if (optionalFields.length === 0) {
154+
// All fields are required
155+
ctx.js(/* js */ `encoder.writeObjectHead(${fields.length});`);
156+
for (const field of fields) {
157+
const key = field.key;
158+
const accessor = normalizeAccessor(key);
159+
ctx.js(/* js */ `encoder.writeStr(${JSON.stringify(key)});`);
160+
encodeFn(ctx, new JsExpression(() => `${r}${accessor}`), field.value);
161+
}
162+
} else {
163+
// Mixed fields - need to count optional ones dynamically
164+
const rSize = codegen.getRegister();
165+
ctx.js(/* js */ `var ${rSize} = ${requiredFields.length};`);
166+
167+
// Count optional fields that exist
168+
for (const field of optionalFields) {
169+
const key = field.key;
170+
const accessor = normalizeAccessor(key);
171+
ctx.js(/* js */ `if (${r}${accessor} !== undefined) ${rSize}++;`);
172+
}
173+
174+
ctx.js(/* js */ `encoder.writeObjectHead(${rSize});`);
175+
176+
// Encode required fields
177+
for (const field of requiredFields) {
178+
const key = field.key;
179+
const accessor = normalizeAccessor(key);
180+
ctx.js(/* js */ `encoder.writeStr(${JSON.stringify(key)});`);
181+
encodeFn(ctx, new JsExpression(() => `${r}${accessor}`), field.value);
182+
}
183+
184+
// Encode optional fields
185+
for (const field of optionalFields) {
186+
const key = field.key;
187+
const accessor = normalizeAccessor(key);
188+
ctx.js(/* js */ `if (${r}${accessor} !== undefined) {`);
189+
ctx.js(/* js */ `encoder.writeStr(${JSON.stringify(key)});`);
190+
encodeFn(ctx, new JsExpression(() => `${r}${accessor}`), field.value);
191+
ctx.js(/* js */ `}`);
192+
}
193+
}
194+
};
195+
196+
export const map = (
197+
ctx: CborEncoderCodegenContext,
198+
value: JsExpression,
199+
type: Type,
200+
encodeFn: CborEncoderFunction,
201+
): void => {
202+
const mapType = type as any; // MapType
203+
const codegen = ctx.codegen;
204+
const r = codegen.var(value.use());
205+
const rKeys = codegen.var(`Object.keys(${r})`);
206+
const rKey = codegen.var();
207+
const rLen = codegen.var(`${rKeys}.length`);
208+
const ri = codegen.var('0');
209+
210+
ctx.js(/* js */ `var ${rKeys} = Object.keys(${r}), ${rLen} = ${rKeys}.length, ${rKey}, ${ri} = 0;`);
211+
ctx.js(/* js */ `encoder.writeObjectHead(${rLen});`);
212+
ctx.js(/* js */ `for (; ${ri} < ${rLen}; ${ri}++) {`);
213+
ctx.js(/* js */ `${rKey} = ${rKeys}[${ri}];`);
214+
ctx.js(/* js */ `encoder.writeStr(${rKey});`);
215+
encodeFn(ctx, new JsExpression(() => `${r}[${rKey}]`), mapType.valueType);
216+
ctx.js(`}`);
217+
};
218+
219+
export const ref = (
220+
ctx: CborEncoderCodegenContext,
221+
value: JsExpression,
222+
type: Type,
223+
): void => {
224+
const refType = type as any; // RefType
225+
const system = ctx.options.system || refType.system;
226+
if (!system) throw new Error('NO_SYSTEM');
227+
const encoder = system.resolve(refType.schema.ref).type.encoder(ctx.encoder.format);
228+
const d = ctx.codegen.linkDependency(encoder);
229+
ctx.js(`${d}(${value.use()}, encoder);`);
230+
};
231+
232+
export const or = (
233+
ctx: CborEncoderCodegenContext,
234+
value: JsExpression,
235+
type: Type,
236+
encodeFn: CborEncoderFunction,
237+
): void => {
238+
const orType = type as any; // OrType
239+
const codegen = ctx.codegen;
240+
const discriminator = orType.discriminator();
241+
const d = codegen.linkDependency(discriminator);
242+
const types = orType.types;
243+
codegen.switch(
244+
`${d}(${value.use()})`,
245+
types.map((childType: Type, index: number) => [
246+
index,
247+
() => {
248+
encodeFn(ctx, value, childType);
249+
},
250+
]),
251+
);
252+
};
253+
254+
/**
255+
* Main router function that dispatches CBOR encoding to the appropriate
256+
* encoder function based on the type's kind.
257+
*/
258+
export const generate = (
259+
ctx: CborEncoderCodegenContext,
260+
value: JsExpression,
261+
type: Type,
262+
): void => {
263+
const kind = type.getTypeName();
264+
265+
switch (kind) {
266+
case 'any':
267+
any(ctx, value, type);
268+
break;
269+
case 'bool':
270+
bool(ctx, value);
271+
break;
272+
case 'num':
273+
num(ctx, value, type);
274+
break;
275+
case 'str':
276+
str(ctx, value, type);
277+
break;
278+
case 'bin':
279+
bin(ctx, value, type);
280+
break;
281+
case 'con':
282+
const_(ctx, value, type);
283+
break;
284+
case 'arr':
285+
arr(ctx, value, type, generate);
286+
break;
287+
case 'tup':
288+
tup(ctx, value, type, generate);
289+
break;
290+
case 'obj':
291+
obj(ctx, value, type, generate);
292+
break;
293+
case 'map':
294+
map(ctx, value, type, generate);
295+
break;
296+
case 'ref':
297+
ref(ctx, value, type);
298+
break;
299+
case 'or':
300+
or(ctx, value, type, generate);
301+
break;
302+
default:
303+
throw new Error(`${kind} type CBOR encoding not implemented`);
304+
}
305+
};

0 commit comments

Comments
 (0)