Tải bản đầy đủ (.pdf) (70 trang)

Nghiên cứu phát triển kỹ thuật debugger cho các bộ phát sinh tự động phân tích từ vựng và phân tích cú pháp

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (2.05 MB, 70 trang )

Đại Học Quốc Gia Tp. Hồ Chí Minh
TRƯỜNG ĐẠI HỌC BÁCH KHOA
-----------------------

PHẠM HÙNG TIẾN

NGHIÊN CỨU PHÁT TRIỂN KỸ THUẬT DEBUGGER
CHO CÁC BỘ PHÁT SINH TỰ ĐỘNG PHÂN TÍCH TỪ
VỰNG VÀ PHÂN TÍCH CÚ PHÁP

Chun ngành: Khoa Học Máy Tính

LUẬN VĂN THẠC SĨ

TP. HỒ CHÍ MINH, tháng 7 năm 2010


CƠNG TRÌNH ĐƯỢC HỒN THÀNH TẠI
TRƯỜNG ĐẠI HỌC BÁCH KHOA
ĐẠI HỌC QUỐC GIA TP HỒ CHÍ MINH

Cán bộ hướng dẫn khoa học: TS. Nguyễn Hứa Phùng.

Cán bộ chấm nhận xét 1: TS. Thoại Nam.

Cán bộ chấm nhận xét 2: TS. Nguyễn Thanh Phương.

Luận văn thạc sĩ được bảo vệ tại HỘI ĐỒNG CHẤM BẢO VỆ LUẬN VĂN
THẠC SĨ TRƯỜNG ĐẠI HỌC BÁCH KHOA, ngày 1 tháng 9 năm 2010.

i




TRƯỜNG ĐẠI HỌC BÁCH KHOA
KHOA: Cơng Nghệ Thơng Tin
----------------

CỘNG HỊA XÃ HỘI CHỦ NGHĨA VIỆT NAM
Độc Lập - Tự Do - Hạnh Phúc
---oOo---

Tp. HCM, ngày 1 tháng 7 năm 2010
NHIỆM VỤ LUẬN VĂN THẠC SĨ
Họ và tên học viên: Phạm Hùng Tiến.

Phái: Nam

Ngày, tháng, năm sinh: 07/11/1983.

Nơi sinh: TPHCM.

Chuyên ngành: Khoa Học Máy Tính.
MSHV: 00708210.

1- TÊN ĐỀ TÀI: Nghiên cứu phát triển kỹ thuật debugger cho các bộ phát sinh tự
động phân tích từ vựng và phân tích cú pháp.
2- NHIỆM VỤ LUẬN VĂN
 Tìm hiểu các các bộ phát sinh tự động phân tích từ vựng và phân tích cú
pháp.
 Đánh giá các giải pháp hiện có về debugger cho các bộ phát sinh tự động
phân tích từ vựng và phân tích cú pháp.

 Nghiên cứu và phát triển debugger cho bộ phát sinh tự động phân tích từ
vựng và phân tích cú pháp.
3- NGÀY GIAO NHIỆM VỤ: 25/1/2010.
4- NGÀY HOÀN THÀNH NHIỆM VỤ: 2/7/2010.
5- HỌ VÀ TÊN CÁN BỘ HƯỚNG DẪN: TS. Nguyễn Hứa Phùng.
Nội dung và đề cương Luận văn thạc sĩ đã được Hội Đồng Chuyên Ngành thơng
qua.
CÁN BỘ HƯỚNG DẪN
CHỦ NHIỆM BỘ MƠN
(Họ tên và chữ ký)
QUẢN LÝ CHUYÊN NGÀNH
(Họ tên và chữ ký)

ii

KHOA QL CHUYÊN NGÀNH
(Họ tên và chữ ký)


LỜI CẢM ƠN
Trước tiên, tôi xin chân thành cảm ơn thầy TS Nguyễn Hứa Phùng đã tận
tình hướng dẫn, truyền đạt kiến thức, kinh nghiệm cho tôi trong suốt quá trình thực
hiện luận văn tốt nghiệp này.

Xin gửi lời cảm ơn đến quý thầy cô Trường Đại học Bách Khoa TP.HCM,
những người đã truyền đạt kiến thức quý báu cho tôi trong thời gian học cao học
vừa qua.

Sau cùng, lời tri ân sâu sắc xin được dành cho bố mẹ, những người đã ni
dạy con khơn lớn và hết lịng quan tâm, động viên để con hoàn thành luận văn tốt

nghiệp này.

iii


