diff --git a/interface/Device.tt b/interface/Device.tt index 4f6b07c..fbea772 100644 --- a/interface/Device.tt +++ b/interface/Device.tt @@ -464,9 +464,10 @@ if (isPrivate) if (member.Value.HasConverter) { var memberType = TemplateHelper.GetInterfaceType(member.Value, register.Type); + var converterInterfaceType = member.Value.GetConverterInterfaceType(register.Type); #> - private static partial <#= memberType #> ParsePayload<#= member.Key #>(<#= register.PayloadInterfaceType #> payload<#= member.Key #>); + private static partial <#= memberType #> ParsePayload<#= member.Key #>(<#= converterInterfaceType #> payload<#= member.Key #>); <# } } @@ -482,7 +483,8 @@ if (isPrivate) member.Key, member.Value, "payload", - register.Type); + register, + deviceMetadata); #> result.<#= member.Key #> = <#= memberConversion #>; <# @@ -532,16 +534,16 @@ if (isPrivate) foreach (var member in register.PayloadSpec) { var payloadIndex = member.Value.Offset.GetValueOrDefault(0); - var memberIndexer = member.Value.Offset.HasValue ? $"[{member.Value.Offset}]" : string.Empty; - var memberConversion = TemplateHelper.GetPayloadMemberFormatter( + var memberAssignment = TemplateHelper.GetPayloadMemberAssignmentFormatter( member.Key, member.Value, - $"value.{member.Key}", - register.Type, + "result", + register, + deviceMetadata, assigned[payloadIndex]); assigned[payloadIndex] = true; #> - result<#= memberIndexer #><#= memberConversion #>; + <#= memberAssignment #>; <# } #> @@ -1002,4 +1004,100 @@ foreach (var groupMask in deviceMetadata.GroupMasks) <# } #> + + internal static partial class PayloadExtensions + { + internal static T[] GetSubArray(this T[] array, int offset, int count) + { + var result = new T[count]; + Array.Copy(array, offset, result, 0, count); + return result; + } + + internal static byte ToByte(this ArraySegment segment) => segment.Array[segment.Offset]; + + internal static sbyte ToSByte(this ArraySegment segment) => (sbyte)segment.Array[segment.Offset]; + + internal static ushort ToUInt16(this ArraySegment segment) => BitConverter.ToUInt16(segment.Array, segment.Offset); + + internal static short ToInt16(this ArraySegment segment) => BitConverter.ToInt16(segment.Array, segment.Offset); + + internal static uint ToUInt32(this ArraySegment segment) => BitConverter.ToUInt32(segment.Array, segment.Offset); + + internal static int ToInt32(this ArraySegment segment) => BitConverter.ToInt32(segment.Array, segment.Offset); + + internal static ulong ToUInt64(this ArraySegment segment) => BitConverter.ToUInt64(segment.Array, segment.Offset); + + internal static long ToInt64(this ArraySegment segment) => BitConverter.ToInt64(segment.Array, segment.Offset); + + internal static float ToSingle(this ArraySegment segment) => BitConverter.ToSingle(segment.Array, segment.Offset); + + internal static string ToUTF8String(this ArraySegment segment) => System.Text.Encoding.UTF8.GetString(segment.Array, segment.Offset, segment.Count); + + internal static void WriteBytes(this ArraySegment segment, byte value) => segment.Array[segment.Offset] = value; + + internal static void WriteBytes(this ArraySegment segment, sbyte value) => segment.Array[segment.Offset] = (byte)value; + + internal static void WriteBytes(this ArraySegment segment, ushort value) + { + segment.Array[segment.Offset] = (byte)value; + segment.Array[segment.Offset + 1] = (byte)(value >> 8); + } + + internal static void WriteBytes(this ArraySegment segment, short value) + { + segment.Array[segment.Offset] = (byte)value; + segment.Array[segment.Offset + 1] = (byte)(value >> 8); + } + + internal static void WriteBytes(this ArraySegment segment, uint value) + { + segment.Array[segment.Offset] = (byte)value; + segment.Array[segment.Offset + 1] = (byte)(value >> 8); + segment.Array[segment.Offset + 2] = (byte)(value >> 16); + segment.Array[segment.Offset + 3] = (byte)(value >> 24); + } + + internal static void WriteBytes(this ArraySegment segment, int value) + { + segment.Array[segment.Offset] = (byte)value; + segment.Array[segment.Offset + 1] = (byte)(value >> 8); + segment.Array[segment.Offset + 2] = (byte)(value >> 16); + segment.Array[segment.Offset + 3] = (byte)(value >> 24); + } + + internal static void WriteBytes(this ArraySegment segment, ulong value) + { + segment.Array[segment.Offset] = (byte)value; + segment.Array[segment.Offset + 1] = (byte)(value >> 8); + segment.Array[segment.Offset + 2] = (byte)(value >> 16); + segment.Array[segment.Offset + 3] = (byte)(value >> 24); + segment.Array[segment.Offset + 4] = (byte)(value >> 32); + segment.Array[segment.Offset + 5] = (byte)(value >> 40); + segment.Array[segment.Offset + 6] = (byte)(value >> 48); + segment.Array[segment.Offset + 7] = (byte)(value >> 56); + } + + internal static void WriteBytes(this ArraySegment segment, long value) + { + segment.Array[segment.Offset] = (byte)value; + segment.Array[segment.Offset + 1] = (byte)(value >> 8); + segment.Array[segment.Offset + 2] = (byte)(value >> 16); + segment.Array[segment.Offset + 3] = (byte)(value >> 24); + segment.Array[segment.Offset + 4] = (byte)(value >> 32); + segment.Array[segment.Offset + 5] = (byte)(value >> 40); + segment.Array[segment.Offset + 6] = (byte)(value >> 48); + segment.Array[segment.Offset + 7] = (byte)(value >> 56); + } + + internal static unsafe void WriteBytes(this ArraySegment segment, float value) => WriteBytes(segment, *(int*)&value); + + internal static unsafe void WriteBytes(this ArraySegment segment, string value) => + System.Text.Encoding.UTF8.GetBytes(value, 0, segment.Count, segment.Array, segment.Offset); + + internal static void WriteBytes(this ArraySegment segment, T[] values) where T : unmanaged + { + Buffer.BlockCopy(values, 0, segment.Array, segment.Offset, segment.Count); + } + } } diff --git a/interface/Interface.tt b/interface/Interface.tt index f7bf2e8..dce1a34 100644 --- a/interface/Interface.tt +++ b/interface/Interface.tt @@ -70,6 +70,7 @@ public class PayloadMemberInfo { public int? Mask; public int? Offset; + public int? Length; public string MaskType; public string InterfaceType; public MemberConverter Converter; @@ -79,6 +80,17 @@ public class PayloadMemberInfo public float? DefaultValue; public bool HasConverter => Converter > MemberConverter.None; + public string GetConverterInterfaceType(PayloadType payloadType) + { + return Converter switch + { + MemberConverter.RawPayload => "ArraySegment", + MemberConverter.Payload => Length.GetValueOrDefault() > 0 + ? $"ArraySegment<{TemplateHelper.GetInterfaceType(payloadType, 0)}>" + : TemplateHelper.GetInterfaceType(payloadType, 0), + MemberConverter.None => TemplateHelper.GetInterfaceType(payloadType, Length.GetValueOrDefault()) + }; + } } public class BitMaskInfo @@ -131,7 +143,7 @@ public static partial class TemplateHelper { if (!string.IsNullOrEmpty(member.InterfaceType)) return member.InterfaceType; else if (!string.IsNullOrEmpty(member.MaskType)) return member.MaskType; - else return GetInterfaceType(payloadType); + else return GetInterfaceType(payloadType, member.Length.GetValueOrDefault()); } public static string GetInterfaceType(PayloadType payloadType) @@ -166,6 +178,26 @@ public static partial class TemplateHelper else return "uint"; } + public static bool GetInterfaceTypeSize(string interfaceType, out PayloadType payloadType, out int size) + { + payloadType = interfaceType switch + { + "byte" => PayloadType.U8, + "sbyte" => PayloadType.S8, + "ushort" => PayloadType.U16, + "short" => PayloadType.S16, + "uint" => PayloadType.U32, + "int" => PayloadType.S32, + "ulong" => PayloadType.U64, + "long" => PayloadType.S64, + "float" => PayloadType.Float, + _ => 0 + }; + + size = GetPayloadTypeSize(payloadType); + return payloadType > 0; + } + public static string GetPayloadTypeSuffix(PayloadType payloadType, int payloadLength = 0) { if (payloadLength > 0) @@ -189,6 +221,11 @@ public static partial class TemplateHelper } } + static int GetPayloadTypeSize(PayloadType payloadType) + { + return (int)payloadType & 0xF; + } + public static string GetRangeAttributeDeclaration(float? minValue, float? maxValue) { var minValueDeclaration = minValue.HasValue ? minValue.Value.ToString() : "long.MinValue"; @@ -244,15 +281,63 @@ public static partial class TemplateHelper return (int)Math.Log(lsb, 2); } + static int GetMemberSize( + PayloadMemberInfo member, + RegisterInfo register, + DeviceInfo deviceMetadata, + out PayloadType payloadType) + { + var interfaceType = GetInterfaceType(member, register.Type); + if (deviceMetadata.GroupMasks.TryGetValue(interfaceType, out GroupMaskInfo groupMask)) + interfaceType = groupMask.InterfaceType; + else if (deviceMetadata.BitMasks.TryGetValue(interfaceType, out BitMaskInfo bitMask)) + interfaceType = bitMask.InterfaceType; + + if (GetInterfaceTypeSize(interfaceType, out payloadType, out int size)) + return size; + else + return GetPayloadTypeSize(register.Type); + } + public static string GetPayloadMemberParser( string name, PayloadMemberInfo member, string expression, - PayloadType payloadType) + RegisterInfo register, + DeviceInfo deviceMetadata) { - if (member.Offset.HasValue) + var payloadType = register.Type; + var memberLength = member.Length.GetValueOrDefault(); + var memberOffset = member.Offset.GetValueOrDefault(); + var payloadInterfaceType = GetInterfaceType(payloadType); + if (memberLength > 0 && member.InterfaceType != "bool") + { + if (member.Converter == MemberConverter.RawPayload) + throw new NotSupportedException("Raw payload converters inside payload spec is not currently supported."); + + var memberInterfaceType = GetInterfaceType(member, register.Type); + var memberSize = GetMemberSize(member, register, deviceMetadata, out PayloadType memberPayloadType); + if (member.InterfaceType == "string") + return $"new ArraySegment<{payloadInterfaceType}>({expression}, {memberOffset}, {memberLength}).ToUTF8String()"; + else if (member.Converter == MemberConverter.Payload || memberInterfaceType != GetInterfaceType(payloadType, register.Length)) + { + expression = $"new ArraySegment<{payloadInterfaceType}>({expression}, {memberOffset}, {memberLength})"; + if (memberPayloadType > 0) + { + expression = $"{expression}.To{GetPayloadTypeSuffix(memberPayloadType)}()"; + return GetConversionToInterfaceType(member.MaskType, expression); + } + else + { + return $"{expression}.To{member.InterfaceType}()"; + } + } + else + expression = $"{expression}.GetSubArray({memberOffset}, {memberLength})"; + } + else if (member.Offset.HasValue) { - expression = $"{expression}[{member.Offset.Value}]"; + expression = $"{expression}[{member.Offset.GetValueOrDefault()}]"; } if (member.Mask.HasValue) { @@ -266,7 +351,6 @@ public static partial class TemplateHelper expression = $"({expression} >> {shift})"; } - var payloadInterfaceType = GetInterfaceType(payloadType); expression = $"({payloadInterfaceType}){expression}"; } } @@ -278,7 +362,44 @@ public static partial class TemplateHelper return GetConversionToInterfaceType(member.InterfaceType ?? member.MaskType, expression); } - public static string GetPayloadMemberFormatter( + public static string GetPayloadMemberAssignmentFormatter( + string name, + PayloadMemberInfo member, + string expression, + RegisterInfo register, + DeviceInfo deviceMetadata, + bool assigned) + { + var payloadType = register.Type; + var memberLength = member.Length.GetValueOrDefault(); + var memberOffset = member.Offset.GetValueOrDefault(); + var payloadInterfaceType = GetInterfaceType(payloadType); + if (memberLength > 0 && member.InterfaceType != "bool") + { + if (member.Converter == MemberConverter.RawPayload) + throw new NotSupportedException("Raw payload converters inside payload spec is not currently supported."); + + var memberSize = GetMemberSize(member, register, deviceMetadata, out PayloadType memberPayloadType); + expression = $"new ArraySegment<{payloadInterfaceType}>({expression}, {memberOffset}, {memberLength})"; + var valueExpression = $"value.{name}"; + if (!string.IsNullOrEmpty(member.MaskType) && memberPayloadType > 0) + valueExpression = GetConversionToInterfaceType(GetInterfaceType(memberPayloadType), valueExpression); + expression = $"{expression}.WriteBytes({valueExpression})"; + return expression; + } + + var memberIndexer = member.Offset.HasValue ? $"[{memberOffset}]" : string.Empty; + var memberConversion = TemplateHelper.GetPayloadMemberValueFormatter( + name, + member, + $"value.{name}", + payloadType, + assigned); + return $"{expression}{memberIndexer}{memberConversion}"; + } + + + public static string GetPayloadMemberValueFormatter( string name, PayloadMemberInfo member, string expression, diff --git a/tests/CompilerTestHelper.cs b/tests/CompilerTestHelper.cs index cd1749d..7b16e4d 100644 --- a/tests/CompilerTestHelper.cs +++ b/tests/CompilerTestHelper.cs @@ -26,7 +26,7 @@ public static void CompileFromSource(params string[] code) nameof(CompilerTestHelper), syntaxTrees: syntaxTrees, references: assemblyReferences, - options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true)); using var memoryStream = new MemoryStream(); var result = compilation.Emit(memoryStream); if (!result.Success) diff --git a/tests/GeneratorTests.cs b/tests/GeneratorTests.cs index 547eb51..1f3bb81 100644 --- a/tests/GeneratorTests.cs +++ b/tests/GeneratorTests.cs @@ -8,6 +8,7 @@ public sealed class GeneratorTests TemplateGenerator generator; CompiledTemplate deviceTemplate; CompiledTemplate asyncDeviceTemplate; + string payloadExtensions; [TestInitialize] public async Task Initialize() @@ -15,6 +16,7 @@ public async Task Initialize() generator = new TestTemplateGenerator(); var deviceTemplateContents = TestHelper.GetManifestResourceText("Device.tt"); var asyncDeviceTemplateContents = TestHelper.GetManifestResourceText("AsyncDevice.tt"); + payloadExtensions = TestHelper.GetManifestResourceText("PayloadExtensions.cs"); deviceTemplate = await generator.CompileTemplateAsync(deviceTemplateContents); TestHelper.AssertNoGeneratorErrors(generator); @@ -41,6 +43,6 @@ public void DeviceTemplate_GenerateAndBuildWithoutErrors(string metadataFileName var asyncDeviceCode = ProcessTemplate(asyncDeviceTemplate, metadataFileName); TestHelper.AssertNoGeneratorErrors(generator); - CompilerTestHelper.CompileFromSource(deviceCode, asyncDeviceCode); + CompilerTestHelper.CompileFromSource(deviceCode, asyncDeviceCode, payloadExtensions); } } diff --git a/tests/Interface.Tests.csproj b/tests/Interface.Tests.csproj index 4b4f3a6..b91fbf7 100644 --- a/tests/Interface.Tests.csproj +++ b/tests/Interface.Tests.csproj @@ -20,6 +20,7 @@ + diff --git a/tests/Metadata/device.yml b/tests/Metadata/device.yml index 11abfe4..7e91418 100644 --- a/tests/Metadata/device.yml +++ b/tests/Metadata/device.yml @@ -21,4 +21,61 @@ registers: Analog1: offset: 1 Analog2: - offset: 2 \ No newline at end of file + offset: 2 + ComplexConfiguration: + address: 33 + type: U8 + access: Write + length: 17 + payloadSpec: + PwmPort: + offset: 0 + length: 1 + maskType: PwmPort + DutyCycle: + offset: 4 + length: 4 + interfaceType: float + Frequency: + offset: 8 + length: 4 + interfaceType: float + EventsEnabled: + offset: 12 + length: 1 + interfaceType: bool + Delta: + offset: 13 + length: 4 + interfaceType: uint + Version: + address: 34 + type: U8 + length: 32 + access: Event + payloadSpec: + ProtocolVersion: + offset: 0 + length: 3 + interfaceType: HarpVersion + FirmwareVersion: + offset: 3 + length: 3 + interfaceType: HarpVersion + HardwareVersion: + offset: 6 + length: 3 + interfaceType: HarpVersion + CoreId: + offset: 9 + length: 3 + interfaceType: string + InterfaceHash: + offset: 12 + length: 20 +groupMasks: + PwmPort: + values: + Pwm0: 0x1 + Pwm1: 0x2 + Pwm2: 0x4 diff --git a/tests/PayloadExtensions.cs b/tests/PayloadExtensions.cs new file mode 100644 index 0000000..8fefacf --- /dev/null +++ b/tests/PayloadExtensions.cs @@ -0,0 +1,22 @@ +#pragma warning disable IDE0005 +using System; +#pragma warning restore IDE0005 +using Bonsai.Harp; + +namespace Interface.Tests; + +internal static partial class PayloadExtensions +{ + internal static HarpVersion ToHarpVersion(this ArraySegment segment) + { + var major = segment.Array[segment.Offset]; + var minor = segment.Array[segment.Offset + 1]; + return new HarpVersion(major, minor); + } + + internal static void WriteBytes(this ArraySegment segment, HarpVersion value) + { + segment.Array[segment.Offset] = (byte)value.Major.GetValueOrDefault(); + segment.Array[segment.Offset + 1] = (byte)value.Major.GetValueOrDefault(); + } +} \ No newline at end of file