Skip to content

Commit d14712b

Browse files
authored
Merge pull request #38 from jsonjoy-com/move-codegen-out
Move codegen out
2 parents 1424622 + 540e521 commit d14712b

File tree

8 files changed

+1562
-96
lines changed

8 files changed

+1562
-96
lines changed

src/codegen/binary/cborEncoders.ts

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

0 commit comments

Comments
 (0)