Văn phạm là một cấu trúc dùng để lưu quy tắc sinh các câu của một ngôn ngữ. Nó cũng được dùng để phân tích cú pháp ngôn ngữ. Một cách đơn giản, văn phạm gồm một tập các ký hiệu và tập các luật biến đổi các chuỗi ký hiệu này thành chuỗi ký hiệu khác, mặc dù vậy chúng ta có định nghĩa chính xác về văn phạm như sau
G = <V, i, F, C, P> = <Σ, ∆, i, Π>
Trong đó
V gọi là tập ký hiệu
i xác định ký hiệu khởi đầu
F xác định tập các ký hiệu khởi đầu phân loại, F có thể chứa i C xác định tập ký hiệu kết thúc C ≡ Σ, C ∩ F = ∅, C ∩ {i}=∅ P là tập luật, P≡Π
Σ là tập các ký hiệu kết thúc
Bảng 2-7. Định nghĩa hình thức văn phạm
Các luật của văn phạm là công thức xác định một ký tự sẽ sinh ra một chuỗi ký tự nào hoặc một số chuỗi ký tự nào. Do đó tập luật có thể được coi như ánh xạ từ tập ký tự sang tập chuỗi ký tự, chính xác hơn là sang tập các tập con của tập chuỗi ký tự. Ví dụ văn phạm đơn giản nhất
G = <V, i, F, C, P>
V = { s, n, vp, v, pl, tôi, nó, đi, ra, chơi, chợ } i = s
F = { n, v, pl }
C = { tôi, nó, đi, ra, chơi, chợ }
R = { s->n vp, vp->n pl, n->nó, n->tôi, v->đi, v->ra, pl->chơi, pl->chợ }
Bảng 2-8. Ví dụ về văn phạm
Bằng cách sử dụng các luật trong tập luật với ký hiệu ban đầu, chúng ta có thể sinh ra các câu trong ngôn ngữ. Quá trình thay thế sẽ kết thúc khi tất cả các ký tự trong chuỗi thuộc tập ký tự hằng C. Một chuỗi ký tự có thể sinh ra hai chuỗi mới nếu nó chứa một ký tự có hai luật sinh.
s n vp
tôi vp, nó vp tôi v pl, nó v pl
tôi đi pl, tôi ra pl, nó đi pl, nó ra pl
tôi đi chơi, tôi đi chợ, tôi ra chơi, tôi ra chợ, nó đi chơi, nó đi chợ, nó ra chơi, nó ra chợ
Bảng 2-9. Ngôn ngữ sinh bởi văn phạm 2.2.2. Biểu diễn đơn giản của văn phạm
Trong quá trình triển khai, chúng tôi biểu diễn văn phạm bằng một tập tin văn bản gồm danh sách các luật cách nhau bằng dấu chấm phảy. Các luật có cùng ký hiệu phía trái được viết gộp cách nhau bởi dấu ghạch. Ký hiệu bắt đầu được đánh
dấu bằng dấu sao trong định nghĩa, ký hiệu phân loại có dấu cộng trong phần định nghĩa, ký hiệu hằng được bọc trong giữa các dấu ngoặc kép.
*s = n vp; +n = "tôi" | "nó"; vp = v pl; +v = "đi" | "ra"; +pl = "chơi" | "chợ"; Bảng 2-10. Biểu diễn văn phạm trong tập tin
Cách biểu diễn này xúc tích và trực quan hơn cách biểu diễn toán học. Do đó văn phạm trên hoàn toàn tương đương với văn phạm trong ví dụ. Thật vậy, ký tự i có thể được nhặt ra từ phía sau dấu sao. Tập ký tự F có thể được nhặt ra bằng các ký tự sau dấu cộng. Tập ký tự C có thể được nhặt ra bằng các ký tự trong dấu ngoặc. Tập luật có thể được nhặt ra bằng cách tách các dòng trên thành dạng một vế phải và một vế trái.
2.2.3. Định nghĩa triển khai của văn phạm
Trong triển khai, chúng tôi thay đổi một chút để biểu diễn văn phạm. Thay đổi này gần với biểu diễn đơn giản của văn phạm đã trình bày trong phần trên. Chúng tôi coi văn phạm là tập các luật và tập các ký hiệu. Tập luật cần được duy trì để chia sẻ với tất cả các ký hiệu. Chúng tôi chỉ lưu vế phải của luật. Mỗi ký tự sẽ trỏ để các luật mà nó sinh ra. Cách lưu trữ này đặc biệt hữu ích cho việc sinh và phân tích cú pháp từ dưới lên như SLR hay GLR.
struct grammar_s { symbol_s **m_sym; longnat_t m_sym_num; rule_s **m_rule; longnat_t m_rule; };
Bảng 2-11. Định nghĩa triển khai của văn phạm
Các tập i, F, C sẽ được chuyển vào như thuộc tính của ký hiệu. Nghĩa là ký hiệu loại i và ký hiệu loại F, và ký hiệu loại C. Chú ý rằng ký hiệu loại hằng (constant) hay C thì không thể là ký hiệu biến (variable) hai i và F, vì chúng ta có ràng buộc trong định nghĩa C ∩ F = ∅, C ∩ {i} = ∅. Nhưng ký hiệu khởi đầu i cũng có thể là ký hiệu phân loại, vì không nhất thiết {i} ∩ F = ∅ .
struct symbol_s { longnat_t m_id; string_s m_name;
enum_s m_var; //là hằng hay biến
bool_t m_var_start; //nếu là biến thì có thể là khởi đầu hay không bool_t m_var_pos; //nếu là biến thì có thể là phân loại không rule_s **m_rule;
longnat_t m_rule_num; };
Bảng 2-12. Định nghĩa ký hiệu trong văn phạm
Các luật được coi là danh sách các ký hiệu, và không cần có vế trái do các ký hiệu vế trái luôn tham khảo tới danh sách luật. Các luật cần có định danh do chúng được chia sẻ trong hệ thống.
struct rule_s { longnat_t m_id; symbol_s **m_RHS; longnat_t m_RHS_len; }; Bảng 2-13. Định nghĩa luật trong văn phạm
2.3. Ngôn ngữ (language)
Trong quá trình triển khai, chúng tôi cần dùng đến khái niệm ngôn ngữ để mô tả giới hạn các mệnh lệnh xử lý. Ngôn ngữ (language) theo cách sử dụng của chúng tôi như là vật tương đương của văn phạm (grammar). Tuy nhiên ngôn ngữ chỉ dùng để biểu diễn ngôn ngữ chính quy và do đó dùng để xây dựng sơ đồ chuyển trạng thái tuyến tính, trong khi văn phạm biểu diễn ngôn ngữ phi ngữ cảnh và dùng để xây dựng sơ đồ chuyển trạng thái đẩy xuống.
2.3.1. Định nghĩa hình thức về ngôn ngữ
Có rất nhiều định nghĩa khác nhau, xem [4], [7], [17], của các nhà khoa học xã hội cũng như khoa học chính xác về ngôn ngữ, nhưng chúng tôi đưa ra định nghĩa về ngôn ngữ theo quan điểm toán học “ngôn ngữ là tập hợp các câu (sentence) hay các chuỗi (string) được thành lập trên trên một từ điển các ký hiệu”
L = language(V) = { s | s là chuỗi thành lập trên V }
Trong đó phép thành lập chuỗi trên một từ điển ký hiệu chỉ đơn giản là phép ghép liên tiếp các ký hiệu vào nhau để tạo thành chuỗi. Trừ khi tập ký hiệu là rỗng, các ngôn ngữ thành lập trên từ điển đều là tập hợp vô hạn các chuỗi.
Về mặt toán học, ngôn ngữ là tập hợp vô hạn các câu. Nhưng về mặt triển khai, chúng ta thường lưu trữ một ngôn ngữ là tập hợp hữu hạn các câu. Ngôn ngữ hữu hạn như vậy thường được biểu diễn dễ dàng trong máy tính dưới dạng tập tin văn bản (tập hợp các câu, mỗi câu trên một dòng) hoặc dưới dạng sơ đồ chuyển trạng thái tuyến tính (tập hợp các trạng thái nối với nhau bởi các từ).
Ví dụ cho từ điển gồm ba ký hiệu V1 = { tôi, yêu, em } Chúng ta sẽ có một ngôn ngữ rất phong phú bao được liệt kê như sau (Chúng tôi thêm dấu cách vào giữa các ký hiệu để dễ nhìn)
L1 = language(V1) = { tôi, yêu, em,
tôi tôi yêu, tôi tôi em, tôi tôi tôi, tôi yêu em, em yêu tôi, .. }
Bảng 2-14. Ví dụ về ngôn ngữ
Tất nhiên phần lớn các câu hay các chuỗi trong ngôn ngữ trên không phải là các câu của tiếng Việt. Nhưng chúng tôi không định mô tả tiếng Việt trong ví dụ này, mà chúng tôi chỉ đưa ra ví dụ về ngôn ngữ
Ví dụ cho từ điển ký hiệu gồm hai ký tự V2 = { 0, 1} chúng ta sẽ có một ngôn ngữ rất phong phú được liệt kê như sau
L2 = language(V2) = { 0, 1, 00, 01, 10, 11, 000, 001, 010, 011, 100, 101, 110, 111, … } Bảng 2-15. Ví dụ khác về ngôn ngữ 2.3.2. Biểu diễn ngôn ngữ trong đĩa từ
Trong đĩa từ, chúng tôi lưu ngôn ngữ thành một tập tin văn bản. Trong đó mỗi câu của ngôn ngữ được viết trên một dòng. Chú thích dòng và chú thích khối cũng được thêm vào nhằm làm tập tin có thêm phần giải thích. Chú thích này được viết theo kiểu C++. Các dấu câu như hai chấm, chấm phảy, chấm nếu có cũng được coi như một từ bình thường, do đó có thể viết ở bất kỳ phần nào trong dòng. Trên thực tế, lúc ta đọc chính tả hoặc ra lệnh bằng giọng nói để các dấu câu xuất hiện chúng ta phải đọc cả tên của dấu câu.
/*Các câu lệnh của hệđiều hành */
Run Winamp //ấn vào biểu tượng winamp trong desktop Run Winword //ấn vào biểu tượng winword trong desktop Show desktop //ấn tổ hợp Win+D
Show taskbar //ấn tổ hợp Ctrl+Esc
Close current application //ấn tổ hợp Alt+F4 Shutdown //đóng các ứng dụng và tắt máy
/*Các câu lệnh thường gặp trong một sốứng dụng */
File Open … Ok //Câu lệnh mở tài liệu thông thường, tên tập tin phải chọn File Close //Câu lệnh đóng tài liệu thông thường
File Print Edit Copy Edit Select All Edit Paste
Bảng 2-16. Biểu diễn ngôn ngữ trong tập tin
2.4. Sơđồ chuyển trạng thái tuyến tính (linear state transition diagram)
Sơ đồ chuyển trạng thái tuyến tính cũng có thể được hiểu như mạng lưới các trạng thái (state) và các phép chuyển (transition) trong đó các phép chuyển được gán các nhãn là các từ. Do đó duyệt dọc theo một đường đi từ trạng thái đầu tiên đến trạng thái cuối cùng ta sẽ có một câu sinh bởi sơ đồ chuyển trạng thái tuyến tính.
Sơ đồ chuyển trạng thái tuyến tính có thể dùng để lưu trữ một ngôn ngữ (language) nghĩa là một tập câu và theo một cách nào đó có thể hiểu tương đương với khái niệm mô hình ngôn ngữ.
2.4.1. Định nghĩa hình thức
Một sơ đồ chuyển trạng thái tuyến tính gồm một tập các trạng thái Q, tập từ vựng Σ và ∆, một trạng thái ban đầu i, và tập trạng thái kết thúc F, và một tập các phép chuyển δ
M = <Σ, ∆, Q, δ, i, F> Σ là tập các ký hiệu vào
∆ là tập ký hiệu ra, thường được dùng ở các trạng thái kết thúc Q là tập trạng thái
i xác định trạng thái ban đầu i∈Q F xác định các trạng thái kết thúc F⊂Q
δ là ánh xạ từ Σ×Q vào Q xác định các phép chuyển
Bảng 2-17. Định nghĩa hình thức sơđồ chuyển trạng thái tuyến tính
Chúng tôi chỉ định nghĩa sơ đồ chuyển trạng thái tuyến tính đơn định trong đó một trạng thái cũ q và một ký hiệu vào v sẽ cho ta một trạng thái mới q’ mà không phải tập các trạng thái. Mặc dù trên thực tế và về mặt toán học có tồn tại các sơ đồ như vậy nhưng trong nghiên cứu của chúng tôi, chúng tôi chỉ dùng sơ đồ chuyển trạng thái tuyến tính đơn định (deterministic).
Dưới đây là ví dụ về một sơ đồ chuyển trạng thái tuyến tính trong đó các từ được ghi trên các cung chuyển, các trạng thái kết thúc được vẽ bằng hình vuông, trạng thái bắt đầu được vẽ bằng hình tròn có mũi tên đi vào. Bên cạnh là ngôn ngữ tương ứng do sơ đồ chuyển trạng thái biểu diễn. Chúng ta cũng có thể dùng ngôn ngữ này để sinh ra sơ đồ chuyển trạng thái.
0 1 run winamp winword 7 file open 8 ok cancel 4 show desktop taskbar 2 3 5 6 9 10 run winamp run winword show desktop show taskbar file open ok file open cancel
Hình 2-1. Ví dụ về sơđồ chuyển trạng thái tuyến tính 2.4.2. Định nghĩa triển khai
Trong triển khai, chúng tôi coi sơ đồ chuyển trạng thái như một tập các trạng thái và tập các đường chuyển. Mỗi trạng thái sẽ có một thuộc tính để xác định xem nó có là trạng thái đặc biệt hay không (ví dụ như trạng thái bắt đầu hoặc trạng thái kết thúc) do đó chúng ta không cần mô tả tập trạng thái.
struct linear_diagram_s { longnat_t m_state_num; linear_state_s **m_state; longnat_t m_insym_num; input_symbol_s **m_insym; longnat_t m_outsym_num; output_symbol_s **m_outsym;
linear_state_s *append_state(linear_state_s *pre, word_s *word); linear_state_s *get_first_state();
linear_state_s *get_next_state(vector_s *vect, linear_state_s *state=NULL); };
Bảng 2-18. Định nghĩa triển khai sơđồ chuyển trạng thái tuyến tính
Mỗi trạng thái chứa một danh sách các phép dịch chuyển tới trạng thái tiếp theo để bao gồm ánh xạ δ. Mỗi trạng thái cũng cần có định danh để được liên kết. Trạng thái cuối cùng thường trỏ tới một ký hiệu ra
struct linear_state_s { longnat_t m_id; output_symbol_s *m_sym; longnat_t m_shift_num; linear_shift_s **m_shift; }; Bảng 2-19. Định nghĩa trạng thái tuyến tính
Mỗi một phép dịch chuyển sẽ bao gồm một từ hoặc một ký hiệu vào và một con trỏ tới trạng thái sẽ được chuyển đến
struct linear_shift_s {
input_symbol_s *m_sym; linear_state_s *m_state; };
Bảng 2-20. Định nghĩa phép chuyển tuyến tính 2.4.3. Thuật toán xây dựng
Các sơ đồ chuyển trạng thái tuyến tính thường được xây dựng trên một ngôn ngữ tương ứng. Cho một ngôn ngữ hay một tập câu, chúng ta sẽ thêm các câu vào sơ đồ chuyển trạng thái tuyến tính cho tới khi hết tập câu.
linear_diagram_s::built_from_language(language l) { for(sentence sent ∈ l) {
st = this->get_first_state();
for(i=0; i<sent.length; i++) st = this->append_state(st, sent.word(i)); st->m_sym = sent.output_symbol;
} }
Bảng 2-21. Thuật toán xây dựng sơđồ chuyển trạng thái tuyến tính
Trong các tính huống khác nhau, thuật toán này có thể được triển khai để thêm ký hiệu đầu ra (tường hợp trạng thái kết thúc).
2.5. Sơđồ chuyển trạng thái đẩy xuống (pushdown state transition diagram)
Sơ đồ chuyển trạng thái đẩy xuống là dạng đơn giản nhất của ô tô mát đẩy xuống thường được dùng trong các chương trình dịch. Về cơ bản, sơ đồ chuyển trạng thái đẩy xuống chỉ khác với sơ đồ chuyển trạng thái tuyến tính ở chỗ mỗi trạng thái có chứa một danh sách các phép đẩy xuống. Nhưng thuật toán sinh sơ đồ chuyển trạng thái đẩy xuống thì hoàn toàn khác.
2.5.1. Định nghĩa hình thức
Mỗi trạng thái trong sơ đồ chuyển trạng thái đẩy xuống có thêm một tập các phép dịch chuyển (reduce). Đây là khác biệt so với sơ đồ chuyển trạng thái tuyến tính. Chính nhờ đặc điểm này mà ngăn xếp hay đồ thị chuyển hình trạng của các bộ phân tích đẩy xuống có thể co lại (đẩy xuống) thay vì kéo dài mãi mãi như ngăn xếp của các bộ phân tích tuyến tính.
M = <V, Π, Q, i, F, δ, ρ>
V là từ điển đầy đủ của văn phạm tương ứng Π là tập các luật của văn phạm tương ứng Q là tập trạng thái của sơ đồ
F xác định các trạng thái kết thúc F⊂Q
δ là ánh xạ từ Σ×Q vào Q xác định các phép chuyển
ρ là ánh xạ từ Q vào Π xác định các phép rút gọn của mỗi trạng thái
Bảng 2-22. Định nghĩa hình thức sơđồ chuyển trạng thái đẩy xuống
Sơ đồ chuyển trạng thái đẩy xuống thường được sinh từ một văn phạm phi ngữ cảnh tương ứng. Ví dụ như sơ đồ dưới đây
0 (initial) 1 (final) 0 -> s 2 3 n -> t«i 4 n -> nã 5 s -> n vp 6 7 v -> ®i 8 v -> ra 9 vp -> v pl 10 pl -> chî 11 pl -> ch¬i s n t«i nã vp v ®i ra pl chî ch¬i * s = n vp; + n = tôi | nó; vp = v pl; + v = đi | ra; + pl = chơi | chợ; Hình 2-2. Ví dụ về sơđồ chuyển trạng thái đẩy xuống
Trong sơ đồ này, chúng ta có thể thấy các câu của văn phạm được triển khai thế nào nếu như chúng ta đi từ trạng thái ban đầy 0 đến các trạng thái khác, ví dụ như câu “s”, hay câu “n v pl”, hay câu “n vp”. Chúng ta có thể thấy các khái niệm
V = { s, n, vp, v, pl, tôi, nó, đi, ra, chơi, chợ } Π = { s->n vp, n->tôi, n->nó, vp->v pl, v->đi, v->ra, pl->chơi, pl->chợ } Q = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 } i = 0 F = { 1 } ρ(0) = {} ρ(1) = { 0->s } ρ(2) = {} ρ(3) = { n->tôi } ρ(4) = { n->nó } ρ(5) = { s-> n vp } ρ(6) = { } ρ(7) = { v->đi } ρ(8) = { v->ra } ρ(9) = { vp-> v pl } ρ(10) = { pl->chợ } ρ(11) = { pl->chơi }
δ(0, s) = 1, δ(0, n) = 2, δ(0, tôi) = 3, δ(0, nó) = 4
δ(2, vp) = 5, δ(2, v) = 6, δ(2, đi) = 7, δ(2, ra) = 8,
δ(6, pl) = 9, δ(6, chợ) = 10, δ(6, chơi) = 11
Bảng 2-23. Ví dụ về sơđồ chuyển trạng thái đẩy xuống