- 积分
- 10875
- 明经币
- 个
- 注册时间
- 2015-8-18
- 在线时间
- 小时
- 威望
-
- 金钱
- 个
- 贡献
-
- 激情
-
|
本帖最后由 你有种再说一遍 于 2024-9-17 22:41 编辑
# 用C#实现Lisp解释器
想Lisp转译成C#怎么进行呢?
转译编程语言本质上就是切割字符串,大家也不需要想得太高大上.
# 名词解释
CST具体语法树:
拥有全部信息,这一步利用分词器产生有序的tokens.
AST抽象语法树:
抛弃很多无用信息,例如空格/注释/语法糖/死代码消除,通过树形图获取简化的运行结构,类似于"a + b"就会变成"+ a b"函数在前.
用它的目的是为了语言和语言之间的简化转换.
c语言函数需要顺序声明才能够低访问高,而现代语言都不需要函数顺序声明,那么这一步其实构造了函数名map.
LLVM:
苹果公司发现GCC在某些方面难以满足他们的需求,包括对Objective-C的支持和集成到Xcode开发环境中的困难,所以苹果主导的一个开源工程LLVM.
它的意义在于编程语言和硬件层之间加入一个IR层(类似汇编的中间语言)进行转换,这样不同的编程语言就不用关心硬件指令的实现(硬件会更换指令地址,包括X86/X64/ARM/苹果定制ARM的实现).
梦想是美好的,实际上不同系统平台有各自的特性,所以编程语言开发者还会利用已有的C++库,例如vector的扩容大小不同,ARM没有除法器需要优化.
这些种种问题在IR层是处理不了的.
不过我们其实不会用到它,因为我们转译只需要对接微软的接口,在.net上面中间层叫MSIL.
如果是用AOT编译,那么微软其实会把MSIL转译成LLVM的IR上面.并且C#有了AOT编译之后,微软自举部分的编译代码.
LLVM的AST实际上长这样:
`-FunctionDecl 0x7fc02585a228 <test.c:1:1, line:3:1> line:1:5 main 'int ()'
`-CompoundStmt 0x7fc02585a340 <col:12, line:3:1>
`-ReturnStmt 0x7fc02585a330 <line:2:5, col:12>
`-IntegerLiteral 0x7fc02585a310 <col:12> 'int' 0
想知道含义的可以去粘贴问问AI.
参考:https://zhuanlan.zhihu.com/p/161626997
# 0x01参考项目-mal
使用75种语言编写一个Lisp解释器:
https://www.jianshu.com/p/8764dd49ac2d
直接进入它的C#项目:
https://github.com/kanaka/mal/tree/master/impls/cs
一个参考的C语言实现的例子:
https://ksco.gitbooks.io/build-your-own-lisp/content/Parsing.html
# 0x02参考项目-JS文章
参考这篇JS文章很容易就知道语言转译顺序了.
https://zhuanlan.zhihu.com/p/140889954
整体流程就是:
LispCode->LispTokens->LispAST
->CSharpAST->CSharpCode
->MSIL(dll/exe)->LLVM_IR(无GC的dll/exe)
具体步骤:
1,Lisp代码进入分词器,构造一个有序tokens数组.
2,顺序遍历tokens,构造Lisp的AST (Lisp几乎等价于AST,不然为什么说它除了汇编之外最简单呢).
3,LispAST可以很少差异地转换为其他语言的AST,也就是转换LispAST为C#AST.
3.1,链接器
生成C#AST之前执行:
`mal`和`JS文章`都缺少处理一些AutoLisp的setq/宏等符号需要补充.
遍历LispAST每个节点,标记函数签名(函数签名是包含参数),并加入对应容器:LispFuncMap/CsFuncMap/CFuncMap/ComMap.
其实此时只会知道两种签名,一种是Lisp函数,一种是未知,假设未知都是调度外部函数(FFI).
我们可以提前导出FFI载入Map,就可以分离出哪些是真正的未知,然后报错.
COM(COM+/ActiveX)是跨进程的,可以调用html和excel,所以无法导出全部的COM函数表.
分为两步:
a,制作acad专用的vla/vlax用tlbexp.exe工具导出,然后映射成后绑.就可以加入FFI Map进行函数提示.
工具链接:https://learn.microsoft.com/zh-c ... pe-library-exporter
b,直接提供后绑app.invoke函数,这也是vlax的功能.
3.2,优化器
优化器既需要在LispAST做,也需要在C#AST做,这取决于转换复杂性.
a,生成C#AST之前执行:
宏(小撇')展开转换为Lisp函数.
提供特殊符号给循环continue和break.
死代码消除
b,生成C#AST之后执行:
常量折叠,循环展开,内联函数,SIMD扩展.
c,加入新的语法规则,
函数头上面加入注解
;[此函数系统变量改了后末尾自动恢复,系统变量()]
;[栈帧变量自动捕获并释放(全局)]
Jig模式补充??
加入反射自身注解...嘿嘿...似乎会诞生一门方言.
提供array/hashset/map/有序结构.
要不弄个一键转原生Lisp.
**动态语言特性,类型推断**
一个语言可以是弱类型又可以是动态类型的.
弱类型:类型隐式转换,例如JS的: "A"+1 == "A1".
动态类型:运行时检查.这样会令简单的A+B面临各种检查.
为了降低这种检查,人们发明了JIT特化,例如运行5次之后收集了运行时间,JIT将构造一个函数进行调用,以提高命中率加速时间.https://b23.tv/EFYy1Kg
把Lisp转为C#就会面临动态转静态的难题.
https://stackoverflow.org.cn/questions/53425328
要先进行类型推导,然后标记类型,然后每个类型生成函数(不能生成模板而是直接展开,像C语言一样直接生成对应的函数).
这个工作可以参考开源项目.
实际上类型推断大部分是失败的,成为了object了,只能运行时求类型.
例如:同一个变量改为表之后可以改为数值,LispAST可以看见上面描述的行为.
分情况处理:
a:变量有类型改变.
如果变量是分离的,然后入参之后再改变.那么可以把第二个变量改名**
b:变量没有类型改变.
可以进行类型标记.
如果根据入参确定,Lisp入参是支持动态类型的.动态类型是运行时确定的,无法标记,
即使追溯到原生函数的参数类型标记,然后把过程链上面每个都标记.如果遇到已有标记,那么就需要生成新的函数,实现重载参数.
如果是动态那么它原生也是动态啊.
**泛型**
数值类型的类型提升:
python整数类型它是无限精度的BigInteger,它是字符串记录的,用时间换空间...如果要做优化,可以命中Int64运算,如果溢出再回到BigInteger,用基础数值类型提高效率.https://b23.tv/EFYy1Kg
Lisp没有,只是Int32/double.采用截断吗?还是报溢出呢?
AOT编译要确定类型的啊,岂不是每次运行之后都要判断是否溢出?然后执行数值类型提升?
4.C#的AST转为C#代码.
`JS文章`是C语言例子,只是调换了函数名出现的位置.
我们实际上需要用Roslyn提供的类去构造C#的AST和转为Code,但它本质就是一个巨大的字符串而已.
5,编译C#.
5.1,JIT动态编译,根据编译选项,导出文件就是.net的exe/dll了,也可以不导出直接在acad内存里执行.
5.2,AOT编译:利用AOT语法树进行编译,那么可以生成没有GC的exe/dll.
优化建议:
能不能并行SIMD分词呢?
学十亿行天文台数据挑战,直接返回token下标.
Java版本思路描述更好:
https://segmentfault.com/a/1190000044679461
c#版本:
https://www.cnblogs.com/InCerry/ ... aster-than-java-cpp
SimdJson项目:它在处理微小Json数据2048字节是通过栈帧内存进行的,并且还用了一些零拷贝/内存映射的方法加速.
https://www.zhihu.com/question/313943804
以下是`JS文章`代码翻译成CSharp,仅为原理参考,不保证编译能够通过.
## 调用例子
```c#
public class Program {
public static void Main() {
string lispCode = "(add 2 (\"string\" 42)";
// 分词
var tz = new Tokenizer();
tokenizer.Tokenize(lispCode);
// code to AST
var parser = new Parser(tz.Tokens.ToArray());
var lispAst = parser.Parse();
// LispAST to CSharpAST
var transformer = new Transformer();
var csharpAst = transformer.Transform(lispAst);
// 生成c#代码
var codeGenerator = new CodeGenerator();
string csharpCode = codeGenerator.GenerateCode(csharpAst);
// 打印结果验证
Console.WriteLine(csharpCode);
}
}
```
## CST具体语法树
分词器把LispCode转Tokens
```c#
// 定义接口
public interface IToken {
string Type { get; }
string Value { get; }
}
// 行号,列号,为了并行构造tokens,需要它们进行排序.并且需要提示语法错误位置
public class Token : IToken {
public string Type {get;} // 节点类型
public string Value {get;}// 节点值
public List<Node> Children; // 子节点列表
public Node Parent; // 父节点
public int Line; // 行号
public int Column;// 列号
public Dictionary<string, object> Attributes;// 其他属性
public Token (string type, string value) {
Type = type;
Value = value;
Children = new ();
Attributes = new ();
}
public override string ToString() {
return $"行:{Line} 列{Column} Type: {Type} Value: {Value}";
}
}
public class Tokenizer {
// 有序结构,存储分词结果的列表
public List<IToken> Tokens = new();
// 分词器,输入lisp代码
public void Tokenize(string input/*lispcode*/) {
// 遍历位置指针
int current = 0;
while (current < input.Length) {
char ch = input[current]; // 当前字符
// 处理左括号
if (ch == '(') {
Tokens.Add(new Token("paren", "("));
current++; continue; }
// 处理右括号
if (ch == ')') {
Tokens.Add(new Token("paren", ")"));
current++; continue; }
// 跳过无用字符
if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r') { current++; continue; }
// 处理数字,数字字面量0x 0b 1e-10..负数
var num = GetNumberRange(input,ref current);
if(mum is not null) {
Tokens.Add("number",mum);
current++; continue; }
// 处理字符串,包括转义字符
if (ch == '"') {
StringBuilder value = new();
ch = input[++current]; // 跳过开头的双引号
while (current < input.Length && ch != '"') {
if (ch == '\\' && input[current + 1] == '"') {
value.Append('\\');
value.Append('"');
current += 2; // 跳过转义
continue;
}
value.Append(ch);
ch = input[++current];
}
Tokens.Add(new Token("string", value.ToString()));
current++; // 跳过结尾的双引号
continue;
}
// 处理宏
// 分词时候不处理,AST把它作为函数或者数组,给前面的表达式.岂不是传递函数指针?
// 这里很复杂: '((1 .0)) 要把后面整个表取走?
// 还有什么地方呢?
if(ch=='\''){
Tokens.Add(new Token("quote","'"));
}
// 处理标识符和关键字,可以添加不同的前缀
// 控制流关键字 if while progn
// 用户定义的函数
if ((char.IsLetter(ch) || ch == '_') && current < input.Length) {
StringBuilder value = new();
while (current < input.Length && (char.IsLetterOrDigit(ch) || ch == '_')) {
value.Append(ch);
ch = input[++current];
}
Tokens.Add(new Token("name", value.ToString()));
continue;
}
// 无法识别的字符
throw new Exception($"分词器错误,异常符号: {ch}");
}
}
}
public class NumberParser {
// 获取数字字面量
public static string? GetNumberRange(char[] input, ref int current) {
StringBuilder numberRange = new StringBuilder();
if (TryParseNumber(input, ref current, numberRange))
return numberRange.ToString();
return null;
}
private static bool TryParseNumber(char[] input, ref int index, StringBuilder numberRange) {
if (index >= input.Length) return false;
if (input[index] == '-') {
numberRange.Append('-');
index++;
if (index >= input.Length) return false;
}
if (input[index] == '0') {
index++;
if (index < input.Length && (input[index] == 'x' || input[index] == 'X')) {
index++;
if (!ParseHex(input, ref index, numberRange)) return false;
}
else if (index < input.Length && (input[index] == 'b' || input[index] == 'B')) {
index++;
if (!ParseBinary(input, ref index, numberRange)) return false;
}
else
{
if (!ParseDecimal(input, ref index, numberRange)) return false;
}
}
else
{
if (!ParseDecimal(input, ref index, numberRange)) return false;
}
return numberRange.Length > 0;
}
private static bool ParseHex(char[] input, ref int index, StringBuilder numberRange) {
while (index < input.Length) {
char c = input[index++];
if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))) { index--; break; }
numberRange.Append(c);
}
return numberRange.Length > 0;
}
private static bool ParseBinary(char[] input, ref int index, StringBuilder numberRange) {
while (index < input.Length) {
char c = input[index++];
if (c != '0' && c != '1') { index--; break; }
numberRange.Append(c);
}
return numberRange.Length > 0;
}
private static bool ParseDecimal(char[] input, ref int index, StringBuilder numberRange) {
while (index < input.Length) {
char c = input[index++];
if (!char.IsDigit(c) && c != '.' && (c != 'e' && c != 'E' || (index == 1 && numberRange.Length == 0))) { index--; break; }
if (c == 'e' || c == 'E') {
if (index < input.Length && (input[index] == '+' || input[index] == '-')) index++;
while (index < input.Length && char.IsDigit(input[index])) index++;
index--;
}
numberRange.Append(c);
}
return numberRange.Length > 0;
}
}
// 单元测试
class Program {
static void Main() {
char[] input = "-0x1A3E".ToCharArray();
int current = 0;
string result = NumberParser.GetNumberRange(input, ref current);
Console.WriteLine(result); // 输出: -0x1A3E
Console.WriteLine("Current index after parsing: " + current); // 输出解析后的当前索引
}
}
```
## Tokens转Lisp的AST
节点定义,为什么同类型容器呢?这样就不用写名字了吧.
```c#
// 定义抽象语法树节点的接口
public interface IASTNode {
string Type { get; }
}
// 数字字面量的节点
public class NumberLiteral : IASTNode {
public string Type { get; } = "NumberLiteral";
public object Value { get; }
public NumberLiteral(object value) {
Value = value;
}
}
// 字符串字面量的节点
public class StringLiteral : IASTNode {
public string Type { get; } = "StringLiteral";
public object Value { get; }
public StringLiteral(object value) {
Value = value;
}
}
// 调用表达式的节点
public class CallExpression : IASTNode {
public string Type { get; } = "CallExpression";
public string Name { get; }
public List<IASTNode> Params { get; } = new();
public CallExpression(string name) {
Name = name;
}
}
// 变量赋值的节点,用于表示LISP中的变量赋值操作
public class Assignment : IASTNode {
public string Type { get; } = "Assignment";
public string Variable { get; } // 变量名
public IASTNode Value { get; } // 赋值的值
public Assignment(string variable, IASTNode value) {
Variable = variable;
Value = value;
}
}
// 函数定义节点,用于表示LISP中的函数定义
public class FunctionDefinition : IASTNode {
public string Type { get; } = "FunctionDefinition";
public string Name { get; } // 函数名
public List<string> Parameters { get; } // 函数参数列表
public IASTNode Body { get; } // 函数体
public FunctionDefinition(string name, List<string> parameters, IASTNode body) {
Name = name;
Parameters = parameters;
Body = body;
}
}
// AST抽象语法树的根节点
// ...怎么不是左右节点形成树呢? eval实现:https://www.cnblogs.com/lsdsjy/p/licp-the-interpreter.html
public class Program : IASTNode {
public string Type { get; } = "Program";
public List<IASTNode> Body { get; } = new();
}
```
```c#
// 解析器类
public class Parser {
private readonly IToken[] tokens;
private int current = 0;
public Parser(IToken[] tokens) {
this.tokens = tokens;
}
private IASTNode Walk() {
var token = tokens[current++];
// 数字
if (token.Type == "number") {
return new NumberLiteral(token.Value);
}
// 字符串
if (token.Type == "string") {
return new StringLiteral(token.Value);
}
// 带括号的表达式
if (token.Type == "paren" && token.Value.Equals('(')) {
token = tokens[current++];
var node = new CallExpression(token.Value.ToString());
token = tokens[current++];
while (token.Type != "paren" || (token.Type == "paren" && token.Value.Equals('('))) {
// 进一步遍历
node.Params.Add(Walk());
token = tokens[current];
}
current++;
return node;
}
throw new InvalidOperationException($"Unexpected token type: {token.Type}");
}
// 生成Lisp的AST,返回根节点
public Program Parse() {
var ast = new Program();
while (current < tokens.Length) {
ast.Body.Add(Walk());
}
return ast;
}
}
```
## 将LISP的AST转译为C#的AST
两种语言存在语法差别,需要转换.
```c#
public class Transformer {
public static Program Transform(IASTNode lispAst) {
var newAst = new Program();
TraverseNode(lispAst, null, newAst);
return newAst;
}
// 递归遍历Lisp AST节点,并转译为C# AST节点(文章是c语言,所以要需要修改)
private static void TraverseNode(IASTNode node, IASTNode parent, Program newAst) {
switch (node.Type) {
case "NumberLiteral":
newAst.Body.Add(new NumberLiteral(((NumberLiteral)node).Value));
break;
case "StringLiteral":
newAst.Body.Add(new StringLiteral(((StringLiteral)node).Value));
break;
case "CallExpression":
var callExpr = (CallExpression)node;
var csharpCallExpr = new CallExpression(callExpr.Name) {
Params = callExpr.Params.Select(param => (IASTNode)TraverseNode(param, node, newAst)).ToList()
};
newAst.Body.Add(csharpCallExpr);
break;
case "Assignment":
var assignment = (Assignment)node;
newAst.Body.Add(new Assignment(assignment.Variable, TraverseNode(assignment.Value, node, newAst)));
break;
case "FunctionDefinition":
var function = (FunctionDefinition)node;
var csharpFunction = new FunctionDefinition(function.Name, function.Parameters, TraverseNode(function.Body, node, newAst));
newAst.Body.Add(csharpFunction);
break;
default:
throw new InvalidOperationException($"Unknown node type: {node.Type}");
}
}
}
```
## 用AST生成c#代码,这一步应该用动态编译,或者AOT编译
public class CodeGenerator {
public static string GenerateCode(Program ast) {
StringBuilder code = new();
foreach (var node in ast.Body) {
switch (node.Type) {
case "NumberLiteral":
code.AppendLine($"int number = {((NumberLiteral)node).Value};");
break;
case "StringLiteral":
code.AppendLine($"string text = \"{((StringLiteral)node).Value}\";");
break;
case "CallExpression":
var callExpr = (CallExpression)node;
code.AppendLine($"{callExpr.Name}({string.Join(", ", callExpr.Params.Select(p => p.ToString()))});");
break;
case "Assignment":
var assignment = (Assignment)node;
code.AppendLine($"{assignment.Variable} = {assignment.Value};");
break;
case "FunctionDefinition":
var function = (FunctionDefinition)node;
code.AppendLine($"void {function.Name}({string.Join(", ", function.Parameters)}) {{");
code.AppendLine($" // Function body");
code.AppendLine("}");
break;
default:
throw new InvalidOperationException($"Unknown node type: {node.Type}");
}
}
return code.ToString();
}
}
```
# CSharp的AST例子
微软参考链接:
https://learn.microsoft.com/en-u ... ted/syntax-analysis
C#的处理不是code to tokens to AST,然后逆转.
而是code to SyntaxTree(CST具体语法树),就可以to其他了.
在C#中,使用Roslyn编译器平台可以分析和操作语法树.
语法树是由各种语法节点组成的,这些节点代表了代码中的不同结构.
以下是一些常见的Roslyn语法树节点类型及其含义:
1.CompilationUnit:整个源文件的根节点.
2.NamespaceDeclaration:命名空间声明.
3.ClassDeclaration:类声明.
4.MethodDeclaration:方法声明.
5.PropertyDeclaration:属性声明.
6.FieldDeclaration:字段声明.
7.VariableDeclaration:变量声明.
8.UsingDirective:导入using指令.
9.SyntaxToken:独立的关键词/标识符/操作符/标点.
10.SyntaxTrivia:语法上不重要的信息,例如空白/预处理指令和注释.
这些节点构成了语法树的结构,允许开发者进行代码分析/重构/生成等操作.
例如,可以通过遍历语法树来查找特定的代码模式,或者生成新的代码结构.
在Roslyn中,语法树的节点是通过继承自SyntaxNode的类来表示的.每个节点都可以包含子节点,这些子节点可以是其他SyntaxNode对象,或者是SyntaxToken和SyntaxTrivia对象.
这样的结构使得语法树能够详细地表示源代码的结构.
Roslyn还提供了语义模型(SemanticModel),
它与语法树配合使用,可以提供关于代码元素的语义信息,例如类型信息/符号信息等.这使得开发者能够进行更深入的代码分析和操作.
## 例子一
代码转CST,再转其他
```c#
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace RoslynExample {
class Program {
static void Main(string[] args) {
// 原始的C#源代码字符串
string originalCode = @"
using System;
namespace HelloWorld
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}
";
// 解析源代码字符串为CST
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(originalCode);
// CST中获取所有的Tokens
IEnumerable<SyntaxToken> tokens = syntaxTree.GetRoot().DescendantTokens();
Console.WriteLine("Tokens:");
foreach (var token in tokens)
{
Console.WriteLine($"Token: {token.Text}, Type: {token.Kind()}");
}
// 获取AST的根节点
CompilationUnitSyntax compilationUnit = syntaxTree.GetCompilationUnitRoot();
// 将AST转换回源代码字符串
string generatedCode = compilationUnit.ToFullString();
Console.WriteLine("\nGenerated Code:");
Console.WriteLine(generatedCode);
Console.ReadKey();
}
}
}
```
## 例子二
AST语法树转代码,或者转CST再转其他.
```c#
using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace RoslynReverseExample {
class Program {
static void Main(string[] args) {
// 创建一个命名空间节点,名称为"HelloWorld"
var namespaceDeclaration = SyntaxFactory.NamespaceDeclaration(
SyntaxFactory.Identifier("HelloWorld"));
// 创建一个公共类Program的声明
var classDeclaration = SyntaxFactory.ClassDeclaration("Program")
.WithModifiers(
// 设置类为public
SyntaxFactory.TokenList(
SyntaxFactory.Token(SyntaxKind.PublicKeyword)));
// 创建一个Main方法的声明
var methodDeclaration = SyntaxFactory.MethodDeclaration(
// 方法返回类型为void
SyntaxFactory.PredefinedType(
SyntaxFactory.Token(SyntaxKind.VoidKeyword)),
"Main")
.WithModifiers(
// 设置方法为static
SyntaxFactory.TokenList(
SyntaxFactory.Token(SyntaxKind.StaticKeyword)))
.WithParameterList(
// 添加一个参数args,类型为string数组
SyntaxFactory.ParameterList(
SyntaxFactory.SingletonSeparatedList(
SyntaxFactory.Parameter(
SyntaxFactory.Identifier("args"))
.WithType(
SyntaxFactory.ArrayType(
SyntaxFactory.PredefinedType(
SyntaxFactory.Token(SyntaxKind.StringKeyword)),
SyntaxFactory.SingletonList(
SyntaxFactory.ArrayRankSpecifier(
new [] { SyntaxFactory.OmittedArraySizeExpression() }))))))));
// 创建一个Console.WriteLine的调用表达式
var writeLineExpression = SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.IdentifierName("Console"),
SyntaxFactory.IdentifierName("WriteLine")))
.WithArgumentList(
SyntaxFactory.ArgumentList(
SyntaxFactory.SingletonSeparatedList(
SyntaxFactory.Argument(
SyntaxFactory.LiteralExpression(
SyntaxKind.StringLiteralExpression,
SyntaxFactory.Literal("Hello, World!"))))));
// 创建一个表达式语句,将writeLineExpression包装成语句
var writeLineStatement = SyntaxFactory.ExpressionStatement(writeLineExpression);
// 为Main方法添加方法体,包含writeLineStatement
var methodWithBody = methodDeclaration.WithBody(SyntaxFactory.Block(writeLineStatement));
// 将Main方法添加到Program类中
var classWithMainMethod = classDeclaration.WithMembers(SyntaxFactory.SingletonList<MemberDeclarationSyntax>(methodWithBody));
// 将Program类添加到命名空间中
var namespaceWithClass = namespaceDeclaration.WithMembers(
SyntaxFactory.SingletonList<NamespaceMemberSyntax>(classWithMainMethod));
// 将AST节点转换为源代码
var code = namespaceWithClass.NormalizeWhitespace().ToFullString();
// 打印生成的源代码
Console.WriteLine(code);
Console.WriteLine("===========");
// 如果要转为CST
// SyntaxFactory.CompilationUnit是编译单元,表示一个代码文件.
// 创建语法树的根节点
var compilationUnit = SyntaxFactory.CompilationUnit()
.WithMembers(namespaceWithClass) // 添加命名空间声明到编译单元
.NormalizeWhitespace(); // 标准化空白字符
// 转换为CST的SyntaxTree
SyntaxTree syntaxTree = CSharpSyntaxTree.Create(compilationUnit);
// 从SyntaxTree获取源代码字符串
string generatedCode = syntaxTree.GetCompilationUnitRoot().ToFullString();
// 打印生成的源代码
Console.WriteLine(generatedCode);
// 这样就能对上了
// 获取AST的根节点
// CompilationUnitSyntax compilationUnit = syntaxTree.GetCompilationUnitRoot();
// 将AST转换回源代码字符串
// string generatedCode = compilationUnit.ToFullString();
Console.ReadKey();
}
}
}
```
(完) |
评分
-
查看全部评分
|