Tìm hiểu Phân tích cú pháp bằng phương pháp đoán nhận trước
Trang 11 1.Giới thiệu 2
2 Phân tích cú pháp đệ quy xuống 3
3 Phân tích cú pháp dự đoán (Predictive Parser) 4
4 Sơ đồ chuyển vị cho thể phân cú pháp dự đoán 7
5 Xây dựng bảng phân tích cú pháp dự đoán 8
5.1.Tính FIRST 8
Để tính FIRST(X) cho mọi ký hiệu văn phạm X, chúng ta áp dụng các quy tắc bên dưới cho đến khi không còn thêm được một tận hoặc ε nào vào mọi tập FIRST 8
1 Nếu X là một tận thì FIRST(X) là {X} 8
2 Nếu X→ε là một luật sinh thì thêm ε vào FIRST(X) 8
3 Nếu X là một chưa tận và X→Y1Y2 Yk là một luật sinh thì đặt a vào FIRST(X) nếu với I nào đó, a thuộc FIRST(Y1) và ε thuộc tất cả FIRST(Y1),…, FIRST(Yi-1); Nghĩa là Y1 Yi-1=>* ε Nếu ε thuộc FIRST(Y1) với mọi j=1, 2,…,k thì thêm ε vào FIRST(X) Chẳng hạn, mọi tận trong FIRST(Y1) đều thuộc FIRST(X) Nếu Y1 không dẫn xuất ε thì chúng ta không thêm gì voà FIRST(X) nhưng nếu Y1=>*ε thì chúng ta đưa FIRST(Y2) vào… 8
5.2.Tính FOLLOW 8
Để tính FOLLOW(A) cho các chưa tận A, chúng ta áp dụng các quy tắc sau đây cho đến khi không thêm được gì nữa vào mọi tập FOLLOW 9
1 Đặt $ vào tập FOLLOW(S), trong đó S là ký hiệu khởi đầu và $ là dấu kết cuối nguyên liệu 9
2 Nếu có một luật sinh A→αBβ thì mọi phần tử của tập FIRST(β) trừ ε đều được đặt vào FOLLOW(B) 9
3 Nếu có một luật sinh A→α hoặc một luật sinh A→αBβ trong đó FIRST(β) chứa ε (nghĩa là β=>*ε) thì mọi phần tử trong FOLLOW(A) đều thuộc FOLLOW(B) 9
5.3.Thuật Toán 9
6 Phân tích cú pháp dự đoán không đệ quy 10
7 Xây dựng chương trình mô phỏng 11
Trang 21 1.Giới thiệu
Về cơ bản có hai chiến lược phân tích cú pháp đó là phân tích cú pháp từ trên xuống và phân tích cú pháp từ dưới lên Phân tích cú pháp từ trên xuống xây dựng dần cây phân tích cú pháp khởi đi từ gốc và xây dựng dần cây hướng xuống trong khi đọc nguyên liệu (dòng thẻ từ) từ trái sang Tổ hợp hoa lợi của cây phân tích cú pháp thu được phải là dòng nguyên liệu cần phân tích Ngược lại, phân tích cú pháp từ dưới lên xây dựng cây phân tích cú pháp khởi đi từ dòng nguyên liệu làm hoa lợi Trong quá trình đọc dòng nguyên liệu từ trái sang, quá trình này suy dần ra các nút nội của cây phân tích cú pháp và cuối cùng xây dựng được gốc cây, là ký hiệu khởi đầu của văn phạm
Phân tích cú pháp từ trên xuống có thể xem như một nổ lực tìm kiếm một dẫn xuất tận trái cho chuỗi nguyên liệu Nó cũng có thể được xem như một nổ lực xây dựng cây phân tích cú pháp cho nguyên liệu, khởi đi từ gốc và tạo ra các nút của cây theo lối phiên trước
Phân tích cú pháp từ dưới lên có phần khó nhận thấy hơn Nói đơn giản, đây có thể xem là một nổ lực xây dựng cây phân tích cú pháp bằng cách tái hiện lại một dẫn xuất tận phải lùi ngược bằng cách xem xét nguyên liệu lần lượt từ trái sang Ở mỗi bước dẫn xuất, chúng ta luôn có một dạng câu dẫn xuất phải và cần tìm một chuỗi con trong dạng câu đó khớp được với vế phải của một luật sinh Khi tìm được một chuỗi như thế, nó có thể đi ngược thêm một bước dần tới ký hiệu khởi đầu của văn phạm Hai câu hỏi luôn đặt ra tại mỗi bước dẫn xuất là:
Khi dạng câu ở vế phải của một bước dẫn xuất có nhiều chưa tận có thể khai triển, chúng ta sẽ chọn khai triển theo chưa tận nào?
Khi một chưa tận khai triển có nhiều khả triển, làm thế nào chúng ta chọn được khả triển phù hợp, nghĩa là khả triển được cho bảo đảm sẽ dẫn đến phân tích cú pháp thành công cho chuỗi cần phân tích nếu chuỗi đó thật
Trang 32 Phân tích cú pháp đệ quy xuống
Trở lại với câu hỏi thứ nhất ở trên, tại mỗi bước dẫn xuất, dạng câu thu được có thể có nhiều chưa tận, khi đó chúng ta sẽ chọn khai triển theo chưa tận nào? Chúng ta
sẽ không chọn chưa tận để thay một cách ngẫu nhiên mà thực hiện một cách có hệ thống Khai triển theo chưa tận tận phải (tìm một dẫn xuất tận phải) hoặc triển khai theo chưa tận tận trái (tìm một dẫn xuất tận trái)
Vì vậy phân tích cú pháp từ trên xuống có thể được xem như một nổ lực tìm kiếm một dẫn xuất tận trái cho chuỗi nguyên liệu Nó cũng có thể được xem như một
nỗ lực xây dựng cây phân tích cú pháp cho nguyên liệu, khởi đi từ gốc và tạo ra các nút của cây theo lỗi phiên đầu (preorder), nghĩa là xây dựng nút cha trước rồi lần lượt xây dựng các nút con theo thứ tự từ trái sang
Bây giờ ta xét một dạng tổng quát của kỹ thuật phân tích từ trên xuống có tên là phân tích cú pháp đệ quy xuống (recursive descent parsing) Trong phương pháp phân tích cú pháp đệ quy xuống, chúng ta xây dựng cây phân tích cú pháp bằng cách mô phỏng hành động khai triển mỗi ký hiệu chưa tận bằng một hàm, khi khai triển một chưa tận A theo A → α, chúng ta xây dựng một hàm funcA() thực hiện lần lượt các hành động mô phỏng các ký hiệu có trong α theo đúng thứ tự đó
1 Nếu ký hiệu hiện đang cần phân tích trong α là một tận a, hành động tương ứng là đọc ký hiệu tiếp theo từ nguyên liệu và so xem ký hiệu đó có phải là α hay không Nếu ký hiệu đọc được cũng là α, hành động tiếp theo
là hành động tương ứng vói ký hiệu nằm bên phải a trong α Ngược lại, phân tích cú pháp thất bại
2 Nếu ký hiệu trong α là một chưa tận B (B có thể là A), gọi hàm funcB() tương ứng
Ở trường hợp (2), một hàm có thể gọi đệ quy trực tiếp hoặc gián tiếp đến chính
nó Ngoài vấn đề phải thực hiện trở lui trong một số tình huống, một số văn phạm có thể có một hoặc nhiều luật sinh có đệ quy trái và chúng có thể khiến cho một thể phân
Trang 4cú pháp đệ quy xuống phải trở lui hoặc chạy vào một vòng luẩn quẩn vô tận Nghĩa là khi cố gắng khai triển A, cuối cùng chúng ta có thể lại phải tiếp tục khai triển A mà không tiêu dùng một ký hiệu nào trong nguyên liệu Chúng ta có thể trái được bằng phương pháp khư đệ quy trái và thừa số hoá trái văn phạm
3 Phân tích cú pháp dự đoán (Predictive Parser)
Phân tích cú pháp thực chất là phân tích cú pháp đệ quy xuống, nhưng đã tối ưu văn phạm nhằm giải quyết tình huống có thể khiến cho thể phân cú pháp đệ quy xuống phải trở lui hoặc chạy vào một vòng luẩn quẩn do văn phạm có luật sinh đệ quy trái Trước tiên ta tìm cách khử đệ qui trái, sau đó thực hiện thừa số hoá trái cho văn phạm, Cho phép xây dựng được một thể phân cú pháp dự đoán
3.1.Khử đệ qui trái
Một văn phạm là đề qui trái (left recursive) nếu nó có một chưa tận A sao cho có một dẫn xuất A => Aα với α là một chuỗi nào đó Các phương pháp phân tích cú pháp
từ trên xuống không xử lý được các văn phạm đệ quy trái Vì thế cần phải dùng một phép biến đổi để loại các đệ quy trái Đối với một cặp luật sinh đệ quy trái A => Aα | β chúng ta có thể thay nó bằng các luật sinh không đệ qui trái
A → βA’
A’→ αA’| ε
Mà không làm thay đổi tập chuỗi khả dẫn từ A Quy tắc đơn giản này là đủ dùng cho nhiều văn phạm
Bất kể số lượng A luật sinh là bao nhiêu, chúng ta đều có thể khử các đệ quy trái trực tiếp ra khỏi chúng bằng kỹ thuật dưới đây Trước tiên nhóm các A luật sinh lại
A → Aα1| Aα2 | …| Aαm | β1 | β2 |…| βn
Trong đó không βi nào bắt đầu bằng một A Sau đó thay các A luật sinh bằng
A → β1A’ | β2A’ |…| βnA’
A’ → α1A’| α2A’ |…| αnA’ | ε
Trang 5Chưa tận A sinh ra các chuỗi giống như trước kia nhưng không còn đệ quy trái Thủ tục này khử bỏ tất cả mọi đệ quy trái trực tiếp ra khỏi các luật sinh A và A’ (với điều kiện không có αi nào là ε), nhưng nó lại không loại bỏ được đệ quy trái nằm trong các dẫn xuất có hai hoặc nhiều bước Thí dụ xét văn phạm:
S → Aα | b
A → Ac | Sd | ε
Chưa tận S là đệ quy trái bời vì S => Aa => Sda nhưng không phải đệ quy trực tiếp
Thuật toán được trinh bày sau đây sẽ khử bỏ một cách có hệ thống các đệ quy trái ra khỏi một văn phạm Nó bảo đảm hoạt động được nếu văn phạm không có chu trình (là dẫn xuất có dạng A =>* A) hoặc các ε luật sinh ( là luật sinh có dạng A → ε) Các chu trình có thể được loại bỏ một cách có hệ thống ra khỏi một văn phạm cũng như các ε luật sinh
Thuật toán khử đệ quy trái Một văn phạm
Nguyên liệu: Văn phạm G không vòng và không có các ε luật sinh
Thành phẩm: Một văn phạm tương đương không có đệ quy trái.
Phương pháp: Áp dụng thuật toán sau cho văn phạm G (Chú ý
rằng văn phạm không có đệ quy trái thu được có thể có các ε luật sinh)
1.Sắp các chưa tận theo một thứ tự nào đó như A1, A2, …, An
2.For i:=1 to n do begin
for j:=1 to i-1 do begin
Thay mỗi luật sinh dạng Ai → Ajγ bằng các luật sinh
Aj → δ1 | δ2 |…| δk là tất cả các Aj luật sinh hiện hành
End
khử bỏ đệ quy trái trực tiếp trong số các Ai luật sinh
Trang 6Lý do thuật toán trên hoạt động được là vì sau lần lặp thứ i-1 của vòng for ngoài (bước 2), mọi luật sinh dạng Ak→Alα vơi k<i phải có l>k kết quả là ở lần lặp tiếp theo, vòng lặp trong (trên j) tăng dần giới hạn dưới cho m trong mọi luật sinh
Ai→Amα cho đến khi chúng ta có m>=i Như thế việc loại bỏ đệ quy trái cho Ai luật sinh buộc m phải lớn hơn i
3.2.Thừa số hoá trái
Thừa số hoá trái (left factoring) là một phép biến đổi văn phạm rất có ích nhằm sinh ra một văn phạm thích hợp cho việc phân tích cú pháp dự đoán Ý tưởng cơ bản là khi không rõ luật sinh nào trong hai luật sinh khả triển có thể dùng để khai triển một chưa tận A, chúng ta có thể viết lại các A luật sinh nhằm “hoãn quyết định” lại cho đến khi “thấy” đủ nguyên liệu để có thể đưa ra một chọn lựa đúng Thí dụ, nếu chúng ta có hai luật sinh:
Stmt → if expr then stmt else stmt | if expr then stmt Khi thấy thẻ từ if Chúng ta không thể nói ngay được rằng cần chọn luật sinh nào
để khai triển stmt Tổng quát, nếu A → αβ1 | αβ2 là hai luật sinh và nguyên liệu bắt đầu bằng một chuỗi không rỗng dẫn xuất từ α Chúng ta không biết phải khai triển A thành αβ1 hay αβ2 Tuy nhiên chúng ta có thể hoãn quyết định này bằng cách khai triển A thành αA’ Thế rồi sau khi thấy nguyên liệu dẫn xuất từ a, chúng ta khai triển A’ thành β1 hoặc thành β2 Nghĩa là khi được thừa số hoá trái, các luật sinh ban đầu trở thành
A → αA’
A’ → β1 | β2
Thuật toán thừa số hoá trái một văn phạm:
Nguyên liệu: Văn phạm G
Thành phẩm: Một văn phạm tương đương đã thừa số hoá trái.
Trang 7 Phương pháp:
Với mỗi chưa tận A, chúng ta tìm tiền tố chung α dài nhất của hai hoặc nhiều khả triển của nó Nếu α ≠ ε, nghĩa là có một tiền tố chung không tầm thường thì thay tất cả các A luật sinh A → αβ1 | αβ2 |…| αβn | γ, với γ biểu diễn tất cả các khả triển không bắt đầu bằng α, bằng các luật sinh
A → αA’ | γ
A’ → β1 | β2 |…| βn
Ở đây A’ là một chưa tận mới Áp dụng lặp lại phép biến đổi này cho đến khi không còn hai khả triển nào cho một chưa tận có một tiền tố chung
Như vậy, trong nhiều trường hợp, bằng cách viết văn phạm một cách cẩn thận, loại bỏ đệ quy trái ra khỏi văn phạm rồi thừa số hoá trái văn phạm, chúng ta có thể thu được một văn phạm mà một thể phân cú pháp đệ quy xuống phân tích được nhưng không cần phải trở lui, nghĩa là một thể phân cú pháp dự đoán (Predictive parser) Để xây dụng một thể phân cú pháp dự đoán, khi cho trước kí hiệu nguyên liệu hiện hành a
và một chưa tận A cần được khai triển, chúng ta phải biết khả triển nào trong số các khả triển của luật sinh A → α1 | α2 | … | αn là khả triển duy nhất dẫn xuất ra một chuỗi bắt đầu bằng a Nghĩa là khả triển thích hợp phải có khả năng được phát hiện ra khi chỉ cần xem ký hiệu đầu tiên mà nó dẫn xuất Các kết cấu dòng điều khiển (flow of
control) trong phần lớn các ngôn ngữ lập trình với các từ khoá phân biệt thường được nhận ra theo cách này
4 Sơ đồ chuyển vị cho thể phân cú pháp dự đoán
Để xây dựng sơ đồ chuyển vị của một thể phân cú pháp dự đoán từ một văn phạm, trước tiên cần loại bỏ đệ quy trái ra khỏi văn phạm rồi thừa số hoá trái văn phạm Sau đó thực hiện các bước sau cho mỗi chưa tận A:
Tạo một khởi trạng và một kết trạng
Trang 8 Với mỗi luật sinh A → X1 X 2 X n , tạo một đường đi từ khởi trạng đến kết trạng bằng các cạnh có nhãn là X1, X2…, Xn
Thể phân cú dự đoán dựa trên sơ đồ chuyển vị hành động như sau: Nó bắt đầu tại khởi trạng cho ký hiệu khởi đầu Nếu sau một số hành động nó đang trong trạng thái
s với số cạnh co nhãn là một tận a hướng đến trạng thái t, và nếu ký hiệu kế tiếp là a thì thể phân cú pháp dịch con trỏ nguyên liệu sang phải một vị trí và chuyển đến trạng thái
t Ngược lại nếu cạnh này có nhãn là một chưa tận A Thì thể phân cú pháp chuyển đến khởi trạng cho A mà không di chuyển con trỏ Nếu đến được kết trạng cho A, lập tức
nó chuyển ngay đến trạng thái t, làm như là đã “đọc” A từ nguyên liệu trong thời gian
nó di chuyển từ trạng thái s đến t Cuối cùng nếu có một cạnh từ s đến t có nhãn ε thì từ trạng thái s, thể phân cú pháp sẽ chuyển ngay đến trạng thái t mà không dịch con trỏ nguyên liệu sang phải
5 Xây dựng bảng phân tích cú pháp dự đoán
5.1.Tính FIRST
Để tính FIRST(X) cho mọi ký hiệu văn phạm X, chúng ta áp dụng các quy tắc bên dưới cho đến khi không còn thêm được một tận hoặc ε nào vào mọi tập FIRST
1 Nếu X là một tận thì FIRST(X) là {X}
2 Nếu X→ε là một luật sinh thì thêm ε vào FIRST(X)
3 Nếu X là một chưa tận và X→Y1Y2 Yk là một luật sinh thì đặt a vào FIRST(X) nếu với I nào đó, a thuộc FIRST(Y1) và ε thuộc tất cả FIRST(Y1),…, FIRST(Yi-1); Nghĩa là Y1 Yi-1=>* ε Nếu ε thuộc FIRST(Y1) với mọi j=1, 2,…,k thì thêm ε vào FIRST(X) Chẳng hạn, mọi tận trong FIRST(Y1) đều thuộc FIRST(X) Nếu Y1 không dẫn xuất ε thì chúng ta không thêm gì voà FIRST(X) nhưng nếu Y1=>*ε thì chúng
ta đưa FIRST(Y2) vào…
5.2.Tính FOLLOW
Trang 9Để tính FOLLOW(A) cho các chưa tận A, chúng ta áp dụng các quy tắc sau đây cho đến khi không thêm được gì nữa vào mọi tập FOLLOW
1 Đặt $ vào tập FOLLOW(S), trong đó S là ký hiệu khởi đầu và $ là dấu kết cuối nguyên liệu
2 Nếu có một luật sinh A→αBβ thì mọi phần tử của tập FIRST(β) trừ ε đều được đặt vào FOLLOW(B)
3 Nếu có một luật sinh A→α hoặc một luật sinh A→αBβ trong đó
FIRST(β) chứa ε (nghĩa là β=>*ε) thì mọi phần tử trong FOLLOW(A) đều thuộc FOLLOW(B)
5.3.Thuật Toán.
Thuật toán dưới đây có thể dùng để xây dựng bảng phân tích cú pháp dự đoán
(predictive parser table) cho một văn phạm G Ý tưởng của nó như sau: Giả sử A→α là một luật sinh với a thuộc FIRST(α) Thế thì thể phân cú pháp se khai triển A theo α khi
ký hiệu nguyên liệu hiện là a Rắc rối nảy sinh khi α=ε hoặc α=>* ε Trong trường hợp này, chúng ta lại phải khai triển A theo α nếu ký hiệu nguyên liệu hiện tại thuộc
FOLLOW(A) hoặc nếu đã đọc đến $ trong nguyên liệu và $ thuộc FOLLOW(A)
Thuật toán xây dựng bảng phân tích cú pháp dự đoán.
Nguyên liệu: Văn phạm G
Thành phẩm: Bảng phân tích cú pháp M
Phương pháp:
1 Với mỗi luật sinh A→α của văn phạm, thực hiện các bước 2 và 3
2 Với mỗi tận a trong FIRST(α), đưa A→α vào mục M[A,a]
3 Nếu ε thuộc FIRST(α), đưa A→α vào M[A,b] với mỗi tận b thuộc tập FOLLOW(A), Nếu ε thuộc FIRST(α) và $ thuộc FOLLOW(A), đưa A→α vào M[A,$] Đánh dấu các mục chưa được định nghĩa của M là error
Trang 106 Phân tích cú pháp dự đoán không đệ quy
Chúng ta có thể xây dựng một thể phân cú pháp dự đoán không đệ quy bằng cách duy trì tường minh một chồng xếp chứ không phải ngầm định qua các lời gọi đệ quy Vấn đề chính trong quá trình phân tích dự đoán là việc xác định luật sinh sẽ được
áp dụng cho một chưa tận Thể phân cú pháp dự đoán trong hình dưới kiếm tìm luật sinh được áp dụng trong một bảng phân tích cú pháp (parsing table)
Mô hình của thể phân cú pháp dự đoán không đệ quy.
Một thể phân cú pháp dự đoán dựa theo bảng có một vùng đệm nguyên liệu, một chồng xếp, một bảng phân tích cú pháp, và một dòng thành phẩm Vùng đệm nguyên liệu chứa chuỗi cần phân tích, theo sau là ký hiệu $ được dùng để đánh dấu đầu phải, báo hiệu kết thúc chuỗi nguyên liệu Chồng xếp chứa một loạt các ký hiệu văn phạm với dấu $ chỉ ra đáy chồng xếp Lúc ban đầu chồng xếp chứa ký hiệu khởi đầu của văn phạm ở ngay bên trên $ Bảng phân tích cú pháp là một mảng hai chiều
M[A,a], trong đó A là một chưa tận và a là một tận hoặc ký hiệu $