6

Tôi đã quan tâm đến trình biên dịch/thiết kế thông dịch/thực hiện miễn là tôi đã lập trình (chỉ 5 năm nay) và nó luôn có vẻ như "ma thuật" đằng sau hậu trường mà không ai thực sự nói về (tôi biết ít nhất 2 diễn đàn để phát triển hệ điều hành, nhưng tôi không biết bất kỳ cộng đồng nào để phát triển trình biên dịch/phiên dịch/ngôn ngữ). Dù sao, gần đây tôi đã quyết định bắt đầu làm việc một mình, với hy vọng mở rộng kiến ​​thức của tôi về lập trình nói chung (và hey, nó khá thú vị :). Vì vậy, dựa trên số lượng giới hạn tài liệu đọc mà tôi có và Wikipedia, tôi đã phát triển khái niệm này về các thành phần cho trình biên dịch/phiên dịch:Cây cú pháp trừu tượng là gì?

Mã nguồn -> Phân tích Lexical -> Cú pháp trừu tượng Tree -> Phân tích cú pháp -> Phân tích ngữ nghĩa -> Tạo mã -> Mã thực thi.

(Tôi biết có nhiều mã thế hệ và mã thực thi, nhưng tôi đã không nhận rằng đến nay chưa :)

Và với kiến ​​thức đó, tôi đã tạo ra một lexer rất cơ bản (trong Java) để tận đầu vào từ một tệp nguồn và xuất các mã thông báo vào một tệp khác. Một đầu vào mẫu/đầu ra sẽ trông như thế này:

Input:

int a := 2 
if(a = 3) then 
    print "Yay!" 
endif 

Output (từ lexer):

INTEGER 
A 
ASSIGN 
2 
IF 
L_PAR 
A 
COMP 
3 
R_PAR 
THEN 
PRINT 
YAY! 
ENDIF 

Cá nhân, tôi nghĩ rằng nó sẽ thực sự dễ dàng để đi từ đó đến phân tích cú pháp/ngữ nghĩa, và thậm chí có thể tạo mã, dẫn tôi đến câu hỏi: Tại sao lại sử dụng AST, khi có vẻ như lexer của tôi đang làm tốt như một công việc? Tuy nhiên, 100% nguồn của tôi tôi sử dụng để nghiên cứu chủ đề này tất cả đều có vẻ kiên quyết rằng đây là một phần cần thiết của bất kỳ trình biên dịch/phiên dịch nào. Tôi có thiếu điểm AST thực sự là gì không (một cái cây cho thấy luồng logic của một chương trình)?

TL; DR: Hiện đang trong quá trình phát triển trình biên dịch, đã hoàn thành lexer, có vẻ như tôi sẽ tạo ra phân tích cú pháp/phân tích cú pháp dễ dàng hơn là làm AST. Vì vậy, tại sao sử dụng một? Tôi có thiếu điểm không?

Cảm ơn!

+0

Có nhiều tài nguyên về trình biên dịch và ngôn ngữ. Bắt đầu với http://lambda-the-ultimate.org/ –

Trả lời

13

Trước hết, một điều về danh sách thành phần của bạn không có ý nghĩa. Xây dựng một AST (khá nhiều) phân tích cú pháp, do đó, nó không nên ở trong đó, hoặc ít nhất là đến trước AST.

Những gì bạn có ở đó là một từ khóa. Tất cả nó mang lại cho bạn là thẻ cá nhân. Trong bất kỳ trường hợp nào, bạn sẽ cần một trình phân tích cú pháp thực tế, bởi vì các ngôn ngữ thông thường không có bất kỳ chương trình nào thú vị. Bạn thậm chí không thể (đúng) các biểu thức lồng nhau. Heck, bạn thậm chí không thể xử lý quyền ưu tiên của toán tử. Luồng mã thông báo không cung cấp cho bạn:

  1. Ý tưởng nơi các câu lệnh và biểu thức bắt đầu và kết thúc.
  2. Ý tưởng cách các câu lệnh được nhóm thành các khối.
  3. Ý tưởng Phần nào của biểu thức có ưu tiên, liên kết, v.v.
  4. Chế độ xem rõ ràng, gọn gàng tại cấu trúc thực tế của chương trình.
  5. Cấu trúc có thể được chuyển qua vô số phép biến đổi, mà không cần phải vượt qua từng bước để biết và có mã để chứa rằng điều kiện trong một if được kèm theo dấu ngoặc đơn.
  6. ... thông thường hơn, bất kỳ loại hiểu nào vượt quá mức của một mã thông báo duy nhất.

Giả sử bạn có hai lần chuyển trong trình biên dịch, tối ưu hóa một số loại toán tử nhất định áp dụng cho các đối số nhất định (ví dụ: gấp đơn giản và đơn giản đại số như x - x -> 0). Nếu bạn đưa các mã thông báo cho biểu thức x - x * 1, các thẻ này bị lộn xộn với việc tìm ra phần x * 1 sẽ xuất hiện trước. Và họ để biết điều đó, vì sợ rằng việc chuyển đổi không chính xác (xem xét 1 + 2 * 3).

