3. ỨNG DỤNG PHÂN TÍCH MỘT SỐ CÚ PHÁP ĐƠN GIẢN
3.1. Phân tích các phép tính cơ bản
Trước khi thực hiện ví dụ này, chúng ta hãy lưu ý rằng trong ngôn ngữ python, việc phân tích một biểu thức và thậm chí các câu lệnh của python có thể thực hiện một cách dễ dàng với sự hỗ trợ sẳn có của nó. Ví dụ:
while True:
x = raw_input(">>>") print eval(x)
hàm eval sẽ thực hiện phân tích và tính toán biểu thức nhập vào theo đúng ngữ pháp của ngôn ngữ python.
Tuy nhiên, nó chỉ phân tích được với cú pháp của python chứ không thể phân tích được cú pháp theo một văn phạm khác. Ví dụ này chỉ có tính minh họa cho việc phân tích cú pháp chứ không tập trung vào ước lượng giá trị của một biểu thức.
Chúng ta sẽ chỉ hỗ trợ 4 toán tử cơ bản và sẽ hạn chế với toán hạng. Mục đích của ví dụ này là tính toán giá trị 1 biểu thức đơn giản có dạng
Toán hạng – toán tử - toán hạng : ví dụ 1 + 11
Chúng ta sẽ phải thực hiện 2 bước cơ bản để tính giá trị của nó:
Phân tích biểu thức trên thành 3 thành phần cơ bản, và đặt tên cho nó
Dựa vào văn phạm cố định như trên và các thành phần tìm được, tính toán giá trị kết quả.
Tạo tên file có tên calc.py
File có nội dung như sau
import sys
import operator
from pyparsing import nums, oneOf, Word
from pyparsing import ParseException
# Map math operations to callable functions within # the operator module.
op_map = { '*': operator.mul, '+': operator.add, '/': operator.div, '-': operator.sub } # define grammar
MATH_GRAMMAR = Word(nums).setResultsName('first_term') + \ oneOf('+ - / *').setResultsName('op') + \
Word(nums).setResultsName('second_term')
def handle_line(line): """
Parse a newly read line. """
result = MATH_GRAMMAR.parseString(line)
return op_map[result.op](int(result.first_term), int(result.second_term))
try:
print handle_line(raw_input('> ')) except ParseException, e:
print >>sys.stderr, "Syntax err at position %d: %s" % (e.col, e.line)
Chạy ứng dụng và thử nhập 1 số biểu thức, lưu ý các ngoại lệ và lỗi cú pháp
Hình 2.1. kết quả phân tích biểu thức (a +-*/) b
Giải thích
Trước tiên, chúng ta sẽ import 2 modules từ thư viên chuẩn : Module sys để truy xuất vào dòng xuất nhập chuẩn. module operator để truy xuất vào các hàm toán học cơ bản, ở đây nó cung cấp tên hàm cho các toán tử cơ bản.
Import nums, oneOf, và Word từ gói thư viện pyparsing. Mỗi đối tượng được sử dụng để tạo ra ngữ pháp đơn giản dựa trên Python của chúng ta sau này trong kịch bản.
Đối tượng nums chỉ đơn giản là một chuỗi có chứa tất cả các thể hiện của
bảng ASCII cho các chữ số từ 0 - 9. Nói cách
khác, nums.isdigit sẽ trả về True.
oneOf là một hàm phát sinh ra một thể hiện của pyparsing.Regex.
Word là một lớp để so khớp các từ (word) được hình thành từ các kí tự trong quá trình tạo.
Dựa vào các nền tảng trên, ta xét một cú pháp đơn giản dưới đây
Word(nums) + oneOf('+ - / *') + Word(nums)
Biểu thức trên chưa 3 thành phần chính : một toán hạng kiểu số , theo sau là một toán tử (+-/*) cuối cùng là một toán hạng kiểu số khác trong một biểu thức tính toán 2 toán hạng. Thư viện PyParsing thay đổi hàm __add__ vì vậy mà trong đoạn mã chúng kết hợp với nhau để tạo ra một đối tượng thuộc lớp PyParsing.And, là lớp so khớp biểu thức trên.
Trước khi kết hợp các thành phần với nhau, các thành phần này được đặt với một định danh với hàm setResultsName, nhằm mục đích truy xuất các thành phần sau khi được phân tích.
Phần còn lại là hàm handle_line, hàm này nhận giá trị là một chuỗi có cấu tạo như biểu thức trên và xuất ra giá trị trả về. Phần quang trọng nhất của hàm này là việc phân tích biểu thức ra kết quả bằng hàm MATH_GRAMMAR.parseString, hàm này làm việc tương tự như hàm re.match trong thư viện sử dụng Regular Expression. Các hàm thực hiện tương tự nhưng với cách thức khác nhau như
parseFile, parseWithTabs, scanString searchString. Nếu như phân tích bị lỗi, parseString sẽ quăng ra một ngoại lệ ParseException.
Cuối cùng chúng ta sẽ in kết quả ra màn hình sử dụng sys.print/
Thao tác dịch trực tiếp
Đoạn mã ví dụ trên, chúng ta thực hiện phân tích ra các thành phần biểu thức dưới dạng chuỗi. Sau đó chuyển sang kiểu int cho các toán hạng trước khi tính toán. Mỗi đối tượng PyParsing cho phép thiết lập một thao tác xử lý với một sự kiện của một phần tử khi được phân tích bằng cách sử dụng hàm
setParseAction trước khi phân tích def to_i(s, loc, toks):
"""Translate to int""" return int(toks[0]) MATH_GRAMMAR =
Word(nums).setResultsName('first_term').setParseAction(to_i) + \ oneOf('+ - / *').setResultsName('op') + \
Word(nums).setResultsName('second_term').setParseAction(to_i) def handle_line(line):
"""
Parse a newly read line. """
result = MATH_GRAMMAR.parseString(line) return op_map[result.op](result.first_term, result.second_term)
Kết quả sau khi phân tích ra của các toán hạng lúc này sẽ là biểu int , do đó chúng ta không cần chuyển kiểu. Và tất nhiên kết quả cuối cùng không đổi.