Cách xác định lỗi cú pháp và lỗi ngữ nghĩa

Mỗi ngôn ngữ lập trình thường có ba thành phần cơ bản: bảng chữ cái, cú pháp và ngữ nghĩa.

a. Bảng chữ cái: Là tập các kí hiệu dùng để viết chương trình.

- Trong ngôn ngữ Pascal bảng chữ cái gồm: Các chữ cái trong bảng chữ cái tiếng Anh, các chữ số 0 -> 9 và một số kí tự đặc biệt (SGK)

b. Cú pháp: là bộ qui tắc dùng để viết chương trình.

c . Ngữ nghĩa: xác định ý nghĩa thao tác cần phải thực hiên , ứng với tổ hợp kí tự dựa vào ngữ cảnh của nó

-  Cú pháp cho biết cách viết một chương trình hợp lệ , còn ngữ nghĩa xác định ý nghĩa của các tổ hợp kí tự trong chương trình.

 -  Lỗi cú pháp được chương trình dịch phát hiện và thông báo cho người lập chương trình biết , chỉ có các chương trình không còn lỗi cú pháp mới có thể được dịch sang ngôn ngữ máy.

- Lỗi ngữ nghĩa chỉ được phát hiện khi thực hiện chương trình trên dữ liệu  cụ thể .

2. Một số khái niệm
a. Tên

Mọi đối tượng trong chương trình đều phải được  đặt tên theo quy tắc của ngôn ngữ lập trình và từng chương trình dịch cụ thể

Trong Turbo Pascal, tên là một dãy liên tiếp không quá 127 kí tự bao gồm chữ số , chữ cái hoặc dấu gạch dưới.

Trong chương trình dịch Free Pascal, tên có thể có độ dài tới 255 kí tự

Ngôn ngữ pascal không phân biệt chữ hoa, chữ thường trong tên. Một số ngôn ngữ lập trình khác (ví dụ C++) phân biệt chữ hoa, chữ thường

Tên không bắt đầu bằng chữ số, không chứa dấu cách, không chứa kí tự đặt biệt

Nhiều ngôn ngữ lập trình, trong đó có pascal, phân biệt ba loại tên .

    - Tên dành riêng

    - Tên chuẩn

    - Tên cho người lập trình đặt

Tên  dành riêng :

+ Là những tên được ngôn ngữ lập trình quy định với ý nghĩa xác định

Mà người lập trình không thể dùng với ý nghĩa khác.

+ Tên dành riêng còn được gọi là từ khóa

Ví dụ : Một số từ khóa

  Trong ngôn ngữ Pascal: program, var, uses, Begin, End…

 Trong ngôn ngữ C++: main, include, while, void…

Tên chuẩn

+ Là những tên được NNLT dùng với ý nghĩa nào đó trong các thư viện của NNLT, tuy nhiên người lập trình có thể sử dụng với ý nghĩa khác

+ Tên dành riêng còn được gọi là từ khóa.

Ví dụ Một số tên chuẩn

- Trong ngôn ngữ Pascal: Real, lnteger, Sin , Cos, Char…

- Trong ngôn ngữ C++: cin, cout, getchar…

Tên do người lập trình tự đặt

- Được xác định bằng cách khai báo trước khi sử dụng và không được trùng với tên dành riêng

- Các tên trong chương trình không được trùng nhau

b. Hằng và biến

Hằng: là các đại lượng có giá trị không đổi trong quá trình  thực hiên chương trình

- Các ngôn ngữ lập trình thường có:

+ Hằng số học : số nguyên hoặc số thực

+ Hằng xâu: là chuổi kí tự đặt trong cặp dấu nháy đơn “hoặc  cặp dấu nháy kép tùy theo NNLT“’’. Trong pascal hằng đặt trong cặp nháy đơn.

+ Hằng logic: là  các giá trị đúng hoặc sai

Biến:

  - Là đại lượng  được đặt tên , giá trị có thể thay đổi được trong chương trình

 - Các NNLT có nhiều loại biến khác nhau

 -  Biến phải khai báo trước khi sử dụng

c. Chú thích :

-  Trong khi viết chương trình có thể viết các chú thích cho chương trình. Chú thích không làm ảnh hưởng đến chương trình

-  Trong pascal chú thích được đặt trong  (và) hoặc (*và*)

-  Trong C++chú thích đặt trong /* và */.

Củng cố - dặn dò:

Ø    Nhắc lại một số khái niệm mới, ra bài tập về nhà.

