C ++: Siêu lập trình với API trình biên dịch chứ không phải với các tính năng của C ++


10

Điều này bắt đầu như một câu hỏi SO nhưng tôi nhận ra rằng nó khá độc đáo và dựa trên mô tả thực tế trên các trang web, nó có thể phù hợp hơn với các lập trình viên. Vì câu hỏi này có trọng lượng lớn về mặt khái niệm.

Tôi đã học clang LibTooling và nó là một công cụ rất mạnh có khả năng phơi bày toàn bộ "gritty" của mã theo cách thân thiện, nghĩa là, theo cách ngữ nghĩa , và không phải bằng cách đoán. Nếu clang có thể biên dịch mã của bạn, thì clang chắc chắn về ngữ nghĩa của mỗi ký tự bên trong mã đó.

Bây giờ cho phép tôi lùi lại một lát.

Có nhiều vấn đề thực tế phát sinh khi một người tham gia vào siêu lập trình mẫu C ++ (và đặc biệt là khi mạo hiểm vượt ra ngoài các mẫu vào lãnh thổ của các macro thông minh mặc dù đáng sợ). Thành thật mà nói, đối với nhiều lập trình viên, bao gồm cả tôi, nhiều cách sử dụng thông thường của các mẫu cũng có phần đáng sợ.

Tôi đoán một ví dụ tốt sẽ là chuỗi thời gian biên dịch . Đây là một câu hỏi đã hơn một năm tuổi, nhưng rõ ràng C ++ tính đến thời điểm hiện tại không làm cho điều này trở nên dễ dàng đối với những người bình thường. Mặc dù nhìn vào các tùy chọn này không đủ để gây buồn nôn cho tôi, tuy nhiên tôi vẫn không tự tin về việc có thể tạo ra mã máy hiệu quả, tối đa để phù hợp với bất kỳ ứng dụng ưa thích nào tôi có cho phần mềm của mình.

Ý tôi là, hãy đối mặt với nó, folks, chuỗi khá đơn giản và cơ bản. Một số người trong chúng ta chỉ muốn một cách thuận tiện để phát ra mã máy có một số chuỗi nhất định "được nướng" nhiều hơn đáng kể so với khi chúng ta mã hóa nó theo cách đơn giản. Trong mã C ++ của chúng tôi.

Nhập clang và LibTooling, hiển thị cây cú pháp trừu tượng (AST) của mã nguồn và cho phép ứng dụng C ++ tùy chỉnh đơn giản xử lý chính xác và đáng tin cậy mã nguồn thô (sử dụng Rewriter) cùng với mô hình hướng đối tượng ngữ nghĩa phong phú của mọi thứ trong AST. Nó xử lý rất nhiều thứ. Nó biết về các mở rộng vĩ mô và cho phép bạn theo các chuỗi đó. Vâng, tôi đang nói về chuyển đổi hoặc dịch mã nguồn-nguồn.

Luận điểm cơ bản của tôi ở đây là clang bây giờ cho phép chúng ta tạo ra các tệp thực thi mà chính chúng có thể hoạt động như các giai đoạn tiền xử lý tùy chỉnh lý tưởng cho phần mềm C ++ của chúng ta và chúng ta có thể thực hiện các giai đoạn siêu lập trình này với C ++. Chúng tôi chỉ đơn giản bị hạn chế bởi thực tế là giai đoạn này phải lấy đầu vào là mã C ++ hợp lệ và tạo ra như đầu ra mã C ++ hợp lệ hơn. Cộng với bất kỳ hạn chế nào khác hệ thống xây dựng của bạn áp dụng.

Đầu vào ít nhất phải rất gần với mã C ++ hợp lệ bởi vì, sau tất cả, clang là giao diện của trình biên dịch và chúng tôi chỉ chọc vào và sáng tạo với API của nó. Tôi không biết có bất kỳ quy định nào để có thể xác định cú pháp mới để sử dụng hay không, nhưng rõ ràng chúng tôi phải phát triển các cách phân tích chính xác và thêm nó vào dự án clang để thực hiện điều này. Mong đợi nhiều hơn là có một cái gì đó trong dự án clang nằm ngoài phạm vi.