Những điều này khó đủ để có được quyền như vậy, vì vậy bạn không muốn bị quấy rầy bằng cách phân tích cú pháp các vấn đề. Đó là lý do tại sao bạn giải quyết vấn đề phân tích cú pháp trước tiên, trong một bước phân tích cú pháp riêng biệt. Sau đó,, bạn có thể, thay thế một cuộc gọi chức năng với định nghĩa của nó mà không phải lo lắng về việc thêm dấu ngoặc đơn để ý nghĩa vẫn giữ nguyên. Bạn tiết kiệm thời gian, bạn tách mối quan tâm, bạn tránh lặp lại, bạn cho phép mã đơn giản hơn ở nhiều nơi khác, v.v.

Trình phân tích cú pháp vẽ tất cả những thứ đó và xây dựng một AST chứa tất cả thông tin đó. Nếu không có thêm bất kỳ dữ liệu nào trên các nút, hình dạng của AST một mình sẽ không cho bạn. 1, 2, 3, và nhiều hơn nữa, miễn phí. Không có trong số các thẻ vượt ngưỡng mà bạn phải lo lắng về nó nữa.

Điều đó không có nghĩa là bạn luôn phải có AST. Đối với các ngôn ngữ đơn giản, bạn có thể thực hiện một trình biên dịch một lần. Thay vì tạo một AST hoặc một số biểu diễn trung gian khác trong quá trình phân tích cú pháp, bạn sẽ phát ra mã khi bạn đi. Tuy nhiên, điều này trở nên khó khăn hơn đối với các ngôn ngữ ít đơn giản hơn và bạn không thể làm được nhiều thứ một cách hợp lý (chẳng hạn như 70% của tất cả các tối ưu hóa và chẩn đoán - và vâng tôi chỉ tạo số đó). Nói chung, tôi sẽ không khuyên bạn làm điều này. Có một số lý do tốt khiến các trình biên dịch đơn lẻ bị chết. Ngay cả các ngôn ngữ cho phép chúng (ví dụ: C) ngày nay được triển khai với nhiều lần truyền và AST. Đó là một cách đơn giản để bắt đầu, nhưng sẽ giới hạn nghiêm trọng bạn (và ngôn ngữ, nếu bạn thiết kế nó) sau này.

8

Bạn đã có AST tại điểm sai trong sơ đồ lưu lượng của mình. Thông thường, đầu ra của lexer là một loạt các mã thông báo (như bạn có trong đầu ra của bạn), và chúng được đưa vào bộ phân tích cú pháp/cú pháp, tạo ra AST. Vì vậy, đầu ra của lexer của bạn khác với AST vì chúng được sử dụng tại các điểm khác nhau trong quá trình biên dịch và thực hiện các mục đích khác nhau.

Câu hỏi logic tiếp theo là: Cái gì, sau đó, là một AST? Vâng, mục đích của phân tích cú pháp/cú pháp là biến hàng loạt các thẻ được tạo ra bởi lexer thành một AST (hoặc phân tích cây). AST là một biểu diễn trung gian để nắm bắt mối quan hệ giữa các phần tử cú pháp theo cách dễ dàng hơn khi làm việc với lập trình. Một cách để suy nghĩ về điều này là một chương trình văn bản là một cấu trúc một chiều, và chỉ có thể biểu diễn ý tưởng như một chuỗi các phần tử, trong khi AST được giải phóng khỏi ràng buộc này và có thể biểu diễn các mối quan hệ cơ bản giữa các phần tử đó trong 2 chiều. như thường được vẽ), hoặc bất kỳ không gian chiều cao nào hơn nếu bạn chọn suy nghĩ theo cách đó. Ví dụ, một toán tử nhị phân có hai toán hạng, hãy gọi chúng là A và B. Trong mã, điều này có thể được đánh vần 'A * B' (giả sử một toán tử infix - một ưu điểm khác của AST là ẩn những phân biệt đó có thể quan trọng cú pháp, nhưng không ngữ nghĩa), nhưng đối với trình biên dịch "hiểu" biểu thức này, nó phải đọc 5 ký tự tuần tự, và logic này có thể nhanh chóng trở nên cồng kềnh, với nhiều khả năng thậm chí là một ngôn ngữ nhỏ. Tuy nhiên, trong một biểu diễn AST, chúng ta có một nút "toán tử nhị phân" có giá trị là '*' và nút đó có hai con, các giá trị 'A' và 'B'.

Khi dự án trình biên dịch của bạn tiến triển, tôi nghĩ bạn sẽ bắt đầu thấy những ưu điểm của biểu diễn này.

Các vấn đề liên quan