2012-06-26 31 views
11

Tôi đang làm việc trong một DSL nhúng Scala và macro đang trở thành một công cụ chính để đạt được mục đích của tôi. Tôi gặp lỗi khi cố gắng sử dụng lại một cây con từ biểu thức macro đến vào biểu thức kết quả. Tình hình khá phức tạp, nhưng (tôi hy vọng) tôi đã đơn giản hóa nó vì sự hiểu biết của nó.Làm cách nào để tôi có thể sử dụng lại các subtrees định nghĩa (AST) trong macro?

Giả sử chúng ta có mã này:

val y = transform { 
    val x = 3 
    x 
} 
println(y) // prints 3 

nơi 'cải tạo' là macro tham gia. Mặc dù nó có thể dường như nó hoàn toàn không có gì, nó thực sự là chuyển khối hiển thị vào biểu thức này:

3 match { case x => x } 

Nó được thực hiện với thực hiện macro này:

def transform(c: Context)(block: c.Expr[Int]): c.Expr[Int] = { 
    import c.universe._ 
    import definitions._ 

    block.tree match { 
    /* { 
    * val xNam = xVal 
    * xExp 
    * } 
    */ 
    case Block(List(ValDef(_, xNam, _, xVal)), xExp) => 
     println("# " + showRaw(xExp)) // prints Ident(newTermName("x")) 
     c.Expr(
     Match(
      xVal, 
      List(CaseDef(
      Bind(xNam, Ident(newTermName("_"))), 
      EmptyTree, 
      /* xExp */ Ident(newTermName("x")))))) 
    case _ => 
     c.error(c.enclosingPosition, "Can't transform block to function") 
     block // keep original expression 
    } 
} 

ý rằng xNam tương ứng với tên biến, xVal tương ứng với giá trị được liên kết và cuối cùng là xExp tương ứng với biểu thức có chứa biến. Vâng, nếu tôi in cây thô xExp tôi nhận được Ident (newTermName ("x")), và đó là chính xác những gì được thiết lập trong trường hợp RHS. Vì biểu thức có thể được sửa đổi (ví dụ x + 2 thay vì x), đây không phải là giải pháp hợp lệ cho tôi. Những gì tôi muốn làm là tái sử dụng cây xExp (xem bình luận xExp) trong khi thay đổi ý nghĩa 'x' (nó là một định nghĩa trong biểu thức đầu vào nhưng sẽ là biến LHS trong trường hợp đầu ra), nhưng nó khởi chạy lỗi dài tóm tắt trong:

symbol value x does not exist in org.habla.main.Main$delayedInit$body.apply); see the error output for details. 

giải pháp hiện tại của tôi bao gồm trên phân tích của xExp để sustitute tất cả các Idents với những cái mới, nhưng nó là hoàn toàn phụ thuộc vào internals trình biên dịch, và như vậy, một cách giải quyết tạm thời. Rõ ràng là xExp xuất hiện cùng với nhiều thông tin hơn được cung cấp bởi showRaw. Làm thế nào tôi có thể làm sạch xExp đó để cho phép 'x' đóng vai trò biến trường hợp? Bất cứ ai có thể giải thích toàn bộ hình ảnh của lỗi này?

PS: Tôi đã cố gắng không thành công để sử dụng gia đình phương pháp thay thế * từ TreeApi nhưng tôi thiếu những điều cơ bản để hiểu ý nghĩa của nó.

+0

Tác vụ 'resetAllAttrs' có hoạt động không? –

+0

Có, nó đã làm. Nó làm việc trong đoạn mã đã cho thấy, cũng như trong một cái cây khá phức tạp với một vài "thay đổi" thay đổi. – jeslg

+0

Bạn gọi là 'resetAllAttrs' trên kết quả' c.Expr', trên 'block', hoặc trên một số cây đã chọn? –

Trả lời

21

Tháo các biểu thức nhập và ghép lại chúng theo một cách khác là một kịch bản quan trọng trong vĩ mô (đây là những gì chúng tôi thực hiện trong nội bộ trong macro reify). Nhưng thật không may, nó không phải là đặc biệt dễ dàng vào lúc này.

Vấn đề là các đối số đầu vào của việc triển khai macro phạm vi tiếp cận macro đã được đánh máy. Đây là cả một phước lành và một lời nguyền.

Quan tâm đặc biệt đối với chúng tôi là thực tế rằng các ràng buộc biến trong cây tương ứng với các đối số đã được thiết lập. Điều này có nghĩa là tất cả các nút IdentSelect đều có các trường sym được điền vào, trỏ đến định nghĩa mà các nút này đề cập đến.

Dưới đây là ví dụ về cách biểu tượng hoạt động.Tôi sẽ sao chép/dán một bản in từ một trong các cuộc đàm phán của tôi (tôi không đưa ra một liên kết ở đây, vì hầu hết thông tin trong các cuộc đàm phán của tôi không được dùng nữa, nhưng bản in cụ thể này đã hữu ích):