Tóm tắt luận văn
Sự ra đời của các bộ sinh mã trình biên dịch đã làm giảm đi rất nhiều cơng
sức phát triển một trình biên dịch. Người phát triển chỉ cần đặc tả văn phạm cho bộ
phân tích từ vựng và cú pháp. Khi đó bộ sinh sẽ dựa vào phần đặc tả này sinh mã
cho bộ phân tích từ vựng và cú pháp.Tuy nhiên nhược điểm chính của các bộ sinh là
khơng có hoặc hỗ trợ rất hạn chế quá trình tìm lỗi phát sinh trong quá trình đặc tả
văn phạm.
Mục đích chính của luận văn là nghiên cứu và đưa ra giải pháp phát triển một
debugger cho phép debug ở mức cao – mức đặc tả văn phạm.

iv


MỤC LỤC
CHƯƠNG 1. MỞ ĐẦU ......................................................................................... 1
1.1

Giới thiệu .................................................................................................. 1

1.2

Đóng góp của luận văn .............................................................................. 4

1.3


Cấu trúc luận văn ...................................................................................... 5

CHƯƠNG 2. BỘ SINH MÃ TRÌNH BIÊN DỊCH ................................................. 6
2.1

Bộ phân tích từ vựng ................................................................................. 7

2.1.1

2.1.1.1

Tách token sử dụng NFA .............................................................. 9

2.1.1.2

Tách token sử dụng DFA ............................................................ 11

2.1.2
2.2

Nguyên lý hoạt động ............................................................................ 7

Bộ sinh mã phân tích từ vựng ............................................................ 14

Bộ phân tích cú pháp ............................................................................... 15

2.2.1

LR (k) ................................................................................................ 15


2.2.2

LL(k) ................................................................................................. 18

2.2.2.1

Recursive Decent Parser (RDP) ................................................... 18

2.2.2.2

Non-recursive Predictive Parser (NPP)........................................ 20

CHƯƠNG 3. DEBUGGER .................................................................................. 24
3.1

Giới thiệu debugger................................................................................. 24

3.2

Phân loại debugger .................................................................................. 24

3.3

Kiến trúc tổng quát debugger .................................................................. 24

3.3.1

Tầng giao diện (User Interface).......................................................... 25

3.3.1.1


Source View................................................................................ 25

3.3.1.2

Stack View .................................................................................. 26

3.3.1.3

Breakpoint View ......................................................................... 27

3.3.1.4

Control, Disassembly và Hardware Register View (CPU View) .. 27

3.3.1.5

Inspect View ............................................................................... 28

3.3.1.6

Variable View ............................................................................. 28

3.3.2

Debugger Kernel................................................................................ 29

3.3.3

Operating System Debugger Interface (OSDI) ................................... 29


v


3.4

Nguyên lý hoạt động debugger ................................................................ 29

CHƯƠNG 4. CÁC NGHIÊN CỨU LIÊN QUAN ................................................ 31
4.1

Antlrworks .............................................................................................. 31

4.2

AntlrIDE ................................................................................................. 33

4.3

Antlr Studio ............................................................................................ 35

4.4

Nooza...................................................................................................... 36

4.5

Zyacc ...................................................................................................... 36

CHƯƠNG 5. DEBUGGER CHO BỘ SINH MÃ TRÌNH BIÊN DỊCH ................ 37

5.1

Chức năng debugger cho bộ sinh............................................................. 39

5.2

Kỹ thuật hiện thực debugger cho bộ sinh mã ........................................... 39

5.2.1

Kiến trúc debugger cho bộ sinh .......................................................... 39

5.2.2

Ánh xạ giữa văn phạm và mã sinh ra. ................................................ 40

5.2.3

Nguyên lý hoạt động của debugger cho bộ sinh ................................. 42

5.2.4

Các chức năng chính của debugger cho bộ sinh ................................. 45

5.3

5.2.4.1

Breakpoint................................................................................... 45


5.2.4.2

Chạy từng bước (stepping) .......................................................... 46

5.2.4.3

Phát hiện lỗi ................................................................................ 48

HLDCC (High Level Debugger for Compiler Compiler)......................... 48

5.3.1

Kiến trúc tổng quan ........................................................................... 49

5.3.2

Nguyên lý hoạt động của HLDCC ..................................................... 50

5.3.3

Hiện thực ........................................................................................... 51

5.3.3.1

Language API ............................................................................. 51

5.3.3.2

Debugger API ............................................................................. 52


5.3.4

Debugger cho ngôn ngữ lập trình (Native Debugger) ......................... 59

CHƯƠNG 6. KẾT LUẬN VÀ HƯỚNG PHÁT TRIỂN ....................................... 61
6.1

Kết luận .................................................................................................. 61

6.2

Hướng phát triển ..................................................................................... 61

