Mối quan hệ giữa ngôn ngữ lập trình, biểu thức chính quy và ngôn ngữ chính thức là gì


25

Tôi đã tìm trên mạng để tìm câu trả lời cho câu hỏi này và dường như mọi người đều ngầm biết câu trả lời trừ tôi. Có lẽ điều này là bởi vì những người duy nhất quan tâm là những người đã có giáo dục đại học về chủ đề này. Tôi, mặt khác, đã bị ném vào tận cùng cho một nhiệm vụ trung học.

Câu hỏi của tôi là, chính xác các ngôn ngữ lập trình liên quan đến ngôn ngữ chính thức như thế nào? Ở mọi nơi tôi đọc, một cái gì đó dọc theo dòng "ngôn ngữ chính thức được sử dụng để xác định ngữ pháp của ngôn ngữ lập trình" được nói.

Bây giờ từ những gì tôi có thể thu thập được, một ngôn ngữ chính thức là một loạt các quy tắc sản xuất áp dụng cho một bộ ký hiệu cụ thể (bảng chữ cái của ngôn ngữ). Các quy tắc sản xuất này xác định một tập hợp các biến đổi, chẳng hạn như:

b -> a

aaa->c

Điều này có thể được áp dụng sao cho:

abab->aaaa aaaa-> ca

Cũng như một ghi chú bên cạnh, nếu chúng tôi xác định rằng bảng chữ cái của ngôn ngữ chính thức của chúng tôi là {a, b, c}, thì a và b không phải là thiết bị đầu cuối và c là thiết bị đầu cuối vì nó không thể được chuyển đổi (vui lòng sửa cho tôi nếu tôi sai về cái đó).

Vì vậy, với tất cả những điều đó, làm thế nào điều này áp dụng cho các ngôn ngữ lập trình? Thông thường người ta cũng nói rằng regex được sử dụng để phân tích một ngôn ngữ ở dạng văn bản của nó để đảm bảo ngữ pháp là chính xác. Điều này thật ý nghĩa. Sau đó, nó được tuyên bố rằng regex được xác định bởi các ngôn ngữ chính thức. Regex trả về đúng hay sai (ít nhất là theo kinh nghiệm của tôi) tùy thuộc vào việc automata trạng thái hữu hạn đại diện cho regex đạt đến điểm mục tiêu. Theo như tôi có thể thấy, điều đó không liên quan gì đến các phép biến đổi *.

Để biên dịch chương trình, tôi cho rằng một ngôn ngữ chính thức sẽ có thể chuyển đổi mã thành mã cấp thấp hơn liên tiếp, cuối cùng đạt đến sự lắp ráp thông qua một bộ quy tắc phức tạp mà phần cứng có thể hiểu được.

Vì vậy, đó là những điều từ quan điểm nhầm lẫn của tôi. Có lẽ có rất nhiều điều sai về cơ bản với những gì tôi đã nói, và đó là lý do tại sao tôi đang yêu cầu giúp đỡ.


* Trừ khi bạn coi một cái gì đó giống như (a|b)*b*c->truelà một quy tắc sản xuất, trong trường hợp đó quy tắc yêu cầu một automata trạng thái hữu hạn (ví dụ: regex). Điều này vô nghĩa khi chúng ta vừa nói rằng


2
Bạn đang nhầm lẫn ngữ pháp chính thức với các ngôn ngữ chính thức . Một ngữ pháp là một tập hợp các quy tắc viết lại mô tả một ngôn ngữ. Ngôn ngữ là tập hợp các chuỗi được mô tả bởi ngữ pháp. Vì vậy, một ngữ pháp là một thay thế cho một biểu thức thông thường: đó là một cách để mô tả một ngôn ngữ.
rebierpost

@reinierpost Bạn hoàn toàn đúng, sau khi xem qua các bài giảng của trường đại học tôi nhận được một số thông tin này từ, tôi thấy lỗi của mình.
Zwander

Tôi đã chia sẻ sự nhầm lẫn của bạn khi tôi bắt đầu. Tất nhiên, ngữ pháp cũng tạo thành một ngôn ngữ và các biểu thức chính quy cũng vậy. Nhưng lý thuyết ngôn ngữ chính thức được dành cho việc nghiên cứu làm thế nào cú pháp (hình thức) của ngôn ngữ có thể được mô tả, vì vậy nó thường sử dụng thuật ngữ 'ngôn ngữ' cho những gì được mô tả, chứ không phải những gì được mô tả.
Revierpost

Câu trả lời:


24

Bất cứ ai nói với bạn rằng các biểu thức thông thường được sử dụng để phân tích mã đã lan truyền thông tin sai lệch. Về mặt kinh điển (tôi không biết mức độ nào là đúng trong trình biên dịch hiện đại), việc phân tích mã - chuyển đổi mã từ văn bản sang cây cú pháp - bao gồm hai giai đoạn:

  1. Phân tích từ vựng: Xử lý văn bản thô thành các đoạn như từ khóa , hằng số , chuỗi , định danh , v.v. Điều này được thực hiện một cách cổ điển bằng cách sử dụng một số loại máy trạng thái hữu hạn, tương tự như tinh thần tự động hữu hạn xác định (DFA).

  2. Trình phân tích cú pháp: Chạy sau khi phân tích từ vựng và chuyển đổi văn bản thô thành cây cú pháp có chú thích. Ngữ pháp của các ngôn ngữ lập trình là (gần đúng đầu tiên) không có ngữ cảnh (thực ra, người ta cần một tập hợp con thậm chí chặt chẽ hơn) và điều này cho phép các thuật toán hiệu quả nhất định phân tích mã từ vựng thành một cây cú pháp. Điều này tương tự như vấn đề nhận biết một chuỗi đã cho có thuộc về ngữ pháp không ngữ cảnh hay không, sự khác biệt là chúng tôi cũng muốn chứng minh dưới dạng cây cú pháp.

Các ngữ pháp cho các ngôn ngữ lập trình được viết dưới dạng các ngữ pháp không ngữ cảnh và biểu diễn này được sử dụng bởi các trình tạo trình phân tích cú pháp để xây dựng các trình phân tích cú pháp nhanh cho chúng. Một ví dụ đơn giản sẽ có một số TUYÊN BỐ không đầu cuối và sau đó là các quy tắc của mẫu STATMENT IF-STATMENT, trong đó IF-STATMENT if CONDATION rồi BLOCK endif, hoặc tương tự (trong đó BLOCK STATMENT | BLOCK; STATMENT, cho thí dụ). Thông thường các ngữ pháp này được chỉ định ở dạng Backus-Naur (BNF).

Các thông số kỹ thuật thực tế của ngôn ngữ lập trình không có ngữ cảnh. Ví dụ: một biến không thể xuất hiện nếu nó chưa được khai báo bằng nhiều ngôn ngữ và các ngôn ngữ có kiểu gõ nghiêm ngặt có thể không cho phép bạn gán một số nguyên cho biến chuỗi. Công việc của trình phân tích cú pháp chỉ là chuyển đổi mã thô thành một dạng dễ xử lý hơn.

Tôi nên đề cập rằng có các cách tiếp cận khác như phân tích cú pháp gốc đệ quy không thực sự tạo ra một cây phân tích cú pháp, nhưng xử lý mã của bạn khi nó phân tích cú pháp. Mặc dù nó không bận tâm để tạo ra cây, nhưng trong tất cả các khía cạnh khác, nó hoạt động ở cùng cấp độ như được mô tả ở trên.


Cảm ơn bạn đã trả lời của bạn, nó chắc chắn đã xóa một vài điều lên. Nó cũng mang lại nhiều câu hỏi hơn. Tôi có nên nối chúng với câu hỏi của tôi, hoặc hỏi họ ở đây?
Zwander

5
@Zwander - thực ra, cũng không. Trên trang web này, chúng tôi muốn bạn viết một câu hỏi cho mỗi câu hỏi. Đây không phải là một diễn đàn thảo luận: đó là một trang web hỏi và trả lời, và chúng tôi muốn mỗi câu hỏi nằm trong một chuỗi riêng biệt. Nếu câu trả lời này đặt ra một câu hỏi mới, thì hãy dành thời gian nghiên cứu câu hỏi tiếp theo đó và nếu bạn không thể tìm thấy câu trả lời trong bất kỳ nguồn tiêu chuẩn nào, hãy đăng câu hỏi mới. (Nhưng hãy đảm bảo xem xét các tài nguyên tiêu chuẩn trước.)
DW

1
@DW Gotcha, chúc mừng.
Zwander

3
Giai đoạn đầu tiên trong hai giai đoạn mà bạn đề cập thường được thực hiện bằng cách sử dụng các biểu thức thông thường. Định dạng của mỗi mã thông báo thường được đưa ra bởi một biểu thức thông thường. Các biểu thức chính quy đó được biên dịch thành một DFA duy nhất, DFA sau đó được áp dụng cho mã thực tế.
kasperd

2
@Zwander Phân tích cú pháp gốc đệ quy chỉ là một kỹ thuật phân tích cú pháp. Nó có thể hoặc không thể tạo ra một cây phân tích cú pháp. Nói chung, thuật toán phân tích số lượng để phát triển một chiến lược tính toán để khám phá ẩn cây cú pháp trong văn bản chương trình. Cây cú pháp / phân tích cú pháp này có thể hoặc không thể được khám phá trong quá trình, tùy thuộc vào chiến lược biên dịch (số lượng giai đoạn). Điều cần thiết là cuối cùng có ít nhất một cuộc thăm dò từ dưới lên của cây phân tích, cho dù được khám phá hay để lại ẩn trong cấu trúc tính toán.
babou

12

Đây là một số thứ nặng cho một bài tập trung học.

Câu trả lời của Yuval Filmus thực sự tốt, vì vậy đây là câu trả lời bổ sung để làm rõ một số điểm mà anh ấy đưa ra.

Một ngôn ngữ chính thức là một công trình toán học. Việc sử dụng chúng cho các ngôn ngữ lập trình chỉ là một trong nhiều cách sử dụng có thể; trên thực tế, nhà ngôn ngữ học Noam Chomsky đã có những đóng góp đáng kể cho lý thuyết ban đầu về ngôn ngữ chính thức. Ông đã phát minh ra thứ bậc Chomsky, trong đó phân loại các ngôn ngữ chính thức thành ngôn ngữ thông thường, không ngữ cảnh, vv Các ngôn ngữ chính thức cũng được áp dụng trong ngôn ngữ học để mô tả cú pháp của các ngôn ngữ tự nhiên như tiếng Anh. Hãy nghĩ về nó giống như những con số thực: chúng ta có thể sử dụng những con số thực để mô tả cả những thứ cụ thể như khoảng cách từ Los Angeles đến New York và những thứ trừu tượng như tỷ lệ chu vi của một vòng tròn với đường kính của nó. Mặc dù cả hai thứ đó đều tồn tại độc lập với số thực, nhưng số thực là một hệ thống hữu ích để mô tả chúng. Ngôn ngữ chính thức là một hệ thống hữu ích để mô tả cả tiếng Anh và Python, vì cả hai đều có định dạng cấu trúc tương tự nhau.

a+b+c=da+b=dcabc như số tiền đô la, ví dụ, và sau đó phương trình có ý nghĩa.

Về mặt kinh điển, một ngôn ngữ lập trình sẽ có hai ngữ pháp: ngữ pháp từ vựng và ngữ pháp cú pháp. Ngữ pháp từ vựng liên quan đến các ký tự như chữ cái, dấu chấm phẩy, dấu ngoặc nhọn và dấu ngoặc đơn. Nó thường là một ngữ pháp thông thường, vì vậy nó có thể được diễn đạt bằng các biểu thức thông thường hoặc DFA hoặc NFA. (Có bằng chứng trong lý thuyết ngôn ngữ chính thức cho thấy ba ngôn ngữ tương đương với sức mạnh có nghĩa là họ chấp nhận cùng một bộ ngôn ngữ.) Giai đoạn lexing của trình biên dịch hoặc trình thông dịch là một trình thông dịch nhỏ cho ngữ pháp thông thường. Nó đọc các quy tắc của ngữ pháp và tuân theo các quy tắc đó, nó gộp các ký tự riêng lẻ thành các mã thông báo. Ví dụ: nếu ngôn ngữ có ifcâu lệnh trông giống hoặc ít hơn C, thì từ vựng có thể gộp các ký tự ifvào một mã thông báo duy nhấtIF, sau đó tìm dấu ngoặc đơn mở và xuất mã thông báo OPEN_PAREN, sau đó xử lý bất cứ điều gì giữa các dấu ngoặc đơn và sau đó tìm dấu ngoặc đơn đóng và đầu ra a CLOSE_PAREN. Khi các nhà ngoại giao thực hiện việc tạo mã thông báo, nó sẽ trao cho họ bộ phân tích cú pháp, xác định xem các mã thông báo có thực sự tạo thành các câu lệnh hợp lệ của ngôn ngữ lập trình hay không. Vì vậy, nếu bạn viết ip a == bbằng Python, lexer chỉ cố gắng đoán loại mã thông báo iplà gì (có lẽ nó sẽ được sử dụng cho một mã định danh bởi hầu hết các từ vựng) và chuyển nó cho trình phân tích cú pháp, vì nó không thể có định danh ở vị trí đó.

ab

Chúng ta hãy xem các quy tắc ngữ pháp cho iftuyên bố của Python . Đây là quy tắc:

if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]

Quy tắc đó cho chúng ta biết làm thế nào trình phân tích cú pháp sẽ tìm ra nếu một chuỗi mã thông báo được gửi từ lexer là một if-statement. Bất kỳ từ nào trong dấu ngoặc đơn cần xuất hiện, giống như vậy, trong mã nguồn, vì vậy trình phân tích cú pháp sẽ tìm từ đơn giản if. Trình phân tích cú pháp sau đó sẽ cố gắng khớp một số mã thông báo với quy tắc cho test:

test: or_test ['if' or_test 'else' test] | lambdef

testđược định nghĩa theo các quy tắc khác trong ngữ pháp. Lưu ý làm thế nào testcũng bao gồm chính nó trong định nghĩa của nó; đó gọi là định nghĩa đệ quy. Đó là sức mạnh lớn của các ngôn ngữ không ngữ cảnh mà các ngôn ngữ thông thường không có, và nó cho phép những thứ như các vòng lặp lồng nhau được xác định cho cú pháp ngôn ngữ lập trình.

Nếu trình phân tích cú pháp quản lý để khớp một số mã thông báo test, nó sẽ cố gắng khớp với dấu hai chấm. Nếu điều đó thành công, nó sẽ cố gắng khớp một số mã thông báo khác bằng cách sử dụng quy tắc cho suite. Phần này ('elif' test ':' suite)*có nghĩa là chúng ta có thể có bất kỳ số lần lặp lại của văn bản chữ elif, theo sau là một cái gì đó phù hợp test, tiếp theo là dấu hai chấm, tiếp theo là một cái gì đó phù hợp suite. Chúng ta cũng có thể có số lần lặp lại bằng không; dấu hoa thị ở cuối có nghĩa là "không hoặc nhiều như chúng ta muốn".

Cuối cùng là vậy ['else' ':' suite]. Phần đó được đặt trong dấu ngoặc vuông; điều đó có nghĩa là chúng ta có thể có không hoặc một, nhưng không còn nữa. Để phù hợp với điều này, trình phân tích cú pháp cần phải khớp văn bản bằng chữ else, dấu hai chấm và sau đó a suite. Đây là quy tắc cho một suite:

suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT

Về cơ bản, nó là một khối trong các ngôn ngữ giống như C. Kể từ khi Python sử dụng dòng mới và thụt đầu dòng để điều trị trung bình, các kết quả đầu ra lexer NEWLINE, INDENTDEDENTthẻ để nói với các phân tích cú pháp, nơi một dòng mới bắt đầu, nơi mã bắt đầu được thụt vào, và nơi nó được quay trở lại mức ngoài của thụt đầu dòng.

Nếu bất kỳ nỗ lực đối sánh nào thất bại, trình phân tích cú pháp sẽ đánh dấu lỗi và dừng lại. Nếu quá trình phân tích cú pháp của toàn bộ chương trình thành công, trình phân tích cú pháp thường sẽ xây dựng một cây phân tích như Yuval trong câu trả lời của anh ta, và có thể là một bảng biểu tượng và các cấu trúc dữ liệu khác lưu trữ thông tin ngữ nghĩa. Nếu ngôn ngữ được gõ tĩnh, trình biên dịch sẽ chuyển cây phân tích cú pháp và tìm lỗi loại. Nó cũng đi trên cây phân tích để tạo mã mức thấp (ngôn ngữ hợp ngữ, mã byte Java, Ngôn ngữ trung gian .Net hoặc một cái gì đó tương tự) là những gì thực sự chạy.

