序列化.NET Roslyn中的ScriptState<T>

放弃了, 没找到解决方案

本文内容存在错误,正在等待修复

ScriptState<T>.NETRoslyn中用于储存脚本运行结果的对象。ScriptState中只暴露了少部分内容,所以不能通过直接使用System.Text.Json.JsonSerializer或XML等进行序列化。

如果使用自定义派生类的方式进行Json或XML序列化,需要大量Dirty work。

思路

这时候不妨回顾一下序列化的定义:

序列化(serialization)在计算机科学的资料处理中,是指将数据结构对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。

–Wikipedia

所以实际上没有必要把目光局限在Json或者XML等格式,序列化ScriptState当然只需要把Script存储下来即可。

问题

笔者的场景是一个类似于csi.exe的交互式脚本shell,用户会在脚本中输入如return 0;或者表达式a+b,函数调用func(arg);的代码:如果直接拼接用户输入,保存并在反序列化时重放会出现问题如:

  • 代码被return;阻断,return;后面代码参与的上下文无法被反序列化。
  • 密集计算函数func(arg)被重复调用,有时候这些函数的结果并不存储在上下文中,导致反序列化时间过长。

解决这些问题的方法其实非常明了,只需要把脚本中的这些部分去除即可。

解决方案

造成以上问题的语句看似(待验证)只有以上两种语句,也即

  • return;
  • expression

Roslyn提供了许多非常方便的功能,比如我们可以通过直接读取语法树,知道代码中包含什么内容。

1
2
3
4
Compilation compilation = newScript.GetCompilation();
SyntaxTree syntaxTree = compilation.SyntaxTrees.Single();

var syntaxNodeRoot = (CompilationUnitSyntax)syntaxTree.GetRoot();

这两种无值的、需要筛除的语句都是顶级语句,所以我们可以先筛选出语法树中的顶级语句:

1
2
var globalStatements = syntaxNodeRoot.ChildNodes()
.OfType<GlobalStatementSyntax>();

然后我们可以从中进一步筛选出返回和表达式:

1
2
3
var returnAndExpressionStatements = globalStatements
.Where(s => s.Statement is ReturnStatementSyntax || s.Statement is ExpressionStatementSyntax)
.Select(s => s.Statement);

这样我们就得到了需要筛除的所有Statement

Statement对象的Span属性标记了该对象在源代码中的具体位置,非常方便我们进行筛除:

1
2
3
4
5
6
7
8
var processedCode = rawCode;
var offset = 0;

foreach(var s in globalStatements)
{
processedCode = processedCode.Remove(s!.Span.Start - offset, s.Span.Length);
offset += s.Span.Length;
}

这样就大功告成了。

留言评论

0条搜索结果。