Không thành vấn đề. Tôi sẽ tưởng tượng rằng một số chức năng macro không có op có thể xử lý công việc này.

Một cách khác để xem những gì tôi mô tả là triển khai các cấu trúc siêu lập trình bằng cách sử dụng thời gian chạy C ++ bằng cách thao tác AST của mã nguồn của chúng tôi (nhờ clang và API của nó) thay vì triển khai chúng bằng các công cụ hạn chế hơn có sẵn trong ngôn ngữ. Điều này cũng có các lợi ích hiệu suất biên dịch rõ ràng (các tiêu đề nặng mẫu chậm biên dịch chậm tỷ lệ thuận với mức độ thường xuyên bạn sử dụng chúng. Rất nhiều công cụ được biên dịch sau đó được liên kết cẩn thận và bỏ đi bởi trình liên kết).

Tuy nhiên, điều này phải trả giá bằng việc giới thiệu một hoặc hai bước bổ sung trong quá trình xây dựng và cả yêu cầu viết một số phần mềm dài dòng (thừa nhận) phần nào (nhưng ít nhất đó là thời gian chạy đơn giản C ++) như một phần của công cụ của chúng tôi .

Đó không phải là toàn bộ bức tranh. Tôi khá chắc chắn rằng có một không gian chức năng lớn hơn nhiều có thể có được từ việc tạo mã cực kỳ khó khăn hoặc không thể với các tính năng ngôn ngữ cốt lõi. Trong C ++, bạn có thể viết mẫu hoặc macro hoặc kết hợp cả hai, nhưng trong công cụ clang, bạn có thể sửa đổi các lớp và hàm theo bất kỳ cách nào bạn có thể đạt được với C ++, trong thời gian chạy , trong khi có quyền truy cập đầy đủ vào nội dung ngữ nghĩa, ngoài mẫu và macro và mọi thứ khác.

Vì vậy, tôi tự hỏi tại sao mọi người không làm điều này. Có phải chức năng này từ clang rất mới và không ai quen thuộc với hệ thống phân cấp lớp khổng lồ của AST của clang? Đó không thể là nó.

Có lẽ tôi chỉ đánh giá thấp độ khó của điều này một chút, nhưng thực hiện "thao tác chuỗi thời gian biên dịch" với một công cụ clang thì gần như đơn giản. Thật dài dòng, nhưng nó hoàn toàn đơn giản. Tất cả những gì cần thiết là một loạt các hàm macro không hoạt động, ánh xạ tới các std::stringhoạt động thực tế thực sự . Plugin clang thực hiện điều này bằng cách tìm nạp tất cả các lệnh gọi macro không liên quan và thực hiện các thao tác với chuỗi. Công cụ này sau đó được chèn vào như một phần của quá trình xây dựng. Trong quá trình xây dựng, các lệnh gọi hàm macro không op này được tự động đánh giá vào kết quả của chúng và sau đó được chèn lại dưới dạng các chuỗi thời gian biên dịch cũ đơn giản trong chương trình. Chương trình sau đó có thể được biên dịch như bình thường. Trong thực tế, chương trình kết quả này cũng dễ mang theo hơn nhiều, do đó không yêu cầu trình biên dịch mới lạ mắt hỗ trợ C ++ 11.


Đây là một câu hỏi dài bất thường. Có lẽ bạn có thể ngưng tụ nó đến các điểm có liên quan nhất của bạn?
amon

Tôi đăng rất nhiều câu hỏi dài. Nhưng đặc biệt với câu hỏi này, tất cả các phần của câu hỏi đều quan trọng, tôi nghĩ vậy. Có thể bỏ qua 6 đoạn đầu tiên? Haha.
Steven Lu

3
Âm thanh rất giống như các macro cú pháp tiên phong trong Lisp và gần đây được chọn bởi Haxe, Nemerle, Scala và các ngôn ngữ tương tự. Có khá nhiều đọc về lý do tại sao macro Lisp được coi là có hại. Mặc dù tôi chưa nghe thấy một cuộc tranh luận thuyết phục nào, bạn cũng có thể tìm thấy lý do tại sao mọi người không muốn thêm chúng vào mọi ngôn ngữ (bên cạnh thực tế là nó không nhất thiết phải thẳng tiến).
back2dos

