2012-03-19 31 views
5

Tôi đã gặp phải một tình huống khó khăn khi sử dụng SyntaxRewriter trong Roslyn. Tôi muốn viết lại một số loại câu lệnh, bao gồm các khai báo biến cục bộ. Các giải pháp đòi hỏi tôi phải thay đổi những điều khoản trong câu hỏi thành nhiều báo cáo, như trong ví dụ tầm thường như sau:SyntaxRewriter và nhiều câu lệnh

void method() 
{ 
    int i; 
} 

trở thành

void method() 
{ 
    int i; 
    Console.WriteLine("I declared a variable."); 
} 

Tôi đã nhìn thấy những ví dụ khác, nơi khối được sử dụng để thực hiện một cái gì đó tương tự, nhưng tất nhiên trong trường hợp của một tuyên bố biến, phạm vi của tuyên bố sẽ bị ảnh hưởng. Tôi đã đưa ra giải pháp sau đây, nhưng tôi đang balking vào nó. Có vẻ như quá phức tạp và yêu cầu ngắt trong mẫu khách truy cập:

class Rewriter: SyntaxRewriter 
{ 
    public override SyntaxList<TNode> VisitList<TNode>(SyntaxList<TNode> list) 
    { 
     if (typeof(TNode) == typeof(StatementSyntax)) 
      return Syntax.List<TNode>(list.SelectMany(st => RewriteStatementInList(st as StatementSyntax).Cast<TNode>())); 
     else 
      return base.VisitList<TNode>(list); 
    } 

    private IEnumerable<SyntaxNode> RewriteStatementInList(StatementSyntax node) 
    { 
     if (node is LocalDeclarationStatementSyntax) 
      return PerformRewrite((LocalDeclarationStatementSyntax)node); 
     //else if other cases (non-visitor) 

     return Visit(node).AsSingleEnumerableOf(); 
    } 

    private IEnumerable<SyntaxNode> PerformRewrite(LocalDeclarationStatementSyntax orig) 
    { 
     yield return orig; 
     yield return Syntax.ParseStatement("Console.WriteLine(\"I declared a variable.\");"); 
    } 
} 

Tôi đang thiếu gì? Các câu lệnh chỉnh sửa và xóa chúng (thông qua câu lệnh trống) có vẻ thẳng về phía trước hơn là viết lại cho các bội số.

tôi đảm nhận câu trả lời:

class Rewriter : SyntaxRewriter 
{ 
    readonly ListVisitor _visitor = new ListVisitor(); 

    public override SyntaxList<TNode> VisitList<TNode>(SyntaxList<TNode> list) 
    { 
     var result = Syntax.List(list.SelectMany(_visitor.Visit).Cast<TNode>()); 
     return base.VisitList(result); 
    } 

    private class ListVisitor : SyntaxVisitor<IEnumerable<SyntaxNode>> 
    { 
     protected override IEnumerable<SyntaxNode> DefaultVisit(SyntaxNode node) 
     { 
      yield return node; 
     } 

     protected override IEnumerable<SyntaxNode> VisitLocalDeclarationStatement(
      LocalDeclarationStatementSyntax node) 
     { 
      yield return node; 
      yield return Syntax.ParseStatement("Console.WriteLine(\"I declared a variable.\");"); 
     } 
    } 
} 

Trả lời

3

Tôi nghĩ rằng có một cách đơn giản để làm cho bạn Rewriter nhiều người truy cập như: sử dụng truy cập khác để xử lý các nút trong danh sách:

class Rewriter: SyntaxRewriter 
{ 
    readonly Visitor m_visitor = new Visitor(); 

    public override SyntaxList<TNode> VisitList<TNode>(SyntaxList<TNode> list) 
    { 
     var result = Syntax.List(list.SelectMany(m_visitor.Visit).Cast<TNode>()); 
     return base.VisitList(result); 
    } 
} 

class Visitor : SyntaxVisitor<IEnumerable<SyntaxNode>> 
{ 
    protected override IEnumerable<SyntaxNode> DefaultVisit(SyntaxNode node) 
    { 
     return new[] { node }; 
    } 

