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á
Trang 1TRƯỜNG ĐẠI HỌC BÁCH KHOA
Trang 2CÔNG TRÌNH ĐƯỢC HOÀ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
Trang 3TRƯỜNG ĐẠI HỌC BÁCH KHOA CỘNG HÒA XÃ HỘI CHỦ NGHĨA VIỆT NAM KHOA: Công Nghệ Thông Tin Độ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 KHOA QL CHUYÊN NGÀNH
(Họ tên và chữ ký) QUẢN LÝ CHUYÊN NGÀNH (Họ tên và chữ ký)
(Họ tên và chữ ký)
Trang 4LỜ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 đã nuôi 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
Trang 5Tó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
Trang 6MỤ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 Nguyên lý hoạt động 7
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 Bộ sinh mã phân tích từ vựng 14
2.2 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
Trang 73.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.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
5.3 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
Trang 8CHƯƠ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 quá 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ả
Trang 9chuyể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 Ngoà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 Quá 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 toá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
Ngoà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ụ
Trang 10Hì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 quá 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?
Trang 11 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)
Trang 121.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
Trang 13CHƯƠ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 quá 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)
Trang 142.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 quá 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)
Trang 15Trạ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
Hình 2-6: Bảng truyền cho
sơ đồ Hình 2-5
Hình 2-5: Sơ đồ truyền (NFA) cho biểu
thức chính quy (a|b)*abb
Trang 16Như 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
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
Trang 17Từ 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
Trang 18Thủ 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
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 quá 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
Trang 19Trạng thái Ký hiệu nhập Kết quả nhận
- ID3 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
Trang 20else error();
break;
case 5:
if(c==’+’ || c==’-‘) state = 6 else if(isdigit(c)) state = 7;
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
Trang 21if(s == null) error();
else if(s == trạng thái kết thúc)
Bộ phân tích từ vựng
Đặc tả từ vựng Bộ sinh
Bảng truyền (transition table)
Giải thuật nhận dạng automat
Hình 2-17: Bộ sinh mã phân tích từ vựng
Trang 222.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 quá 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
Trang 23Thủ 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 quá 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
4 a the symbol pointed to by ip;
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
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:
Trang 24Hì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:
shift 5 reduce by F→id reduce by T→T*F reduce by E→T shift 6
shift 5 reduce by F→id reduce by T→F reduce by E→E+T
accept
F→id T→F
F→id T→T*F E→T
F→id T→F E→E+T
Hình 2-21: Quá trình chạy từng bước giải thuật phân tích LR
Trang 25Bộ 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 quá 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)
Trang 26case điều kiện n:
mã thực thi βn;
break;
}
Hình 2-22: Mã sinh ra ứng với ký hiệu không kết thúc có nhiều lựa chọn
Đối với luật sinh có dạng α → β+, mã sinh ra có dạng sau (Hình 2-23)
mã thực thi β;
while(true){
if (điều kiện dừng) break;
else
mã thực thi β;
}
Hình 2-23: Mã sinh ra của luật sinh có dạng α → β+
Đối với luật sinh có dạng α → β*, mã sinh ra sẽ có dạng sau (Hình 2-24):
while(true){
if (điều kiện dừng) break;
else
mã thực thi β;
} Hình 2-24: Mã sinh ra của luật sinh có dạng α → β*
Đối với luật sinh có dạng α → β?, mã sinh ra sẽ có dạng như sau (Hình 2-25):
if (điều kiện thực thi β)
mã thực thi β;
Hình 2-25: Mã sinh ra của luật sinh có dạng α → β?
Để hiểu rõ hơn quy tắc sinh mã trên, xét ví dụ sau (Hình 2-26):
prog ::= ‘program’ body
Trang 27Hình 2-26: Văn phạm và mã sinh ra tương ứng theo giải thuật RDP
Ta thấy rằng luật sinh prog sẽ tương ứng với thủ tục prog bên mã sinh Kế
tiếp do gặp ký hiệu kết thúc ‘program’ nên dòng mã tiếp theo sẽ thực thi lệnh match nhằm đảm bảo token hiện tại có giá trị ‘program’ Tiếp theo vế phải body sẽ tương ứng với lời gọi thủ tục body và cuối cùng hành vi ngữ nghĩa action_block_1 sẽ được
chép nguyên vẹn sang phần tiếp theo của mã sinh ra
2.2.2.2 Non-recursive Predictive Parser (NPP)
Khác với RPP, NPP không thực hiện gọi đệ quy các thủ tục mà thay vào đó
sử dụng stack và thực hiện các thao tác push và pop tùy thuộc vào token tiếp theo của dữ liệu nhập NPP có cơ chế hoạt động sử dụng bảng phân tích giống với bộ sinh LR Hình 2-27 minh họa mô hình hoạt động của NPP
Hình 2-27: Các thành phần của bộ sinh NPP
Trang 28Thủ tục hoạt động của bộ sinh NPP như sau (Hình 2-28):
1 set ip to point to the first symbol of w$
9 if M[X,a] = XY1Y2…Yk then begin
10 pop X from the stack;
11 push Yk, Yk-1 ,…,Y1 onto the stack with Y on top
12 output the production X Y1Y2…Yk
Hình 2-29: Văn phạm tính giá trị biểu thức
Do văn phạm chứa luật sinh đệ quy trái, do đó trước tiên ta phải khử đệ quy trái cho văn phạm này Hình 2-30 là luật sinh văn phạm sau khi khử đệ quy trái
Trang 29Ứng với văn phạm này bảng phân tích sẽ như sau (Hình 2-31):
E’
T’
Hình 2-31: Bảng phân tích của văn phạm LL(1)
Thực hiện quá trình chạy từng bước giải thuật phân tích (Hình 2-32), ta ra được kết quả như Hình 2-32
T FT’
F id
…
Hình 2-32: Từng bước quá trình chạy giải thuật NPP
Do tiếp cận theo hướng từ trên xuống nên bộ phân tích LL có cấu trúc gần giống với ngôn ngữ tự nhiên và do đó giúp cho việc đặc tả văn phạm cũng như tìm lỗi dễ dàng hơn so với bộ phân tích LR
Trang 30Một số bộ sinh hỗ trợ văn phạm thuộc tính (attributed grammar) cho phép đặc tả các hành vi ngữ nghĩa trực tiếp vào văn phạm hoặc cung cấp các phương thức
hỗ trợ đặc tả hành vi ngữ nghĩa thông qua các lớp (class) sinh ra Tùy từng bộ sinh khác nhau mà cách thức hỗ trợ nhúng hành vi ngữ nghĩa trực tiếp vào văn phạm khác nhau Hình 2-33 minh họa cách đặc tả ngữ nghĩa của văn phạm Antlr
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 2-33: Hành vi ngữ nghĩa nhúng vào văn phạm
Như hình trên ta thấy, văn phạm Antlr cho phép đặc tả tham số (value) và giá
trị trả về cho luật sinh Các biến này có thể sử dụng để nhúng vào ngữ nghĩa bằng cách sử dụng ký hiệu $ Ngoài ra Antlr còn cho phép đặt tên cho các vế phải và sử dụng tên này như các biến
Trang 31CHƯƠNG 3 DEBUGGER
3.1 Giới thiệu debugger
Debugger là chương trình phần mềm giúp xác định tìm ra nguyên nhân tại sao một chương trình (debugee) hoạt động chưa chính xác Với sự trợ giúp của debugger, lập trình viên có thể theo vết sự thực thi của chương trình, tạm dừng chương trình và kiểm tra trạng thái hiện tại từ đó tìm ra được các lỗi
Debugger thực sự là một chương trình rất phức tạp Bản chất hoạt động bên trong của debugger đòi hỏi sự hiểu biết thuật toán và cấu trúc dữ liệu Một số debugger thậm chí đòi hỏi sự hiểu biết sâu về hệ điều hành (kernel debugger) Quá trình phát triển debugger là một quãng thời gian khá dài, có rất nhiểu loại debugger
ra đời và ngày càng được cải tiến nhằm hỗ trợ tối đa quá trình tìm lỗi
3.2 Phân loại debugger
Các chương trình phần mềm rất đa dạng Mỗi loại đòi hỏi kỹ thuật và debugger riêng Dưới đây là một số cách thức phân loại debugger:
Source-level (Symbolic) và Machine-level: mức source-level cho phép ta debug trên source code mà không cần quan tâm đến ngôn ngữ máy đã được biên dịch và ánh xạ bởi trình biên dịch
OS-kernel và Application-level: Kernel debugging là phần quan trong trong quá trình viết driver cho các thiết bị ngoại vi Thường mức này được hỗ trợ bởi hệ điều hành thông qua các công cụ và API Trong khi đó application-level là các ứng dụng giao tiếp với người dùng thông thường
3.3 Kiến trúc tổng quát debugger
Kiến trúc tổng quan của debugger như Hình 3-1 [9] Kiến trúc này bao gồm nhiều tầng khác nhau Vòng ngoài cùng là phần giao diện người dùng tương tác trực tiếp với debugger còn các vòng trong là các thành phân được hệ điều hành cung cấp trợ giúp cho quá trình debug Phần tiếp theo sẽ trình bày ý nghĩa tầng UI, Kernel và OS Debug API
Trang 32Hình 3-1: Kiến trúc điển hình của debugger
3.3.1 Tầng giao diện (User Interface)
3.3.1.1 Source View
Đây là view giữa vai trò quan trọng nhất của debugger Source View thể hiện trực quan quá trình debugger thực thi các lệnh của chương trình cũng như thiết lập các chức năng hỗ trợ cho quá trình debug (thiết lập điểm dừng…) Hình 3-2 ví dụ
về source view trong visual studio
Trang 33Hình 3-2: Source View trong Visual Studio và các chức năng sử dụng debug 3.3.1.2 Stack View
Stack view thể hiện quá trình gọi các phương thức (thủ tục) trong quá trình thực thi chương trình Stack này bao gồm tập các stack frame – lưu trữ toàn bộ trạng thái hiện tại của thủ tục được gọi Ở trạng thái dừng, ta có thể chọn bất kỳ frame nào
để xem xét trạng thái Hình 3-3 ví dụ về stack view trong visual studio
Hình 3-3: Stack View trong Visual Studio
Trang 343.3.1.3 Breakpoint View
Breakpoint view có chức năng liệt kê danh sách các breakpoint đã được thiết lập Qua đó ta có thể thấy được tổng quan về các breakpoint hiện tại đã được thiết lập trong chương trình Hình 3-4 ví dụ về breakpoint view trong visual studio
Hình 3-4: Breakpoint View trong Visual Studio
3.3.1.4 Control, Disassembly và Hardware Register View (CPU View)
Source view ánh xạ giữa mã nguồn và mã assembly.Tuy nhiên trong một số trường hợp, để hiểu rõ thực sự hoạt động bên trong của một số loại chương trình thì
mã assembly vẫn cần thiết CPU View có nhiệm vụ hiển thị mã assembly, giá trị thanh ghi… Hình 3-5 ví dụ về CPU view trong visual studio
Trang 35Hình 3-5: CPU View trong Visual Studio
3.3.1.5 Inspect View
Inspect view cho phép định nghĩa các biểu thức nhằm xem xét trạng thái, giá trị hiện tại các biến ứng với thời điểm tạm dừng hiện tại của chương trình (giá trị các biến tại stack frame nào đó…) Hình 3-6 ví dụ về inspect view trong visual studio
Hình 3-6: Inspect View trong Visual Studio
3.3.1.6 Variable View
Variable view hiển thị trực quan giá trị các biến cục bộ của stack frame hiện tại Hình 3-7 ví dụ về variable view trong visual studio