>cat Foo.scala 
def foo[T: TypeTag](x: Any) = x.asInstanceOf[T] 
foo[Long](42) 

>scalac -Xprint:typer -uniqid Foo.scala 
[[syntax trees at end of typer]]// Scala source: Foo.scala 
def foo#8339 
    [T#8340 >: Nothing#4658 <: Any#4657] 
    (x#9529: Any#4657) 
    (implicit evidence$1#9530: TypeTag#7861[T#8341]) 
    : T#8340 = 
x#9529.asInstanceOf#6023[T#8341]; 
Test#14.this.foo#8339[Long#1641](42)(scala#29.reflect#2514.`package`#3414.mirror#3463.TypeTag#10351.Long#10361) 

Tóm lại, chúng ta viết một đoạn nhỏ và biên dịch nó bằng scalac, yêu cầu trình biên dịch đổ cây sau pha typer, in các id duy nhất của các biểu tượng được gán cho cây (nếu có).

Trong bản in kết quả, chúng ta có thể thấy rằng số nhận dạng đã được liên kết với các định nghĩa tương ứng. Ví dụ, một mặt, ValDef("x", ...), đại diện cho tham số của phương thức foo, xác định một biểu tượng phương thức có id = 9529. Mặt khác, Ident("x") trong phần thân của phương thức có trường sym được đặt thành cùng một biểu tượng, thiết lập ràng buộc.

Được rồi, chúng tôi đã thấy cách các ràng buộc hoạt động trong scalac, và bây giờ là thời điểm hoàn hảo để giới thiệu một thực tế cơ bản.

If a symbol has been assigned to an AST node, 
then subsequent typechecks will never reassign it. 

Đây là lý do tại sao việc tái hợp là hợp vệ sinh. Bạn có thể lấy kết quả của việc sửa đổi và chèn nó vào một cây tùy ý (có thể định nghĩa các biến có tên xung đột) - các ràng buộc ban đầu sẽ vẫn còn nguyên vẹn. Điều này làm việc bởi vì reify bảo tồn các ký hiệu ban đầu, do đó, typechecks tiếp theo sẽ không rebind các nút AST được sửa đổi.

Bây giờ chúng ta tất cả thiết lập để giải thích các lỗi mà bạn đang phải đối mặt:

symbol value x does not exist in org.habla.main.Main$delayedInit$body.apply); see the error output for details. 

Đối số của transform vĩ mô chứa cả một định nghĩa và một tham chiếu đến một biến x. Như chúng ta vừa biết, điều này có nghĩa là ValDef và Ident tương ứng sẽ đồng bộ hóa các trường sym của chúng. Càng xa càng tốt.

Tuy nhiên, không may macro bị hỏng ràng buộc được thiết lập. Nó tái tạo ValDef, nhưng không dọn sạch trường sym của Ident tương ứng. Typecheck tiếp theo gán một biểu tượng mới cho ValDef mới được tạo ra, nhưng không chạm vào Ident gốc được sao chép vào kết quả nguyên văn.

Sau khi đánh máy, điểm nhận dạng ban đầu sẽ không còn tồn tại nữa (đây chính xác là thông báo lỗi :)) dẫn đến sự cố trong quá trình tạo mã byte.

Vậy làm cách nào để khắc phục lỗi? Thật không may là không có câu trả lời dễ dàng.

Một tùy chọn sẽ là sử dụng c.resetLocalAttrs, thao tác này sẽ xóa tất cả các ký hiệu trong một nút AST đã cho đệ quy. Sau đó typecheck sau đó sẽ thiết lập lại các ràng buộc được cấp rằng mã bạn tạo ra không gây rối với chúng (ví dụ, bạn bọc xExp trong một khối tự định nghĩa một giá trị có tên là x, sau đó bạn gặp rắc rối).

Một tùy chọn khác là làm nổi bật bằng các biểu tượng. Ví dụ: bạn có thể viết resetLocalAttrs của riêng mình mà chỉ xóa các ràng buộc bị hỏng và không chạm vào các liên kết hợp lệ. Bạn cũng có thể tự mình thử và chỉ định biểu tượng, nhưng đó là một con đường ngắn đến điên rồ, mặc dù đôi khi người ta buộc phải đi theo nó.

Hoàn toàn không hề mát mẻ, tôi đồng ý. Chúng tôi biết điều đó và có ý định cố gắng khắc phục vấn đề cơ bản này đôi khi. Tuy nhiên, ngay bây giờ, bàn tay của chúng tôi đã đầy lỗi với bản phát hành cuối cùng 2.10.0, vì vậy chúng tôi sẽ không thể giải quyết vấn đề trong tương lai gần nhất. cập nhật. Xem https://groups.google.com/forum/#!topic/scala-internals/rIyJ4yHdPDU để biết thêm thông tin.


Bottom line. Những điều xấu xảy ra, bởi vì các ràng buộc bị rối tung lên. Hãy thử resetLocalAttrs trước, và nếu nó không hoạt động, hãy chuẩn bị cho một công việc vặt.

Các vấn đề liên quan