Tải bản đầy đủ (.doc) (43 trang)

NGHIÊN CỨU ỨNG DỤNG LEXYACC ĐỄ HỖ TRỢ 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 (585.6 KB, 43 trang )

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

BÁO CÁO TỔNG KẾT
ĐỀ TÀI NGHIÊN CỨU KHOA HỌC CỦA SINH VIÊN
THAM GIA XÉT GIẢI THƯỞNG...

NGHIÊN CỨU ỨNG DỤNG LEX/YACC ĐỄ HỖ TRỢ
PHÁT SINH MÃ NGUỒN.

Mã số đề tài: 60.48.01
Thuộc nhóm ngành khoa học: Khoa học máy tính.

ĐÀ NẴNG, Tháng 4/ Năm 2016


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

BÁO CÁO TỔNG KẾT
ĐỀ TÀI NGHIÊN CỨU KHOA HỌC CỦA SINH VIÊN
THAM GIA XÉT GIẢI THƯỞNG...

NGHIÊN CỨU ỨNG DỤNG LEX/YACC ĐỄ HỖ TRỢ
PHÁT SINH MÃ NGUỒN.
Mã số đề tài: 60.48.01
Thuộc nhóm ngành khoa học: Khoa học máy tính.

Sinh viên thực hiện:

Nguyễn Văn Nhớ



Nam, Nữ:

Nam

Dân tộc: Kinh
Lớp, khoa: Lớp 13i2, Khoa Công Nghệ Thông Tin

Năm thứ: 03 /Số năm đào tạo: 03

Ngành học: Công Nghệ Thông Tin
(Ghi rõ họ và tên sinh viên chịu trách nhiệm chính thực hiện đề tài)
Người hướng dẫn: Thạc sĩ. Dương Thị Mai Nga

ĐÀ NẴNG, Tháng 4/ Năm 2016


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn

MỤC LỤC
MỤC LỤC.................................................................................................................... 1
DANH MỤC BẢNG....................................................................................................2
LỜI NÓI ĐẦU.............................................................................................................. 5
I.Các khái niệm về trình biên dịch..........................................................................6
1.Tổng quan về trình biên dịch............................................................................6
2.Khái quát chương trình.....................................................................................6
3.Bộ phân tích từ vựng..........................................................................................6
4.Bộ phân tích cú pháp.........................................................................................9
5.Bộ xử lý ngữ nghĩa...........................................................................................13
II.Nội dung thực hiện phân tích thực nghiệm......................................................13

1.Nội dung lý thuyết về Lex/Yacc.......................................................................13
2.Nội dung phân tích và ví dụ thực nghiệm.......................................................25
III.Kết Luận và kiến nghị:.....................................................................................41

SVTH: Nguyễn Văn Nhớ _ 13I2

1


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn

DANH MỤC BẢNG
Bảng 1: Pattern Matching Primitives........................................................................16
Bảng 2: Pattern Matching Examples.........................................................................17
Bảng 3: Lex Predefined Variables..............................................................................19

SVTH: Nguyễn Văn Nhớ _ 13I2

2


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn
ĐẠI HỌC ĐÀ NẴNG
TRƯỜNG CĐ CÔNG NGHỆ THÔNG TIN

THÔNG TIN KẾT QUẢ NGHIÊN CỨU CỦA ĐỀ TÀI
1. Thông tin chung:
- Tên đề tài: Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn.
- Sinh viên thực hiện: Nguyễn Văn Nhớ
- Lớp: 13i2


Khoa: Công Nghệ Thông Tin Năm thứ: 03 Số năm đào tạo: 03

- Người hướng dẫn: Ths. Dương Thị Mai Nga
2. Mục tiêu đề tài:
Ngiên cứu ứng dụng Lex/Yacc để tăng hiệu quả khi lập trình ứng dụng.
Sau khi nghiên cứu, sẽ trình bày hệ thống cách sử dụng và hướng dẫn lưu ý khi
làm việc với chương trình ứng dụng.
3. Tính mới và sáng tạo:
Ứng dụng đưa ra cho người lập trình khá nhiều lựa chọn hơn trước tránh
được các lỗi khó trong lập trình các ứng dụng. Tăng chất lương sản phẩm được
làm ra bởi các lập trình viên.
4. Kết quả nghiên cứu:
Kết quả quá trình tìm hiểu và nghiên cứu đề tài ứng dụng công cụ
Lex/Yacc để phát sinh mã nguồn trong lập trình đã cho thấy kết quả tương đối
tốt. Có nhiều ưu điểm và sự tiện ích cao, thích hợp cho việc áp dụng vào thực
tiễn trong nền công nghiệp tin học ứng dụng.
5. Đóng góp về mặt kinh tế - xã hội, giáo dục và đào tạo, an ninh, quốc
phòng và khả năng áp dụng của đề tài:
Đóng góp to lớn vào các mặt kinh tế - xã hội, giáo dục,.. tiết kiệm thời
gian chi phí của người sử dụng, giúp người sử dụng tiếp cận với các tiện ích cao
đặc biết là trong xã hội có nền công nghệ thông tin đang phát triển như nước ta.
Thúc đẩy nền giáo dục đi lên, học sinh sinh viên được tiếp cận với các phương
pháp học tiên tiến hơn…
6. Công bố khoa học của sinh viên từ kết quả nghiên cứu của đề tài (ghi rõ
tên tạp chí nếu có) hoặc nhận xét, đánh giá của cơ sở đã áp dụng các kết quả
nghiên cứu (nếu có):

SVTH: Nguyễn Văn Nhớ _ 13I2


3


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn

Ngày

tháng

năm 2016

Sinh viên chịu trách nhiệm chính
thực hiện đề tài
(ký, họ và tên)

Nguyễn Văn Nhớ
Nhận xét của người hướng dẫn về những đóng góp khoa học của sinh viên
thực hiện đề tài (phần này do người hướng dẫn ghi):
Ngày

tháng

năm 2016

Xác nhận của đơn vị

Người hướng dẫn

(ký tên và đóng dấu)


(ký, họ và tên)

Ths. Dương Thị Mai Nga

SVTH: Nguyễn Văn Nhớ _ 13I2

4


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn

LỜI NÓI ĐẦU
Trong công nghệ thông tin, cùng với sự phát triển các thiết bị phần cứng là sự
hình thành và phát triển của các kĩ thuật lập trình. Làm thế nào để lập trình nhanh và
hiệu quả nhất cho một bài toán luôn 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 mạnh là kĩ thuật tự
động phát sinh mã nguồn.
Tự động phát sinh mã nguồn là một ý tưởng táo bạo và mang lại hiệu quả cao
trong lập trình. Một trong những công cụ hỗ trợ phát sinh mã nguồn đó là công cụ
Lex/Yacc. Lex/Yacc có chức năng phát sinh mã nguồn trong lĩnh vực tạo các chương
trình dịch và sau này được mở rộng ra nhiều lĩnh vực khác. Lex/Yacc sẽ dựa vào các
đặt tả ngữ pháp và tự động phát sinh ra mã nguồn C hoặc Pascal. Việc này cho phép
tiết kiệm rất nhiều thời gian cũng như hạn chế lỗi trong quá trình biên dịch.
Trong báo cáo sau đây, giúp chúng ta phân tích, đánh giá hiệu quả trong lập
trình bằng cách sử dụng công cụ Lex/Yacc. Cung cấp tài liệu về công cụ Lex/Yacc một
cách hệ thống và đầy đủ để phục vụ cho việc phát triển ứng dụng sau này. Góp phần
thúc đẩy việc ứng dụng công cụ Lex/Yacc trong lập trình tại Việt Nam.
Khi thực hiện đề tài, chúng tôi đã kết hợp giữa phương pháp nghiên cứu lý
thuyết và phương pháp nghiên cứu thực nghiệm. về mặt lý thuyết, chúng tôi tiến hành
nghiên cứu tổng quan về chương trình dịch, các bộ phân tích từ vựng và cú pháp, các

bộ phân tích và phát sinh mã nguồn Lex/Yacc. Về mặt thực nghiệm, chúng tôi tiến
hành một số ví dụ minh họa trên một số bài toán tiêu biểu với Lex/Yacc.
Đối tượng nghiên cứu chính của đề tài là chương trình dịch, các ngôn ngữ đặc
tả các công cụ hỗ trợ phát sinh mã nguồn. Tuy nhiên tôi giới hạn phạm vi nghiên cứu
của mình trên Lex/Yacc và phát sinh mã nguồn trong ngôn ngữ lập trình C.

SVTH: Nguyễn Văn Nhớ _ 13I2

5


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn

I. Các khái niệm về trình biên dịch
1.

Tổng quan về trình biên dịch

Trình biên dịch là chương trình chiệu trách nhiệm dịch một mã văn bản chương
trình nguồn của một ngôn ngữ lập trình ra mã máy. Mã văn bản chương trình nguồn
được dịch sang một dạng mã trung gian sau đó mã trung gian sẽ được dịch xuống mã
máy và mã máy sẽ được thực thi.
2.

Khái quát chương trình

Chương này giới thiệu một trình biên dịch cho các biểu thức số học đơn giản
(trình biên dịch đơn giản) gồm hai kỳ: Kỳ đầu (Front end) và kỳ sau (Back end). Nội
dung chính của chương tập trung vào kỳ đầu gồm các giai đoạn: Phân tích từ vựng,
phân tích cú pháp và sinh mã trung gian với mục đích chuyển một biểu thức số học

đơn giản từ dạng trung tố sang hậu tố. Kỳ sau chuyển đổi biểu thức ở dạng hậu tố
sang mã máy ảo kiểu stack, sau đó sẽ thực thi đoạn mã đó trên máy ảo kiểu stack để
cho ra kết quả tính toán cuối cùng.
Chương trình gồm 3 thành phần chính:
- Bộ phân tích từ vựng
- Bộ phân tích cú pháp
- Bộ xử lý ngữ nghĩa
3.

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

Bộ phân tích từ vựng có nhiệm vụ nạp toàn bộ văn bản chương trình nguồn vào
bộ nhớ. Phân tích văn bản chương trình nguồn thành các token riêng biệt. Báo lỗi khi
gặp các kí hiệu không thuộc tập ký hiệu hoặc các token không thỏa mãn luật danh
hiệu.
Trước hết ta trình bày một số chức năng cần thiết của bộ phân tích từ vựng.
a. Loại bỏ các khoảng trắng và các dòng chú thích.
Quá trình dịch sẽ xem xét tất cả các ký tự trong dòng nhập nên những ký tự
không có nghĩa (như khoảng trắng bao gồm ký tự trống (blanks), ký tự tabs, ký tự
newlines) hoặc các dòng chú thích (comment) phải bị bỏ qua. Khi bộ phân tích từ
vựng đã bỏ qua các khoảng trắng này thì bộ phân tích cú pháp không bao giờ xem xét
đến chúng nữa. Chọn lựa cách sửa đổi văn phạm để đưa cả khoảng trắng vào trong cú
pháp thì hầu như rất khó cài đặt.

SVTH: Nguyễn Văn Nhớ _ 13I2

6


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn


b. Xử lý các hằng.
Bất cứ khi nào một ký tự số xuất hiện trong biểu thức thì nó được xem như là một
hằng số. Bởi vì một hằng số nguyên là một dãy các chữ số nên nó có thể được cho bởi
luật sinh văn phạm hoặc tạo ra một token cho hằng số đó. Bộ phân tích từ vựng có
nhiệm vụ ghép các chữ số để được một số và sử dụng nó như một đơn vị trong suốt
quá trình dịch.
Ðặt num là một token biểu diễn cho một số nguyên. Khi một chuỗi các chữ số
xuất hiện trong dòng nhập thì bộ phân tích từ vựng sẽ gửi num cho bộ phân tích cú
pháp. Giá trị của số nguyên được chuyển cho bộ phân tích cú pháp như là một thuộc
tính của token num. Về mặt logic, bộ phân tích từ vựng sẽ chuyển cả token và các
thuộc tính cho bộ phân tích cú pháp. Nếu ta viết một token và thuộc tính thành một bộ
nằm giữa < > thì dòng nhập 31 + 28 + 59 sẽ được chuyển thành một dãy các bộ :
<num, 31>, < +, >, <num, 28>, < +, >, <num, 59>.
Bộ <+, > cho thấy thuộc tính của + không có vai trò gì trong khi phân tích cú
pháp nhưng nó cần thiết dùng đến trong quá trình dịch.
c. Nhận dạng các danh biểu và từ khóa.
Ngôn ngữ dùng các danh biểu (identifier) như là tên biến, mảng, hàm và văn
phạm xử lý các danh biểu này như là một token. Người ta dùng token id cho các danh
biểu khác nhau do đó nếu ta có dòng nhập count = count + increment; thì bộ phân tích
từ vựng sẽ chuyển cho bộ phân tích cú pháp chuỗi token: id = id + id (cần phân biệt
token và trị từ vựng lexeme của nó: token id nhưng trị từ vựng (lexeme) có thể là
count hoặc increment). Khi một lexeme thể hiện cho một danh biểu được tìm thấy
trong dòng nhập cần phải có một cơ chế để xác định xem lexeme này đã được thấy
trước đó chưa? Công việc này được thực hiện nhờ sự lưu trữ trợ giúp của bảng ký
hiệu (symbol table) đã nêu ở chương trước. Trị từ vựng được lưu trong bảng ký hiệu
và một con trỏ chỉ đến mục ghi trong bảng trở thành một thuộc tính của token id.
Nhiều ngôn ngữ cũng sử dụng các chuỗi ký tự cố định như begin, end, if,... để
xác định một số kết cấu. Các chuỗi ký tự này được gọi là từ khóa (keyword). Các từ
khóa cũng thỏa mãn qui luật hình thành danh biểu, do vậy cần qui ước rằng một chuỗi

ký tự được xác định là một danh biểu khi nó không phải là từ khóa.

SVTH: Nguyễn Văn Nhớ _ 13I2

7


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn

Một vấn đề nữa cần quan tâm là vấn đề tách ra một token trong trường hợp một
ký tự có thể xuất hiện trong trị từ vựng của nhiều token. Ví dụ một số các token là các
toán tử quan hệ trong Pascal như : <, < =, < >.
d. Giao diện của bộ phân tích từ vựng.
Bộ phân tích từ vựng được đặt xen giữa dòng nhập và bộ phân tích cú pháp nên
giao diện với hai bộ này như sau:

Bộ phân tích từ vựng đọc từng ký tự từ dòng nhập, nhóm chúng lại thành các trị
từ vựng và chuyển các token xác định bởi trị từ vựng này cùng với các thuộc tính của
nó đến những giai đoạn sau của trình biên dịch. Trong một vài tình huống, bộ phân
tích từ vựng phải đọc vượt trước một số ký tự mới xác định được một token để
chuyển cho bộ phân tích cú pháp. Ví dụ, trong Pascal khi gặp ký tự >, phải đọc thêm
một ký tự sau đó nữa; nếu ký tự sau là = thì token được xác định là “lớn hơn hoặc
bằng”, ngược lại thì token là “lớn hơn” và do đó một ký tự đã bị đọc quá. Trong
trường hợp đó thì ký tự đọc quá này phải được đẩy trả về (push back) cho dòng nhập
vì nó có thể là ký tự bắt đầu cho một trị từ vựng mới.
Bộ phân tích từ vựng và bộ phân tích cú pháp tạo thành một cặp "nhà sản xuất người tiêu dùng" (producer - consumer). Bộ phân tích từ vựng sản sinh ra các token
và bộ phân tích cú pháp tiêu thụ chúng. Các token được sản xuất bởi bộ phân tích từ
vựng sẽ được lưu trong một bộ đệm (buffer) cho đến khi chúng được tiêu thụ bởi bộ
phân tích cú pháp. Bộ phân tích từ vựng không thể hoạt động tiếp nếu buffer bị đầy và
bộ phân tích cú pháp không thể hoạt động nữa nếu buffer rỗng. Thông thường, buffer

chỉ lưu giữ một token. Ðể cài đặt tương tác dễ dàng, người ta tạo ra một thủ tục phân
tích từ vựng được gọi từ trong thủ tục phân tích cú pháp, mỗi lần gọi trả về một token.
Việc đọc và quay lui ký tự cũng được cài đặt bằng cách dùng một bộ đệm nhập.
Một khối các ký tự được đọc vào trong buffer nhập tại một thời điểm nào đó, một con
trỏ sẽ giữ vị trí đã được phân tích. Quay lui ký tự được thực hiện bằng cách lùi con trỏ
trở về. Các ký tự trong dòng nhập cũng có thể cần được lưu lại cho công việc ghi
nhận lỗi bởi vì cần phải chỉ ra vị trí lỗi trong đoạn chương trình.
SVTH: Nguyễn Văn Nhớ _ 13I2

8


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn

Ðể tránh việc phải quay lui, một số trình biên dịch sử dụng cơ chế đọc trước một
ký tự rồi mới gọi đến bộ phân tích từ vựng. Bộ phân tích từ vựng sẽ đọc tiếp các ký tự
cho đến khi đọc tới ký tự mở đầu cho một token khác. Trị từ vựng của token trước đó
sẽ bao gồm các ký tự từ ký tự đọc trước đến ký tự vừa ngay ký tự vừa đọc được. Ký
tự vừa đọc được sẽ là ký tự mở đầu cho trị từ vựng của token sau. Với cơ chế này thì
mỗi ký tự chỉ được đọc duy nhất một lần.
4.

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

Bộ phân tích cú pháp có nhiệm vụ dựa vào tập luật sinh cho trước phân tích câu
lệnh nhập xem có thỏa tập luật sinh hay không, bá lỗi khi câu lệnh nhập không đúng
cú pháp.
Phần lớn các phương pháp phân tích cú pháp đều rơi vào một trong 2 lớp:
phương pháp phân tích từ trên xuống và phương pháp phân tích từ dưới lên. Những
thuật ngữ này muốn đề cập đến thứ tự xây dựng các nút trong cây phân tích cú pháp.

Trong phương pháp đầu, quá trình xây dựng bắt đầu từ gốc tiến hành hướng xuống
các nút lá, còn trong phương pháp sau thì thực hiện từ các nút lá hướng về gốc.
Phương pháp phân tích từ trên xuống thông dụng hơn nhờ vào tính hiệu quả của nó
khi xây dựng theo lối thủ công. Ngược lại, phương pháp phân tích từ dưới lên lại có
thể xử lý được một lớp văn phạm và lược đồ dịch phong phú hơn. Vì vậy, đa số các
công cụ phần mềm giúp xây dựng thể phân tích cú pháp một cách trực tiếp từ văn
phạm đều có xu hướng sử dụng phương pháp từ dưới lên.
a. 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 (Backers Naur Form).
Văn phạm phi ngữ cảnh bao gồm bốn thành phần:
• Một tập hợp các token - các ký hiệu kết thúc (terminal symbols).
Ví dụ: Các từ khóa, các chuỗi, dấu ngoặc đơn, ...
• Một tập hợp các ký hiệu chưa kết thúc (nonterminal symbols), còn gọi là các
biến (variables).
Ví dụ: Câu lệnh, biểu thức, ...
• Một tập hợp các luật sinh (productions) trong đó mỗi luật sinh bao gồm một ký
hiệu chưa kết thúc - gọi là vế trái, một mũi tên và một chuỗi các token và /
hoặc các ký hiệu chưa kết thúc gọi là vế phải.
SVTH: Nguyễn Văn Nhớ _ 13I2

9


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn

• 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.
Chúng ta qui ước:
-


Mô tả văn phạm bằng cách liệt kê các luật sinh.

-

Luật sinh chứa ký hiệu bắt đầu sẽ được liệt kê đầu tiên.

-

Nếu có nhiều luật sinh có cùng vế trái thì nhóm lại thành một luật sinh duy
nhất, trong đó các vế phải cách nhau bởi ký hiệu “|”đọc là “hoặc”.

b. Cây phân tích cú pháp (Parse Tree).
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ữ.
Nếu ký hiệu chưa kết thúc A có luật sinh A → XYZ thì cây phân tích cú pháp có
thể có một nút trong có nhãn A và có 3 nút con có nhãn tương ứng từ trái qua phải là
X, Y, Z.

Một cách hình thức, cho một văn phạm phi ngữ cảnh thì cây phân tích cú pháp là
một cây có các tính chất sau đây:
-

Nút gốc có nhãn là ký hiệu bắt đầu.

-

Mỗi một lá có nhãn là một ký hiệu kết thúc hoặc một ε.

-


Mỗi nút trong có nhãn là một ký hiệu chưa kết thúc.

-

Nếu A là một ký hiệu chưa kết thúc được dùng làm nhãn cho một nút trong
nào đó và X1 ... Xn là nhãn của các con của nó theo thứ tự từ trái sang phải
thì A → X1X2 ... Xn là một luật sinh. Ở đây X1, ..., Xn có thể là ký hiệu kết
thúc hoặc chưa kết thúc. Ðặc biệt, nếu A → ε thì nút có nhãn A có thể có một
con có nhãn ε.

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ồ.
Ví du 1: Giả sử chúng ta không phân biệt một list với một digit, xem chúng đều
là một string ta có văn phạm:
string → string + string | string - string | 0 | 1 | ... | 9.
SVTH: Nguyễn Văn Nhớ _ 13I2

10


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn

Với văn phạm này thì chuỗi biểu thức 9 - 5 + 2 có đến hai cây phân tích cú pháp
như sau :

Tương tự với cách đặt dấu ngoặc vào biểu thức như sau :
(9 - 5) + 2


9 - ( 5 + 2)

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 qui tắc cần thiết để giải quyết sự mơ
hồ cho văn phạm.
d. Sự kết hợp của các toán tử.
Thông thường, theo quy ước ta có biểu thức 9 + 5 + 2 tương đương (9 + 5) + 2 và
9 - 5 - 2 tương đương với (9 - 5) - 2. Khi một toán hạng như 5 có hai toán tử ở trái và
phải thì nó phải chọn một trong hai để xử lý trước. Nếu toán tử bên trái được thực
hiện trước ta gọi là kết hợp trái. Ngược lại là kết hợp phải.
Thường thì bốn phép toán số học: +, -, *, / có tính kết hợp trái. Các phép toán như
số mũ, phép gán bằng (=) có tính kết hợp phải.

SVTH: Nguyễn Văn Nhớ _ 13I2

11


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn

e. Thứ tự ưu tiên của các toán tử.
Xét biểu thức 9 + 5 * 2. Có 2 cách để diễn giải biểu thức này, đó là 9 + (5 * 2)
hoặc ( 9 + 5) * 2. Tính kết hợp của phép + và * không giải quyết được sự mơ hồ này,
vì vậy cần phải quy định một thứ tự ưu tiên giữa các loại toán tử khác nhau.
Thông thường trong toán học, các toán tử * và / có độ ưu tiên cao hơn + và -.
Cú pháp cho biểu thức:
Văn phạm cho các biểu thức số học có thể xây dựng từ bảng kết hợp và ưu tiên
của các toán tử. Chúng ta có thể bắt đầu với bốn phép tính số học theo thứ bậc sau :


Chúng ta tạo hai ký hiệu chưa kết thúc expr và term cho hai mức ưu tiên và một
ký hiệu chưa kết thúc factor làm đơn vị phát sinh cơ sở của biểu thức. Ta có đơn vị cơ
bản trong biểu thức là số hoặc biểu thức trong dấu ngoặc.
Phép nhân và chia có thứ tự ưu tiên cao hơn đồng thời chúng kết hợp trái nên luật
sinh cho term tương tự như cho list :
term → term * factor | term / factor | factor
Tương tự, ta có luật sinh cho expr :
expr → expr + term | expr - term | term
Vậy, cuối cùng ta thu được văn phạm cho biểu thức như sau :
expr → expr + term | expr - term | term
term → term * factor | term / factor | factor
factor → digit | (expr)
Như vậy: Văn phạm này xem biểu thức như là một danh sách các term được phân
cách nhau bởi dấu + hoặc -. Term là một list các factor phân cách nhau bởi * hoặc /.
Chú ý rằng bất kỳ một biểu thức nào trong ngoặc đều là factor, vì thế với các dấu
ngoặc chúng ta có thể xây dựng các biểu thức lồng sâu nhiều cấp tuỳ ý.
Cú pháp cho câu lệnh:
Từ khóa (keyword) cho phép chúng ta nhận ra câu lệnh trong hầu hết các ngôn
ngữ. Ví dụ trong Pascal, hầu hết các lệnh đều bắt đầu bởi một từ khóa ngoại trừ lệnh
gán. Một số lệnh Pascal được định nghĩa bởi văn phạm (mơ hồ) sau, trong đó id chỉ
một danh biểu (tên biến).
SVTH: Nguyễn Văn Nhớ _ 13I2

12


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn

stmt →


id := expr
| if expr then stmt
| if expr then stmt else stmt
| while expr do stmt
| begin opt_stmts end
Ký hiệu chưa kết thúc opt_stmts sinh ra một danh sách có thể rỗng các lệnh, phân
cách nhau bởi dấu chấm phẩy (;).
5.

Bộ xử lý ngữ nghĩa

Tổng quát: Khi bộ phân tích cú pháp nhận dạng được cấu trúc của một câu lệnh
thì gọi thực hiện hành vi ngữ nghĩa tương ứng.
- Cấu trúc rẽ nhánh
- Cấu trúc lệnh Switch
- Các cấu trúc lặp
II. Nội dung thực hiện phân tích thực nghiệm
1.

Nội dung lý thuyết về Lex/Yacc

Hình 3: Sơ đồ tóm tắt cấu trúc trình biên dịch và mối liên hệ của Lex/Yacc
Chương trình nguồn

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

Lex

Mẫu/ luật mô tả


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

Yacc

Cú pháp/văn phạm

Sinh mã đích

Chương trình đích

 Tổng quan về Lex :
Ở giai đoạn đầu của trình biên dịch Lex có chức năng đọc các mẫu chứa trong tập
tin mẫu/luật mô tả (có tên với đuôi mở rộng .l được tạo ra từ một trình soạn thảo bất
kì) và tạo ra mã C cho bộ phân tích từ vựng có chức năng chuyển dữ liệu vào thành
các từ tố căn cứ trên các mẫu đã được định nghĩa.
Sau đây đại diện cho một mô hình đơn giản, bao gồm một biểu hiện thường
xuyên, có thể quét để định danh. Lex sẽ đọc mô hình này và sản xuất mã C cho một
bộ phân tích từ vựng mà quét để định danh.
letter(letter|digit)*
SVTH: Nguyễn Văn Nhớ _ 13I2

13


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn

Mô hình này phù hợp với một chuỗi các ký tự bắt đầu bằng một chữ cái tiếp theo
không hoặc nhiều chữ cái hoặc chữ số.
Bất kỳ biểu thức biểu hiện thường xuyên có thể được thể hiện như một máy tự
động hữu hạn nhà nước (FSA). Chúng tôi có thể đại diện cho một FSA sử dụng tiểu

bang, và chuyển tiếp giữa các quốc gia. Có một trạng thái bắt đầu và một hoặc nhiều
tiểu bang cuối cùng hoặc chấp nhận.

Hình 4: Hữu hạn nhà nước Automaton
Trong hình 4 trạng thái 0 là trạng thái bắt đầu và trạng thái 2 là nhà nước chấp
nhận. Là nhân vật được đọc, chúng tôi làm cho một quá trình chuyển đổi từ một tiểu
bang khác. Khi các chữ cái đầu tiên được đọc, chúng tôi chuyển sang trạng thái 1.
Chúng tôi vẫn còn trong trạng thái 1 như nhiều ký tự hoặc số được đọc. Khi chúng ta
đọc một nhân vật khác hơn là một quá trình chuyển đổi chữ cái hoặc chữ chúng tôi để
chấp nhận nhà nước 2. Bất kỳ FSA có thể được thể hiện như một chương trình máy
tính. Ví dụ, bộ máy 3-nhà nước của chúng ta được lập trình dễ dàng:
start:

goto state0

state0: read c
if c = letter goto state1
goto state0
state1: read c
if c = letter goto state1
if c = digit goto state1
goto state2
state2: accept string

Đây là kỹ thuật được sử dụng bởi lex. Biểu thức thông thường được dịch bởi lex
đến một chương trình máy tính bắt chước một FSA. Sử dụng tiếp theo đầu vào nhân

SVTH: Nguyễn Văn Nhớ _ 13I2

14



Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn

vật và tình trạng hiện tại của trạng thái tiếp theo được xác định dễ dàng bằng cách
đánh chỉ mục vào một bảng trạng thái máy tính tạo ra.
Bây giờ chúng ta có thể dễ dàng hiểu được một số hạn chế của lex. Ví dụ, lex
không thể được sử dụng để nhận ra các cấu trúc lồng nhau như dấu ngoặc đơn. Cấu
trúc lồng nhau được xử lý bằng cách kết hợp một chồng. Bất cứ khi nào chúng ta gặp
phải một "(" chúng tôi đẩy nó vào stack.Khi a ")" là gặp chúng tôi phù hợp nó với
các đỉnh của ngăn xếp và pop stack. Tuy nhiên lex chỉ có trạng thái và chuyển tiếp
giữa bang. Vì nó không có chồng, nó cũng không phải là thích hợp cho việc phân tích
các cấu trúc lồng nhau.
• Cấu tạo và hoạt động của một file Lex :
Một chương trình Lex (file lex.l ) bao gồm ba phần :
1. Tờ khai phần %%
2. quy tắc chuyển đổi một phần %%
3. Thủ tục Auxillary phần
Phần khai báo bao gồm tờ khai của các biến, hằng số biểu hiện ( Một hằng số
biểu hiện là một định danh được khai báo để đại diện cho một ví dụ hằng số #define
PIE 3.14 ), các tập tin được bao gồm và định nghĩa của các biểu thức thông thường .
Các quy tắc bản dịch của một chương trình Lex là các câu lệnh có dạng:
p1

{ hành động 1}

p2

{ hành động 2}


p3

{ hành động 3}

...

...

nơi mà mỗi p là một biểu hiện thường xuyên và mỗi hành động là một đoạn chương
trình mô tả những hành động phân tích từ vựng nên được dùng khi một mô hình chuỗi
phù hợp với p phù hợp với một lexeme. Trong Lex các hành động được viết bằng C.
Phần thứ ba giữ bất cứ thủ tục phụ trợ cần thiết bởi actions Alternatively các thủ
tục này có thể được biên dịch riêng rẽ và nạp với analyzer. Các từ vựng peocedures
Auxillary được viết bằng ngôn ngữ C.
Khi được kích hoạt bởi các cú pháp, phân tích từ vựng bắt đầu đọc đầu vào còn
lại của nó, một ký tự tại một thời gian, cho đến khi nó đã tìm thấy các tiền tố dài nhất
của các đầu vào được khớp với một trong các biểu thức thông thường p sau đó nó
thực hiện các hành động tương ứng. Thông thường các hành động sẽ trở về điều khiển
SVTH: Nguyễn Văn Nhớ _ 13I2

15


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn

các phân tích cú pháp. Tuy nhiên, nếu không, sau đó các proeeds phân tích từ vựng để
tìm thêm lexemes, cho đến khi một hành động gây ra sự kiểm soát để trở về phân tích
cú pháp. Các tìm kiếm lặp đi lặp lại cho đến khi lexemes một sự trở lại rõ ràng cho
phép phân tích từ vựng để xử lý khoảng trắng và các ý kiến thuận tiện.
Các phân tích từ vựng trả về một số lượng đơn, dấu hiệu, để phân tích cú pháp.

Để vượt qua một giá trị thuộc tính với các thông tin về các lexeme, chúng ta có thể
thiết lập các yylval biến toàn cầu.
Lex làm cho lexeme có sẵn cho các thói quen xuất hiện trong phần thứ ba thông
qua hai biến yytext và yyleng:
-

yytext là một biến mà là một con trỏ đến ký tự đầu tiên của lexeme.

-

yyleng là một số nguyên nói bao lâu lexeme.

Trong chương trình lex, a () chức năng chính thường bao gồm như:
main(){
yyin = fopen (filename, "r");
while (yylex ());
}
Ở đây tên tập tin tương ứng với tập tin đầu vào và các thói quen yylex () được gọi
là trả về thẻ. yyin là con trỏ FILE tuyên bố bởi Lex phần.

Metacharacter

Matches

.

bất kỳ nhân vật ngoại trừ xuống dòng

\ n


dòng mới

*

bằng không hoặc nhiều bản sao của biểu thức trước

+

một hoặc nhiều bản sao của biểu thức trước

?

không hoặc một bản sao của biểu thức trước

^

bắt đầu của dòng

$

kết thúc dòng

a | b

một hoặc b

(Ab) +

một hoặc nhiều bản sao của ab (nhóm)


"Một + b"

chữ " a + b " (C thoát vẫn làm việc)

[]

lớp nhân vật

Bảng 1: Pattern Matching Primitives
Matches

SVTH: Nguyễn Văn Nhớ _ 13I2

16


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn
Expression

abc

abc

abc *

ab abc abcc abccc ...

abc +

abc, abcc, abccc, abcccc, ...


a (bc) +

abc, abcbc, abcbcbc, ...

a (bc)?

một, abc

[Abc]

một trong số: a, b, c

[Az]

bất kỳ lá thư, một z qua

[A \ -z]

một trong số: a, -, z

[-az]

một trong số: - az

[A-Za-z0-9] +

một hoặc nhiều ký tự chữ và số

[\ T \ n] +


khoảng trắng

[^ Ab]

bất cứ điều gì ngoại trừ: a, b

[A ^ b]

một, ^, b

[A | b]

một, |, b

a | b

a, b
Bảng 2: Pattern Matching Examples

Biểu thức thông thường trong lex bao gồm các siêu ký tự (Bảng 1). Ví dụ mô
hình khớp được thể hiện trong Bảng 2. Trong thời hạn một lớp nhân vật khai thác
bình thường mất đi ý nghĩa của họ. Hai nhà khai thác cho phép trong một lớp nhân
vật là các dấu gạch ngang ( " - ") và circumflex ( " ^ ").Khi sử dụng giữa hai ký tự
gạch nối đại diện cho một loạt các nhân vật. Các circumflex, khi được sử dụng như là
nhân vật đầu tiên, phủ nhận sự biểu hiện. Nếu hai mô hình phù hợp với cùng một
chuỗi các trận đấu dài nhất thắng. Trong trường hợp cả hai trận đấu là chiều dài tương
tự, sau đó mô hình đầu tiên được liệt kê được sử dụng.
... Định nghĩa ... %% ... quy tắc ... %% ... chương trình con ...
Input Lex được chia thành ba phần với %% chia phần. Điều này được minh hoạ

bằng ví dụ. Ví dụ đầu tiên là tập tin lex ngắn nhất có thể:
%%
Đầu vào được sao chép vào đầu ra một ký tự tại một thời điểm. Việc đầu tiên %
% là luôn luôn cần thiết vì có phải lúc nào cũng là một phần các nguyên tắc. Tuy
nhiên, nếu chúng ta không chỉ định bất kỳ quy tắc sau đó hành động mặc định là để
phù hợp với tất cả mọi thứ và sao chép nó vào đầu ra. Mặc định cho đầu vào và đầu ra
SVTH: Nguyễn Văn Nhớ _ 13I2

17


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn

là stdin và stdout , tương ứng. Dưới đây là những ví dụ tương tự với giá trị mặc định
được mã hóa một cách rõ ràng:
%%
/* match everything except newline */
.

ECHO;
/* match newline */

\n

ECHO;

%%
int yywrap(void) {
return 1;
}

int main(void) {
yylex();
return 0;
}

Hai mô hình đã được quy định trong phần quy tắc. Mỗi mẫu phải bắt đầu trong
một cột. Tiếp theo là khoảng trắng (space, tab hoặc xuống dòng) và một hành động
tùy chọn kết hợp với các mô hình. Các hành động có thể là một tuyên bố đơn C, hoặc
nhiều câu lệnh C, kèm theo trong dấu ngoặc.Bất cứ điều gì không phải bắt đầu trong
một cột được sao chép nguyên văn để các tập tin C tạo ra. Chúng tôi có thể tận dụng
lợi thế của hành vi này để xác định ý kiến trong tập tin lex của chúng tôi. Trong ví dụ
này có hai mẫu, " . " và " \ n ", với một ECHO hành động liên quan cho mỗi
mẫu. Một số macro và các biến được định nghĩa trước bởi lex. ECHO là một macro
để viết mã phù hợp của mô hình. Đây là hành động mặc định cho bất kỳ chuỗi chưa
từng có. Thông thường, ECHO được định nghĩa là:
#define ECHO fwrite(yytext, yyleng, 1, yyout)

Biến yytext là một con trỏ đến chuỗi phù hợp (NULL-chấm dứt) và yyleng là
chiều dài của chuỗi phù hợp. Biến yyout là tập tin đầu ra và mặc định để thiết bị xuất
chuẩn . Chức năng yywrap được gọi bởi lex khi đầu vào là hết. Trở về 1 nếu bạn
đang thực hiện hoặc 0 nếu chế biến hơn là cần thiết. Mỗi chương trình C đòi hỏi
SVTH: Nguyễn Văn Nhớ _ 13I2

18


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn

một chức năng chính. Trong trường hợp này, chúng ta chỉ cần gọi yylex đó là mục
điểm chính cho lex. Một số triển khai của lex bao gồm các bản sao của chức

năng chính và yywrap trong một thư viện do đó loại bỏ sự cần thiết phải mã chúng
một cách rõ ràng. Đây là lý do tại sao ví dụ đầu tiên của chúng tôi, chương trình lex
ngắn nhất, hoạt động tốt.
Chức năng

Tên

int yylex (void) gọi để gọi lexer, trả thẻ
char * yytext

con trỏ đến chuỗi phù hợp

yyleng

chiều dài của chuỗi phù hợp

yylval

giá trị kết hợp với token

int yywrap (void)

WRAPUP, trả về 1 nếu được thực hiện, 0 nếu không
thực hiện

FILE * yyout

tập tin đầu ra

FILE * yyin


tập tin đầu vào

BAN ĐẦU

điều kiện bắt đầu ban đầu

BEGIN condition

điều kiện bắt đầu chuyển đổi

ECHO

viết chuỗi phù hợp

Bảng 3: Lex Predefined Variables
Dưới đây là một chương trình mà không làm gì cả. Tất cả các đầu vào là giống
nhưng không có hành động là liên kết với bất kỳ mô hình như vậy sẽ có không có đầu
ra.
%%
.
\ N

Ví dụ sau prepends số dòng cho mỗi dòng trong một tập tin. Một số triển khai của
lex ấn định trước và tính toán yylineno. Các tập tin đầu vào cho lex là yyin và mặc
định là stdin.
% {
Int yylineno;
%}
%%

^ (. *) \ N printf ( "% 4d \ t% s", ++ yylineno, yytext);
%%
SVTH: Nguyễn Văn Nhớ _ 13I2

19


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn
int main (int argc, char * argv []) {
yyin = fopen (argv [1], "r");
yylex ();
fclose (yyin)
}

Phần định nghĩa bao gồm thay thế, mã, và bắt đầu tiểu bang. Mã trong phần định
nghĩa đơn giản là sao chép như-là để phía trên cùng của tập tin C tạo ra và phải được
đặt trong ngoặc vuông với " % {" và " % }" dấu. Thay thế đơn giản hóa quy tắc mô
hình khớp. Ví dụ, chúng ta có thể xác định các chữ số và chữ cái:
digit

[0-9]

letter

[A-Za-z]

%{
int count;
%}
%%

/* match identifier */
{letter}({letter}|{digit})*

count++;

%%
int main(void) {
yylex();
printf("number of identifiers = %d\n", count);
return 0;
}

Khoảng trắng phải tách biệt các thuật ngữ định nghĩa và các biểu hiện liên
quan. Tài liệu tham khảo để thay thế trong phần quy định này được bao quanh bởi dấu
ngoặc ( {letter}) để phân biệt với chữ. Khi chúng ta có một trận đấu trong các quy tắc
phần mã C liên quan được thực hiện. Dưới đây là một máy quét mà đếm số ký tự, từ,
và các dòng trong một tập tin (tương tự như Unix wc):
%{
int nchar, nword, nline;
%}
%%
\n

{ nline++; nchar++; }

[^ \t\n]+

{ nword++, nchar += yyleng; }

SVTH: Nguyễn Văn Nhớ _ 13I2


20


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn
.

{ nchar++; }

%%
int main(void) {
yylex();
printf("%d\t%d\t%d\n", nchar, nword, nline);
return 0;
}

 Tổng quan về Yacc :
