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:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user