@@ -4,6 +4,7 @@ import { SchemaTable } from "..";
4
4
export type Variant = {
5
5
tag : number ; // number assigned to the variant in the FooKind enum
6
6
peek_type : string | string [ ] ; // decode this variant if the CBOR type tag equals any of these values
7
+ valid_tags ?: number [ ] ;
7
8
name : string ; // used in Class.new_foo()
8
9
type : string ; // used to do if (reader.getTag() == tag) type.deserialize()
9
10
kind_name ?: string ; // name of the variant in the FooKind enum
@@ -13,6 +14,15 @@ export type GenUnionOptions = {
13
14
variants : Variant [ ] ;
14
15
} & CodeGeneratorBaseOptions ;
15
16
17
+ // We say tagged to refer to variants that are encoded with a CBOR tag
18
+ type TaggedVariant = {
19
+ tag : number ; // number assigned to the variant in the FooKind enum
20
+ valid_tags : number [ ] ;
21
+ name : string ; // used in Class.new_foo()
22
+ type : string ; // used to do if (reader.getTag() == tag) type.deserialize()
23
+ kind_name ?: string ; // name of the variant in the FooKind enum
24
+ }
25
+
16
26
export class GenUnion extends CodeGeneratorBase {
17
27
variants : Variant [ ] ;
18
28
@@ -87,33 +97,103 @@ export class GenUnion extends CodeGeneratorBase {
87
97
}
88
98
89
99
generateDeserialize ( reader : string , path : string ) : string {
100
+ const constructUntagged = ( v : Variant ) => {
101
+ let out = "" ;
102
+ if ( Array . isArray ( v . peek_type ) ) {
103
+ for ( let t of v . peek_type ) {
104
+ out += `case "${ t } ":\n` ;
105
+ }
106
+ } else {
107
+ out += `case "${ v . peek_type } ":\n` ;
108
+ }
109
+ return out +
110
+ `
111
+ variant = {
112
+ kind: ${ this . name } Kind.${ v . kind_name ?? v . type } ,
113
+ value: ${ this . typeUtils . readType ( reader , v . type , `[...${ path } , '${ v . type } (${ v . name } )']` ) }
114
+ };
115
+ break;
116
+ `
117
+ }
118
+
119
+ const constructTagged = ( v : TaggedVariant ) => {
120
+ if ( v . valid_tags . length == 0 ) {
121
+ throw new Error ( "Expected a non-empty 'valid_tags' field because multiple tagged variants exist. These are needed to disambiguate." )
122
+ } else {
123
+ return `if ([${ v . valid_tags . toString ( ) } ].includes(tagNumber)) {
124
+ variant = {
125
+ kind: ${ this . name } Kind.${ v . kind_name ?? v . type } ,
126
+ value: ${ this . typeUtils . readType ( reader , v . type , `[...${ path } , '${ v . type } (${ v . name } )']` ) }
127
+ };
128
+ break;
129
+ }`
130
+ }
131
+ }
132
+
133
+ // split variants into tagged and untagged types
134
+ let [ taggedVariants , untaggedVariants ] = this . variants . reduce ( ( acc , v ) => {
135
+ let [ tagged , untagged ] = acc ;
136
+ if ( typeof v . peek_type == "string" ) {
137
+ if ( v . peek_type == "tagged" ) {
138
+ let tagged_v : TaggedVariant = {
139
+ tag : v . tag ,
140
+ valid_tags : v . valid_tags ? v . valid_tags : [ ] ,
141
+ name : v . name ,
142
+ type : v . type ,
143
+ kind_name : v . kind_name
144
+ }
145
+ tagged . push ( tagged_v ) ;
146
+ } else {
147
+ untagged . push ( v ) ;
148
+ }
149
+ } else if ( v . peek_type . includes ( "tagged" ) ) {
150
+ let untagged_v : Variant = structuredClone ( v )
151
+ untagged_v . peek_type = v . peek_type . filter ( ( t ) => t != "tagged" ) ;
152
+
153
+ let tagged_v : TaggedVariant = {
154
+ tag : v . tag ,
155
+ valid_tags : v . valid_tags ? v . valid_tags : [ ] ,
156
+ name : v . name ,
157
+ type : v . type ,
158
+ kind_name : v . kind_name
159
+ }
160
+
161
+ untagged . push ( untagged_v ) ;
162
+ tagged . push ( tagged_v ) ;
163
+ } else {
164
+ untagged . push ( v ) ;
165
+ }
166
+
167
+ return [ tagged , untagged ]
168
+ } , [ [ ] , [ ] ] as [ TaggedVariant [ ] , Variant [ ] ] ) ;
169
+
90
170
return `
91
171
let tag = ${ reader } .peekType(${ path } );
92
172
let variant: ${ this . name } Variant;
93
173
94
174
switch(tag) {
95
- ${ this . variants
96
- . map ( ( x ) => {
97
- let out = "" ;
98
- if ( Array . isArray ( x . peek_type ) ) {
99
- for ( let t of x . peek_type ) {
100
- out += `case "${ t } ":\n` ;
101
- }
102
- } else {
103
- out += `case "${ x . peek_type } ":\n` ;
104
- }
105
- return (
106
- out +
107
- `
108
- variant = {
109
- kind: ${ this . name } Kind.${ x . kind_name ?? x . type } ,
110
- value: ${ this . typeUtils . readType ( reader , x . type , `[...${ path } , '${ x . type } (${ x . name } )']` ) }
111
- };
112
- break;
113
- `
114
- ) ;
115
- } )
175
+ ${ untaggedVariants
176
+ . map ( constructUntagged )
116
177
. join ( "\n" ) }
178
+ ${ taggedVariants . length > 0
179
+ ? ( taggedVariants . length == 1
180
+ ? `case "tagged":
181
+ variant = {
182
+ kind: ${ this . name } Kind.${ taggedVariants [ 0 ] . kind_name ?? taggedVariants [ 0 ] . type } ,
183
+ value: ${ this . typeUtils . readType ( reader , taggedVariants [ 0 ] . type , `[...${ path } , '${ taggedVariants [ 0 ] . type } (${ taggedVariants [ 0 ] . name } )']` ) }
184
+ };
185
+ break;
186
+ `
187
+ : `case "tagged":
188
+ const tagNumber = ${ reader } .peekTagNumber(${ path } );
189
+ ${ constructTagged ( taggedVariants [ 0 ] ) }
190
+ ${ taggedVariants . slice ( 1 ) . map ( ( v ) => "else " + constructTagged ( v ) ) . join ( "\n" ) }
191
+ else {
192
+ throw new Error("Unexpected tag number " + tagNumber + " (at " + ${ path } .join("/") + ")")
193
+ }
194
+ ` )
195
+ : ''
196
+ }
117
197
default:
118
198
throw new Error("Unexpected subtype for ${ this . name } : " + tag + "(at " + ${ path } .join("/") + ")");
119
199
}
0 commit comments