    protected override IEnumerable<SyntaxNode> VisitLocalDeclarationStatement(
     LocalDeclarationStatementSyntax node) 
    { 
     return new SyntaxNode[] 
       { 
        node, 
        Syntax.ParseStatement(
         "Console.WriteLine(\"I declared a variable.\");") 
       }; 
    } 
} 

Lưu ý rằng đây không phải là an toàn và sẽ ném một InvalidCastException nếu bạn trả lại bộ sưu tập chứa đối tượng không phải là TNode từ Visitor.

+0

Tôi đã nghĩ đến việc làm theo cách này. Phía chỉ xuống sẽ là IEnumerable sẽ chỉ có ý nghĩa bên trong một cuộc gọi đến VisitList. Tôi nghĩ rằng một người xây dựng/nhà văn, sẽ là một câu trả lời hấp dẫn, nhưng tôi nghĩ rằng tôi quá ám ảnh về nó :) Tôi sẽ upvote bạn guys sau, rõ ràng tôi không có đủ SO clout nào được nêu ra. –

+0

Tại sao nó là một vấn đề mà 'IEnumerable' có ý nghĩa chỉ trong một cuộc gọi đến' VisitList() '? Bất cứ khi nào nó có ý nghĩa để thay thế một nút với một chuỗi các nút, nút đó phải là một phần của một số danh sách, tôi nghĩ. – svick

+0

Trên thực tế, tôi đã không nhận thấy bạn chỉ gọi 'Visitor' trực tiếp và chỉ từ 'VisitList'. Tôi thích giải pháp này rất nhiều.Tôi sẽ đặt nó trong câu hỏi của tôi, với 'lợi nhuận' để tạo ra các vòng lặp và lồng vào lớp danh sách khách truy cập. –

2

Tôi không biết một cách triệt để hơn để xử lý này. Một cách tiếp cận khác có nghĩa là "khách truy cập như" nhiều hơn một chút là sử dụng VisitLocalDeclaration để chú thích các nút mà bạn muốn thay thế bằng một cái gì đó như: return (base.Visit(node).WithAdditionalAnnoations(myAnnotation);. Sau đó trong VisitList, bạn chỉ có thể tìm thấy các nút con mà chú thích của bạn và viết lại tại thời điểm đó.

+0

Cảm ơn, Kevin. Đó là một cái gì đó tôi đã không nhìn vào điều này. Vì vậy, nếu tôi đã có cách tiếp cận đó, tôi vẫn sẽ cần phải xử lý bất kỳ xử lý đặc biệt cho mỗi loại nút, khi viết lại trong VisitList, chính xác? –

0

Tôi đã duyệt qua mã nguồn Roslyn để xem nhóm Roslyn tự làm như thế nào. Dưới đây là một ví dụ: http://source.roslyn.codeplex.com/Microsoft.CodeAnalysis.CSharp.Features/R/bcd389b836bf7b4c.html

Tóm lại, tôi nghĩ nó trông giống như thế này. (viết lại này xảy ra để chỉ cần xóa StatementExpressions, nhưng bạn có thể thấy nó được xây dựng từ một phương thức iterator, vì vậy thật dễ dàng để thêm các phương thức nữa).

class TreeRewriter : CSharpSyntaxRewriter 
{ 
    public override SyntaxNode VisitBlock(BlockSyntax node) 
     => node.WithStatements(VisitList(SyntaxFactory.List(ReplaceStatements(node.Statements)))); 

    public override SyntaxNode VisitSwitchSection(SwitchSectionSyntax node) 
     => node.WithStatements(VisitList(SyntaxFactory.List(ReplaceStatements(node.Statements)))); 

    IEnumerable<StatementSyntax> ReplaceStatements(IEnumerable<StatementSyntax> statements) 
    { 
     foreach (var statement in statements) 
     { 
      if (statement is ExpressionStatementSyntax) continue; 
      yield return statement; 
     } 
    } 
} 

Đây là cách tôi lái xe mã:

var rewriter = new TreeRewriter(); 
var syntaxTree = await document.GetSyntaxTreeAsync(); 
var root = await syntaxTree.GetRootAsync(); 
var newRoot = rewriter.Visit(root); 
var newDocument = document.WithSyntaxRoot(newRoot); 
Các vấn đề liên quan