MỤC LỤC
CHƯƠNG 1: MỞ ĐẦU 4
1.1 GIỚI THIỆU ĐỀ TÀI 4
1.2 QUAN ĐIỂM THIẾT KẾ 4
1.3 KỸ THUẬT BIÊN DỊCH 5
1.4 CẤU TRÚC ĐỒ ÁN 5
CHƯƠNG 2: THIẾT KẾ NGÔN NGỮ ĐƠN GIẢN 6
2.1 CÁC KHÁI NIỆM CƠ BẢN CỦA NGÔN NGỮ ĐƠN GIẢN 6
2.1.1 Tập các ký tự hợp lệ trong ngôn ngữ 6
2.1.2 Định danh 6
2.1.3 Từ khóa 6
2.1.4 Dấu chấm phẩy, lời giải thích, hằng ký tự 7
2.1.5 Phép toán 7
2.1.6 Biểu thức 7
2.2 SƠ ĐỒ CÚ PHÁP 7
2.3 CÁC CẤU TRÚC LỆNH 10
2.3.1 Cấu trúc tuần tự 10
2.3.2 Cấu trúc rẽ nhánh 10
2.3.3 Cấu trúc lặp 11
2.4 BỘ LỆNH 13
2.4.1 Lệnh gán 13
2.4.2 Các lệnh vào ra dữ liệu 13
2.4.3 Các lệnh điều khiển 14
2.4.4 Các hàm toán học 15
2.4.5 Các hàm vẽ đồ họa 15
CHƯƠNG 3: ÁP DỤNG MỘT SỐ KỸ THUẬT BIÊN DỊCH CƠ BẢN 17
3.1 PHÂN TÍCH TỪ VỰNG 17
3.1.1 Loại bỏ khoảng trắng và các dòng chú thích 17
3.1.2 Các hằng 18
3.1.3 Nhận diện định danh và từ dành riêng 18
3.1.4 Giao diện cho bộ phân tích từ vựng 18
3.2 ĐỊNH NGHĨA CÚ PHÁP 19
3.2.1 Cây phân tích cú pháp 20
3.2.2 Tính đa nghĩa 20
3.2.3 Tính kết hợp của toán tử 20
3.2.4 Tính thứ bậc của toán tử 21
3.3 PHÂN TÍCH CÚ PHÁP 22
3.3.1 Vai trò của bộ phân tích cú pháp 22
3.3.2 Phân tích cú pháp từ trên xuống 23
3.3.4 Phân tích cú pháp dự đoán 25
3.3.5 Thiết kế bộ phân tích cú pháp dự đoán 27
3.3.6 Đệ qui trái 28
3.4 BẢNG KÝ HIỆU 28
3.4.1 Giao diện của bảng ký hiệu 28
3.4.2 Xử lý các dành riêng 28
3.4.3 Một cài dặt cho bảng ký hiệu 29
CHƯƠNG 4:THIẾT KẾ CHƯƠNG TRÌNH DỊCH CHO NGÔN NGỮ ĐƠN GIẢN 31
4.1 MÔ TẢ CHƯƠNG TRÌNH DỊCH 31
4.2 MÔ ĐUN PHÂN TÍCH TỪ VỰNG 33
4.3 MÔ ĐUN PHÂN TÍCH CÚ PHÁP 33
4.3.1 Xây dựng biểu đồ cú pháp 34
4.3.2 Phân tích cú pháp đệ qui xuống 34
4.4 XỬ LÝ NGỮ NGHĨA 372
CHƯƠNG 5: THỬ NGHIỆM CHƯƠNG TRÌNH 37
5.1 CÁCH SỬ DỤNG 37
5.2 BÀI TOÁN VÍ DỤ 37
CHƯƠNG 6: LUẬN KẾT 41
6.1 KẾT QUẢ ĐẠT ĐƯỢC 41
6.2 TÍNH KHẢ THI 41
6.3 HẠN CHẾ 41
6.4 HƯỚNG PHÁT TRIỂN 42
PHỤ LỤC 43
TÀI LIỆU THAM KHẢO 45
49 trang |
Chia sẻ: netpro | Lượt xem: 2042 | Lượt tải: 4
Bạn đang xem trước 20 trang tài liệu Đồ án Thiết kế ngôn ngữ đơn giản, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
ộ phân tích từ vựng là thành phần đọc chương trình nguồn của trình biên dịch, nó thường thực hiện thêm một số tác vụ khác ở mức giao diện người sử dụng như sau:
3.1.1 Loại bỏ khoảng trắng và các dòng chú thích
Chương trình dịch sẽ xét các ký tự trong nguyên liệu, vì thế các ký tự ngoài dự kiến như các ký tự trống sẽ khiến nó thất bại. Ngôn ngữ lập trình thường cho phép các khoảng trắng (ký tự trống, ký tự tab, ký tự xuống dòng) xuất hiện giữa các thẻ từ. Bộ phân tích từ vựng có nhiệm vụ loại bỏ các khoảng trắng này trước khi đưa qua bộ phân tích cú pháp.
Các dòng chú thích cũng được bộ phân tích cú pháp và chương trình dịch bỏ qua, vì thế chúng cũng được xử lý như khoảng trắng.
3.1.2 Các hằng
Mỗi khi có một ký số xuất hiện trong một biểu thức, có lẽ sẽ hợp lý hơn khi cho phép đặt một hằng số nào đó ở vị trí của nó. Bởi vì một hằng số là một dãy các ký số, nó có thể dùng được bằng cách thêm các luật sinh vào văn phạm cho các biểu thức hoặc bằng cách tạo ra một thẻ từ cho các hằng như thế. Công việc gom các ký số thành số, được thực hiện bởi bộ phân tích từ vựng bởi vì các số có thể được xử lý như những đơn vị riêng biệt trong quá trình dịch.
Chẳng hạn num là thẻ từ biểu thị một số thì bộ phân tích từ vựng sẽ chuyển num cho bộ phân tích cú pháp. Giá trị của số sẽ được chuyển theo dưới dạng một thuộc tính của thẻ từ num, bộ phân tích từ vựng sẽ chuyển cả thẻ từ và thuộc tính của nó cho bộ phân tích cú pháp.
Nếu ta viết một thẻ từ và thuộc tính của nó như một bộ dữ liệu được bao giữa hai dấu thì nguyên liệu
25.5 + 90 - 20
được biến đổi thành một dãy các bộ
Thẻ từ +,- không có thuộc tính. Thành phần thứ hai của các bộ không có vai trò gì trong khi phân tích cú pháp nhưng sẽ cần dùng đến khi dịch.
3.1.3 Nhận diện định danh và từ dành riêng
Ngôn ngữ sử dụng định danh làm tên cho các biến. Một văn phạm cho một ngôn thường xử lý định danh như một thẻ từ. Bộ phân tích cú pháp dựa trên một văn phạm, như thế muốn nhận được cùng một thẻ từ chẳng hạn là id, mỗi khi có một định danh xuất hiện trong nguyên liệu.
Ví dụ: a:= a+ b;
sẽ được bộ phân tích từ vựng biến đổi thành dòng thẻ từ id = id + id
Khi gặp một từ tố tạo ra một định danh trong nguyên liệu, thì ta cần có một cơ chế xem từ tố này đã gặp trước đó hay chưa. Ta sẽ dùng một bảng ký hiệu cho công việc này. Từ tố lưu trong bảng ký hiệu và một con trỏ chỉ đến mục ghi trên bảng trở thành một thuộc tính của thẻ từ id.
Ngoài định danh còn có các từ khóa, các tên hàm là các từ dành riêng, các định danh không được trùng tên với các từ này. Bộ phân tích từ vựng xác định định danh hay từ dành riêng bằng cách sau khi gom thành thẻ từ rồi so sánh với các từ dành riêng trong bảng ký hiệu nếu không trùng thì thẻ từ đó là id, ngược lại là từ dành riêng.
3.1.4 Giao diện cho bộ phân tích từ vựng
Khi một bộ phân tích từ vựng được đặt giữa bộ phân tích cú pháp và dòng nguyên
liệu, nó tương tác với cả hai phần này như (hình 3.2). Nó đọc các ký tự từ nguyên liệu, nhóm chúng lại thành các từ tố rồi gửi các thẻ từ được tạo bởi các từ tố cùng với giá trị thuộc tính của chúng đến giai đoạn sau của trình biên dịch. Trong một số tình huống, bộ phân tích từ vựng phải đọc trước một ký tự trước khi có thể quyết định sẽ trả về thẻ từ nào cho bộ phân tích cú pháp.
Ví dụ:
Khi gặp ký tự > thì bộ phân tích từ vựng sẽ đọc tiếp. Nếu ký tự kế tiếp là = thì chuỗi ký tự >= là từ tố tạo ra thẻ từ cho toán tử “lớn hơn hoặc bằng”. Ngược lại thì > là từ tố tạo ra thẻ từ cho toán tử “lớn hơn”, và bộ phân tích từ vựng đã đọc một ký tự nhiều lần. Ký tự “dư” ra này phải được đưa trở lại nguyên liệu, bởi vì nó có thể là ký từ đầu tiên của từ tố kế tiếp trong nguyên liệu.
nguyên liệu
truyền thẻ từ và
thuộc tính
đọc kí tự
đẩy kí tự trở về
bộ phân tích
từ vựng
Hình 3.2 Đặt bộ phân tích từ vựng vào giữa nguyên liệu và bộ phân tích cú pháp
bộ phân tích
cú pháp
Bộ phân tích từ vựng tạo ra các thẻ từ và bộ phân tích cú pháp sẽ sử dụng nó. Các thẻ từ sinh ra được giữ trong vùng đệm cho đến khi chúng được sử dụng. Tương tác giữa hai bộ phân tích này chỉ bị ràng buộc bởi kich thước vùng đệm bởi vì bộ phân tích từ vựng sẽ không thể tiến hành khi vùng đệm đầy còn bộ phân tích cú pháp sẽ không thể tiến hành khi vùng đệm rỗng. Vì vùng đệm chỉ giữ một thẻ từ nên tương tác được cài đặt đơn giản bằng cách đưa bộ phân tích từ vựng thành một hàm được bộ phân tích cú pháp gọi, trả về các thẻ từ theo như yêu cầu.
3.2 ĐỊNH NGHĨA CÚ PHÁP
Trong phần này sẽ giới thiệu một hệ ký pháp có tên gọi là văn phạm phi ngữ cảnh được dùng để xác định cú pháp của một ngôn ngữ.
Một văn phạm thường mô tả cấu trúc phân cấp của nhiều kết cấu của các ngôn ngữ lập trình.
Ví dụ:
Một câu lệnh trong C có dạng:
if (biểu_thức) lệnh else lệnh
Có nghĩa là một phép nối của từ khóa if, một dấu ngoặc mở, một biểu thức, một dấu ngoặc đóng, câu lệnh, từ khóa else và câu lệnh khác.
Câu lệnh có thể biểu diễn theo quy tắc sau:
lệnh ® if (biểu_thức) lệnh else lệnh
Một qui tắc như trên được gọi là luật sinh. Trong luật sinh, một thành phần từ vựng
như từ khóa và các dấu ngoặc được gọi là các thẻ từ (token). Các biến như biểu_thức
và lệnh biểu thị chuỗi các thẻ từ và được gọi là ký hiệu chưa kết thúc.
Một văn phạm phi ngữ cảnh có bốn thành phần :
1. Một tập các thẻ từ, gọi là các ký hiệu kết thúc.
2. Một tập các ký hiệu chưa kết thúc.
4. Một ký hiệu khởi đầu, đó là một ký hiệu chưa kết thúc.
3. Một tập luật sinh, trong đó mỗi luật sinh gồm một ký hiệu chưa kết thúc gọi là vế trái của luật sinh, một mũi tên, và một chuỗi các thẻ từ và/hoặc các ký hiệu chưa kết thúc là vế phải của luật sinh. Ví dụ: A® a1ôa2ô. . .ôan, với a1, a2 ... là các ký hiệu kết thúc hoặc chưa kết thúc.
Ví dụ:
Cho văn phạm G sinh ra các biểu thức gồm các số, ngăn giữa các số là dấu + hoặc -
list ® list + digit
| list - digit (3.1)
| digit
digit ® 0|1|2| ... |9
3.2.1 Cây phân tích cú pháp
Cây phân tích cú pháp cho thấy bằng hình ảnh xem làm như thế nào dẫn xuất ra một chuỗi của ngôn ngữ từ ký hiệu khởi đầu của một văn phạm. Về hình thức cho trước một văn phạm phi ngữ cảnh, một cây phân tích cú pháp là một cây có đặc tính sau:
- Gốc có nhãn là ký hiệu khởi đầu
- Mỗi nút là có nhãn là một thẻ từ hoặc e
- Mỗi nút không phải là nút cuối của cây thì đó là ký hiệu chưa kết thúc.
- Nếu A một ký hiệu chưa kết thúc, là nhãn của nút không phải là nút cuối, X1, X2,...Xn là nhãn các con của nút có nhãn A từ trái sang phải thì A ® X1, X2,...Xn là một luật sinh. X1, X2,...Xn là các ký hiệu kết thúc hoặc chưa kết thúc. Nếu A® e thì một nút được gán nhãn A có thể có một con duy nhất có nhãn e.
3.2.2 Tính đa nghĩa
Mỗi cây phân tích cú pháp đều dẫn xuất chính xác chuỗi được đọc từ các nút lá nhưng một văn phạm có thể có nhiều cây phân tích cú pháp sinh ra chuỗi thẻ từ đã cho. Một văn phạm như thế gọi là đa nghĩa. Một chuỗi với nhiều cây phân tích cú pháp thường có nhiều nghĩa. Để biên dịch các chương trình ứng dụng, chúng ta cần thiết kế các văn phạm đơn nghĩa, hoặc sử dụng các văn phạm đa nghĩa với các qui tắc bổ sung nhằm giải quyết tính đa nghĩa.
3.2.3 Tính kết hợp của toán tử
Trong phần lớn các ngôn ngữ lập trình bốn toán tử số học cộng(+), trừ(-), nhân(*) chia(/) đều có tính kết hợp trái, toán tử lấy lũy thừa, toán tử gán có tính kết hợp phải.
3.2.4 Tính thứ bậc của toán tử
Chúng ta nói rằng toán tử nhân có thứ bậc cao hơn toán tử cộng nếu * lấy các toán hạng của nó trước toán tử +. Trong số hoc phép nhân và phép chia có thứ bậc cao hơn phép cộng và phép trừ.
a. Cú pháp cho biểu thức
Văn phạm cho các biểu thức số học có thể được xây dựng từ một bảng trình bày tính kết hợp và thứ bậc của toán tử. Chúng ta bắt đầu với bốn toán tử số học thông thường và một bảng thứ bậc, trình bày các toán tử theo thứ bậc ngày càng cao, các toán tử có cùng thứ bậc ở trên một hàng.
kết hợp trái: + -
kết hợp trái: * /
Chúng ta tạo ra hai chưa tận expr và term cho hai mức thứ bậc và một chưa tận factor nữa để tạo ra những đơn vị cơ bản cho biểu thức. Các đơn vị cơ bản hiện có trong biểu thức là các ký số(digit) và các biểu thức có đóng mở ngoặc đơn.
factor ® digit| (expr)
Bây giờ ta xét đến toán tử hai ngôi * và / với thứ bậc cao nhất. Bởi vì những toán tử này có tính kết hợp trái, luật sinh của chúng tương tự như luật sinh cho các list(3.1) với tính kết hợp trái.
term ® term * factor
|term / factor
|factor
Tương tự, expr sinh ra danh sách(list) các term được phân cách bởi toán tử cộng và trừ
expr ® expr + term
|expr - term
|term
Do vậy chúng ta thu được văn phạm sau
expr ® expr + term | expr - term | term
term ® term * factor | term / factor | factor
factor ® digit | (expr)
Văn phạm này xử lý biểu thức như một danh sách các term được phân cách bởi dấu * hoặc /. Bất kỳ một biểu thức nào có đóng mở ngoặc đều là factor, vì thế với các dấu ngoặc chúng ta có thể xây dựng các biểu thức tùy nghi nhiều cấp lồng vào nhau.
b. Cú pháp các câu lệnh
Từ khóa cho phép chúng ta nhận ra các câu lệnh trong phần lớn các ngôn ngữ
lập trình. Tất cả mọi câu lệnh được xây dựng cho ngôn ngữ Đơn Giản đều bắt đầu bằng một từ khóa ngoại trừ lệnh gán và lời gọi hàm.
Ví dụ:
Một đoạn văn phạm được xây dựng cho cú pháp câu lệnh Đơn Giản như sau:
lệnh ® id:=biểu_thức
| nếu bt_logic thì lệnh
| nếu bt_logic thì lệnh nếu_không lệnh
| trong_khi bt_logic lặp lệnh
Dựa vào nguyên tắc này chúng ta có thể xây dựng cú pháp cho biểu thức số học, dùng hai ký hiệu chưa kết thúc expr và term cho hai mức ưu tiên, term cho * và /, expr cho + và -. Ký hiệu chưa kết thúc factor sinh ra cho các biểu thức cơ bản.
expr ® expr + term ï expr - term ï term
term ® term * factor ï term / factor ï factor
factor ® digit ï (expr)
3.3 PHÂN TÍCH CÚ PHÁP
Quá tình tạo ra dẫn xuất hoặc cây dẫn xuất câu của ngôn ngữ cho trước được gọi là sự phân tích hay là phân tích cú pháp của câu. Mục đích của quá trình này không chỉ khẳng định là liệu một xâu trên bảng chữ của ngôn ngữ có phải là câu của ngôn ngữ đó không, nghĩa là không chứa các lỗi cú pháp, mà còn không kém phần quan trọng là tìm cấu trúc cú pháp của câu, để sau đó có thể tạo ra bản dịch sang ngôn ngữ khác. Bộ phân tích cú pháp phân tích chuỗi các thẻ từ do bộ phân tích từ vựng cung cấp, để có được cấu trúc phức tạp hơn của chương trình nguồn như biểu thức, phát biểu, chương trình con. Các cấu trúc này được thể hiện dưới dạng cây cú pháp hay cây dẫn xuất dựa trên định nghĩa của ngôn ngữ nguồn. Các nút lá của cây cú pháp là các thẻ từ. Hầu hết các phương pháp phân tích đều nằm trong các lớp: Phân tích từ trên xuống (top-down) và phân tích cú pháp từ dưới lên (bottom-down).
3.3.1 Vai trò của bộ phân tích cú pháp
Bộ phân tích cú pháp nhận các chuỗi thẻ từ từ bộ phân tích từ vựng (hình 3.3) để tạo ra cấu trúc cú pháp của chương trình nguồn. Đó cũng là quá trình xây dựng cây phân tích cú pháp cho chuỗi thẻ từ.
Phương pháp thông dụng để xây dựng bộ phân tích cú pháp là phân tích cú pháp từ trên xuống hoặc từ dưới lên. Bộ phân tích cú pháp từ trên xuống sẽ tạo cây phân tích bắt đầu từ gốc tiến hành đến các nút lá. Ngược lại bộ phân tích cú pháp từ dưới lên sẽ bắt đầu từ các nút lá đi đến đỉnh của cây phân tích của một câu cho trước. Ký hiệu nhập của cả hai bộ phân tích cú pháp trên đều được đọc từ trái sang phải và đọc từng ký hiệu một. Tuy nhiên hai phương pháp trên chỉ làm việc trên tập con của văn phạm phi ngữ cảnh mà thôi. Mặc dù là các tập con của văn phạm phi ngữ cảnh nhưng chúng cũng đủ miêu tả cú pháp của hầu hết ngôn ngữ lập trình. Tính thông dụng của bộ phân tích cú pháp từ trên xuống là do có thể xây dựng được chúng một cách hiệu quả theo lối thủ công. Bộ phân tích cú pháp từ dưới lên thường được tạo từ các công cụ tự động.
cây phân tích
cú pháp
các phần khác của TBD
chương trình nguồn
bộ phân tích
từ vựng
bảng ký hiệu
thẻ từ
lấy thẻ từ
kế tiếp
Hình 3.3 Tương tác của bộ phân tích từ vựng và bộ phân tích cú pháp
bộ phân tích
cú pháp
(a)
type
type
array
[
simple
]
of
type
type
(c)
array
[
simple
]
of
type
num
dotdot
num
type
(d)
array
[
simple
]
of
type
num
dotdot
num
simple
integer
type
(e)
array
[
simple
]
of
type
num
dotdot
num
simple
Hình 2.4 Các bước xây dựng cây phân tích cú pháp từ trên xuống
3.3.2 Phân tích cú pháp từ trên xuống
Khi phân tích cú pháp từ trên xuống ta bắt đầu tạo ra cây dẫn xuất từ gốc là ký hiệu đầu của văn phạm và lần lược áp dụng các qui tắc cho ký hiệu chưa kết thúc trái nhất trong các câu thu được cho đến đỉnh cuối của cây dẫn xuất là ký hiệu nhất
trong các câu thu được cho đến đỉnh cuối của cây dẫn xuất là ký hiệu của câu được phân tích.
Giới thiệu kỹ thuật phân tích cú pháp qua xem xét một văn phạm sau:
type ® simple
| id
| array[simple] of type (3.2)
simple ® integer
| char
| num dotdot num
Sử dụng thẻ từ dotdot thay cho “..” để nhấn mạnh rằng chuỗi ký tự này được xử lý như một đơn vị.
Xây dựng cây phân tích cú pháp theo lối từ trên xuống (hình 3.4) được thực hiện từ gốc, với nhãn là chưa tận khởi đầu rồi thực hiện hai bước sau lặp đi lặp lại:
-Tại nút n có nhãn là chưa tận A, chọn một trong những luật sinh cho A và cho các ký hiệu ở vế phải của luật sinh này làm con của n.
-Tìm nút kế tiếp để xây dựng một cây con tại đó.
Đối với một số văn phạm các bước trên có thể cài đặt trong khi quét chuỗi nguyên liệu từ trái sang phải. Thẻ từ đang được quét trong nguyên liệu thường được gọi là ký hiệu nhìn trước (lookahead symbol). Lúc ban đầu ký hiệu nhìn trước là thẻ từ đầu tiên, nghĩa là thẻ từ tận trái của chuỗi nguyên liệu. Hình 3.5 minh họa việc phân tích chuỗi.
array [num dotdot num ] of integer
Ban đầu thẻ từ array là ký hiệu nhìn trước và phần đã biết của cây phân tích cú pháp bao gồm gốc có nhãn là chưa tận khởi đầu type trong (hình 3.4a). Mục đích là xây dựng phần còn lại của cây sao cho chuỗi được sinh ra bởi cây sẽ so khớp được với chuỗi nguyên liệu.
Để có được một đối sánh cho chưa tận type phải dẫn xuất ra một chuỗi bắt đầu bằng ký hiệu nhìn trước array. Trong văn phạm (3.2) chỉ có một luật sinh cho type có thể dẫn xuất một chuỗi như thế nên ta sẽ chọn luật sinh đó và xây dựng các con cho gốc có nhãn là những ký hiệu ở vế phải của luật sinh.
Mỗi trong ba hình 3.4 có các mũi tên chỉ ra ký hiệu nhìn trước trong nguyên liệu và nút đang được xét trong cây phân tích cú pháp. Khi xây dựng các con của một nút thì bước kế tiếp ta xét các con tận trái. Trong hình 3.4b, các con vừa được xây dựng tại gốc và con tận trái với nhãn array sẽ được xét.
Trong cây phân tích cú pháp, khi một tận và một nút cho tận đó đối sánh được với ký hiệu nhìn trước thì ta dịch tới trước một bước, cả ở cây phân tích cú pháp và ở nguyên liệu. Thẻ từ kế tiếp trong nguyện liệu trở thành lookahead(nhìn trước) mới và con kế tiếp trong cây sẽ được xét.
Trong hình 3.4c mũi tên trong cây đã được dịch tới con kế tiếp của gốc và mũi
tên trong nguyên liệu sẽ được dịch chuyển tới thẻ từ tiếp theo là [. Sau khi dịch tới trước, mũi tên trong cây sẽ chỉ tới con có nhãn là chưa tận simple. Khi một nút có nhãn là chưa tận được xét đến, ta sẽ lặp lại quá trình chọn một luật sinh cho chưa tận đó.
Nói chung, việc chọn một luật sinh cho một ký hiệu chưa tận có thể được thực hiện theo kiểu thử và sai; nghĩa là ta có thể phải thử một luật sinh rồi phải quay lại để thử một luật sinh khác nếu luật sinh thứ nhất không phù hợp. Một luật sinh sẽ không phù hợp nếu sau khi dùng nó, ta không thể tạo ra một cây khớp được với chuỗi nguyện liệu. Tuy nhiên có một trường hợp đặc biệt có tên là phân tích cú pháp dự đoán(predictive parsing) không có tình trạng phải thử lại.
(a)
type
Hình 3.5 Phân tích cú pháp từ trên xuống
trong khi đọc nguyên liệu từ trái sang phải
array
of
integer
[ num
dotdot
num ]
type
(b)
array
[
simple
]
of
type
cây PTCP
nguyên liệu
array
of
integer
[ num
dotdot
num ]
type
(c)
array
[
simple
]
of
type
cây PTCP
nguyên liệu
array
of
integer
[ num
dotdot
num ]
cây PTCP
nguyên liệu
3.3.4 Phân tích cú pháp dự đoán
Phân tích cú pháp đệ qui xuống(recursive desent parsing) là phương pháp phân tích từ trên xuống, trong đó chúng ta thực thi một tập thủ tục đệ qui để xử lý chuỗi nguyên liệu. Mỗi chưa tận của văn phạm được kèm với một thủ tục. Ở đây ta xét một hình thái đặc biệt của phân tích cú pháp đệ qui xuống, đó là phân tích cú pháp dự đoán, trong đó lookahead giúp xác định đúng thủ tục cần chọn cho mỗi chưa tận. Loạt thủ tục được gọi trong khi xử lý chuỗi nguyên liệu ngầm định nghĩa một cây phân tích cú pháp cho nguyên liệu.
Bộ phân tích cú pháp dự đoán (hình 3.5) gồm các thủ tục cho chưa tận type và simple của văn phạm (3.2) và một thủ tục bổ sung match. Ta dùng match nhằm đơn giản hóa đoạn mã cho type và simple; nó dịch tới thẻ từ tiếp theo nếu đối t của nó so khớp được với lookahead. Vì thế match làm thay đổi biến lookahead, đó là thẻ từ hiện đang được quét trong nguyên liệu.
procedure match(t:token);
begin
if lookahead=t then
lookahead:= nexttolen
else error
end;
procedure type;
begin
if lookahead thuộc{integer, char,num} then
simple
else if lookahead = ‘’ then
begin
match(‘‘); match(id)
end
else if looahead = array then
begin
match(array);match(‘[‘); simple; match(‘]’)
match(of); type
end
else error
end;
procedure simple;
begin
if lookahead = integer then
match(integer)
else if lookahead=char then
match(char)
else if lookahead = num then
begin
match(num); match(dotdot); match(num)
end
else error
end;
Hình 3.6 Đoạn mã giả cho bộ phân tích cú pháp dự đoán
Phân tích cú pháp bắt đầu bằng một lời gọi đến thủ tục cho chưa tận khởi đầu type. Cùng với nguyên liệu (hình 3.5), ban đầu lookahead là thẻ từ thứ nhất array. Thủ tục type thực thi đoạn mã.
match(array); match(‘[‘); simple; match(‘]’) match(of); type
tương ứng với vế phải của luật sinh
type ® array [simple] of type
Mỗi tận ở vế phải được đối sánh với ký hiệu nhìn trước và mỗi chưa tận ở vế phải dẫn đến một lời gọi thủ tục của nó.
Với nguyên liệu (hình 3.4), sau khi các thẻ từ array và [ đã đối sánh, ký hiệu nhìn trước sẽ là num. Lúc này thủ tục simple được gọi là đoạn mã
match(num); match(dotdot); match(num)
trong phần thân của nó được thực thi.
Ký hiệu nhìn trước hướng dẫn việc chọn lựa luật sinh. Nếu vế phải của một luật sinh bắt đầu bằng một thẻ từ thì luật sinh có thể được dùng khi ký hiệu nhìn trước đối sánh được với thẻ từ. Bây giờ ta xét vế phải bắt đầu bằng một chưa tận như:
type ® simple
Luật sinh này được dùng nếu ký hiệu nhìn trước có thể được sinh ra từ simple. Ví dụ trong khi thực hiện đoạn mã:
match(array); match(‘[‘); simple;
match(‘]’) match(of); type
giả sử ký hiệu nhìn trước integer khi quyền điều khiển được trao cho lời gọi thủ tục type. Không có luật sinh nào cho type bắt đầu bằng thẻ từ integer. Tuy nhiên một luật sinh simple lại có vì thế luật sinh type ®simple được dùng bằng cách yêu cầu type gọi thủ tục simple trên nhìn trước integer.
Phân tích cú pháp dự đoán dựa vào thông tin về những ký hiệu đầu tiên được sinh ra bởi vế phải của luật sinh. Nói chính xác hơn, gọi a là vế phải của một luật sinh cho bởi chưa tận A. Ta định nghĩa FIRST(a) là tập thẻ từ xuất hiện như những ký hiệu đầu tiên của một hoặc nhiều chuỗi được sinh ra từ a. Nếu a là e hoặc có thể sinh ra e thì e cũng thuộc FIRST(a).
Các tập FIRST phải được xét đến nếu có hai luật sinh A ® a và B ®b. Phân tích đệ qui xuống không trở lui đòi hỏi rằng FIRST(a) và FIRST(b) phải rời nhau. Vì thế ký hiệu nhìn trước có thể được dùng để quyết định xem phải sử dụng luật sinh nào; nếu ký hiệu sải với thuộc FIRST(a) thì a được dùng. Ngược lại nếu ký hiệu sải với thuộc FIRST(b) thì b được dùng.
3.3.5 Thiết kế bộ phân tích cú pháp dự đoán
Bộ phân tích cú pháp dự đoán là một chương trình gồm có một thủ tục cho mỗi ký hiệu chưa tận. Mỗi thủ tục sẽ được thực hiện hai việc:
1. Nó quyết định xem luật sinh nào nhờ vào ký hiệu sải với. Luật sinh có vế phải a sẽ được dùng nếu ký hiệu sải với thuộc FIRST(a). Nếu có xung đột giữa hai vế trên một ký hiệu sải với nào đó thì chúng ta không thể dùng phương pháp phân tích cú pháp này trên văn phạm đang có. Một luật sinh có e ở vế phải được dùng nếu nhìn trước(lookahead) không thuộc tập FIRST của một vế phải khác.
2. Thủ tục sẽ được sử dụng một luật sinh bằng cách mô phỏng vế phải. Một chưa tận gây ra một lời gọi đến thủ tục cho ký hiệu đó và một thẻ từ đối sánh được với ký hiệu nhìn trước gây ra hành động đọc thẻ từ kế tiếp. Nếu tại một thời điểm nào đó, thẻ từ trong luật sinh không đối sánh được với ký hiệu nhìn trước thì sẽ phải khai báo một lỗi.
3.3.6 Đệ qui trái
Một văn phạm là đệ qui trái nếu nó có một chưa tận A sao cho có một dẫn xuất
A ® aA với a 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 văn phạm đệ qui trái, vì thế cần phải có một phép biến đổi loại bỏ đệ qui trái. Ở đây chỉ nêu ra cách đơn giản là thay thế cặp luật sinh đệ qui trái A ® aA|b
bằng các luật sinh không đệ qui trái
A ® bA’
A’ ® aA’ | e
mà không làm thay đổi tập chuỗi khả suy từ A.
3.4 BẢNG KÝ HIỆU
Một cấu trúc dữ liệu được gọi là bảng ký hiệu(symbol table) được dùng để lưu trữ thông tin về nhiều kết cấu của ngôn ngữ nguồn. Các thông tin này được thu thập trong giai đoạn phân tích cú pháp, sẽ cần thiết cho giai đoạn phân tích ngữ nghĩa và sinh mã đối tượng.Ví dụ trong quá trình phân tích từ vựng, các từ tố tạo ra một định danh sẽ được lưu vào mục ghi trong bảng ký hiệu. Các giai đoạn sau có thể bổ sung thêm các thông tin về kiểu của định danh và vị trí được lưu trong bảng.
3.4.1 Giao diện của bảng ký hiệu
Các thủ tục của bảng ký hiệu chủ yếu liên quan đến việc lưu và truy xuất các từ tố. Khi một từ tố được lưu chúng ta cũng lưu thẻ từ đi kèm với từ tố đó. Các thao tác được thực hiện trên bảng ký hiệu:
insert(s,t): trả về chỉ mục của mục ghi mới cho chuỗi s, thẻ từ t
lookup(s): trả về chỉ mục của mục ghi dành cho chuỗi s, hoặc là 0 nếu không tìm thấy s.
Bộ phân tích từ vựng dùng thao tác lookup để xác định xem đã có một mục ghi dành cho một từ tố trong bảng ký hiệu hay chưa. Nếu mục ghi đó chưa có thì nó dùng thao tác insert để tạo ra. Một cài đặt trong đó cả bộ phân tích từ vựng và bộ phân tích cú pháp đều biết về dạng thức của các mục ghi trong bảng ký hiệu sẽ được thảo luận phần sau.
3.4.2 Xử lý các dành riêng
Có thể sử dụng các thủ tục trên để xử lý các từ dành riêng.
Ví dụ xét các thẻ từ (là từ khóa) if và then với các từ tố if và then: insert (“if”,
if); insert(“then”,then).
Mọi lời gọi của hàm lookup(if), lookup(then) sau đó sẽ trả về thẻ từ if, then vì thế if, then không thể làm dùng định danh được.
Một tập các từ dành riêng có thể được xử lý theo lối này qua việc khởi gán bảng ký hiệu cho phù hợp.
3.4.3 Một cài dặt cho bảng ký hiệu
Cấu trúc dữ liệu cài đặt cho bảng ký hiệu được phát thảo trong (hình 3.7).
lexptr
token
value
1
5
9
15
div
mod
id
id
1
2
3
4
0
symtable
EOS
n
EOS
EOS
d
i
v
m
o
d
c
o
u
t
i
EOS
lexems
Hình 3.7 Bảng ký hiệu
Ta không muốn dành ra một lượng không gian nhất định để lưu các từ tố tạo ra định danh; một lượng không gian cố định có thể không đủ lớn để lưu các định danh rất dài và có thể làm lãng phí nhiều khi gặp một định danh ngắn.
Bảng symtale là một dãy. Mỗi phần tử của dãy là mẩu tin lưu giữa hai vùng. Vùng con trỏ lexptr chỉ đến ký tự bắt đầu của mỗi trị từ vựng trong dãy lexemes. Vùng token lưu giữ loại token. Còn các trị thuộc tính khác sẽ không nói ở đây. Vị trí thứ 0 của symtale để trống vì khi lookup không tìm thấy token cần tìm, nó sẽ trả về 0 là vị trí của symtable. Vị trí 1, 2 chứa các từ khóa div, mod. Vị trí 3, 4 chứa danh biểu có trị từ vựng là count và i, ngăn cách giữa các trị từ vựng ký hiệu EOS, không thuộc trong danh biểu. Giải thuật nhận dạng token của bộ phân tích từ vựng (hình 3.8). Nếu ký tự là dấu trống, ký tự tab, dấu xuống dòng được bộ phân tích từ vựng rà đến chúng sẽ bỏ qua mà không cất vào bảng danh ký hiệu và không sinh ra token. Biến toàn cục lineno sẽ tăng lên một khi dấu xuống dòng được rà đến.
Bộ phân tích từ vựng sẽ đọc ký tự tiếp theo. Nếu gặp ký tự đầu tiên của trị từ
vựng mới là số, bộ phân tích từ vựng sẽ đọc tiếp các ký tự kế tiếp. Nếu lại là số, bộ
phân tích từ vựng ghép các số thành một con số chứa vào biến tokenval, cho loại
token là num.
Nếu ký tự đầu tiên của token tiếp theo là chữ, bộ phân tích từ vựng sẽ cất chữ số vào bộ đệm lexbuf. Sau đó lookup sẽ dùng lexbuf tìm kiếm trong bảng ký hiệu, nếu nội dung của lexbuf là div hoặc mod thì không có hành vi cất vào bảng danh biểu, mà trả sang bộ phân tích cú pháp hoặc . Nếu lookup tìm thấy nội dung trong lexbuf đã có trong bảng ký hiệu mà không phải là từ dành riêng thì bộ phân tích từ vựng sẽ gởi sang bộ phân tích cú pháp . Trong trường hợp này trị thuộc tính là vị trí của danh biểu trong bảng symtable. Nếu không tìm thấy lookup sẽ trả về trị 0, lúc đó insert thực hiện hành vi thêm nội dung của lexbuf và loại token vào bảng ký hiệu, trị p được tokenval lưu chứa và loại token được
Các file đính kèm theo tài liệu này:
- DATN.DOC