@@ -12,19 +12,27 @@ public partial class ContractDescriptorParser
1212{
1313 public const string TypeDescriptorSizeSigil = "!" ;
1414
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 )
1622 {
17- return JsonSerializer . Deserialize ( json , ContractDescriptorContext . Default . CompactContractDescriptor ) ;
23+ return JsonSerializer . Deserialize ( json , ContractDescriptorContext . Default . ContractDescriptor ) ;
1824 }
1925
20- [ JsonSerializable ( typeof ( CompactContractDescriptor ) ) ]
26+ [ JsonSerializable ( typeof ( ContractDescriptor ) ) ]
2127 [ JsonSerializable ( typeof ( int ) ) ]
2228 [ JsonSerializable ( typeof ( string ) ) ]
2329 [ JsonSerializable ( typeof ( Dictionary < string , int > ) ) ]
2430 [ JsonSerializable ( typeof ( Dictionary < string , TypeDescriptor > ) ) ]
2531 [ JsonSerializable ( typeof ( Dictionary < string , FieldDescriptor > ) ) ]
32+ [ JsonSerializable ( typeof ( Dictionary < string , GlobalDescriptor > ) ) ]
2633 [ JsonSerializable ( typeof ( TypeDescriptor ) ) ]
2734 [ JsonSerializable ( typeof ( FieldDescriptor ) ) ]
35+ [ JsonSerializable ( typeof ( GlobalDescriptor ) ) ]
2836 [ JsonSourceGenerationOptions ( AllowTrailingCommas = true ,
2937 DictionaryKeyPolicy = JsonKnownNamingPolicy . Unspecified , // contracts, types and globals are case sensitive
3038 PropertyNamingPolicy = JsonKnownNamingPolicy . CamelCase ,
@@ -34,15 +42,15 @@ internal sealed partial class ContractDescriptorContext : JsonSerializerContext
3442 {
3543 }
3644
37- public class CompactContractDescriptor
45+ public class ContractDescriptor
3846 {
3947 public int ? Version { get ; set ; }
4048 public string ? Baseline { get ; set ; }
4149 public Dictionary < string , int > ? Contracts { get ; set ; }
4250
4351 public Dictionary < string , TypeDescriptor > ? Types { get ; set ; }
4452
45- // TODO: globals
53+ public Dictionary < string , GlobalDescriptor > ? Globals { get ; set ; }
4654
4755 [ JsonExtensionData ]
4856 public Dictionary < string , object ? > ? Extras { get ; set ; }
@@ -51,25 +59,35 @@ public class CompactContractDescriptor
5159 [ JsonConverter ( typeof ( TypeDescriptorConverter ) ) ]
5260 public class TypeDescriptor
5361 {
54- public uint Size { get ; set ; }
62+ public uint ? Size { get ; set ; }
5563 public Dictionary < string , FieldDescriptor > ? Fields { get ; set ; }
5664 }
5765
58- // TODO: compact format needs a custom converter
5966 [ JsonConverter ( typeof ( FieldDescriptorConverter ) ) ]
6067 public class FieldDescriptor
6168 {
6269 public string ? Type { get ; set ; }
6370 public int Offset { get ; set ; }
6471 }
6572
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+
6681 internal sealed class TypeDescriptorConverter : JsonConverter < TypeDescriptor >
6782 {
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
6886 public override TypeDescriptor Read ( ref Utf8JsonReader reader , Type typeToConvert , JsonSerializerOptions options )
6987 {
7088 if ( reader . TokenType != JsonTokenType . StartObject )
7189 throw new JsonException ( ) ;
72- uint size = 0 ;
90+ uint ? size = null ;
7391 Dictionary < string , FieldDescriptor > ? fields = new ( ) ;
7492 while ( reader . Read ( ) )
7593 {
@@ -97,6 +115,7 @@ public override TypeDescriptor Read(ref Utf8JsonReader reader, Type typeToConver
97115 }
98116 break ;
99117 case JsonTokenType . Comment :
118+ // unexpected - we specified to skip comments. but let's ignore anyway
100119 break ;
101120 default :
102121 throw new JsonException ( ) ;
@@ -113,60 +132,187 @@ public override void Write(Utf8JsonWriter writer, TypeDescriptor value, JsonSeri
113132
114133 internal sealed class FieldDescriptorConverter : JsonConverter < FieldDescriptor >
115134 {
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
116138 public override FieldDescriptor Read ( ref Utf8JsonReader reader , Type typeToConvert , JsonSerializerOptions options )
117139 {
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 } ;
120142 if ( reader . TokenType != JsonTokenType . StartArray )
121143 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 )
126195 {
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
138226 {
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 )
159230 throw new JsonException ( ) ;
231+ return new GlobalDescriptor { Type = type , Value = valueCase2or3 } ;
160232 }
161- eltIdx ++ ;
233+ else
234+ throw new JsonException ( ) ;
162235 }
163- throw new JsonException ( ) ;
164236 }
165237
166- public override void Write ( Utf8JsonWriter writer , FieldDescriptor value , JsonSerializerOptions options )
238+ public override void Write ( Utf8JsonWriter writer , GlobalDescriptor value , JsonSerializerOptions options )
167239 {
168240 throw new NotImplementedException ( ) ;
169241 }
170242 }
171243
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+
172318}
0 commit comments