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
back_patch trong câu lệnh if ta sẽ sử dụng nhƣ sau:
back_patch( if_var->for_jmp_false,JMP_FALSE,gen_label());
Tại địa chỉ ngăn xếp mã trung gian for_jmp_false sẽ thực hiện mã trung gian
thực hiện khi exp trả về giá trị 0. Vì vậy khi giá trị của điều kiện exp là sai (có
giá trị 0) thì sẽ thực hiện nhảy về vị trí ngăn xếp mã trung gian sau token ELSE.
back_patch( if_var->for_goto, GOTO, gen_label() ); Tại địa
chỉ ngăn xếp mã trung gian for_goto sẽ thực hiện mã trung gian là GOTO và
nhảy đến vị trí ngăn xếp là gen_label(), ở đây vị trí ngăn xếp gen_label() là vị trí sau token ENDIF. Vì vậy trƣờng hợp giá trị của điều kiện exp là đúng thì sẽ khơng thực hiện mã trung gian JMP_FALSE và tiếp tục thực hiện commands sau
token THEN. Sau khi thực hiện commands sau token THEN thì tại vị trí đấy là địa
chỉ ngăn xếp mã trung gian for_goto, nên sẽ thực hiện mã trung gian GOTO, thực hiện nhảy đến vị trí sau token ENDIF.
Xử lý ngữ nghĩa sinh câu lệnh while...do
command: WHILE { $1 = (struct lbs *) newlblrec(); $1->for_goto = gen_label(); } exp { $1->for_jmp_false = reserve_loc(); } DO commands ENDWHILE { gen_code(GOTO, $1->for_goto ); back_patch($1->for_jmp_false,JMP_FALSE, gen_label()); }
Khi nhận ra cấu trúc WHILE thì chƣơng trình sẽ thực hiện hành vi ngữ
nghĩa sau: { $1 = (struct lbs *) newlblrec(); $1->for_goto =
gen_label(); }. Tức là tạo ra một cấu trúc có tên là $1. Lƣu lại vị trí trong
ngăn xếp mã trung gian vào biến $1->for_goto để thực hiện nhảy cho vòng lặp
WHILE trong trƣờng hợp điều kiện là đúng.
Tiếp đến là thực hiện hành vi ngữ nghĩa của biểu thức exp. Sau khi thực hiện hành vi ngữ nghĩa của biểu thức exp thì chƣơng trình sẽ thực hiện tiếp
hành vi ngữ nghĩa: { $1->for_jmp_false = reserve_loc(); }. để lƣu địa chỉ ngăn xếp mã trung gian vào biến $1->for_jmp_false.
Tiếp đến chƣơng trình sẽ thực hiện hành vi ngữ nghĩa cho các câu lệnh trong commands nằm sau token DO.
Kết thúc của câu lệnh WHILE thì chƣơng trình sẽ thực hiện hành vi ngữ
nghĩa sinh mã trung gian GOTO với tham số $1->for_goto và mã trung gian
JMP_FALSE cho địa chỉ ngăn xếp mã trung gian $1->for_jmp_false. Với ý
nghĩa: khi gặp mã trung gian GOTO thì sẽ nhảy trở về địa chỉ là $1->for_goto.
Khi gặp mã trung gian JMP_FALSE tại địa chỉ ngăn xếp mã trung gian $1-
>for_jmp_false sẽ kiểm tra giá trị của exp có bằng 0 hay khơng. Nếu bằng 0
thì nhảy về địa chỉ ngăn xếp mã trung gian là nơi kết thúc lệnh WHILE.
Xử lý ngữ nghĩa sinh câu lệnh for...to...do
command: FOR IDENTIFIER ASSGNOP exp
{
context_check( STORE, $2, function_name );
$1 = (struct lbs *) newlblrec(); $1->for_goto = gen_label();
context_check( LD_VAR, $2, function_name ); } TO exp { gen_code( LTEQ, 0 ); $1->for_jmp_false = reserve_loc(); } DO commands { context_check(LD_VAR, $2, function_name); gen_code( LD_INT, 1 ); gen_code( ADD, 0 );
context_check( STORE, $2, function_name);
}
ENDFOR
{
gen_code( GOTO, $1->for_goto );
back_patch($1->for_jmp_false,JMP_FALSE,gen_label());
}
Về cơ bản nguyên lý của câu lệnh FOR giống với câu lệnh WHILE. Tuy
nhiên điểm khác quan trọng khi chƣơng trình thực hiện sinh mã trung gian là câu lệnh WHILE bản thân trong nó đã chứa đựng điều kiện của biểu thức exp để thực hiện lặp cho câu lệnh này, còn câu lệnh FOR thì phải sinh điều kiện thực hiện lặp cho nó và phải thực hiện việc tăng biến chạy thêm một giá trị sau mỗi vòng lặp.
Xử lý ngữ nghĩa sinh hàm
func_decls : | func_decls func_decl ; func_decl: FUNCTION IDENTIFIER
if (function_name != NULL) free(function_name); function_name = (char *) malloc (strlen($2)+1); function_name = $2;
install( function_name , function_name, FUN, gen_label()); install( function_name , "global", FUN, gen_label()); } '(' func_var_decls ')'
var
func_declarations BEGIN
{
gen_code( DATA, data_location() - 1 ); }
commands ENDFUNCTION ';' {
if (function_name != NULL) free(function_name); function_name = "main";
gen_code(END_CAL, 0); }
Khi xử lý với hàm trong Minipas, sẽ chia thành các bƣớc cơ bản sau: + Nhận biết khai báo hàm
+ Nhận biết gọi hàm
Ta sẽ tập trung vào việc gọi hàm. Cách nhận biết gọi hàm trong chƣơng trình nhƣ sau:
Chƣơng trình sẽ duyệt trong tệp parser.y để tìm ra cấu trúc gọi hàm thì sẽ thực hiện hành vi ngữ nghĩa của cấu trúc này nhƣ sau:
'('values')' {context_check(CAL, $1, "global");}
Trong trƣờng hợp nhận biết đƣợc một câu lệnh gọi hàm thì sẽ sinh ra một mã trung gian là bắt đầu gọi hàm để đánh dấu từ điểm bắt đầu gọi hàm đến mã trung gian gọi hàm là danh sách các giá trị truyền vào cho hàm đó. Ở hành vi ngữ nghĩa trên thì thể hiện của nó nhƣ sau:
{gen_code(BEGIN_CAL,0);}: sinh mã trung gian bắt đầu gọi hàm
'(' values ')': xử lý sinh mã trung gian cho các giá trị truyền vào hàm
context_check(CAL, $1, "global");: Kiểm tra hàm đã đƣợc khai báo
KẾT LUẬN
Sau quá trình nghiên cứu, luận văn đã đạt đƣợc những kết quả nhƣ sau: Tìm hiểu đƣợc tổng quan về chƣơng trình dịch gồm các bƣớc để thiết kế một chƣơng trình dịch. Đi sâu vào tìm hiểu ba bƣớc chính là phân tích từ vựng, phân tích cú pháp và phân tích ngữ nghĩa.
Tìm hiểu về hai thuật tốn để phân tích cú pháp là thuật toán Earley và