Các trình phân tích cú pháp thông thường vì chúng thường được dạy có một giai đoạn lexer trước khi trình phân tích cú pháp chạm vào đầu vào. Các lexer (cũng có thể là máy quét của máy quét hay, máy quét tokenizer,), cắt đầu vào thành các mã thông báo nhỏ được chú thích bằng một loại. Điều này cho phép trình phân tích cú pháp chính sử dụng mã thông báo làm thành phần đầu cuối thay vì phải coi mỗi ký tự là đầu cuối, điều này dẫn đến tăng hiệu quả đáng chú ý. Đặc biệt, lexer cũng có thể xóa tất cả các bình luận và khoảng trắng. Tuy nhiên, một giai đoạn mã thông báo riêng biệt có nghĩa là các từ khóa cũng không thể được sử dụng làm định danh (trừ khi ngôn ngữ hỗ trợ việc cắt xén có phần không được ưa chuộng hoặc tiền tố tất cả các định danh có dạng sigil như $foo
).
Tại sao? Giả sử chúng ta có một mã thông báo đơn giản để hiểu các mã thông báo sau:
FOR = 'for'
LPAREN = '('
RPAREN = ')'
IN = 'in'
IDENT = /\w+/
COLON = ':'
SEMICOLON = ';'
Mã thông báo sẽ luôn khớp với mã thông báo dài nhất và thích từ khóa hơn số nhận dạng. Vì vậy, interesting
sẽ được lexed như IDENT:interesting
, nhưng in
sẽ được lex như IN
, không bao giờ như IDENT:interesting
. Một đoạn mã như
for(var in expression)
sẽ được dịch sang luồng mã thông báo
FOR LPAREN IDENT:var IN IDENT:expression RPAREN
Cho đến nay, điều đó làm việc. Nhưng bất kỳ biến in
nào sẽ được coi là từ khóa IN
chứ không phải là một biến, sẽ phá vỡ mã. Các lexer không giữ bất kỳ trạng thái nào giữa các mã thông báo và không thể biết đó in
thường là một biến trừ khi chúng ta ở trong một vòng lặp for. Ngoài ra, mã sau đây phải hợp pháp:
for(in in expression)
Đầu tiên in
sẽ là một định danh, thứ hai sẽ là một từ khóa.
Có hai phản ứng cho vấn đề này:
Từ khóa theo ngữ cảnh gây nhầm lẫn, thay vào đó hãy sử dụng lại từ khóa.
Java có nhiều từ dành riêng, một số từ không có tác dụng ngoại trừ việc cung cấp các thông báo lỗi hữu ích hơn cho các lập trình viên chuyển sang Java từ C ++. Thêm từ khóa mới phá vỡ mã. Việc thêm các từ khóa theo ngữ cảnh gây nhầm lẫn cho người đọc mã trừ khi chúng có tính năng tô sáng cú pháp tốt và khiến công cụ khó thực hiện vì chúng sẽ phải sử dụng các kỹ thuật phân tích cú pháp nâng cao hơn (xem bên dưới).
Khi chúng tôi muốn mở rộng ngôn ngữ, cách tiếp cận lành mạnh duy nhất là sử dụng các ký hiệu mà trước đây không hợp pháp trong ngôn ngữ. Đặc biệt, đây không thể là định danh. Với cú pháp vòng lặp foreach, Java đã sử dụng lại :
từ khóa hiện tại với nghĩa mới. Với lambdas, Java đã thêm một ->
từ khóa mà trước đây không thể xảy ra trong bất kỳ chương trình pháp lý nào ( -->
vẫn sẽ được coi '--' '>'
là hợp pháp và ->
trước đây có thể đã '-', '>'
bị từ chối, nhưng trình tự đó sẽ bị trình phân tích cú pháp từ chối).
Từ khóa theo ngữ cảnh đơn giản hóa các ngôn ngữ, hãy thực hiện chúng
Lexers là hữu ích không thể chối cãi. Nhưng thay vì chạy một lexer trước trình phân tích cú pháp, chúng ta có thể chạy chúng song song với trình phân tích cú pháp. Các trình phân tích cú pháp từ dưới lên luôn biết tập hợp các loại mã thông báo sẽ được chấp nhận tại bất kỳ vị trí nào. Sau đó, trình phân tích cú pháp có thể yêu cầu lexer khớp với bất kỳ loại nào ở vị trí hiện tại. Trong một vòng lặp for, mỗi trình phân tích cú pháp sẽ ở vị trí được biểu thị bằng ·
ngữ pháp (đơn giản hóa) sau khi tìm thấy biến:
for_loop = for_loop_cstyle | for_each_loop
for_loop_cstyle = 'for' '(' declaration · ';' expression ';' expression ')'
for_each_loop = 'for' '(' declaration · 'in' expression ')'
Tại vị trí đó, các mã thông báo hợp pháp là SEMICOLON
hoặc IN
, nhưng không IDENT
. Một từ khóa in
sẽ hoàn toàn rõ ràng.
Trong ví dụ cụ thể này, các trình phân tích cú pháp từ trên xuống sẽ không gặp vấn đề gì vì chúng ta có thể viết lại ngữ pháp trên thành
for_loop = 'for' '(' declaration · for_loop_rest ')'
for_loop_rest = · ';' expression ';' expression
for_loop_rest = · 'in' expression
và tất cả các mã thông báo cần thiết cho quyết định có thể được nhìn thấy mà không cần quay lại.
Xem xét khả năng sử dụng
Java luôn có xu hướng đơn giản về ngữ nghĩa và cú pháp. Ví dụ, ngôn ngữ không hỗ trợ quá tải toán tử vì nó sẽ làm cho mã phức tạp hơn nhiều. Vì vậy, khi quyết định giữa in
và :
cho một cú pháp vòng lặp cho mỗi vòng lặp, chúng ta phải xem xét cái nào ít gây nhầm lẫn và rõ ràng hơn cho người dùng. Trường hợp cực đoan có lẽ sẽ là
for (in in in in())
for (in in : in())
(Lưu ý: Java có các không gian tên riêng biệt cho tên loại, biến và phương thức. Tôi nghĩ rằng đây là một lỗi, chủ yếu. Điều này không có nghĩa là thiết kế ngôn ngữ sau này phải thêm nhiều lỗi.)
Sự thay thế nào cung cấp sự phân tách trực quan rõ ràng hơn giữa biến lặp và bộ sưu tập lặp? Sự thay thế nào có thể được nhận ra nhanh hơn khi bạn nhìn vào mã? Tôi đã thấy rằng các biểu tượng tách biệt tốt hơn một chuỗi các từ khi nói đến các tiêu chí này. Các ngôn ngữ khác có giá trị khác nhau. Ví dụ: Python đánh vần nhiều toán tử bằng tiếng Anh để chúng có thể được đọc một cách tự nhiên và dễ hiểu, nhưng những thuộc tính tương tự có thể làm cho việc hiểu một mẩu Python trong nháy mắt khá khó khăn.