TÀI LIỆU THAM KHẢO ..................................................................................... 62

vi


MỞ ĐẦU

CHƯƠNG 1.

MỞ ĐẦU

1.1 Giới thiệu
Ra đời từ thập niên 50, lý thuyết trình biên dịch đã có khoảng thời gian phát
triển khá dài. Các lý thuyết này được ứng dụng rộng rãi từ việc phát triển trình biên
dịch cho các ngơn ngữ lập trình hiện đại đến các ngơn ngữ scripting và ngay cả ứng
dụng đơn giản như đọc các tập tin cấu hình. Quá trình hiện thực trình biên dịch trải
qua nhiều bước. Hình 1-1 minh họa sơ đồ tổng quan của các bước thực thi của trình

biên dịch.

Hình 1-1: Các bước của q trình biên dịch.
Phân tích từ vựng sẽ tiến hành phân tích dữ liệu đầu vào và tách thành các ký
hiệu kết thúc (token). Các token này sẽ được sử dụng cho quá trình phân tích cú
pháp. Kết quả của phân tích cú pháp là cây cú pháp. Kế tiếp, quá trình xử lý ngữ
nghĩa sẽ được thực thi dựa vào cây phân tích cú pháp này. Nhằm đảm bảo tính khả

1


MỞ ĐẦU

chuyển, quá trình sinh mã trung gian được thực thi. Ứng với các hệ máy khác nhau
trình biên dịch sẽ tiến hành tối ưu mã và cuối cùng sinh ra mã thực thi. Trong quá
trình biên dịch, trình biên dịch lưu trữ các thông tin trung gian (biến, tên thủ tục…)
trong bảng danh biểu nhằm hỗ trợ cho quá trình phân tích. Ngồi ra trình biên dịch
cịn cung cấp chức năng xử lý lỗi( lỗi cú pháp hoặc ngữ nghĩa…). Tuy nhiên không
phải lúc nào các bước trên được thực thi một cách đầy đủ. Tùy theo từng nhu cầu cụ
thể mà một số bước ta có thể bỏ qua.
Với sự ra đời của biểu thức chính quy (regular expression), Cú pháp cho các
từ vựng có thể được định nghĩa một cách hình thức. Hình 1-2 minh họa cách thức
đặc tả từ vựng dựa vào biểu thức chính quy, trong đó ID là biểu thức các định danh,
INT là biểu thức số nguyên:

ID  ('a'..'z'|'A'..'Z')+ ;
INT  '0'..'9'+ ;
Hình 1-2: Biểu thức chính quy cho phân tích từ vựng.
Tương tự như vậy, cú pháp của chương trình cũng có thể đặc tả một cách
hình thức dựa vào văn phạm phi ngữ cảnh (context-free grammar). Văn phạm phi

ngữ cảnh bao gồm tập hợp các luật sinh. Q trình phân tích cú pháp sẽ được vào
các luật sinh này để kiểm tra xem chương trình nhập đúng cú pháp hay khơng.
Hình 1-3 minh họa văn phạm phi ngữ cảnh cho biểu thức tính tốn:

expr  multExpr (('+'|'-') multExpr)*;
multExpr  atom (('*'|’/’)

atom)*;

atom  INT | ID | '(' expr ')';
Hình 1-3: Văn phạm phi ngữ cảnh cho cú pháp tính giá trị biểu thức.
Ngồi việc đặc tả một cách hình thức, các luật từ vựng và cú pháp có thể
được kết hợp với các hành vi ngữ nghĩa. Hành vi ngữ nghĩa được đặc tả sử dụng
chính ngơn ngữ lập trình phát triển cho bộ phân tích từ vựng hoặc cú pháp. Ví dụ

2


MỞ ĐẦU

Hình 1-4 minh họa luật sinh kết hợp với hành vi ngữ nghĩa. Luật sinh expr và
multExpr nhận giá trị trả về là biến value. Các hành vi ngữ nghĩa được đặc tả giữa
“{“ và “}”. Đặc tả ngữ nghĩa có thể sử dụng các ký hiệu văn phạm ở vế phải (hoặc
đặt lại tên cho ký hiệu văn phạm ở vế phải) như là các biến.

expr returns [int value]
 e=multExpr {$value = $e.value;}
( '+' e=multExpr {$value += $e.value;}
| '-' e=multExpr {$value -= $e.value;}
)*


;

multExpr returns [int value]
 e=atom {$value = $e.value;} ('*' e=atom {$value *= $e.value;})* ;
Hình 1-4: Luật sinh được kết hợp với hành vi ngữ nghĩa.
Từ các đặc tả hình thức từ vựng, ta có thể hiện thực bộ phân tích từ vựng dựa
vào lý thuyết về automat và cũng từ các đặc tả cú pháp ta có thể sử dụng phương
pháp từ dưới lên (bottom-up – LR(k)) hoặc từ trên xuống (top-down – LL(k)) để
kiếm tra tính đúng đắn của chương trình nhập.
Trước đây việc hiện thực các lý thuyết trên tốn nhiều cơng sức. Vì vậy sự ra
đời của các bộ sinh mã cho trình biên dịch (compiler generator hay compiler
compiler) đã giảm bớt đi rất nhiều gánh nặng cho người phát triển. Mục đích bộ
sinh mã là dựa vào các đặc tả hình thức các luật sinh kết hợp với hành vi ngữ nghĩa
như trên sẽ sinh ra mã ngơn ngữ lập trình mong muốn cho q trình phân tích từ
vựng và cú pháp.
Đến nay rất nhiều bộ sinh mã đã ra đời nhưng các cơng cụ hỗ trợ phát triển
cịn rất hạn chế nên việc ứng dụng bộ sinh mã gặp nhiều giới hạn như:
 Làm thế nào để phát hiện lỗi trong quá trình đặc tả văn phạm?
 Làm thế nào để biết chắc rằng văn phạm đặc tả đúng yêu cầu?

3


MỞ ĐẦU

 Khi bộ phân tích (từ vựng hoặc cú pháp) được thực thi, trong trường hơp
phát sinh lỗi thì nguyên nhân lỗi này do đâu?
Bản thân chính các bộ sinh mã hiện tại khơng cung cấp hoặc nếu có thì rất
hạn chế các cơng cụ gỡ rối (debugger). Người đặc tả chủ yếu dựa vào kinh nghiệm

là chính. Một số bộ sinh cũng hỗ trợ nhưng đòi hỏi người đặc tả phải có các kiến
thức về trình biên dịch (quá trình shift/reduce…) và cấu trúc mã sinh ra (trong
trường hợp sử dụng debugger có sẵn của ngơn ngữ lập trình – source level
debugger). Do đó nhu cầu phát triển debugger ở mức cấp cao (high level debugger)
cho bộ sinh là cần thiết.
Mục đích chính của debugger ở mức cao bao gồm:
 Ghi nhận được quá trình áp dụng luật sinh cho đến vị trí phát sinh lỗi
hoặc điểm dừng (breakpoint).
 Cho phép chạy từng bước (stepping) qua các luật sinh cũng như hành vi
ngữ nghĩa.
 Cho phép xem xét trạng thái chương trình (giá trị biến…) tại điểm dừng.
1.2 Đóng góp của luận văn
Luận văn trình bày về HLDCC (High level debugger for compiler compiler)
do tác giả phát triển. HLDCC là real-time debugger cho bộ sinh mã trình biên dịch.
HLDCC có cách tiếp cận khác với các debugger cho bộ sinh hiện có: hỗ trợ nhiều
bộ sinh và ngơn ngữ lập trình khác nhau. Chức năng chính của HLDCC bao gồm:
 Hỗ trợ các bộ sinh mã RDP.
 Môi trường phát triển tích hợp (IDE).
 Hỗ trợ đặc tả văn phạm cho nhiều bộ sinh.
 Hỗ trợ debug nhiều ngơn ngữ lập trình khác nhau.
 Real-time debugger: cho phép debug các hành vi ngữ nghĩa.
 Hỗ trợ hầu hết các chức năng của debugger: step (in, out, over),
breakpoint (có điều kiện và không điều kiện).

4


MỞ ĐẦU

1.3 Cấu trúc luận văn

Chương 2 sẽ giới thiệu nguyên lý hoạt động của bộ sinh mã trình biên dịch.
Chuơng 3 giới thiệu về debugger. Kế tiếp, chương 4 sẽ trình bày các nghiên cứu
liên quan trong việc phát triển debugger cho bộ sinh mã trình biên dịch. Chương 5
tập trung chính vào phát triển debugger cho bộ sinh mã cho trình biên dịch và
chương 6 đưa ra kết luận và hướng phát triển của đề tài.

5


BỘ SINH MÃ TRÌNH BIÊN DỊCH

CHƯƠNG 2.

BỘ SINH MÃ TRÌNH BIÊN DỊCH

