Giáo trình môn Chương trình dịch (Compiler construction) - Phần 1

Khoa công nghệ thông tin - Đại học Thái Nguyên  
Bộ môn công nghệ phần mềm  
GIÁO TRÌNH MÔN CHƯƠNG TRÌNH DỊCH  
(Compiler Construction)  
Thái nguyên, 2007  
LỜI NÓI ĐẦU  
Môn học chương trình dịch là môn học của ngành khoa học máy tính. Trong  
suốt thập niên 50, trình biên dịch được xem là cực kỳ khó viết. Ngày nay, việc viết  
một chương trình dịch trở nên đơn giản hơn cùng với sự hỗ trợ của các công cụ  
khác. Cùng với sự phát triển của các chuyên ngành lý thuyết ngôn ngữ hình thức và  
automat, lý thuyết thiết kế một trình biên dịch ngày một hoàn thiện hơn.  
Có rất nhiều các trình biên dịch hiện đại, có hỗ trợ nhiều tính năng tiện ích  
khác nữa. Ví dụ: bộ visual Basic, bộ studio của Microsoft, bộ Jbuilder, netbean,  
Delphi … Tại sao ta không đứng trên vai những người khổng lồ đó mà lại đi nghiên  
cứu cách xây dựng một chương trình dịch nguyên thuỷ. Với vai trò là sinh viên  
công nghệ thông tin ta phải tìm hiểu nghiên cứu xem một chương trình dịch thực sự  
thực hiện như thế nào?  
Mục đích của môn học này là sinh viên sẽ học các thuật toán phân tích ngữ  
pháp và các kỹ thuật dịch, hiểu được các thuật toán xử lý ngữ nghĩa và tối ưu hóa  
quá trình dịch.  
Yêu cầu người học nắm được các thuật toán trong kỹ thuật dịch.  
Nội dung môn học : Môn học Chương trình dịch nghiên cứu 2 vấn đề:  
- Lý thuyết thiết kế ngôn ngữ lập trình ( cách tạo ra một ngôn ngữ giúp người  
lập trình có thể đối thoại với máy và có thể tự động dịch được).  
- Cách viết chương trình chuyển đổi từ ngôn ngữ lập trình này sang ngôn ngữ  
lập trình khác.  
Học môn chương trình dịch giúp ta:  
- Nắm vững nguyên lý lập trình: Hiểu từng ngôn ngữ, điểm mạnh điểm yếu  
của nó => chọn ngôn ngữ thích hợp cho dự án của mình. Biết chọn chương trình  
dịch thích hợp (VD với pascal dưới Dos: chương trình dịch là turbo pascal. Đối với  
ngôn ngữ C: chọn turbo C hay bolean C? Bolean C tiện lợi, dễ dùng, turbo C sinh  
mã gọn, không phải lo vè vấn đề tương thích với hệ điều hành nhưng khoá dùng  
hơn). Phân biệt được công việc nào do chương trình dịch thực hiện và do chương  
trình ứng dụng thực hiện.  
- Vận dụng: thực hiện các dự án xây dựng chương trình dịch. Áp dụng vào  
các ngành khác như xử lý ngôn ngữ tự nhiên…  
Để viết được trình biên dịch ta cần có kiến thức về ngôn ngữ lập trình, cấu  
trúc máy tính, lý thuyết ngôn ngữ, cấu trúc dữ liệu, phân tích thiết kế giải thuật và  
công nghệ phần mềm.  
Những kiê  
́
̉
̣
́
thê  
̉
đươ  
c sư  
̉
du  
̣
́
̃
̣
kha  
́
̉
́
̃
tư  
̣ nhiên.  
Tài liệu tham khảo:  
1. Giáo trình sử dụng: Dick Grune, Ceriel Jacobs, Parsing Techniques: A  
Practical Guide, 1998  
2. Một số tài nguyên trực tuyến có thể được tìm thấy bằng việc sử dụng máy  
kiếm, chẳng hạn như http://www.cppreference.com/ và  
3. Bai giang Ly thuyê  
khoa Công Nghê, ĐHQG Ha  
tìm  
̀
̉
́
́
̀
Thư  
̣c hành Chương Trình Dịch của Lê Anh Cường,  
̣
̀
nô  
̣i, 2004.  
4. Giáo trình lý thuyết, thực hành môn học Chương trình dịch của Phạm  
Hồng Nguyên, Khoa Công Nghệ, ĐHQG Hà nội, 1998.  
5. Ngôn ngữ hình thức của Nguyễn Văn Ba, ĐHBK Hà nội, 1994  
6. Thực hành kỹ thuật biên dịch của Nguyễn Văn Ba, ĐHBK Hà nội, 1993  
7. Compiler: principles techniques and tools của A.V. Aho, Ravi Sethi, D.  
Ulman, 1986  
8. Bản dịch của tài liệu: Trình biên dịch: Nguyên lý, kỹ thuật và công cụ của  
Trần Đức Quang, 2000.  
Chương 1: Tổng quan về ngôn ngữ lập trình và chương trình dịch  
1. Ngôn ngữ lập trình và chương trình dịch.  
Con người muốn máy tính thực hiện công việc thì con người phải viết yêu cầu  
đưa cho máy tính bằng ngôn ngữ máy hiểu được. Việc viết yêu cầu gọi là lập  
trình. Ngôn ngữ dùng để lập trình gọi là ngôn ngữ lập trình. Có nhiều ngôn  
ngữ lập trình khác nhau. Dựa trên cơ sở của tính không phụ thuộc vào máy  
tính ngày càng cao người ta phân cấp các ngôn ngữ lập trình như sau:  
- Ngôn ngữ máy (machine languge)  
- Hợp ngữ (acsembly langguge)  
- Ngôn ngữ cấp cao (high level langguage)  
Ngôn ngữ máy chỉ gồm các số 0 và 1, khó hiểu đối với người sử dụng. Mà  
ngôn ngữ tự nhiên của con người lại dài dòng nhiều chi tiết mập mờ, không rõ ràng  
đối với máy. Để con người giao tiếp được với máy dễ dàng cần một ngôn ngữ trung  
gian gần với ngôn ngữ tự nhiên. Vì vậy ta cần có một chương trình để dịch các  
chương trình trên ngôn ngữ này sang mã máy để có thể chạy được. Những chương  
trình làm nhiệm vụ như vậy gọi là các chương trình dịch. Ngoài ra, một chương  
trình dịch còn chuyển một chương trình từ ngôn ngữ nay sang ngôn ngữ khác tương  
đương. Thông thường ngôn ngưc nguồn là ngôn ngữ bậc cao và ngôn ngữ đích là  
ngôn ngữ bậc thấp, ví dụ như ngôn ngữ Pascal hay ngôn ngữ C sang ngôn ngữ  
Acsembly.  
* Định nghĩa chương trình dịch:  
Chương trình dịch  
chương trình  
nguồn (ngôn  
ngữ bậc cao)  
chương trình  
đích (ngôn  
ngữ máy)  
là một chương trình  
thực hiện việc chuyển  
đổi một chương trình  
hay đoạn chương trình  
từ ngôn ngữ này (gọi là  
ngôn ngữ nguồn) sang  
ngôn ngữ khác (gọi là  
ngôn ngữ đích) tương  
đương.  
chương trình  
dịch  
Lỗi  
Hình 1.1: Sơ đồ một chương trình dịch  
Để xây dựng được chương trình dịch cho một ngôn ngữ nào đó, ta cần biết về  
đặc tả của ngôn ngữ lập trình, cú pháp và ngữ nghĩa của ngôn ngữ lập trình đó…  
Để đặc tả ngôn ngữ lập trình, ta cần định nghĩa:  
- Tập các kí hiệu cần dùng trong các chương trình hợp lệ.  
- Tập các chương trình hợp lệ.  
- Nghĩa của từng chương trình hợp lệ.  
Việc định nghĩa tập các kí hiệu cần dùng của ngôn ngữ là dế dàng, ta chỉ cần  
liệt kê là đủ. Việc xác định các chương trình hợp lệ thì khó khăn hơn. Thông  
thường ta dùng các luật của văn phạm để đặc tả. Việc thứ 3, định nghĩa ý nghĩa của  
chương trình hợp lệ là khó khăn nhất. Có 3 phương pháp để xác định nghĩa của  
chương trình hợp lệ.  
+ Phương pháp 1: định nghã bằng phép ánh xạ. ánh xạ mỗi chương trình vào  
một câu trong ngôn ngữ mà ta có thể hiểu được.  
+ Phương pháp 2: Xác định ý nghĩa của chương trình bằng một máy lý tưởng.  
Ý nghĩa của chương rình được đăc tả trong ngôn từ của máy lý tưởng. Máy lý  
tưởng là bộ thông dịch của ngôn ngữ.  
+ Phương pháp 3: ý nghĩa cảu chương trình nguồn là sản phẩm xuất ra của  
trình biên dịch, khi nó dịch chương trình nguồn.  
2. Phân loại chương trình dịch.  
Có thể phân thành nhiều loại tuỳ theo các tiêu chí khác nhau.  
- Theo số lần duyệt: Duyệt đơn, duyệt nhiều lần.  
- Theo mục đích: Tải và chạy, gỡ rối, tối ưu, chuyển đổi ngôn ngữ, chuyển đôỉ  
định dạng…  
- Theo độ phức tạp của chương trình nguồn và đích:  
+ Asembler (chương trình hợp dịch): Dịch từ ngôn ngữ asembly ra ngôn ngữ  
máy.  
+ Preproccessor: (tiền xử lý) : Dịch từ ngôn ngữ cấp cao sang ngôn ngữ cấp  
cao khác (thực chất là dịch một số cấu trúc mới sang cấu trúc cũ).  
+ Compiler: (biên dịch) dịch từ ngôn ngữ cấp cao sang ngôn ngữ cấp thấp.  
- Theo phương pháp dịch chạy:  
+ Thông dịch: (diễn giải - interpreter) chương trình thông dịch đọc chương  
trình nguồn theo từng lệnh và phân tích rồi thực hiện nó. (Ví dụ hệ điều hành thực  
hiện các câu lệnh DOS, hay hệ quản trị cơ sở dữ liệu Foxpro). Hoặc ngôn ngữ  
nguồn không được chuyển sang ngôn ngữ máy mà chuyển sang một ngôn ngữ  
trung gian. Một chương trình sẽ có nhiệm vụ đọc chương trình ở ngôn ngữ trung  
gian nàhiện từng câu lệnh. Ngôn ngữ ian được gọi là ngôn ngữ của  
Chương  
CT ở NN  
trung gian  
trình  
một trình thực ày gọ
Compiler  
Interpreter  
nguồn  
Kết  
quả  
Hình 1.2 Hệ thống thông dịch  
Ví dụ hệ thông dịch Java. Mã nguồn Java được dịch ra dạng Bytecode. File  
đích này được một trình thông dịch gọi là máy ảo Java thực hiện. Chính vì vậy mà  
người ta nói Java có thể chạy trên mọi hệ điều hành có cài máy ảo Java.  
+ Biên dịch: toàn bộ chương trình nguồn được trình biên dịch chuyển sang  
chương trình đích ở dạng mã máy. Chương trình đích này có thể chạy độc lập trên  
máy mà không cần hệ thống biên dịch nữa.  
- Theo lớp văn phạm: LL (1) (LL – Left to right, leftmost) LR(1) (LR – letf to  
right, right most)  
1.3. Cấu trúc của chương trình dịch.  
1.3.1. cấu trúc tĩnh (cấu trúc logic)  
1) Phân tích từ vựng: đọc luồng kí tự tạo thành chương trình nguồn từ trái  
sang phải, tách ra thành các từ tố (token).  
- Từ vựng: Cũng như ngôn ngữ tự nhiên, ngôn ngữ lập trình cũng được xây  
dựng dựa trên bộ từ vựng. Từ vựng trong ngôn ngữ lập trình thường được xây dựng  
dựa trên bộ chữ gồm có:  
+ chữ cái: A .. Z, a . . z  
+ chữ số: 0..9  
+ các ký hiệu toán học: +, - , *, /, (, ), =, <, >, !, %, /  
+ các ký hiệu khác: [, ], . . .  
Các từ vựng được ngôn ngữ hiểu bao gồm các từ khóa, các tên hàm, tên hằng, tên  
biến, các phép toán, . . .  
Các từ vựng có những qui định nhất định ví dụ: tên viết bởi chữ cái đầu tiên sau đó  
là không hoặc nhiều chữ cái hoặc chữ số, phép gán trong C là =, trong Pascal là  
:=,v. . .  
Để xây dựng một chương trình dịch, hệ thống phải tìm hiểu tập từ vựng của  
ngôn ngữ nguồn và phân tích để biết được từng loại từ vựng và các thuộc tính của  
nó,  
Ví dụ:  
Câu lệnh trong chương trình nguồn  
viết bằng ngôn ngữ pascal:  
“a := b + c * 60”  
Chương trình phân tích từ vựng sẽ trả về:  
a là tên (tên (định danh ))  
:= là toán tử gán  
b là tên (định danh)  
+ là toán tử cộng  
c là định danh  
* là toán tử nhân  
60 là một số  
Kết quả phân tích từ vựng sẽ là: (tên, a), phép gán, (tên, b) phép cộng (tên, c)  
phép nhân, (số, 60)  
2). Phân tích cú pháp: Phân tích cấu  
trúc ngữ pháp của chương trình. Các từ tố  
được nhóm lại theo cấu trúc phân cấp.  
- Cú pháp: Cú pháp là thành phần  
quan trọng nhất trong một ngôn ngữ. Như  
chúng ta đã biết trong ngôn ngữ hình thức  
thì ngôn ngữ là tập các câu thỏa mãn văn  
phạm của ngôn ngữ đó. Ví dụ như  
câu = chủ ngữ + vị ngữ  
vị ngữ = động từ + bổ ngữ  
v.v. . .  
Trong ngôn ngữ lập trình, cú pháp của nó  
được thể hiện bởi một bộ luật cú pháp. Bộ  
luật này dùng để mô tả cấu trúc của  
chương trình, các câu lệnh. Chúng ta quan  
tâm đến các cấu trúc này bao gồm:  
1) các khai báo  
2) biểu thức số học, biểu thức logic  
3) các lệnh: lệnh gán, lệnh gọi hàm,  
lệnh vào ra, . . .  
4) câu lệnh điều kiện if  
5) câu lệnh lặp: for, while  
6) chương trình con (hàm và thủ tục)  
Nhiệm vụ trước tiên là phải biết được bộ luật cú pháp của ngôn ngữ mà mình định  
xây dựng chương trình cho nó.  
Với một chuỗi từ tố và tập luật cú pháp của ngôn ngữ, bộ phân tích cú pháp tự  
động đưa ra cây cú pháp cho chuỗi nhập. Khi cây cú pháp xây dựng xong thì quá  
trình phân tích cú pháp của chuỗi nhập kết thúc thành công. Ngược lại nếu bộ phân  
tích cú pháp áp dụng tất cả các luật hiện có nhưng không thể xây dựng được cây cú  
pháp của chuỗi nhập thì thông báo rằng chuỗi nhập không viết đúng cú pháp.  
Chương trình phải phân tích chương trình nguồn thành các cấu trúc cú pháp  
của ngôn ngữ, từ đó để kiểm tra tính đúng đắn về mặt ngữ pháp của chương trình  
nguồn.  
3). Phân tích ngữ nghĩa: Phân tích các đặc tính khác của chương trình mà  
không phải đặc tính cú pháp. Kiểm tra chương trình nguồn để tìm lỗi cú pháp và sự  
hợp kiểu.  
Dựa trên cây cú pháp bộ phân tích ngữ nghĩa xử lý từng phép toán. Mỗi phép  
toán nó kiểm tra các toán hạng và loại dữ liệu của chúng có phù hợp với phép toán  
không.  
VD: tên (biến) được khai báo kiểu real, 60 là số kiểu interge vì vậy trình biên  
dịch đổi thành số thực 60.0.  
- Ngữ nghĩa: của một ngôn ngữ lập trình liên quan đến:  
+ Kiểu, phạm vi của hằng và biến  
+ Phân biệt và sử dụng đúng tên hằng, tên biến, tên hàm  
Chương trình dịch phải kiểm tra được tính đúng đắn trong sử dụng các đại lượng  
này. Ví dụ kiểm tra không cho gán giá trị cho hằng, kiểm tra tính đúng đắn trong  
gán kiểu, kiểm tra phạm vi, kiểm tra sử dụng tên như tên không được khai báo  
trùng, dùng cho gọi hàm phải là tên có thuộc tính hàm, . . .  
4) Sinh mã trung gian: Sinh chương trình rong ngôn ngữ trung gian nhằm: dễ  
sinh và tối ưu mã hơn dễ chuyển đổi về mã máy hơn.  
sau giai đoạn phân tích thì mã trung gian sinh ra như sau:  
temp1 := 60  
temp2 := id3 * temp1  
temp3 := id2 + temp 2  
id1 := temp3  
(1.2)  
(trong đó id1 là position; id2 là initial và id3 là rate)  
5). Tối ưu mã: Sửa đổi chương trình trong ngôn ngữ trung gian hằm cải tién  
chương trình đích về hiệu năng.  
Ví dụ như với mã trung gian ở (1.2), chúng ta có thể làm tốt hơn đoạn mã để  
tạo ra được các mã máy chạy nhanh hơn như sau:  
temp1 := id3 * 60  
id1 := id2 + temp1 (1.3)  
6). Sinh mã: tạo ra chương trình đích từ chương trình trong ngôn ngữ trung  
gian đẫ tối ưu.  
Thông thường là sinh ra mã máy hay mã hợp ngữ. Vấn đề quyết định là việc  
gán các biến cho các thanh ghi.  
Chẳng hạn sử dụng các thanh ghi R1 và R2, các chỉ thị lệnh MOVF, MULF,  
ADDF, chúng ta sinh mã cho (1.3) như sau:  
MOVF id3, R2  
MULF #60, R2  
MOVF id2, R1  
ADDF R2, R1  
MOVF R1, id1  
(1.4)  
Ngoài ra, chương trình dịch còn phải thực hiện nhiệm vụ:  
* Quản lý bảng ký hiệu: Để ghi lại các kí hiệu, tên … đã sử dụng trong  
chương trình nguồn cùng các thuộc tính kèm theo như kiểu, phạm vi, giá trị ... để  
dùng cho các bước cần đến.  
Tõ tè(token) + Thuéc tÝnh (kiÓu, ®Þa chØ lu tr÷) = B¶ng ký  
hiÖu (Symbol table).  
Trong quá trình phân tích từ vựng, các tên sẽ được lưu vào bảng ký hiệu, sau  
đó từ giai đoạn phân tích ngữ nghĩa các thông tin khác như thuộc tính về tên (tên  
hằng, tên biến, tên hàm) sẽ được bổ sung trong các giai đoạn sau.  
- Giai đoạn phân tích từ vựng: lưu trữ trị từ vựng vào bảng kí hiệu nếu nó  
chưa có.  
- Giai đoạn còn lại: lưu trữ thuộc tính của từ vựng hoặc truy xuất các thông  
tin thuộc tính cho từng giai đoạn.  
Bảng kí hiệu được tổ chức như cấu trúc dữ liệu với mỗi phần tử là một mẩu  
tin dùng để lưu trữ trị từ vựng và các thuộc tính của nó.  
- Trị từ vựng: tên từ tố.  
- Các thuộc tính: kiểu, tầm hoạt động, số đối số, kiểu của đối số ...  
VÝ dô: var position, initial, rate : real th× thuéc tÝnh kiÓu  
real cha thÓ x¸c ®Þnh. C¸c giai ®o¹n sau ®ã nh ph©n tÝch  
ng÷ nghÜa vµ sinh m· trung gian míi ®a thªm c¸c th«ng tin  
nµy vµo vµ sö dông chóng. Nãi chung giai ®o¹n sinh m· sÏ sö  
dông b¶ng ký hiÖu ®Ó gi÷ c¸c th«ng tin chi tiÕt vÒ danh  
biÓu.  
* Xử lý lỗi: Khi phát hiện ra lỗi trong quá trình dịch thì nó ghi lại vị trí gặp  
lỗi, loại lỗi, những lỗi khác có liên quan đến lỗi này để thông báo cho người lập  
trình.  
Mçi giai ®o¹n cã thÓ cã nhiÒu lçi, tïy thuéc vµo tr×nh biªn  
dÞch  
mµ  
cã  
thÓ  
lµ:  
- Dõng vµ th«ng b¸o lçi khi gÆp lçi dÇu tiªn (Pascal).  
- Ghi nhËn lçi vµ tiÕp tôc qu¸ tr×nh dÞch (C).  
+ Giai ®o¹n ph©n tÝch tõ vùng: cã lçi khi c¸c ký tù kh«ng thÓ  
ghÐp thµnh mét token (vÝ dô: 15a, a@b,...)  
+ Giai ®o¹n ph©n tÝch có ph¸p: Cã lçi khi c¸c token kh«ng  
thÓ kÕt hîp víi nhau theo cÊu tróc ng«n ng÷ (vÝ dô: if stmt then  
expr).  
+ Giai ®o¹n ph©n tÝch ng÷ nghÜa b¸o lçi khi c¸c to¸n h¹ng cã  
kiÓu kh«ng ®óng yªu cÇu cña phÐp to¸n.  
* Giai đoạn phân tích có đầu vào là ngôn ngữ nguồn, đầu ra là ngôn ngữ trung  
gian gọi là kỳ trước (fron end). Giai đoạn tổng hợp có đầu vào là ngôn ngữ trung  
gian và đầu ra là ngô ngữ đích gọi là kỳ sau (back end).  
Đối với các ngôn ngữ nguồn, ta chỉ cần quan tâm đến việc sinh ra mã trung  
gian mà không cần biết mã máy đích của nó. Điều này làm cho công việc đơn giản,  
không phụ thuộc vào máy đích. Còn giai đoạn sau trở nên đơn giản hơn vì ngôn  
ngữ trung gian thường thì gần với mã máy. Và nó còn thể hiện ưu điểm khi chúng  
ta xây dựng nhiều cặp ngôn ngữ. Ví dụ có n ngôn ngữ nguồn, muốn xây dựng  
chương trình dịch cho n ngôn ngữ này sang m ngôn ngữ đích thì chúng ta cần n*m  
chương trình dịch; còn nếu chúng ta xây dựng theo kiến trúc front end và back end  
thì chúng ta chỉ cần n+m chương trình dịch.  
1.3.2. Cấu trúc động.  
Cấu trúc động (cấu trúc theo thời gian) cho biết quan hệ giữa các phần khi  
hoạt động.  
Các thành phần độc lập của chương trình có thể hoạt động theo 2 cách: lần  
lượt hay đồng thời. mỗi khi một phần nào đó của chương trình dịch xong toàn bộ  
chương trình nguồn hoặc chương trình trung gian thì ta gọi đó là một lần duyệt.  
* Duyệt đơn (duyệt một lần): một số thành phần của chương trình được thực  
hiện đồng thời. Bộ phân tích cú pháp đóng vai trò trung tâm, điều khiển cả chương  
trình. Nó gọi bộ phân tích từ vựng khi cần một từ tố tiếp theo và gọi bộ phân tích  
ngữ nghĩa khi muốn chuyển cho một cấu trúc cú pháp đã được phân tích. Bộ phân  
tích ngữ nghĩa lại đưa cấu trúc sang phần sinh mã trung gian để sinh ra các mã  
trong một  
ngữ trung  
rồi đưa  
bộ tối ưu  
sinh mã.  
Mã nguồn  
ngôn  
gian  
vào  
và  
Phân tích  
cú pháp  
Phân tích từ vựng  
Phân tích  
ngữ nghĩa  
Phân tích  
từ vựng  
Phân tích cú pháp  
Phân tích ngữ nghĩa  
Chương trình nguồn  
Sinh mã trung gian  
Tối ưu mã  
Sinh mã trung gian  
Tối ưu mã  
Sinh mã  
Sinh mã đích  
mã đích  
Chương trình đích  
Chương trình dịch duyệt đơn  
Chương trình dịch duyệt nhiều lần  
* Duyệt nhiều lần: các thành phần trong chương trình được thực hiện lần lượt  
và độc lập với nhau. Qua mỗi một phần, kết quả sẽ được lưu vào thiết bị lưu trữ  
ngaòi để lại được đọc vào cho bước tiếp theo.  
Người ta chỉ muốn có một số ít lượt bởi vì mỗi lượt đều mất thời gian đọc và  
ghi ra tập tin trung gian. Ngược lại nếu gom quá nhiều giai đoạn vào trong một lượt  
thì phải duy trì toàn bộ chương trình trong bộ nhớ, vì 1 giai đoạn cần thông tin  
theo thứ tự khác với thứ tự nó được tạo ra. Dạng biểu diễn trung gian của chương  
trình lớn hơn nhiều so với ct nguồn hoặc ct đích, nên sẽ gặp vấn đề về bộ nhớ.  
Ưu và nhược điểm của các loại:  
Trong giáo trình này  
chúng ta nghiên cứu các  
giai đoạn của một  
chương trình dịch một  
cách riêng rẽ nhưng theo  
thiết kế duyệt một lượt.  
So sánh  
tốc độ  
duyệt đơn duyệt nhiều lần  
tốt  
Kém  
tốt  
bộ nhớ  
kém  
kém  
độ phức tạp  
tốt  
Các ứng dụng lớn Kém  
tốt  
1.4. Môi trường biên dịch  
Chương trình dịch là 1 chương trình trong hệ thống liên hoàn giúp cho người  
lập trình có được một môi trường hoàn chỉnh để phát triển các ứng dụng của họ.  
Chương trình dịch trong hệ thống đó thể hiện trong sơ đồ sau:  
Chương trình nguồn nguyên thủy  
Tiền xử lý  
Chương trình nguồn  
Chương trình  
dịch  
Chương trình đích hợp ngữ  
Assembler  
Mã máy định vị lại được  
Tải / Liên kết  
Thư viện và  
các file đối  
tượng định vị  
lại được  
Mã máy thật sự  
Hình 1.3: Hệ thống xử lý ngôn ngữ  
* Bộ tiền xử lý:  
Chuỗi kí tự nhập vào chương trình dịch là các kí tự của chương trình nguồn  
nhưng trong thực tế, trước khi là đầu vào của một chương trình dịch, toàn bộ file  
nguồn sẽ được qua một thậm chí một vài bọo tiền xử lý. Sản phẩm của các bộ tiền  
xử lý này mới là chương trình nguồn thực sự của chương trình dịch. Bộ tiền xử lý  
sẽ thực hiện các công việc sau:  
- Xử lý Macro: Cho phep người dùng định nghĩa các macro là cách viết tắt của  
các cấu trúc dài hơn.  
- Chèn tệp tin: Bổ sung nội dung của các tệp tin cần dùng trong chương trình.  
Ví dụ : Trong ngôn ngữ Pascal có khai báo thư viện  
“Uses crt;”  
bộ tiền xử lý sẽ chền tệp tin crt vào thay cho lời khai báo.  
- Bộ xử lý hoà hợp: hỗ trợ những ngôn ngữ xưa hơn bằng các cấu trúc dữ liệu  
hoặc dòng điều khiển hiện đại hơn.  
- Mở rộng ngôn ngữ: gia tăng khả năng của ngôn ngữ bằng các macro có sẵn.  
* Trình biên dịch hợp ngữ: Dịch các mã lệnh hợp ngữ thành mã máy.  
* Trình tải/ liên kết:  
Trình tải nhận các max máy khả tải định vị, thay đổi các địa chỉ khả tải định  
vị, đặt các chỉ thị và dữ liệu trong bộ nhớ đã được sửa đổi vào các vik trí phù hợp.  
Trình liên kết cho phép tạo ra một hcương rình từ các tệp tin thư viện hoặc  
nhiều tệp tin mã máy khả tải định vị mà chúng là kết quả của những biên dịch khác  
nhau.  
CHƯƠNG 2  
PHÂN TÍCH TỪ VỰNG  
1. Vai trò của bộ phân tích từ vựng.  
1.1. Nhiệm vụ.  
Bộ phân tích từ vựng có nhiệm vụ là đọc các kí tự vào từ văn bản chương  
trình nguồn và phân tích đưa ra danh sách các từ tố (từ vựng và phân loại cú pháp  
của nó) cùng một số thông tin thuộc tính.  
Đầu ra của bộ phân tích từ vựng là danh sách các từ tố và là đầu vào cho phân  
tích cú pháp. Thực tế thì phân tích cú pháp sẽ gọi lần lượt mỗi từ tố từ bộ phân tích  
để xử lý, chứ không gọi một lúc toàn bộ danh sách từ tố của cả chương trình nguồn.  
Khi nhận được yêu cầu lấy một từ tố tiếp theo từ bộ phân tích cú pháp, bộ  
phân tích từ vựng sẽ đọc kí tự vào cho dến khi đưa ra được một từ tố.  
yêu cầu lấy từ tố  
tiếp theo  
chương trình  
nguồn  
Phân tích  
từ vựng  
Phân tích  
cú pháp  
từ tố  
Bảng ký hiệu  
Hinh 2.4: Sơ đồ phân tích từ tố  
1.2. Quá trình phân tích từ vựng  
1). Xóa bỏ kí tự không có nghĩa (các chú thích, dòng trống, kí hiệu xuống dòng,  
kí tự trống không cần thiết)  
Quá trình dịch sẽ xem xét tất cả các ký tự trong dòng nhập nên những ký tự  
không có nghĩa (khoảng trắng (blanks, tabs, newlines) hoặc lời chú thích phải bị bỏ  
qua. Khi bộ phân tích từ vựng bỏ qua các khoảng trắng này thì bộ phân tích cú  
pháp không bao giờ quan tâm đến nó nữa.  
2). Nhận dạng các kí hiệu: nhận dạng các từ tố.  
Ví dụ ghép các chữ số để được một số và sử dụng nó như một đơn vị trong  
suốt quá trình dịch. Đặt num là một token biểu diễn cho một số nguyên. Khi một  
chuỗi các chữ số xuất hiện trong dòng nhập thì bộ phân tích sẽ gửi cho bộ phân tích  
cú pháp num. Giá trị của số nguyên đã được chuyển cho bộ phân tích cú pháp như  
là một thuộc tính của token num.  
3). Số hoá các kí hiệu: Do con số xử lý dễ dàng hơn các xâu, từ khoá, tên, nên  
xâu thay bằng số, các chữ số được đổi thành số thực sự biểu diễn trong máy. Các  
tên được cất trong danh sách tên, các xâu cất trong danh sách xâu, các chuỗi số trong  
danh sách hằng số.  
1.2. Từ vị (lexeme), từ tố (token), mẫu (patter).  
* Từ vị: là một nhóm các kí tự kề nhau có thể tuân theo một quy ước (mẫu hay  
luật) nào đó.  
* Từ tố: là một thuật ngữ chỉ các từ vựng có cùng ý nghĩa cú pháp (cùng một  
luật mô tả).  
- Đối với ngôn ngữ lập trình thì từ tố có thể được phân vào các loại sau:  
+ từ khoá  
+ tên của hằng, hàm, biến  
+ số  
+ xâu ký tự  
+ các toán tử  
+ các ký hiệu.  
Ví dụ: position := initial + 10 * rate ;  
ta có các từ vựng  
position, :=, initial, +, 10, *, rate, ;  
trong đó position, initial, rate là các từ vựng có cùng ý nghĩa cú pháp là các tên.  
:=  
+
*
10  
;
là phép gán  
là phép cộng  
là phép nhân  
là một con số  
là dấu chấm phẩy  
Như vậy trong câu lệnh trên có 8 từ vựng thuộc 6 từ tố.  
Phân tích cú pháp sẽ làm việc trên các từ tố chứ không phải từ vựng, ví dụ như  
là làm việc trên khái niệm một số chứ không phải trên 5 hay 2; làm việc trên khái  
niệm tên chứ không phải là a, b hay c.  
* Thuộc tính của từ tố:  
Một từ tố có thể ứng với một tập các từ vị khác nhau, ta buộc phải thêm một số thông tin  
nữa để khi cần có thể biết cụ thể đó là từ vị nào. Ví dụ: 15 và 267 đều là một chuỗi số có từ tố là  
num nhưng đến bộ sinh mã phải biết cụ thể đó là số 15 và số 267.  
Thuộc tính của từ tố là những thông tin kết hợp với từ tố đó. Trong thực tế,  
một từ tố sẽ chứa một con trỏ trỏ đến một vị trí trên bảng kí hiệu có chứấcc thông  
tin về nó.  
Ví dụ: position := initial + 10 * rate ;  
ta nhận được dãy từ tố:  
<tên, con trỏ trỏ đến position trên bảng kí hiệu>  
<phép gán, >  
<tên, con trỏ trỏ đến initial trên bảng kí hiệu>  
<phép cộng, >  
<tên, con trỏ trỏ đến rate trên bảng kí hiệu>  
<phép nhân>  
<số nguyên, giá trị số nguyên 60>  
* Mẫu (luật mô tả - patter): Để cho bộ phân tích từ vựng nhận dạng được các  
từ tố, thì đối với mỗi từ tố chúng ta phải mô ta  
̉
đă  
̣
̉
̉
xác định một từ vựng  
co thuôc tư tô đo không, mô ta đo đươc goi la mâ  
́
̣
̀
́
́
̉
́
̣
̣
̀
̃
̀
́
̣
Token  
MÉu (luËt m« t¶)  
Trị từ vựng  
const  
if  
const  
if  
quan hÖ  
(relation)  
tªn (id)  
Sè (num)  
const  
if  
<,<=,=,<>,>,> <> hoÆc > hoÆc >=  
< hoÆc <= hoÆc =hoÆc <> hoÆc  
=
më ®Çu lµ ch÷ c¸i theo sau lµ ch÷  
c¸i, ch÷ sè  
bÊt kú h»ng sè nµo  
bÊt kú c¸c character n»m gi÷a " vµ "  
ngo¹i trõ "  
pi, count, d2  
3.1416, 0, 5  
X©u (literal) "hello"  
Ta có thể coi: từ vị giống các từ cụ thể trong từ điển như nhà, cửa… từ tố gần giống khái  
niệm từ loại như danh từ động từ… Các mẫu (luật mô tả) dùng để nhận dạng loại từ tố, giống  
như những quy định để nhận dạng một từ là danh từ hay động từ…  
Trị từ vựng được so cùng với mẫu của từ tố là chuỗi kí tự và là đơn vị của từ  
vựng. Khi đọc chuỗi kí tự của chương trình nguồn bộ phân tích từ vựng sẽ so sánh  
chuỗi kí tự đó với mẫu của từ tố nếu phù hợp nó sẽ đoán nhận được từ tố đó và đưa  
từ tố vào bảng kí hiệu cùng với trị từ vưng của nó.  
1.4. Cách lưu trữ tạm thời chương trình nguồn.  
Việc đọc từng kí tự trong chương trình nguồn tốn một thời gian đáng kể nên nó ảnh hưởng  
tới tốc độ chương trình dịch. Để giải quyết vấn đề này, thiết kế đọc vào một lúc một chuỗi  
kí tự lưu trữ vào vùng nhớ tạm buffer. Nhưng việc đọc như vậy gặp khó khăn do không thể  
xác định được một chuỗi như thế nào thì chứa chọn vẹn 1 từ tố. Và phải phân biệt được một  
chuỗi như thế nào thì chứa chọn vẹn một từ tố.Có 2 phương pháp giải quyết như sau:  
1. Cặp bộ đệm (buffer pairs)  
* Cấu tạo:  
- Chia buffer thành 2 nửa, một nửa chứa n kí tự ( n = 1024, 4096, …).  
- Sử dụng 2 con trỏ dò tìm trong buffer:  
p1: (lexeme_ beginning) Đặt tại vị trí đầu của một từ vị.  
p2: (forwar):di chuyển trên từng kí tự trong buffer để xác định từ tố.  
E =  
M
*
C
*
*
2
EOF  
* Hoạt động:  
- Đọc n kí tự vào nửa đầu của buffer, 2 con trỏ trùng nhau tại vị trí bắt đầu.  
- Con trỏ p2 tiến sang phải cho tới khi xác định được một từ tố có từ vị là  
chuỗi kí tự nằm giữa 2 con trỏ. Dời p1 lên trùng với p2, tiếp tục dò tìm từ tố mới.  
- khi p2 ở cuối nửa đầu của buffer thì đọc tiếp n kí tự vào nửa đầu thứ 2. Khi  
p2 nằm ở nửa cuối của buffer thì đọc tiếp n kí tự vào nửa đầu của buffer và p2 được  
dời về đầu của bộ đệm.  
- Nếu số kí tự trong chương trình nguồn còn lại ít hơn n thì một kí tự đặc biệt  
được đưa vào buffer sau các kí tự vừa đọc để báo hiệu chương trình nguồn đã được  
đọc hết.  
* Giải thuật hình thức  
if p2 ở cuối nửa đầu then  
begin  
Đọc vào nửa cuối. p2 := p2 + 1;  
end  
else if p2 ở cuối của nửa thứ hai then  
begin  
Đọc vào nửa đầu. p2 := p2 + 1;  
end  
else p2 := p2 + 2  
2. Phương pháp cầm canh.  
Phương pháp trên mỗi lần di chuyển p2 phải kiểm tra xem có phải đã hết một  
nửa buffer chưa nên kém hiệu quả vì phải 2 lần test. Khắc phục:  
- Mỗi lần chí đọc n-1 kí tự vào mỗi nửa buffer còn kí tự thứ n là kí tự đặc  
biệt (thường là EOF). Như vậy ta chỉ cần một lần test.  
E = M * EOF  
C * * 2 EOF  
EOF  
Giải thuật:  
p2 := p2 + 1;  
if p2( = eof then  
begin  
if p2 ở cuối của nửa đầu then  
begin Đọc vào nửa cuối; p2 := p2 + 1 end  
else if p2 ở cuối của nửa cuối then  
begin Đọc vào nửa đầu; Dời p2 vào đầu của nửa đầu end  
else /* eof ở giữa chỉ hết chơng trình nguồn */  
kết thúc phân tích từ vựng  
end  
2. XÁC ĐỊNH TỪ TỐ.  
2.1. Biểu diễn từ tố  
Cách biểu diễn các luật đơn giản nhất là biểu diễn bằng lời. Tuy nhiên cách này thường  
gặp hiện tượng nhập nhằng ( cùng một lời nói có thể hiểu theo nhiều nghĩa khác nhau), phát biểu  
theo nhièu cách khác nhau khó đưa vào máy tính. Các từ tố khác nhau có các mẫu hay luật mô tả  
khác nhau. Các mẫu này là cơ sở để nhận dạng các từ tố. Ta cần thiết phải hình thức hoá các  
mẫu này để làm sao có thể lập trình được. Việc này có thể thực hiện được nhờ biểu thức chính  
qui và ôtômát hữu hạn. Ngoài ra ta có thể dùng cách biểu diễn trực quan của văn phạm phi ngữ  
cảnh là đồ thị chuyển để mô tả các loại từ tố.  
2.1.1. Một số khái niệm về ngôn ngữ hình thức.  
2.1.1.1. Kí hiệu, Xâu, ngôn ngữ.  
* Bảng chữ cái: là một tập Σ ≠  
hữu hạn hoặc vô hạn các đối tượng. Mỗi  
phần tử a Σ gọi là kí hiệu hoặc chữ cái (thuộc bảng chữ cái Σ ).  
* Xâu: Là một dãy liên tiếp các kí hiệu thuộc cùng một bảng chữ cái.  
- Độ dài xâu: là tổng vị trí của tất cả các kí hiệu có mặt trong xâu, kí hiệu là |  
w|.  
- Xâu rỗng: là từ có độ dài = 0 kí hiệu là ε hoặc . Độ dài của từ rỗng = 0.  
- Xâu v là Xâu con của w nếu v được tạo bởi các ký hiệu liền kề nhau trong w.  
*
* Tập tất cả các từ trên bảng chữ cái Σ kí hiệu là Σ . Tập tất cả các từ khác  
+
*
+
rỗng trên bảng chữ cái Σ kí hiệu là Σ .  
Σ = Σ  
{ε }  
* Tiền tố: của một xâu là một xâu con bất kỳ nằm ở đầu xâu. Hậu tố của một  
xâu là xâu con nằm ở cuối xâu. (Tiền tố và hậu tố của một xâu khác hơn chính xâu đó  
ta gọi là tiền tố và hậu tố thực sự)  
* Ngôn ngữ: Một ngôn ngữ L là một tập các chuỗi của các ký hiệu từ một bộ  
*
chữ cái Σ nào đó. (Một tập con A Σ được gọi là một ngôn ngữ trên bảng chữ cái Σ ).  
- Tập rỗng được gọi là ngôn ngữ trống (hay ngôn ngữ rỗng). Ngôn ngữ rỗng là  
ngôn ngữ trên bất kỳ bảng chữ cái nào. (Ngôn ngữ rỗng khác ngôn ngữ chỉ gồm từ rỗng:  
ngôn ngữ không có phần tử nào trong khi ngôn ngữ {ε } có một phần tử là chuỗi rỗng ε )  
* Các phép toán trên ngôn ngữ.  
+ Phép giao: L = L1 ∩ L2 = {x Σ | x L1 hoặc x L2}  
+ Phép hợp: L = L1 L2 = {x Σ | x L1 và x L2}  
+ Phép lấy phần bù của ngôn ngữ L là tập CL = { x Σ | x L}  
*
*
*
+ Phép nối kết (concatenation) của hai ngôn ngữ L1/ Σ và L2/Σ là :  
1
2
L1L2 = {w1w2 | w1 L1 và w2 L2 }/ Σ  
Σ
1
2
Tải về để xem bản đầy đủ
pdf 76 trang yennguyen 20940 Free
Bạn đang xem 20 trang mẫu của tài liệu "Giáo trình môn Chương trình dịch (Compiler construction) - Phần 1", để tải tài liệu gốc về máy hãy click vào nút Download ở trên

File đính kèm:

  • pdfgiao_trinh_mon_chuong_trinh_dich_compiler_construction_phan.pdf