1. Trang chủ
  2. » Luận Văn - Báo Cáo

tiểu luận môn Nguyên lý các ngôn ngữ lập trình. Đề tài tìm hiểu bộ công cụ Flex, Bison, ứng dụng trong phân tích từ Vựng và phân tích cú pháp của một ngôn ngữ nào đó

12 1,8K 11

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 12
Dung lượng 57,82 KB

Nội dung

 Trong khi đó Mike Lesk và Eric Schmidt đã thiết kế và phát triển lex – bộ phát 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.. Nhiệm vụ chủ

Trang 1

TRƯỜNG ĐẠI HỌC BÁCH KHOA HÀ NỘI VIỆN ĐÀO TẠO SAU ĐẠI HỌC

=====o0o=====

BÀI TẬP LỚN MÔN: NGUYÊN LÝ NGÔN NGỮ LẬP TRÌNH

Giáo viên giảng dạy: TS Nguyễn Hữu Đức

Đề tài: Tìm hiểu bộ công cụ Flex, Bison, ứng dụng trong phân tích từ

Vựng và phân tích cú pháp của một ngôn ngữ nào đó

NHÓM: 10 Lớp: CH2012B

Sinh viên thực hiện:

Nguyễn Thành Đô Nguyễn Xuân Trường Trần Văn Trung Nguyễn Thị Thùy Dương

Hà Nội, tháng 12/2012

Trang 2

I GIỚI THIỆU

Ta sẽ phải tốn rất nhiều thời gian để tìm hiểu và xây dựng một trình trình biên dịch hoàn chỉnh một cách thủ công Trên thực tế, đã có rất nhiều công cụ có khả năng phát 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 (Yet Another Compiler Compiler) – công trình của Stephen

C Johnson được ra đời sớm hơn, có nhiệm vụ phát 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 – bộ phát 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.

FLEX[5] và BISON[5, 7] 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

Phân tích từ vựng là giai đoạn đầu tiên của mọi trình biên dịch Nhiệm vụ chủ yếu của nó là đọc các ký hiệu nhập rồi tạo ra một chuỗi các token được sử dụng bởi bộ phân tích cú pháp Do đó, bộ phân tích từ vựng được thiết kế như một thủ tục được gọi bởi bộ phân tích cú pháp, trả về một token khi được gọi Trên thực tế, ta thường dùng biểu thức chính quy để mô tả các token

Ví dụ một văn bản nguồn trong thực tế : “tôi ăn cơm”, khi qua trình phân tích từ vựng sẽ thành : danh từ - động từ - danh từ Như vậy, trình biên dịch không quan tâm ngữ nghĩa của 3 chữ “tôi”, “ăn”, “cơm” theo cách con người (sử dụng tiếng Việt) nghĩ mà nó

sẽ hiểu là xuất hiện có thứ tự 3 đơn vị từ vựng (gọi là token) Một số loại token chính trong

trình biên dịch C :

Trang 3

Bảng 1 Mô tả một số token của ngôn ngữ C

Ví dụ 1: Với câu nhập

COUNT = COUNT + 1;

Khi qua trình phân tích từ vựng, kết quả nhận được sẽ là chuỗi các token sau :

IDENTIFIER ASSIGN_OP IDENTIFIER ADD_OP CONSTANT PUNC

Trình phân tích từ vựng giao tiếp trực tiếp với trình phân tích cú pháp qua một giao thức đơn giản nhưng được định nghĩa khá đầy đủ Giao thức đó được thể hiện qua hình

vẽ :

Hình 1 Giao thức liên hệ của bộ phân tích từ vựng.

CONSTANT Các hằng số với các kiểu dữ liệu (số nguyên, số thực,

…) STRING_VALUE Chuỗi hằng (ví dụ “hello world”)

IDENTIFIER Các danh biểu trong chương trình, bao gồm tên biến,

tên hàm (bắt đầu là chữ cái, theo sau là chữ hoặc số)

Trang 4

Qua đó, trình phân tích từ vựng sẽ nhận những định nghĩa về token từ trình phân tích cú pháp và sẽ trả về token phù hợp

Chính sự giao tiếp đơn giản này đã khiến cho bộ phân tích từ vựng tỏ ra khá độc

lập so với các phần còn lại của trình biên dịch Theo giao thức đã trình bày ở Hình , trình

phân tích từ vựng chỉ liên hệ trực tiếp với trình phân tích cú pháp trong vai trò như một thủ tục Do đó, một sự thay đổi dù lớn hay nhỏ ở trình phân tích từ vựng cũng không gây ảnh hưởng đến hoạt động chung của trình biên dịch

Tuy nhiên, có một số trường hợp mà trình phân tích từ vựng, trình phân tích cú pháp và bảng danh biểu cần có sự liên hệ mật thiết với nhau để xử lý

Ví dụ 2: Khai báo kiễu dữ liệu do người dùng tự định nghĩa

typedef int Dummy;

Hoặc

struct Dummy {

int first, second;

};

Từ cấu trúc khai báo trở đi, Dummy khi được sử dụng sẽ không được hiểu là một danh biểu nữa mà phải là một từ dành riêng, một kiểu dữ liệu do người dùng định nghĩa Vì vậy,

bộ phân tích từ vựng sẽ trả về một token đặc biệt khác để bộ phân tích cú pháp có thể nhận dạng Dummy như một kiểu dữ liệu

Một công việc nữa của bộ phân tích từ vựng là xác định loại hằng số và chuyển sang dạng lưu trữ thích hợp (từ dạng dữ liệu nhập là chuỗi)

Ví dụ 3: Với các biễu diễn:

0b1010 : biểu diễn nhị phân của số 10

012 : biểu diễn bát phân của số 10

10 : biểu diễn thập phân của số 10 0x10 : biểu diễn thập lục phân của số 10 thì ta đều phải chuyển sang dạng lưu trữ với giá trị hằng số thích hợp

Trang 5

Trình phân tích từ vựng Trình phân tích cú pháp Phần còn lại của chuyến trước

Bảng danh biểu

cây phân tích cú pháp token

yêu cầu token

III PHÂN TÍCH CÚ PHÁP

Mỗi ngôn ngữ lập trình đều có các quy tắc diễn tả cấu trúc cú pháp của các chương trình có định dạng đúng Các cấu trúc cú pháp này được mô tả bởi văn phạm phi ngữ cảnh (Context Free Gramma – CFG)

Trình phân tích cú pháp (parser) nhận chuỗi các token từ trình phân tích từ vựng

(Hình 2 ), và xác định rằng chuỗi này có hợp lệ hay không bằng cách tạo ra cây phân tích

cú pháp từ văn phạm của ngôn ngữ nguồn

Có 2 phương pháp chính được dùng để phân tích cú pháp theo tập văn phạm đã được định nghĩa:

 Phân tích cú pháp từ trên xuống (Top-Down Parsing) hay phân tích cú pháp dự đoán (Predictive Parser): ta sẽ cố gắng tìm kiếm một chuỗi dẫn xuất trái nhất cho chuỗi nhập bằng cách xây dựng cây phân tích cú pháp bắt đầu từ nút gốc.

 Phân tích cú pháp từ dưới lên (Bottom-Up Parsing): bộ phân tích cú pháp sẽ bắt đầu từ chuỗi token và cố gắng tìm kiếm luật sinh thích hợp để có thể dẫn về ký tự bắt đầu của văn phạm

IV Bộ công cụ Flex và Bison

Hình 2 Vị trí của trình phân tích cú pháp trong chuyến trước của trình biên dịch

Trang 6

1 Bộ phát sinh trình phân tích từ vựng FLEX

a Cấu trúc.

Cấu trúc của một chương trình FLEX :

Phần khai báo (định nghĩa, khai báo biến, prototype)

%%

Biểu thức chính quy do người dùng định nghĩa nhằm xác định các token.

%%

Các hàm hỗ trợ

Phần khai báo chủ yếu là các phần định nghĩa cái biến, hàm và khởi tạo mà ta sẽ đưa trực tiếp vào chương trình (mã C) Phần mã C được giới hạn trong giữa ký hiệu “%{“ và “%}” FLEX sẽ copy toàn bộ những gì giữa ký hiệu “%{“ và “%}” trực tiếp vào file Lexer.c sau khi chạy FLEX

Phần thứ hai là các biểu thức chính quy do người dùng định nghĩa hay các luật nhằm xác định loại token

từ những ký tự nhập vào Những luật trong phần này luôn bao gồm 2 phần: biểu thức chính quy và phương thức thực hiện khi chuỗi nhập phù hợp với mẫu được định nghĩa trong biểu thức chính quy Phương thức thực hiện được xác định nhờ cặp ngoặc “{“ và “}”

Ví dụ 1: Biểu thức chính quy và phương thức thực hiện khi xác định số thập lục.

0[xX][a-fA-F0-9]+ { return CONSTANT; }

Phần cuối cùng là các hàm hỗ trợ cho quá 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

b Quy trình vận hành.

