Chương trình dịch hay trình biên dịch 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 (ngôn ngữ nguồn) sang ngôn ngữ khác (ngôn ngữ đích) tương đương.
Trang 1TRƯỜNG ĐẠI HỌC HỒNG ĐỨC KHOA CÔNG NGHỆ THÔNG TIN VÀ TRUYỀN THÔNG
CHƯƠNG TRÌNH DỊCH
ĐỀ TÀI: TÌM HIỂU VỀ ĐẶC TẢ LEX
Giảng viên hướng dẫn:
Nguyễn Đình Định Sinh viên thực hiện:
Nguyễn Hữu Nhân MSV: 1861030017
Thanh Hoá, tháng 10 năm 2020
Trang 2MỤC LỤC
MỤC LỤC 2
LỜI MỞ ĐẦU 3
I GIỚI THIỆU VỀ CHƯƠNG TRÌNH DỊCH 4
1.1 Định nghĩa 4
1.2 Phân loại chương trình dịch 4
II GIỚI THIỆU VỀ LEX 5
2.1 Định nghĩa 5
2.2 Quy trình hoạt động của Lex 5
2.3 Cấu trúc của các chương trình Lex 6
III SỬ DỤNG LEX 6
3.1 Khai báo 7
3.2 Quy tắc dịch 7
3.3 Các chức năng phụ trợ 8
3.4 Các biến yyvariables 9
3.4.1 yyin 9
3.4.2 yytext 10
3.4.3 yyleng 11
3.5 Các yyfunctions 11
3.5.1 yylex 11
3.5.2 yywrap 13
3.6 Quy tắc định vị 14
3.7 Khớp mẫu bằng LEX 15
KẾT LUẬN 18
TÀI LIỆU THAM KHẢO 19
2
Trang 3LỜI MỞ ĐẦU Chương trình dịch hay trình biên dịch 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 (ngôn ngữ nguồn) sang ngôn ngữ khác (ngôn ngữ đích) tương đương
Hầu hết các trình biên dịch sẽ chuyển dịch mã nguồn viết trong một ngôn ngữ cấp cao thành mã đối tượng hay ngôn ngữ máy mà có thể được thi hành trực tiếp bởi một máy tính hay bởi một máy ảo Dù vậy, việc chuyển dịch từ một ngôn ngữ cấp thấp sang một ngôn ngữ cấp cao hơn cũng có thể xảy ra; quá trình này thường được hiểu như
là bộ biên dịch ngược nếu nó có thể tái tạo lại một chương trình trong ngôn ngữ cấp cao Cũng tồn tại các trình biên dịch chuyển đổi
từ ngôn ngữ cao này sang ngôn ngữ cao khác, hay là chuyển đổi sang một ngôn ngữ mà nó cần để tiếp tục xử lý về sau; những trình biên dịch như vậy được biết như là bộ biên dịch phân tầng
Các loại trình biên dịch cho kết quả là mã đối tượng thì một cách
cơ bản bao gồm mã máy tăng cường thêm các thông tin về tên vị trí của các ngõ vào và các gọi ngoài (đến các hàm mà không có sẵn bên trong của nó) Một tập hợp của các tập tin đối tượng, mà không hẳn được cung cấp từ cùng một trình biên dịch, thì vẫn có thể được liên kết với nhau để tạo nên các chương trình khả thi cuối cùng cho một người dùng Dĩ nhiên, để làm được như vậy thì các tập tin đối tượng
đó phải được thiết kế chung nhau về dạng thức Ví dụ của kiểu tập đối tượng này là các tập có đuôi là.obj có thể dùng chung giữa ASM, C/C++, Fortran hay các tập tin DLL trong các kiến trúc Windows dùng chung được cho nhiều ngôn ngữ
Qua quá trình học tập cũng như tìm hiểu cùng với sự hướng dẫn của thầy Nguyễn Đình Định, em đã chọn đề tài "Tìm hiểu về đặc tả Lex" để nghiên cứu Trong khuôn khổ bài báo cáo này, em xin trình bày cơ bản về Lex bao gồm cấu trúc của một chương trình Lex, cách
sử dụng Lex và một số lưu ý
Em cũng xin chân thành cảm ơn thầy Nguyễn Đình Định đã hướng dẫn và giúp
đỡ em để em của thể hoàn thành tốt đề tài này Mặc dù đã rất cố gắng nhưng trong quá trình thực hiện đề tài không thể tránh khỏi những sai sót, rất mong quý thầy cô cùng các bạn sinh viên có thể bỏ qua và góp ý để đề tài của em được hoàn thiện hơn
Em xin chân thành cảm ơn!
Trang 5I GIỚI THIỆU VỀ CHƯƠNG TRÌNH DỊCH
I.1 Định nghĩa
Chương trình dịch hay còn gọi là trình biên dịch, đơn giản là một chương trình làm nhiệm vụ đọc một chương trình nguồn hay đoạn chương trình nguồn viết bằng một ngôn ngữ (gọi là ngôn ngữ nguồn và thường là các ngôn ngữ lập trình bậc cao) rồi dịch nó thành một chương trình đích tương đương ở một ngôn ngữ khác (gọi là ngôn ngữ đích và đa số các trường hợp thì nó là ngôn ngữ máy) Một phần quan trọng trong quá trình dịch là ghi nhận lại các lỗi có trong chương trình nguồn để thông báo lại cho người viết chương trình
I.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
Chương trình đích (ngôn ngữ máy)
Chương trình dịch
Chương trình
nguồn (ngôn ngữ
bậc cao)
Lỗi
Trang 6đượ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ày và thực hiện từng câu lệnh Ngôn ngữ trung gian được gọi là ngôn ngữ của một máy ảo, chương trình thông dịch thực hiện ngôn ngữ này gọi là máy ảo
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 (LL – Left to right, leftmost) LR(1) (LR – letf to right, right most) [1]
II GIỚI THIỆU VỀ LEX
II.1 Định nghĩa
Lex là một chương trình tạo ra máy phân tích từ vựng Nó được
sử dụng với trình tạo phân tích cú pháp YACC Trình phân tích từ vựng là một chương trình chuyển dòng đầu vào thành một chuỗi các
mã thông báo Nó đọc luồng đầu vào và tạo ra mã nguồn dưới dạng đầu ra thông qua việc triển khai trình phân tích từ vựng trong chương trình C
II.2 Quy trình hoạt động của Lex
Đầu tiên, bộ phân tích từ vựng tạo ra một chương trình lex.1 bằng ngôn ngữ Lex Sau đó, trình biên dịch Lex chạy chương trình lex.1 và tạo ra một chương trình C lex.yy.c Cuối cùng trình biên dịch
C chạy chương trình lex.yy.c và tạo ra một chương trình đối tượng a.out
a.out là trình phân tích từ vựng chuyển đổi luồng đầu vào thành một chuỗi mã thông báo
Lex Source
tokens
6
Trang 7yylval là một biến toàn cục được chia sẻ bởi trình phân tích và
phân tích cú pháp từ vựng để trả về tên và giá trị thuộc tính của mã thông báo
Giá trị thuộc tính có thể là mã số, con trỏ tới bảng ký hiệu hoặc không có gì
Một công cụ khác để tạo máy phân tích từ vựng là Flex
II.3 Cấu trúc của các chương trình Lex
Một chương trình lex bao gồm 3 thành phần:
Khai báo
%%
Quy tắc dịch
%%
Các thủ tục phụ
Phần khai báo bao gồm khai báo biến, hằng và các định nghĩa chính
quy
Phần quy tắc dịch cho các lệnh có dạng:
p1 {action 1 }
p2 {action 2 }
pn {action n }
Trong đó pi là các biểu thức chính quy, action i là đoạn chương
trình mô tả hành động của bộ phân tích từ vựng thực hiện khi pi tương ứng phù hợp với trị từ vựng Trong lex các đoạn chương trình này được viết bằng C nhưng nói chung có thể viết bằng bất cứ ngôn ngữ nào
III SỬ DỤNG LEX
Một chương trình lex bao gồm 3 thành phần: khai báo (Declarations), quy tắc dịch (Rules) và các chức năng phụ trợ (Auxiliary functions)
Khai báo
Trang 8Quy tắc dịch
%%
Các chức năng phụ trợ
III.1 Khai báo
Phần khai báo gồm hai phần: Phần khai báo phụ và phần định nghĩa thông thường
Các khai báo phụ được Lex sao chép như vậy vào tệp lex.yy.c đầu ra Mã C này bao gồm các hướng dẫn cho trình biên dịch C và không được xử lý bởi công cụ Lex Các khai báo phụ (là tuỳ chọn) được viết bằng ngôn ngữ C và được bao gồm trong ‘% {’ và ‘%}’ Nó thường được sử dụng để khai báo các hàm, bao gồm các tệp tiêu đề hoặc xác định các biến và hằng số toàn cục
Lex cho phép sử dụng viết tắt và phần mở rộng cho các biểu thức chính quy cho các định nghĩa thông thường Định nghĩa chính quy trong Lex có dạng: D R trong đó
D là ký hiệu đại diện cho biểu thức chính quy R
Ví dụ:
/* Phần khai báo bắt đầu tại đây */
/* Các khai báo bổ trợ bắt đầu tại đây */
%{
#include<stdio.h>
int global_variable;
%}
/* Kết thúc khai báo phụ trợ & Định nghĩa thông thường bắt đầu tại đây */
number [0-9] + //Định nghĩa thông thường
op [- | + | * | / | ^ | =] // Định nghĩa thông thường
/* Phần khai báo báo kết thúc tại đây */
%%
/* Quy tắc */
%%
/* Các chức năng phụ trợ */
III.2 Quy tắc dịch
8
Trang 9Các quy tắc trong một chương trình Lex gồm hai phần:
- Mẫu được kết hợp
- Hành động tương ứng được thực hiện
Ví dụ:
/ * Khai báo */
%%
{number} {printf(“number”);}
{op} {printf(“operator”);}
%%
/* Các chức năng phụ trợ*/
Mẫu được so khớp được chỉ định dưới dạng một biểu thức chính quy
Đầu vào/Đầu ra mẫu cho ví dụ trên:
Input: 234
Output: Number
Input: *
Output: operator
Input: 2+3
Output: number operator number
Lex lấy các biểu thức chính quy của các ký hiệu ‘number’ và ‘op’ từ phần khai báo và tạo mã thành một hàm yylex() trong tệp lex.yy.c Hàm này kiểm tra luồng đầu vào cho kết quả khớp đầu tiên với một trong các mẫu được chỉ định và thực thi mã trong phần hành động tương ứng với mẫu
III.3 Các chức năng phụ trợ
Lex tạo mã C cho các quy tắc được chỉ định trong phần Quy tắc và đặt mã này vào một hàm duy nhất được gọi là yylex() Ngoài mã được tạo Lex này, lập trình viên
có thể muốn thêm mã của riêng mình vào tệp lex.yy.c Phần chức năng phụ trợ cho phép người lập trình đạt được điều này
Ví dụ:
/* Khai báo */
%%
/* Quy tắc */
Trang 10int main()
{
yylex();
return 1;
}
Các khai báo phụ và các hàm phụ được sao chép như vậy sang tệp lex.yy.c
Sau khi mã được viết, lex.yy.c có thể được tạo bằng lệnh lex “filename.l” và được biên dịch thành gcc lex.yy.c
III.4 Các biến yyvariables
Các biến sau do Lex cung cấp để hỗ trợ lập trình viên thiết kế các bộ phân tích từ vựng phức tạp Các biến này có thể truy cập vào chương trình Lex và sẽ tự động được khai báo bởi Lex trong lex.yy.c:
- yyin
- yytext
- yyleng
III.4.1 yyin
yyin là một biến có kiểu file * và trỏ đến tệp đầu vào, yyin được xác định bởi Lex
tự động Nếu người lập trình gán vào cho yyin trong phần chức năng phụ trợ, thì yyin được đặt để trỏ đến tệp đó Nếu không, Lex chỉ định yyin cho stdin (đầu vào bảng điều khiển)
Ví dụ:
/* Khai báo */
%%
/* Quy tắc */
%%
main (int argc, char * argv[])
{
if (argc > 1)
{
FILE *fp = fopen(argv[1], “r”);
if(fp)
yyin = fp;
10
Trang 11yylex();
return 1;
}
III.4.2 yytext
yytext thuộc loại char* và nó chứa lexeme hiện được tìm thấy Một lexeme là một chuỗi các ký tự trong dòng đầu vào phù hợp với một số mô hình trong phần quy tắc (Trên thực tế, nó là chuỗi khớp đầu tiên trong dữ liệu đầu vào từ vị trí được trỏ tới bởi yyin) Mỗi lần gọi hàm yylex() dẫn đến yytext mang một con trỏ tới lexeme được tìm thấy trong luồng đầu vào của yylex() Giá trị của yytext sẽ được ghi đè sau lần gọi yylex() tiếp theo
Ví dụ:
%option noyywrap
%{
#include<stdlib.h>
#include<stdio.h>
%}
number [0-9]+
%%
{numberr} { printf(“Found: %d\n”,atoi(yytext));}
%%
int main()
{
yylex();
yeturn 1;
}
Trong ví dụ trên, nếu tìm thấy lexeme cho mẫu được xác định bằng số thì hành động tương ứng sẽ được thực thi
Đầu vào/đầu ra mẫu:
Input: 25
Output: Tìm thấy 25
Trong trường hợp này khi yylex() được gọi, đầu vào được đọc từ vị trí được cung cấp bởi yyin và một chuỗi “25” được tìm thấy dưới dạng khớp với “number” Vị trí của chuỗi này trong bộ nhớ được trỏ tới bởi yytext Hành động tương ứng trong quy tắc trên sử dụng một hàm có sẵn atoi() để chuyển đổi chuỗi “25” (kiểu char*) thành số
Trang 12nguyên 25 (kiểu int) và sau đó in kết quả ra màn hình Lưu ý rằng tệp tiêu đề “stdlib.h” được gọi trong phần khai báo phụ trợ để gọi atoi() trong phần hành động của quy tắc Lưu ý:
- Lexeme do Lex tìm thấy được lưu trữ trong một số bộ nhớ do Lex cấp phát có thể được truy cập thông qua yytext con trỏ ký tự
- %option noyywrap được sử dụng để thông báo cho trình biên dịch rằng hàm yywrap() chưa được xác định
III.4.3 yyleng
yyleng là một biến kiểu int và nó lưu trữ độ dài của lexeme và yytext trỏ tới
Ví dụ:
/* Các khai báo */
%%
/* Quy tắc */
%%
{number} printf (“Số chữ số = %d”, yyleng);
Đầu vào/đầu ra mẫu:
Input: 1234
Output: Số chữ số = 4
III.5 Các yyfunctions
yyfunctions gồm có 2 thành phần là:
- yylex()
- yywarap()
III.5.1 yylex
yylex() là một hàm của kiểu trả về int Lex tự động địng nghĩa yylex() trong lex.yy.c nhưng không gọi nó Người lập trình phải gọi yylex() trong phần “Các hàm phụ của chương trình Lex” Lex tạo mã cho định nghĩa của yylex() theo các quy tắc được chỉ định trong phần “Quy tắc”
Lưu ý: yylex() đó không nhất thiết phải được gọi trong phần Chức năng phụ của chương trình lex khi được sử dụng với YACC
/* Khai báo */
%%
{number} { return atoi(yytext);}
12
Trang 13int main()
{
int num = yylex();
printf(“Found: %d”,num);
return 1;
}
Đầu vào/đầu ra mẫu:
Input: 42
Output: Found: 42
Khi yylex() được gọi, nó đọc đầu vào như được trỏ tới bởi yyin và quét qua đầu vào để tìm một mẫu phù hợp Khi đầu vào hoặc một phần của đầu vào khớp với một trong các mẫu đã cho, yylex() thực hiện hành động tương ứng được liên kết với mẫu như được chỉ định trong phần Quy tắc Trong ví dụ trên, vì không có định nghĩa rõ ràng
về yyin, đầu vào được lấy từ bảng điều khiển Nếu khớp được tìm thầy trong đầu vào cho số mẫu, yylex() thực hiện hành động tương ứng, tức là trả về atoi(yytext) Kết quả là yylex() trả về đã khớp Giá trị được trả về bởi yylex() được lưu trữ trong biến num Giá trị được lưu trữ trong biến này sau đó được in ra màn hình bằng printf()
yylex () tiếp tục quét đầu vào cho đến khi một trong các hành động tương ứng với một mẫu đã khớp thực hiện câu lệnh trả về hoặc cho đến khi gặp kết thúc đầu vào Trong trường hợp của ví dụ trên, yylex() kết thúc ngay sau khi thực hiện quy tắc vì nó bao gồm một câu lệnh trả về
Lưu ý rằng nếu không có hành động nào trong phần Quy tắc thực thi câu lệnh trả về, yylex() sẽ tiếp tục quét các mẫu phù hợp hơn trong tệp đầu vào cho đến cuối tệp
Trong trường hợp nhập bảng điều khiển, yylex() sẽ đợi thêm đầu vào thông qua bảng điều khiển Người dùng sẽ phải nhập ctrl + d trong terminal để kết thúc yylex() Nếu yylex() được gọi nhiều lần, nó chỉ bắt đầu quét từ vị trí trong tệp đầu vào mà nó đã trả về trong lần gọi trước
III.5.2 yywrap
Trang 14Lex tuyên bố chức năng yywrap() trả lại kiểu int trong file lex.yy.c Lex không cung cấp bất kỳ định nghĩa nào cho yywrap(), yylex () thực hiện cuộc gọi đến yywrap() khi nó gặp phần cuối của đầu vào Nếu yywrap() trả về 0 (cho biết sai) thì yylex() giả
sử có nhiều dữ liệu đầu vào hơn và nó tiếp tục quét từ vị trí mà yyin chỉ đến Nếu yywrap() trả về giá trị khác 0 (cho biết true), yylex() kết thúc quá trình quét và trả về 0 (tức là “kết thúc”) Nếu lập trình viên muốn quét nhiều hơn một tệp đầu vào bằng trình phân tích từ vựng
đã tạo, thì có thể thực hiện đơn giản bằng cách đặt yyin thành tệp đầu vào mới trong yywrap() và trả về 0
Vì Lex không định nghĩa yywrap() trong tệp lex.yy.c nhưng thực hiện lệnh gọi nó trong yylex(), lập trình viên phải định nghĩa nó trong phần Các hàm bổ trợ hoặc cung cấp %option noyywrap trong phần khai báo Tùy chọn này loại bỏ lệnh gọi đến yywrap() trong tệp lex.yy.c Lưu ý rằng, bắt buộc phải xác định yywrap() hoặc chỉ ra sự vắng mặt bằng cách sử dụng tính năng tùy chọn% Nếu không, Lex
sẽ gắn cờ lỗi
Ví dụ:
%{
#include<stdio.h>
char *file1;
%}
%%
[0-9]+ printf(“number”);
%%
int yywrap()
{
FILE *newfile_pointer;
char *file2 = “input_file_2.l”;
Newfile_pointer = fopen(“intput_file_2.l”, “r”);
if(strcmp(file,file2)!=0)
{
file1 = file2;
yyin = newfile_pointer;
return 0;
}
else
14