csppg: 一个用于……的 C# 预处理器





5.00/5 (4投票s)
这是一个小型模板引擎构建工具,我用它来使我的代码生成项目更易于维护。
引言
我尝试使用 T4 模板。我想,“嘿,这听起来是个好主意!”直到它开始向我的项目中添加引用,并让我搜索谷歌,试图弄清楚如何从命令行调用它。
我不需要那么麻烦。 KISS(保持简单,愚蠢)。一切都应该尽可能简单,而不是更简单。 输入 csppg。 这是一个快速而粗糙的工具,允许你使用 <#
/#>
或 <%
/%>
分隔符和 C# 代码编写一些输入模板,然后它会生成一个 C# 文件,其中包含一个类,该类包含一个 Run()
方法,该方法接受一个文本阅读器和一些参数,并写入一个 TextWriter
。 然后,可以将该类包含在你的项目中,以便你可以将其用作代码生成模板,或者说如果你正在制作邮件发送器,你可以将其用作邮件合并 – 坦白说,它不在乎输出是什么,我通常只是用它来渲染 C# 代码,或者用其他语言编写的代码。
理解这段乱码
这东西比使用起来更难解释,但如果你曾经使用过 ASP(还记得吗?),你可以模板化 HTML 输出。 这几乎做了同样的事情,除了输出是你想要的任何东西,并且“服务器端”语言总是 C#。 你有一个 Response
TextWriter
和一个 Arguments
Dictionary<string, object>
。
如果仅仅是这样,它还是可行的,但还不够好,因为单独使用时,你实际上做不了多少事情,例如,它无法访问调用者的内部类型和方法。 此外,必须在类似 ASP 的页面中进行元编码,并且失去正确的 Intellisense 也非常麻烦。 你真的需要某种代码隐藏,并且你需要访问你正在运行的应用程序的对象。
为了解决这个问题,它输出了用于输出代码的代码。 然后,你可以将这些东西包含在你的项目中,并从你的代码内部调用它,在那里它可以访问所有内容。
如果你理解 ASP 上下文切换是如何工作的,这可能会有所帮助。 考虑以下内容
<%
var tokenizerDfa = (int[])Arguments["tokenizerDfa"];
var arrayCount = 1;
%>
static readonly int[] _TokenizerDfa = new int[] {
<%for(var i = 0;i<tokenizerDfa.Length;++i) {%><%=tokenizerDfa[i]%><%
if(i<tokenizerDfa.Length-1){%>, <%}%><%if(0==((arrayCount++)%50)){%>
<%}%><%}%>
};
请原谅格式。在这里,如果不影响最终输出,就不可能干净地换行。
这将解析为以下代码片段
var tokenizerDfa = (int[])Arguments["tokenizerDfa"];
var arrayCount = 1;
Response.Write("\r\nstatic readonly int[] _TokenizerDfa = new int[] {\r\n");
for(var i = 0;i<tokenizerDfa.Length;++i) {
Response.Write(tokenizerDfa[i]);
if(i<tokenizerDfa.Length-1){
Response.Write(", ");
}
if(0==((arrayCount++)%50)){
Response.Write("\r\n");
}
}
Response.Write("\r\n};\r\n");
上面的代码将由 csppg 生成。当它被传递一个 DFA 状态机作为整数的打包数组时,它将在使用 int[]
tokenizerDfa
参数执行后将其呈现为一些输出
var args = new Dictionary<string, object>();
args.Add("tokenizerDfa", _ToDfaTable(tokenizer));
TokenizerGenerator.Run(Console.Out, args); // invokes the csspgen created code
根据传入的分词器,输出将类似于这样
static readonly int[] _TokenizerDfa = new int[] {
-1, 9, 1146, 2, 9, 13, 32, 32, 1154, 1, 34, 34, 1210, 1, 39, 39, 1250,
1, 46, 46, 1322, 1, 47, 47, 1330, 1, 48, 48, 1376, 1, 49, 57, 1790, 1,
64, 64, 1814, 554, 65, 90, 97, 122, 170, 170, 181, 181, 186, 186, 192, 214,
216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 880, 884,
886, 887, 890, 893, 895, 895, 902, 902, 904, 906, 908, 908, 910, 929,
931, 1013, 1015, 1153, 1162, 1327, 1329, 1366, 1369, 1369, 1377, 1415,
1488, 1514, 1520, 1522, 1568, 1610, 1646, 1647, 1649, 1747,
1749, 1749, 1765, 1766, 1774, 1775, 1786, 1788, 1791, 1791, 1808, 1808,
1810, 1839, 1869, 1957, 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042,
2048, 2069, 2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2208, 2228, 2308,
2361, 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2432, 2437, 2444, 2447, 2448,
2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2493, 2493, 2510, 2510, 2524,
2525, 2527, 2529, 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608,
2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654,
2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741,
2745, 2749, 2749, 2768, 2768, 2784, 2785, 2809, 2809, 2821, 2828, 2831, 2832,
2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2877, 2877, 2908, 2909, 2911,
2913, 2929, 2929, 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970,
2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3024, 3024,
3077, 3084, 3086, 3088, 3090, 3112, 3114, 3129, 3133, 3133, 3160, 3162, 3168,
3169, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261,
3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344,
3346, 3386, 3389, 3389, 3406, 3406, 3423, 3425, 3450, 3455, 3461, 3478, 3482,
3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654,
3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735,
3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, 3762,
3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3807, 3840, 3840, 3904, 3911,
3913, 3948, 3976, 3980, 4096, 4138, 4159, 4159, 4176, 4181, 4186, 4189, 4193,
4193, 4197, 4198, 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4295, 4295,
4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694,
4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792,
4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954,
4992, 5007, 5024, 5109, 5112, 5117, 5121, 5740, 5743, 5759, 5761, 5786, 5792,
5866, 5873, 5880, 5888, 5900, 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996,
5998, 6000, 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6263, 6272, 6312, 6314,
6314, 6320, 6389, 6400, 6430, 6480, 6509, 6512, 6516, 6528, 6571, 6576, 6601,
6656, 6678, 6688, 6740, 6823, 6823, 6917, 6963, 6981, 6987, 7043, 7072, 7086,
7087, 7098, 7141, 7168, 7203, 7245, 7247, 7258, 7293, 7401, 7404, 7406, 7409,
7413, 7414, 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016,
8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124,
8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147,
8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8305, 8305, 8319, 8319, 8336,
8348, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484,
8486, 8486, 8488, 8488, 8490 ...
};
显然,输出格式化的数组甚至不是类似 ASP 的引擎特别擅长的事情,这就是我通常编写一个代码隐藏方法来为我完成它的原因。
static readonly int[] _TokenizerDfa = <%
var dt = ToLexer(rules);
WriteCSArray(dt,Response);
%>
我开始对 Reggie 的最新版本使用这种方法,而不是传统的手动编写代码,因为这些例程越来越难以更新和维护。
使用这个烂摊子
使用该工具非常简单。
Usage: csppg.exe <inputfile> [/output <outputfile>] [/class <codeclass>]
[/namespace <codenamespace>] [/internal] [/ifstale]
csppg 0.5.0.0 - Runs an ASP/T4 style template over some input,
and generates code that can be used to run the template.
<inputfile> The input template
<outputfile> The preprocessor source file - defaults to STDOUT
<codeclass> The name of the main class to generate - default derived from <outputfile>
<codenamespace> The namespace to generate the code under - defaults to none
<internal> Mark the generated class as internal - defaults to public
<ifstale> Only generate if the input is newer than the output
输出文件有点粗糙,但它可以包含在你的代码生成器项目中,以运行你提供给它的模板。
创建一个代码隐藏只是创建一个具有相同名称的 partial class
的问题。
这是一些输出的片段
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
namespace Reggie {
internal partial class TableMatcherGenerator {
public static void Run(TextWriter Response, IDictionary<string, object> Arguments) {
var rules = (IList<LexRule>)Arguments["rules"];
var ignoreCase = (bool)Arguments["ignorecase"];
var inputFile = (string)Arguments["inputfile"];
var blockEnds = BuildBlockEnds(rules,inputFile,ignoreCase);
Response.Write("\r\n");
for(var k = 0;k<2;++k) {
bool reader = k==1;
string curtype = reader ?
"System.IO.TextReader":"System.Collections.Generic.IEnumerator<char>";
string curname = reader ? "text":"cursor";
string texttype = reader ?
"System.IO.TextReader":"System.Collections.Generic.IEnumerable<char>";
Response.Write("\r\nstatic System.Collections.Generic.IEnumerable
<System.Collections.Generic.KeyValuePair<long, string>>
_MatchTable(int[] entries,int[] blockEnd, ");
Response.Write(texttype);
Response.Write(" text, long position)\r\n
{\r\n var sb = new System.Text.StringBuilder();\r\n");
if(!reader) {
Response.Write(" var cursor = text.GetEnumerator();");
}
请原谅格式。它是由一个工具生成的,实际上并不能很好地手动编辑。
现在你有了这样的东西,你可以这样使用它
var args = new Dictionary<string, object>();
args.Add("rules", rules);
args.Add("inputfile", inputFile);
args.Add("blockEnds", blockEnds);
args.Add("lines", false);
args.Add("ignorecase", ignoreCase);
// late bind so if we break the build it doesn't blow up all over the place
// when we have to delete the output
var tp = Type.GetType("Reggie.TableMatcherGenerator");
tp.GetMethod("Run", BindingFlags.Static |
BindingFlags.Public).Invoke(null, new object[] { writer, args });
//TableMatcherGenerator.Run(writer, args);
请注意,我正在进行后期绑定,而不是使用更简单的调用(上面注释掉的)。 这不是绝对必要的,但它允许的情况是,如果你在模板中编写了错误的代码,它可能会导致项目无法编译。 为了让项目重新编译,你可以删除生成文件中的代码。 由于它是后期绑定的,因此在未找到该方法时,它不会导致编译错误。 它只是让生活更轻松。
历史
- 2021 年 10 月 28 日 - 初始提交