第一步:標記化
處理表達式的第一步就是將其轉化為包含一個個獨立符號的列表。這一步很簡單,且不是本文的重點,因此在此處我省略了很多。
首先,我定義了一些標記(數字不在此中,它們是默認的標記)和一個標記類型:
token_map = {'+':'ADD', '-':'ADD', '*':'MUL', '/':'MUL', '(':'LPAR', ')':'RPAR'} Token = namedtuple('Token', ['name', 'value'])下面就是我用來標記 `expr` 表達式的代碼:
split_expr = re.findall('[/d.]+|[%s]' % ''.join(token_map), expr)tokens = [Token(token_map.get(x, 'NUM'), x) for x in split_expr]第一行是將表達式分割為基本標記的技巧,因此
'1.2 / ( 11+3)' --> ['1.2', '/', '(', '11', '+', '3', ')']下一行命名標記,這樣分析器就能通過分類識別它們:
['1.2', '/', '(', '11', '+', '3', ')']->[Token(name='NUM', value='1.2'), Token(name='MUL', value='/'), Token(name='LPAR', value='('), Token(name='NUM', value='11'), Token(name='ADD', value='+'), Token(name='NUM', value='3'), Token(name='RPAR', value=')')]任何不在 token_map 中的標記被假定為數字。我們的分詞器缺少稱為驗證的屬性,以防止非數字被接受,但幸運的是,運算器將在以后處理它。
就是這樣
第二步: 語法定義
我選擇的解析器實現自一個本地垂直解析器,其來源于LL解析器的一個簡單版本。它是一個最簡單的解析器實現,事實上,只有僅僅14行代碼。它是一種自上而下的解析器,這意味著解析器從最上層規則開始解析(like:expression),然后以遞歸方式嘗試按照其子規則方式解析,直至符合最下層的規則(like:number)。換句話解釋,當自底向上解析器(LR)逐步地收縮標記,使規則被包含在其它規則中,直到最后僅剩下一個規則,而自頂向下解析器(LL)逐步展開規則并進入到少數的抽象規則,直到它能夠完全匹配輸入的標記。
在深入到實際的解析器實現之前,我們可對語法進行討論。在我之前發表的文章中,我使用過LR解析器,我可以像如下方式定義計算器語法(標記使用大寫字母表示):
add: add ADD mul | mul;mul: mul MUL atom | atom;atom: NUM | '(' add ')' | neg;neg: '-' atom;
(如果您還不理解上述語法,請閱讀我之前發表的文章)
現在我使用LL解析器,以如下方式定義計算器的語法:
rule_map = { 'add' : ['mul ADD add', 'mul'], 'mul' : ['atom MUL mul', 'atom'], 'atom': ['NUM', 'LPAR add RPAR', 'neg'], 'neg' : ['ADD atom'],}大家可以看到,這里有一個微妙的變化。有關"add and mul"的遞歸定義被反轉了。這是個非常重要的細節,我會向大家詳細說明這一點。
新聞熱點
疑難解答