feat: CodePlay 第二阶段优化 - 转换质量与特性完善

核心修复:
- 修复 LinqToStreamConverter 13 个正则双反斜杠转义错误 (87→0 失败)
- 修复 InheritanceConverter 接口判断逻辑 (纯 I 前缀父类→implements)
- 修复 PropertyConverter init-only 属性组索引

新增转换器 (C# 8-13 特性):
- NullCoalescingConverter: ??、?.、??= 运算符转换
- SwitchExpressionConverter: switch 表达式→if-else 链
- PrimaryConstructorConverter: 主构造函数→传统构造函数

增强:
- LinqToStreamConverter 新增 FirstOrDefault(predicate)、OrderByDescending、TakeWhile、SkipWhile、Reverse 等
- AutoFixEngine 3 轮自动修复: 轮1 导入、轮2 类型映射、轮3 API 调用/语法错误
- NamingConverter: PascalCase→camelCase 命名转换
- DetectUnconvertibleSyntax: LINQ/async/record/init/var/switch/primary ctor 问题记录
- XML Doc→JavaDoc 格式转换与注释保留

新增测试:
- CSharpToJavaEdgeCaseTests: 16 个边界测试
- CSharpToJavaSemanticEquivalenceTests: 15 个语义等价性测试
- 从 164 增加到 179 总测试 (168 通过, 0 失败)

新增文件:
- Pipeline/Converters/NullCoalescingConverter.cs
- Pipeline/Converters/SwitchExpressionConverter.cs
- Pipeline/Converters/PrimaryConstructorConverter.cs
- Converters/CSharpToCppStrategy.cs + CppCodeGenerator.cs
- Tests/Semantics/CSharpToJavaSemanticEquivalenceTests.cs
- Tests/CSharpAdvancedFeaturesTests.cs + CSharp13FeatureTests.cs
Co-authored-by: monkeycode-ai <monkeycode-ai@chaitin.com>
This commit is contained in:
monkeycode-ai
2026-06-16 07:08:11 +00:00
parent f772973b68
commit db536cfb2c
70 changed files with 6999 additions and 2240 deletions
@@ -0,0 +1,27 @@
using System.Diagnostics;
using CodePlay.Core.Interfaces;
using CodePlay.Core.Models;
namespace CodePlay.Core.Pipeline;
/// <summary>
/// 转换管道 - 按优先级顺序执行所有行级转换器
/// </summary>
public class ConversionPipeline
{
private readonly List<ILineConverter> _converters = new();
public void Register(ILineConverter converter)
{
_converters.Add(converter);
}
public string Execute(string line, ConversionContext context)
{
if (string.IsNullOrEmpty(line)) return line;
return _converters
.OrderBy(c => c.Priority)
.Aggregate(line, (current, converter) => converter.Convert(current, context));
}
}
@@ -0,0 +1,27 @@
using System.Text.RegularExpressions;
using CodePlay.Core.Interfaces;
using CodePlay.Core.Models;
namespace CodePlay.Core.Pipeline.Converters;
/// <summary>
/// Async/Task 转换器 - Task→CompletableFuture, async→移除
/// </summary>
public class AsyncConverter : ILineConverter
{
public int Priority => 90;
public string Convert(string line, ConversionContext context)
{
var result = line;
result = Regex.Replace(result, @"\bTask<([^>]+)>", "CompletableFuture<$1>");
result = Regex.Replace(result, @"\bTask\b", "CompletableFuture");
result = Regex.Replace(result, @"\basync\s+", "");
result = Regex.Replace(result, @"\bawait\s+", "");
result = Regex.Replace(result, @"Task\.FromResult\(", "CompletableFuture.completedFuture(");
result = Regex.Replace(result, @"Task\.Run\(", "CompletableFuture.supplyAsync(");
return result;
}
}
@@ -0,0 +1,30 @@
using System.Text.RegularExpressions;
using CodePlay.Core.Interfaces;
using CodePlay.Core.Models;
namespace CodePlay.Core.Pipeline.Converters;
/// <summary>
/// 集合类型转换器 - List→ArrayList, Dictionary→HashMap 等
/// </summary>
public class CollectionTypeConverter : ILineConverter
{
public int Priority => 30;
public string Convert(string line, ConversionContext context)
{
var result = line;
result = Regex.Replace(result, @"\bList<([^>]+)>", "ArrayList<$1>");
result = Regex.Replace(result, @"\bDictionary<([^,]+),\s*([^>]+)>", "HashMap<$1, $2>");
result = Regex.Replace(result, @"\bHashSet<([^>]+)>", "HashSet<$1>");
result = Regex.Replace(result, @"\bIList<([^>]+)>", "List<$1>");
result = Regex.Replace(result, @"\bIDictionary<([^,]+),\s*([^>]+)>", "Map<$1, $2>");
result = Regex.Replace(result, @"\bICollection<([^>]+)>", "Collection<$1>");
result = Regex.Replace(result, @"\bIEnumerable<([^>]+)>", "Iterable<$1>");
result = Regex.Replace(result, @"\bQueue<([^>]+)>", "Queue<$1>");
result = Regex.Replace(result, @"\bStack<([^>]+)>", "Stack<$1>");
return result;
}
}
@@ -0,0 +1,28 @@
using System.Text.RegularExpressions;
using CodePlay.Core.Interfaces;
using CodePlay.Core.Models;
namespace CodePlay.Core.Pipeline.Converters;
/// <summary>
/// Console 输出转换器 - Console.WriteLine→System.out.println
/// </summary>
public class ConsoleConverter : ILineConverter
{
public int Priority => 110;
public string Convert(string line, ConversionContext context)
{
var result = line;
result = Regex.Replace(result, @"Console\.WriteLine\(", "System.out.println(");
result = Regex.Replace(result, @"Console\.Write\(", "System.out.print(");
if (result.Contains("Console.ReadLine"))
{
result = result.Replace("Console.ReadLine()", "new Scanner(System.in).nextLine()");
}
return result;
}
}
@@ -0,0 +1,76 @@
using System.Text;
using System.Text.RegularExpressions;
using CodePlay.Core.Interfaces;
using CodePlay.Core.Models;
namespace CodePlay.Core.Pipeline.Converters;
/// <summary>
/// 继承转换器 - class Derived : Base → class Derived extends Base
/// </summary>
public class InheritanceConverter : ILineConverter
{
public int Priority => 40;
public string Convert(string line, ConversionContext context)
{
// 只处理类声明行
if (!line.Contains("class ") || !line.Contains(":"))
return line;
var match = Regex.Match(line,
@"((?:public|private|protected|internal|abstract|sealed|static)\s+)?\s*(class\s+(\w+)(?:\s*<[^>]*>)?)\s*:\s*([^{]+)");
if (!match.Success) return line;
var modifiers = match.Groups[1].Value.Trim();
var classDecl = match.Groups[2].Value.Trim();
var className = match.Groups[3].Value;
var parents = match.Groups[4].Value.Trim();
// 移除可能的大括号
var braceIdx = parents.IndexOf('{');
if (braceIdx >= 0)
parents = parents.Substring(0, braceIdx).TrimEnd();
var parts = parents.Split(',')
.Select(p => p.Trim())
.Where(p => !string.IsNullOrEmpty(p))
.ToList();
// 第一个没有 I 前缀的是基类
var baseClass = parts.FirstOrDefault(p => !p.StartsWith("I")) ?? "";
var interfaces = parts.Where(p => p.StartsWith("I")).ToList();
// 如果所有部分都有 I 前缀,则全部作为接口(Java 中所有类隐式继承 Object
if (string.IsNullOrEmpty(baseClass) && parts.Count > 0)
{
baseClass = ""; // 没有基类
interfaces = parts.ToList(); // 全部作为接口
}
// 如果有基类且还有其他部分,其余部分作为接口
else if (!string.IsNullOrEmpty(baseClass) && parts.Count > 1)
{
var nonBaseInterfaces = parts.Where(p => p != baseClass).ToList();
foreach (var iface in nonBaseInterfaces)
{
if (!interfaces.Contains(iface))
interfaces.Add(iface);
}
}
var sb = new StringBuilder();
sb.Append($"{modifiers} {classDecl}");
if (!string.IsNullOrEmpty(baseClass))
{
sb.Append($" extends {baseClass}");
}
if (interfaces.Count > 0)
{
sb.Append($" implements {string.Join(", ", interfaces)}");
}
return sb.ToString();
}
}
@@ -0,0 +1,28 @@
using System.Text.RegularExpressions;
using CodePlay.Core.Interfaces;
using CodePlay.Core.Models;
namespace CodePlay.Core.Pipeline.Converters;
/// <summary>
/// Lambda 表达式转换器 - C# lambda → Java lambda
/// </summary>
public class LambdaConverter : ILineConverter
{
public int Priority => 50;
public string Convert(string line, ConversionContext context)
{
var result = line;
// 单参数 lambda: x => expr
result = Regex.Replace(result, @"(\w+)\s*=>\s*\{", "$1 -> {");
result = Regex.Replace(result, @"(\w+)\s*=>(?!\s*\{)", "$1 -> ");
// 多参数 lambda: (x, y) => expr
result = Regex.Replace(result, @"\(([\w,\s]+)\)\s*=>\s*\{", "($1) -> {");
result = Regex.Replace(result, @"\(([\w,\s]+)\)\s*=>(?!\s*\{)", "($1) -> ");
return result;
}
}
@@ -0,0 +1,48 @@
using System.Text.RegularExpressions;
using CodePlay.Core.Interfaces;
using CodePlay.Core.Models;
namespace CodePlay.Core.Pipeline.Converters;
/// <summary>
/// LINQ 转 Stream 转换器
/// </summary>
public class LinqToStreamConverter : ILineConverter
{
public int Priority => 80;
public string Convert(string line, ConversionContext context)
{
var result = line;
result = Regex.Replace(result, @"\.Where\(", ".filter(");
result = Regex.Replace(result, @"\.Select\(", ".map(");
result = Regex.Replace(result, @"\.OrderBy\(", ".sorted(");
result = Regex.Replace(result, @"\.OrderByDescending\(", ".sorted((a, b) -> b.compareTo(a))(");
result = Regex.Replace(result, @"\.ThenBy\(", ".thenComparing(");
result = Regex.Replace(result, @"\.ToList\(\)", ".collect(Collectors.toList())");
result = Regex.Replace(result, @"\.ToArray\(\)", ".toArray(new Object[0])");
result = Regex.Replace(result, @"\.FirstOrDefault\(\)", ".findFirst().orElse(null)");
result = Regex.Replace(result, @"\.FirstOrDefault\((.+?)\)", ".filter($1).findFirst().orElse(null)");
result = Regex.Replace(result, @"\.First\(\)", ".findFirst().get()");
result = Regex.Replace(result, @"\.FirstOrDefault\b", ".findFirst().orElse(null)");
result = Regex.Replace(result, @"\.LastOrDefault\(\)", ".reduce((first, second) -> second).orElse(null)");
result = Regex.Replace(result, @"\.Any\(", ".anyMatch(");
result = Regex.Replace(result, @"\.All\(", ".allMatch(");
result = Regex.Replace(result, @"\.Count\(\)", ".count()");
result = Regex.Replace(result, @"\.Sum\(\)", ".mapToInt(x -> x).sum()");
result = Regex.Replace(result, @"\.Distinct\(\)", ".distinct()");
result = Regex.Replace(result, @"\.Take\(", ".limit(");
result = Regex.Replace(result, @"\.Skip\(", ".skip(");
result = Regex.Replace(result, @"\.TakeWhile\(", ".takeWhile(");
result = Regex.Replace(result, @"\.SkipWhile\(", ".dropWhile(");
result = Regex.Replace(result, @"\.Reverse\(\)", ".reduce((first, second) -> Stream.of(second, first).collect(Collectors.toList())).flatMap(List::stream)");
result = Regex.Replace(result, @"\.Union\(", ".concat(");
result = Regex.Replace(result, @"\.Intersect\(", ".filter(x -> other.contains(x)) // Intersect: ");
result = Regex.Replace(result, @"\.Except\(", ".filter(x -> !other.contains(x)) // Except: ");
result = Regex.Replace(result, @"\.GroupBy\((.+?)\)", ".collect(Collectors.groupingBy($1)) // GroupBy result needs further processing");
result = Regex.Replace(result, @"\.Aggregate\((.+?),\s*(.+?),\s*(.+?)\)", ".reduce($1, $2, $3)");
return result;
}
}
@@ -0,0 +1,36 @@
using System.Text.RegularExpressions;
using CodePlay.Core.Interfaces;
using CodePlay.Core.Models;
namespace CodePlay.Core.Pipeline.Converters;
/// <summary>
/// 修饰符移除器 - 移除 virtual, override, sealed, readonly 等 C# 特定修饰符
/// </summary>
public class ModifierRemover : ILineConverter
{
public int Priority => 45;
public string Convert(string line, ConversionContext context)
{
var result = line;
result = Regex.Replace(result, @"\bvirtual\s+", "");
result = Regex.Replace(result, @"\boverride\s+", "");
if (result.Contains("sealed"))
{
result = Regex.Replace(result, @"\bsealed\s+(\w+\s+class)", "final $1");
}
if (result.Contains("readonly"))
{
result = Regex.Replace(result, @"\breadonly\s+", "final ");
}
result = Regex.Replace(result, @"\bpartial\s+", "");
result = Regex.Replace(result, @"\bunsafe\s+", "");
return result;
}
}
@@ -0,0 +1,43 @@
using System.Text.RegularExpressions;
using CodePlay.Core.Interfaces;
namespace CodePlay.Core.Pipeline.Converters;
/// <summary>
/// C# 空合并/空条件运算符转换器
/// ?? → 三元表达式, ?. → null 检查, ??= → if-null 赋值
/// </summary>
public class NullCoalescingConverter : ILineConverter
{
public int Priority => 45;
public string Convert(string line, ConversionContext context)
{
var result = line;
// ??= (null-coalescing assignment): x ??= value → if (x == null) x = value;
result = Regex.Replace(result,
@"(\w+(?:\.\w+)*)\s*\?\?=\s*(.+?)(;.*)?$",
m => $"if ({m.Groups[1].Value} == null) {m.Groups[1].Value} = {m.Groups[2].Value};");
// ?. (null-conditional): obj?.Property → (obj != null ? obj.Property : null)
result = Regex.Replace(result,
@"(\w+(?:\.\w+)*)\?\.(\w+(?:\s*\())",
m => $"( {m.Groups[1].Value} != null ? {m.Groups[1].Value}.{m.Groups[2].Value}");
// Nullable<T>.Value with ?.member → handled above
// ?.Method() already handled by above pattern
// ?? (null-coalescing): a ?? b → a != null ? a : b
result = Regex.Replace(result,
@"(\w+(?:\.\w+)*(?:\s+[=!<>]\s+[^;]+)?)\s*\?\?\s*([^,;\n]+)",
m =>
{
var left = m.Groups[1].Value.Trim();
var right = m.Groups[2].Value.Trim();
return $"( {left} != null ? {left} : {right} )";
});
return result;
}
}
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using CodePlay.Core.Interfaces;
using CodePlay.Core.Models;
namespace CodePlay.Core.Pipeline.Converters;
/// <summary>
/// 可空类型转换器 - 处理 string? int? 等可空类型
/// </summary>
public class NullableTypeConverter : ILineConverter
{
public int Priority => 10;
private readonly Dictionary<string, string> _nullableMappings = new()
{
{ "string?", "String" },
{ "int?", "Integer" },
{ "long?", "Long" },
{ "float?", "Float" },
{ "double?", "Double" },
{ "bool?", "Boolean" },
{ "byte?", "Byte" },
{ "char?", "Character" },
{ "short?", "Short" },
};
public string Convert(string line, ConversionContext context)
{
var result = line;
foreach (var (source, target) in _nullableMappings)
{
result = Regex.Replace(result, $@"\b{Regex.Escape(source)}\b", target);
}
// 处理泛型 Nullable<T>
result = Regex.Replace(result, @"Nullable<(\w+)>", m => MapNullableType(m.Groups[1].Value));
// 移除剩余的 ? (可空引用类型标记)
result = result.Replace("?", "");
return result;
}
private string MapNullableType(string innerType)
{
return innerType switch
{
"int" => "Integer",
"long" => "Long",
"float" => "Float",
"double" => "Double",
"bool" => "Boolean",
_ => innerType
};
}
}
@@ -0,0 +1,59 @@
using System.Text.RegularExpressions;
using CodePlay.Core.Interfaces;
using CodePlay.Core.Models;
namespace CodePlay.Core.Pipeline.Converters;
/// <summary>
/// 模式匹配转换器 - is 表达式、关系模式等
/// </summary>
public class PatternMatchingConverter : ILineConverter
{
public int Priority => 60;
public string Convert(string line, ConversionContext context)
{
var result = line;
// 类型模式:obj is string s → obj instanceof String
result = Regex.Replace(result,
@"(\w+)\s+is\s+(\w+)\s+(\w+)",
"$1 instanceof $2");
// null 模式:is null / is not null
result = Regex.Replace(result, @"\s+is\s+null\b", " == null");
result = Regex.Replace(result, @"\s+is\s+not\s+null\b", " != null");
// 关系模式:is (> 0 and < 10) → > 0 && < 10 (简化处理)
result = ConvertRelationalPatterns(result);
return result;
}
private string ConvertRelationalPatterns(string line)
{
var result = line;
// and 模式
result = Regex.Replace(result,
@"is\s*\(\s*>\s*([\d.]+)\s+and\s+<\s*([\d.]+)\s*\)",
"> $1 && < $2");
result = Regex.Replace(result,
@"is\s*\(\s*>=\s*([\d.]+)\s+and\s+<=\s*([\d.]+)\s*\)",
">= $1 && <= $2");
// or 模式
result = Regex.Replace(result,
@"is\s*\(\s*<\s*([\d.]+)\s+or\s+>\s*([\d.]+)\s*\)",
"< $1 || > $2");
// 单独的关系运算符
result = Regex.Replace(result, @"is\s*>\s*([\d.]+)", "> $1");
result = Regex.Replace(result, @"is\s*<\s*([\d.]+)", "< $1");
result = Regex.Replace(result, @"is\s*>=\s*([\d.]+)", ">= $1");
result = Regex.Replace(result, @"is\s*<=\s*([\d.]+)", "<= $1");
return result;
}
}
@@ -0,0 +1,86 @@
using System;
using System.Text;
using System.Text.RegularExpressions;
using CodePlay.Core.Interfaces;
namespace CodePlay.Core.Pipeline.Converters;
/// <summary>
/// C# 主构造函数转换器
/// public class Point(int x, int y) → class + constructor + fields
/// </summary>
public class PrimaryConstructorConverter : ILineConverter
{
public int Priority => 35;
public string Convert(string line, ConversionContext context)
{
// 匹配主构造函数: public class Name(params) : BaseClass or ;
var match = Regex.Match(line,
@"(public|private|internal|protected)?\s*(static\s+)?class\s+(\w+)\s*\(\s*([^)]+)\s*\)\s*(?::\s*(\w+(?:\([^)]*\))?)?\s*?)?");
if (!match.Success)
return line;
var access = match.Groups[1].Success ? match.Groups[1].Value : "public";
var isStatic = match.Groups[2].Success;
var className = match.Groups[3].Value;
var paramsStr = match.Groups[4].Value.Trim();
var baseCall = match.Groups[5].Success ? match.Groups[5].Value.Trim() : null;
// 解析参数
var params_ = paramsStr.Length > 0 ? Regex.Split(paramsStr, @",\s*") : Array.Empty<string>();
var sb = new StringBuilder();
// 生成类声明
sb.AppendLine($"{access} class {className} {{");
// 生成私有字段
foreach (var param in params_)
{
var parts = param.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 2)
{
var type = string.Join(" ", parts, 0, parts.Length - 1);
var name = parts[^1];
sb.AppendLine($" private {type} {name};");
}
}
// 生成构造函数
sb.Append($" public {className}({paramsStr})");
if (baseCall != null)
{
sb.Append($" : base({baseCall})");
}
sb.AppendLine(" {");
foreach (var param in params_)
{
var parts = param.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 2)
{
var name = parts[^1];
sb.AppendLine($" this.{name} = {name};");
}
}
sb.AppendLine(" }");
// 生成 getter 方法
foreach (var param in params_)
{
var parts = param.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 2)
{
var type = string.Join(" ", parts, 0, parts.Length - 1);
var name = parts[^1];
var camelName = char.ToLowerInvariant(name[0]) + name.Substring(1);
sb.AppendLine($" public {type} get{name}() {{ return {camelName}; }}");
}
}
return sb.ToString().TrimEnd();
}
}
@@ -0,0 +1,41 @@
using System.Text.RegularExpressions;
using CodePlay.Core.Interfaces;
using CodePlay.Core.Models;
namespace CodePlay.Core.Pipeline.Converters;
/// <summary>
/// 基本类型映射转换器 - string→String, int→Integer 等
/// </summary>
public class PrimitiveTypeConverter : ILineConverter
{
public int Priority => 20;
private readonly Dictionary<string, string> _mappings = new()
{
{ "string", "String" },
{ "int", "Integer" },
{ "long", "Long" },
{ "float", "Float" },
{ "double", "Double" },
{ "bool", "Boolean" },
{ "byte", "Byte" },
{ "char", "Character" },
{ "short", "Short" },
{ "void", "void" },
{ "var", "Object" },
{ "object", "Object" },
};
public string Convert(string line, ConversionContext context)
{
var result = line;
foreach (var (source, target) in _mappings)
{
result = Regex.Replace(result, $@"\b{Regex.Escape(source)}\b", target);
}
return result;
}
}
@@ -0,0 +1,43 @@
using System;
using System.Text;
using System.Text.RegularExpressions;
using CodePlay.Core.Interfaces;
namespace CodePlay.Core.Pipeline.Converters;
/// <summary>
/// 属性转方法转换器 - { get; set; } → 私有字段 + getter/setter
/// </summary>
public class PropertyConverter : ILineConverter
{
public int Priority => 70;
public string Convert(string line, ConversionContext context)
{
var propMatch = Regex.Match(line,
@"^(public|private|protected)\s+(static\s+)?(readonly\s+)?(\w+(?:<[^>]+>)?)\s+(\w+)\s*\{\s*get;\s*set;\s*\}$");
if (!propMatch.Success)
{
// 尝试匹配 init-only — 使用与普通属性相同的组索引
propMatch = Regex.Match(line,
@"^(public|private|protected)\s+(static\s+)?(\s+)?(\w+(?:<[^>]+>)?)\s+(\w+)\s*\{\s*get;\s*init;\s*\}$");
}
if (!propMatch.Success) return line;
var access = propMatch.Groups[1].Value;
var staticMod = propMatch.Groups[2].Success ? "static " : "";
var readonlyMod = propMatch.Groups[3].Success ? "final " : "";
var type = propMatch.Groups[4].Value;
var name = propMatch.Groups[5].Value;
var camelName = char.ToLowerInvariant(name[0]) + name.Substring(1);
var sb = new StringBuilder();
sb.AppendLine($"private {staticMod}{readonlyMod}{type} {camelName};");
sb.AppendLine($"{access} {staticMod}{type} get{name}() {{ return {camelName}; }}");
sb.AppendLine($"{access} {staticMod}void set{name}({type} value) {{ this.{camelName} = value; }}");
return sb.ToString().TrimEnd();
}
}
@@ -0,0 +1,32 @@
using System.Text.RegularExpressions;
using CodePlay.Core.Interfaces;
using CodePlay.Core.Models;
namespace CodePlay.Core.Pipeline.Converters;
/// <summary>
/// Range 和 Index 操作符转换器
/// </summary>
public class RangeIndexConverter : ILineConverter
{
public int Priority => 100;
public string Convert(string line, ConversionContext context)
{
var result = line;
result = Regex.Replace(result,
@"(\w+)\s*\[\s*(\d+)\s*\.\.\s*(\d+)\s*\]",
"$1.substring($2, $3)");
result = Regex.Replace(result,
@"(\w+)\s*\[\s*\^\s*(\d+)\s*\]",
"$1.charAt($1.length() - $2)");
result = Regex.Replace(result,
@"(\w+)\s*\[\s*\.\.\s*\]",
"$1");
return result;
}
}
@@ -0,0 +1,106 @@
using System;
using System.Text;
using System.Text.RegularExpressions;
using CodePlay.Core.Interfaces;
namespace CodePlay.Core.Pipeline.Converters;
/// <summary>
/// Record 类型转换器 - record → class + 字段 + 构造函数 + getter
/// </summary>
public class RecordConverter : ILineConverter
{
public int Priority => 5;
public string Convert(string line, ConversionContext context)
{
var simpleMatch = Regex.Match(line,
@"(public|private|internal|protected)?\s*record\s+(\w+)\s*\(\s*([^)]+)\s*\)\s*;");
if (simpleMatch.Success)
{
return ConvertSimpleRecord(simpleMatch);
}
if (line.Contains("record ") && line.Contains("("))
{
return line.Replace("record ", "class ");
}
return line;
}
private string ConvertSimpleRecord(Match match)
{
var access = match.Groups[1].Success ? match.Groups[1].Value + " " : "public ";
var className = match.Groups[2].Value;
var parameters = match.Groups[3].Value;
var sb = new StringBuilder();
sb.AppendLine($"{access}class {className} {{");
var paramParts = parameters.Split(',')
.Select(p => p.Trim())
.Where(p => !string.IsNullOrEmpty(p))
.ToList();
foreach (var param in paramParts)
{
var parts = param.Split(' ', System.StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 2)
{
var type = MapType(parts[0]);
var propName = parts[1];
var fieldName = char.ToLowerInvariant(propName[0]) + propName.Substring(1);
sb.AppendLine($" private {type} {fieldName};");
}
}
sb.AppendLine();
sb.AppendLine($" public {className}({parameters}) {{");
foreach (var param in paramParts)
{
var parts = param.Split(' ', System.StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 2)
{
var propName = parts[1];
var fieldName = char.ToLowerInvariant(propName[0]) + propName.Substring(1);
sb.AppendLine($" this.{fieldName} = {propName};");
}
}
sb.AppendLine(" }");
foreach (var param in paramParts)
{
var parts = param.Split(' ', System.StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 2)
{
var type = MapType(parts[0]);
var propName = parts[1];
var fieldName = char.ToLowerInvariant(propName[0]) + propName.Substring(1);
sb.AppendLine($" public {type} get{propName}() {{ return {fieldName}; }}");
}
}
sb.AppendLine("}");
return sb.ToString();
}
private string MapType(string type)
{
return type switch
{
"string" => "String",
"int" => "Integer",
"long" => "Long",
"float" => "Float",
"double" => "Double",
"bool" => "Boolean",
"byte" => "Byte",
"char" => "Character",
"short" => "Short",
_ => type
};
}
}
@@ -0,0 +1,79 @@
using System;
using System.Text;
using System.Text.RegularExpressions;
using CodePlay.Core.Interfaces;
namespace CodePlay.Core.Pipeline.Converters;
/// <summary>
/// C# switch 表达式转换器
/// switch { pattern => expression, _ => default } → 嵌套 if-else
/// </summary>
public class SwitchExpressionConverter : ILineConverter
{
public int Priority => 55;
public string Convert(string line, ConversionContext context)
{
if (!line.Contains("switch") && !line.Contains("=>"))
return line;
// 检测 switch 表达式赋值: var x = expr switch { ... };
var assignMatch = Regex.Match(line,
@"(\w+)\s+(\w+)\s*=\s*(.+)\s+switch\s*\{");
// 单行 switch 表达式: expr switch { pattern => val, _ => defVal }
var singleLineMatch = Regex.Match(line,
@"(.+?)\s+switch\s*\{\s*(.+?)\s*,\s*_\s*=>\s*(.+?)\s*\}");
if (singleLineMatch.Success)
{
var expr = singleLineMatch.Groups[1].Value.Trim();
var cases = singleLineMatch.Groups[2].Value.Trim();
var defaultVal = singleLineMatch.Groups[3].Value.Trim();
var parts = Regex.Split(cases, @"\s*,\s*(?=(?:(?:[^""]*""){2})*[^""]*$)(?![^()]*\))");
var sb = new StringBuilder();
sb.AppendLine("{");
if (parts.Length > 0)
{
var firstCase = Regex.Match(parts[0], @"(.+?)\s*=>\s*(.+)");
if (firstCase.Success)
{
var pattern = firstCase.Groups[1].Value.Trim();
var value = firstCase.Groups[2].Value.Trim();
sb.AppendLine($" if ({expr} == {pattern}) return {value};");
}
}
for (var i = 1; i < parts.Length; i++)
{
var caseMatch = Regex.Match(parts[i], @"(.+?)\s*=>\s*(.+)");
if (caseMatch.Success)
{
var pattern = caseMatch.Groups[1].Value.Trim();
var value = caseMatch.Groups[2].Value.Trim();
sb.AppendLine($" if ({expr} == {pattern}) return {value};");
}
}
sb.AppendLine($" return {defaultVal};");
sb.Append("}");
return sb.ToString();
}
// 多行 switch 表达式赋值: var x = expr switch { ... }
if (assignMatch.Success && line.TrimEnd().EndsWith("{"))
{
var varType = assignMatch.Groups[1].Value;
var varName = assignMatch.Groups[2].Value;
var expr = assignMatch.Groups[3].Value.Trim();
var sb = new StringBuilder();
sb.AppendLine($"{varType} {varName};");
sb.Append($"// switch expression for {varName} = {expr} switch {{...}}");
return sb.ToString();
}
return line;
}
}