Vâng, siêu C ++ của nó. Có thể có nghĩa là tốt hơn, mã nhanh hơn. Đối với những ngôn ngữ đó. Tôi sẽ bắt đầu từ đâu. Trò chơi video trị giá hàng triệu đô la được thực hiện bằng bất kỳ ngôn ngữ nào? Trình duyệt web hiện đại được triển khai bằng bất kỳ ngôn ngữ nào? Nhân hệ điều hành? Được rồi, có vẻ như Haxe có một lực kéo, nhưng bạn hiểu ý.
Steven Lu

1
@nwp, Chà, tôi không thể không chỉ ra rằng bạn dường như đã bỏ lỡ toàn bộ điểm của bài viết. Chuỗi thời gian biên dịch đơn giản là một ví dụ cụ thể nhất và tối thiểu nhất về các khả năng có sẵn cho chúng ta bây giờ.
Steven Lu

Câu trả lời:


7

Vâng, Virginia, có một ông già Noel.

Khái niệm sử dụng các chương trình để sửa đổi các chương trình đã có từ lâu. Ý tưởng ban đầu đến từ John von Neumann dưới dạng máy tính lưu trữ chương trình. Nhưng mã máy sửa đổi mã máy theo cách tùy ý là khá bất tiện.

Mọi người thường muốn sửa đổi mã nguồn . Điều này chủ yếu được thực hiện dưới dạng các hệ thống chuyển đổi chương trình (PTS) .

PTS thường cung cấp, cho ít nhất một ngôn ngữ lập trình, khả năng phân tích các AST, thao tác AST đó và tạo lại văn bản nguồn hợp lệ. Nếu trên thực tế bạn khai thác xung quanh, đối với hầu hết các ngôn ngữ chính, ai đó đã xây dựng một công cụ như vậy (Clang là một ví dụ cho C ++, trình biên dịch Java cung cấp khả năng này dưới dạng API, Microsoft cung cấp cho Rosyln, JDT của Eclipse, ...) với một thủ tục API thực sự khá hữu ích. Đối với cộng đồng rộng lớn hơn, hầu hết mọi cộng đồng ngôn ngữ cụ thể đều có thể chỉ ra một thứ như thế này, được thực hiện với nhiều mức độ trưởng thành khác nhau (thường là khiêm tốn, nhiều "chỉ là các trình phân tích cú pháp tạo ra AST"). Chúc mừng siêu lập trình.

[Có một cộng đồng định hướng phản xạ cố gắng thực hiện siêu lập trình từ bên trong ngôn ngữ lập trình, nhưng chỉ đạt được sự điều chỉnh hành vi "thời gian chạy" và chỉ trong phạm vi mà trình biên dịch ngôn ngữ tạo ra một số thông tin có sẵn bằng phản xạ. Ngoại trừ LISP, luôn có các chi tiết về chương trình không có sẵn bởi sự phản chiếu ("Luke, bạn cần nguồn") luôn giới hạn những gì sự phản chiếu có thể làm.]

PTS thú vị hơn làm điều này cho các ngôn ngữ tùy ý (bạn cung cấp cho công cụ một mô tả ngôn ngữ làm tham số cấu hình, bao gồm tối thiểu BNF). PTS như vậy cũng cho phép bạn thực hiện chuyển đổi "nguồn thành nguồn", ví dụ: chỉ định các mẫu trực tiếp bằng cú pháp bề mặt của ngôn ngữ được nhắm mục tiêu; bằng cách sử dụng các mẫu như vậy, bạn có thể mã các đoạn quan tâm và / hoặc tìm và thay thế các đoạn mã. Điều này thuận tiện hơn nhiều so với API lập trình, bởi vì bạn không cần phải biết mọi chi tiết cực nhỏ về AST để thực hiện hầu hết công việc của mình. Hãy nghĩ về điều này như siêu lập trình meta: -}

