Tải bản đầy đủ (.docx) (21 trang)

Đề tài khoa học và công nghệ cấp cơ sở nghiên cứu ứng dụng lexyacc trong tự động phát sinh mã nguồn

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (2.42 MB, 21 trang )

ĐẠI HỌC ĐÀ NẴNG
TRƯỜNG CĐ CÔNG NGHỆ THÔNG TIN

BÁO CÁO TỔNG KẾT
ĐỀ TÀI KHOA HỌC VÀ CÔNG NGHỆ
CẤP CƠ SỞ

NGHIÊN CỨU ỨNG DỤNG LEX/YACC TRONG TỰ
ĐỘNG PHÁT SINH MÃ NGUỒN
Mã số: T2016-07-04

Chủ nhiệm đề tài: ThS. Dương Thị Mai Nga

Đà Nẵng, 12/

1


ĐẠI HỌC ĐÀ NẴNG
TRƯỜNG CĐ CÔNG NGHỆ THÔNG TIN

BÁO CÁO TỔNG KẾT

ĐỀ TÀI KHOA HỌC VÀ CÔNG NGHỆ
CẤP CƠ SỞ

NGHIÊN CỨU ỨNG DỤNG LEX/YACC TRONG TỰ
ĐỘNG PHÁT SINH MÃ NGUỒN
Mã số: T2016-07-04

Xác nhận của cơ quan chủ trì đề tài



Đà Nẵng, 12/

Chủ nhiệm đề tài


MỤC LỤC
MỞ ĐẦU...........................................................................................................8
CHƯƠNG 1: TỔNG QUAN VỀ TRÌNH BIÊN DỊCH.................................... 9
1.1.

KHÁI NIỆM TRÌNH BIÊN DỊCH.................................................................................... 9

1.2.

PHÂN TÍCH CHƯƠNG TRÌNH NGUỒN.....................................................................10

TỔNG KẾT CHƯƠNG 1........................................................................................................... 14

CHƯƠNG 2: CÔNG CỤ LEX/ YACC........................................................... 15
2.1. GIỚI THIỆU VỀ LEX/YACC................................................................................................ 15
2.2. LEX................................................................................................................................... 17
2.3. YACC................................................................................................................................21
2.4. CÀI ĐẶT CÁC ỨNG DỤNG................................................................................................25
TỔNG KẾT CHƯƠNG 2...........................................................................................................25

CHƯƠNG 3: XÂY DỰNG CÔNG CỤ SINH MÃ NGUỒN.........................26
3.1.

GIỚI THIỆU NGÔN NGỮ ĐẶC TẢ TTT.......................................................................26


3.1.1.

Cấu trúc cơ bản của một chương trình...............................................................26

3.1.2.

Các lệnh của ngơn ngữ TTT.................................................................................26

3.1.3.

Các thành phần khác...........................................................................................27

3.2.

XÂY DỰNG GIẢI PHÁP DỊCH NGÔN NGỮ TTT SANG NGƠN NGỮ C.......................29

3.3.

XÂY DỰNG TRÌNH BIÊN DỊCH VÀ THỬ NGHIỆM......................................................35

KẾT LUẬN VÀ KIẾN NGHỊ.........................................................................38
TÀI LIỆU THAM KHẢO...............................................................................39
PHỤ LỤC........................................................................................................40


DANH MỤC BẢNG BIỂU
Số hiệu bảng

Tên bảng


Trang

Bảng 2.1
Bảng 2.2
Bảng 2.3
Bảng 3.1

Mơ tả cách biểu diễn các biểu thức chính quy
Biểu diễn một số ví dụ của biểu thức chính quy
Danh sách các macro, biến được định nghĩa trước của lex
Thứ tự ưu tiên các phép toán
Bảng chuyển đổi từ vựng giữa ngôn ngữ TTT và ngôn ngữ

18
18
20
28

Bảng 3.2
Bảng 3.3
Bảng 3.4

C
Bảng chuyển đổi cú pháp giữa ngôn ngữ TTT và ngôn ngữ
C
Chuyển đổi các phép tốn CTT của ngơn ngữ TTT sang
ngôn ngữ C

29

31
33


DANH MỤC CÁC HÌNH
Số hiệu hình
Hình 1.1
Hình 1.2
Hình 1.3
Hình 1.4
Hình 2.1
Hình 2.2

Tên hình
Một trình biên dịch
Giao diện của bộ phân tích từ vựng trong trình biên dịch
Giao diện của bộ phân tích cú pháp trong trình biên dịch
Cây phân tích cú pháp cho câu lệnh:
position := initial + rate * 60
Ví dụ trình tự biên dịch đoạn mã nguồn a = b + c * d
Quy trình vận dụng và cách đặt tên với Lex, Yacc

Trang
9
11
12
13
15
16



DANH MỤC CÁC TỪ VIẾT TẮT
Từ viết tắt/
Thuật ngữ
TTT
CTT
BNF

Tiếng Anh
Task Tree Test
Concur Task Tree
Backus-Naur Form

Tiếng Việt
Kiểm thử cây nhiệm vụ
Cây nhiệm vụ tương tranh
Hình thức BNF


ĐẠI HỌC ĐÀ NẴNG

CỘNG HÒA XÃ HỘI CHỦ NGHĨA VIỆT NAM

TRƯỜNG CĐ CÔNG NGHỆ THÔNG TIN

Độc lập – Tự do – Hạnh phúc

THƠNG TIN KẾT QUẢ NGHIÊN CỨU
1. Thơng tin chung:
- Tên đề tài: Nghiên cứu ứng dụng Lex/Yacc trong tự động phát sinh mã nguồn

- Mã số: T2016-07-04
- Chủ nhiệm: Dương Thị Mai Nga
- Cơ quan chủ trì: Trường CĐ Công nghệ Thông tin – Đại học Đà Nẵng
- Thời gian thực hiện: từ 1/2016 đến 12/2016
2. Mục tiêu:
Áp dụng lý thuyết vào xây dựng công cụ ứng dụng vào việc phát sinh mã
nguồn sang ngơn ngữ lập trình C nhằm tạo điều kiện để tiếp tục nghiên cứu và xây
dựng việc tự động phát sinh ca kiểm thử từ ngơn ngữ mơ hình hóa.
3. Tính mới và sáng tạo:
Nghiên cứu và ứng dụng công cụ trong việc xây dựng trình biên dịch, giúp
quá trình này được nhanh hơn.
4. Tóm tắt kết quả nghiên cứu:
Đã nghiên cứu về trình biên dịch, các cơng cụ hỗ trợ xây dựng trình biên
dịch và xây dựng được trình biên dịch dựa trên những nghiên cứu đó.
5. Tên sản phẩm:
Cơng cụ chuyển mã nguồn từ ngôn ngữ đặc tả TTT sang ngôn ngữ C.
6. Hiệu quả, phương thức chuyển giao kết quả nghiên cứu và khả năng áp
dụng:
Có thể sử dụng nghiên cứu này và áp dụng vào quá trình tự động sinh ca
kiểm thử cho các ứng dụng tương tác được đặc tả bằng ngôn ngữ TTT.
Đà Nẵng, ngày 08 tháng 12 năm 2016
Cơ quan chủ trì

Chủ nhiệm đề tài


MỞ ĐẦU
Ngày nay song song với sự phát triển các thiết bị phần cứng là sự phát triển
các kĩ thuật phát triển phần mềm. Bài toán đặt ra là làm thế nào để lập trình nhanh,
hiệu quả và độ chính xác cao, một trong những kĩ thuật đảm bảo những yêu cầu trên

là kĩ thuật tự động phát sinh mã nguồn.
Phần mềm hiện nay được sử dụng rộng rãi trong đời sống, trong nhiều lĩnh
vực khoa học, kinh tế và xã hội. Vì vậy, việc xây dựng phần mềm đáp ứng mong
muốn của người sử dụng là đòi hỏi quan trọng. Đó là mong muốn về độ chính xác,
tính ổn định, tính tương thích và giá thành của các ứng dụng. Giá thành của ứng
dụng phụ thuộc vào chi phí trong quá trình phát triển, Làm thế nào để lập trình
nhanh và hiệu quả nhất cho một bài tốn ln là cơ sở để các phương pháp, kĩ thuật
và ngôn ngữ lập trình ra đời. Một trong những kĩ thuật ra đời sớm và phát triển là kĩ
thuật tự động phát sinh mã nguồn. Hiện nay có nhiều cơng cụ hỗ trợ phát sinh mã
nguồn như Lex/Yacc, Antlr…. Công cụ Lex/Yacc hỗ trợ tự động sinh ra mã nguồn
C hoặc Pascal, Antlr hỗ trợ việc tạo mã cho nhiều ngôn ngữ hơn bao gồm C++,
Java, Python, C#. Trong nghiên cứu của mình tác giả chọn cơng cụ Lex/Yacc và đề
xuất đề tài “Nghiên cứu ứng dụng Lex/Yacc trong tự động phát sinh mã nguồn”
nhằm rút ngắn thời gian sinh mã nguồn, dễ dàng trong việc bảo trì, từ đó góp phần
giảm giá thành sản phẩm.
Trong đề tài của mình, tác giả thực hiện nghiên cứu lý thuyết và áp dụng lý
thuyết vào xây dựng công cụ ứng dụng trong việc phát sinh mã nguồn trong ngơn
ngữ lập trình C nhằm tạo điều kiện để tiếp tục nghiên cứu và xây dựng việc tự động
phát sinh ca kiểm thử từ ngôn ngữ mơ hình hóa. Trong nghiên cứu của mình tác giả
thực hiện những cơng việc sau: tìm hiểu văn phạm phi ngữ cảnh và ngơn ngữ lập
trình C; tìm hiểu công cụ Lex/Yacc; xây dựng cơ chế dịch ngôn ngữ; xây dụng công
cụ sinh mã nguồn trong ngôn ngữ lập trình C; kiểm tra, thử nghiệm và đưa ra nhận
xét, kết quả đạt được; viết báo cáo tổng kết đề tài.


CHƯƠNG 1: TỔNG QUAN VỀ TRÌNH BIÊN DỊCH
Chương này tác giả trình bày về trình biên dịch, chức năng của trình biên dịch,
cấu trúc của trình biên dịch, mơi trường hoạt động của trình biên dịch.
1.1.


KHÁI NIỆM TRÌNH BIÊN DỊCH
Trình biên dịch, hay cịn gọi là chương trình dịch, đơn giản là một chương
trình làm nhiệm vụ đọc một chương trình được viết bằng một ngơn ngữ (gọi là ngơn
ngữ nguồn) rồi dịch nó thành một chương trình tương đương ở một ngơn ngữ khác
(gọi là ngơn ngữ đích). 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.
Chương trình nguồn
Trình biên dịch

Thơng báo lỗi

Chương trình đích
Hình 1.1. Một trình biên dịch
Chỉ nhìn thống qua, số lượng trình biên dịch dường như q nhiều. Có đến
hang ngàn ngơn ngữ nguồn, từ các ngơn ngữ lập trình truyền thống như Fortran và
C đến các ngôn ngữ chuyên dụng dành riêng cho từng lĩnh vực ứng dụng khác nhau.
Ngơn ngữ đích cũng đa dạng như thế; một ngơn ngữ đích có thể là một ngơn ngữ
lập trình khác, hoặc là một ngơn ngữ máy của một loại máy tính, từ máy tính PC
cho đến siêu máy tính. Trình biên dịch đơi khi cũng được phân thành các loại như
loại một lượt (single-pass), loại nhiều lượt (multi-pas), loại nạp và tiến hành (loadand-go), loại gỡ rối (debugging) hoặc loại tối ưu hóa (optimizing), tùy vào cách
thức chúng ta xây dựng hoặc tùy vào chức năng mà chúng thực hiện. Mặc dù tính
chất phức tạp, nhiệm vụ cơ bản của mọi trình biên dịch đều như nhau. Nhờ hiểu rõ
được nhiệm vụ này mà chúng ta có thể xây dựng trình biên dịch cho rất nhiều loại
ngơn ngữ nguồn và ngơn ngữ đích bằng cách sử dụng các kỹ thuật cơ bản giống
nhau.
Hiểu biết của chúng ta về cách thức tổ chức và viết các trình biên dịch đã tiến
rất xa kể từ khi trình biên dịch đầu tiên xuất hiện vào đầu thập niên 50. Trong suốt
thập niên 50 đó, trình biên dịch đã được xem là những chương trình cực kỳ khó viết.
Từ đó chúng ta đã dần khám phá ra các kỹ thuật mang tính hệ thống để xử lý nhiều
nhiệm vụ quan trọng xảy ra trong q trình biên dịch. Một số ngơn ngữ để cài đặt,



các mơi trường lập trình và các cơng cụ phần mềm cũng được phát triển. Với những
thành quả này, việc viết một trình biên dịch có chất lượng hiện đã dễ dàng hơn.
Cơng việc biên dịch có thể được chia thành hai phần: phân tích và tổng hợp.
Phần phân tích sẽ phân rã chương trình nguồn thành các phần cấu thành và tạo ra
một dạng biểu diễn trung gian cho chương trình nguồn. Phần tổng hợp sẽ xây dựng
ngơn ngữ đích từ dạng biểu diễn trung gian này.
1.2.

PHÂN TÍCH CHƯƠNG TRÌNH NGUỒN
Khi biên dịch, q trình phân tích bao gồm ba giai đoạn:
- Phân tích tuyến tính (linear analysis), trong đó các dịng ký tự tạo ra chương
trình nguồn sẽ được đọc từ trái sang phải và được nhóm lại thành các từ tố (thẻ từ token), đó là các chuỗi ký tự được hợp lại để tạo ra một nghĩa chung.
Phân tích cấu trúc phân cấp (hierarchical analysis), trong đó các ký tự hoặc
các thẻ từ được nhóm thành các nhóm lồng nhau theo kiểu phân cấp để tạo ra một
nghĩa chung.
Phân tích ngữ nghĩa (semantic analysis), trong đó thực hiện một số kiểm tra
để đảm bảo rằng các thành phần của chương trình kết lại với nhau một cách có
nghĩa.
Trong một trình biên dịch, phân tích tuyến tính được gọi là phân tích từ vựng
(lexical analysis) hay hành động quét nguyên liệu (scanning) 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ý tự vào từ văn bản
chương trình nguồn và phân tích đưa ra danh sách các từ tố cùng một số thơng tin
thuộc tính.
Đầu ra của bộ phân tích từ vựng là danh sách các từ tố và là đầu vào cho
phân tích cú pháp. Trên thực tế q trình phân tích cú pháp sẽ gọi lần lượt mỗi từ tố
từ bộ phân tích từ vựng để xử lý, chứ khơng gọi một lúc tồn bộ danh sách từ tố của
cả chương trình nguồn.
Khi nhận được yêu cầu lấy một từ tố tiếp theo từ bộ phân tích cú pháp, bộ

phân tích từ vựng sẽ đọc ký tự vào cho đến khi đưa ra được một từ tố.


u cầu lấy từ
tố tiếp theo
Chương trình
nguồn

Bộ phân tích từ vựng

Bộ phân tích cú pháp

Từ tố
Bảng ký hiệu
Hình 1.2. Giao diện của bộ phân tích từ vựng trong trình biên dịch
Q trình phân tích từ vựng gồm các giai đoạn sau:
- Xóa bỏ ký tự khơng có nghĩa (các chú thích, dịng trống, ký hiệu xuống
dịng, ký tự trống khơng cần thiết)
- Nhận dạng các ký hiệu: nhận dạng các từ tố.
- Số hóa các ký hiệu: do con số xử lý dễ dàng hơn các chuỗi ký
tự Trong phân tích từ vựng chúng ta quan tâm tới các khái niệm:
- Trị từ vựng (từ vị - lexeme): là một nhóm các ký tự kề nhau có thể tuân
theo một quy ước (mẫu hay luật) nào đó.
- Từ tố (token): là một thuật ngữ chỉ các từ vựng có cùng ý nghĩa cú pháp
(cùng một luật mô tả). Bộ phân tích cú pháp sẽ làm việc trên các từ tố. Một từ tố có
thể có các thuộc tính (các thơng tin kết hợp với từ tố đó), trong thực tế một từ tố sẽ
chứa một con trỏ trỏ đến một vị trí trên bảng ký hiệu có chứa thơng tin về nó.
Mẫu (luật mơ tả - patter): để bộ phân tích từ vựng nhận dạng được các từ tố thì
đối với mỗi từ tố chúng ta phải mô tả đặc điểm để xác định một từ vựng có thuộc từ
tố đó khơng, mơ tả đó được gọi là mẫu từ tố hay luật mơ tả.

Chẳng hạn khi phân tích từ vựng, các ký tự của câu lệnh gán
position := initial + rate * 60
sẽ được nhóm lại thành các từ tố sau:
1.Định danh position
2. Ký hiệu gán :=
3. Định danh initial
4. Dấu cộng (+)
5. Định danh rate
6. Dấu nhân (*)


7. Số 60
Các khoảng trống (blank) phân cách các ký tự của những thẻ từ này sẽ được
loại bỏ trong q trình phân tích từ vựng.
Phân tích cấu trúc phân cấp được gọi là phân tích cú pháp (syntax analysis
hay thường được gọi là parsing). 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. Bộ phân tích cú pháp có chức năng phân
tích cú pháp chương trình nguồn. Nhiệm vụ chủ yếu của nó là nhận các từ tố từ bộ
phân tích từ vựng và xác nhận rằng chuỗi này có thể được sinh ra từ văn phạm của
ngôn ngữ nguồn bằng cách tạo ra cây phân tích cú pháp cho chuỗi. Trên thực tế bộ
phân tích cú pháp cịn một số nhiệm vụ thu thập thông tin về từ tố vào bảng ký hiệu.
Bộ phân tích cú pháp cũng có cơ chế ghi nhận các lỗi cú pháp và có khả năng phục
hồi được các lỗi thường gặp để có thể tiếp tục xử lý phần còn lại của chuỗi đầu vào.

u cầu lấy
từ tố tiếp theo
Chương trình
nguồn


Cây
tích

phân


Bộ phân tích cú pháp

Bộ phân tích từ vựng

Từ tố
Bảng ký hiệu

Hình 1.3. Giao diện của bộ phân tích cú pháp trong trình biên dịch
Một số vấn đề cần quan tâm trong phân tích cú pháp:
- Văn phạm phi ngữ cảnh: để xác định cú pháp của một ngôn ngữ, người ta
dùng văn phạm phi ngữ cảnh CFG (Context Free Grammar) hay còn gọi là văn
phạm BNF (Backus Naur Form). Văn phạm phi ngữ cảnh bao gồm bốn thành phần:
o Một tập hợp các từ tố (token) – các ký hiệu kết thúc (terminal
symbols)
o Một tập hợp các ký hiệu chưa kết thúc (nonterminal symbols)
o Một tập hợp các luật sinh (productions)


o Một trong các ký hiệu chưa kết thúc được dùng làm ký hiệu bắt đầu
của văn phạm.
-

Cây phân tích cú pháp: minh họa ký hiệu ban đầu của một văn phạm dẫn


đến một chuỗi trong ngôn ngữ. Cây phân tích cú pháp có các tính chất sau:
o Nút gốc có nhãn là ký hiệu bắt đầu.
o Mỗi một lá có nhãn là một ký hiệu kết thúc hoặc một  .
o Mỗi nút trong có nhãn là một ký hiệu chưa kết thúc.
-

Sự mơ hồ của văn phạm: một văn phạm có thể sinh ra nhiều hơn một cây

phân tích cú pháp cho cùng một chuỗi nhập thì gọi là văn phạm mơ hồ. Bởi vì một
chuỗi với nhiều cây phân tích cú pháp thường sẽ có nhiều nghĩa, do đó khi biên dịch
các chương trình ứng dụng, chúng ta cần thiết kế các văn phạm khơng có sự mơ hồ
hoặc cần bổ sung thêm các quy tắc cần thiết để giải quyết sự mơ hồ cho văn phạm.
Hình 1.4. trình bày cây phân tích cú pháp (parse tree) cho câu lệnh gán
position := initial + rate * 60.
assignment statement

identifier

position

expression

:=
expression

+

expression
expression


identifie
r

*

initial

identifier

expression
number
60

rate

Hình 1.4. Cây phân tích cú pháp cho câu lệnh position := initial + rate * 60
Giai đoạn phân tích ngữ nghĩa sẽ kiểm tra các lỗi ngữ nghĩa của chương trình
nguồn và thu nhận các thơng tin về kiểu cho giai đoạn tạo mã tiếp theo. Giai đoạn
này sử dụng cấu trúc phân cấp được xác định trong giai đoạn phân tích cú pháp để
xác định tốn tử và toán hạng của các biểu thức và câu lệnh.


Một phần quan trọng trong giai đoạn phân tích ngữ nghĩa là kiểm tra kiểu
(tye checking). Ở đây trình biên dịch sẽ kiểm tra, dựa theo đặc tả của ngôn ngữ


nguồn xem các tốn hạng của một tốn tử có hợp lệ hay không. Chẳng hạn nhiều
định nghĩa của các ngơn ngữ lập trình u cầu trình biên dịch ghi nhận lỗi mỗi khi
một số thực được sử dụng làm chỉ mục cho một mảng. Tuy nhiên đặc tả của ngơn
ngữ có thể cho phép việc áp đặt tốn hạng (operand coercion), ví dụ như khi, một

tốn tử số học hai ngôi (binary arithmetic operator) được áp dụng cho một số
nguyên và một số thực. Trong trường hợp này, trình biên dịch có thể phải chuyển số
nguyên thành số thực.
TỔNG KẾT CHƯƠNG 1
Trong chương này tác giả trình bày khái niệm chung về trình biên dịch, các
giai đoạn xây dựng trình biên dịch, các thành phần cấu thành trình biên dịch và mối
quan hệ giữa các thành phần đó. Từ đó có cái nhìn tổng qt về việc xây dựng và
vận hành trình biên dịch.


