diff --git a/Coder.Test/Ast/TypeDeclarationTests.cs b/Coder.Test/Ast/TypeDeclarationTests.cs
index 8157ec2..2dc738a 100644
--- a/Coder.Test/Ast/TypeDeclarationTests.cs
+++ b/Coder.Test/Ast/TypeDeclarationTests.cs
@@ -111,13 +111,58 @@ public void Enum_IsSpelledByEachLanguage()
StringComparison.Ordinal);
Assert.Contains("Dynamic = 7,", new CppGenerator().Generate(kind), StringComparison.Ordinal);
- // Python and JavaScript have no enumeration, so a member with no value of its own is numbered
- // from its position rather than left to a keyword that does not exist.
+ // Python and JavaScript have no enumeration, so a member with no value of its own is given one
+ // rather than left to a keyword that does not exist.
Assert.Contains("Static = 0", new PythonGenerator().Generate(kind), StringComparison.Ordinal);
Assert.Contains("Static: 0,", new JavaScriptGenerator().Generate(kind), StringComparison.Ordinal);
Assert.Contains("Object.freeze({", new JavaScriptGenerator().Generate(kind), StringComparison.Ordinal);
}
+ ///
+ /// A member with no value of its own follows the one before it, as every language with real
+ /// enumerations numbers it, rather than taking its position — which would disagree with those
+ /// languages and could repeat an earlier member's value.
+ ///
+ [TestMethod]
+ public void Enum_NumbersAMemberAfterAnExplicitValueFromThatValue()
+ {
+ EnumDeclaration colour = new("Colour");
+ colour.Members.Add(new EnumMember("Red"));
+ colour.Members.Add(new EnumMember("Green") { Value = "5" });
+ colour.Members.Add(new EnumMember("Blue"));
+
+ string python = new PythonGenerator().Generate(colour);
+ Assert.Contains("Red = 0", python, StringComparison.Ordinal);
+ Assert.Contains("Green = 5", python, StringComparison.Ordinal);
+ Assert.Contains("Blue = 6", python, StringComparison.Ordinal);
+
+ string javaScript = new JavaScriptGenerator().Generate(colour);
+ Assert.Contains("Red: 0,", javaScript, StringComparison.Ordinal);
+ Assert.Contains("Green: 5,", javaScript, StringComparison.Ordinal);
+ Assert.Contains("Blue: 6,", javaScript, StringComparison.Ordinal);
+ }
+
+ ///
+ /// A member following a value that is not a plain number counts on from that value's expression,
+ /// since the generator cannot work out what it comes to.
+ ///
+ [TestMethod]
+ public void Enum_CountsOnFromAnExplicitValueThatIsNotANumber()
+ {
+ EnumDeclaration flags = new("Flags");
+ flags.Members.Add(new EnumMember("High") { Value = "1 << 4" });
+ flags.Members.Add(new EnumMember("Next"));
+ flags.Members.Add(new EnumMember("After"));
+
+ string python = new PythonGenerator().Generate(flags);
+ Assert.Contains("Next = (1 << 4) + 1", python, StringComparison.Ordinal);
+ Assert.Contains("After = (1 << 4) + 2", python, StringComparison.Ordinal);
+
+ string javaScript = new JavaScriptGenerator().Generate(flags);
+ Assert.Contains("Next: (1 << 4) + 1,", javaScript, StringComparison.Ordinal);
+ Assert.Contains("After: (1 << 4) + 2,", javaScript, StringComparison.Ordinal);
+ }
+
///
/// A class body is not a block, so the namespace-scope spelling of an enumeration cannot simply
/// be nested inside one.
diff --git a/Coder/Languages/JavaScriptGenerator.cs b/Coder/Languages/JavaScriptGenerator.cs
index a05b963..2a22986 100644
--- a/Coder/Languages/JavaScriptGenerator.cs
+++ b/Coder/Languages/JavaScriptGenerator.cs
@@ -3,7 +3,6 @@
namespace ktsu.Coder.Languages;
using System.Collections.ObjectModel;
-using System.Globalization;
using System.Linq;
using ktsu.Coder.Ast;
using ktsu.CodeBlocker;
@@ -137,10 +136,8 @@ private void GenerateNestedEnum(EnumDeclaration enumDecl, CodeBlocker code)
/// The writer to emit into.
private static void WriteEnumMembers(EnumDeclaration enumDecl, CodeBlocker code)
{
- for (int index = 0; index < enumDecl.Members.Count; index++)
+ foreach ((EnumMember member, string value) in enumDecl.Members.Zip(EnumMemberValues(enumDecl)))
{
- EnumMember member = enumDecl.Members[index];
- string value = member.Value ?? index.ToString(CultureInfo.InvariantCulture);
code.WriteLine($"{member.Name ?? "UNNAMED"}: {value},");
}
}
diff --git a/Coder/Languages/LanguageGeneratorBase.cs b/Coder/Languages/LanguageGeneratorBase.cs
index a0df1f6..924c325 100644
--- a/Coder/Languages/LanguageGeneratorBase.cs
+++ b/Coder/Languages/LanguageGeneratorBase.cs
@@ -792,4 +792,44 @@ protected static string EscapeString(string value)
/// The operator has no mapping.
/// Every language the generators target spells these identically.
protected static string GetAssignmentOperator(AssignmentOperator op) => OperatorSymbols.GetSymbol(op);
+
+ ///
+ /// Gives the value each member of an enumeration stands for, for a language that has to write
+ /// every one out.
+ ///
+ /// The declaration whose members to number.
+ /// One value per member, in order.
+ ///
+ /// A member with no value of its own is one more than the member before it, which is what every
+ /// language with real enumerations gives it; numbering it from its position instead would disagree
+ /// with those languages once any member has a value, and could repeat one. When the value being
+ /// counted on from is not a plain integer, it is counted on from as an expression, since the
+ /// generator cannot work out what it comes to.
+ ///
+ protected static IEnumerable EnumMemberValues(EnumDeclaration enumDecl)
+ {
+ Ensure.NotNull(enumDecl);
+
+ string? origin = null;
+ long offset = 0;
+
+ foreach (EnumMember member in enumDecl.Members)
+ {
+ if (member.Value is not null)
+ {
+ bool isNumber = long.TryParse(member.Value, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out long number);
+ origin = isNumber ? null : member.Value;
+ offset = isNumber ? number : 0;
+ yield return member.Value;
+ }
+ else
+ {
+ yield return origin is null
+ ? offset.ToString(CultureInfo.InvariantCulture)
+ : $"({origin}) + {offset.ToString(CultureInfo.InvariantCulture)}";
+ }
+
+ offset++;
+ }
+ }
}
diff --git a/Coder/Languages/PythonGenerator.cs b/Coder/Languages/PythonGenerator.cs
index 2e60f6e..13a95d1 100644
--- a/Coder/Languages/PythonGenerator.cs
+++ b/Coder/Languages/PythonGenerator.cs
@@ -3,7 +3,6 @@
namespace ktsu.Coder.Languages;
using System.Collections.ObjectModel;
-using System.Globalization;
using System.Linq;
using ktsu.Coder.Ast;
using ktsu.CodeBlocker;
@@ -277,8 +276,8 @@ private void WriteArguments(ConstructionExpression construction, CodeBlocker cod
///
///
/// Python has no enumeration syntax; enum.Enum is a class. A member with no value of its
- /// own is numbered from its position, matching what a language with real enumerations would give
- /// it. The from enum import Enum this needs belongs to the file rather than to the
+ /// own is one more than the member before it, matching what a language with real enumerations
+ /// would give it; see . The from enum import Enum this needs belongs to the file rather than to the
/// declaration.
///
protected override void GenerateEnumDeclaration(EnumDeclaration enumDecl, CodeBlocker code)
@@ -296,10 +295,8 @@ protected override void GenerateEnumDeclaration(EnumDeclaration enumDecl, CodeBl
return;
}
- for (int index = 0; index < enumDecl.Members.Count; index++)
+ foreach ((EnumMember member, string value) in enumDecl.Members.Zip(EnumMemberValues(enumDecl)))
{
- EnumMember member = enumDecl.Members[index];
- string value = member.Value ?? index.ToString(CultureInfo.InvariantCulture);
code.WriteLine($"{member.Name ?? "UNNAMED"} = {value}");
}
}