@@ -12,19 +12,27 @@ public partial class ContractDescriptorParser
12
12
{
13
13
public const string TypeDescriptorSizeSigil = "!" ;
14
14
15
- public static CompactContractDescriptor ? Parse ( ReadOnlySpan < byte > json )
15
+ /// <summary>
16
+ /// Parses the "compact" representation of a contract descriptor.
17
+ /// </summary>
18
+ /// <remarks>
19
+ /// See data_descriptor.md for the format.
20
+ /// </remarks>
21
+ public static ContractDescriptor ? ParseCompact ( ReadOnlySpan < byte > json )
16
22
{
17
- return JsonSerializer . Deserialize ( json , ContractDescriptorContext . Default . CompactContractDescriptor ) ;
23
+ return JsonSerializer . Deserialize ( json , ContractDescriptorContext . Default . ContractDescriptor ) ;
18
24
}
19
25
20
- [ JsonSerializable ( typeof ( CompactContractDescriptor ) ) ]
26
+ [ JsonSerializable ( typeof ( ContractDescriptor ) ) ]
21
27
[ JsonSerializable ( typeof ( int ) ) ]
22
28
[ JsonSerializable ( typeof ( string ) ) ]
23
29
[ JsonSerializable ( typeof ( Dictionary < string , int > ) ) ]
24
30
[ JsonSerializable ( typeof ( Dictionary < string , TypeDescriptor > ) ) ]
25
31
[ JsonSerializable ( typeof ( Dictionary < string , FieldDescriptor > ) ) ]
32
+ [ JsonSerializable ( typeof ( Dictionary < string , GlobalDescriptor > ) ) ]
26
33
[ JsonSerializable ( typeof ( TypeDescriptor ) ) ]
27
34
[ JsonSerializable ( typeof ( FieldDescriptor ) ) ]
35
+ [ JsonSerializable ( typeof ( GlobalDescriptor ) ) ]
28
36
[ JsonSourceGenerationOptions ( AllowTrailingCommas = true ,
29
37
DictionaryKeyPolicy = JsonKnownNamingPolicy . Unspecified , // contracts, types and globals are case sensitive
30
38
PropertyNamingPolicy = JsonKnownNamingPolicy . CamelCase ,
@@ -34,15 +42,15 @@ internal sealed partial class ContractDescriptorContext : JsonSerializerContext
34
42
{
35
43
}
36
44
37
- public class CompactContractDescriptor
45
+ public class ContractDescriptor
38
46
{
39
47
public int ? Version { get ; set ; }
40
48
public string ? Baseline { get ; set ; }
41
49
public Dictionary < string , int > ? Contracts { get ; set ; }
42
50
43
51
public Dictionary < string , TypeDescriptor > ? Types { get ; set ; }
44
52
45
- // TODO: globals
53
+ public Dictionary < string , GlobalDescriptor > ? Globals { get ; set ; }
46
54
47
55
[ JsonExtensionData ]
48
56
public Dictionary < string , object ? > ? Extras { get ; set ; }
@@ -51,25 +59,35 @@ public class CompactContractDescriptor
51
59
[ JsonConverter ( typeof ( TypeDescriptorConverter ) ) ]
52
60
public class TypeDescriptor
53
61
{
54
- public uint Size { get ; set ; }
62
+ public uint ? Size { get ; set ; }
55
63
public Dictionary < string , FieldDescriptor > ? Fields { get ; set ; }
56
64
}
57
65
58
- // TODO: compact format needs a custom converter
59
66
[ JsonConverter ( typeof ( FieldDescriptorConverter ) ) ]
60
67
public class FieldDescriptor
61
68
{
62
69
public string ? Type { get ; set ; }
63
70
public int Offset { get ; set ; }
64
71
}
65
72
73
+ [ JsonConverter ( typeof ( GlobalDescriptorConverter ) ) ]
74
+ public class GlobalDescriptor
75
+ {
76
+ public string ? Type { get ; set ; }
77
+ public ulong Value { get ; set ; }
78
+ public bool Indirect { get ; set ; }
79
+ }
80
+
66
81
internal sealed class TypeDescriptorConverter : JsonConverter < TypeDescriptor >
67
82
{
83
+ // Almost a normal dictionary converter except:
84
+ // 1. looks for a special key "!" to set the Size property
85
+ // 2. field names are property names, but treated case-sensitively
68
86
public override TypeDescriptor Read ( ref Utf8JsonReader reader , Type typeToConvert , JsonSerializerOptions options )
69
87
{
70
88
if ( reader . TokenType != JsonTokenType . StartObject )
71
89
throw new JsonException ( ) ;
72
- uint size = 0 ;
90
+ uint ? size = null ;
73
91
Dictionary < string , FieldDescriptor > ? fields = new ( ) ;
74
92
while ( reader . Read ( ) )
75
93
{
@@ -97,6 +115,7 @@ public override TypeDescriptor Read(ref Utf8JsonReader reader, Type typeToConver
97
115
}
98
116
break ;
99
117
case JsonTokenType . Comment :
118
+ // unexpected - we specified to skip comments. but let's ignore anyway
100
119
break ;
101
120
default :
102
121
throw new JsonException ( ) ;
@@ -113,60 +132,187 @@ public override void Write(Utf8JsonWriter writer, TypeDescriptor value, JsonSeri
113
132
114
133
internal sealed class FieldDescriptorConverter : JsonConverter < FieldDescriptor >
115
134
{
135
+ // Compact Field descriptors are either one or two element arrays
136
+ // 1. [number] - no type, offset is given as the number
137
+ // 2. [number, string] - has a type, offset is given as the number
116
138
public override FieldDescriptor Read ( ref Utf8JsonReader reader , Type typeToConvert , JsonSerializerOptions options )
117
139
{
118
- if ( reader . TokenType == JsonTokenType . Number || reader . TokenType == JsonTokenType . String )
119
- return new FieldDescriptor { Offset = reader . GetInt32 ( ) } ;
140
+ if ( GetInt32FromToken ( ref reader , out int offset ) )
141
+ return new FieldDescriptor { Offset = offset } ;
120
142
if ( reader . TokenType != JsonTokenType . StartArray )
121
143
throw new JsonException ( ) ;
122
- int eltIdx = 0 ;
123
- string ? type = null ;
124
- int offset = 0 ;
125
- while ( reader . Read ( ) )
144
+ reader . Read ( ) ;
145
+ // two cases:
146
+ // [number]
147
+ // ^ we're here
148
+ // or
149
+ // [number, string]
150
+ // ^ we're here
151
+ if ( ! GetInt32FromToken ( ref reader , out offset ) )
152
+ throw new JsonException ( ) ;
153
+ reader . Read ( ) ; // end of array or string
154
+ if ( reader . TokenType == JsonTokenType . EndArray )
155
+ return new FieldDescriptor { Offset = offset } ;
156
+ if ( reader . TokenType != JsonTokenType . String )
157
+ throw new JsonException ( ) ;
158
+ string ? type = reader . GetString ( ) ;
159
+ reader . Read ( ) ; // end of array
160
+ if ( reader . TokenType != JsonTokenType . EndArray )
161
+ throw new JsonException ( ) ;
162
+ return new FieldDescriptor { Type = type , Offset = offset } ;
163
+ }
164
+
165
+ public override void Write ( Utf8JsonWriter writer , FieldDescriptor value , JsonSerializerOptions options )
166
+ {
167
+ throw new NotImplementedException ( ) ;
168
+ }
169
+ }
170
+
171
+ internal sealed class GlobalDescriptorConverter : JsonConverter < GlobalDescriptor >
172
+ {
173
+ public override GlobalDescriptor Read ( ref Utf8JsonReader reader , Type typeToConvert , JsonSerializerOptions options )
174
+ {
175
+ // four cases:
176
+ // 1. number - no type, direct value, given value
177
+ // 2. [number] - no type, indirect value, given aux data ptr
178
+ // 3. [number, string] - type, direct value, given value
179
+ // 4. [[number], string] - type, indirect value, given aux data ptr
180
+
181
+ // Case 1: number
182
+ if ( GetUInt64FromToken ( ref reader , out ulong valueCase1 ) )
183
+ return new GlobalDescriptor { Value = valueCase1 } ;
184
+ if ( reader . TokenType != JsonTokenType . StartArray )
185
+ throw new JsonException ( ) ;
186
+ reader . Read ( ) ;
187
+ // we're in case 2 or 3 or 4
188
+ // case 2: [number]
189
+ // ^ we're here
190
+ // case 3: [number, string]
191
+ // ^ we're here
192
+ // case 4: [[number], string]
193
+ // ^ we're here
194
+ if ( reader . TokenType == JsonTokenType . StartArray )
126
195
{
127
- switch ( reader . TokenType )
128
- {
129
- case JsonTokenType . EndArray :
130
- return new FieldDescriptor { Type = type , Offset = offset } ;
131
- case JsonTokenType . Comment :
132
- // don't incrment eltIdx
133
- continue ;
134
- default :
135
- break ;
136
- }
137
- switch ( eltIdx )
196
+ // case 4: [[number], string]
197
+ // ^ we're here
198
+ reader . Read ( ) ; // number
199
+ if ( ! GetUInt64FromToken ( ref reader , out ulong value ) )
200
+ throw new JsonException ( ) ;
201
+ reader . Read ( ) ; // end of inner array
202
+ if ( reader . TokenType != JsonTokenType . EndArray )
203
+ throw new JsonException ( ) ;
204
+ reader . Read ( ) ; // string
205
+ if ( reader . TokenType != JsonTokenType . String )
206
+ throw new JsonException ( ) ;
207
+ string ? type = reader . GetString ( ) ;
208
+ reader . Read ( ) ; // end of outer array
209
+ if ( reader . TokenType != JsonTokenType . EndArray )
210
+ throw new JsonException ( ) ;
211
+ return new GlobalDescriptor { Type = type , Value = value , Indirect = true } ;
212
+ }
213
+ else
214
+ {
215
+ // case 2 or 3
216
+ // case 2: [number]
217
+ // ^ we're here
218
+ // case 3: [number, string]
219
+ // ^ we're here
220
+ if ( ! GetUInt64FromToken ( ref reader , out ulong valueCase2or3 ) )
221
+ throw new JsonException ( ) ;
222
+ reader . Read ( ) ; // end of array (case 2) or string (case 3)
223
+ if ( reader . TokenType == JsonTokenType . EndArray ) // it was case 2
224
+ return new GlobalDescriptor { Value = valueCase2or3 , Indirect = true } ;
225
+ else if ( reader . TokenType == JsonTokenType . String ) // it was case 3
138
226
{
139
- case 0 :
140
- {
141
- // expect an offset - either a string or a number token
142
- if ( reader . TokenType == JsonTokenType . Number || reader . TokenType == JsonTokenType . String )
143
- offset = reader . GetInt32 ( ) ;
144
- else
145
- throw new JsonException ( ) ;
146
- break ;
147
- }
148
- case 1 :
149
- {
150
- // expect a type - a string token
151
- if ( reader . TokenType == JsonTokenType . String )
152
- type = reader . GetString ( ) ;
153
- else
154
- throw new JsonException ( ) ;
155
- break ;
156
- }
157
- default :
158
- // too many elements
227
+ string ? type = reader . GetString ( ) ;
228
+ reader . Read ( ) ; // end of array for case 3
229
+ if ( reader . TokenType != JsonTokenType . EndArray )
159
230
throw new JsonException ( ) ;
231
+ return new GlobalDescriptor { Type = type , Value = valueCase2or3 } ;
160
232
}
161
- eltIdx ++ ;
233
+ else
234
+ throw new JsonException ( ) ;
162
235
}
163
- throw new JsonException ( ) ;
164
236
}
165
237
166
- public override void Write ( Utf8JsonWriter writer , FieldDescriptor value , JsonSerializerOptions options )
238
+ public override void Write ( Utf8JsonWriter writer , GlobalDescriptor value , JsonSerializerOptions options )
167
239
{
168
240
throw new NotImplementedException ( ) ;
169
241
}
170
242
}
171
243
244
+ // Somewhat flexible parsing of numbers, allowing json number tokens or strings as decimal or hex, possibly negatated.
245
+ private static bool GetUInt64FromToken ( ref Utf8JsonReader reader , out ulong value )
246
+ {
247
+ if ( reader . TokenType == JsonTokenType . Number )
248
+ {
249
+ if ( reader . TryGetUInt64 ( out value ) )
250
+ return true ;
251
+ else if ( reader . TryGetInt64 ( out long signedValue ) )
252
+ {
253
+ value = ( ulong ) signedValue ;
254
+ return true ;
255
+ }
256
+ }
257
+ if ( reader . TokenType == JsonTokenType . String )
258
+ {
259
+ var s = reader . GetString ( ) ;
260
+ if ( s == null )
261
+ {
262
+ value = 0u ;
263
+ return false ;
264
+ }
265
+ if ( ulong . TryParse ( s , out value ) )
266
+ return true ;
267
+ if ( long . TryParse ( s , out long signedValue ) )
268
+ {
269
+ value = ( ulong ) signedValue ;
270
+ return true ;
271
+ }
272
+ if ( ( s . StartsWith ( "0x" ) || s . StartsWith ( "0X" ) ) &&
273
+ ulong . TryParse ( s . AsSpan ( 2 ) , System . Globalization . NumberStyles . HexNumber , null , out value ) )
274
+ return true ;
275
+ if ( ( s . StartsWith ( "-0x" ) || s . StartsWith ( "-0X" ) ) &&
276
+ ulong . TryParse ( s . AsSpan ( 3 ) , System . Globalization . NumberStyles . HexNumber , null , out ulong negValue ) )
277
+ {
278
+ value = ~ negValue + 1 ; // twos complement
279
+ return true ;
280
+ }
281
+ }
282
+ value = 0 ;
283
+ return false ;
284
+ }
285
+
286
+ // Somewhat flexible parsing of numbers, allowing json number tokens or strings as either decimal or hex, possibly negated
287
+ private static bool GetInt32FromToken ( ref Utf8JsonReader reader , out int value )
288
+ {
289
+ if ( reader . TokenType == JsonTokenType . Number )
290
+ {
291
+ value = reader . GetInt32 ( ) ;
292
+ return true ;
293
+ }
294
+ if ( reader . TokenType == JsonTokenType . String )
295
+ {
296
+ var s = reader . GetString ( ) ;
297
+ if ( s == null )
298
+ {
299
+ value = 0 ;
300
+ return false ;
301
+ }
302
+ if ( int . TryParse ( s , out value ) )
303
+ return true ;
304
+ if ( ( s . StartsWith ( "0x" ) || s . StartsWith ( "0X" ) ) &&
305
+ int . TryParse ( s . AsSpan ( 2 ) , System . Globalization . NumberStyles . HexNumber , null , out value ) )
306
+ return true ;
307
+ if ( ( s . StartsWith ( "-0x" ) || s . StartsWith ( "-0X" ) ) &&
308
+ int . TryParse ( s . AsSpan ( 3 ) , System . Globalization . NumberStyles . HexNumber , null , out int negValue ) )
309
+ {
310
+ value = - negValue ;
311
+ return true ;
312
+ }
313
+ }
314
+ value = 0 ;
315
+ return false ;
316
+ }
317
+
172
318
}
0 commit comments