Như một bài tập, tôi khuyên bạn nên sử dụng ngữ pháp của một số ngôn ngữ lập trình mà bạn quen thuộc (một lần nữa, Python , Java và đây là C # , Javascript , C ) và thử phân tích cú pháp một cái gì đó đơn giản, như có thể x = a + b;hoặc if (True): print("Yay!"). Nếu bạn đang tìm kiếm một cái gì đó đơn giản hơn, thì cũng có một ngữ pháp hay cho JSON , về cơ bản chỉ bao gồm cú pháp cho các đối tượng bằng chữ trong Javascript (như {'a': 1, 'b': 2}). Chúc may mắn, đây là công cụ uốn não nhưng hóa ra nó thực sự thú vị khi bạn không ở một thời hạn điên rồ nào đó.


Tôi biết tôi không nên đăng "cảm ơn" ở đây, nhưng chúc mừng vì đã dành thời gian để giải thích tất cả những điều này. "Đây là một số thứ nặng nề cho một bài tập trung học." Mục đích của bài tập là lướt qua đầu và nói về các biểu thức thông thường, nhưng là một sinh viên khoa học máy tính khao khát, tôi muốn có được toàn bộ bức tranh. Toàn bộ chủ đề là hấp dẫn.
Zwander

1
@Zwander Tôi vừa tốt nghiệp đại học, và hầu hết các môn tự chọn của tôi là những thứ như thế này. Tôi nhớ là hoàn toàn bối rối và hoàn toàn bị hấp thụ. Bạn cũng có thể thích các bài viết về thiết kế trình biên dịch được đề cập trong blog này , hoặc các cuốn sách Giới thiệu về Lý thuyết tính toán , của Michael Sipser, và John C. Martin, Giới thiệu về Ngôn ngữ và Lý thuyết tính toán . Bạn có thể tìm thấy các bản sao được sử dụng giá rẻ trên Amazon. Cả hai làm cho lý thuyết ngôn ngữ chính thức về đơn giản như nó sẽ nhận được.
tsleyon

10

Tóm lại

Các ngôn ngữ lập trình bao gồm một cú pháp đại diện cho chương trình dưới dạng các chuỗi ký tự và một ngữ nghĩa là ý nghĩa dự định của chương trình.

Ngôn ngữ chính thức là cú pháp không có ý nghĩa. Nó có nghĩa là để nghiên cứu cấu trúc của các chuỗi được xác định chính thức, mà không thường gắn ý nghĩa với các chuỗi đó.

Biểu thức chính quy và các hình thức chính thức khác (như Ngữ pháp không ngữ cảnh) được sử dụng để định nghĩa các ngôn ngữ chính thức, được sử dụng làm thành phần cú pháp của lập trình và ngôn ngữ tự nhiên, nghĩa là biểu diễn các câu theo cách có cấu trúc. Các cơ chế khác được sử dụng để liên kết cấu trúc đó với ngữ nghĩa của các ngôn ngữ lập trình.

Phần lớn ở đây được đơn giản hóa đáng kể, đặc biệt là về ngôn ngữ tự nhiên.

Với nhiều chi tiết hơn

Để trả lời câu hỏi của bạn, chúng ta nên bắt đầu lại từ đầu. Một ngôn ngữ theo nghĩa thông thường là, một cách không chính thức, là một phương tiện để truyền đạt thông tin hoặc ý tưởng. Trong một ngôn ngữ, người ta thường phân biệt giữa cú pháp và ngữ nghĩa. Ngữ nghĩa là những gì bạn muốn nói / viết về. thông tin bạn muốn truyền đạt. Cú pháp là phương tiện bạn sử dụng để truyền đạt nó, tức là một đại diện thông thường có thể trao đổi giữa người với người và bây giờ cũng là giữa người và thiết bị hoặc giữa các thiết bị (máy tính).

Thông thường, bạn sẽ sử dụng từ này dogđể truyền đạt ý tưởng của một con chó. Từ dognày được tạo thành từ ba chữ cái, hoặc một số âm thanh tương đương, và được dự định là đại diện của một loại động vật. Ý tưởng chính là giao tiếp được thực hiện thông qua việc thể hiện những gì sẽ được truyền đạt. Các cấu trúc biểu diễn thường được gọi là cú pháp, trong khi những gì được biểu diễn được gọi là ngữ nghĩa. Điều này ít nhiều xảy ra đối với ngôn ngữ tự nhiên cũng như ngôn ngữ lập trình.

Các từ là các thực thể cú pháp để thể hiện các khái niệm ngữ nghĩa cơ bản ít nhiều. Nhưng những khái niệm cơ bản này phải được kết hợp theo nhiều cách khác nhau để mang lại ý nghĩa phức tạp hơn. Chúng tôi viết the dogđể truyền đạt rằng chúng tôi có nghĩa là một con chó cụ thể, và the dog bites the catđể truyền đạt một ý tưởng phức tạp hơn. Nhưng cách các từ được sắp xếp phải được cố định bởi các quy tắc, để chúng ta có thể biết con chó và con mèo nào thực sự cắn con kia.

Vì vậy, chúng tôi có các quy tắc như sentence -> subject verb complementđược cho là khớp các câu và cho chúng tôi biết các ý tưởng liên quan đến từng phần được khớp nối như thế nào. Các quy tắc này là quy tắc cú pháp, vì chúng cho chúng ta biết cách tổ chức thông điệp của chúng ta. Bản subjectthân nó có thể được xác định bởi một quy tắc subject -> article noun, v.v.

2x+1=23x123

equation -> expression "=" expression  
expression -> expression "+" expression 
expression -> number

Cấu trúc của ngôn ngữ lập trình là như nhau. Ngôn ngữ lập trình là chuyên ngành ngữ nghĩa trong việc diễn đạt các tính toán sẽ được thực hiện, thay vì diễn đạt các vấn đề cần giải quyết, bằng chứng về các định lý hoặc quan hệ thân thiện giữa động vật. Nhưng đó là sự khác biệt chính.

Các đại diện được sử dụng trong cú pháp thường là các chuỗi ký tự hoặc âm thanh cho các ngôn ngữ nói. Ngữ nghĩa thường thuộc về miền trừu tượng, hoặc có thể là thực tế, nhưng vẫn được trừu tượng hóa trong các quá trình suy nghĩ của chúng ta hoặc thuộc miền hành vi của các thiết bị. Truyền thông đòi hỏi phải mã hóa thông tin / ý tưởng thành cú pháp, được truyền và giải mã bởi người nhận. Kết quả sau đó được giải thích theo bất cứ cách nào bởi người nhận.

Vì vậy, những gì chúng ta thấy của ngôn ngữ chủ yếu là cú pháp và cấu trúc của nó. Ví dụ trên chỉ là một trong những cách phổ biến nhất để xác định chuỗi cú pháp và tổ chức cấu trúc của chúng. Co nhung nguoi khac. Đối với một ngôn ngữ nhất định, một số chuỗi có thể được gán một cấu trúc và được cho là thuộc về ngôn ngữ, trong khi các chuỗi khác thì không.

Điều này cũng đúng với các từ. Một số chuỗi chữ cái (hoặc âm thanh) là những từ hợp pháp, trong khi một số khác thì không.

Ngôn ngữ chính thức chỉ là cú pháp mà không có ngữ nghĩa. Họ định nghĩa với một bộ quy tắc những chuỗi nào có thể được xây dựng, sử dụng các yếu tố cơ bản của bảng chữ cái. Những gì các quy tắc có thể rất thay đổi, đôi khi phức tạp. Nhưng các ngôn ngữ chính thức được sử dụng cho nhiều mục đích toán học ngoài giao tiếp ngôn ngữ, cho dù là tự nhiên đối với các ngôn ngữ lập trình. Tập hợp các quy tắc xác định các chuỗi trong một ngôn ngữ được gọi là ngữ pháp. Nhưng có nhiều cách khác để định nghĩa ngôn ngữ.

Trong thực tế, một ngôn ngữ được cấu trúc theo hai cấp độ. Cấp độ từ vựng xác định các từ được xây dựng từ bảng chữ cái của các ký tự. Cấp độ cú pháp xác định các câu hoặc chương trình được xây dựng từ bảng chữ cái của các từ (hoặc chính xác hơn là các họ từ, để nó vẫn là một bảng chữ cái hữu hạn). Điều này nhất thiết phải được đơn giản hóa một chút.

Cấu trúc của các từ khá đơn giản trong hầu hết các ngôn ngữ (lập trình hoặc tự nhiên) do đó chúng thường được định nghĩa với loại thường được coi là loại ngôn ngữ chính thức đơn giản nhất: ngôn ngữ thông thường. Chúng có thể được định nghĩa bằng các biểu thức chính quy (regrec) và được xác định khá dễ dàng với các thiết bị được lập trình được gọi là automata trạng thái hữu hạn. Trong các trường hợp của ngôn ngữ lập trình, các ví dụ của một từ là một định danh, một số nguyên, chuỗi, một số thực, một từ dành riêng như if hoặc repeat, một ký hiệu dấu chấm câu hoặc dấu ngoặc đơn mở. Ví dụ về họ từ là định danh, chuỗi, số nguyên.

Cấp độ cú pháp thường được xác định bởi một loại ngôn ngữ chính thức phức tạp hơn một chút: ngôn ngữ không ngữ cảnh, sử dụng các từ làm bảng chữ cái. Các quy tắc chúng ta đã thấy ở trên là các quy tắc không ngữ cảnh cho ngôn ngữ tự nhiên. Trong trường hợp các quy tắc ngôn ngữ lập trình có thể là:

statement -> assignment
statement -> loop
loop ->  "while" expression "do" statement
assignment -> "identifier" "=" expression
expression -> "identifier"
expression -> "integer"
expression -> expression "operator" expression

Với các quy tắc như vậy bạn có thể viết:

while aaa /= bbb do aaa = aaa + bbb / 6 đó là một tuyên bố.

Và cách nó được tạo ra có thể được biểu diễn bằng cấu trúc cây gọi là cây phân tích cú pháp hoặc cây cú pháp (không hoàn thành ở đây):

                          statement
                              |
            _______________  loop _______________
           /      /                 \            \
      "while" expression           "do"       statement
       __________|_________                       |
      /          |         \                  assignment
 expression "operator" expression          _______|_______
     |           |          |             /       |       \
"identifier"   "/="   "identifier" "identifier"  "="   expression
     |                      |            |                 |
    aaa                    bbb          aaa             ... ...

Các tên xuất hiện ở bên trái của một quy tắc được gọi là không phải thiết bị đầu cuối, trong khi các từ được gọi cũng là thiết bị đầu cuối, vì chúng nằm trong bảng chữ cái cho ngôn ngữ (trên cấp độ từ vựng). Non-terminal đại diện cho các cấu trúc cú pháp khác nhau, có thể được sử dụng để soạn chương trình.

Các quy tắc như vậy được gọi là không có ngữ cảnh, bởi vì một thiết bị không đầu cuối có thể được thay thế tùy ý bằng cách sử dụng bất kỳ quy tắc tương ứng nào, độc lập với bối cảnh mà nó xuất hiện. Tập hợp các quy tắc xác định ngôn ngữ được gọi là ngữ pháp không ngữ cảnh.

Trên thực tế, có những hạn chế về điều đó, khi các định danh phải được khai báo đầu tiên hoặc khi một biểu thức phải thỏa mãn các hạn chế loại. Nhưng hạn chế như vậy có thể được coi là ngữ nghĩa, hơn là cú pháp. Trên thực tế một số chuyên gia đặt chúng trong những gì họ gọi là ngữ nghĩa tĩnh .

Cho bất kỳ câu, bất kỳ chương trình, ý nghĩa của câu đó được trích xuất bằng cách phân tích cấu trúc được đưa ra bởi cây phân tích cho câu này. Do đó, rất quan trọng để phát triển các thuật toán, được gọi là trình phân tích cú pháp, có thể khôi phục cấu trúc cây tương ứng với một chương trình, khi được cung cấp chương trình.

Trình phân tích cú pháp được đi trước bởi bộ phân tích từ vựng nhận ra các từ và xác định họ thuộc về họ. Sau đó, chuỗi các từ, hoặc các yếu tố từ vựng, được đưa cho trình phân tích cú pháp lấy cấu trúc cây bên dưới. Từ cấu trúc này, trình biên dịch có thể xác định cách tạo mã, phần ngữ nghĩa của phần xử lý chương trình ở phía trình biên dịch.

Trình phân tích cú pháp của trình biên dịch thực sự có thể xây dựng một cấu trúc dữ liệu tương ứng với cây phân tích cú pháp và chuyển nó sang các giai đoạn sau của quá trình biên dịch, nhưng nó không phải. Chạy số lượng thuật toán phân tích cú pháp để phát triển một chiến lược tính toán để khám phá cây cú pháp ẩn trong văn bản chương trình. Cây cú pháp / phân tích cú pháp này có thể hoặc không thể được khám phá trong quá trình, tùy thuộc vào chiến lược biên dịch (số lượng giai đoạn). Điều cần thiết là cuối cùng có ít nhất một cuộc thăm dò từ dưới lên của cây phân tích, cho dù được khám phá hay để lại ẩn trong cấu trúc tính toán.

Lý do cho điều đó, theo trực giác, là một cách chính thức tiêu chuẩn để xác định ngữ nghĩa liên quan đến cấu trúc cây cú pháp là bằng cách gọi là đồng cấu. Đừng sợ từ lớn. Ý tưởng chỉ là xem xét ý nghĩa của tổng thể được xây dựng từ ý nghĩa của các bộ phận, trên cơ sở toán tử kết nối chúng

Ví dụ, câu the dog bites the catcó thể được phân tích với quy tắc sentence -> subject verb complement. Biết ý nghĩa của 3 cây con subject, verbcomplement, quy tắc sáng tác chúng cho chúng ta biết rằng đối tượng đang thực hiện hành động và con mèo là người bị cắn.

Đây chỉ là một lời giải thích trực quan, nhưng nó có thể được chính thức hóa. Ngữ nghĩa được xây dựng đi lên từ các thành phần. Nhưng điều này ẩn giấu rất nhiều phức tạp.

Công việc nội bộ của trình biên dịch có thể được phân tách thành nhiều giai đoạn. Trình biên dịch thực tế có thể làm việc theo từng giai đoạn, sử dụng các biểu diễn trung gian. Nó cũng có thể hợp nhất một số giai đoạn. Điều này phụ thuộc vào công nghệ được sử dụng và mức độ phức tạp của việc biên dịch ngôn ngữ trong tay.


Tuyệt vời, rất hữu ích. Tôi hiểu rằng regex được sử dụng trong quá trình mã thông báo (ví dụ: một chuỗi ký tự có thể được định nghĩa bằng "[^"]*"hình thức đơn giản nhất của nó, bỏ qua ký tự thoát, v.v.), nhưng nó cũng được sử dụng trong việc tạo cây cú pháp (Nói về ngôn ngữ lập trình)? Tôi cho rằng không, như một automata trạng thái hữu hạn, theo định nghĩa là hữu hạn. Một cây cú pháp, ngay cả đối với một ifcâu lệnh, có thể là vô hạn về mặt lý thuyết do lồng nhau. Do đó, regex, là một automata trạng thái hữu hạn không thể được sử dụng cho mục đích tạo cây cú pháp.
Zwander

@Zwander thx 4 chỉnh sửa- Ví dụ về regex của bạn là chính xác (tôi nên đưa ra một số ví dụ). BTW, Regex cũng là một ngôn ngữ, với ngữ nghĩa riêng trong thế giới của các chuỗi và với cú pháp Ngữ cảnh ( CF ). Nó chỉ được sử dụng để mã hóa chuỗi ngôn ngữ, ít nhất là cho các ngôn ngữ lập trình, thường không phải trong việc xác định cú pháp lớn hơn được sử dụng cho các cây cú pháp, ngoại trừ như viết tắt trong Extended BNF (EBNF). Thêm Regex dưới một số hình thức vào các hình thức phức tạp hơn không làm thay đổi sức mạnh biểu cảm của chúng trong hầu hết các trường hợp. Nhận xét của bạn về vô cùng không hoàn toàn chính xác. Xem bình luận tiếp theo.
babou

@Zwander Tất cả các hình thức (ngôn ngữ chính thức) được mô tả chính xác. Đó là một giả thuyết cơ bản. Ngay cả khi bạn quan tâm, giả sử, ngữ pháp CF với vô số quy tắc, bạn phải đưa ra một mô tả hữu hạn về sự vô hạn của quy tắc đó. Ngoài ra vô cùng chơi các thủ đoạn trên bạn (không có không gian cho điều đó). Một iftuyên bố là không giới hạn (lớn tùy ý) nhưng luôn luôn hữu hạn. Một vô hạn xác định hữu hạn iflà a while. Sự khác biệt giữa CF và thông thường là CF kiểm soát / cho phép lồng nhau (tức là cha mẹ hóa) trong khi thông thường thì không. Nhưng cả hai đều được mô tả chính xác và cho phép các chuỗi không giới hạn.
babou

1
@Zwander Chủ nghĩa hình thức phải có khả năng đại diện cho bất kỳ câu (chương trình) được hình thành tốt , nhưng chỉ các câu được hình thành tốt. Nói một cách đơn giản, FSA không thể đếm không giới hạn. Vì vậy, họ không thể biết có bao nhiêu dấu ngoặc đơn đã được mở nên được đóng hoặc lồng đúng hai loại dấu ngoặc khác nhau. Nhiều cấu trúc ngôn ngữ có dấu ngoặc đơn "ẩn". Nó không chỉ đơn giản là vấn đề kiểm tra cú pháp, mà chủ yếu ngụ ý rằng cấu trúc cây thích hợp không thể được biểu diễn và xây dựng, từ đó rút ra ngữ nghĩa. Phục hồi một số cấu trúc cây đầy đủ đòi hỏi phải thực hiện đếm.
babou

1
(((AB)+3)×C)

2

Có sự khác biệt đáng kể. Trưởng trong số họ, tôi sẽ nói, là phân tích các ngôn ngữ lập trình thực là tất cả về xử lý lỗi cú pháp. Với một ngôn ngữ chính thức, bạn chỉ cần nói "ồ nó không phải là ngôn ngữ", nhưng một trình biên dịch nói rằng nó không hữu ích - nó sẽ cho bạn biết những gì sai, và nếu đó là một lỗi nhỏ, lý tưởng là hãy phân tích cú pháp để nó có thể báo cáo nhiều lỗi. Rất nhiều nghiên cứu (và nỗ lực thực hiện) đi vào đó. Vì vậy, thực sự bạn thậm chí không quan tâm nhiều đến kết quả đúng / sai, bạn chỉ muốn phân tích cấu trúc của đầu vào. Các ngôn ngữ chính thức được sử dụng như một công cụ ở đó và có rất nhiều sự chồng chéo, nhưng bạn thực sự đang giải quyết một vấn đề khác.

Ngoài ra, trong hầu hết các ngôn ngữ, nó đã được chọn để không thực thi một số điều trong ngữ pháp , ví dụ như ví dụ bạn đã đề cập, "một biến không thể xuất hiện nếu nó không được khai báo". Đó thường là một thứ sẽ bị trình phân tích cú pháp bỏ qua hoàn toàn, và sau đó bị cuốn vào một phân tích riêng (phân tích ngữ nghĩa) xem xét loại đó và không bị ảnh hưởng bởi các cân nhắc như bối cảnh. Nhưng không phải lúc nào - ví dụ để phân tích C, hack lexer thường được sử dụng và C ++ là một ví dụ nổi tiếng về ngôn ngữ không thể được phân tích cú pháp mà không thực hiện một số phân tích ngữ nghĩa nghiêm trọng (thực sự phân tích cú pháp C ++ là không thể giải quyết được, vì các mẫu đã hoàn thành ). Trong các ngôn ngữ đơn giản hơn, nó có xu hướng được phân chia, theo cách đó dễ dàng hơn.


1

Một ngôn ngữ chính thức là một tập hợp các từ - trong đó một từ là một chuỗi các ký hiệu từ một số bảng chữ cái.

Điều này có nghĩa là sự kết hợp giữa các quy tắc sản xuất và ngôn ngữ chính thức của bạn quá mạnh. Điều đó không đúng khi nói rằng ngôn ngữ chính thức là quy tắc sản xuất. Thay vì các quy tắc sản xuất xác định ngôn ngữ chính thức. Ngôn ngữ chính thức là những từ có thể được tạo ra bởi quy tắc sản xuất. (Điều này đòi hỏi ngôn ngữ chính thức thuộc loại có thể được xác định bởi quy tắc sản xuất, ví dụ: ngôn ngữ thông thường có thể được xác định bằng ngữ pháp tự do ngữ cảnh)

Vì vậy, ngôn ngữ thông thường tương ứng với biểu thức (a|b)*c*dđược xác định bởi các quy tắc sản xuất;

S->ACd
A->
A->aA
A->bA
C->
C->cC

Các từ mà các quy tắc sản xuất này tạo ra từ ký hiệu bắt đầu S chính là các chuỗi mà biểu thức chính quy chấp nhận.


0

Có một mối quan hệ khác giữa các biểu thức chính quy và ngôn ngữ lập trình liên quan đến ngữ nghĩa. Các cấu trúc điều khiển cơ bản của một ngôn ngữ bắt buộc là thành phần tuần tự (làm A và sau đó B), lựa chọn (làm A hoặc B) và lặp lại (làm A nhiều lần).

Ba cách kết hợp hành vi giống nhau được tìm thấy trong các biểu thức thông thường. Ném vào các cuộc gọi chương trình con và bạn có một sự tương tự với EBNF.

Vì vậy, có rất nhiều điểm tương đồng giữa đại số của biểu thức chính quy và đại số của các lệnh. Điều này được Dijkstra khám phá chi tiết trong "Sự hợp nhất của ba phép tính". Nó cũng là nền tảng của CCS của Milner, cung cấp câu trả lời cho câu hỏi: nếu chúng ta thêm song song thì sao?

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.