3.3. Bộ sinh trình phân tích cú pháp Bison
3.3.1. Cấu trúc
Tƣơng tự nhƣ Flex, Bison nhận đầu vào là một tệp bao gồm các đặc tả của một ngơn ngữ. Từ đó, Bison biên dịch ra bộ phân tích cú pháp bằng mã C để chạy cùng với chƣơng trình. Cấu trúc của tệp đặc tả ngơn ngữ gồm ba phần:
Phần khai báo % % Phần luật dịch %% Flex Compiler C Compiler flex.out File mơ tả nguồn flex.l flex.yy.c Chƣơng trình nguồn flex.yy.c flex.out chuỗi token
Các thủ tục
1. Phần khai báo: Gồm các khai báo C thông thƣờng (biến, hàm, cấu trúc,
định nghĩa...), đƣợc giới hạn trong %{ và %} nhƣ Flex, khai báo các token và các thuộc tính kết hợp với token (nếu có).
2. Phần luật dịch: Mỗi luật dịch là một luật sinh kết hợp với hành vi ngữ
nghĩa. Mỗi luật sinh có dạng:
<vế trái> <alt1> | <alt2> | ....| <altn> đƣợc mô tả trong Bison nhƣ sau:
<vế trái> : <alt1> {hành vi ngữ nghĩa 1} | <alt2> {hành vi ngữ nghĩa 2}
...
| <altn> {hành vi ngữ nghĩa n} ;
Trong luật sinh, các ký tự nằm trong cặp dấu nháy đơn ' ' là một ký hiệu kết thúc, một chuỗi chữ cái và chữ số không nằm trong cặp dấu nháy đơn và không đƣợc khai báo là token sẽ là ký hiệu chƣa kết thúc.
Hành vi ngữ nghĩa của Bison là một chuỗi các lệnh đƣợc viết bằng mã C mã C với:
$$: giá trị thuộc tính kết hợp với ký hiệu chƣa kết thúc bên vế trái của luật sinh.
$n: giá trị thuộc tính kết hợp với ký hiệu văn phạm thứ n (ký hiệu kết thúc hoặc chƣa kết thúc) của vế phải.
3.3.2. Quy trình vận hành
Hình 3.2. Tạo một chƣơng trình dịch input / output với Bison
Một chƣơng trình dịch có thể đƣợc xây dựng nhờ Bison bằng phƣơng thức đƣợc minh họa trong hình 3.2 trên. Trƣớc tiên, cần chuẩn bị một tập tin, chẳng hạn là translate.y, chứa một đặc tả Bison của chƣơng trình dịch. Lệnh bison translate.y sẽ biến đổi tập tin translate.y thành một chƣơng trình C có tên là y.tab.C. Bằng cách dịch y.tab.C cùng với thƣ viện ly nhờ lệnh cc y.tab.C - ly chúng ta thu đƣợc một chƣơng trình đối tƣợng a.out thực hiện quá trình dịch đƣợc đặc tả bởi chƣơng trình Bison ban đầu. Nếu cần thêm các thủ tục khác, chúng có thể đƣợc biên dịch hoặc đƣợc tải vào y.tab.C giống nhƣ mọi chƣơng trình C khác.
a.out Bison Compiler a.out translate.y y.tab.c input y.tab.c c C Compiler output
Chƣơng 4 - XÂY DỰNG CHƢƠNG TRÌNH DỊCH CHO NGƠN NGỮ MINIPAS
Trong chƣơng này, luận văn sẽ định nghĩa một ngơn ngữ tựa Pascal có tên gọi là Minipas và sau đó sử dụng các cơng cụ đã đƣợc giới thiệu trong chƣơng ba để xây dựng bộ phân tích từ vựng và bộ phân tích cú pháp cho ngơn ngữ
Minipas.
4.1. Yêu cầu
Minipas đƣợc xây dựng dựa trên ngơn ngữ Pascal. Mặc dù cịn đơn giản hơn Pascal rất nhiều nhƣng Minipas đáp ứng đƣợc các yêu cầu sau:
- Đơn giản, gọn nhẹ, dễ học.
- Đủ dùng để diễn tả một số thuật toán đơn giản cho ngƣời mới làm quen với ngơn ngữ lập trình.
4.2. Ngơn ngữ Minipas 4.2.1. Giới thiệu sơ lƣợc 4.2.1. Giới thiệu sơ lƣợc
Ngôn ngữ xây dựng đƣợc đặt tên là Minipas có một số đặc điểm:
- Có năm kiểu dữ liệu đơn giản: nguyên (integer), thực (double), logic (bool), kí tự (char), chuỗi ký tự (string). Hỗ trợ các phép toán số học - logic cơ bản trên ba kiểu đầu tiên. Kiểu char chỉ hỗ trợ phép gán và so sánh.
- Có kiểu cấu trúc là kiểu mảng một chiều với chỉ số nguyên, kiểu phần tử là kiểu đơn giản.
- Có ba cấu trúc điều khiển: tuần tự, rẽ nhánh (if… then… else) và vịng lặp (for…to…do, while.... do).
- Có kiểu chƣơng trình con là hàm. Chƣơng trình con có thể có tối đa là ba tham số.
- Có hai lệnh nhập dữ liệu từ bàn phím và xuất dữ liệu ra màn hình. - Cho phép hai dạng chú thích: chú thích khối và chú thích trên dịng. - Ngơn ngữ Minipas có phân biệt chữ hoa và chữ thƣờng.
Ngôn ngữ Minipas nhƣ vậy là tƣơng đối đủ cho ngƣời mới bắt đầu làm quen với việc học lập trình. Một số chi tiết phức tạp của ngơn ngữ Pascal (con
trỏ, kiểu dữ liệu cấu trúc, unit, đồ hoạ, tham biến...) đã đƣợc lƣợc bỏ giúp
Minipas đơn giản hơn.
4.2.2. Hƣớng dẫn sử dụng ngôn ngữ Minipas 4.2.2.1. Bảng chữ cái
Bảng chữ cái của Minipas là bảng kí tự ASCII.
4.2.2.2. Từ khố
Từ khóa là các từ dành riêng của ngôn ngữ Minipas, mỗi từ khóa có một cơng dụng riêng biệt. Minipas khơng cho phép đặt tên biến, tên hằng, tên chƣơng trình con… trùng với tên của từ khóa. Minipas có các từ khoá sau:
var const function endfunction begin
if then else endif for
do endfor to return end
while endwhile write read wait
Bảng 4.1. Bảng các token của ngôn ngữ Minipas 4.2.2.3. Tên 4.2.2.3. Tên
Tên trong Minipas đƣợc đặt theo quy tắc sau: tên bắt đầu bằng một chữ cái hoặc dấu gạch dƣới, sau đó có thể là chữ cái, chữ số và dấu gạch dƣới. Tên phân
biệt chữ hoa, chữ thƣờng. Độ dài tối đa cho tên là mƣời kí tự (nếu dài hơn sẽ bị cắt bỏ phần cuối).
4.2.2.4. Giá trị
Giá trị trong Minipas có năm loại:
- Giá trị nguyên: đƣợc viết dƣới dạng thập phân, cho phép có dấu hoặc khơng dấu, tối đa chín chữ số.
- Giá trị thực: đƣợc viết dƣới dạng thập phân, cho phép có dấu hoặc khơng dấu, tối đa chín chữ số.
- Giá trị logic: có hai giá trị là true (đúng) và false (sai).
- Giá trị kí tự: một ký tự đƣợc viết giống nhƣ trong Pascal, bắt dầu và kết thúc bằng cặp dấu „‟, ở bên trong là một kí tự.
- Giá trị xâu: xâu đƣợc viết giống nhƣ trong Pascal, một xâu đƣợc viết bên trong cặp dấu “ “, độ dài xâu có hơn một kí tự.
4.2.2.5. Hằng
Hằng là đại lƣợng có giá trị khơng đổi. Trong Minipas có hỗ trợ năm kiểu hằng ứng với năm kiểu giá trị ở trên.
Khai báo hằng trong Minipas nhƣ sau:
const
kiểu dữ liệu tên hằng := giá trị;
4.2.2.6. Khai báo biến
Biến là đại lƣợng có giá trị thay đổi. Trong Minipas, có bao nhiêu kiểu dữ liệu thì có bấy nhiêu kiểu biến, biến đƣợc khai báo nhƣ sau:
var
Khai báo biến kiểu mảng nhƣ sau:
var
array kiểu dữ liệu tên mảng[số phần tử];
4.2.2.7. Biểu thức
Biểu thức trong Minipas dùng để thể hiện một cơng thức tốn học. Biểu thức gồm tốn hạng và các phép tốn. Tốn hạng có thể là biến, hằng, hàm. Các phép tốn gồm:
- Các phép toán số học: +, - , *, /, % (chia lấy kết quả là phần dư) - Các phép toán quan hệ: <, > , =, != (khác), >=, <=
- Các phép toán logic: & (và), | (hoặc), ! (phủ định).
4.2.2.8. Cấu trúc chƣơng trình
<các khai báo> begin
<các câu lệnh>
end.
Trong phần <các khai báo> thì thứ tự của các khai báo nhƣ sau: 1. Khai báo biến
2. Khai báo hằng số
3. Khai báo chƣơng trình con
4.2.2.9. Các câu lệnh
Câu lệnh trong Minipas bắt buộc kết thúc bằng dấu „ ; „, kể cả câu lệnh đứng trƣớc từ khóa end kết thúc chƣơng trình. Khơng cho phép câu lệnh rỗng. Có các loại câu lệnh sau trong Minipas:
Câu lệnh gán
<biến> := <biểu thức>;
<biến mảng> [<chỉ số>] := <biểu thức>;
Câu lệnh nhập giá trị cho biến
read <biến 1>,…,<biến n>;
Câu lệnh in ra giá trị của biểu thức
write <biểu thức>; Câu lệnh rẽ nhánh Dạng 1: if <biểu thức> then <câu lệnh> endif; Dạng 2:
if <biểu thức> then <câu lệnh 1>
else <câu lệnh 2>
endif;
Câu lệnh lặp với số bƣớc lặp xác định
for <biến>:=<giá trị đầu> to <giá trị cuối> do
<câu lệnh>
Câu lệnh lặp với số bƣớc lặp không xác định
while <biểu thức> do
<câu lệnh>
endwhile;
4.2.2.10. Chƣơng trình con
Chƣơng trình con trong Minipas chỉ bao gồm hàm. Hàm có thể có tối đa ba tham số hình thức.
Khai báo hàm nhƣ sau:
function Tên_hàm(danh sách tham số hình thức);
<các khai báo cục bộ>
begin
<các câu lệnh>
endfunction;
Khi gọi hàm, phải viết Tên_hàm kèm theo danh sách tham số thực sự. Các tham số thực sự phải giống về số lƣợng và tƣơng ứng về kiểu dữ liệu với các tham số hình thức.
4.2.2.11. Chú thích
Minipas cho phép chú thích gồm hai cách:
Đoạn văn bản chú thích đƣợc đặt sau dấu //, chú thích trên một dịng.
Đoạn văn bản chú thích đƣợc đặt giữa cặp /* và */, đặt trên số dòng tuỳ ý.
4.3. Xây dựng chƣơng trình dịch cho Minipas
4.3.1. Xây dựng trình phân tích từ vựng cho Minipas
Bảng sau liệt kê danh sách các token của ngôn ngữ Minipas và biểu thức chính quy xác định các token đó:
Token Biểu thức chính quy
CHAR \'.\'
STRING \".*\"
DIGIT [0-9]+|\-[0-9]+
DOUBLE [0-9]+\.[0-9]*|\-[0-9]+\.[0-9]*
BOOLEAN true | false
IDENTIFIER [a-zA-Z_][a-zA-Z0-9_]* BEGIN begin END end LET var CONST const READ read WRITE write WRITELINE writeline IF if THEN then ELSE else FOR for TO to DO do ENDFOR endfor WHILE while ENDWHILE endwhile NUMBER_VAL {DIGIT} NUMBERD_VAL {DOUBLE}
CHR_VAL {CHAR} STR_VAL {STRING} FUNCTION function RETURN return ENDFUNCTION endfunction WAIT wait
ARRAY_I "array integer"
ARRAY_B "array bol"
ARRAY_C "array char"
ARRAY_D "array double"
ARRAY_S "array string"
ASSGNOP ":="
comment "//"
comment "/*"
whitespace [ \t\n]+
Bảng 4.1. Biểu thức chính quy đặc tả token 4.3.2. Xây dựng trình phân tích cú pháp cho Minipas
Văn phạm phi ngữ cảnh sinh ngơn ngữ Minipas:
// Luật sinh sinh chƣơng trình
program var declarations
constant const_declarations func_decls BEGIN commands END ‘.’
// Các luật sinh khai báo identifier
var | LET
declarations | declarations declaration
declaration INTEGER id_seq_int IDENTIFIER ‘;’
| CHAR id_seq_chr IDENTIFIER ‘;’
| DOUBLE id_seq_dou IDENTIFIER ‘;’
| STRING id_seq_str IDENTIFIER ‘;’
| BOOLEAN id_seq_bol IDENTIFIER ‘;’
| ARRAY_I IDENTIFIER '[' NUMBER_VAL ']'‘;’ | ARRAY_D IDENTIFIER '[' NUMBER_VAL ']'‘;’ | ARRAY_C IDENTIFIER '[' NUMBER_VAL ']'‘;’ | ARRAY_B IDENTIFIER '[' NUMBER_VAL ']'‘;’
id_seq_int | id_seq_int IDENTIFIER‘,’
id_seq_chr | id_seq_chr IDENTIFIER‘,’
id_seq_dou | id_seq_dou IDENTIFIER’,’
id_seq_bol | id_seq_bol IDENTIFIER‘,’
id_seq_str | id_seq_str IDENTIFIER‘,’
// Các luật sinh khai báo hằng
constant | CONST
const_declarations | const_declarations const_declaration
const_declaration INTEGER IDENTIFIER ASSGNOP NUMBER_VAL‘;’
| CHAR IDENTIFIER ASSGNOP CHR_VAL ‘;’
| DOUBLE IDENTIFIER ASSGNOP NUMBERD_VAL ‘;’ | STRING IDENTIFIER ASSGNOP STR_VAL ‘;’ | BOOLEAN IDENTIFIER ASSGNOP NUMBERB_VAL‘;’ // Các luật sinh khai báo hàm
func_decls | func_decl
func_decl FUNCTION IDENTIFIER '(' func_var_decls ')'
func_declarations IN
commands ENDFUNCTION ‘;’
func_var_decls | func_var_decls func_var_decl
func_var_decl var_seq INTEGER IDENTIFIER
| var_seq CHAR IDENTIFIER
| var_seq DOUBLE IDENTIFIER
| var_seq TRING IDENTIFIER
| var_seq BOOLEAN IDENTIFIER
var_seq | var_seq INTEGER IDENTIFIER ‘,’
| var_seq CHAR IDENTIFIER ‘,’
| var_seq DOUBLE IDENTIFIER ‘,’
| var_seq STRING IDENTIFIER ‘,’
| var_seq BOOLEAN IDENTIFIER ‘,’
func_declarations | func_declarations func_declaration
func_declaration INTEGER fun_id_seq_int IDENTIFIER ‘;’
| CHAR fun_id_seq_chr IDENTIFIER ‘;’
| DOUBLE fun_id_seq_dou IDENTIFIER ‘;’
| STRING fun_id_seq_str IDENTIFIER ‘;’
| BOOLEAN fun_id_seq_bol IDENTIFIER ‘;’
| ARRAY_I IDENTIFIER '[' NUMBER_VAL ']' ‘;’
| ARRAY_D IDENTIFIER '[' NUMBER_VAL ']' ‘;’
| ARRAY_C IDENTIFIER '[' NUMBER_VAL ']' ‘;’
| ARRAY_B IDENTIFIER '[' NUMBER_VAL ']' ‘;’
fun_id_seq_int | fun_id_seq_int IDENTIFIER ‘,’
fun_id_seq_chr | fun_id_seq_chr IDENTIFIER ‘,’
fun_id_seq_dou | fun_id_seq_dou IDENTIFIER ‘,’
// Các luật sinh câu lệnh
commands | commands command ‘;’
command READ IDENTIFIER read_exp
| READ IDENTIFIER '[' index ']' read_array_exp
| WRITE IDENTIFIER
| WRITE STR_VAL | WRITE exp
| WRITELINE | RETURN exp
| IDENTIFIER ASSGNOP exp
| IDENTIFIER '[' index ']' ASSGNOP exp | WAIT | IF exp THEN commands else_exp ENDIF | WHILE exp DO commands ENDWHILE
|FOR IDENTIFIER ASSGNOP exp TO exp DO commands ENDFOR
read_exp | read_exp ‘,’ IDENTIFIER
read_array_exp | read_array_exp ‘,’ IDENTIFIER
else_exp | ELSE commands
// Luật sinh biểu thức
exp NUMBER_VAL | NUMBERD_VAL | STR_VAL | CHR_VAL
| NUMBERB_VAL | IDENTIFIER | IDENTIFIER '(' values ')' | exp '<' exp | exp '=' exp
| exp '!''=' exp | exp '>''=' exp | exp '<''=' exp | exp '>' exp | exp '+' exp | exp '-' exp | exp '*' exp | exp '/' exp | exp '&' exp | exp '|' exp | exp '%' exp | '!' exp
| arr_exp | '(' exp ')'
arr_exp IDENTIFIER '[' index ']'
values | exp value_seq
index NUMBER_VAL | IDENTIFIER
4.3.3. Xử lý ngữ nghĩa cho ngôn ngữ Minipas
Khi bộ phân tích cú pháp nhận dạng ra một luật sinh (khai báo, biểu thức, câu lệnh...) thì nó sẽ gọi đến một chƣơng trình con xử lý ngữ nghĩa tƣơng ứng nằm ở phía bên phải luật sinh đó.
Xử lý ngữ nghĩa cho khai báo identifier
install( char *symname , char *scope, enum type_code type, int length);
{
symrec *s;
s = getsym (sym_name, scope);
if (s == 0) {s = putsym(sym_name, scope, type, length);} else
{ errors++;
printf( "%s ", sym_name );
yyerror("is already defined");
} }
Hàm install(char *symname, char *scope, enum type_code
type, int length) dùng để kiểm tra cài đặt một ký hiệu có tên là symname,
phạm vi là scope, độ dài là length đã có trong bảng ký hiệu hay chƣa. Nếu chƣa có thì sẽ lƣu ký hiệu này vào trong bảng ký hiệu, ngƣợc lại thì sẽ thơng báo là ký hiệu đã tồn tại trong bảng ký hiệu.
Hàm getsym(sym_name, scope) dùng để truy xuất một ký hiệu có tên là sym_name và phạm vi là scope trong bảng ký hiệu.
Hàm putsym(sym_name, scope, type, length) dùng để lƣu một ký
hiệu có tên là sym_name, phạm vi là scope, kiểu dữ liệu là type và độ dài là length vào trong bảng ký hiệu.
Xử lý ngữ nghĩa cho việc sinh biểu thức
exp : NUMBER_VAL { gen_code( LD_INT, $1 ); }
| NUMBERD_VAL { gen_code_double( LD_DOU, $1 );}
| STR_VAL { gen_code_string( LD_STR, $1);}
| CHR_VAL { gen_code_char ( LD_CHR, $1);}
| NUMBERB_VAL { gen_code_boolean ( LD_BOL, $1);}
| IDENTIFIER { context_check( LD_VAR, $1,
function_name);}
| exp '<' exp { gen_code( LT, 0 ); }
| exp '=' exp { gen_code( EQ, 0 ); }
| exp '!''=' exp { gen_code( NEQ, 0 );}
| exp '>''=' exp { gen_code( GTEQ, 0 );}
| exp '<''=' exp { gen_code( LTEQ, 0 ); }
| exp '>' exp { gen_code( GT, 0 ); }
| exp '+' exp { gen_code( ADD, 0 ); }
| exp '-' exp { gen_code( SUB, 0 ); }
| exp '*' exp { gen_code( MULT, 0 ); }
| exp '/' exp { gen_code( DIV, 0 ); }
| exp '&' exp { gen_code( AND, 0 ); }
| exp '|' exp { gen_code( OR, 0 ); }
| exp '%' exp { gen_code( MOD, 0 );}
| '!' exp { gen_code( NOT, 0 );}
| '(' exp ')'
Thủ tục gen_code(enum code_ops operation, int arg) dùng để sinh mã trung gian cho mỗi ký hiệu phép toán với tham số thứ nhất là tên của
mã trung gian và tham số thứ hai là giá trị thuộc tính của ký hiệu bên phải của luật sinh.
Xử lý ngữ nghĩa sinh câu lệnh nhập, xuất dữ liệu
context_check ( enum code_ops operation, char *sym_name, char *scope )
{
symrec *identifier;
identifier = getsym(sym_name, scope); if ( identifier == 0 )
{
identifier = getsym(sym_name, "global");
if ( identifier == 0 )
{
errors++;
printf( "%s", sym_name );
yyerror(" is an undeclared identifier");
}
}
if (identifier != 0) {
if (identifier->type==ARR_D && operation==INT_ARR_STORE)
gen_code_variable( DOU_ARR_STORE, identifier);
else
if (identifier->type==ARR_B && operation==INT_ARR_STORE)
gen_code_variable( BOL_ARR_STORE, identifier);
else
if (identifier->type==ARR_C && operation==INT_ARR_STORE)
gen_code_variable( CHR_ARR_STORE, identifier);
if (identifier->type==ARR_D && operation == READ_INT_ARR)
gen_code_variable( READ_DOU_ARR, identifier);
else
if (identifier->type==ARR_B && operation == READ_INT_ARR)
gen_code_variable( READ_BOL_ARR, identifier);
else
if (identifier->type==ARR_C && operation == READ_INT_ARR)
gen_code_variable( READ_CHR_ARR, identifier);
else
gen_code_variable( operation, identifier);
} }
Hàm context_check(enum code_ops operation, char *sym_name,
char *scope) sẽ thực hiện kiểm tra một ký hiệu có tên là sym_name và phạm vi
là scope đã đƣợc khởi tạo hay chƣa sau đó sinh ra mã trung gian cho ký hiệu đó có tên là operation cho vào trong ngăn xếp chứa mã trung gian thông qua hàm
gen_code_variable(operation, identifier). Xử lý ngữ nghĩa sinh câu lệnh if ...then....else
command: IF exp
{
if_var = (struct lbs *) newlblrec();
if_var->for_jmp_false = reserve_loc(); } THEN commands { if_var->for_goto = reserve_loc(); } else_exp
ENDIF
{
back_patch( if_var->for_goto, GOTO, gen_label());
}
Trƣớc tiên do sau token IF là một exp vì vậy chƣơng trình sẽ tìm kiếm cấu trúc của exp trƣớc rồi thực hiện hành vi ngữ nghĩa của exp đó (xem phần hành
vi ngữ nghĩa của biểu thức).
Sau khi nhận dạng exp thì sẽ thực hiện hành vi ngữ nghĩa { if_var =
(struct lbs *) newlblrec(); if_var->for_jmp_false =
reserve_loc()}. lbs là một cấu trúc dành riêng cho câu lệnh if, while, for.
Riêng đối với câu lệnh if nó có nhiệm vụ lƣu lại vị trí ngăn xếp mã trung gian sau exp của IF (lưu vào biến for_jmp_false) và vị trí ngăn xếp mã trung gian trƣớc khối lệnh ELSE (lưu vào biến for_goto). Cách dùng lbs trong câu lệnh if nhƣ sau. Nếu giá trị của điều kiện exp là sai thì sẽ nhảy đến vị trí sau token ELSE cịn nếu là đúng thì sau khi thực hiện command trong if thì sẽ nhảy đến vị trí kết thúc câu lệnh if. Muốn thực hiện hành động này ta dùng hàm back_patch ( int addr, enum code_ops operation, int arg ). Hàm này có nhiệm
vụ lƣu lại tại một địa chỉ addr sẽ thực hiện một mã trung gian là operation với tham số để nhảy của mã trung gian là arg. Nhƣ vậy nếu sử dụng hàm