Các phần mở rộng ngôn ngữ đã biến mất của trình biên dịch MetaWare High C (2023)
(duriansoftware.com)- Năm 1989, High C Compiler cho FM TOWNS không chỉ hỗ trợ môi trường DOS mà còn tích hợp nhiều tính năng ngôn ngữ hướng tới người dùng, vốn hiếm thấy ở các trình biên dịch C thời đó
- Khi kết hợp với DOS extender của Phar Lap, nó trở thành trình biên dịch C 1st-party cho FM TOWNS trong dòng phát triển tận dụng 80386 32-bit trên môi trường MS-DOS 16-bit
- Dấu gạch dưới trong literal số, đối số có nhãn, phạm vi
case, hàm lồng nhau, generator... là những tính năng chỉ xuất hiện muộn hơn rất nhiều trong chuẩn C/C++, hoặc đến nay vẫn chưa có trong chuẩn - Hàm lồng nhau cung cấp “full function value” dưới dạng closure không thoát ra ngoài, truyền cả con trỏ hàm lẫn con trỏ ngữ cảnh, nên biểu đạt tốt hơn con trỏ hàm C thông thường
- Generator được triển khai như lớp cú pháp đường trên hàm lồng nhau, hoạt động theo cấu trúc đơn giản: biến phần thân vòng lặp
forcủa bên gọi thành một hàm lồng nhau rồi truyền nó làm đối số choyield
Vị trí của FM TOWNS và High C
- Một cuốn manual trình biên dịch C từ thập niên 1980, tìm thấy trong đống sách về FM TOWNS, hóa ra chứa lượng phần mở rộng ngôn ngữ phong phú hơn dự đoán
- Muốn dùng C và các ngôn ngữ cùng họ trong môi trường thực tế thì trong thời gian dài vẫn cần đến phần mở rộng của nhà cung cấp
- Ngày nay, trong hệ sinh thái xoay quanh GCC, Clang và MSVC, các phần mở rộng có xu hướng tập trung vào xử lý theo nền tảng hoặc kiểm soát chi tiết mức thấp
- Vào thập niên 1980, có nhiều công ty nhỏ hơn cùng cạnh tranh để được chấp nhận, nên các tính năng mở rộng cũng đa dạng hơn
- Phar Lap đã tạo ra một trong những DOS extender đầu tiên giúp tận dụng bộ xử lý 80386 32-bit trong môi trường MS-DOS 16-bit
- MetaWare đã port High C Compiler sang SDK DOS extender của Phar Lap theo yêu cầu từ Phar Lap
- Fujitsu tích hợp DOS extender của Phar Lap vào OS của nền tảng FM TOWNS dựa trên 803386, và High C trở thành trình biên dịch C 1st-party của nền tảng này
- FM TOWNS ra mắt năm 1989, ngay trước khi chuẩn ANSI C đầu tiên là C89 được phê chuẩn
Những tiện ích nhỏ đi trước chuẩn
-
Dấu phân tách bằng gạch dưới trong literal số
- Có thể chèn dấu gạch dưới phân tách vào literal số để các số dài dễ đọc hơn
- C++ giới thiệu dấu phân tách bằng nháy đơn như
1'000'000trong C++14 - C mãi đến C23 mới đưa vào một tính năng tương tự
-
Đối số có nhãn
- Có thể gắn tên cho đối số trong các hàm có nhiều tham số hoặc hay dùng những kiểu như
bool, nơi ý nghĩa khó thể hiện rõ tại chỗ gọi - Đối số có nhãn của High C hoạt động tương tự một tính năng nổi tiếng của Python
- Nhãn đối số là tùy chọn
- Nếu có nhãn, có thể chỉ định đối số theo thứ tự bất kỳ bằng cú pháp
argumentName => value - Có thể trộn đối số không nhãn với đối số có nhãn, nhưng mọi tham số của hàm đều phải có đối số tương ứng
- Chuẩn C và C++ hiện vẫn chưa có tính năng này
- Có thể gắn tên cho đối số trong các hàm có nhiều tham số hoặc hay dùng những kiểu như
-
Phạm vi
case- Cung cấp khả năng khớp cả một khoảng giá trị cùng lúc, giống
case low..highcủa Pascal - Chuẩn C và C++ chưa chấp nhận tính năng này
- Cung cấp khả năng khớp cả một khoảng giá trị cùng lúc, giống
Hàm lồng nhau và full function value
- High C cho phép khai báo hàm lồng nhau bên trong hàm khác, giống Pascal
- Cách triển khai của nó gần với một dạng hoàn chỉnh hơn so với Pascal chuẩn hay phần mở rộng hàm lồng nhau của GCC
- High C không chỉ cho phép khai báo hàm lồng nhau mà còn cho phép khai báo kiểu full function value
- Khác với con trỏ hàm C truyền thống, nó chứa cả con trỏ hàm lẫn con trỏ ngữ cảnh
- Nhờ đó có thể tìm lại ngữ cảnh mà hàm lồng nhau đã capture
- Đây là closure không thoát ra ngoài, nên vòng đời không kéo dài đến sau khi hàm bao ngoài trả về
- Phần mở rộng hàm lồng nhau của GCC cố tham chiếu hàm lồng nhau như con trỏ hàm thông thường bằng cách ghi mã thực thi được lên stack gọi để thunk con trỏ ngữ cảnh
- Cách làm này dẫn đến rủi ro bảo mật lớn, khiến nhiều nền tảng phải vô hiệu hóa hoàn toàn tính năng đó
- Tham chiếu tới hàm cục bộ trong High C có thể được dùng như giá trị hạng nhất, nhưng vòng đời không được kéo dài đến sau khi hàm bao ngoài trả về
- Hàm lồng nhau còn có thể
gototới hàm cha- Tương tự block của Smalltalk, nó hỗ trợ thoát phi cục bộ, tức thoát ra ngoài hàm lồng nhau
- Nhờ đó có thể tạo ra các hàm hoạt động như điều khiển luồng
- Objective-C đến năm 2009 mới có blocks dùng được như escaping closure, còn C++ giới thiệu lambda vào năm 2011
- Cả hai đều không có khả năng thoát phi cục bộ
- Chuẩn C đến nay vẫn chưa có tính năng hàm lồng nhau chính thức
Coroutine generator
- MetaWare nhấn mạnh mạnh tính năng generator đến mức dành hẳn một chương để nói về nó
- High C đã hỗ trợ coroutine generator kiểu Python trong plain C ngay từ năm 1989
- Hàm generator được khai báo bằng cú pháp
void foo(Arg arguments) -> (Yield yields)- Bên trong hàm có thể gọi nhiều lần hàm ma thuật
yield(values...)để tạo ra một chuỗi giá trị - Bên gọi sẽ duyệt lần lượt các giá trị được tạo ra bằng cú pháp vòng lặp
formới dạngfor variable... <- foo(arguments...) do { ... }
- Bên trong hàm có thể gọi nhiều lần hàm ma thuật
- Cách triển khai này có thể kết hợp phức tạp với hàm lồng nhau
- Hàm lồng nhau bên trong generator có thể capture hành vi
yieldcủa generator bao ngoài - Hàm lồng nhau có thể tự gọi đệ quy để duyệt cây hoặc cấu trúc dữ liệu đệ quy, đồng thời
yieldở mỗi bước
- Hàm lồng nhau bên trong generator có thể capture hành vi
- Kiểu này có vẻ khó triển khai trong Python hoặc nhiều ngôn ngữ coroutine generator phổ biến khác
Cách triển khai generator và khác biệt với ngôn ngữ chuẩn
- Generator của High C hoạt động như lớp cú pháp đường trên hàm lồng nhau, không cần runtime cao cấp
- Khai báo generator dạng
void foo(Arg arguments) -> (Yield yields)tương đương với khai báo hàm thông thườngvoid foo(void yield(Yield yields)!, Arg arguments)yieldlà một tham số ngầm có kiểu “full function value”- Lời gọi
yield(values)trong phần thân generator chỉ là lời gọi hàm bình thường tới tham số hàm ngầm này
- Phần thân vòng lặp
forphía bên gọi sẽ được chuyển thành một hàm lồng nhau- Hàm lồng nhau này được truyền làm đối số
yieldcho generator - Cấu trúc đơn giản nhưng hiệu quả
- Hàm lồng nhau này được truyền làm đối số
- Vì hàm lồng nhau hỗ trợ thoát phi cục bộ, nên các lệnh
break,continue,gotonhảy ra khỏi phần thân vòng lặpforcũng hoạt động bằng cáchgototới vị trí thích hợp bên ngoài vòng lặp - Khả năng chuẩn C tích hợp những tính năng như vậy là khá thấp
- C++20 cung cấp tính năng coroutine rất linh hoạt nhưng cũng phức tạp, dựa trên biến đổi coroutine ở thời điểm biên dịch
- Có lẽ có thể dùng nó để triển khai generator
- Tuy nhiên, kết quả tạo ra có vẻ sẽ không kết hợp trực quan với hàm cục bộ theo cách này
1 bình luận
Các bình luận trên Hacker News
May mắn là tôi có một bản tiếng Anh của High C/C++ Language Reference
http://jdebp.uk./FGA/metaware-iterator-driven-for.html
http://jdebp.uk./Proposals/metaware-iterator-driven-for.html
breakhayreturnđược biên dịch như thế nào. Có lẽ nó phải được biến đổi sao cho hàmyieldtrả về mã trạng thái rồi kiểm tra tại điểm gọi?case, đối số đặt tên, hàm lồng nhau, hàm lồng nhau tĩnh, và tính năng tương tự generatorVí dụ có thể viết các dạng như
int a = 1_234_567;,case 5 .. case 6:,test(b:3, a:4);Hàm lồng nhau tĩnh không thể truy cập biến frame của hàm bên ngoài, nên sẽ báo lỗi kiểu
Error: static function test.foo.plus cannot access variable i in frame of function test.fooTính năng tương tự generator nằm ở https://dlang.org/spec/statement.html#foreach_over_struct_an...
Ví dụ nếu xây dựng một dịch vụ cache in-memory, tốt nhất là các mục cache tự thân không nên để garbage collector theo dõi. Vì garbage collector thường không biết mẫu truy cập thực tế nên có thể gây cản trở. Nhưng với hầu hết các thành phần khác của dịch vụ đó, có garbage collector lại phù hợp hơn
Vì sao đối số đặt tên lại có dạng
test(a:4, b:3)chứ không phảitest(.a=4, b.=3);?Tôi cũng tò mò không biết trong C có thể xử lý kiểu hạng nhất như thế nào
lcc-winđã thêm operator overloading, đối số hàm mặc định và function overloading. Trong tài liệu, hãy xem mục “generic functions” [1]Trình biên dịch C của Plan 9 cũng đưa vào nhiều phần mở rộng ngôn ngữ, trong đó một số như struct/union ẩn danh sau này đã đi vào chuẩn C. Hiện GCC chấp nhận cờ
-fplan9-extensions[2], có thể bật một số tính năng khá hữu ích như tự động chuyển đổi con trỏ struct thành trường ẩn danh trong lời gọi hàm và phép gán[1] https://lcc-win32.services.net/C-Tutorial.pdf
[2] https://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html
Thật tiếc là chúng không lan rộng ra thế giới để ảnh hưởng đến chuẩn ngôn ngữ. Thật đáng kinh ngạc khi những tính năng như vậy đã tồn tại từ lâu đến thế
Hacker News trước đây cũng từng bàn về chuyện này: https://news.ycombinator.com/item?id=38938402
Có bản PDF nào ở đâu đó không?
yield[0]. Ngôn ngữ Icon cùng thời kỳ cũng có tính năng generator tương tự [1], dùngsuspendthay choyield. Theo tôi biết Ada (1983) cũng có tính năng như vậyNhững tính năng ngôn ngữ này không hẳn là hoàn toàn vô danh
[0] https://publications.csail.mit.edu/lcs/pubs/pdf/MIT-LCS-TR-2...
[1] https://dl.acm.org/doi/pdf/10.1145/800055.802034
Nó mô tả cả dấu gạch dưới trong số, phạm vi
case, tham số đặt tên, hàm lồng nhau và cả biến hàm hoàn chỉnhhttps://bitsavers.org/pdf/metaware/…
Xem Appendix A, cách cuối tệp khoảng hơn 50 trang
Hồi trước, khi còn học và viết code, tôi biết đến họ qua vài trang web hơi đáng ngờ
Bạn cũng sẽ phát hiện lịch sử phong phú của các ngôn ngữ lập trình hệ thống. Và sẽ thấy thiết kế của C và Go giống nhau đến mức nào ở chỗ đã bỏ qua những gì đang diễn ra trong các hệ sinh thái khác cũng như các kinh nghiệm trong quá khứ
Liên kết tới manual của compiler nằm ở https://winworldpc.com/product/metaware-high-c-cpp/33x
PDF manual C có ghi bản quyền năm 2007
https://f.duriansoftware.com/@joe/113195961485703110
¥nchứ không phải\n, có vẻ các ví dụ code này được viết bằng Shift-JIS. Trong Shift-JIS, vị trí của\trong ASCII được thay bằng¥[0] https://en.wikipedia.org/wiki/JIS_X_0201
Với mục đích này thì EUC-JP tốt hơn, vì không có vấn đề đó. Trong Pascal, nếu dùng chú thích
(* *)và không dùng chú thích{ }thì dùng Shift-JIS cũng không gặp vấn đề nàyThay vào đó, nhiều khả năng JIS X 0201(https://en.m.wikipedia.org.org/wiki/JIS_X_0201), nền tảng của Shift-JIS, đã được sử dụng
C:¥chứ không phảiC:\Call (Param_A => 1, Param_B => "Foo");, dấu gạch dưới trong số ở cơ số tùy ý (X : Integer := 1_000;), subprogram lồng nhau, và kiểm tra dựa trên phạm viCó vẻ người ta thường quên rằng C thời đó thô sơ đến khó tin khi so với nhiều ngôn ngữ khác
Tôi không hiểu đủ về quy ước viết tiếng Nhật hay quy tắc kerning, nhưng trông như họ lấy một phông chữ độ rộng biến thiên có cả kanji và chữ Latin rồi cố nhét vào các ô cố định độ rộng
Dù sao thì cũng tốt khi các ví dụ code không dùng cỡ chữ 8pt như nhiều cuốn sách tôi có
try_fold()(https://scribe.rip/@veedrac/rust-is-slow-and-i-am-the-cure-3...)Nhưng có vẻ chính vì lý do đó mà các phần mở rộng này tương đối ít được biết đến, và nhiều thập kỷ sau mới phải được phát hiện lại và tái phát minh trong C/C++ hiện đại