在Roslyn合成lambda';s C#编译器

在Roslyn合成lambda';s C#编译器,c#,roslyn,C#,Roslyn,我一直在Roslyn试用最近开源的C#编译器,看看是否可以添加语言功能 我现在尝试添加一些语法sugar,一个新的前缀操作符,基本上是某种模式的简写。目前,我在不安全的上下文之外,依靠预先存在的&地址 我要展开的模式如下:&n相当于: Property.Bind(v => n = v, () => n) 假定方法Property.Bind在库中可用,其签名为: public static IProperty<T> Bind<T>(Action<T>

我一直在Roslyn试用最近开源的C#编译器,看看是否可以添加语言功能

我现在尝试添加一些语法sugar,一个新的前缀操作符,基本上是某种模式的简写。目前,我在
不安全的
上下文之外,依靠预先存在的
&
地址

我要展开的模式如下:
&n
相当于:

Property.Bind(v => n = v, () => n)
假定方法
Property.Bind
在库中可用,其签名为:

public static IProperty<T> Bind<T>(Action<T> set, Func<T> get)
所以(大概吧!)现在我有了第一个lambda的赋值块,但是获得一个完整的
BoundLambda
看起来是一个全新的挑战


我想知道:有没有一种方法可以“欺骗”这种语法糖,让解析器/绑定器处理一个C#字符串,就好像它出现在实际代码的位置一样?这样,就不需要手动构建所有零件并将它们缝合在一起。毕竟,现有的编译器非常适合这种情况

更新:我已经确定了一个名为
SyntaxTemplate
的新类,它是不可变的,因此可以静态创建并重用。e、 g

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 (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));
    }
}
示例用法,仅包装上面的第一个示例:

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

现在红色的曲线可以正常工作了(在真正的编译中,错误消息上的行号是正确的)。

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

让它只在安全区域工作似乎违反了最小意外原则<如果
p
是一个属性访问表达式,那么代码>&p就没有意义了,那么为什么不从中触发呢?另外,您可以选择
property.Bind(delegate(tv){n=v;},delegate(){return n;})
并跳过lambdas。我只是借用
&
进行实验。如果我能让它工作,我计划定义一个新的操作符
%
来模拟C++/CLI和C++/CX。@BenVoigt-使用委托会使构建
边界表达式更容易吗?我真的不知道。但是旧的功能通常更简单。我至少会在替换项周围添加一些括号,并可能在生成的AST中放置一个可以替换的占位符。@BenVoigt是的,我正在考虑在语言中为占位符定义一种特殊语法。我可以用它来代替
{0}
,这样可以避免对操作数进行双重解析。甚至可以将其作为元编程特性公开,这样类似的扩展就可以在用户源代码中声明。在什么情况下需要括号?如果
v=>{0}=v
被视为文本,则lambda运算符
=>
和赋值运算符
=
的任何优先级都将与出现在
{0}
中的任何运算符进行比较。例如,
x |=y=v
被解析为
x |=(y=v)
Ah。我有一个理论,
&
的用户需要自己为任何复杂的东西添加括号,但如果这不是真的,那么你是对的。但是,如果我能让占位符替代方案正常工作,那就可以完全避免这个问题。好吧,我不想打赌括号在解析过程中会被保留,然后
Operand.GetText()
+1不确定你为什么会对此投反对票,所以我已经平衡了它。这是一个非常好的建议,在我真正懒惰之前,我确实开始了调查。
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));
    }
}
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));