Yacc có chức năng đọc cú pháp chứa trong tập tin cú pháp/văn phạm (có tên với
đuôi mở rộng .y được tạo ra từ một trình soạn thảo bất kì) 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ố. Ngữ pháp cho yacc được mô tả bằng cách sử dụng một biến thể của Backus
Naur Form (BNF). Kỹ thuật này, đi tiên phong của John Backus và Peter Naur, đã
được sử dụng để mô tả ALGOL60. Một ngữ pháp BNF thể được sử dụng để thể
hiện ngôn ngữ cảnh Việt ngữ. Hầu hết các cấu trúc trong ngôn ngữ lập trình hiện đại
có thể được đại diện trong BNF. 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. Ví dụ, ngữ pháp cho một biểu thức
đó sẽ nhân và cho biết thêm số là
E -> E + E
E -> E * E

E -> id
Ba tác phẩm đã được chỉ định. Điều khoản xuất hiện ở phía bên tay trái (LHS)
của một sản xuất, chẳng hạn như E (expression) là thuộc đầu cuối. Những từ
như id (định danh) là thiết bị đầu cuối (thẻ trả về bởi lex) và chỉ xuất hiện ở phía bên
phải (RHS) của một sản xuất. Ngữ pháp này quy định rằng một biểu thức có thể là
tổng của hai biểu thức, các sản phẩm của hai biểu thức, hoặc một định danh. Chúng
tôi có thể sử dụng ngữ pháp này để tạo ra các biểu thức:
E -> E * E (r2)
-> E * z (R3)
-> E + E * z (r1)
-> E + y * z (R3)
-> X + y * z (R3)
SVTH: Nguyễn Văn Nhớ _ 13I2

21


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn

Tại mỗi bước chúng tôi mở rộng một hạn và thay thế các LHS của một sản xuất
với RHS tương ứng. Những con số bên phải chỉ ra các quy tắc áp dụng. Để phân tích
một biểu hiện chúng tôi cần phải làm các hoạt động ngược lại. Thay vì bắt đầu với
một (biểu tượng bắt đầu) không thuộc đầu cuối duy nhất và tạo ra một biểu hiện từ
một ngữ pháp chúng ta cần phải giảm một biểu thức để một không thuộc đầu cuối
đơn. Điều này được biết đến như là từ dưới lên hoặc dịch chuyển-giảm phân tích và
sử dụng một ngăn xếp để lưu trữ các điều khoản. Đây là nguồn gốc tương tự nhưng
theo thứ tự ngược:
1

. x + y * z


shift

2

x . + y * z

reduce(r3)

3

E . + y * z

shift

4

E + . y * z

shift

5

E + y . * z

reduce(r3)

6

E + E . * z


shift

7

E + E * . z

shift

8

E + E * z .

reduce(r3)

9

E + E * E .

reduce(r2)

emit multiply

10

E + E .

reduce(r1)

emit add


11

E .

accept

Điều khoản bên trái của dấu chấm là trên stack trong khi đầu vào còn lại là ở bên
phải của dấu chấm. Chúng tôi bắt đầu bằng cách chuyển thẻ vào ngăn xếp. Khi trên
cùng của ngăn xếp phù hợp với RHS của một sản xuất chúng tôi thay thế các thẻ phù
hợp trên các ngăn xếp với LHS của sản xuất.Nói cách khác các thẻ phù hợp của RHS
được popped ra khỏi ngăn xếp, và các LHS sản xuất được đẩy vào stack. Các thẻ phù
hợp được biết đến như một tay cầm và chúng tôi đang làm giảm các xử lý cho các
LHS của sản xuất. Quá trình này tiếp tục cho đến khi chúng tôi đã chuyển tất cả các
đầu vào để ngăn xếp và chỉ không thuộc đầu cuối bắt đầu vẫn còn trên stack. Trong
bước 1 chúng ta dịch chuyển x vào stack. Bước 2 áp dụng quy tắc r3 để ngăn xếp để
thay đổi x để E. Chúng tôi tiếp tục chuyển dịch và giảm cho đến khi không thuộc đầu
cuối đơn, ký hiệu bắt đầu, vẫn còn trong ngăn xếp. Trong bước 9, khi chúng ta giảm
quy tắc r2, chúng phát ra các hướng dẫn nhân. Tương tự như các hướng dẫn add được
phát ra trong bước 10. Do đó nhân có độ ưu tiên cao hơn ngoài.

SVTH: Nguyễn Văn Nhớ _ 13I2

22


Nghiên cứu ứng dụng Lex/Yacc để hỗ trợ phát sinh mã nguồn

Hãy xem xét những thay đổi ở bước 6. Thay vì chuyển chúng ta có thể giảm và
áp dụng các quy tắc r1. Điều này sẽ cho kết quả ngoài việc có một ưu tiên cao hơn so

với phép nhân. Điều này được biết đến như một ca-giảm xung đột. Ngữ pháp của
chúng tôi là không rõ ràng vì có nhiều hơn một nguồn gốc có thể sẽ mang lại sự biểu
hiện. Trong trường hợp này điều hành ưu tiên bị ảnh hưởng. Một ví dụ khác,
associativity trong quy tắc
E -> E + E
là mơ hồ, vì chúng ta có thể recurse vào bên trái hoặc bên phải. Để khắc phục tình
trạng này, chúng ta có thể viết lại ngữ pháp hoặc cung cấp yacc với chỉ thị mà chỉ ra có
độ ưu tiên. Phương pháp thứ hai là đơn giản và sẽ được chứng minh trong phần thực
hành.
Ngữ pháp sau đây có giảm, làm giảm xung đột. Với một id trên stack, chúng tôi
có thể làm giảm tới T hoặc E.
E -> T
E -> id
T -> id
Yacc mất một hành động mặc định khi có một cuộc xung đột. Đối với các cuộc
xung đột shift-giảm yacc sẽ thay đổi. Để giảm-giảm xung đột, nó sẽ sử dụng các
nguyên tắc đầu tiên trong danh sách. Nó cũng đưa ra một thông điệp cảnh báo bất cứ
khi nào một cuộc xung đột tồn tại. Những cảnh báo có thể bị ức chế bằng cách làm cho
ngữ pháp rõ ràng. Một số phương pháp để loại bỏ sự mơ hồ sẽ được trình bày trong
các phần tiếp theo.

SVTH: Nguyễn Văn Nhớ _ 13I2

23


×