在Roslyn';s的C#编译器
本文关键字:编译器 Roslyn | 更新日期: 2023-09-27 18:00:23
我一直在Roslyn中试用最近开源的C#编译器,看看是否可以添加语言功能。
我现在尝试添加一些语法sugar,一种新的前缀运算符,它基本上是某种模式的简写。现在,在unsafe
上下文之外,我将依靠预先存在的&
"的地址"。
我想扩展的模式如下:&n
等效于:
Property.Bind(v => n = v, () => n)
假设方法Property.Bind
在库中可用,签名为:
public static IProperty<T> Bind<T>(Action<T> set, Func<T> get)
所以本质上我需要合成两个λ:
- 作为左值绑定到
n
,并根据其参数对其进行赋值,以及 - 没有参数并且评估CCD_ 6。然后,我需要调用
Property.Bind
,将这两个lambda作为参数传递
我之前的实验比这容易得多,因为他们能够利用容易找到的现有功能,所以几乎没有什么工作要做!
但这一次,我正在努力寻找与我在这里所做的类似的事情。到目前为止,我一直在了解编译器是如何从源代码构建BoundLambda
的,它正在发展成一个大混乱。我在Binder_Operators.cs
中修改了BindAddressOfExpression
,使其从一个额外的if
语句开始,用于安全上下文:
private BoundExpression BindAddressOfExpression(PrefixUnaryExpressionSyntax node, DiagnosticBag diagnostics)
{
if (!this.InUnsafeRegion)
{
BoundExpression rValue = BindValue(node.Operand, diagnostics, BindValueKind.RValue);
BoundExpression lValue = BindValue(node.Operand, diagnostics, BindValueKind.Assignment);
var valueParamSymbol = new SourceSimpleParameterSymbol(null, rValue.Type, 0, RefKind.None, "__v", ImmutableArray<Location>.Empty);
var valueParam = new BoundParameter(node, valueParamSymbol);
var assignment = new BoundAssignmentOperator(node, lValue, valueParam, RefKind.None, rValue.Type);
var assignmentStatement = new BoundExpressionStatement(node, assignment);
var assignmentBlock = new BoundBlock(node, ImmutableArray<LocalSymbol>.Empty, ImmutableArray.Create<BoundStatement>(assignmentStatement)) { WasCompilerGenerated = true };
assignmentBlock = FlowAnalysisPass.AppendImplicitReturn(assignmentBlock);
所以(大概吧!)现在我有了第一个lambda的赋值块,但要围绕它获得一个完整的BoundLambda
似乎是一个全新的挑战。
我想知道:有没有一种方法可以"欺骗"这种语法糖,让解析器/绑定器处理一个C#字符串,就好像它出现在实际代码的位置一样?这样,就不需要手动构建所有零件并将它们缝合在一起。毕竟,现有的编译器非常适合这个!
UPDATED:我已经确定了一个名为SyntaxTemplate
的新类,它是不可变的,因此可以静态创建并重用。例如
private static readonly SyntaxTemplate _pointerIndirectionTemplate
= new SyntaxTemplate("p.Value");
private static readonly SyntaxTemplate _propertyReferenceTemplate
= new SyntaxTemplate("System.Property.Bind(__v_pr__ => o = __v_pr__, () => o)");
private static readonly SyntaxTemplate _propertyReferenceTypeTemplate
= new SyntaxTemplate("System.IProperty<T>");
private static readonly SyntaxTemplate _enumerableTypeTemplate
= new SyntaxTemplate("System.Collections.Generic.IEnumerable<T>");
它内部有一个包含所有标识符的不可变字典,因此任何标识符都可以用名称替换,例如对于表达式:
if (!operand.Type.IsPointerType())
return BindExpression(
_pointerIndirectionTemplate.Replace("p", node.Operand).Syntax,
diagnostics);
或者对于一种类型:
if (this.IsIndirectlyInIterator || !this.InUnsafeRegion)
return BindNamespaceOrTypeOrAliasSymbol(
_enumerableTypeTemplate.Replace("T", node.ElementType).Syntax,
diagnostics, basesBeingResolved, suppressUseSiteDiagnostics);
SyntaxTemplate
看起来像这样:
internal class SyntaxTemplate
{
public ExpressionSyntax Syntax { get; private set; }
private readonly ImmutableDictionary<string, ImmutableList<IdentifierNameSyntax>> _identifiers;
public SyntaxTemplate(string source)
{
Syntax = SyntaxFactory.ParseExpression(source);
var identifiers = ImmutableDictionary<string, ImmutableList<IdentifierNameSyntax>.Builder>.Empty.ToBuilder();
foreach (var node in Syntax.DescendantNodes().OfType<IdentifierNameSyntax>())
{
ImmutableList<IdentifierNameSyntax>.Builder list;
if (!identifiers.TryGetValue(node.Identifier.Text, out list))
list = identifiers[node.Identifier.Text] =
ImmutableList<IdentifierNameSyntax>.Empty.ToBuilder();
list.Add(node);
}
_identifiers = identifiers.ToImmutableDictionary(
p => p.Key, p => p.Value.ToImmutableList());
}
private SyntaxTemplate(ExpressionSyntax syntax,
ImmutableDictionary<string, ImmutableList<IdentifierNameSyntax>> identifiers)
{
Syntax = syntax;
_identifiers = identifiers;
}
public SyntaxTemplate Replace(string identifier, SyntaxNode value)
{
return new SyntaxTemplate(
Syntax.ReplaceNodes(_identifiers[identifier], (o1, o2) => value),
_identifiers.Remove(identifier));
}
}
因为替换值是SyntaxNode
,所以您可以使用解析器已经创建的节点,因此不会浪费精力对同一语法进行两次重新分析。
YET MORE:这很有效,只是如果用户的源代码中存在错误(例如,他们在没有意义的情况下使用新语法),那么绑定过程中生成的错误指的是模板源代码中的位置,而这些位置在用户的源中没有意义。因此IDE不能显示红色的歪歪扭扭等。
为了解决这个问题,你可以使用一个助手方法,将诊断捕获在一个临时包中,然后将它们回放到真正的包中,位置更改为用户源中使用语法的位置:
private T RedirectDiagnostics<T>(DiagnosticBag diagnostics, CSharpSyntaxNode nodeWithLocation, Func<DiagnosticBag, T> generate)
{
var captured = new DiagnosticBag();
var result = generate(captured);
foreach (var diag in captured.AsEnumerable().OfType<DiagnosticWithInfo>())
diagnostics.Add(new CSDiagnostic(diag.Info, nodeWithLocation.Location));
return result;
}
示例用法,仅包装上面的第一个示例:
if (!operand.Type.IsPointerType())
return RedirectDiagnostics(diagnostics, node, redirected =>
BindExpression(_pointerIndirectionTemplate.Replace("p", node.Operand).Syntax, redirected));
现在红色的歪歪扭扭可以正常工作(在真正的编译中,错误消息上的行号是正确的)。
我建议您看看查询表达式如何使用编译器生成的lambda"扩展"为方法调用。