Chương này trình bày tổng quan về nguyên lý hoạt động chung của các bộ
sinh mã trình biên dịch, phương pháp sinh mã cho bộ phân tích từ vựng và hai
phương pháp sinh mã cho bộ phân tích cú pháp: phương pháp LR (từ dưới lên) và
LL (từ trên xuống).
Các bộ sinh mã trình biên dịch cung cấp ngơn ngữ cho phép đặc tả hình thức
phân tích từ vựng (lexer) và phân tích cú pháp (parser). Từ đặc tả này, bộ sinh mã sẽ
tiến hành xử lý và sinh ra mã tương ứng cho bộ phân tích từ vựng và cú pháp. Hình
2-1 minh họa quá trình này.

Hình 2-1: Nguyên tắc hoạt động của bộ sinh mã trình biên dịch.
Trong q trình hoạt động phân tích cú pháp, bộ phân tích cú pháp sẽ sử
dụng bộ phân tích từ vựng để lấy các token phục vụ cho quá trình phân tích. Mối
quan hệ giữa 2 bộ phân tích này được minh họa trong Hình 2-2.

Hình 2-2: Giao tiếp giữa bộ phân tích từ vựng và cú pháp.

Phần tiếp theo sẽ trình bày chi tiết về nguyên lý hoạt động của bộ phân tích
và bộ sinh mã từ vựng (mục 2.1) và cú pháp (mục 2.2).

6


BỘ SINH MÃ TRÌNH BIÊN DỊCH

2.1 Bộ phân tích từ vựng
2.1.1 Nguyên lý hoạt động
Nhiệm vụ chính của bộ phân tích từ vựng là tách ra các token phục vụ cho
q trình phân tích cú pháp. Từ vựng có thể được đặc tả một cách hình thức dựa vào
biểu thức chính quy và văn phạm chính quy. Từ đặc tả này, sơ đồ truyền (transition
diagram) sẽ được tạo ra. Sơ đồ truyền biểu diễn luồng đi của các ký tự nhập trong
quá trình quét (scan) các ký tự đầu vào. Kết quả thực thi của sơ đồ truyền sẽ đưa ra
token.
Sơ đồ truyền bao gồm tập các trạng thái (state) và các đường truyền
(transition) giữa các trạng thái. Mỗi đường truyền được quy định điều kiện dựa trên
ký tự nhập. Điều kiện này thỏa thì đường truyền mới được thực thi.
Để minh họa ta xét văn phạm chính quy đặc tả chữ số như sau (Hình 2-3):
number  digit+ ('.' digit+ ‘E’)? ('+' | ‘-‘)? digit+ ;
digit  ‘0’..’9’ ;
Hình 2-3: Văn phạm chính quy nhận dạng chữ số.
Khi đó sơ đồ truyền cho luật sinh number như Hình 2-4

Hình 2-4: Sơ đồ truyền cho quá trình nhận dạng chữ số (luật sinh number).

7



BỘ SINH MÃ TRÌNH BIÊN DỊCH

Trạng thái được biểu diễn dưới dạng các vòng tròn và các đường truyền là
các mũi tên nối giữa các vòng tròn. Trên các mũi tên quy định điều kiện ký tự nhập
để từ trạng thái này có thể chuyển sang trạng thái khác. Ví dụ ta đang ở trạng thái
xuất phát(trạng thái 1 - vịng trịn in đậm) thì khi gặp ký tự số ta chuyển sang trạng
thái 2. Tại trạng thái 2 ta có ba đường đi. Nếu ký tự nhập tiếp theo là số thì trạng
thái 2 vẫn giữ nguyên. Trong trường hợp là ký tự ‘.’ ta chuyển sang trạng thái 3 và
cuối cùng ta chuyển sang trạng thái 5 nếu ký tự nhập là ‘E’.
Các lý thuyết về automat hữu hạn (finite automata) được áp dụng để xây
dựng sơ đồ truyền từ đặc tả văn phạm. Có hai loại automat hữu hạn:
 Xác định (deterministic finite automata – DFA) là automat mà ở đó mỗi
trạng thái chỉ có duy nhất 1 đường truyền đi ra trên cùng ký hiệu nhập.
Hình 2-2 minh họa một DFA.
 Không xác định (nondeterministic finite automata – DFA) là automat mà
ở đó trạng thái hiện tại nào đó có nhiều đường truyền đi ra trên cùng ký
hiệu nhập.
Chi tiết quá trình xây dựng DFA hoặc NFA từ đặc tả có thể tham khảo trong
[1]. Các DFA hoặc NFA được lưu trữ sử dụng bảng truyền (transition table). Bảng
truyền có dạng như Hình 2-6.

Trạng thái

Ký hiệu nhập
a

b

0


{0,1}

{0}

1

-

{2}

2

-

{3}

Hình 2-6: Bảng truyền cho
Hình 2-5: Sơ đồ truyền (NFA) cho biểu
thức chính quy (a|b)*abb.

sơ đồ Hình 2-5.

