2013-03-14 36 views
6

Tôi đang cố gắng viết một trình biên dịch Scala plugin cho phép tạo mã cực kỳ chung chung: một cái gì đó giống như tính tổng quát của bộ tiền xử lý C, nhưng an toàn hơn một chút (tôi không chắc chắn nếu đây là một ý tưởng khủng khiếp, nhưng đó là một bài tập thú vị). trường hợp sử dụng lý tưởng của tôi trông giống như sau:Vượt qua đóng cửa cho trình biên dịch Scala plugin

// User code. This represents some function that might take some args 
// and outputs an abstract syntax tree. 
def createFooTree(...): scala.reflect.runtime.universe.Tree = ... 

// Later user code (maybe separate compilation?). Here the user generates 
// code programmatically using the function call to |createFooTree| and inserts 
// the code using insertTree. 
insertTree(createFooTree(...)) 

Mã plugin quan trọng có thể trông như thế này (dựa trên this):

class InsertTreeComponent(val global: Global) 
    extends PluginComponent 
    with TypingTransformers { 
    import global._ 
    import definitions._ 

    override val phaseName = "insertTree" 

    override val runsRightAfter = Some("parser") 
    override val runsAfter = runsRightAfter.toList 
    override val runsBefore = List[String]("typer") 

    def newPhase(prev: Phase): StdPhase = new StdPhase(prev) { 
    def apply(unit: CompilationUnit) { 
     val onTransformer = new TypingTransformer(unit) { 
     override def transform(tree: Tree): Tree = tree match { 
      case orig @ Apply(
      function, 
      // |treeClosure| is the closure we passed, which should 
      // evaluate to a Tree (albeit a runtime Tree). 
      // The function.toString bit matches anything that looks like a 
      // function call with a function called |insertTree|. 
      treeClosure) if (function.toString == "insertTree") => { 
      // This function evaluates and returns the Tree, inserting it 
      // into the call site as automatically-generated code. 
      // Unfortunately, the following line isn't valid. 
      eval(treeClosure): Tree 
      } 
    ... 

Bất kỳ ý tưởng làm thế nào để làm điều này? Vui lòng không nói "chỉ sử dụng macro"; ít nhất là 2,10, chúng không đủ chung.

BTW, tôi thấy hai vấn đề với cách tiếp cận mà tôi đã phác thảo: 1) Trình biên dịch plugin mất một AST, không phải là đóng. Nó sẽ cần một số cách để tạo ra các đóng cửa, có lẽ thêm một sự phụ thuộc xây dựng trên mã người dùng. 2) Người dùng không có quyền truy cập vào scala.reflect.internal.Trees.Tree, chỉ scala.reflect.runtime.universe.Tree, vì vậy plugin sẽ cần dịch giữa hai.

+0

Đó chắc chắn là một ý tưởng khủng khiếp - nhưng là một bài tập tuyệt vời;) - bạn sẽ nghĩ về việc tìm kiếm trong việc xâm nhập macro trong thiên đường. –

Trả lời

9

Khó khăn triển khai mà bạn gặp phải là một phần lý do tại sao các macro trong 2,10 không đủ chung. Họ trông rất khó khăn và thậm chí cơ bản, nhưng tôi lạc quan rằng cuối cùng họ có thể bị đánh bại. Dưới đây là một số câu hỏi thiết kế phức tạp:

1) Làm thế nào để bạn biết rằng hàm bạn đang gọi là đúng insertTree? Điều gì xảy ra nếu người dùng đã viết chức năng riêng của mình có tên là insertTree - làm thế nào để bạn phân biệt một cuộc gọi ma thuật với chức năng đặc biệt của bạn và một cuộc gọi bình thường đến một hàm do người dùng xác định? Để chắc chắn bạn sẽ cần phải đánh máy tham chiếu đến hàm. Nhưng đó không phải là chính xác dễ dàng (xem bên dưới).

2) Bạn đánh giá chính xác cuộc gọi createFooTree(...) như thế nào? Cũng giống như trước đây, bạn sẽ cần phải đánh máy phần createFooTree để tìm hiểu điều gì là viết tắt của nó, điều này không hề dễ dàng.

3) Và sau đó có thêm một vấn đề nữa. Điều gì sẽ xảy ra nếu createFooTree được xác định trong một trong các tệp bạn hiện đang biên soạn? Sau đó, bạn bằng cách nào đó sẽ cần phải tách nó và phụ thuộc của nó từ phần còn lại của chương trình, đặt nó vào một trình biên dịch khác nhau, biên dịch nó và sau đó gọi nó. Và sau đó, điều gì sẽ xảy ra nếu việc biên dịch hàm hoặc một trong những phụ thuộc này dẫn đến việc mở rộng macro, điều này được cho là sẽ làm biến đổi một số trạng thái toàn cục của trình biên dịch. Làm thế nào chúng ta sẽ tuyên truyền nó cho phần còn lại của chương trình?

4) Tôi đang nói về việc đánh máy tất cả thời gian. Đó có phải là vấn đề không? Rõ ràng là có. Nếu các macro của bạn có thể mở rộng bất cứ đâu vào bất cứ thứ gì, thì việc đánh máy lại trở nên thực sự phức tạp. Ví dụ: bạn đánh máy như thế nào:

class C { 
    insertTree(createFoo(bar)) // creates `def foo = 2`, requires `bar` to be defined to operate 
    insertTree(createBar(foo)) // creates `def bar = 4`, requires `foo` to be defined to operate 
} 

5) Tin tốt, tuy nhiên, bạn không phải sử dụng scala.reflect.runtime.universe.Tree. Bạn có thể có createFooTree được nhập một cách phụ thuộc: def createFooTree[U <: scala.reflect.api.Universe with Singleton](u: Universe): u.Tree. Điều này, hoặc cách tiếp cận với scala.reflect.macros.Context chúng tôi sử dụng trong Scala 2.10. Không phải là rất đẹp, nhưng giải quyết vấn đề của vũ trụ không phù hợp. Như một điểm mấu chốt, cảm giác hiện tại của tôi là các macro trong một ngôn ngữ được gõ tĩnh (đặc biệt là trong ngôn ngữ hướng đối tượng, vì OO mang đến một loạt các cách mã tuyệt vời để phụ thuộc lẫn nhau) khôn lanh. Một mô hình mạnh mẽ cho các macro đánh máy sửa đổi các đoạn tùy ý trong chương trình đang được biên dịch vẫn chưa được phát hiện.

Nếu bạn muốn chúng tôi có thể thảo luận chi tiết hơn qua email. Chúng tôi cũng có thể cộng tác để đưa ý tưởng về các macro phù hợp thành hiện thực.Hoặc cách khác, nếu bạn có thể chia sẻ trường hợp sử dụng của bạn, tôi có thể cố gắng giúp đỡ với việc tìm kiếm cách giải quyết cho tình huống cụ thể của bạn.

+0

Cảm ơn, tôi vừa gửi cho bạn một email. – emchristiansen

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