Skip to content

Commit 6e583da

Browse files
committed
more parsing
1 parent 10c58f5 commit 6e583da

File tree

2 files changed

+302
-55
lines changed

2 files changed

+302
-55
lines changed

src/native/managed/cdacreader/src/ContractDescriptorParser.cs

Lines changed: 194 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)