8


BỘ SINH MÃ TRÌNH BIÊN DỊCH

Như ta đã biết đặc tả cho phân tích từ vựng bao gồm một tập các văn phạm
chính quy. Vậy làm thế nào để bộ phân tích từ vựng có thể tách được các token này
từ các đặc tả này? Phần tiếp theo sẽ trình bày hai cách tách dữ liệu nhập thành các

token dựa vào NFA và DFA.
2.1.1.1 Tách token sử dụng NFA
Để minh họa quá trình tách token sử dụng NFA, ta xét đặc tả văn phạm cho
từ vựng như Hình 2-7.
ID1  a;
ID2  cbb;
ID3  b*d+;
Hình 2-7: Đặc tả văn phạm phân tích từ vựng.
Các luật sinh trên được biểu diễn dưới dạng sơ đồ NFA như Hình 2-8, Hình
2-9 và Hình 2-10.

Hình 2-8: NFA cho luật sinh ID1.

Hình 2-9: NFA cho luật sinh ID2.

Hình 2-10: NFA cho luật sinh ID3.

9


BỘ SINH MÃ TRÌNH BIÊN DỊCH

Từ 3 sơ đồ trên ta xây dựng sơ đồ NFA (Hình 2-11) tổng hợp bằng cách
thêm trạng thái 0 nối với các trạng thái bắt đầu của 3 sơ đồ này với điều kiện
chuyển là (ký tự đọc là rỗng). Từ đó ta áp dụng giải thuật nhận dạng để tiến hành
tách token. Tùy vào trạng thái kết thúc đạt được ta sẽ phân loại được các token. Nếu
trạng thái 2 thì token sẽ là ID1, trạng thái 6 token sẽ là ID2 và trạng thái 8 token là
ID3.

Hình 2-11: NFA tổng hợp cho 3 luật sinh ID1, ID2 và ID3.

Từ trạng thái 0, giải thuật nhận dạng sẽ thực thi bước nhìn trước (look-ahead)
ký hiệu nhập để xác định trạng thái kế tiếp là 1, 3 hoặc 7 (thực tế là bất cứ khi nào
gặp các đường truyền có điều kiện là thì bước nhìn trước được thực thi). Hình
2-12 minh họa thủ tục nhìn trước cho NFA Hình 2-11.
la = look-ahead(input); //nhìn trước 1 ký tự nhập
if (la == ‘a’) state = 1
else if (la == ‘c’) state = 3
else if (la == ‘b’ || la == ‘d’) state = 7
else error();
//tiếp thục thực thi giải thuật nhận dạng
….
Hình 2-12: Thủ tục thực thi bước nhìn trước.

10


BỘ SINH MÃ TRÌNH BIÊN DỊCH

Thủ tục nhìn trước kết hợp với thủ tục nhận dạng token dựa trên NFA cho ra
thủ tục tách token như sau:
while a ≠ null do begin
s0 = trạng thái kết quả của quá trình nhìn trước.
S = -closure({s0});
a = nextchar();
while true do begin
S = -closure(move(S,a));
a = nextchar();
if(S ==∅) error();
else if S ∩ F = ∅ then
lưu token;

S = -closure({s0});
end
end
end
Trong đó
 F: tập trạng thái kết thúc của NFA.
 move(S,a): tập trạng thái có thể đạt đến từ
trạng thái trong tập S với ký hiệu nhập là a.


-closure(T): tập trạng thái có thể đạt đến từ
trạng thái s ∈T trên ký hiệu nhập rỗng( ). Chi
tiết giải thuật tính tham khảo [1].

Hình 2-13: Thủ tục nhận dạng dựa trên NFA.
2.1.1.2 Tách token sử dụng DFA
Do có sự tương đồng giữa NFA và DFA, ta có thể áp dụng giải thuật để
chuyển từ NFA sang DFA [1]. Sau đó áp dụng giải thuật nhận dạng dựa trên DFA
này cho q trình tách token. Xét lại ví dụ Hình 2-11, sau khi áp dụng giải thuật
chuyển từ NFA sang DFA ta sẽ được bảng truyền DFA như Hình 2-14.

11


BỘ SINH MÃ TRÌNH BIÊN DỊCH

Trạng thái

Ký hiệu nhập Kết quả nhận
a


b

dạng token

0137

247

8

-

247

7

58

ID1

8

-

8

ID3

7


7

8

-

58

-

68

ID3

68

-

8

ID2

