Giới thiệu ngôn ngữ lập trình Cognition
Vấn đề đặt ra
- Các lập trình viên Lisp cho rằng có thể tạo metaprogramming và hệ thống tổng quát nhờ mã S-expression và hệ thống macro năng lực cao
- Nhưng Lisp có những vấn đề cốt lõi:
- metaprogramming và lập trình không giống nhau, vì vậy Lisp luôn có cú pháp chặt chẽ (một số ký tự cho ngoặc hoặc cho việc look-ahead)
- dấu ngoặc trái cho Lisp biết phải tiếp tục đọc cho đến khi gặp dấu ngoặc phải
- điều này khiến chữ trái/phải ngoặc trở thành không thể thay đổi trong ngôn ngữ (về mặt khái niệm không phải, nhưng ở một số hiện thực là không khả thi)
- quan trọng hơn, không thể thay đổi trật tự phân tách token như vậy sau này nếu không xử lý chuỗi
- Các ngôn ngữ khác cũng có cách khác để quyết định trước nội dung cần đọc tiếp bằng cách dựa vào token cụ thể
- quá trình này được gọi là cú pháp (syntax)
- Cognition sử dụng
antisyntax với cú pháp hậu tố (postfix) hoàn toàn nên khác biệt
- điều này tương tự như ngôn ngữ concatenative, nhưng ngôn ngữ concatenative cũng có hai vấn đề chính:
- việc đưa vào ký tự ngoặc trái/phải (thực ra là cú pháp tiền tố)
- ký tự trích dẫn chuỗi
- nó không phù hợp cho ngôn ngữ phổ biến
- các vấn đề tương tự cũng xuất hiện trong các hiện thực cú pháp C của Lisp (quá nhiều ký tự escape, cần ký tự khoảng trắng để phân biệt bắt đầu/kết thúc token cụ thể)
- Racket có hệ thống macro nhưng không dynamic ở runtime và dùng tiền xử lý
Giới thiệu Cognition
- Dự án đã được nghiên cứu cùng với Matthew Hinton trong vài tháng
- Mục tiêu là xây dựng một trong những hệ thống cú pháp tổng quát nhất bằng việc sử dụng cú pháp hậu tố hoàn toàn
- Có thể cần kiến thức nền về cú pháp/tokenization/parsing, nhưng cố gắng giải thích theo cách dễ hiểu nhất
- Kho lưu trữ: https://github.com/metacrank/cognition
Baremetal Cognition
- Baremetal Cognition tương tự Brainfuck nhưng cho phép metaprogramming sâu
- Ví dụ mã bootstrap:
ldfgldftgldfdtgldf dfiff1 crank f
- Khoảng trắng và xuống dòng rất quan trọng
- Dòng thứ hai có khoảng trắng sau
df
- Dòng thứ ba có ký tự khoảng trắng
- Nhờ đó có thể đưa vào hai khái niệm mới là delimiter và ignore
Tokenization
- Delimiter cho phép tokenizer phân biệt điểm bắt đầu và kết thúc token
- Danh sách tokenizer ký tự đơn được công khai để sửa đổi trong Cognition
- Ký tự ignored là ký tự mà tokenizer bỏ qua hoàn toàn ở bước đầu tiên của mọi vòng read-eval-print
- tức là khi bắt đầu thu thập token sẽ bỏ qua các ký tự trong tập ignored
- Mặc định tất cả ký tự là delimiter và không có ký tự nào bị ignored
- Với các danh sách delimiter và ignored, có thể bật/tắt blacklist/whitelist ký tự để mang lại tính ngắn gọn và thực dụng
Falias
- Falias là danh sách từ được thực thi khi được đặt lên stack
- Mọi Falias đều thực thi phần tử trên cùng stack (tương đương với
eval trong Stem)
f là Falias mặc định, nó thực thi d trên đỉnh stack mà không cần đẩy f lên stack
d biến danh sách delimiter thành chuỗi giá trị của từ (tức là loại bỏ ký tự l khỏi delimiter)
- Trong môi trường mặc định, không có từ nào được thực thi ngoài các Falias đặc biệt
Lưu ý về delimiter
- Delimiter có một quy tắc thú vị
- trong vòng tokenization, nếu không bỏ qua ký tự, ký tự delimiter sẽ được đưa vào token hiện tại rồi tiếp tục
- điều này trái ngược với singlet (đưa chính nó vào token rồi skip để kết thúc thu thập token)
- Có thể đặt blacklist/whitelist
- có thể blacklist/whitelist các danh sách delimiter, singlet, ignored character
- mặc định không có delimiter nào bị blacklist, singlet nào bị whitelist, hoặc ignored nào bị whitelist
- Tất cả các ký tự còn lại được thu thập vào token hiện tại và tiếp tục thu thập ký tự mới cho tới khi vòng lặp dừng bởi quy tắc delimiter hoặc singlet
Tiếp tục mã bootstrap
ldf
l trở thành ký tự không phải delimiter
gldftgldfdtgldf dfiff1 crank f
- Vì
d là delimiter nên gl được đẩy lên stack, Falias f được gọi để biến gl thành ký tự không phải delimiter
tgl được đẩy lên stack và trở thành không phải delimiter bởi df
dtgl được đẩy lên stack và bởi \ndf, newline (\n) trở thành duy nhất ký tự không phải delimiter (newline có trong mã thực tế)
- Theo quy tắc delimiter, ký tự space và
\n được đẩy lên stack (dòng thứ 3 chứa space)
- Một từ
\ \n khác được token hóa
- Stack hiện tại như sau (từ dưới lên trên):
3. dtgl
2. [kí tự space]\n
- [kí tự space]\n
df đặt \ \n thành ký tự không phải delimiter
if đặt \ \n thành ký tự ignored (bỏ qua khi bắt đầu tokenization)
f thực thi dtgl để đẩy toggle của dflag lưu lại việc whitelist/blacklist delimiter
- Bây giờ mọi ký tự không phải delimiter trở thành delimiter và mọi delimiter trở thành không phải delimiter
- Cuối cùng, space và newline trở thành delimiter của token và bị ignored khi bắt đầu token
- Tiếp đó
1 được token hóa và đẩy lên stack, rồi từ crank token hóa rồi được f thực thi (1 trong trường hợp này được coi là số nhưng trong Cognition tất cả đều là từ)
- Hoàn thành chuỗi bootstrap!
crank làm gì sẽ được giải thích ở phần sau
Tóm tắt bootstrap
- Cognition cho phép thay đổi phương thức tokenization theo lập trình một cách động
- không thể làm điều này trong các ngôn ngữ khác
- bạn có thể lập trình tokenizer cho ngôn ngữ ngoài ngay trong Cognition và token hóa theo ý muốn
- Nhờ dùng cú pháp hậu tố và không look-ahead
- không cần parse hơn một token trước khi đánh giá biểu thức
- Dùng Falias, các từ có thể được thực thi mà không cần từ tiền tố hay hàm thực thi mặc định
Crank
- Hệ thống metacrank cho phép cấu hình cách mặc định để thực thi token trên stack
- Từ
crank nhận một đối số số và mỗi khi đưa n từ vào stack sẽ thực thi phần tử trên cùng
- Ví dụ mã (
crank 1 đã được đặt):
5 crank 2
crank 2 crank
1 crank unglue swap quote prepose def
- Trong môi trường
crank 1, có thể tắt việc dùng f trong lúc đánh giá token
- mỗi token theo cú pháp phân tách bởi newline và space sẽ được đánh giá từng token một
- Mã bắt đầu thử đánh giá
5 (không phải builtin nên tự đánh giá)
crank được cấu hình để thực thi khi có 5 token trong stack (crank 5 được đặt nên crank không thực thi)
2crank, 2, crank, 1 đều được đẩy lên stack (crank không được thực thi bởi vì đang được crank 5 thiết lập):
4. 2crank
3. 2
2. crank
- 1
crank là từ thứ 5 nên được thực thi (crank 1 đang đặt)
unglue là builtin lấy giá trị của từ trên cùng stack (được sử dụng bởi 1)
- tức là lấy function pointer gắn với builtin
crank
- Stack hiện tại:
3. 2crank
2. 2
- [CLIB]
- CLIB là function pointer trỏ tới builtin
crank
- Thực thi
swap:
3. 2crank
2. [CLIB]
- 2
- Thực thi
quote (builtin trích dẫn phần tử trên cùng stack):
3. 2crank
2. [CLIB]
- [2]
- Thực thi
prepose (giống compose trong Stem nhưng đặt trước và cho là VMACRO):
2. 2crank
- ([2] [CLIB])
- Gọi
def
- Định nghĩa từ
2crank để đẩy 2 rồi gọi function pointer của builtin crank
- Cần giải thích VMACRO là gì, và sự khác biệt giữa stack Cognition và Stem stack
Khác biệt với Stem
- Trong stack Stem có thể đặt từ trực tiếp
- Trong Cognition, từ được đưa vào container trước rồi mới đẩy vào stack thay vì được evaluate ngay
- Ở Stem, từ như
compose hoạt động giữa một từ (hoặc container có một từ) và container khác
- Điều này làm cho API của Cognition nhất quán hơn
- Những từ như
cd cũng tận dụng khái niệm này
Macro
- Một điểm khác nữa giữa quoting của Stem và container của Cognition
- Khi đánh giá macro, mọi thứ bên trong macro đều được đánh giá và crank bị ignore
- Khi một từ được binding, đánh giá từ đó sẽ
1 bình luận
Ý kiến từ Hacker News
Một số ý kiến chính có thể tóm tắt như sau: