Thiết kế trình biên dịch: Phân tích ngữ nghĩa

Thiết kế trình biên dịch là một trình dịch chuyển đổi từ ngôn ngữ nguồn thành ngôn ngữ đối tượng. Trong đó quá trình phân tích ngữ nghĩa được xem là đặc biệt quan trọng. Vậy thì phân tích ngữ nghĩa là gì? Cùng Comdy tìm hiểu trong bài viết này nhé.

Thiết kế trình biên dịch: Phân tích ngữ nghĩa

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ý.

Phân tích ngữ nghĩa là gì?

Phân tích ngữ nghĩa là gì
Phân tích ngữ nghĩa là một phần quan trọng trong quá trình biên dịch

Phân tích ngữ nghĩa là quá trình liên hệ cấu trúc ngữ nghĩa từ cấp độ cụm từ, mệnh đề, câu, đoạn cho đến toàn bài viết và ý nghĩa độc lập của chúng. Hay nói cách khác thì quá trình phân tích nhằm tifm ra ngữ nghĩa của đầu vào ngôn từ.

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.

Các lỗi ngữ nghĩa thường gặp

Các lỗi ngữ nghĩa thường gặp
Các lỗi ngữ nghĩa thường gặp trong lập trình là gì?

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.

Lỗi cú pháp là gì?

Lỗi cũ pháp được hiểu là các lỗi thường xảy ra khi người lập trình vi phạm những quy tắc lập trình của ngôn ngữ. Điều đó khiến cho trình biên dịch không thể thực thi chương trình nếu như các lỗi này không được sửa chữa.

Các lỗi cú pháp thường gặp nhất trong lập trình C++ gồm có: Lỗi chính tả, lỗi khai báo, lỗi về dấu chấm phẩy hoặc ngoặc kép.

Ví dụ:

  • Lỗi thiếu dấu ngoặc {}
#include <iostream>
int main()
    std::cout << “Hello, World!” << std::endl;
    return 0;
  • Lỗi quên khai báo thư viện
int main() {
cout << “Hello, World!” << endl; // Thiếu khai báo thư viện
return 0;
}

Ngữ pháp thuộc tính là gì?

Ngữ pháp thuộc tính là gì
Ngữ pháp thuộc tính là một dạng ngữ pháp đặc biệt không có ngữ cả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 kế thừa

Thuộc tính kế thừa
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

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.

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.

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.

Ngôn ngữ mà máy tính có thể hiểu trực tiếp là gì?

Ngôn ngữ mà máy tính có thể hiểu trực tiếp là gì
Ngôn ngữ mà máy tính có thể hiểu trực tiếp là ngôn ngữ máy

Máy tính có thể hiểu trực tiếp ngôn ngữ máy (Machine Language) hay còn được gọi là mã máy (Machine Code hoặc Machine Language Code). Đây là ngôn ngữ mà bộ xử lý (CPU) của máy tính có thể thực hiện trực tiếp. Mã máy là một chuỗi các lệnh và dữ liệu nhị phân được hiểu và thực hiện bởi các thành phần phần cứng của máy tính.

Mỗi loại CPU hay kiến trúc máy tính sẽ có ngôn ngữ máy riêng biệt. Mã máy là mức thấp nhất của ngôn ngữ máy tính và thường được biểu diễn dưới dạng các dãy số nhị phân. Người lập trình hiếm khi sử dụng mã máy trực tiếp, vì nó làm cho quá trình lập trình trở nên phức tạp và khó bảo trì.

Hợp ngữ là ngôn ngữ gì?

Hợp ngữ là một ngôn ngữ lập trình có mức độ trừu tượng hóa thấp, chặn giới hạn so với ngôn ngữ máy, nhưng vẫn dễ đọc và hiểu hơn rất nhiều so với mã máy trực tiếp. Ngôn ngữ này sử dụng các từ khóa và từ ngữ có thể hiểu được bởi con người, tương ứng với các lệnh máy cụ thể.

Hợp ngữ thường gắn liền với kiến trúc máy cụ thể và mỗi lệnh hợp ngữ thường tương ứng với một lệnh máy duy nhất. Mỗi kiến trúc máy có thể có một hợp ngữ riêng, và vì vậy, mã hợp ngữ của một máy tính không thể chạy trên máy tính với kiến trúc khác mà không cần sửa đổi.

Ví dụ:

section .data
    message db ‘Hello, World!’, 0
section .text
    global _start
_start:
    ; syscall to write message to stdout
    mov eax, 4
    mov ebx, 1
    mov ecx, message
    mov edx, 13
    int 0x80
    ; syscall to exit program
    mov eax, 1
    xor ebx, ebx
    int 0x80

Kết lại

Trên đây là toàn bộ chia sẻ của Comdy về trình biên dịch phân tích ngữ nghĩa trong lập trình. Hi vọng những thông tin, kiến thức trong bài viết này là bổ ích và có ý nghĩa với mọi người. Xin cám ơn.

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *