• SUSPENDED BRANCH: là một hàng đợi các nhánh chờ được xử lý.
• ECHO: giá trị tổng hợp echo khi nhận được từ các nhánh con. Echo bao gồm 4 loại là: TRUE, FALSE, DONE, ABORT.
• REMAINING ECHO: số lượng echo còn lại chờ nhận được từ các nhánh con.
• ENCLOSED WAVE: chuỗi wave chứa trong Rule
• SUSPENDED TAIL: chỗi wave còn lại khi loại bỏ toàn bộ Rule
• WAVE ENVIRONMENT: Giá trị của các biến mỗi trường wave
4. 5. 4 Parsing Unit
Trong bất cứ trình biên dịch, hay thông dịch nào, thành phần parser bao giờ cũng là quan trọng nhất. Thành phần parser chịu trách nhiệm phân tích các đoạn mã wave rồi từ đó đưa ra các hành động tương ứng với đoạn mã phân tích được. Vì tính chất quan trọng của nó nên thành phần này sẽđược mô tả chi tiết dưới đây.
1. Giới thiệu về JavaCC a. JavaCC là gì?
JavaCC – Java Compiler Compiler là bộ sinh parser hiện đang được dùng phổ biến trong các ứng dụng Java. Bộ sinh parser này sẽđọc các đặc tả cú pháp của ngôn ngữ nào đó và chuyển nó thành chương trình Java – chương trình có thể nhận dạng ra chính xác cấu trúc của ngôn ngữ đó. Bên cạnh đó, công cụ
PROP ADDR2 OS Parent Children TRUE … PROP ADDR1 AP Parent Children TRUE … SECT ADDR3 NM Parent Children TRUE … SECT ADDR4 NM Parent Children FALSE … Hình 4-10: Track Forest
- 45 -
JavaCC còn cung cấp các khả năng khác trong quá trình đọc cú pháp như việc xây dựng cây, gỡ lỗi…
Để sinh ra bộ mã Parser cho ngôn ngữ wave ta sử dụng 2 công cụ trong gói JavaCC bao gồm:
- JJTree: đọc đặc tả file .jjt (jjt bao gồm các đặc tả về ngôn ngữ wave bao gồm các token và các luật triển khai, cây cú pháp của wave) và đưa ra đặc tả về Token (các files AST: abstract synctax tree) và file .jj dùng cho việc sinh ra mã nguồn parser
- JavaCC: đọc đặc tả .jj và sinh ra bộ mã bao gồm parser và các mã để kiểm soát lỗi của ngôn ngữ wave
b. Quá trình xử lý của JavaCC
Quá trình phân tích từ vựng và cú pháp diễn ra song song với nhau. Bộ phân tích từ vựng sẽđọc từng ký tự (ký tự trong các đoạn mã của wave) sau đó sẽ sinh ra các đối tượng Token. Cách tách các ký tự thành token tùy thuộc vào người lập trình – đó là các định nghĩa nằm trong biểu thức chính quy. JavaCC cho phép sử dụng luật sinh EBNF đểđịnh nghĩa.
Mỗi token trong quá trình phân tích tự vựng sẽ là đầu vào cho quá trình phân tích cú pháp để thực thi phân tích cấu trúc của ngôn ngữ và sinh ra cây cú pháp.
c. Các ngôn ngữ có thể xây dựng bởi JavaCC
Rất nhiều loại ngôn ngữ khác nhau có thể được sinh bởi JavaCC như: Visual Basic, Python, Rational Rose mdl files, XML, XML DTDs, HTML, C, C++, Java, JavaScript, Oberon, SQL, VHDL, VRML, ASN1,.. và rất nhiều loại ngôn ngữ khác nữa: WAVE...
2. File cú pháp của JavaCC
Để javaCC có thể biên dịch một đặc tả cú pháp sang dạng mã java, cần có một quy ước về cách viết file cú pháp để từđó có thể sinh ra các đoạn mã java (phục vụ cho quá trình parser hay quản lý token) một cách hiệu quả.
Cú pháp file đặc tả JavaCC bao gồm các thành phần sau: javacc_input ::= javacc_options "PARSER_BEGIN" "(" <IDENTIFIER> ")" java_compilation_unit "PARSER_END" "(" <IDENTIFIER> ")" ( production )* <EOF>
- 46 -
Một file cú pháp bắt đầu với một danh sách các tùy chọn. Theo sau là thành phần biên dịch java được bao trong hai từ khóa "PARSER_BEGIN(parser_name)" và "PARSER_END(parser_name)". Tiếp đến là danh sách các cú pháp luật sinh. Các tùy chọn và luật sinh sẽđược trình bày dưới đây.
Tham số name trong cặp từ khóa PARSER_BEGIN và PARSER_END là tên của bộ phân tích cú pháp được sinh ra. Đối với đề tài name có tên là WaveParser thì JavaCC tự động chèn các tên này vào sau các file tùy chọn được sinh ra như WaveParserConstants.java, WaveParserTokenManager.java … PARSER_BEGIN(parser_name) . . . class parser_name . . . { . . . } . . . PARSER_END(parser_name)
a. Các từ khóa và tùy chọn của JavaCC
JavaCC cung cấp một tập các từ khóa như bảng sau:
EOF IGNORE_CASE JAVACODE LOOKAHEAD MORE options PARSER_BEGIN PARSER_END
SKIP SPECIAL_TOKEN TOKEN TOKEN_MGR_DECLS Ngoài ra JavaCC còn cung cấp một số từ khóa thuộc loại tùy chọn trong một file đặc tả cú pháp của JavaCC.
LOOKAHEAD: Số token được nhìn trước trước khi quyết định lựa chọn một điểm trong quá trình phân tích. Mặc định có giá trị 1. Số nhỏ hơn, quá trình phân tích nhanh hơn. Số này có thểđịnh nghĩa lại cho các luật sinh cụ thể trong phạm vi ngữ pháp đểđịnh rõ tính chất giai đoạn sau.
CHOICE_AMBIGUITY_CHECK: Đây là một tùy chọn số nguyên có giá trị mặc định là 2. Đây là số token có được trong việc kiểm tra sự lựa chọn của hình thức "A | B | ..." cho tình trạng có nhiều nghĩa.
- 47 -
OTHER_AMBIGUITY_CHECK: Đây là một tùy chọn số nguyên có giá trị mặc định là 1. Đây là số token có được trong việc kiểm tra tất cả các hình thức khác nhau của sự lựa chọn (ví dụ của hình thức "(A)*", "(A)+", and "(A)?") cho tình trạng có nhiều nghĩa. Việc này mất nhiều thời gian hơn sự kiểm tra lựa chọn, do đó giá trị mặc định được thiết lập là 1 hơn là 2.
STATIC: Đây là một tùy chọn kiểu boolean có giá trị mặc định là true. Nếu true tất cả các phương thức và biến của lớp theo lý thuyết đều là static trong sự phát sinh cú pháp và quản lý token.
DEBUG_PARSER: Đây là một tùy chọn kiểu boolean có giá trị mặc định là false. Tùy chọn này được sử dụng để gỡ lỗi từ việc phát sinh cú pháp. Thiết lập tùy chọn này là true là nguyên nhân quá trình phân tích cú pháp phát sinh dấu hiệu của hành động.
DEBUG_LOOKAHEAD: Đây là tùy chọn kiểu boolean có giá trị mặc định là false. Thiết lập tùy chọn này là true là nguyên nhân quá trình phân tích cú pháp phát sinh tất cả dấu hiệu thông tin nó thực hiện khi tùy chọn DEBUG_PARSER là true, đó cũng là nguyên nhân để nó phát sinh ra dấu hiệu hành động đã thi hành trong thao tác lookahead.
DEBUG_TOKEN_MANAGER: Tùy chọn boolean có giá trị mặc định là false. Tùy chọn này được sử dụng để gỡ lỗi thông tin từ các token đã phát sinh. Thiết đặt tùy chọn này là true là nguyên nhân để quản lý token phát sinh dấu hiệu của hành động. Dấu hiệu này khá rộng và chỉ nên sử dụng nó khi có một lỗi từ vựng, những lỗi thông báo đến bạn và bạn không biết tại sao. Điển hình trong trường hợp này, bạn có thể giải quyết vấn đề bằng cách chú ý đến vài dòng trước đó của dấu hiệu.
ERROR_REPORTING: Tùy chọn boolean có giá trị mặc định là true. Thiết lập tùy chọn này là false là nguyên nhân các lỗi mắc phải từ các lỗi phân tích cú pháp có báo cáo thiếu chi tiết. Lý do để thiết lập tùy chọn này là false là để cải tiến sự thực thi.
JAVA_UNICODE_ESCAPE: Đây là tùy chọn boolean có giá trị mặc định là false. Khi thiết lập true, sự phân tích cú pháp đã phát sinh sử dụng một luồng vào xử lý bởi Java Unicode escapes(\u...) trước khi gửi ký tự tới token manager. Mặc định Java Unicode escapes không xử lý.
UNICODE_INPUT: Đây là tùy chọn kiểu boolean giá trị mặc định là false. Khi thiết lập true, sự phân tích cú pháp đã phát sinh sử dụng một luồng nhập để đọc file Unicode. Mặc định file ASCII được thừa nhận.
Tùy chọn này được bỏ qua nếu một trong hai tùy chọn USER_TOKEN_MANAGER, USER_CHAR_STREAM được thiết lập là true.
IGNORE_CASE: Tùy chọn kiểu boolean có giá trị mặc định là false. Thiết lập tùy chọn này là true là nguyên nhân phát sinh ra token manager để bác bỏ trong trường hợp token được chỉ định rõ và những hồ sơđưa vào. Tùy chọn này hữu dụng trong việc viết ngữ pháp cho ngôn ngữ như HTML.
- 48 -
USER_TOKEN_MANAGER: Tùy chọn kiểu boolean, giá trị ngầm định là false. Hành động mặc định là phát sinh ra một token manager làm việc trên những token ngữ pháp đã chỉ rõ. Nếu nó được thiết lập true, bộ phân tích cú pháp được phát sinh để thừa nhận những token từ bất kỳ token quản lý nào thuộc kiểu "TokenManager"- giao diện được phát sinh vào trong thư mục bộ phân tích cú pháp.
b. Bộ quản lý Token
Phần đặc tả từ vựng của JavaCC được tổ chức thành tập các trạng thái từ vựng “lexical states”. Mỗi trạng thái từ vựng có một định danh. Trạng thái từ vựng chuẩn là DEFAULT. Trình quản lý token được sinh ra tại mỗi thời điểm sẽ có một trạng thái từ vựng trong tập các trạng thái từ vựng. Khi trình quản lý token khởi tạo nó sẽ có trạng thái từ vựng mặc định là DEFAULT. Trạng thái từ vựng bắt đầu có thểđược xác định giống như là tham số khi xây dựng một đối tượng quản lý token.
Có 4 loại biểu thức chính qui: SKIP, MORE, TOKEN, và SPECIAL_TOKEN. o SKIP: Bỏ qua chuỗi từ vựng khi gặp khai báo SKIP.
o MORE: Tiếp tục cho đến khi gặp trạng thái kế tiếp, đưa chuỗi từ vựng khi gặp khai báo MORE vào trạng thái chờ. Chuỗi này sẽ là tiền tố cho chuỗi kế tiếp.
o TOKEN: Tạo ra một token sử dụng chuỗi và gửi nó vào bộ phân tích cú pháp.
o SPECIAL_TOKEN: Tạo ra một token đặc biệt không tham gia vào quá trình phân tích.
c. Luật sinh
Có 4 loại luật sinh trong JavaCC gồm javacode_production , bnf_production, regular_expr_production, token_manager_decls.
javacode_production và bnf_production được sử dụng để định nghĩa ngữ pháp trực tiếp cho bộ phân tích cú pháp. regular_expr_production được sử dụng để định nghĩa ngữ pháp của các Token, trình quản lý Token sử dụng các ngữ pháp này để tạo ra Token manager, có thể hiểu đây các đặc tả token trong bộ phân tích cú pháp. token_manager_decls được sử dụng để giới thiệu các khai báo dùng để chèn vào bên trong trình quản lý token.
• javacode_production
javacode_production là một cách để viết mã Java cho một số các luật sinh thay vì sử dụng EBNF. Điều này hữu ích khi mà việc định nghĩa một cú pháp bằng EBNF quá khó khăn. Ví dụ sau minh họa cho việc định nghĩa xác định luật sinh của một block.
JAVACODE
void skip_to_matching_brace() { Token tok;
- 49 - int nesting = 1;
while (true) {
tok = getToken(1);
if (tok.kind == LBRACE) nesting++; if (tok.kind == RBRACE) { nesting--; if (nesting == 0) break; } tok = getNextToken(); } } • bnf_production
bnf_production ::= java_access_modifier java_return_type java_identifier "(" java_parameter_list ")" ":"
java_block
"{" expansion_choices "}"
bnf_production là luật sinh chuẩn được sử dụng trong JavaCC.
Mỗi luật sinh BNF có vế trái là một đặc tả không kết thúc. Luật sinh BNF sẽ định nghĩa đặc tả không kết thúc này theo cách thức của BNF mở rộng hay EBNF. Điều này được thực hiện giống như các khai báo của các phương thức trong Java.
Có 2 phần chính ở vế phải của luật sinh BNF :
- Phần thứ nhất là tập các khai báo và mã của Java(Java block). Các mã java này sẽ được sinh ra ở phần đầu của các phương thức cho luật sinh BNF. Mỗi khi phương thức luật sinh này được thực thi trong quá trình phân tích cú pháp thì những khai báo và mã java này sẽ được thực thi. Khai báo trong phần này sẽ có mặt ở tất cả các hành động trong EBNF. JavaCC không xử lý bất kỳ một khai báo hoặc đoạn mã java nào, ngoại trừ việc bỏ qua các dấu ngoặc đơn và thu thập tất cả mã lệnh để làm cơ sở trong các phương thức được sinh ra sau này.
- Phần thứ hai của vế phải là các lựa chọn mở rộng expansion_choices có cú pháp như sau:
expansion_choices ::= expansion ( "|" expansion )*
Các expansion_choices được viết giống như một danh sách của một hoặc nhiều các mở rộng (expansion) được ngăn cách nhau bởi các dấu hoặc | . expansion ::= ( expansion_unit )*
- 50 -
Một mở rộng(expansion) được viết thành một tập các đơn vị mở rộng(expansion units). Một expansion đúng khi tất cả các expansion units đều đúng. Ví dụ, mở rộng (expansion) "{" decls() "}" bao gồm 3 đơn vị mở rộng(expansion units) “{” , decls(), và “}”. Một cú pháp đúng cho mở rộng này là phải bắt đầu bằng một dấu “{” kết thúc bởi dấu “}” và thỏa mãn hàm decls() ở phần thân.
Một đơn vị mở rộng(expansion_unit) có thể là một lookahead, một tập các khai báo và mã java (java_block), Các lựa chọn mở rộng(expansion_choices), .. expansion_unit ::= local_lookahead | java_block | "(" expansion_choices ")" [ "+" | "*" | "?" ] | "[" expansion_choices "]" | [ java_assignment_lhs "=" ] regular_expression | [ java_assignment_lhs "=" ] java_identifier "(" java_expression_list ")" Một đơn vị mở rộng có thể gồm các lựa chọn mở rộng có các tùy chọn “+”, “*”, “?”.
lookahead ::= "LOOKAHEAD" "(" [ java_integer_literal ] [ "," ] [ expansion_choices ] [ "," ] [ "{" java_expression "}" ] ")"
Số token được nhìn trước trước khi quyết định lựa chọn một điểm trong quá trình phân tích. Mặc định có giá trị 1. Số nhỏ hơn, quá trình phân tích nhanh hơn. Số này có thể định nghĩa lại cho các luật sinh cụ thể trong phạm vi ngữ pháp đểđịnh rõ tính chất giai đoạn sau.
Ví dụ của luật sinh BNF: void WriteStatement() : { Token t; } { "printf" t = <IDENTIFIER> { jjtThis.name = t.image; } } • regular_expr_production regular_expr_production ::= [ lexical_state_list ] regexpr_kind [ "[" "IGNORE_CASE" "]" ] ":" "{" regexpr_spec ( "|" regexpr_spec )* "}"