Nhược điểm: trừ khi PTS cung cấp các loại phân tích tĩnh hữu ích khác nhau (bảng biểu tượng, phân tích điều khiển và phân tích luồng dữ liệu), thật khó để viết các phép biến đổi thực sự thú vị theo cách này, bởi vì bạn cần kiểm tra các loại và xác minh luồng thông tin cho hầu hết các tác vụ thực tế. Thật không may, khả năng này thực tế là rất hiếm trong PTS nói chung. (Nó luôn luôn không có sẵn với "Nếu tôi chỉ có một trình phân tích cú pháp ..." Xem tiểu sử của tôi để thảo luận lâu hơn về "Cuộc sống sau khi phân tích cú pháp").

Có một định lý cho biết nếu bạn có thể thực hiện viết lại chuỗi [do đó viết lại cây] thì bạn có thể thực hiện chuyển đổi tùy ý; và do đó, một số PTS dựa vào điều này để khẳng định bạn có thể lập trình siêu dữ liệu bất cứ thứ gì chỉ bằng cây viết lại mà họ cung cấp. Mặc dù định lý này thỏa mãn theo nghĩa bạn chắc chắn bạn có thể làm bất cứ điều gì, nhưng nó không thỏa mãn theo cách mà khả năng của Turing Machine để làm bất cứ điều gì không làm cho lập trình trở thành phương pháp lựa chọn của Turing Machine. (Điều tương tự cũng đúng đối với các hệ thống chỉ có API thủ tục, nếu chúng cho phép bạn thực hiện các thay đổi tùy ý đối với AST [và thực tế tôi nghĩ điều này không đúng với Clang]).

Những gì bạn muốn là tốt nhất của cả hai thế giới, một hệ thống cung cấp cho bạn tính tổng quát của loại PTS được tham số hóa ngôn ngữ (thậm chí xử lý nhiều ngôn ngữ), với các phân tích tĩnh bổ sung, khả năng kết hợp các biến đổi nguồn-nguồn với quy trình API. Tôi chỉ biết hai người làm điều này:

  • Ngôn ngữ siêu lập trình Rascal (MPL)
  • Bộ công cụ tái cấu trúc phần mềm DMS của chúng tôi

Trừ khi bạn muốn tự viết mô tả ngôn ngữ và máy phân tích tĩnh (đối với C ++, đây là một khối lượng công việc khổng lồ, đó là lý do Clang được xây dựng như một trình biên dịch và là nền tảng siêu lập trình thủ tục chung), bạn sẽ muốn PTS với các mô tả ngôn ngữ trưởng thành đã có sẵn. Nếu không, bạn sẽ dành toàn bộ thời gian để định cấu hình PTS và không thực hiện công việc bạn thực sự muốn làm. [Nếu bạn chọn một ngôn ngữ ngẫu nhiên, không chính thống, bước này rất khó tránh].

Rascal cố gắng thực hiện điều này bằng cách đồng chọn "OPP" (Các trình phân tích cú pháp nhân dân khác) nhưng điều đó không giúp ích gì cho phần phân tích tĩnh. Tôi nghĩ rằng họ có Java trong tay khá tốt, nhưng tôi rất chắc chắn họ không làm C hoặc C ++. Nhưng, nó là một công cụ nghiên cứu học thuật; khó trách họ

Tôi nhấn mạnh, công cụ DMS [thương mại] của chúng tôi có sẵn các giao diện đầy đủ Java, C, C ++. Đối với C ++, nó bao gồm hầu hết mọi thứ trong C ++ 14 cho các biến thể của GCC và thậm chí của Microsoft (và chúng tôi hiện đang đánh bóng), mở rộng vĩ mô và quản lý có điều kiện, và kiểm soát mức độ phương pháp và phân tích luồng dữ liệu. Và vâng, bạn có thể chỉ định thay đổi ngữ pháp một cách thực tế; chúng tôi đã xây dựng một hệ thống VectorC ++ tùy chỉnh cho một máy khách mở rộng triệt để C ++ để sử dụng số tiền cho các hoạt động mảng song song dữ liệu F90 / APL. DMS đã được sử dụng để thực hiện các tác vụ siêu lập trình lớn khác trên các hệ thống C ++ lớn (ví dụ: định hình lại kiến ​​trúc ứng dụng). (Tôi là kiến ​​trúc sư đằng sau DMS).

