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