2.4. Phân tích ngữ nghĩa
2.4.1. Định nghĩa ngữ nghĩa trực tiếp trong văn phạm
Để bổ sung ngữ nghĩa vào văn phạm ta dùng một văn phạm phi ngữ cảnh, trong đó mỗi ký hiệu văn phạm kết hợp với một tập các thuộc tính. Mỗi luật sinh kết hợp với một tập các luật ngữ nghĩa. Việc đánh giá các luật ngữ nghĩa đƣợc sử
dụng để thực hiện một cơng việc nào đó nhƣ tạo ra mã trung gian, lƣu thơng tin vào bảng ký hiệu, xuất các thơng báo lỗi.
Thuộc tính của văn phạm là phần dữ liệu đƣợc liên kết với những ký hiệu kết thúc hoặc ký hiệu khơng kết thúc. Thuộc tính có thể lƣu bất cứ thông tin nào nhƣ số, chuỗi, con trỏ, hoặc một cấu trúc nào đó do ngƣời dùng tự định nghĩa.
Nếu A là một ký hiệu và a là một trong những thuộc tính của A thì ta sử dụng A.a để biểu diễn giá trị của a tại một nút A trên cây phân tích cú pháp. Có hai loại thuộc tính:
Thuộc tính tổng hợp: là thuộc tính mà giá trị của nó tại mỗi nút trên cây phân tích cú pháp đƣợc tính từ giá trị thuộc tính tại các nút con của nó.
Thuộc tính kế thừa: là thuộc tính mà giá trị của nó đƣợc xác định từ các giá trị thuộc tính của các nút cha hoặc anh em của nó.
Cây cú pháp có giá trị thuộc tính tại mỗi nút đƣợc gọi là cây chú thích.
Ví dụ: Ngữ nghĩa trong văn phạm sinh biểu thức đơn giản
Luật sinh Luật ngữ nghĩa
L En Print( E.val)
E E1 + T E.val := E1.val + T.val
E1 T E1.val := T.val
T T1 * F T.val := T1.val * F.val
T1 F T1.val := F.val
F (E) F.val := E.val
F digit F.val := digit.lexval
trong đó các ký hiệu E, T, F có một thuộc tính tổng hợp có giá trị nguyên tên là val. Token digit có một thuộc tính tổng hợp lexval, và n là ký hiệu newline. Với biểu thức 3 * 5 + 4n có cây chú thích nhƣ hình 2.4.
Hình 2.4. Cây chú thích cho biểu thức 3 * 5 + 4n
2.4.2. Lƣợc đồ dịch
Là một văn phạm phi ngữ cảnh trong đó các thuộc tính đƣợc kết hợp với các ký hiệu văn phạm và các hành vi ngữ nghĩa nằm trong cặp dấu { } đƣợc chèn vào bên phải của luật sinh.
Ví dụ: Lƣợc đồ dịch của một văn phạm
T E
E 1 {E.val :=E1.val + T.val}
T E
E 1 {E.val :=E1.val - T.val}
T E1 {E1.val := T.val} ) (E T {T.val := E.val} L n E.val = 19 + E.val = 15 T.val = 4 * T.val = 15 T.val = 3 F.val = 5 F.val = 4 F.val = 3 digit.lexval = 4 digit.lexval = 5 digit.lexval = 3
Chƣơng 3 - CÁC CÔNG CỤ HỖ TRỢ XÂY DỰNG CHƢƠNG TRÌNH DỊCH
3.1. Giới thiệu
Trên thực tế, đã có rất nhiều cơng cụ có khả năng sinh ra bộ phân tích từ vựng và bộ phân tích cú pháp. Một trong những bộ cổ điển nhất là lex và yacc – đƣợc phát minh tại Bell Lab trong thập niên 1970.
Yacc [6] (Yet Another Compiler Compiler) – cơng trình của Stephen C. Johnson đƣợc ra đời sớm hơn, có nhiệm vụ sinh ra trình phân tích cú pháp.
Trong khi đó Mike Lesk và Eric Schmidt đã thiết kế và phát triển Lex [6]– bộ sinh trình phân tích từ vựng – để hỗ trợ yacc trong việc xác định các token từ chuỗi nhập.
Flex4 và Bison5 chính là phiên bản cải tiến của lex và yacc, có khả năng phân tích trên bộ văn phạm rộng hơn, quản lý bộ nhớ tốt hơn và đƣợc sử dụng trên nhiều nền tảng.
3.2. Bộ sinh trình phân tích từ vựng Flex 3.2.1. Cấu trúc 3.2.1. Cấu trúc
Flex nhận đầu vào là một tệp bao gồm các đặc tả của các token. Cấu trúc của một chƣơng trình đƣợc viết bằng ngơn ngữ Flex gồm ba phần:
Phần khai báo
%%
Phần quy tắc dịch %%
Các hàm hỗ trợ
1. Phần khai báo chủ yếu là các phần định nghĩa các biến, hàm mà ta sẽ
đƣa trực tiếp vào chƣơng trình (được viết bằng mã C). Phần mã C đƣợc giới hạn giữa ký hiệu “%{“ và “%}”. Flex sẽ copy tồn bộ những gì giữa ký hiệu “%{“ và “%}” trực tiếp vào tệp Lexer.c sau khi chạy Flex.
2. 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 đặc tả cho các token, {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 flex các đoạn chƣơng trình này đƣợc viết bằng ngơn ngữ C nhƣng nói chung có thể viết bằng bất kỳ ngôn ngữ nào.
3. Các hàm hỗ trợ cho q trình phân tích từ vựng đƣợc viết bằng mã C
và sẽ đƣợc đƣa trực tiếp vào chƣơng trình.
3.2.2. Quy trình vận hành
Việc tạo ra bộ phân tích từ vựng đƣợc thực hiện trong ba bƣớc. Bƣớc đầu là tạo ra chƣơng trình flex.l trong ngơn ngữ Flex. Sau đó chƣơng trình
chuẩn. Flex dùng bảng để nhận dạng trị từ vựng. Các hành vi có liên quan đến các biểu thức chính quy trong flex.l là các chƣơng trình con trong ngơn ngữ C và chúng đƣợc chuyển thẳng sang chƣơng trình flex-yy.c. Cuối cùng lex-yy.c sẽ đƣợc chƣơng trình dịch C dịch và sinh ra chƣơng trình đối tƣợng a.out. Nó chính là bộ phân tích từ vựng, có khả năng chuyển các dịng nhập thành các token của ngơn ngữ, đƣợc xác định bởi các biểu thức chính quy, đã đƣợc đặc tả trong ngơn ngữ Flex.
Hình 3.1. Q trình phân tích từ vựng 3.3. Bộ sinh trình phân tích cú pháp Bison 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ừ khố 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 toá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