CHƯƠNG 2: CƠNG CỤ LEX/ YACC
Chương này trình bày những nghiên cứu của tác giả về công cụ Lex/Yacc,
quy tắc sử dụng Lex/Yacc, mối quan hệ giữa chúng và các cơng cụ hỗ trợ. Trong
chương này tác giả cũng trình bày cách thức hoạt động của công cụ này trong việc
hỗ trợ tạo ra trình biên dịch.
2.1. GIỚI THIỆU VỀ LEX/YACC
Lex và Yacc là các công cụ dung để sinh bộ phân tích từ vựng và phân tích
cú pháp. Trước năm 1975 việc xây dựng mộ trình biên dịch là một q trình tốn
nhiều cơng sức và thời gian. Sau đó Lesk và Johnson đã cơng bố tài liệu về Lex và
Yacc. Những cơng cụ này đã giúp ích rất nhiều trong việc xây dựng trình biên dịch.
mã nguồn

a=b+c*d
Bộ phân tích từ vựng

từ tố

Mẫu/ luật mơ tả

Yacc


Cú pháp/văn

id1 = id2 + id3* id4
Bộ phân tích cú pháp

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

Lex

=
+

id1

*

id2
id3

id4

Sinh mã đích


đích

load

mul
add

id3
id4
id2

Hình 2.1. Ví dụ trình tự biên dịch đoạn mã nguồn a = b + c * d


Mẫu/ luật mơ tả ở hình trên là tập tin được tạo ra bằng trình soạn thảo bất kì.
Lex sẽ đọc những luật này và sinh ra mã C cho bộ phân tích từ vựng. Bộ phân tích
tích từ vựng này thực hiện việc so khớp chuỗi kí tự trong dữ liệu đầu vào với những
luật đã được mô tả và chuyển những chuối kí tự này thành các từ tố. Từ tố là sự số
hóa các chuỗi, giúp cho quá trình xử lý được dễ dàng hơn.
Khi các định danh trong dữ liệu vào được tìm thấy, bộ phân tích từ vựng sẽ
đưa chúng vào bảng ký hiệu. Bảng ký hiệu có thể chứa nhiều thơng tin khác, ví dụ
như kiểu dữ liệu và vị trí mỗi biến rtong bộ nhớ.
Cú pháp/ văn phạm trong hình trên cũng là một tập tin văn bản được tạo ra
bởi trình soạn thảo bất kì. Yacc sẽ đọc cú pháp này và tạo ra mã C cho bộ phân tích
cú pháp. Bộ phân tích cú pháp sử dụng các luật cho phép phân tích các từ tố từ bộ
phân tích từ vựng và tạo ra cây cú pháp. Cây cú pháp biểu diễn một cấu trúc phân
cấp các từ tố.
Cuối cùng, chương trình đích sẽ được sinh ra từ cây cú pháp được cung cấp
bởi bộ phân tích cú pháp.
Quy trình chung để sử dụng Lex và Yacc khi phát sinh ứng dụng được thể
hiện qua Hình 2.2.
(yyparse)
compile.y


yacc

compile.c

sourc

compile_tab.c

gcc
compile_tab.h

compile.exe
Compiled
lex.yy.c

lex

compile.l

output

(yylex)

Hình 2.2. Quy trình vận dụng và cách đặt tên với Lex, Yacc
Ví dụ chúng ta muốn xây dựng một trình biên dịch. Đầu tiên, chúng ta cần
xây dựng mẫu/ luật mô tả cho Lex trong file compile.l, và văn phạm/ cú pháp cho
Yacc trong file compile.y. Các câu lệnh để tạo ra trình biên dịch, compile.exe, được
liệt kê dưới đây:
yacc lex -d compile.y
cc

compile.l
lex.yy.c

y.tab.c

# create y.tab.h, y.tab.c # create lex.yy.c
# compile/link
–o compile.exe


