Ngôn ngữ lập trình với cơ chế mở rộng cú pháp giống Lisp [đã đóng]


20

Tôi chỉ có kiến ​​thức hạn chế về Lisp (cố gắng học một chút trong thời gian rảnh) nhưng theo tôi hiểu thì macro Lisp cho phép giới thiệu các cấu trúc và cú pháp ngôn ngữ mới bằng cách mô tả chúng trong chính Lisp. Điều này có nghĩa là một cấu trúc mới có thể được thêm vào như một thư viện, mà không thay đổi trình biên dịch / trình thông dịch Lisp.

Cách tiếp cận này rất khác với các ngôn ngữ lập trình khác. Ví dụ, nếu tôi muốn mở rộng Pascal bằng một loại vòng lặp mới hoặc một số thành ngữ cụ thể, tôi sẽ phải mở rộng cú pháp và ngữ nghĩa của ngôn ngữ và sau đó triển khai tính năng mới đó trong trình biên dịch.

Có ngôn ngữ lập trình nào khác ngoài họ Lisp (tức là ngoài Common Lisp, Scheme, Clojure (?), Vợt (?), V.v.) cung cấp khả năng tương tự để mở rộng ngôn ngữ trong chính ngôn ngữ đó không?

CHỈNH SỬA

Xin vui lòng, tránh thảo luận mở rộng và được cụ thể trong câu trả lời của bạn. Thay vì một danh sách dài các ngôn ngữ lập trình có thể được mở rộng bằng cách này hay cách khác, tôi muốn hiểu theo quan điểm khái niệm, cái gì đặc trưng cho các macro Lisp như một cơ chế mở rộng và các ngôn ngữ lập trình không Lisp cung cấp một số khái niệm đó là gần với họ.


6
Lisp có một mẹo khác ngoài các macro thông thường. "Macro Macros" cho phép bắt giữ và mở rộng cú pháp của trình phân tích cú pháp khi chạy, do đó, ngay cả cấu trúc mã thông báo cơ bản của ngôn ngữ cũng được kiểm soát.
ddyer

@ddyer: Cảm ơn, tôi không biết về điều đó: một chủ đề khác sẽ được thêm vào danh sách đọc của tôi.
Giorgio

Tôi muốn cá Ruby lập trình có thể đáp ứng điều này.
Giàn

1
Câu hỏi của bạn trái ngược nhau, đầu tiên bạn hỏi danh sách, sau đó bạn hỏi thông tin khái niệm, đó là gì ???

Tôi đang yêu cầu một danh sách, nhưng không phải là một danh sách chung chung (không phải bất kỳ ngôn ngữ lập trình nào có thể được mở rộng theo một cách nào đó) bởi vì điều này sẽ quá chung chung. Tôi muốn biết các ngôn ngữ có thể được mở rộng theo cách tương tự như Lisp (phần mở rộng được xác định bằng cùng một ngôn ngữ mà nó mở rộng, không phải là một loại ngôn ngữ meta). Các câu trả lời của Péter Török, Thomas Eding, SK-logic và ốc cơ học dường như là gần nhất với những gì tôi có trong tâm trí. Tôi vẫn phải đọc qua tất cả các câu trả lời một cách cẩn thận mặc dù.
Giorgio

Câu trả lời:


19

Scala cũng thực hiện điều này (trên thực tế, nó được thiết kế một cách có ý thức để hỗ trợ định nghĩa về cấu trúc ngôn ngữ mới và thậm chí DSL hoàn chỉnh).

Ngoài các chức năng bậc cao, lambdas và currying, vốn phổ biến trong các ngôn ngữ chức năng, còn có một số tính năng ngôn ngữ đặc biệt ở đây để kích hoạt tính năng này *:

  • không có toán tử - mọi thứ đều là hàm, nhưng tên hàm có thể bao gồm các ký tự đặc biệt như '+', '-' hoặc ':'
  • dấu chấm và dấu ngoặc có thể được bỏ qua cho các lệnh gọi phương thức tham số đơn, nghĩa a.and(b)là tương đương với a and bở dạng infix
  • đối với các lệnh gọi tham số đơn, bạn có thể sử dụng dấu ngoặc nhọn thay vì dấu ngoặc nhọn thông thường - điều này (cùng với currying) cho phép bạn viết những thứ như

    val file = new File("example.txt")
    
    withPrintWriter(file) {
      writer => writer.println("this line is a function call parameter")
    }
    

    trong đó withPrintWritermột phương thức đơn giản có hai danh sách tham số, cả hai đều chứa một tham số

  • tham số theo tên cho phép bạn bỏ qua danh sách tham số trống trong lambdas, cho phép bạn viết các cuộc gọi như myAssert(() => x > 3)ở dạng ngắn hơn nhưmyAssert(x > 3)

Việc tạo ra một ví dụ DSL được thảo luận chi tiết trong Chương 11. Ngôn ngữ dành riêng cho miền trong Scala của cuốn sách miễn phí Lập trình Scala .

* Tôi không có nghĩa là những thứ này là duy nhất đối với Scala, nhưng ít nhất chúng dường như không phổ biến lắm. Tôi không phải là một chuyên gia về ngôn ngữ chức năng mặc dù.


1
+1: Thú vị. Điều này có nghĩa là để mở rộng cú pháp tôi có thể định nghĩa các lớp mới và chữ ký phương thức đó sẽ cung cấp cú pháp mới dưới dạng sản phẩm phụ?
Giorgio

@Giorgio, cơ bản là có.
Péter Török

Các liên kết không hoạt động nữa.
Nate Glenn

13

Perl cho phép xử lý trước ngôn ngữ của nó. Mặc dù điều này thường không được sử dụng đến mức thay đổi hoàn toàn cú pháp trong ngôn ngữ, nhưng nó có thể được nhìn thấy trong một số ... mô-đun lẻ:

  • Acme :: Bleach cho mã thực sự sạch
  • Acme :: Morse để viết bằng mã morse
  • Lingua :: Romana :: Perligata để viết bằng tiếng Latinh (ví dụ: danh từ là biến và số, trường hợp và từ chối thay đổi loại danh từ nextum ==> $ next, nexta ==> @next)

Ngoài ra còn có một mô-đun cho phép perl chạy mã trông giống như được viết bằng Python.

Một cách tiếp cận hiện đại hơn cho vấn đề này trong perl sẽ là sử dụng Filter :: Simple (một trong những mô-đun cốt lõi trong perl5).

Lưu ý rằng tất cả những ví dụ liên quan đến Damian Conway, người được gọi là "Bác sĩ điên của Perl". Nó vẫn là một khả năng mạnh mẽ đáng kinh ngạc trong vòng perl để xoay chuyển ngôn ngữ mà người ta muốn nó.

Thêm tài liệu cho điều này và các lựa chọn thay thế khác tồn tại tại perlfilter .


13

Haskell

Haskell có "Mẫu Haskell" cũng như "Quasiquotation":

http://www.haskell.org/haskellwiki/Template_Haskell

http://www.haskell.org/haskellwiki/Quasiquotation

Các tính năng này cho phép người dùng thêm đáng kể vào cú pháp của ngôn ngữ bên ngoài các phương tiện thông thường. Chúng cũng được giải quyết tại thời gian biên dịch, điều mà tôi nghĩ là phải lớn (ít nhất là đối với các ngôn ngữ được biên dịch) [1].

Tôi đã sử dụng phép khai báo trong Haskell một lần trước đây để tạo một trình so khớp mẫu nâng cao trên ngôn ngữ giống như C:

moveSegment :: [Token] -> Maybe (SegPath, SegPath, [Token])
moveSegment [hc| HC_Move_Segment(@s, @s); | s1 s2 ts |] = Just (mkPath s1, mkPath s2, ts)
moveSegment _ = Nothing

[1] Khác, sau đây đủ điều kiện là phần mở rộng cú pháp:, runFeature "some complicated grammar enclosed in a string to be evaluated at runtime"tất nhiên đó là một tải crap.


3
Ngoài ra còn có các tính năng khác của Haskell về cơ bản cho phép cú pháp tùy chỉnh, như tạo toán tử của riêng bạn hoặc tự động currying kết hợp với các hàm lambda (ví dụ: sử dụng điển hình forM).
Xion

Tôi thành thật nghĩ rằng các nhà khai thác cà ri và tùy chỉnh không đủ điều kiện. Chúng cho phép một người sử dụng ngôn ngữ một cách gọn gàng, nhưng họ không cho phép bạn thêm / mới / chức năng vào ngôn ngữ. TH và QQ làm. Theo một nghĩa nghiêm ngặt, cả TH và QQ cũng không hiểu theo nghĩa họ làm chính xác những gì họ được thiết kế để làm, nhưng họ cho phép bạn thực sự "hết ngôn ngữ" vào thời gian biên dịch.
Thomas Eding

1
"Khác sau đây đủ điều kiện là phần mở rộng cú pháp ...": điểm rất tốt.
Giorgio

12

Tcl có một lịch sử lâu dài về việc hỗ trợ cú pháp mở rộng. Ví dụ, đây là việc thực hiện một vòng lặp lặp ba biến số (cho đến khi dừng lại) trên các hồng y, hình vuông và hình khối của chúng:

proc loopCard23 {cardinalVar squareVar cubeVar body} {
    upvar 1 $cardinalVar cardinal $squareVar square $cubeVar cube

    # We borrow a 'for' loop for the implementation...
    for {set cardinal 0} true {incr cardinal} {
        set square [expr {$cardinal ** 2}]
        set cube [expr {$cardinal ** 3}]

        uplevel 1 $body
    }
}

Điều đó sau đó sẽ được sử dụng như thế này:

loopCard23 a b c {
    puts "got triplet: $a, $b, $c"
    if {$c > 400} {
        break
    }
}

Loại kỹ thuật này được sử dụng rộng rãi trong lập trình Tcl và chìa khóa để thực hiện nó hoàn toàn là các lệnh upvaruplevel( upvarliên kết một biến được đặt tên trong một phạm vi khác với một biến cục bộ và uplevelchạy một kịch bản trong phạm vi khác: trong cả hai trường hợp, 1chỉ ra rằng phạm vi trong câu hỏi là người gọi). Nó cũng được sử dụng rất nhiều trong mã kết hợp với cơ sở dữ liệu (chạy một số mã cho mỗi hàng trong tập kết quả), trong Tk cho GUI (để thực hiện liên kết gọi lại với các sự kiện), v.v.

Tuy nhiên, đó chỉ là một phần nhỏ của những gì được thực hiện. Ngôn ngữ nhúng thậm chí không cần phải là Tcl; nó có thể là hầu như bất cứ điều gì (miễn là nó cân bằng niềng răng của mình - mọi thứ trở nên khủng khiếp về mặt cú pháp nếu điều đó không đúng - đó là phần lớn các chương trình) và Tcl chỉ có thể gửi đến ngôn ngữ nước ngoài nhúng khi cần thiết. Ví dụ về việc này bao gồm nhúng C để thực hiện các lệnh Tcl và tương đương với Fortran. (Có thể cho rằng, tất cả các lệnh tích hợp của Tcl đều được thực hiện theo cách này, theo nghĩa chúng thực sự chỉ là một thư viện chuẩn chứ không phải ngôn ngữ.)


10

Đây là một phần câu hỏi về ngữ nghĩa. Ý tưởng cơ bản của Lisp là chương trình là dữ liệu có thể tự thao tác. Các ngôn ngữ được sử dụng phổ biến trong họ Lisp, như Scheme, không thực sự cho phép bạn thêm cú pháp mới theo nghĩa trình phân tích cú pháp; tất cả chỉ là danh sách được phân cách bằng dấu cách. Chỉ là do cú pháp cốt lõi làm quá ít, bạn có thể tạo ra hầu hết mọi cấu trúc ngữ nghĩa từ nó. Scala (được thảo luận dưới đây) là tương tự: các quy tắc tên biến rất tự do đến mức bạn có thể dễ dàng tạo ra các DSL đẹp từ nó (trong khi vẫn nằm trong cùng các quy tắc cú pháp cốt lõi).

Các ngôn ngữ này, mặc dù chúng không thực sự cho phép bạn xác định cú pháp mới theo nghĩa của bộ lọc Perl, có lõi đủ linh hoạt để bạn có thể sử dụng nó để xây dựng DSL và thêm cấu trúc ngôn ngữ.

Đặc điểm chung quan trọng là chúng cho phép bạn xác định các cấu trúc ngôn ngữ hoạt động cũng như các cấu trúc tích hợp, sử dụng các tính năng được hiển thị bởi các ngôn ngữ. Mức độ hỗ trợ cho tính năng này khác nhau:

  • Nhiều ngôn ngữ cũ với điều kiện tích hợp các chức năng như sin(), round(), vv, mà không cách nào để thực hiện của riêng bạn.
  • C ++ cung cấp hỗ trợ hạn chế. Ví dụ một số được xây dựng trong các từ khóa như phôi ( static_cast<target_type>(input), dynamic_cast<>(), const_cast<>(), reinterpret_cast<>()) có thể được mô phỏng sử dụng các hàm mẫu, trong đó sử dụng Boost cho lexical_cast<>(), polymorphic_cast<>(), any_cast<>(), ....
  • Java đã được xây dựng trong cấu trúc điều khiển ( for(;;){}, while(){}, if(){}else{}, do{}while(), synchronized(){}, strictfp{}) và không cho phép bạn xác định của riêng bạn. Thay vào đó, Scala định nghĩa một cú pháp trừu tượng cho phép bạn gọi các hàm bằng cách sử dụng cú pháp giống như cấu trúc điều khiển thuận tiện và các thư viện sử dụng điều này để xác định hiệu quả các cấu trúc điều khiển mới (ví dụ: react{}trong thư viện diễn viên).

Ngoài ra, bạn có thể xem chức năng cú pháp tùy chỉnh của Mathicala trong gói Ký hiệu . (Về mặt kỹ thuật, nó thuộc họ Lisp, nhưng có một số tính năng mở rộng được thực hiện khác nhau, cũng như khả năng mở rộng Lisp thông thường.)


2
Sai rồi. Lisp không thực sự cho phép bạn thêm hoàn toàn bất kỳ loại một cú pháp mới. Giống như trong ví dụ này: meta-alternative.net/pfront.pdf - nó không là gì ngoài macro Lisp.
SK-logic

Điều đó dường như được thực hiện bằng một ngôn ngữ được thiết kế đặc biệt để xây dựng DSL . Tất nhiên bạn có thể tạo một ngôn ngữ trong gia đình Lisp cũng cung cấp các tính năng như vậy; Tôi có nghĩa là tính năng đó không phải là một ý tưởng Lisp cốt lõi được hỗ trợ Lisps được sử dụng bcommonly (ví dụ Scheme). Chỉnh sửa để làm rõ.
Ốc cơ khí

"Ngôn ngữ để xây dựng DSL" này là một tập hợp các macro được xây dựng trên đỉnh của một Lisp khá tối giản, điển hình. Nó có thể dễ dàng chuyển đến bất kỳ Lisp nào khác (defmacro ...). Trên thực tế, tôi hiện đang chuyển ngôn ngữ này sang vợt, chỉ để cho vui. Nhưng, tôi đồng ý rằng đó là một cái gì đó không hữu ích lắm, vì cú pháp biểu thức S là quá đủ cho hầu hết các ngữ nghĩa hữu ích có thể có.
SK-logic

Và, Scheme không khác bất kỳ Lisp thông thường nào, bắt đầu từ R6RS chính thức và không chính thức cho các lứa tuổi, vì nó cung cấp một cách (define-macro ...)tương đương, do đó, có thể sử dụng bất kỳ loại phân tích nội bộ nào.
SK-logic

8

Rebol nghe gần giống như những gì bạn đang mô tả, nhưng hơi lệch sang một bên.

Thay vì xác định cú pháp cụ thể, mọi thứ trong Rebol là một lệnh gọi hàm - không có từ khóa. (Có, bạn có thể xác định lại ifwhilenếu bạn thực sự mong muốn). Ví dụ: đây là một iftuyên bố:

if now/time < 12:00 [print "Morning"]

iflà một hàm có 2 đối số: một điều kiện và một khối. Nếu điều kiện là đúng, khối được đánh giá. Âm thanh giống như hầu hết các ngôn ngữ, phải không? Chà, khối là một cấu trúc dữ liệu, nó không bị hạn chế đối với mã - ví dụ, đây là một khối, và một ví dụ nhanh về tính linh hoạt của "mã là dữ liệu":

SomeArray: [ [foo "One"] [bar "Two"] [baz "Three"] ]
foreach action SomeArray [action/1: 'print] ; Change the data
if now/time < 12:00 SomeArray/2 ; Use the data as code - right now, if now/time < 12:00 [print "Two"]

Miễn là bạn có thể tuân thủ các quy tắc cú pháp, phần lớn việc mở rộng ngôn ngữ này sẽ không có gì khác hơn là xác định các hàm mới. Ví dụ, một số người dùng đã nhập các tính năng của Rebol 3 vào Rebol 2.


7

Ruby có một cú pháp khá linh hoạt, tôi nghĩ đó là một cách "mở rộng ngôn ngữ trong chính ngôn ngữ".

Một ví dụ là cào . Nó được viết bằng Ruby, nó là Ruby, nhưng có vẻ như tạo ra .

Để kiểm tra một số khả năng, bạn có thể tìm từ khóa Rubysiêu lập trình .


13
"Cú pháp linh hoạt" khác RẤT khác với "cú pháp mở rộng". Đã lâu rồi kể từ khi tôi lập trình trong Ruby, nhưng Rake trông giống như một cách sử dụng cú pháp tích hợp được thiết kế tốt. Nói cách khác, đây là một ví dụ.
Thomas Eding

2
Đó không phải là vấn đề bằng cấp, không phải là loại? Làm thế nào bạn có thể phân biệt giữa một ngôn ngữ "cú pháp mở rộng" có thể mở rộng một số khía cạnh của cú pháp của nó nhưng không phải là ngôn ngữ "cú pháp linh hoạt"?
sắp tới

1
Nếu dòng bị mờ, thì hãy đẩy lùi dòng để C được coi là hỗ trợ cú pháp mở rộng.
Thomas Eding

Để liên kết với ví dụ của bạn, tôi sẽ vẽ đường ở đây: Một ngôn ngữ có cú pháp mở rộng có thể tạo ra cào, trông giống như thực hiện. Một ngôn ngữ với cú pháp linh hoạt có thể được mở rộng (ha, pha trộn ngôn ngữ tốt ở đó) để biên dịch và chạy tệp tạo tệp. Quan điểm của bạn về vấn đề bằng cấp là một điều tốt mặc dù. Có thể một số ngôn ngữ cho phép biên dịch Make, nhưng không phải Python. Và những người khác sẽ cho phép cả hai.
Clayton Stanley

Bất kỳ ngôn ngữ hoàn chỉnh nào cũng có thể được thực hiện để xử lý Tạo tệp. Tính linh hoạt của cú pháp không phải là một yếu tố vượt quá mức thách thức.
Giàn

7

Mở rộng cú pháp theo cách bạn đang nói về cho phép bạn tạo các ngôn ngữ cụ thể cho miền. Vì vậy, có lẽ cách hữu ích nhất để viết lại câu hỏi của bạn là, những ngôn ngữ khác có hỗ trợ tốt cho các ngôn ngữ cụ thể của tên miền?

Ruby có cú pháp rất linh hoạt và rất nhiều DSL đã phát triển mạnh ở đó, chẳng hạn như cào. Groovy bao gồm rất nhiều điều tốt đẹp đó. Nó cũng bao gồm các biến đổi AST, tương tự trực tiếp hơn với các macro Lisp.

R, ngôn ngữ cho tính toán thống kê, cho phép các hàm lấy các đối số của chúng không được đánh giá. Nó sử dụng điều này để tạo DSL để chỉ định công thức hồi quy. Ví dụ:

y ~ a + b

có nghĩa là "khớp một dòng có dạng k0 + k1 * a + k2 * b với các giá trị trong y."

y ~ a * b

có nghĩa là "khớp một dòng có dạng k0 + k1 * a + k2 * b + k3 * a * b với các giá trị trong y."

Và như vậy.


2
Các biến đổi AST của Groovy rất dài dòng so với các macro Lisp hoặc Clojure. Ví dụ, ví dụ Groovy 20 dòng tại groovy.codehaus.org/Global+AST+Transformations có thể được viết lại thành một dòng ngắn trong Clojure, ví dụ `(this (println ~ message)). Không chỉ vậy mà bạn cũng sẽ không phải biên dịch jar, viết siêu dữ liệu hoặc bất kỳ nội dung nào khác trên trang Groovy đó.
Vorg van Geir

7

Converge là một ngôn ngữ siêu lập trình không lispy khác. Và, ở một mức độ nào đó, C ++ cũng đủ điều kiện.

Có thể cho rằng, MetaOCaml khá xa Lisp. Đối với một phong cách hoàn toàn khác về khả năng mở rộng cú pháp, nhưng khá mạnh mẽ, hãy xem CamlP4 .

Nemerle là một ngôn ngữ có thể mở rộng khác với siêu lập trình kiểu Lisp, mặc dù nó gần với các ngôn ngữ như Scala.

Và, chính Scala cũng sẽ sớm trở thành một ngôn ngữ như vậy.

Chỉnh sửa: Tôi quên mất ví dụ thú vị nhất - JetBrains MPS . Nó không chỉ rất xa với bất cứ thứ gì Lispish, nó thậm chí còn là một hệ thống lập trình phi văn bản, với một trình soạn thảo hoạt động trực tiếp ở cấp độ AST.

Edit2: Để trả lời một câu hỏi cập nhật - không có gì độc đáo và đặc biệt trong các macro Lisp. Về lý thuyết, bất kỳ ngôn ngữ nào cũng có thể cung cấp một cơ chế như vậy (tôi thậm chí đã làm nó với C đơn giản). Tất cả những gì bạn cần là quyền truy cập vào AST của bạn và khả năng thực thi mã trong thời gian biên dịch. Một số phản ánh có thể giúp đỡ (truy vấn về các loại, các định nghĩa hiện có, v.v.).


Liên kết Scala của bạn nói rõ ràng rằng các macro được đề xuất "không thể thay đổi cú pháp của Scala". (Thật thú vị, nó liệt kê đây là một sự khác biệt giữa đề xuất đó và các macro tiền xử lý C / C ++!)
ruakh

@ruakh, vâng, đó là cách tiếp cận tương tự như Converge và Template Haskell - ứng dụng macro được đánh dấu rõ ràng và không thể trộn lẫn với cú pháp "bình thường". Nhưng, bên trong bạn có thể có bất kỳ cú pháp nào bạn thích, vì vậy có thể nói đó là một cú pháp mở rộng. Thật không may, yêu cầu "không thể thiếu", không may, giới hạn các tùy chọn của bạn đối với các ngôn ngữ như thế này.
SK-logic

"Tôi thậm chí đã làm điều đó với đồng bằng C": Làm thế nào điều này có thể?
Giorgio

@Giorgio, tất nhiên tôi đã sửa đổi trình biên dịch (thêm macro và biên dịch mô đun gia tăng, điều này thực sự khá tự nhiên đối với C).
SK-logic

Tại sao truy cập AST cần thiết?
Elliot Gorokhovsky

6

Prolog cho phép xác định các toán tử mới được dịch sang các thuật ngữ ghép cùng tên. Ví dụ, cái này định nghĩa một has_cattoán tử và định nghĩa nó là một vị ngữ để kiểm tra xem một danh sách có chứa nguyên tử hay không cat:

:- op(500, xf, has_cat).
X has_cat :- member(cat, X).

?- [apple, cat, orange] has_cat.
true ;
false.

xfnghĩa has_catlà một toán tử postfix; việc sử dụng fxsẽ làm cho nó trở thành một toán tử tiền tố và xfxsẽ biến nó thành một toán tử infix, lấy hai đối số. Kiểm tra liên kết này để biết thêm chi tiết về việc xác định toán tử trong Prolog.


5

TeX hoàn toàn không có trong danh sách. Tất cả các bạn đều biết điều đó, phải không? Nó trông giống như thế này:

Some {\it ``interesting''} example.

Khác ngoại trừ việc bạn có thể xác định lại cú pháp mà không bị hạn chế. Mỗi mã thông báo (!) Trong ngôn ngữ có thể được gán một ý nghĩa mới. ConTeXt là gói macro đã thay thế dấu ngoặc nhọn bằng dấu ngoặc vuông:

Some \it[``interesting''] example.

Gói macro phổ biến hơn LaTeX cũng xác định lại ngôn ngữ cho các mục đích của nó, ví dụ: thêm \begin{environment}…\end{environment}cú pháp.

Nhưng nó không dừng lại ở đó. Về mặt kỹ thuật, bạn cũng có thể xác định lại các mã thông báo để phân tích các nội dung sau:

Some <it>“interesting”</it> example.

Vâng, hoàn toàn có thể. Một số gói sử dụng điều này để xác định các ngôn ngữ cụ thể miền nhỏ. Chẳng hạn, gói TikZ định nghĩa một cú pháp súc tích cho các bản vẽ kỹ thuật, cho phép như sau:

\foreach \angle in {0, 30, ..., 330} 
  \draw[line width=1pt] (\angle:0.82cm) -- (\angle:1cm);

Hơn nữa, TeX là Turing hoàn chỉnh để bạn có thể thực hiện mọi thứ với nó theo nghĩa đen . Tôi chưa bao giờ thấy điều này được sử dụng hết tiềm năng của nó bởi vì nó sẽ khá vô nghĩa và rất phức tạp nhưng hoàn toàn có thể làm cho đoạn mã sau có thể phân tích cú pháp chỉ bằng cách xác định lại mã thông báo (nhưng điều này có thể sẽ đi đến giới hạn vật lý của trình phân tích cú pháp, do cách nó được xây dựng):

for word in [Some interesting example.]:
    if word == interesting:
        it(word)
    else:
        word

5

Boo cho phép bạn tùy chỉnh ngôn ngữ rất nhiều vào thời gian biên dịch thông qua các cú pháp cú pháp.

Boo có một "đường ống biên dịch mở rộng". Điều đó có nghĩa là trình biên dịch có thể gọi mã của bạn để thực hiện các phép biến đổi AST tại bất kỳ điểm nào trong đường ống trình biên dịch. Như bạn đã biết, những thứ như Generics của Java hay Linq của C # chỉ là các phép biến đổi cú pháp tại thời điểm biên dịch, vì vậy điều này khá mạnh mẽ.

So với Lisp, ưu điểm chính là điều này hoạt động với bất kỳ loại cú pháp nào. Boo đang sử dụng cú pháp lấy cảm hứng từ Python, nhưng có lẽ bạn có thể viết một trình biên dịch mở rộng với cú pháp C hoặc Pascal. Và vì macro được đánh giá tại thời điểm biên dịch, không có hình phạt hiệu suất.

Nhược điểm, so với Lisp, là:

  • Làm việc với AST không thanh lịch như làm việc với biểu thức s
  • Vì macro được gọi vào thời gian biên dịch, nó không có quyền truy cập vào dữ liệu thời gian chạy.

Ví dụ: đây là cách bạn có thể triển khai cấu trúc điều khiển mới:

macro repeatLines(repeatCount as int, lines as string*):
    for line in lines:
        yield [| print $line * $repeatCount |]

sử dụng:

repeatLines 2, "foo", "bar"

sau đó được dịch, tại thời điểm biên dịch, thành một cái gì đó như:

print "foo" * 2
print "bar" * 2

(Thật không may, tài liệu trực tuyến của Boo luôn lỗi thời và thậm chí không bao gồm những thứ nâng cao như thế này. Tài liệu tốt nhất cho ngôn ngữ tôi biết là cuốn sách này: http://www.manning.com/rahien/ )


1
Tài liệu web cho tính năng này hiện đang bị hỏng và tôi không tự viết Boo, nhưng tôi nghĩ sẽ thật đáng tiếc nếu nó bị bỏ qua. Tôi đánh giá cao phản hồi của mod và sẽ xem xét lại cách tôi đóng góp thông tin miễn phí trong thời gian rảnh.
Dan

4

Việc đánh giá Mathicala dựa trên sự phù hợp và thay thế mẫu. Điều đó cho phép bạn tạo các cấu trúc điều khiển của riêng mình, thay đổi cấu trúc điều khiển hiện có hoặc thay đổi cách đánh giá biểu thức. Ví dụ: bạn có thể triển khai "logic mờ" như thế này (đơn giản hóa một chút):

fuzzy[a_ && b_]      := Min[fuzzy[a], fuzzy[b]]
fuzzy[a_ || b_]      := Max[fuzzy[a], fuzzy[b]]
fuzzy[!a_]           := 1-fuzzy[a]
If[fuzzy[a_], b_,c_] := fuzzy[a] * fuzzy[b] + fuzzy[!a] * fuzzy[c]

Điều này ghi đè đánh giá cho các toán tử logic được xác định trước &&, ||,! và Ifmệnh đề tích hợp.

Bạn có thể đọc các định nghĩa này như định nghĩa hàm, nhưng ý nghĩa thực sự là: Nếu một biểu thức khớp với mẫu được mô tả ở bên trái, thì nó được thay thế bằng biểu thức ở bên phải. Bạn có thể định nghĩa mệnh đề If của riêng bạn như thế này:

myIf[True, then_, else_] := then
myIf[False, then_, else_] := else
SetAttributes[myIf, HoldRest]

SetAttributes[..., HoldRest] nói với người đánh giá rằng nó nên đánh giá đối số đầu tiên trước khi khớp mẫu, nhưng giữ đánh giá cho phần còn lại cho đến khi mẫu được khớp và thay thế.

Điều này được sử dụng rộng rãi bên trong các thư viện chuẩn Mathicala để xác định hàm Dlấy biểu thức và đánh giá đạo hàm tượng trưng của nó.


3

Metalua là một ngôn ngữ và trình biên dịch tương thích với Lua cung cấp điều này.

  • Tương thích hoàn toàn với các nguồn Lua 5.1 và mã byte: ngữ nghĩa và cú pháp rõ ràng, thanh lịch, sức mạnh biểu cảm đáng kinh ngạc, hiệu suất tốt, tính di động gần phổ quát. - Một hệ thống vĩ mô hoàn chỉnh, có sức mạnh tương tự như những gì được giới thiệu bởi phương ngữ Lisp hoặc Bản mẫu Haskell; các chương trình bị thao túng có thể được xem
    như mã nguồn, dưới dạng cây cú pháp trừu tượng hoặc là sự pha trộn tùy ý
    của chúng, tùy theo điều kiện nào phù hợp với nhiệm vụ của bạn hơn.
  • Trình phân tích cú pháp có thể mở rộng linh hoạt, cho phép bạn hỗ trợ các macro của mình bằng cú pháp kết hợp độc đáo với phần còn lại của ngôn ngữ.

  • Một tập hợp các phần mở rộng ngôn ngữ, tất cả được triển khai như các macro metalua thông thường.

Sự khác biệt với Lisp:

  • Đừng làm phiền các nhà phát triển với các macro khi họ không viết một: cú pháp và ngữ nghĩa của ngôn ngữ sẽ phù hợp nhất với 95% thời gian khi chúng ta không viết macro.
  • Khuyến khích các nhà phát triển tuân theo các quy ước của ngôn ngữ: không chỉ với "những người thực hành tốt nhất" không ai nghe, mà bằng cách cung cấp một API giúp viết mọi thứ theo cách Metalua dễ dàng hơn. Khả năng đọc của các nhà phát triển đồng nghiệp quan trọng hơn và khó đạt được hơn khả năng đọc của trình biên dịch, và đối với điều này, việc có một tập hợp các quy ước được tôn trọng sẽ giúp ích rất nhiều.
  • Tuy nhiên, cung cấp tất cả sức mạnh của một người sẵn sàng để xử lý. Cả Lua và Metalua đều không bị ràng buộc và kỷ luật bắt buộc, vì vậy nếu bạn biết bạn đang làm gì, ngôn ngữ sẽ không cản trở bạn.
  • Làm cho nó rõ ràng khi một cái gì đó thú vị xảy ra: tất cả các hoạt động meta xảy ra giữa + {...} và - {...}, và trực quan ra khỏi mã thông thường.

Một ví dụ điển hình của ứng dụng là việc thực hiện khớp mẫu giống ML.

Xem thêm: http://lua-users.org/wiki/MetaLua


Mặc dù Lua thực sự không phải là Lisp, nhưng danh sách "sự khác biệt" của bạn khá đáng ngờ (mục duy nhất có liên quan là mục cuối cùng). Và, tất nhiên, cộng đồng Lisp sẽ không đồng ý với một lời ca tụng rằng người ta không nên viết / sử dụng macro 95% thời gian - Cách Lisp hướng tới một cái gì đó giống như 95% thời gian sử dụng và viết DSL, trên đầu các macro, của khóa học.
SK-logic

2

Nếu bạn đang tìm kiếm các ngôn ngữ có thể mở rộng, bạn nên xem qua Smalltalk.

Trong Smalltalk, cách duy nhất để lập trình là thực sự mở rộng ngôn ngữ. Không có sự khác biệt giữa IDE, các thư viện hoặc chính ngôn ngữ. Tất cả đều bị cuốn hút đến mức Smalltalk thường được gọi là một môi trường hơn là một ngôn ngữ.

Bạn không viết các ứng dụng độc lập trong Smalltalk, thay vào đó bạn mở rộng môi trường ngôn ngữ.

Kiểm tra http://www.wworld.st/ để biết một số tài nguyên và thông tin.

Tôi muốn giới thiệu Pharo là phương ngữ của thế giới Smalltalk: http://pharo-project.org

Hy vọng nó sẽ giúp!


1
Nghe có vẻ thú vị.
Thomas Eding

1

Có những công cụ cho phép tạo ngôn ngữ tùy chỉnh mà không cần viết toàn bộ trình biên dịch từ đầu. Ví dụ, có Spaggerax , một công cụ chuyển đổi mã: bạn đặt các quy tắc chuyển đổi và ngữ pháp đầu vào (được viết theo cách khai báo ở mức rất cao), sau đó bạn có thể tạo mã nguồn Java (hoặc ngôn ngữ khác, nếu bạn đủ quan tâm) từ một ngôn ngữ tùy chỉnh được thiết kế bởi bạn.

Vì vậy, có thể lấy ngữ pháp của ngôn ngữ X, xác định ngữ pháp của ngôn ngữ X '(X với các tiện ích mở rộng tùy chỉnh của bạn) và biến đổi X' → X, và Spaggerax sẽ tạo ra trình biên dịch X '→ X.

Hiện tại, nếu tôi hiểu chính xác, hỗ trợ tốt nhất là dành cho Java, với hỗ trợ C # đang được phát triển (hoặc tôi đã nghe thấy). Kỹ thuật này có thể được áp dụng cho bất kỳ ngôn ngữ nào có ngữ pháp tĩnh (vì vậy, ví dụ có thể không phải là Perl ).


1

Forth là một ngôn ngữ khác có khả năng mở rộng cao. Nhiều triển khai Forth bao gồm một hạt nhân nhỏ được viết bằng trình biên dịch mã hoặc C, sau đó phần còn lại của ngôn ngữ được viết bằng chính Forth.

Ngoài ra còn có một số ngôn ngữ dựa trên ngăn xếp được lấy cảm hứng từ Forth và chia sẻ tính năng này, chẳng hạn như Factor .


0

Funge-98

Tính năng vân tay của Funge-98 cho phép có thể được thực hiện để cho phép tái cấu trúc hoàn toàn toàn bộ cú pháp và ngữ nghĩa của ngôn ngữ. Nhưng chỉ khi người triển khai cung cấp một cơ chế vân tay cho phép người dùng thay đổi lập trình langauage (về mặt lý thuyết thì có thể thực hiện theo cú pháp và ngữ nghĩa Funge-98 thông thường). Nếu vậy, người ta có thể làm cho phần còn lại của tệp (hoặc bất kỳ phần nào của tệp) hoạt động như C ++ hoặc Lisp (hoặc bất cứ thứ gì anh ta muốn).

http://quadi.net/funge/spec98.html#Fingerprints


Tại sao bạn đăng bài này một cách riêng biệt thay vì thêm vào câu trả lời trước đó của bạn ?
gnat

1
Bởi vì Funge không liên quan gì đến Haskell.
Thomas Eding

-1

Để có được những gì bạn đang tìm kiếm, bạn thực sự cần những dấu ngoặc đơn đó và thiếu cú ​​pháp. Một vài ngôn ngữ dựa trên cú pháp có thể đến gần, nhưng nó không hoàn toàn giống với một macro thực sự.

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.