diff --git a/BinaryWizard.Sample/SampleEntity.cs b/BinaryWizard.Sample/SampleEntity.cs
index f8e3f16..dcd9ea5 100644
--- a/BinaryWizard.Sample/SampleEntity.cs
+++ b/BinaryWizard.Sample/SampleEntity.cs
@@ -18,17 +18,18 @@ namespace BinaryWizard.Sample;
[BinarySerializable]
public partial class SampleEntity {
- public int VectorCount;
+ public int IntegersCount;
- [BinaryArray(Size = 2)] public SampleVector[] ConstantSizeVectors;
-
- [BinaryArray(SizeMember = nameof(VectorCount))]
- public SampleVector[] DynamicSizeVectors;
+ // [BinaryArray(Size = 2)] public int[] ConstantInts;
+ // [BinaryArray(Size = 2)] public SampleVector[] ConstantSizeVectors;
+ //
+ [BinaryArray(SizeMember = nameof(IntegersCount))]
+ public int[] DynamicSizeVectors;
}
-[BinarySerializable]
-public partial class SampleVector {
- public int X;
- public int Y;
- public int Z;
-}
\ No newline at end of file
+// [BinarySerializable]
+// public partial class SampleVector {
+// public int X;
+// public int Y;
+// public int Z;
+// }
\ No newline at end of file
diff --git a/BinaryWizard.Tests/Samples/Arrays.cs b/BinaryWizard.Tests/Samples/Arrays.cs
new file mode 100644
index 0000000..cd14b50
--- /dev/null
+++ b/BinaryWizard.Tests/Samples/Arrays.cs
@@ -0,0 +1,10 @@
+namespace BinaryWizard.Tests.Samples;
+
+[BinarySerializable]
+public partial struct Arrays {
+ [BinaryArray(Size = 16)] public int[] NumberArray;
+ public int OtherNumbersSize;
+
+ [BinaryArray(SizeMember = nameof(OtherNumbersSize))]
+ public int[] OtherNumbers;
+}
\ No newline at end of file
diff --git a/BinaryWizard.Tests/Samples/Entity.cs b/BinaryWizard.Tests/Samples/Entity.cs
index add2809..f0bb797 100644
--- a/BinaryWizard.Tests/Samples/Entity.cs
+++ b/BinaryWizard.Tests/Samples/Entity.cs
@@ -1,8 +1,8 @@
-namespace BinaryWizard.Tests.Samples;
-
-[BinarySerializable]
-public partial struct Entity {
- public int Id;
- public string Name;
- public Vector3 Position;
-}
\ No newline at end of file
+// namespace BinaryWizard.Tests.Samples;
+//
+// [BinarySerializable]
+// public partial struct Entity {
+// public int Id;
+// public string Name;
+// public Vector3 Position;
+// }
\ No newline at end of file
diff --git a/BinaryWizard.Tests/SerializerTests.cs b/BinaryWizard.Tests/SerializerTests.cs
index dcbfe7d..422a8fa 100644
--- a/BinaryWizard.Tests/SerializerTests.cs
+++ b/BinaryWizard.Tests/SerializerTests.cs
@@ -34,31 +34,63 @@ public void Vector3_CorrectlySerialized() {
using var reader = new BinaryReader(stream);
var actual = Vector3.FromBinary(reader);
- var expected = new Vector3 { X = 1, Y = 2, Z = 3 };
+ var expected = new Vector3 {
+ X = 1, Y = 2, Z = 3,
+ };
Assert.Equal(expected, actual);
}
[Fact]
- public void Entity_CorrectlySerialized() {
+ public void Arrays_CorrectlySerialized() {
using var stream = new MemoryStream();
using (var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true)) {
- writer.Write(69); // Id
- writer.Write("glomdom"); // Name
-
- // Position: Vector3
- writer.Write(1); // X
- writer.Write(2); // Y
- writer.Write(3); // Z
+ for (var i = 0; i < 16; i++) {
+ writer.Write(i);
+ }
+
+ writer.Write(4);
+
+ for (var i = 0; i < 4; i++) {
+ writer.Write(i);
+ }
}
stream.Position = 0;
using var reader = new BinaryReader(stream);
+ var actual = Arrays.FromBinary(reader);
+ var expected = new Arrays {
+ NumberArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
+ OtherNumbersSize = 4,
+ OtherNumbers = [0, 1, 2, 3],
+ };
- var actual = Entity.FromBinary(reader);
- var expected = new Entity { Id = 69, Name = "glomdom", Position = new Vector3 { X = 1, Y = 2, Z = 3 } };
-
- Assert.Equal(expected, actual);
+ Assert.Equal(expected.NumberArray, actual.NumberArray);
+ Assert.Equal(expected.OtherNumbersSize, actual.OtherNumbersSize);
+ Assert.Equal(expected.OtherNumbers, actual.OtherNumbers);
}
+
+ // [Fact]
+ // public void Entity_CorrectlySerialized() {
+ // using var stream = new MemoryStream();
+ // using (var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true)) {
+ // writer.Write(69); // Id
+ // writer.Write("glomdom"); // Name
+ //
+ // // Position: Vector3
+ // writer.Write(1); // X
+ // writer.Write(2); // Y
+ // writer.Write(3); // Z
+ // }
+ //
+ // stream.Position = 0;
+ //
+ // using var reader = new BinaryReader(stream);
+ //
+ // var actual = Entity.FromBinary(reader);
+ // var expected = new Entity { Id = 69, Name = "glomdom", Position = new Vector3 { X = 1, Y = 2, Z = 3 } };
+ //
+ // Assert.Equal(expected, actual);
+ // }
}
\ No newline at end of file
diff --git a/BinaryWizard/AnalyzerReleases.Unshipped.md b/BinaryWizard/AnalyzerReleases.Unshipped.md
index 8c4585e..b543e76 100644
--- a/BinaryWizard/AnalyzerReleases.Unshipped.md
+++ b/BinaryWizard/AnalyzerReleases.Unshipped.md
@@ -1,8 +1,8 @@
### New Rules
Rule ID | Category | Severity | Notes
---------|----------|----------|----------
-BW0001 | Usage | Error | Object is not marked with `[BinarySerializable]` inside marked object.
-BW0002 | Usage | Error | Array type does not have a `[BinaryArray]` attribute.
-BW0003 | Usage | Error | Array marked with `[BinaryArray]` is missing argument defining size.
-BW0004 | Usage | Error | Array marked with `[BinaryArray]` has conflicting size arguments.
\ No newline at end of file
+--------|----------|----------|-------------
+BW0001 | Usage | Error | BinaryWizard
+BW0002 | Usage | Error | BinaryWizard
+BW0003 | Usage | Error | BinaryWizard
+BW0004 | Usage | Error | BinaryWizard
\ No newline at end of file
diff --git a/BinaryWizard/BinaryWizard.csproj b/BinaryWizard/BinaryWizard.csproj
index cc8b80c..5dace7e 100644
--- a/BinaryWizard/BinaryWizard.csproj
+++ b/BinaryWizard/BinaryWizard.csproj
@@ -4,7 +4,7 @@
netstandard2.0
false
enable
- latest
+ 13
true
true
diff --git a/BinaryWizard/Context.cs b/BinaryWizard/Context.cs
new file mode 100644
index 0000000..f3c248e
--- /dev/null
+++ b/BinaryWizard/Context.cs
@@ -0,0 +1,5 @@
+namespace BinaryWizard;
+
+public sealed record Context {
+ public int Offset { get; set; }
+}
\ No newline at end of file
diff --git a/BinaryWizard/DebugUtilities.cs b/BinaryWizard/DebugUtilities.cs
new file mode 100644
index 0000000..5e2fb87
--- /dev/null
+++ b/BinaryWizard/DebugUtilities.cs
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2025 glomdom
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Diagnostics;
+using BinaryWizard.Model;
+
+namespace BinaryWizard;
+
+public static class DebugUtilities {
+ [Conditional("DEBUG")]
+ public static void CreatedFieldDef(FieldDef def) {
+ Debug.WriteLine($"Created field definition {def.Name} of {def.TypeModel.Type} ({def.ByteSize} bytes) (dynamic? {def.IsDynamic})");
+ }
+}
\ No newline at end of file
diff --git a/BinaryWizard/Generator.cs b/BinaryWizard/Generator.cs
index c5415fc..4762eec 100644
--- a/BinaryWizard/Generator.cs
+++ b/BinaryWizard/Generator.cs
@@ -17,22 +17,23 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
+using BinaryWizard.Model;
+using BinaryWizard.Segmenting;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
-using System.Diagnostics.CodeAnalysis;
namespace BinaryWizard;
[Generator]
public class Generator : IIncrementalGenerator {
- private SourceProductionContext _spc;
-
private readonly SyntaxTrivia _autogeneratedComment = SyntaxFactory.Comment("// ");
public void Initialize(IncrementalGeneratorInitializationContext context) {
@@ -59,21 +60,17 @@ public void Initialize(IncrementalGeneratorInitializationContext context) {
(s, _) => s is ClassDeclarationSyntax or StructDeclarationSyntax,
(ctx, _) => GetTypeDeclarationForSourceGen(ctx))
.Where(t => t.reportAttributeFound)
- .Select((t, _) => t.Item1);
+ .Select((t, _) => t.syntax);
var sources = context.CompilationProvider.Combine(provider.Collect());
context.RegisterSourceOutput(
sources,
- ((spc, t) => {
- _spc = spc;
-
- GenerateCode(t.Left, t.Right);
- })
+ (spc, t) => { GenerateCode(spc, t.Left, t.Right); }
);
}
- private static (TypeDeclarationSyntax, bool reportAttributeFound) GetTypeDeclarationForSourceGen(GeneratorSyntaxContext context) {
+ private static (TypeDeclarationSyntax syntax, bool reportAttributeFound) GetTypeDeclarationForSourceGen(GeneratorSyntaxContext context) {
var typeDeclaration = (TypeDeclarationSyntax)context.Node;
foreach (var attributeSyntax in typeDeclaration.AttributeLists.SelectMany(list => list.Attributes)) {
@@ -86,15 +83,22 @@ private static (TypeDeclarationSyntax, bool reportAttributeFound) GetTypeDeclara
return (typeDeclaration, false);
}
- private void GenerateCode(Compilation compilation, ImmutableArray classDeclarations) {
+ private void GenerateCode(SourceProductionContext spc, Compilation compilation, ImmutableArray classDeclarations) {
+ Debug.WriteLine("Starting code generation");
+
foreach (var declarationSyntax in classDeclarations) {
+ var context = new Context();
+
var semanticModel = compilation.GetSemanticModel(declarationSyntax.SyntaxTree);
if (ModelExtensions.GetDeclaredSymbol(semanticModel, declarationSyntax) is not INamedTypeSymbol classSymbol) continue;
var namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
var declarationName = declarationSyntax.Identifier.Text;
- var method = CreateReadMethod(semanticModel, classSymbol);
+ var segmentManager = new SegmentManager();
+ var method = CreateReadMethod(spc, context, segmentManager, semanticModel, classSymbol);
+
+ Debug.WriteLine($"Created read method for {classSymbol.Name}");
TypeDeclarationSyntax declaration = declarationSyntax is ClassDeclarationSyntax
? SyntaxFactory.ClassDeclaration(declarationName)
@@ -108,6 +112,11 @@ private void GenerateCode(Compilation compilation, ImmutableArray BuildReadStatements(SemanticModel semantics, INamedTypeSymbol classSymbol, string outputName) {
+ private IEnumerable BuildReadStatements(SourceProductionContext spc,
+ Context ctx,
+ SegmentManager segmentManager,
+ SemanticModel semantics,
+ INamedTypeSymbol classSymbol,
+ string outputName
+ ) {
var fields = classSymbol.GetMembers().OfType();
foreach (var field in fields) {
var fieldType = field.Type;
if (IsPrimitiveLike(fieldType)) {
- var method = GetReadMethodNameForPrimitive(fieldType);
+ var fieldDef = new FieldDef(field.Name, fieldType) {
+ ByteSize = GetByteSizeForPrimitive(fieldType),
+ };
- yield return SyntaxFactory.ParseStatement($"{outputName}.{field.Name} = reader.{method}();");
- } else {
- if (IsArrayLike(fieldType, out var arrSymbol)) {
- if (arrSymbol.Rank != 1) throw new NotSupportedException("Arrays which have more than 1 dimension are not supported.");
+ segmentManager.AddField(fieldDef);
+ DebugUtilities.CreatedFieldDef(fieldDef);
+ } else if (IsArrayLike(fieldType, out var arrSymbol)) {
+ if (arrSymbol.Rank != 1) throw new NotSupportedException("Arrays which have more than 1 dimension are not supported.");
- var binaryArrayAttr = field.GetAttributes().FirstOrDefault(a => a.AttributeClass?.Name == "BinaryArrayAttribute");
- if (binaryArrayAttr is null) {
- ReportArrayIsMissingAttribute(field);
+ var binaryArrayAttr = field.GetAttributes().FirstOrDefault(a => a.AttributeClass?.Name == "BinaryArrayAttribute");
+ if (binaryArrayAttr is null) {
+ ReportArrayIsMissingAttribute(spc, field);
- yield break;
- }
+ yield break;
+ }
- if (!AreAnyNamedArgsProvided(binaryArrayAttr, "Size", "SizeMember")) {
- ReportArrayIsMissingSizeArgument(field);
+ if (!AreAnyNamedArgsProvided(binaryArrayAttr, "Size", "SizeMember")) {
+ ReportArrayIsMissingSizeArgument(spc, field);
- yield break;
- }
+ yield break;
+ }
- if (AreAllNamedArgsProvided(binaryArrayAttr, "Size", "SizeMember")) {
- ReportArrayHasConflictingSizeArguments(field);
+ if (AreAllNamedArgsProvided(binaryArrayAttr, "Size", "SizeMember")) {
+ ReportArrayHasConflictingSizeArguments(spc, field);
- yield break;
- }
+ yield break;
+ }
- if (TryGetNamedArg(binaryArrayAttr, "Size", out var arrSize)) {
- var statements = GetReadStatementsForArrayWithSize(semantics, arrSymbol, outputName, field.Name, (int)arrSize.Value!);
+ var fieldDef = new FieldDef(field.Name, arrSymbol.ElementType) {
+ TypeModel = {
+ InnerType = arrSymbol.ElementType,
+ InnerTypeByteSize = GetByteSizeForPrimitive(arrSymbol.ElementType),
+ },
+ };
+
+ if (TryGetNamedArg(binaryArrayAttr, "Size", out var arrSize)) {
+ var arrSizeValue = (int)arrSize.Value!;
+ fieldDef.ByteSize = arrSizeValue * GetByteSizeForPrimitive(arrSymbol.ElementType);
+ fieldDef.TypeModel.FixedArraySize = arrSizeValue;
+
+ DebugUtilities.CreatedFieldDef(fieldDef);
+ segmentManager.AddField(fieldDef);
+ } else if (TryGetNamedArg(binaryArrayAttr, "SizeMember", out var sizeMember)) {
+ var arrSizeRef = (string)sizeMember.Value!;
+ fieldDef.ByteSize = -1;
+
+ DebugUtilities.CreatedFieldDef(fieldDef);
+ segmentManager.AddField(fieldDef, arrSizeRef);
+ }
+ } else if (HasBinarySerializableAttribute(fieldType)) {
+ // TODO: maybe implement segmenting for nested reading? unsure of how to implement yet
- foreach (var statement in statements) {
- yield return statement;
- }
- }
+ yield return SyntaxFactory.ParseStatement($"{outputName}.{field.Name} = {fieldType.Name}.FromBinary(reader);");
- if (TryGetNamedArg(binaryArrayAttr, "SizeMember", out var sizeMember)) {
- var statements = GetReadStatementsForArrayWithMember(semantics, arrSymbol, outputName, field.Name, (string)sizeMember.Value!);
+ Debug.WriteLine("Built statements for nested object");
+ } else {
+ ReportUnmarkedSerializableForField(spc, field);
- foreach (var statement in statements) {
- yield return statement;
- }
- }
+ yield break;
+ }
+ }
- continue;
- }
+ var segments = segmentManager.Commit();
- if (HasBinarySerializableAttribute(fieldType)) {
- yield return SyntaxFactory.ParseStatement($"{outputName}.{field.Name} = {fieldType.Name}.FromBinary(reader);");
- } else {
- ReportUnmarkedSerializableForField(field);
+ foreach (var seg in segments) {
+ if (seg is FixedSegment fixedSeg) {
+ foreach (var stmt in ProcessFixedSegment(fixedSeg, ctx)) yield return stmt;
+ }
- yield break;
- }
+ if (seg is DynamicSegment dynSeg) {
+ foreach (var stmt in ProcessDynamicSegment(dynSeg, ctx)) yield return stmt;
}
}
+
+ segmentManager.Clear();
}
- private IEnumerable GetReadStatementsForArrayWithMember(SemanticModel semantics,
- IArrayTypeSymbol fieldType,
- string outName,
- string fieldName,
- string memberName
- ) {
- var elemType = fieldType.ElementType;
+ private IEnumerable ProcessDynamicSegment(DynamicSegment seg, Context ctx) {
+ foreach (var field in seg.Fields) {
+ var elementBytes = GetByteSizeForPrimitive(field.TypeModel.InnerType!);
- if (IsPrimitiveLike(elemType)) {
+ yield return SyntaxFactory.ParseStatement($"result.{field.Name} = new {field.TypeModel.Type}[result.{seg.LengthReferenceFieldName}];");
yield return SyntaxFactory.ParseStatement(
- $"for (var i = 0; i < {outName}.{memberName}; i++) {outName}.{fieldName}[i] = reader.{GetReadMethodNameForPrimitive(elemType)}();");
-
- yield break;
+ $"for (var i = 0; i < result.{seg.LengthReferenceFieldName}; i++)" +
+ " " +
+ $"result.{field.Name}[i] = {GetBinaryPrimitiveReaderForPrimitive(field.TypeModel.Type)}(buf.Slice({ctx.Offset} + ({elementBytes} * i), {elementBytes}));"
+ );
+
+ // TODO: cannot add dynamic size to context. maybe be able to dynamically edit the context in the partial function
+ // and be able to change from a fixed/dynamic type.
+ // ctx.Offset += elementBytes * field.TypeModel.FixedArraySize!.Value;
}
+ }
- if (HasBinarySerializableAttribute(elemType)) {
- yield return SyntaxFactory.ParseStatement($"for (var i = 0; i < {outName}.{memberName}; i++) {outName}.{fieldName}[i] = {elemType.Name}.FromBinary(reader);");
+ private IEnumerable ProcessFixedSegment(FixedSegment seg, Context ctx) {
+ yield return SyntaxFactory.ParseStatement($"Span buf = stackalloc byte[{seg.Bytes}];");
+ yield return SyntaxFactory.ParseStatement($"var __bytes_read = reader.Read(buf);");
+ yield return SyntaxFactory.ParseStatement($"if (__bytes_read < {seg.Bytes}) throw new EndOfStreamException();");
- yield break;
- }
+ foreach (var field in seg.Fields) {
+ if (field.TypeModel.IsFixedArray) {
+ var elementBytes = GetByteSizeForPrimitive(field.TypeModel.InnerType!);
- // idk what to do here, still don't know if we can reach this
- throw new NotSupportedException($"Failed to create read statements for `{elemType}`");
- }
+ yield return SyntaxFactory.ParseStatement($"result.{field.Name} = new {field.TypeModel.Type}[{field.TypeModel.FixedArraySize!.Value}];");
+ yield return SyntaxFactory.ParseStatement(
+ $"for (var i = 0; i < {field.TypeModel.FixedArraySize}; i++)" + " " +
+ $"result.{field.Name}[i] = {GetBinaryPrimitiveReaderForPrimitive(field.TypeModel.Type)}(buf.Slice({ctx.Offset} + ({elementBytes} * i), {elementBytes}));"
+ );
- private IEnumerable GetReadStatementsForArrayWithSize(SemanticModel semantics, IArrayTypeSymbol fieldType, string outName, string fieldName, int arrSize) {
- var elemType = fieldType.ElementType;
+ ctx.Offset += elementBytes * field.TypeModel.FixedArraySize!.Value;
- // little optimization for bytes
- if (elemType.SpecialType == SpecialType.System_Byte) {
- yield return SyntaxFactory.ParseStatement($"{outName}.{fieldName} = reader.ReadBytes({arrSize});");
+ continue;
+ }
- yield break;
+ yield return SyntaxFactory.ParseStatement(
+ $"result.{field.Name} = {GetBinaryPrimitiveReaderForPrimitive(field.TypeModel.Type)}(buf.Slice({ctx.Offset}, {field.ByteSize}));"
+ );
+
+ ctx.Offset += field.ByteSize;
}
+ }
+
+ // TODO: Context is required if we want to support endianness.
+ private string GetBinaryPrimitiveReaderForPrimitive(ITypeSymbol sym) {
+ return sym.SpecialType switch {
+ SpecialType.System_Int32 => "BinaryPrimitives.ReadInt32LittleEndian",
+
+ _ => throw new InvalidOperationException(
+ $"Unexpected type {sym.SpecialType} occurred in GetBinaryPrimitiveReaderForPrimitive"
+ ),
+ };
+ }
+
+ private IEnumerable GetReadStatementsForArrayWithMember(
+ IArrayTypeSymbol fieldType,
+ string outName,
+ string fieldName,
+ string memberName
+ ) {
+ Debug.WriteLine("Building statements for array with size variable");
+
+ var elemType = fieldType.ElementType;
if (IsPrimitiveLike(elemType)) {
- yield return SyntaxFactory.ParseStatement($"for (var i = 0; i < {arrSize}; i++) {outName}.{fieldName}[i] = reader.{GetReadMethodNameForPrimitive(elemType)}();");
+ yield return SyntaxFactory.ParseStatement(
+ $"for (var i = 0; i < {outName}.{memberName}; i++) {outName}.{fieldName}[i] = reader.{GetReadMethodNameForPrimitive(elemType)}();");
yield break;
}
if (HasBinarySerializableAttribute(elemType)) {
- yield return SyntaxFactory.ParseStatement($"for (var i = 0; i < {arrSize}; i++) {outName}.{fieldName}[i] = {elemType.Name}.FromBinary(reader);");
+ yield return SyntaxFactory.ParseStatement($"for (var i = 0; i < {outName}.{memberName}; i++) {outName}.{fieldName}[i] = {elemType.Name}.FromBinary(reader);");
yield break;
}
@@ -261,8 +329,27 @@ private static TypedConstant OneArgOfMany(AttributeData data, params string[] ar
private static bool HasBinarySerializableAttribute(ITypeSymbol sym) => HasAttribute(sym, "BinarySerializableAttribute");
private static bool HasAttribute(ITypeSymbol sym, string attrName) => sym.GetAttributes().Any(a => a.AttributeClass?.Name == attrName);
+ private int GetByteSizeForPrimitive(ITypeSymbol primitive) {
+ return primitive.SpecialType switch {
+ SpecialType.System_Boolean => 1,
+ SpecialType.System_Char => 1,
+ SpecialType.System_SByte => 1,
+ SpecialType.System_Byte => 1,
+ SpecialType.System_Int16 => 2,
+ SpecialType.System_UInt16 => 2,
+ SpecialType.System_Int32 => 4,
+ SpecialType.System_UInt32 => 4,
+ SpecialType.System_Int64 => 8,
+ SpecialType.System_UInt64 => 8,
+ SpecialType.System_Decimal => 16,
+ SpecialType.System_Double => 32,
+
+ _ => throw new InvalidOperationException("Unexpected case encountered."),
+ };
+ }
+
private string GetReadMethodNameForPrimitive(ITypeSymbol primitive) {
- return (primitive.SpecialType) switch {
+ return primitive.SpecialType switch {
SpecialType.System_Boolean => "ReadBoolean",
SpecialType.System_Char => "ReadChar",
SpecialType.System_SByte => "ReadSByte",
@@ -271,8 +358,8 @@ private string GetReadMethodNameForPrimitive(ITypeSymbol primitive) {
SpecialType.System_UInt16 => "ReadUInt16",
SpecialType.System_Int32 => "ReadInt32",
SpecialType.System_UInt32 => "ReadUInt32",
- SpecialType.System_Int64 => "ReadInt32",
- SpecialType.System_UInt64 => "ReadUInt62",
+ SpecialType.System_Int64 => "ReadInt64",
+ SpecialType.System_UInt64 => "ReadUInt64",
SpecialType.System_Decimal => "ReadDecimal",
SpecialType.System_Double => "ReadDouble",
SpecialType.System_String => "ReadString",
@@ -346,40 +433,40 @@ out TypedConstant typedConstant
return false;
}
- private void ReportArrayHasConflictingSizeArguments(IFieldSymbol field) {
+ private void ReportArrayHasConflictingSizeArguments(SourceProductionContext spc, IFieldSymbol field) {
var location = GetVariableDeclaratorNameLocation(field);
- _spc.ReportDiagnostic(Diagnostic.Create(
+ spc.ReportDiagnostic(Diagnostic.Create(
Diagnostics.ArrayHasConflictingSizeArguments,
location,
field.Name
));
}
- private void ReportArrayIsMissingSizeArgument(IFieldSymbol field) {
+ private void ReportArrayIsMissingSizeArgument(SourceProductionContext spc, IFieldSymbol field) {
var location = GetVariableDeclaratorNameLocation(field);
- _spc.ReportDiagnostic(Diagnostic.Create(
+ spc.ReportDiagnostic(Diagnostic.Create(
Diagnostics.MarkedArraylikeHasNoSizeOrSizeProviderRule,
location,
field.Name
));
}
- private void ReportArrayIsMissingAttribute(IFieldSymbol field) {
+ private void ReportArrayIsMissingAttribute(SourceProductionContext spc, IFieldSymbol field) {
var location = GetVariableDeclaratorNameLocation(field);
- _spc.ReportDiagnostic(Diagnostic.Create(
+ spc.ReportDiagnostic(Diagnostic.Create(
Diagnostics.ArrayHasNoBinaryArrayAttributeRule,
location,
field.Name
));
}
- private void ReportUnmarkedSerializableForField(IFieldSymbol field) {
+ private void ReportUnmarkedSerializableForField(SourceProductionContext spc, IFieldSymbol field) {
var location = GetVariableDeclaratorLocation(field);
- _spc.ReportDiagnostic(Diagnostic.Create(
+ spc.ReportDiagnostic(Diagnostic.Create(
Diagnostics.MissingBinarySerializableAttributeRule,
location,
field.Type.Name
diff --git a/BinaryWizard/Model/FieldDef.cs b/BinaryWizard/Model/FieldDef.cs
new file mode 100644
index 0000000..c63f6bf
--- /dev/null
+++ b/BinaryWizard/Model/FieldDef.cs
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2025 glomdom
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using Microsoft.CodeAnalysis;
+
+namespace BinaryWizard.Model;
+
+public sealed record FieldDef {
+ public string Name { get; set; }
+ public int ByteSize { get; set; }
+ public TypeModel TypeModel { get; set; }
+
+ public bool IsDynamic => ByteSize == -1;
+
+ public FieldDef(string name, ITypeSymbol type) {
+ Name = name;
+ TypeModel = new TypeModel(type);
+ }
+}
\ No newline at end of file
diff --git a/BinaryWizard/Model/TypeModel.cs b/BinaryWizard/Model/TypeModel.cs
new file mode 100644
index 0000000..30bade9
--- /dev/null
+++ b/BinaryWizard/Model/TypeModel.cs
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2025 glomdom
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using Microsoft.CodeAnalysis;
+
+namespace BinaryWizard.Model;
+
+public sealed record TypeModel {
+ public ITypeSymbol Type { get; set; }
+
+ public ITypeSymbol? InnerType { get; set; }
+ public int? FixedArraySize { get; set; }
+ public int? InnerTypeByteSize { get; set; }
+ public bool IsFixedArray => FixedArraySize is not null && InnerType is not null;
+ public bool IsDynamicArray => FixedArraySize is null && InnerType is not null;
+
+ public TypeModel(ITypeSymbol type, int? fixedArraySize = null) {
+ Type = type;
+ FixedArraySize = fixedArraySize;
+ }
+}
\ No newline at end of file
diff --git a/BinaryWizard/Segmenting/DynamicSegment.cs b/BinaryWizard/Segmenting/DynamicSegment.cs
new file mode 100644
index 0000000..82f8dce
--- /dev/null
+++ b/BinaryWizard/Segmenting/DynamicSegment.cs
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2025 glomdom
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Collections.Generic;
+using BinaryWizard.Model;
+
+namespace BinaryWizard.Segmenting;
+
+public record DynamicSegment : Segment {
+ public IReadOnlyList Fields { get; set; }
+ public string LengthReferenceFieldName { get; }
+
+ public DynamicSegment(List fields, string lengthRef) {
+ Fields = fields;
+ LengthReferenceFieldName = lengthRef;
+ }
+}
\ No newline at end of file
diff --git a/BinaryWizard/Segmenting/FixedSegment.cs b/BinaryWizard/Segmenting/FixedSegment.cs
new file mode 100644
index 0000000..3f9721d
--- /dev/null
+++ b/BinaryWizard/Segmenting/FixedSegment.cs
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2025 glomdom
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Collections.Generic;
+using BinaryWizard.Model;
+
+namespace BinaryWizard.Segmenting;
+
+public record FixedSegment : Segment {
+ public IReadOnlyList Fields { get; set; }
+ public int Bytes { get; set; }
+
+ public FixedSegment(List fields, int bytes) {
+ Fields = fields;
+ Bytes = bytes;
+ }
+}
\ No newline at end of file
diff --git a/BinaryWizard/Segmenting/Segment.cs b/BinaryWizard/Segmenting/Segment.cs
new file mode 100644
index 0000000..38ca2df
--- /dev/null
+++ b/BinaryWizard/Segmenting/Segment.cs
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2025 glomdom
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace BinaryWizard.Segmenting;
+
+public abstract record Segment;
\ No newline at end of file
diff --git a/BinaryWizard/Segmenting/SegmentManager.cs b/BinaryWizard/Segmenting/SegmentManager.cs
new file mode 100644
index 0000000..82dcbbe
--- /dev/null
+++ b/BinaryWizard/Segmenting/SegmentManager.cs
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2025 glomdom
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using BinaryWizard.Model;
+
+namespace BinaryWizard.Segmenting;
+
+sealed internal class SegmentManager {
+ private readonly List _segments = [];
+ private readonly List _currentFields = [];
+ private int _currentFixedSize;
+
+ public void AddField(FieldDef field, string? lengthRef = null) {
+ if (field.IsDynamic) {
+ if (string.IsNullOrEmpty(lengthRef)) throw new ArgumentNullException(nameof(lengthRef), "Length reference was not provided when field is dynamic");
+
+ CommitFixed();
+
+ _segments.Add(new DynamicSegment([field], lengthRef!));
+
+ Debug.WriteLine($"Added dynamic segment for {field.Name} (dep. {lengthRef})");
+ } else {
+ _currentFields.Add(field);
+
+ var size = field.TypeModel.IsFixedArray
+ ? field.TypeModel.FixedArraySize!.Value * field.TypeModel.InnerTypeByteSize!.Value
+ : field.ByteSize;
+
+ _currentFixedSize += size;
+ }
+ }
+
+ public IReadOnlyList Commit() {
+ CommitFixed();
+
+ Debug.WriteLine($"Finalized segmenting with {_segments.Count} segments");
+
+ return _segments;
+ }
+
+ private void CommitFixed() {
+ if (_currentFields.Count == 0) {
+ return;
+ }
+
+ Debug.WriteLine($"Committing fixed segment with {_currentFields.Count} segments and size of {_currentFixedSize} bytes");
+
+ _segments.Add(new FixedSegment(_currentFields.ToList(), _currentFixedSize));
+ _currentFields.Clear();
+ _currentFixedSize = 0;
+ }
+
+ public void Clear() {
+ _segments.Clear();
+ _currentFields.Clear();
+ _currentFixedSize = 0;
+ }
+}
\ No newline at end of file