简体   繁体   English

在Roslyn的C#编译器中合成lambda

[英]Synthesizing a lambda in Roslyn's C# compiler

I've been experimenting with the recently open-sourced C# compiler in Roslyn, seeing if I can add language features. 我一直在试验Roslyn中最近开源的C#编译器,看看我是否可以添加语言功能。

I'm now trying to add some syntax sugar, a new prefix operator that is basically shorthand for a certain pattern. 我现在正在尝试添加一些语法糖,一个新的前缀运算符,它基本上是某种模式的简写。 For now I'm piggy-backing on the pre-existing & "address of", outside of unsafe contexts. 现在我在预先存在捎带& “地址”,外面unsafe环境。

The pattern I want to expand is as follows: &n is equivalent to: 我想扩展的模式如下: &n相当于:

Property.Bind(v => n = v, () => n)

The method Property.Bind is assumed to be available in a library, with the signature: 假设Property.Bind方法在库中可用,签名为:

public static IProperty<T> Bind<T>(Action<T> set, Func<T> get)

So in essence I need to synthesize two lambdas: 所以本质上我需要合成两个lambdas:

  1. binds to n as an lvalue and does an assignment to it from its parameter, and 绑定到n作为左值并从其参数进行赋值,并且
  2. has no parameters and evaluates n . 没有参数并评估n I then need to make an invocation to Property.Bind passing those two lambdas as the parameters. 然后我需要调用Property.Bind传递这两个lambdas作为参数。

My previous experiments have been much easier than this because they were able to piggy back on easy-to-find existing features, so there was practically zero work to do! 我以前的实验比这更容易,因为他们能够捎带易于查找的现有功能,所以几乎没有工作要做!

But this time I'm struggling to find anything similar to what I'm doing here. 但是这次我很难找到类似于我在这里做的事情。 So far I've been stepping through how a BoundLambda is built by the compiler from the source, and it's unfolding into a big ol' mess. 到目前为止,我已经逐步介绍了编译器从源代码构建BoundLambda方式,并且它正在展开一个大混乱。 I was modifying BindAddressOfExpression in Binder_Operators.cs , making it start with an extra if statement for safe contexts: 我正在修改BindAddressOfExpression中的Binder_Operators.cs ,使其以安全上下文的额外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);

So (presumably!) now I have the assignment block for the first lambda, but getting a complete BoundLambda around it looks to be a whole new challenge. 所以(大概!)现在我有第一个lambda的赋值块,但是在它周围获得一个完整的BoundLambda看起来是一个全新的挑战。

I'm wondering: is there a way to "cheat" for this kind of syntactic sugar, by asking the parser/binder to work on a string of C#, as if it had appeared in place of the actual code? 我想知道:有没有办法通过要求解析器/绑定器处理一串C#来“欺骗”这种语法糖,好像它已经出现在实际代码的位置? That way, manually constructing all the parts and stitching them together won't be necessary. 这样,手动构建所有零件并将它们拼接在一起就没有必要了。 After all, the existing compiler is ideally suited for this! 毕竟,现有的编译器非常适合这个!

UPDATED : I've settled on a new class called SyntaxTemplate , which is immutable and so can be created statically and reused. 更新 :我已经确定了一个名为SyntaxTemplate的新类,它是不可变的,因此可以静态创建并重用。 eg 例如

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>");

It internally has an immutable dictionary of all the identifiers, so any can be replaced by name, eg for an expression: 它内部有一个包含所有标识符的不可变字典,因此任何都可以用名称替换,例如表达式:

if (!operand.Type.IsPointerType())
    return BindExpression(
        _pointerIndirectionTemplate.Replace("p", node.Operand).Syntax, 
        diagnostics);

Or for a type: 或者类型:

if (this.IsIndirectlyInIterator || !this.InUnsafeRegion)
    return BindNamespaceOrTypeOrAliasSymbol(
        _enumerableTypeTemplate.Replace("T", node.ElementType).Syntax,
        diagnostics, basesBeingResolved, suppressUseSiteDiagnostics);

SyntaxTemplate looks like this: 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));
    }
}

Because the replacement value is a SyntaxNode , you can use the node already created by the parser, so there is no wasted effort reparsing the same syntax twice. 因为替换值是一个SyntaxNode ,所以您可以使用已经由解析器创建的节点,因此不会浪费两次相同的语法。

YET MORE : This works-ish except that if there are errors in the user's source (eg they use the new syntax in a situation where it makes no sense) then the errors generated during binding refer to locations in the template source, which are meaningless in the user's source. 更多 :除了如果用户的源中存在错误(例如,他们在没有意义的情况下使用新语法)之外,这是有效的,那么绑定期间生成的错误指的是模板源中的位置,这是无意义的在用户的来源中。 So the IDE cannot display red squiggles, etc. 因此IDE无法显示红色曲线等。

To get around this, you can use a helper method that captures the diagnostics in a temporary bag, and then replays them into the real bag with the location changed to the place in the user's source where your syntax was used: 为了解决这个问题,您可以使用辅助方法在临时包中捕获诊断信息,然后将它们重放到真实包中,并将位置更改为用户源中使用语法的位置:

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;
}

Example usage, just wrapping the first example from above: 示例用法,只是从上面包装第一个示例:

if (!operand.Type.IsPointerType())
    return RedirectDiagnostics(diagnostics, node, redirected => 
        BindExpression(_pointerIndirectionTemplate.Replace("p", node.Operand).Syntax, redirected));

Now the red squiggles work properly (and in a true compilation, the line numbers on the error messages are correct). 现在红色波浪线正常工作(并且在真正的编译中,错误消息上的行号是正确的)。

我建议你看看查询表达式如何“扩展”到使用编译器生成的lambdas的方法调用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM