Một ngôn ngữ lập trình hệ thống không thể mãi là một tập hợp các tính năng rời rạc. Đến một thời điểm, các mảnh ghép như methods, traits, generics và hệ thống attributes phải tìm thấy tiếng nói chung để tạo nên một bề mặt ngữ nghĩa chặt chẽ và an toàn.
Chương này kể về chặng đường DUY đi từ một compiler nguyên mẫu an toàn nhưng khô khốc sang một ngôn ngữ có tính biểu đạt cao. Đây là giai đoạn của Phase T (Sprints 137–150), nơi chúng tôi không còn chấp nhận các giải pháp tình thế của thuở genesis nữa. Mọi tính năng mới đều phải đi qua các gate kiểm thử nghiêm ngặt của native compiler, ép các chi phí ẩn của sự trừu tượng hóa phải lộ diện ngay trong mã nguồn.
DX (Developer Experience) tồi là rào cản lớn nhất của một ngôn ngữ lập trình nghiêm cẩn. Trong DUY V0, để tính độ dài của một vector, lập trình viên buộc phải viết: vector_len(view[my_vec]). Cú pháp functional phẳng này rất an toàn và tường minh, nhưng khi các hàm bắt đầu lồng ghép vào nhau, mã nguồn nhanh chóng trở thành một mê cung rối rắm của những dấu ngoặc đóng mở lặp đi lặp lại. Chúng tôi cần cú pháp gọi phương thức quen thuộc: my_vec.len().
Sprint 138 đặt ra mục tiêu hiện thực hóa inherent methods và cú pháp impl. Thách thức kỹ thuật lớn nhất ở đây là làm sao hỗ trợ cú pháp method call mà không đưa vào các cơ chế dynamic dispatch (vtable) ngầm. DUY là một ngôn ngữ hệ thống; triết lý cốt lõi là zero-cost abstraction và visible cost — nếu có một lời gọi hàm ảo tốn chi phí lookup bảng vtable tại runtime, chi phí đó phải hiển lộ tường minh trong code chứ không được compiler che giấu.
Giải pháp của chúng tôi là áp dụng triệt để cơ chế hạ cấp tĩnh (static lowering) qua Universal Function Call Syntax (UFCS). Cú pháp:
struct Vector {
data: view[i32],
len: i32
}
impl Vector {
fn len(self) -> i32 {
return self.len;
}
}
Khi Parser gặp my_vec.len(), compiler sẽ lập tức kiểm tra kiểu dữ liệu của my_vec. Nếu kiểu dữ liệu là Vector, compiler sẽ rewrite AST của lời gọi này thành Vector::len(my_vec) trong giai đoạn phân tích sơ bộ. Các receiver rules quy định rõ: nếu phương thức nhận self bằng giá trị, quyền sở hữu sẽ bị di chuyển (move); nếu nhận view[self], đó là một phép mượn an toàn read-only không tốn chi phí copy bộ nhớ.
Trận đánh lớn nhất của sprint này là sửa lỗi parser khi xử lý các dấu chấm liên hoàn (method chaining) phức tạp kết hợp với toán tử ? (ví dụ: my_struct.get_buffer()?.read_byte()?). Chỉ một lỗi phân tích cú pháp nhỏ trong việc ưu tiên toán tử trong Pratt parser cũng có thể khiến compiler sinh ra một malformed AST bị lệch luồng control flow.
Tôi nhớ đêm đó, khi tôi gõ thử đoạn code method chaining dài loằng ngoằng, compiler đã crash ngầm và sinh ra mã C emitter bị lỗi lặp nhãn nhảy. Tôi phải ngồi vẽ lại đồ thị phân tích cú pháp trên bảng trắng, sửa lại từng dòng code của Pratt parser native để tính toán chính xác chỉ số precedence của toán tử . so với ?. Khi chạy lại test và thấy màn hình báo xanh, tôi thở phào nhẹ nhõm. Cú pháp phương thức của DUY đã vừa bóng bẩy ở bề mặt vừa vững chãi ở bên dưới.
Bình luận (0)
Đang tải bình luận...