Yacc đọc mô tả cú pháp trong compile.y và phát sinh bộ phân tích cú pháp
(bộ phân tích), bao gồm hàm yyparse, trong tệp compile _tab.c. Trong tệp
compile.y còn bao gồm việc khai báo các từ tố, tùy chọn –d để phát sinh những
định nghĩa cho các từ tố này và đặt chúng trong tệp compile _tab.h.
Lex đọc mô tả các mẫu trong compile.l, cùng với tệp compile _tab.h để phát
sinh bộ phân tích từ vựng, bao gồm hàm yylex, trong tệp lex.yy.c.
Cuối cùng, bộ phân tích cú pháp và bộ phân tích từ vựng được biên dịch và
liên kết với nhau để tạo ra compile.exe có thể thực thi được. Trong hàm main, hàm
yyparse được gọi để chạy trình biên dịch. Hàm yyparse tự động gọi yylex để thu
được từ tố.
2.2. LEX
Trong giai đoạn đầu, trình biên dịch đọc những dòng ký tự đầu vào và
chuyển những dòng ký tự này thành các từ tố. Nhờ các biểu thức chính quy chúng ta
có thể chỉ ra các luật mơ tả cho Lex, từ đó nó có thể sinh ra mã cho phép quét và so
khớp những dòng ký tự đầu vào. Mỗi một luật mô tả trong tập tin Lex (có đi mở
rộng là .l) thường có một hành động đi kèm. Lex dịch các biểu thức chính quy sang
chương trình máy bằng cách bắt chước máy trạng thái hữu hạn. Sử dụng ký tự vào
tiếp theo cùng trạng thái hiện tại, trạng thái tiếp theo được dễ dàng xác định căn cứ
vào bảng trạng thái.
Tuy nhiên Lex cũng có một số hạn chế. Ví dụ, Lex khơng thể sử dụng để

nhận biết các cấu trúc lồng nhau (như các dấu ngoặc đơn). Các cấu trúc lồng nhau
được quản lý bởi một ngăn xếp nhất định. Khi chúng ta gặp một dấu “(“ chúng ta
đẩy nó vào ngăn xếp, cịn khi tìm thấy dấu “)” chúng ta so khớp với phần tử ở đỉnh
ngăn xếp và lấy phần tử đó ra khỏi ngăn xếp. Tuy nhiên Lex chỉ có các trạng thái và
các dịch chuyển giữa các trạng thái, Lex khơng có ngăn xếp nên nó khơng phù hợp
để phân tích các cấu trúc lồng nhau. Yacc sử dụng máy trạng thái hữu hạn cùng với
một ngăn xếp và có thể thực thi các cấu trúc lồng nhau một cách dễ dàng. Lex thích
hợp trong việc so khớp các mẫu cịn Yacc thích hợp cho các vấn đề phức tạp hơn.
Khuôn dạng chung của nguồn Lex (Lex source) gồm ba phần như sau:
………. definitions ……….
%%
……….rules……….
%%
……….subroutines……….


Phần định nghĩa (definitions): đặt ở phần đầu chương trình, dùng để định
nghĩa cú pháp các công thức, các biến, các kiểu, … Mã trong phần này sẽ được sao
chép tới đầu tập tin C và cần được đặt trong cặp dấu “%{“ và “%}”.
Phần luật (rules): phần này được đặt trong cặp dấu “%%”, trình bày nội dung
các luật. Mỗi luật gồm hai phần: một luật và một hành động tương ứng, ngăn cách
nhau bằng ký tự trắng. Bộ phân tích từ vựng mà lex tạo ra sẽ thực hiện hành động
khi nó nhận ra một luật. Những luật này chính là các biểu thức chính quy.
Phần chương trình con (subroutines): đặt ở phần cuối chương trình, trình bày
các hàm được viết bằng mã C. Lex sao chép những hàm này tới tập tin C sau khi
Lex thực hiện xong việc sinh mã.
Bảng 2.1. Mô tả cách biểu diễn các biểu thức chính quy
Ký tự
.
\n

*
+
?
^
$
a|b
(ab)+
“a+b”
[]

Ý nghĩa
Bất kỳ ký tự nào ngoại trừ ký tự xuống dịng
Xuống dịng
Khơng hoặc nhiều bản sao của biểu thức phía trước
Một hoặc nhiều bản sao của biểu thức phía trước
Khơng hoặc một bản sao của biểu thức phía trước
Bắt đầu một dịng
Kết thúc một dịng
a hoặc b
Một hoặc nhiều bản sao của ab
a+b
Nhóm các ký tự

Bảng 2.2. Biểu diễn một số ví dụ của biểu thức chính quy
Biểu thức
abc
abc*
abc+
a(bc)+
a(bc)?

[abc]
[a-z]
[a\-z]
[-az]
[A-Za-z0-9]+
[ \t\n]

Ý nghĩa
abc
ab abc abcc abccc …
abc abcc abccc …
abc abcbc abcbcbc ….
a abc
Một trong các ký tự a, b, c
Bất kỳ ký tự nào từ a đến z
Một trong các ký tự a, -, z
Một trong các ký tự -, a, z
Một hoặc nhiều ký tự/ số
Ký tự trắng


[^ab]
[a^b]
[a|b]
a|b