Ø    Hãy cho biết các tên sau đây, tên nào đúng tên nào sai?

A{B}, AB{C, AB{C}, 12AB(*C*), _AB(*C*).

Chúng ta đã tìm hiểu cách trình phân tích cú pháp xây dựng cây phân tích cú pháp trong giai đoạn phân tích cú pháp.

Cây phân tích cú pháp đơn giản được xây dựng trong giai đoạn đó thường không sử dụng cho trình biên dịch, vì nó không mang bất kỳ thông tin nào về cách đánh giá cây.

Việc tạo ra ngữ pháp không có ngữ cảnh, tạo ra các quy tắc của ngôn ngữ, không phù hợp với cách diễn giải chúng.

Ví dụ:

E → E + T

Quá trình xử lý CFG ở trên không có quy tắc ngữ nghĩa nào đi kèm với nó và nó không thể giúp ích cho việc tạo ra bất kỳ ý nghĩa nào cho quá trình xử lý.

Nếu bỏ lỡ hướng dẫn này thì bạn có thể xem tại đây:

Thiết kế trình biên dịch: Phân tích cụ pháp

Trong hướng dẫn này, bạn sẽ tìm hiểu chi tiết về phân tích cu pháp và trình phân tích cú pháp trong thiết kế trình biên dịch.

Ngữ nghĩa

Ngữ nghĩa của một ngôn ngữ cung cấp ý nghĩa cho các cấu trúc của nó, như mã thông báo và cấu trúc cú pháp. Ngữ nghĩa học giúp giải thích các ký hiệu, kiểu của chúng và mối quan hệ của chúng với nhau. Phân tích ngữ nghĩa đánh giá liệu cấu trúc cú pháp được xây dựng trong chương trình nguồn có phát sinh bất kỳ ý nghĩa nào hay không.

CFG + semantic rules = Syntax Directed Definitions

Ví dụ:

int a = “value”;

không nên đưa ra lỗi trong giai đoạn phân tích từ vựng và cú pháp, vì nó đúng về mặt từ vựng và cấu trúc, nhưng nó sẽ tạo ra lỗi ngữ nghĩa vì kiểu dữ liệu của phép gán khác nhau.

Các quy tắc này được thiết lập bởi ngữ pháp của ngôn ngữ và được đánh giá trong phân tích ngữ nghĩa.

Các tác vụ sau đây cần được thực hiện trong phân tích ngữ nghĩa:

  • Phân giải phạm vi
  • Kiểm tra kiểu dữ liệu.
  • Kiểm tra giới hạn mảng.

Lỗi ngữ nghĩa

Chúng tôi đã đề cập đến một số lỗi ngữ nghĩa mà trình phân tích ngữ nghĩa sẽ nhận ra:

  • Kiểu dữ liệu không phù hợp.
  • Biến không được khai báo.
  • Sử dụng sai định danh dành riêng..
  • Khai báo trùng biến trong một phạm vi.
  • Truy cập một biến ngoài phạm vi.
  • Thông số thực tế và thông số chính thức không khớp.

Ngữ pháp thuộc tính

Ngữ pháp thuộc tính là một dạng ngữ pháp đặc biệt không có ngữ cảnh, trong đó một số thông tin bổ sung (thuộc tính) được nối vào một hoặc nhiều đầu cuối không phải của nó để cung cấp thông tin nhạy cảm theo ngữ cảnh.

Mỗi thuộc tính có miền giá trị được xác định rõ ràng, chẳng hạn như số nguyên, số thập phân, ký tự, chuỗi và biểu thức.

Ngữ pháp thuộc tính là một phương tiện để cung cấp ngữ nghĩa cho ngữ pháp không có ngữ cảnh và nó có thể giúp xác định cú pháp và ngữ nghĩa của một ngôn ngữ lập trình.

Ngữ pháp thuộc tính (khi được xem như một cây phân tích cú pháp) có thể chuyển các giá trị hoặc thông tin giữa các nút của cây.

Ví dụ:

E → E + T { E.value = E.value + T.value }

Phần bên phải của CFG chứa các quy tắc ngữ nghĩa chỉ định cách diễn giải ngữ pháp. Ở đây, các giá trị của không phải đầu cuối E và T được cộng lại với nhau và kết quả được sao chép vào đầu cuối E.

Các thuộc tính ngữ nghĩa có thể được gán cho các giá trị của chúng từ miền của chúng tại thời điểm phân tích cú pháp và đánh giá tại thời điểm gán hoặc điều kiện.

Dựa trên cách các thuộc tính nhận được giá trị của chúng, chúng có thể được chia thành hai loại: thuộc tính tổng hợp và thuộc tính kế thừa.

Thuộc tính tổng hợp

Các thuộc tính này nhận giá trị từ các giá trị thuộc tính của các nút con của chúng. Để minh họa, hãy giả sử sản xuất sau:

S → ABC

Nếu S đang nhận các giá trị từ các nút con của nó (A, B, C), thì nó được cho là một thuộc tính tổng hợp, vì các giá trị của ABC được tổng hợp thành S.

Như trong ví dụ trước của chúng ta (E → E + T), nút cha E nhận giá trị từ nút con của nó. Các thuộc tính tổng hợp không bao giờ lấy giá trị từ các nút cha của chúng hoặc bất kỳ nút anh em nào.

Thuộc tính kế thừa

Ngược lại với các thuộc tính tổng hợp, các thuộc tính kế thừa có thể lấy giá trị từ các nút cha và / hoặc anh em. Như trong xử lý sau,

S → ABC

A có thể nhận giá trị từ S, B và C. B có thể nhận giá trị từ S, A và C. Tương tự như vậy, C có thể nhận giá trị từ S, A và B.

Mở rộng: Khi một non-terminal được mở rộng thành các đầu cuối (terminal) theo quy tắc ngữ pháp:

Giảm: Khi một đầu cuối (terminal) được giảm thành non-terminal tương ứng của nó theo các quy tắc ngữ pháp. Cây cú pháp được phân tích cú pháp từ trên xuống và từ trái sang phải. Bất cứ khi nào việc giảm xảy ra, chúng tôi áp dụng các quy tắc ngữ nghĩa tương ứng (hành động) của nó.

Phân tích ngữ nghĩa sử dụng bản dịch theo hướng cú pháp để thực hiện các nhiệm vụ trên.

Trình phân tích ngữ nghĩa nhận AST (Cây cú pháp trừu tượng) từ giai đoạn trước của nó (phân tích cú pháp).

Trình phân tích ngữ nghĩa đính kèm thông tin thuộc tính với AST, được gọi là AST thuộc tính.

Các thuộc tính là cặp giá trị, <tên thuộc tính, giá trị thuộc tính>

Ví dụ:

int value = 5; <type, “integer”> <presentvalue, “5”>

Đối với mỗi xử lý, chúng tôi đính kèm một quy tắc ngữ nghĩa.

SDT được phân bổ theo S

Nếu một SDT chỉ sử dụng các thuộc tính tổng hợp, nó được gọi là SDT được phân bổ theo S. Các thuộc tính này được đánh giá bằng cách sử dụng các SDT do S phân bổ có các hành động ngữ nghĩa của chúng được viết sau khi xử lý (phía bên phải).

Như được mô tả ở trên, các thuộc tính trong SDT do S phân bổ được đánh giá trong phân tích cú pháp từ dưới lên, vì giá trị của các nút cha phụ thuộc vào giá trị của các nút con.

SDT được phân bổ theo L

Dạng SDT này sử dụng cả thuộc tính tổng hợp và thuộc tính kế thừa với hạn chế là không lấy giá trị từ các anh em bên phải.

Trong SDT được phân bổ theo L, một đầu cuối (terminal) không phải là non-terminal có thể nhận các giá trị từ các nút cha, con và anh em của nó. Như trong xử lý sau

S → ABC

S có thể nhận các giá trị từ A, B và C (tổng hợp). A chỉ có thể nhận các giá trị từ S. B có thể nhận các giá trị từ S và A. C có thể nhận các giá trị từ S, A và B. Không có đầu cuối nào có thể nhận các giá trị từ phần tử anh em ở bên phải của nó.

Các thuộc tính trong SDT do L phân bổ được đánh giá theo cách phân tích cú pháp theo chiều sâu từ trái sang phải.

Chúng ta có thể kết luận rằng nếu một định nghĩa được quy cho S, thì nó cũng được quy cho L vì định nghĩa được quy cho L bao gồm các định nghĩa được quy cho S.

Trong hướng dẫn tiếp theo, bạn sẽ tìm hiểu vì sao cần phải tạo mã trung gian? Cách tạo mã trung gian trong thiết kế trình biên dịch.

Thiết kế trình biên dịch: Tạo mã trung gian

Trong hướng dẫn này, bạn sẽ tìm hiểu vì sao cần phải tạo mã trung gian? Cách tạo mã trung gian trong thiết kế trình biên dịch.

Video liên quan

Chủ đề