FLEX sẽ nhận định nghĩa các token của người dùng bằng biểu thức chính quy, từ đó

sẽ biên dịch (bằng trình biên dịch của FLEX) sang ngôn ngữ C để có thể chạy cùng chương trình

Trang 7

FLEX Compiler

C Compiler

lex.out

File mô tả nguồn

lex.l

lex.yy.c

Chương trình nguồn

lex.yy.c

lex.out

chuỗi token

Sau khi được phát sinh từ tập biểu thức chính quy, bộ phân tích từ vựng sẽ được định nghĩa như một hàm trả ra token tiếp theo (TOKEN yylex();) Khi yylex() được gọi, nó sẽ phân tích chương trình nguồn thành từng đơn vị từ vựng và tìm biểu thức chính quy phù hợp với những đơn vị từ vựng đó Khi có sự đối sánh xuất hiện, yylex() sẽ thực hiện phần mã C tương ứng với biểu thức chính quy được chọn

c Một số hàm hỗ trợ.

int main() :

Thường được ngầm hiểu là không có và người dùng nên tự định nghĩa hàm main riêng cho mình char *yytext

Chuỗi giữ lexeme hiện tại, kết thúc bằng ký tự ‘\0’

int yyleng

Chiều dài của chuỗi yytext

int yylineno

Dòng chứa lexeme mà ta đang xét Nếu lexeme có nhiều dòng thì yylineno chính là dòng cuối cùng của lexeme

int input()

Đọc, trả về ký tự nhập tiếp theo ký tự cuối cùng trong lexeme Ký tự này được thêm vào cuối của lexeme (yytext) yyleng sẽ tự tăng lên 1 đơn vị Hàm trả về 0 khi kết thúc file

void unput(int c)

Hình 1 Quá trình phân tích từ vựng

Trang 8

unput đưa ký tự c ngược trở lại chuỗi nhập Khi đó lexeme hiện tại sẽ giảm đi 1 ký tự, yyleng cũng

sẽ giảm 1 đơn vị Khi gọi input() tiếp theo thì ký tự c sẽ được trả về

void yyless (int n)

Tương tự như unput nhưng ở đây sẽ đưa n ký tự trở lại chuỗi nhập n không được vượt quá yyleng void yymore()

Biểu thức chính quy sẽ bỏ qua trường hợp đối sánh hiện tại mà tiếp tục xét tiếp Ứng dụng yymore trong trường hợp xác định kiểu dữ liệu chuỗi constant mà có chứa dấu nháy kép, như chuỗi : “string with

a \” in it” Khi đó, biểu thức chính quy và phương thức thực hiện sẽ là :

\”[^\”]\”

{

if (yytext[yyleng – 2] == ‘\\’)

yymore();

else

return STRING;

} Trong đó, biểu thức chính quy \”[^\”]\” nhận các chuỗi bắt đầu bằng ký tự “, kết thúc bằng ký tự “ nhưng chưa xét tới trường hợp chuỗi có thể chứa ký tự “ ở giữa như

“string with a \” in it” Rõ ràng đây là chuỗi hợp lệ và ta nhận dạng ký tự ” ở giữa bằng ký tự \ Do đó, khi yytext[yyleng – 2] == ‘\\’, tức là ký tự trước ký tự nháy kép

là ký tự \ thì ta không dừng lại mà tiếp tục xét chuỗi nhập cho đến khi gặp ký báo hiệu kết thúc chuỗi thực sự (yymore())

ECHO

Xuất lexeme hiện tại ra màn hình console (stdout)

2 Bộ phát sinh trình phân tích cú pháp BISON

a Cấu trúc.

Tương tự như FLEX, Bison nhận input là một file 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 file đặc tả ngôn ngữ cũng gồm 3 phần :

Trang 9

- Phần khai báo:

a.Khai báo C thông thường (biến, prototype hàm, cấu trúc, đĩnh nghĩa ), được giới hạn trong %{ và %} như FLEX.

b.Khai báo kiểu dữ liệu của các thuộc tính tổng hợp.

c.Khai báo các token và các thuộc tính kết hợp với token (nếu có).

d.Khai báo các thuộc tính kết hợp với các ký hiệu không kết thúc.

Ví dụ 1: Văn phạm thuộc tính của một số ký hiệu không kết thúc trong file input

của Bison.

%union {

struct value *val;

struct operand *op;

struct sym_link *lnk ;

}

%token <yychar> ID TYPE_NAME

%type <op> stmt_list stmt

%type <yyint> unary_op assignment_op

%type <sym> id Khi đó, các ký tự không kết thúc như stmt, stmt_list… sẽ có những thuộc tính như một biến struct operand*, các ký tự không kết thúc unary_op, assignment_op sẽ có thuộc tính là một biến kiểu liệu cơ sở int Ngoài ra, các token (ký tự kết thúc) như ID, TYPE_NAME cũng có thể được khai báo và kèm theo thuộc tính char[] để biểu diễn kiểu dữ liệu dạng chuỗi.

Trang 10

- Phần mô tả tập luật sinh hình thành nên ngôn ngữ:

Trong luật sinh, các ký tự nằm trong cặp dấu nháy đơn 'c' là một ký hiệu kết thúc 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 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.

- Các hàm hỗ trợ.

Ngôn ngữ chủ yếu để mô tả các luật hình thành nên parser là CFG (văn phạm phi ngữ cảnh) Và dạng tiêu chuẩn để mô tả một CFG

mà BISON áp dụng là BNF (Backus-Naur Form) [5]– từng được dùng để miêu tả ngôn ngữ Algol 60.

Ví dụ 3: Mô tả dạng BNF cho ngôn ngữ xây dựng biểu thức đơn giản

<s> ::= <e>

<e> ::= <e> ‘+’<t>

| <t>

<t> ::= <t> ‘*’ <f>

| <f>

<f> ::= ‘(‘ <e> ‘)’

Mỗi dòng là một luật sinh chỉ ra cách hình thành nên một nhánh của cây phân tích cú pháp Bison đã đơn giản hóa BNF để

dễ sử dụng hơn.

Ví dụ 2: Mô tả của Bison cho ngôn ngữ đưa ra ở Ví dụ 3

Trang 11

Mô tả tập luật sinh, luật ngữ nghĩa (*.y)

Bison

Bản mô tả các trạng thái (y.out)

Bộ phân tích cú pháp bằng mã C (yyout.c) Bản định nghĩa các token (yyout.h)

defines

Văn phạm thuộc tính trong Bison được thể hiện ở phần khai báo Ta có thể gán cho các ký hiệu kết thúc hoặc các ký hiệu không kết thúc các thuộc tính là các kiểu dữ liệu cơ

sở, hoặc ngay cả những cấu trúc tự định nghĩa

b Quy trình vận hành.

Bison nhận tập tin mô tả ngữ pháp (luật sinh, luật ngữ nghĩa) để qua đó phát sinh ra bộ phân tích cú pháp (viết bằng ngôn ngữ C) Bộ phân tích cú pháp này cũng sẽ được biên dịch chung với chương trình

và được sử dụng như một thủ tục (yyparse()).

Khi yyparse() được gọi, nó sẽ dựa vào trạng thái hiện tại và bảng ACTION-GOTO để chọn ra hành vi thích hợp, đồng thời, thực hiện

Hình 2 Quá trình phân tích cú pháp của BISON

Trang 12

luật ngữ nghĩa ứng với văn phạm sau khi rút gọn Ngoài ra, BISON cũng có thể tạo ra file định nghĩa các token (đã khai báo) được sử dụng chung trong trình phân tích từ vựng cũng như trình phân tích cú pháp.

Phương pháp phân tích dưới lên sử dụng một Stack để lưu trữ thông tin về cây con

đã được phân tích Chúng ta có thể mở rộng Stack này để lưu trữ giá trị thuộc tính tổng hợp Stack được cài đặt bởi một cặp mảng trạng thái và giá trị.

Ví dụ 5: Hoạt động của stack lưu trữ trạng thái và giá trị (val)

Trước khi stmt_list2 stmt được rút gọn thành stmt_list thì:

stack[top].val = stmt.op stack[top - 1].val = stmt_list2.op

Sau khi rút gọn, top sẽ giảm đi 1 đơn vị ( top = top – 1), và

stack[top].val = stmt_list1.op Với cách xử lý này, Bison sử dụng thuộc tính của các ký hiệu thông qua stack value Khi muốn sử dụng thuộc tính op của stmt_list2 ta chỉ cần gọi $1, tương tự gọi $2 đối với stmt.Khi biên dịch qua ngôn ngữ C, Bison sẽ đối xử với $1 như yyvsp[-1].op, $2 như yyvsp[0].op Sau khi rút gọn, con trỏ yyvsp sẽ được cập nhật

để đẩy stmt_list1.op vào stack và nằm trên đỉnh stack (yyvsp[0])

Ngày đăng: 17/04/2016, 21:22

TỪ KHÓA LIÊN QUAN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

w