Chúc mừng siêu lập trình meta.


Thật tuyệt, tôi nghĩ Clang và DMS, trong khi chúng có một số khả năng chồng chéo, là những phần mềm không thực sự cùng loại. Ý tôi là, một thứ có lẽ đắt một cách lố bịch và tôi có lẽ không bao giờ có thể biện minh cho các tài nguyên cần có để có quyền truy cập vào nó, và thứ hai là nguồn mở miễn phí không hạn chế. Đây là một sự khác biệt rất lớn ... Một phần khiến tôi phấn khích về các khả năng lập trình siêu thú vị này thực sự là việc cho phép không chỉ sử dụng nó một cách tự do mà còn phân phối các công cụ nhị phân dựa trên clang một cách tự do.
Steven Lu

Bất cứ thứ gì được bán thương mại đều "đắt đỏ" so với miễn phí. Chi phí thô không phải là vấn đề; Vấn đề là ở một số người, tỷ lệ hoàn vốn để mua sản phẩm thương mại cao hơn so với hoàn vốn cho sản phẩm miễn phí, nếu không sẽ không có phần mềm thương mại. Điều này rõ ràng phụ thuộc vào nhu cầu cụ thể của bạn. Clang là một điểm thú vị trong không gian công cụ và chắc chắn sẽ có những điểm ứng dụng hữu ích. Tôi muốn nghĩ (vì tôi là kiến ​​trúc sư DMS) rằng DMS có khả năng rộng hơn. Clang không có khả năng hỗ trợ tốt các ngôn ngữ khác ngoài C ++.
Ira Baxter

Chắc chắn rồi. Không có câu hỏi nào về việc DMS có sức mạnh đáng kinh ngạc, gần như đến mức kỳ diệu (à la Arthur C. Clarke), và trong khi tiếng kêu rất tuyệt, nó thực sự chỉ là một mặt trận C ++ được viết tốt, trong đó có rất nhiều. Rất nhiều bước tiến nhỏ, đó là, nó vẫn sẽ không thực sự công bằng khi so sánh nó với DMS. Than ôi, ngay cả với một công cụ mạnh mẽ như vậy theo ý của chúng tôi, phần mềm làm việc không tự viết. Nó vẫn phải tồn tại thông qua bản dịch cẩn thận bằng cách sử dụng các công cụ, hoặc (hầu như luôn luôn là tùy chọn ưu việt) được viết mới.
Steven Lu

Bạn không đủ khả năng để xây dựng các công cụ như Clang hoặc DMS mới. Bạn cũng không thể đủ khả năng để ném ứng dụng mà bạn đã viết với một nhóm 10 trên 5 năm. Chúng ta sẽ cần các công cụ như vậy ngày càng thường xuyên hơn, vì kích thước phần mềm và tuổi thọ tiếp tục phát triển.
Ira Baxter

@StevenLu: Vâng, DMS cảm ơn bạn đã khen ngợi, nhưng không có gì kỳ diệu về nó. DMS có lợi ích của gần 2 thập kỷ kỹ thuật tuyến tính và một nền tảng kiến ​​trúc sạch (aw, shucks, YMMV) đã hoạt động khá tốt. Tương tự, Clang có rất nhiều kỹ thuật tốt trong đó. Tôi đồng ý, chúng không được thiết kế để giải quyết chính xác cùng một vấn đề ... Phạm vi của DMS rõ ràng là có ý định lớn hơn khi nói đến thao tác chương trình tượng trưng và nhỏ hơn nhiều khi trở thành trình biên dịch sản xuất.
Ira Baxter

4

Lập trình siêu dữ liệu trong C ++ với API của trình biên dịch (thay vì sử dụng các mẫu) thực sự thú vị và thực tế là có thể. Vì siêu lập trình chưa được chuẩn hóa (nên), bạn sẽ được gắn với một trình biên dịch cụ thể, không phải là trường hợp với các mẫu.

Vì vậy, tôi tự hỏi tại sao mọi người không làm điều này. Có phải chức năng này từ clang rất mới và không ai quen thuộc với hệ thống phân cấp lớp khổng lồ của AST của clang? Đó không thể là nó.

