diff --git a/src/Abc.Zebus.MessageDsl.Tests/MessageDsl/CSharpGeneratorTests.cs b/src/Abc.Zebus.MessageDsl.Tests/MessageDsl/CSharpGeneratorTests.cs index 66dd579..6a3b966 100644 --- a/src/Abc.Zebus.MessageDsl.Tests/MessageDsl/CSharpGeneratorTests.cs +++ b/src/Abc.Zebus.MessageDsl.Tests/MessageDsl/CSharpGeneratorTests.cs @@ -333,7 +333,7 @@ public async Task should_generate_enums() }); code.ShouldContain("public enum Foo : short"); - code.ShouldContain("Default,"); + code.ShouldContain("Default = 0,"); code.ShouldContain("Bar = -2,"); code.ShouldContain("Baz = Bar"); code.ShouldContain("[EnumAttr]"); diff --git a/src/Abc.Zebus.MessageDsl.Tests/MessageDsl/ParsedContractsTests.cs b/src/Abc.Zebus.MessageDsl.Tests/MessageDsl/ParsedContractsTests.cs index 5ae9dd3..ca53919 100644 --- a/src/Abc.Zebus.MessageDsl.Tests/MessageDsl/ParsedContractsTests.cs +++ b/src/Abc.Zebus.MessageDsl.Tests/MessageDsl/ParsedContractsTests.cs @@ -557,6 +557,59 @@ public enum Metrics enumDef.Members.ExpectedSingle(i => i.Name == "Test").Value.ShouldEqual("Lifetime | (Latency)"); } + [Test] + public void should_compute_enum_member_values() + { + var msg = ParseValid( + """ + enum Counter + { + Zero, + One, + Two, + _, + _, + Five, + Six, + Seven, + Eight = 2 << 2, + Nine, + Ten, + Eleven, + _, + AnotherOne = One, + AnotherTwo, + AnotherThree, + _, + Twenty = 0x14, + TwentyOne, + TwentyTwo, + _ + } + """ + ); + + var enumDef = msg.Enums.ExpectedSingle(); + enumDef.Members.Select(m => m.CSharpValue).ShouldEqual([ + "0", + "1", + "2", + "5", + "6", + "7", + "2 << 2", + "(2 << 2) + 1", + "(2 << 2) + 2", + "(2 << 2) + 3", + "One", + "(One) + 1", + "(One) + 2", + "0x14", + "21", + "22" + ]); + } + [Test] public void should_handle_double_angled_brackets() { diff --git a/src/Abc.Zebus.MessageDsl/Analysis/AstProcessor.cs b/src/Abc.Zebus.MessageDsl/Analysis/AstProcessor.cs index 96eca48..876a8c6 100644 --- a/src/Abc.Zebus.MessageDsl/Analysis/AstProcessor.cs +++ b/src/Abc.Zebus.MessageDsl/Analysis/AstProcessor.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel; +using System.Globalization; using System.Linq; using Abc.Zebus.MessageDsl.Ast; @@ -87,21 +89,71 @@ private static void ResolveTags(MessageDefinition message) private static void ResolveEnumValues(EnumDefinition enumDef) { - if (!enumDef.Options.Proto) - return; + ResolveCSharpValues(); + ResolveProtoValues(); - if (enumDef.UnderlyingType.NetType != "int") - return; + void ResolveCSharpValues() + { + var lastValue = (string?)null; + var lastValueAsNumber = (long?)null; + var lastValueIsParsed = false; + var lastOffset = 0L; + + foreach (var member in enumDef.Members) + { + if (!string.IsNullOrEmpty(member.Value)) + { + lastValue = member.CSharpValue = member.Value; + lastValueAsNumber = null; + lastValueIsParsed = false; + lastOffset = 0; + } + else if (lastValue is null) + { + lastValue = member.CSharpValue = "0"; + lastValueAsNumber = 0; + lastValueIsParsed = true; + lastOffset = 0; + } + else + { + if (lastValueAsNumber is null && !lastValueIsParsed) + { + if (long.TryParse(lastValue, NumberStyles.Any, CultureInfo.InvariantCulture, out var lastValueParsed)) + lastValueAsNumber = lastValueParsed; + else if (lastValue.StartsWith("0x", StringComparison.OrdinalIgnoreCase) && long.TryParse(lastValue.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out lastValueParsed)) + lastValueAsNumber = lastValueParsed; - var nextValue = (int?)0; + lastValueIsParsed = true; + } - foreach (var member in enumDef.Members) + ++lastOffset; + + member.CSharpValue = lastValueAsNumber is { } lastBaseValue + ? checked(lastBaseValue + lastOffset).ToString(CultureInfo.InvariantCulture) + : $"({lastValue}) + {lastOffset}"; + } + } + } + + void ResolveProtoValues() { - member.ProtoValue = string.IsNullOrEmpty(member.Value) - ? nextValue - : enumDef.GetValidUnderlyingValue(member.Value) as int?; + if (!enumDef.Options.Proto) + return; - nextValue = member.ProtoValue + 1; + if (enumDef.UnderlyingType.NetType != "int") + return; + + var nextValue = (int?)0; + + foreach (var member in enumDef.Members) + { + member.ProtoValue = string.IsNullOrEmpty(member.Value) + ? nextValue + : enumDef.GetValidUnderlyingValue(member.Value) as int?; + + nextValue = member.ProtoValue + 1; + } } } diff --git a/src/Abc.Zebus.MessageDsl/Ast/EnumMemberDefinition.cs b/src/Abc.Zebus.MessageDsl/Ast/EnumMemberDefinition.cs index 7f341ae..6f21bd4 100644 --- a/src/Abc.Zebus.MessageDsl/Ast/EnumMemberDefinition.cs +++ b/src/Abc.Zebus.MessageDsl/Ast/EnumMemberDefinition.cs @@ -5,6 +5,8 @@ public class EnumMemberDefinition : AstNode, INamedNode public string Name { get; set; } = string.Empty; public string? Value { get; set; } public AttributeSet Attributes { get; } = new(); + + internal string? CSharpValue { get; set; } internal int? ProtoValue { get; set; } internal bool IsDiscarded { get; set; } diff --git a/src/Abc.Zebus.MessageDsl/Generator/CSharpGenerator.cs b/src/Abc.Zebus.MessageDsl/Generator/CSharpGenerator.cs index d7abb1a..803202d 100644 --- a/src/Abc.Zebus.MessageDsl/Generator/CSharpGenerator.cs +++ b/src/Abc.Zebus.MessageDsl/Generator/CSharpGenerator.cs @@ -152,7 +152,9 @@ private void WriteEnum(EnumDefinition enumDef) Writer.Write(Identifier(member.Name)); - if (!string.IsNullOrEmpty(member.Value)) + if (!string.IsNullOrEmpty(member.CSharpValue)) + Writer.Write(" = {0}", member.CSharpValue); + else if (!string.IsNullOrEmpty(member.Value)) Writer.Write(" = {0}", member.Value); if (member != lastMember)