Hình 2-14: Bảng trạng thái chuyển từ NFA sang DFA.
Việc xây dựng thủ tục nhận dạng dựa trên sơ truyền tương đối đơn giản. Một
vòng lặp sẽ được thực thi chuyển trạng thái cho đến khi nhận dạng được token hoặc
phát sinh lỗi. Hình 2-15 minh họa chi tiết giải thuật này.
while(true) {
c = nextchar();
switch(state) {

case 1: state = 2;
break;
case 2:
if(c == ‘.’) state = 3
else if(isdigit(c)) state = 2
else if(c==’E’) state = 5
else error();
break;
case 3: state = 4;
break;
case 4:
if(c==’E’) state = 5;
else if(isdigit(c)) state = 4;

12


BỘ SINH MÃ TRÌNH BIÊN DỊCH

else error();
break;
case 5:
if(c==’+’ || c==’-‘) state = 6
else if(isdigit(c)) state = 7;
else error();
case 6: ………………
}
if (state == trạng thái kết thúc) then
lưu token
state = trạng thái bắt đầu

end
}
Hình 2-15: Thủ tục nhận dạng dựa trên DFA.
Mỗi lần lặp một ký tự sẽ được đọc vào. Các case trong lệnh switch chính là
các trạng thái hiện tại. Dựa vào trạng thái hiện tại và ký tự đọc vào thủ tục sẽ tiến
hành ra quyết định xem trạng thái kế tiếp là trạng thái nào.
Một cách khác là ta có thể sử dụng bảng truyền để thực thi quá trình nhận
dạng. Thủ tục minh Hình 2-16 họa giải thuật nhận dạng token dựa trên bảng truyền.
thủ tục move(s,c) sẽ thực hiện tra trong bảng truyền trạng thái kế tiếp trong trường
hợp hợp trạng thái hiện tại là s và ký tự nhập là c hoặc phát sinh lỗi trong trường
hợp khơng tìm thấy trạng thái kế tiếp.

13


BỘ SINH MÃ TRÌNH BIÊN DỊCH

s = s0;
c = nextchar();
while c ≠ null do
s = move(s,c);
c = nextchar();
if(s == null) error();
else if(s == trạng thái kết thúc)
lưu token
s = s0;
end
Trong đó:
 move(s,c): trạng thái kế tiếp từ trạng thái s với ký hiệu
nhập là c.

Hình 2-16: Thủ tục nhận dạng token dựa trên bảng truyền.
2.1.2 Bộ sinh mã phân tích từ vựng
Nhiệm vụ chính của bộ sinh mã cho bộ phân tích từ vựng là sinh ra bảng
truyền dựa trên đặc tả từ vựng. Từ bảng truyền này các giải thuật nhận dạng
automat sẽ được thực thi cho quá trình phân tích từ vựng. Hình 2-17 cho thấy
ngun lý hoạt động của bộ sinh mã phân tích từ vựng.
Bộ phân tích từ vựng

Đặc tả từ vựng

Bảng truyền
(transition
table)

Bộ sinh

Input

Giải thuật nhận
dạng automat

Hình 2-17: Bộ sinh mã phân tích từ vựng.

14

Output


BỘ SINH MÃ TRÌNH BIÊN DỊCH


2.2 Bộ phân tích cú pháp
Hiện nay có hai giải thuật được sử dụng rộng rãi là phân tích LR(k) và
LL(k). Phần tiếp theo sẽ trình bày chi tiết về hai giải thuật này.
2.2.1 LR (k)
Bộ phân tích LR hay cịn gọi là bộ phân tích từ dưới lên (bottom-up) đọc dữ
liệu đầu vào từ trái sang (left-to-right) và xây dựng các dẫn xuất phải nhất
(rightmost), k là số lượng ký hiệu (token) nhìn trước để thực hiện quyết định phân
tích. Các bộ sinh mã thuộc họ LR bao gồm: Flex/Bison [2], Lex/Yacc [3],
GoldParser [4]…
Bộ sinh mã LR hoạt động bằng cách phân tích văn phạm đặc tả và sinh ra bảng
action/goto. Có 3 phương pháp xây dựng bảng action/goto: SLR, Canonical LR,
LALR [1]. Dựa vào bảng này và các token đọc vào, bộ phân tích thực hiện q trình
shift/reduce cho đến khi kết thúc hoặc có lỗi xảy ra. Hình 2-18 minh họa mơ hình
hoạt động của bộ phân tích LR.

Hình 2-18: Mơ hình hoạt động của bộ phân tích LR.

15


BỘ SINH MÃ TRÌNH BIÊN DỊCH

Thủ tục Hình 2-19 minh họa giải thuật shift/reduce của bộ phân tích LR dựa vào
bảng action/goto cho q trình phân tích cú pháp:
1

set ip to point to the first symbol of w$

2


repeat forever begin

3

let s be the state on top of the stack and
a the symbol pointed to by ip;

4
5

if action[s,a] = shift s’ then begin

6

push a then s’ on top of the stack

7

advance ip to the next input symbol

8

end

9

else if action[s,a] = reduce A b then begin

10


pop 2*|b| symbols off the stack;

11

let s’ be the state now on top of the stack;

12

push A then goto[s’,A] on top of the stack;

13

output the production A  b

14

end

15

else if action[s,a] = accept then

16
17

return
else error()

18 end
Hình 2-19: Thủ tục phân tích cú pháp dựa trên bảng action/goto.

Ví dụ dưới (Hình 2-20) minh họa văn phạm và bảng action/goto sinh ra theo
giải thuật SLR cho văn phạm này:

16


BỘ SINH MÃ TRÌNH BIÊN DỊCH

Văn phạm
1)
2)
3)
4)
5)
6)

Bảng Action
Bảng Goto
state id
+
*
(
) $
E
T
F
E  E+T
0
s5
s4

1
2
3
ET
1
s6
acc
T  T*F
2
r2
s7
r2 r2
3
r4
r4
r4 r4
TF
4
s5
s4
8
2
3
F  (E)
5
r6
r6
r6 r6
6
s5

s4
9
3
F  id
7
s5
s4
10
8
s6
s11
9
r1
s7
r1 r1
10
r3
r3
r3 r3
11
r5
r5
r5 r5
Hình 2-20: Bảng action/goto của văn phạm sử dụng phương pháp SLR.
Để minh họa, ta sẽ tiến hành chạy từng bước giải thuật trên với dữ liệu input:

id*id+id$ (Hình 2-21).
stack

input

action
output
0
id * id + id $ shift 5
0 id 5
* id + id $ reduce by F→id
F→id
0F3
* id + id $ reduce by T→F
T→F
* id + id $ shift 7
0T2
id + id $ shift 5
0T2*7
+ id $ reduce by F→id
0 T 2 * 7 id 5
F→id
+ id $ reduce by T→T*F T→T*F
0 T 2 * 7 F 10
+ id $ reduce by E→T
0T2
E→T
+ id $ shift 6
0E1
Id $ shift 5
0E1+6
$ reduce by F→id
0 E 1 + 6 id 5
F→id
$ reduce by T→F

0E1+6F3
T→F
$ reduce by E→E+T E→E+T
0E1+6T9
$ accept
0E1
Hình 2-21: Q trình chạy từng bước giải thuật phân tích LR.

17


BỘ SINH MÃ TRÌNH BIÊN DỊCH

2.2.2 LL(k)
Bộ phân tích LL hay cịn gọi là bộ phân tích từ trên xuống (top-down) đọc dữ
liệu đầu vào từ trái sang (left-to-right) và xây dựng các dẫn xuất trái nhất (leftmost),
k là số lượng ký hiệu (token) nhìn trước để thực hiện quyết định phân tích. Các bộ
sinh mã thuộc họ LL bao gồm: Antlr [5], Coco [6], Grammatica [7], JavaCC [8]…
Một trong những nhược điểm của bộ phân tích LL đó chính là không hỗ trợ
văn phạm chứa luật sinh đệ quy trái. Luật sinh đệ quy trái là luật sinh có dạng:
AAα | β.
Bộ phân tích LL được phân ra thành hai loại: Recursive Decent Parser và
Non-recursive Predictive Parser.
2.2.2.1 Recursive Decent Parser (RDP)
RDP thực thi q trình phân tích bằng cách gọi đệ quy các thủ tục. Các thủ
tục này tương ứng với luật sinh. Quy tắc sinh mã cho bộ sinh RDP như sau:
 Mỗi ký hiệu không kết thúc tương ứng với một chương trình con.
 Mỗi ký hiệu không kết thúc ở vế phải của luật sinh tương ứng với lời gọi
chương trình con.
 Mỗi ký hiệu kết thúc ở vế phải tương ứng với lời gọi hàm match kiểm tra

tính hợp lệ của ký hiệu kết thúc này.
 Các hành vi ngữ nghĩa được sao chép nguyên bản theo thứ tự tương ứng
trong luật sinh.
 Đối với ký hiệu khơng kết thúc có nhiều lựa chọn (alternative) ở vế phải
α → β1 | β2 |…| βn |, mã sinh ra sẽ là lệnh switch/case (hoặc if/else) kiểm
tra điều kiện để thực thi lựa chọn thích hợp (Hình 2-22).
switch(điều kiện){
case điều kiện 1:
mã thực thi β1;
break;
case điều kiện 2:
mã thực thi β2;
break;
….

18


×