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