dotnet 用 SourceGenerator 源代码生成技术实现中文编程语言
相信有很多伙伴都很喜欢自己造编程语言,在有现代的很多工具链的帮助下,实现一门编程语言,似乎已不是一件十分困难的事情。我利用 SourceGenerator 源代码生成技术实现了一个简易的中文编程语言,核心原理是将中文编程语言翻译为 C# 语言,从而完成后续的所有对接,完成了最简单的构建和运行。本文将告诉大家这个有趣的方式是如何实现
开始之前,先给大家看看效果
这是我设计的 csg 格式(Chinese programming language by SourceGenerator)的中文编程语言,设计上完全参考(抄袭)了中文宏的实现方式。原本我是考虑抄袭 易语言 的,但是 易语言 更贴近是 VB 系的方式(? 似乎也不能这么说)感觉不是我随便就能写出来的。我只是想着学习源代码生成技术,顺带测试一下自己能否很随意的就写出一个新的编程语言。当然,测试结果是我不能很随意就写出一个新的编程语言
本文所设计的 csg 格式的中文编程语言,仅仅只能用来做演示使用,丝毫不能用在实际项目里。本文仅仅只是用来告诉大家一个简易的方法来完成自己创建一门编程语言
本文所设计的 csg 格式的中文编程语言,能够和 C# 完美的结合,毕竟实际参与构建的就是 C# 代码。我在本文的最后给出了所有的代码的下载方式,要求在 VS 2022 较新版本上才能成功运行
以下是 csg 的代码,也是本文效果里所使用的代码
引用命名空间 系统;
定义命名空间 这是一个命名空间;
类型 这是测试类型
{
公开的 静态的 无返回值类型的 测试输出()
{
控制台.输出一行文本("你好");
}
}
可以看到,这是全部采用中文编写的一段代码。相信大家看到上面的代码,在熟悉 C# 的前提下,能反应过来这段代码的作用
尽管这是采用中文编写的,但不代表着任何人都能读懂这段代码的作用。因为这仅仅只是使用中文对 C# 的关键词进行翻译而已。同理的,也不是任何会英文的人都能读懂代码
那以上代码可以被如何调用呢?可以完全和 C# 交互,被 C# 直接调用,如以下代码,在 C# 代码的主函数里面调用 测试输出()
方法。这是利用了 C# 里面允许标识符支持 Utf-8
编写,而不仅仅是 ASCII 编码的字符。换句话说是使用中文作用方法名、类名、属性名等,在 C# 里都是合法的
// Program.cs
using 这是一个命名空间;
这是测试类型.测试输出();
以上是采用 C# 9.0 新特性——顶级语句,无须加上类型和主函数定义,直接编写代码体即主函数执行代码体的。如此可以极大简化代码量
执行代码,可以看到控制台输出了 你好
字符串,证明了代码的构建执行正常
接下来将告诉大家实现的原理和实现的细节方法,在开始之前,期望大家已对 C# dotnet 的基础知识熟悉,对 dotnet 整个构建过程熟悉,了解源代码生成技术,本文将略过基础知识
先新建两个项目,分别是 JelallnalukebaqeLairjaybearjair 和 JelallnalukebaqeLairjaybearjair.Analyzers 两个控制台项目。其中 JelallnalukebaqeLairjaybearjair 项目就是用来编写中文编程的项目。而 JelallnalukebaqeLairjaybearjair.Analyzers 是一个分析器项目,将在此项目里编写源代码生成逻辑,用来支持将编写的中文代码转换为 C# 代码,从而参与后续的构建和执行
在 JelallnalukebaqeLairjaybearjair 项目里,将对 JelallnalukebaqeLairjaybearjair.Analyzers
项目进行引用,从而用来启动此分析器的内容。添加引用时设置 OutputItemType 为 Analyzer 类型,且设置不使用不引用 JelallnalukebaqeLairjaybearjair.Analyzers 程序集。引用之后的 JelallnalukebaqeLairjaybearjair 项目的 csproj 项目文件的引用代码如下
<ItemGroup>
<ProjectReference Include="..\JelallnalukebaqeLairjaybearjair.Analyzers\JelallnalukebaqeLairjaybearjair.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
在本文的例子里,在 JelallnalukebaqeLairjaybearjair 项目里只有两个文件,一个是 Program.cs 文件,一个是 这是测试类型.csg
文件。其中 Program.cs 文件就是传统的 C# 项目,采用 C# 9.0 的顶层语句,编写的代码如下
using 这是一个命名空间;
这是测试类型.测试输出();
而 这是测试类型.csg
文件里的内容就是本文开头的中文代码内容
接着,为了让分析器能了解到 这是测试类型.csg
文件是需要参与构建的,额外在 JelallnalukebaqeLairjaybearjair 的 csproj 项目文件里面添加 AdditionalFiles 列表。通过 AdditionalFiles 列表,可以在后续的分析器里面,在增量构建里,通过 AdditionalTextsProvider 监听获取到这部分文件内容。编辑 JelallnalukebaqeLairjaybearjair 的 csproj 项目文件,添加如下代码
<ItemGroup>
<AdditionalFiles Include="这是测试类型.csg" />
</ItemGroup>
以上就是 JelallnalukebaqeLairjaybearjair 项目的所有文件和核心逻辑了。完成了准备工作之后,开始编写 JelallnalukebaqeLairjaybearjair.Analyzers
分析器项目。为了能够在 Visual Studio 里面加载上分析器,以及同时在 dotnet 命令行里加载分析器,设置 TargetFramework 为 .NET Standard 2.0 版本。因为 Visual Studio 采用的是 .NET Framework 运行时,而 dotnet 命令行工具采用的是 .NET Core 运行时,于是分析器采用 .NET Standard 2.0 版本就能刚好在这两个运行时加载
为了编写分析器项目,按照惯例,还需要引用必要的 NuGet 包。这里需要引用 Microsoft.CodeAnalysis.Analyzers 和 Microsoft.CodeAnalysis.CSharp 程序集
编辑 JelallnalukebaqeLairjaybearjair.Analyzers 的 csproj 项目文件为如下代码
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" />
</ItemGroup>
</Project>
完成了安装库之后,即可开始编写核心代码。需求是将 csg 格式的中文编程语言,转换为 C# 代码,从而参与后续的构建和执行
新建一个叫 CsgIncrementalGenerator 类型,继承 IIncrementalGenerator 接口,顺带加上 GeneratorAttribute 特性标识这是生成 C# 代码的。类型名可以自己发挥,只是本文作为例子叫成 CsgIncrementalGenerator 而已
[Generator(LanguageNames.CSharp)]
public class CsgIncrementalGenerator : IIncrementalGenerator
{
// 忽略代码
}
继承 IIncrementalGenerator 接口,需要实现 public void Initialize(IncrementalGeneratorInitializationContext context)
方法。如 尝试 IIncrementalGenerator 进行增量 Source Generator 生成代码 博客所述,在进行增量构建时,只有 Initialize 方法。在 Initialize 方法里面,加上分析器感兴趣的文件以及对这些文件的处理方法即可
咱这里的中文编程语言采用后缀名为 .csg
的文件,在 JelallnalukebaqeLairjaybearjair 项目里也将 csg 文件在 csproj 项目文件里添加到 AdditionalFiles 列表里面。在 Initialize 方法里面,先告诉分析器感兴趣的文件就是 csg 文件,只有有 csg 文件的变更,那将自动触发更新逻辑,在更新逻辑里执行实际的转换代码
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var csgFileIncrementalValuesProvider =
context.AdditionalTextsProvider.Where(t =>
string.Equals(Path.GetExtension(t.Path), ".csg", StringComparison.OrdinalIgnoreCase));
// 忽略文件
}
以上代码的 AdditionalTextsProvider 不是实际立刻提供了文件,而是用来编写文件变更时的过滤命令,这也是增量代码生成的核心逻辑。通过编写过滤命令的方式,可以减少代码生成实际转换逻辑的执行次数,只有在遇到感兴趣的文件的变更的时候才会触发实际的执行逻辑,从而极大的提升性能
接下来将此过滤条件加入注册,在过滤条件 csgFileIncrementalValuesProvider
能过滤出有文件变更时,将执行转换代码。转换代码的输入是 csg 中文编程语言的代码文件,输出是加入到构建的 C# 的代码字符串
通过 RegisterSourceOutput 方法进行注册,注册在满足 csgFileIncrementalValuesProvider
过滤条件时,支持添加额外的参与构建代码
context.RegisterSourceOutput(csgFileIncrementalValuesProvider, (sourceProductionContext, csg) =>
{
// 忽略代码
});
在 RegisterSourceOutput 的开始,是先注册框架部分的代码,如上面的中文代码,可以看到用到了一些需要预设的框架代码,例如 控制台.输出一行文本("你好");
这句代码就需要先有预设的名为 控制台
的类型。先添加框架代码如下
context.RegisterSourceOutput(csgFileIncrementalValuesProvider, (sourceProductionContext, csg) =>
{
AddFrameworkCode(sourceProductionContext);
// 忽略代码
});
这里拿到的 sourceProductionContext
参数,可以用来设置构建的生成代码。在 AddFrameworkCode 里面,添加框架需要的预设代码,代码如下
/// <summary>
/// 添加框架代码
/// </summary>
/// <param name="sourceProductionContext"></param>
private static void AddFrameworkCode(SourceProductionContext sourceProductionContext)
{
string consoleText = @"
using System;
namespace 系统;
static class 控制台
{
public static void 输出一行文本(string 文本)
{
Console.WriteLine(文本);
}
}";
sourceProductionContext.AddSource("DefaultConsole", consoleText);
}
本文这里只添加了用来演示的名为 控制台
的类型,添加方法如上代码。以上代码将会在项目里,添加一个叫做 DefaultConsole
的生成代码,如此即可让中文编程代码里有可以使用的控制台辅助类型
接下来是获取到发生变更的 csg 中文编程语言的文件的内容,用来转换为 C# 代码
context.RegisterSourceOutput(csgFileIncrementalValuesProvider, (sourceProductionContext, csg) =>
{
AddFrameworkCode(sourceProductionContext);
var csgSource = csg.GetText();
if (csgSource == null) return;
// 忽略代码
});
通过 GetText 即可获取到其文本内容
获取到内容之后,需要将 csg 中文编程语言的内容转换为 C# 代码字符串内容。我这里抄袭了中文宏的方法,使用关键词替换。本文这里只是替换了演示所需要的关键词,没有对其他的关键词进行替换
var keyDictionary = new Dictionary<string, string>()
{
{"引用命名空间 ","using "},
{"定义命名空间 ","namespace "},
{"类型 ","class "},
{"公开的 ","public "},
{"静态的 ","static "},
{"无返回值类型的 ","void "},
};
var stringBuilder = new StringBuilder();
foreach (var textLine in csgSource.Lines)
{
var text = textLine.ToString();
if (!string.IsNullOrEmpty(text))
{
foreach (var keyValuePair in keyDictionary)
{
text = text.Replace(keyValuePair.Key, keyValuePair.Value);
}
}
stringBuilder.AppendLine(text);
}
如此一行行进行替换,即可拿到一段 C# 代码
将 stringBuilder
里的 C# 代码作为生成代码,添加到 sourceProductionContext
用于参与构建
sourceProductionContext.AddSource(Path.GetFileNameWithoutExtension(csg.Path) + ".g.cs", stringBuilder.ToString());
添加的时候,设置了 hintName
参数为 Path.GetFileNameWithoutExtension(csg.Path) + ".g.cs"
如此即可在相同的一个 csg 文件变更的时候,生成的代码可以替换旧的生成代码。生成代码之间的替换就是采用 hintName
参数作为判断条件
如此即可完成将 csg 中文编程语言转换为 C# 代码,且加入到构建里
本文只是作为一个演示,告诉大家可以利用 Source Generator 技术,将中文编程语言转换为 C# 代码,方便的加入到构建里,从而复用整个 dotnet 的机制
可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin bba0c728bbc1d850f6f1929ab14a42e995e23e3b
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin bba0c728bbc1d850f6f1929ab14a42e995e23e3b
获取代码之后,进入 JelallnalukebaqeLairjaybearjair 文件夹
更多增量构建请看 尝试 IIncrementalGenerator 进行增量 Source Generator 生成代码
更多编译器、代码分析、代码生成相关博客,请参阅我的 博客导航
dotnet 用 SourceGenerator 源代码生成技术实现中文编程语言的更多相关文章
- 在Linux上编译dotnet cli的源代码生成.NET Core SDK的安装包
.NET 的开源,有了更多的DIY乐趣.这篇博文记录一下在新安装的 Linux Ubuntu 14.04 上通过自己动手编译 dotnet cli 的源代码生成 .net core sdk 的 deb ...
- Javaweb 使用Servlet技术改写用户登录 使用Filter技术解决中文乱码
先把实验3的jsp页面复制过来: WebContent->WEB-INF->lib下面的jar包8.0版本也要记得复制: Java Resources->src下的 cn.edu.h ...
- 源代码jar包中中文注释乱码
目前公司开发的多个组件有打包源代码并发布到nexus,但是很多同事通过maven使用组件时,直接通过eclipse浏览源代码时,发现中文注释为乱码的问题.其实这个eclipse默认编码造成的问题.可以 ...
- Python 生成的页面中文乱码问题
第一 保证 程序源文件里的中文的编码格式,如我们把 源文件的编码设置成utf8的. reload(sys) sys.setdefaultencoding('utf-8') 第二, 告诉浏览器,我们须要 ...
- CSS border三角、圆角图形生成技术简介
http://www.zhangxinxu.com/wordpress/?p=794 一.前言 利用CSS的border属性可以生成一些图形,例如三角或是圆角.纯粹的CSS2的内容,没有兼容性的问题, ...
- PHP源代码生成 main/config.w32.h
PHP源代码生成 main/config.w32.h 1.下载php源代码包php-5.4.0.tar.gz,解压到D:\php-5.4.0 2.下载2个必要的包http://xiazai.jb51. ...
- 怎样用Eclipse将Java源代码生成可执行文件[转]
eclipse将java源代码生成jar可执行文件 用eclipse做了一个web项目的自动化测试,自己用的时候倒是很方便,打开eclipse直接运行即可,但是分享给其他小伙伴用的时候就不太方便,希望 ...
- vs2015 生成 cordova 页面中文乱码
原文:vs2015 生成 cordova 页面中文乱码 1.用VS2015新创建Cordova项目,启动运行index.html 中文显示乱码 解决方案: 1.使用text/html通用解析编码utf ...
- dotnet 读 WPF 源代码笔记 布局时 Arrange 如何影响元素渲染坐标
大家是否好奇,在 WPF 里面,对 UIElement 重写 OnRender 方法进行渲染的内容,是如何受到上层容器控件的布局而进行坐标偏移.如有两个放入到 StackPanel 的自定义 UIEl ...
随机推荐
- 在阿里云Centos7.6上面配置Mysql主从数据库(master/slave),实现读写分离
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_85 在之前的一篇文章中,阐述了如何在高并发高负载的场景下使用nginx做后台服务的负载均衡:在阿里云Centos上配置nginx+ ...
- Javascript 函数声明、调用、闭包
1 # Javascript 函数声明.调用.闭包 2 # 一.函数声明 3 # 1.直接声明.浏览器在执行前,会先将变量和函数声明进行提升. 4 fn(); 5 function fn () { 6 ...
- BZOJ1305/Luogu3153 [CQOI2009]dance跳舞 (network flow)
#define T 1001 #define S 0 struct Edge{ int nxt,pre,w; }e[500007]; int cntEdge,head[N]; inline void ...
- Luogu2869 [USACO07DEC]美食的食草动物Gourmet Grazers (贪心,二分,数据结构优化)
贪心 考场上因无优化与卡常T掉的\(n \log(n)\) //#include <iostream> #include <cstdio> #include <cstri ...
- MyBatis-Plus 配置文件
MyBatis-Plus在实际工作中常用到的配置,供自己和大家查阅学习. mybatis-plus: mapperPackage: com.**.**.mapper # 对应的 XML 文件位置 ma ...
- vue.js及H5常见跨域问题解决方案
一.原生H5跨域问题解决方案 1.live-server 代理解决 首先在有node.js环境下,打开命令行工具,输入 npm install live-server -g 全局安装全局安装 live ...
- Downie for Mac最强视频下载工具(支持B站优酷土豆腾讯等)
我搜集到的一款简单拖放链接到Downie,它就会下载该网站上的视频.理论可以下载各种视频网站上的视频! 应用介绍 Downie 是一款Mac平台上的优秀视频下载软件,使用非常简单,只需将下载链接放置D ...
- LOJ2312 LUOGU-P3733「HAOI2017」八纵八横 (异或线性基、生成树、线段树分治)
八纵八横 题目描述 Anihc国有n个城市,这n个城市从1~n编号,1号城市为首都.城市间初始时有m条高速公路,每条高速公路都有一个非负整数的经济影响因子,每条高速公路的两端都是城市(可能两端是同一个 ...
- 简易的DragDropCarousel 拖拽轮播控件
上一篇文章有写到 自动轮播的控件 简易的AutoPlayCarousel 轮播控件 - 黄高林 - 博客园 (cnblogs.com) 本章是基于自动轮播的一种衍生,通过拖拽鼠标进切换 直接上代码 ...
- Swagger以及knife4j的基本使用
Swagger以及knife4j基本使用 目录 Swagger以及knife4j基本使用 Swagger 介绍: Restful 面向资源 SpringBoot使用swagger Knife4j -- ...