Bất kỳ ký tự nào ngoại trừ a, b
Một trong các ký tự a, ^, b
Một trong các ký tự a, |, b
Một trong các ký tự a, b


Nguồn Lex được chia thành 3 phần với ký hiệu %% ngăn cách giữa các
phần. Tại một thời điểm chỉ có một kí tự từ dữ liệu vào được sao chép qua dữ liệu
ra. Kí hiệu %% đầu tiên ln cần phải có, tuy nhiên, nếu chúng ta khơng chỉ ra bất
kì luật nào thì một hành động mặc định là so khớp mọi thứ và sẽ sao chép toàn bộ
dữ liệu vào thành dữ liệu ra. Mặc định cho đầu vào và đầu ra là stdin và stdout.
Dưới đây là ví dụ với giá trị mặc định được mã hóa một cách rõ rang:
%%
.

/*match everything except newline*/
ECHO;
/*match newline*/
ECHO;

\n
%%
int yywrap(void){
return 1;
}
int main (void){
yylex();
return 0;
}

Có hai luật sinh được chỉ ra trong phần luật. Mỗi một luật sinh được bắt đầu
ở cột đầu tiên, theo sau là kí tự trắng (khoảng trắng, tab hoặc kí tự xuống xịng) và
một hành động tương ứng với luật sinh đó. Hành động này có thể là một hoặc nhiều
câu lệnh C (được đặt trong dấu ngoặc nhọn {}). Mọi thứ không được bắt đầu ở cột
một sẽ được sao chép tới tập tin C. Chúng ta cũng có thể thêm các ghi chú cho tập

tin nguồn lex. Trong ví dụ này có hai luật sinh, “.” và “\n” với hành động ECHO
tương ứng với các luật này. Có một số macro và một số biến được định nghĩa trước
bởi lex. ECHO là một macro được thực hiện khi luật được so khớp. Đây là hành
động mặc định cho bất kì chuỗi nào khơng được so khớp. ECHO được định nghĩa
như sau:
#define ECHO fwrite (yytext, yyleng, 1, yyout)
Biến yytextb là con trỏ trỏ tới chuỗi được so khớp và yyleng là độ dài của
chuỗi so khớp đó. Biến yyout là đầu ra, mặc định là stdout. Hàm yywrap được gọi
bởi lex khi đầu vào đã được xử lý hết, trả lại giá trị 1 nếu mọi việc đã kết thúc hoặc


trả lại 0 nếu còn cần tới một vài xử lý khác. Mọi chương trình C đều cần một hàm
main. Trong trường hợp này chúng ta chỉ cần gọi hàm yylex, đây chính là điểm vào
của lex.
Bảng 2.3. Danh sách các macro, biến được định nghĩa trước của lex
Tên biến/hàm
Chức năng
int yylex (void)
Gọi bộ phân tích từ vựng, trả lại từ tố
char *yytext
Con trỏ tới chuỗi được so khớp
yyleng
Độ dài chuỗi được so khớp
int yywrap (void)
Trả lại 1 nếu đầu vào đã được xử lý hết,
0 nếu chưa
FILE *yyout
Tập tin kết quả
FILE *yyin
Tập tin đầu vào

INITIAL
Trạng thái bắt đầu
BEGIN
Điều kiện chuyển trạng thái bắt đầu
ECHO
Viết chuỗi được so khớp
Dưới đây là một chương trình nhưng khơng thực hiện bất kì cơng việc gì. Tất
cả đầu vào đều được so khớp nhưng khơng có bất kì hành động nào tương ứng với
các luật, vì thế sẽ khơng có đầu ra.
%%
.
\n

Ví dụ sau thực hiện thêm số dòng vào trước mỗi dòng trong tâp tin. Biến
yylineno dùng lưu trữ số dòng. Biến chưa tập tin vào cho lex là yyin và mặc định là
stdin.
%{
int yylineno;
%}
%%
^(.*)\n printf("%4d\t%s", ++yylineno, yytext);
%%
int main(int argc, char *argv[]) {
yyin = fopen(argv[1], "r");
yylex();
fclose(yyin);
}

Phần định nghĩa bao gồm các thay thế, mã và trạng thái bắt đầu. Mã trong
phần định nghĩa được sao chép vào đầu tập tin C và bắt buộc phải nằm trong cặp

dấu “%{“ và “%}”. Các thay thế chính là các luật mơ tả. Ví dụ, chúng ta có thể xác
định số và chữ cái như sau:
digit [0-9]
letter [A-Za-z]
%{



×