Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions Coder.Test/Ast/TypeDeclarationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/// <summary>
/// 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.
/// </summary>
[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);
}

/// <summary>
/// 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.
/// </summary>
[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);
}

/// <summary>
/// A class body is not a block, so the namespace-scope spelling of an enumeration cannot simply
/// be nested inside one.
Expand Down
5 changes: 1 addition & 4 deletions Coder/Languages/JavaScriptGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -137,10 +136,8 @@ private void GenerateNestedEnum(EnumDeclaration enumDecl, CodeBlocker code)
/// <param name="code">The writer to emit into.</param>
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},");
}
}
Expand Down
40 changes: 40 additions & 0 deletions Coder/Languages/LanguageGeneratorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -792,4 +792,44 @@
/// <exception cref="NotSupportedException">The operator has no mapping.</exception>
/// <remarks>Every language the generators target spells these identically.</remarks>
protected static string GetAssignmentOperator(AssignmentOperator op) => OperatorSymbols.GetSymbol(op);

/// <summary>
/// Gives the value each member of an enumeration stands for, for a language that has to write
/// every one out.
/// </summary>
/// <param name="enumDecl">The declaration whose members to number.</param>
/// <returns>One value per member, in order.</returns>
/// <remarks>
/// 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.
/// </remarks>
protected static IEnumerable<string> EnumMemberValues(EnumDeclaration enumDecl)
{
Ensure.NotNull(enumDecl);

string? origin = null;
long offset = 0;

foreach (EnumMember member in enumDecl.Members)

Check warning on line 816 in Coder/Languages/LanguageGeneratorBase.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Loop should be simplified by calling Select(member => member.Value))

Check warning on line 816 in Coder/Languages/LanguageGeneratorBase.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Loop should be simplified by calling Select(member => member.Value))

Check warning on line 816 in Coder/Languages/LanguageGeneratorBase.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Loop should be simplified by calling Select(member => member.Value))

Check warning on line 816 in Coder/Languages/LanguageGeneratorBase.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Loop should be simplified by calling Select(member => member.Value))

Check warning on line 816 in Coder/Languages/LanguageGeneratorBase.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Loop should be simplified by calling Select(member => member.Value))

Check warning on line 816 in Coder/Languages/LanguageGeneratorBase.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Loop should be simplified by calling Select(member => member.Value))

Check warning on line 816 in Coder/Languages/LanguageGeneratorBase.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Loop should be simplified by calling Select(member => member.Value))

Check warning on line 816 in Coder/Languages/LanguageGeneratorBase.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Loop should be simplified by calling Select(member => member.Value))

Check warning on line 816 in Coder/Languages/LanguageGeneratorBase.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Loop should be simplified by calling Select(member => member.Value))

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Coder&issues=AaDfBXDey29mLvhCGqCe&open=AaDfBXDey29mLvhCGqCe&pullRequest=89
{
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++;
}
}
}
9 changes: 3 additions & 6 deletions Coder/Languages/PythonGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -277,8 +276,8 @@ private void WriteArguments(ConstructionExpression construction, CodeBlocker cod
/// <inheritdoc/>
/// <remarks>
/// Python has no enumeration syntax; <c>enum.Enum</c> 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 <c>from enum import Enum</c> 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 <see cref="LanguageGeneratorBase.EnumMemberValues"/>. The <c>from enum import Enum</c> this needs belongs to the file rather than to the
/// declaration.
/// </remarks>
protected override void GenerateEnumDeclaration(EnumDeclaration enumDecl, CodeBlocker code)
Expand All @@ -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}");
}
}
Expand Down
Loading