Nhiều người làm điều này, nhưng trong các ngôn ngữ khác. Ý kiến ​​của tôi là hầu hết các nhà phát triển C ++ (hoặc Java hoặc C) không thấy sự cần thiết của nó (có thể là đúng) hoặc không quen với các phương pháp siêu lập trình; Tôi cũng nghĩ rằng họ hài lòng với các tính năng tái cấu trúc / tạo mã của IDE của họ và rằng mọi thứ mà người hâm mộ có thể thấy quá phức tạp / khó duy trì / khó gỡ lỗi. Nếu không có công cụ thích hợp, nó có thể đúng. Bạn cũng nên tính đến quán tính và các vấn đề phi kỹ thuật khác, như tuyển dụng và / hoặc đào tạo người.

Nhân tiện, vì chúng tôi đang đề cập đến Common Lisp và hệ thống vĩ mô của nó (xem câu trả lời của Basile), tôi phải nói rằng mới hôm qua, clasp đã được phát hành (tôi không liên kết):

Clasp dự định là một triển khai Lisp chung tuân thủ biên dịch thành LLVM IR. Hơn nữa, nó hiển thị các thư viện Clang (AST, Matcher) cho nhà phát triển.

  • Đầu tiên, điều đó có nghĩa là bạn có thể viết bằng CL và không sử dụng C ++ nữa, ngoại trừ khi sử dụng các thư viện của nó (và nếu bạn cần macro, hãy sử dụng macro CL).

  • Thứ hai, bạn có thể viết các công cụ trong CL cho mã C ++ hiện tại của mình (phân tích, tái cấu trúc, ...).


3

Một số trình biên dịch C ++ có một số API ổn định hoặc ít tài liệu hơn, đặc biệt là hầu hết các trình biên dịch phần mềm miễn phí.

Clang / LLVM chủ yếu là một bộ thư viện lớn và bạn có thể sử dụng chúng.

GCC gần đây đang chấp nhận plugin . Cụ thể, bạn có thể mở rộng nó bằng MELT (bản thân nó là một meta-plugin, cung cấp cho bạn một ngôn ngữ cụ thể miền cấp cao hơn để mở rộng GCC).

Lưu ý rằng cú pháp của C ++ không dễ dàng mở rộng trong GCC (và có thể không phải trong Clang), nhưng bạn có thể thêm các pragma, nội dung, thuộc tính và trình biên dịch của riêng bạn để làm những gì bạn muốn (có lẽ cũng cung cấp một số macro tiền xử lý yêu cầu những thứ này để đưa ra một cú pháp thân thiện với người dùng).

Bạn có thể quan tâm đến các ngôn ngữ và trình biên dịch nhiều giai đoạn, xem ví dụ: Thực hiện các ngôn ngữ nhiều giai đoạn sử dụng giấy AST, Gensym và Reflection của C.Calcagno et al. và làm việc xung quanh MetaOcaml . Bạn chắc chắn nên nhìn vào bên trong các cơ sở vĩ mô của Common Lisp . Và bạn có thể được quan tâm bởi các thư viện JIT như libjit , GNU sét , thậm chí LLVM , hoặc đơn giản là -Tại thời gian chạy - tạo ra một số mã C ++, một ngã ba biên soạn nó thành một thư viện đối tượng tự động chia sẻ, sau đó dlopen (3) đã chia sẻ vật. Blog của J.Pitrat cũng liên quan đến các phương pháp phản ánh như vậy. Và cả RefPerSys .


Hấp dẫn. Điều đó rất tốt khi thấy GCC tiếp tục phát triển ở đây. Đây không phải là một câu trả lời giải quyết bất cứ điều gì tôi đã hỏi, nhưng dù sao tôi cũng thích nó.
Steven Lu

Re: các chỉnh sửa mới của bạn ... Đó là một điểm tốt về việc viết lại mã. Điều này thực sự bắt đầu mang lại khả năng siêu chương trình như vậy cho C ++, dễ truy cập hơn nhiều so với trước đây, điều này cũng khá thú vị.
Steven Lu
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.