標籤:

原生Python寫parser

如果不是我經過一天的瞎搞搞出來的這個新辦法的話,我已經不喜歡寫parser了。。。(由於這個方法是我自己瞎搞出來的,雖然和大佬們說的parser組合子有點像,但估摸著還是naive... 所以有大佬看的話,笑笑就好...

四個月前的我非常鬼畜,腦袋裡只知道狀態機大概是什麼樣的(通過字面意思試圖理解含義,同理,遺傳演算法,機器學習這些都可以通過字面意思來研究...),這樣來寫parser(雖然當時我不知道這個叫parser), 腦袋裡時常裝著十幾二十幾個狀態,互相跳轉,一團亂碼。于是之後我就不想寫了。

昨天我想刷題,然後去codewars上寫了兩道看起來對自己比較簡單的紫題後,發現My Language(我當時還沒用Js寫過題,只有Python、Java寫過)里剩的題基本都是解釋器,我看著就難受...

演算法題呢,冰封那個扔雞蛋昨天沒讀懂,那個高樓大廈的演算法又寫不來,然後我就找啊找。

最後折中找到了一個解析Ascii85編碼的題(如果我哪一天開始做不到3kyu的題,就說明我開始養老了。...

一開始我要知道它是Parser我肯定不會去做。

然而寫到中途,關鍵步驟都弄出來了,但是一些邊角情況感覺有點亂。因為昨晚還寫了一個church number的水題,覺得很漂亮,於是心底不願意用OO來處理這些邊角情況。

所以花了一個下午研究如何漂亮地解決問題。

然後我舉幾個例子,表達一下我今天下午的所感所悟。

Parser

parser = lambda f: lambda string, parsed, ok=None : f(string, parsed, ok)nparser_continue = lambda string, parsed, ok=None : (string, parsed, True)n

parser是一個抽象解析器,你需要給它定義一個解析方法才能以解析。

parser_continue這個是一個跳過步驟的parser。

一個具體的Parser對象是callable的,有三個參數接受。第一個參數是未parsed的字元串,第二個參數是一個parse的結果,初始其實是一個空列表, 第三個參數,表示一次parse流程中,一組有優先順序的parser是否已經處理結束。

從Ascii256到Ascii85的轉化,一般情況對原始字元串以4個為一組進行切分,一般處理為長度為5的Ascii85字元串。我們假設已經寫出了一般情況的解析方法,名為_to85。

那麼對一般情況的Parser,先定義一個抽象方法chunk。

(因為各個語言里,chunk函數的意思和這裡分批次做解析看起來很相似,而我時而不太會命名,這裡就用chunk

chunk = lambda n : lambda f: parser(lambda string, parsed, ok: (string[n:], append(parsed,f(string[:n])), True) if not ok else parser_continue(string, parsed))n

chunk接受第一個參數n後,表示該parser按照最大n個為一組進行解析。

接受第二個參數f後,表示解析方法為f。

則一般情況下,從Ascii256到Ascii85的parser是

chunk_encode = chunk(4)(_to85 )n

還是很清晰的。

同理,當我告訴你從Ascii85到Ascii256的轉化,是以5個字元為一組進行,且一般情況的函數為_from85後,你是否可以寫出對應的parser呢?

嗯...是這樣。

chunk_decode = chunk(5)(_to64)n

然後有一些特殊情況,例如從256到85時,如果一組字元全為0,則解析為z

zcompress = parser(lambda string, parsed, ok: parser_continue(string, parsed) if ok else zcompress_ok(string ,parsed ))nzcompress_ok = parser(lambda string, parsed, ok=None:(string[4:], append(parsed, "z" ), True) if string.startswith("0"*4) else (string, parsed, False))n

這東西,我叫它z壓縮,名字我隨便取的...

zcompress這個parser的優先順序要在chunk_encode的前面。

先寫一個按照優先順序組合parser的抽象方法

parser_combine = lambda *f : lambda string, parsed, ok:(string,parsed, False) if not len(f) or ok else parser_combine(*f[1:])(*f[0](string,parsed, False)) n

然後我們把兩個parser按照正確的優先順序組合起來。

encode = parser_combine(zcompress , chunk_encode)n

而對於85到256,也有一些特殊情況,比如z解壓縮(當然這也是我取的名字

zdecompress = parser(lambda string, parsed, ok: parser_continue(string, parsed) if ok else zdecompress_ok(string, parsed)) nzdecompress_ok = parser(lambda string, parsed, ok=None:(string[1:], append(parsed, "0"*4), True) if string.startswith(z) else (string, parsed, False)) n

它把字母z解壓為4個0

組合一下

decode = parser_combine(zdecompress, chunk_decode)n

然後parser是怎麼執行的?

抽象方法如下,接受一個parser組合列。

parsing = lambda f : lambda string, parsed, ok =None : .join(parsed) if not string else parsing(f)(*f(string, parsed, False))n

然後從256轉化為85的方法和逆方法就是

_toAscii85 = parsing(encode)n_fromAscii85 = parsing(decode)nfromAscii85 = lambda string : print([ord(i) for i in string]) or andThen( unbox, lambda str:_fromAscii85(str, []))(re_clean.sub(,string))ntoAscii85 = lambda string : print([ord(i) for i in string]) or andThen(lambda str:_toAscii85(str, []), box)(string)n

(涉及到的一些方法,比如andThen就是Scala那個andThen, unbox表示把"<~xxx~>"轉成"xxx", box是它的逆。

舉一個使用例子

>> fromAscii85(<~GA(]4ATMg !@q?d)ATMq~>)n>> whitespace testn>> toAscii85(fromAscii85(<~GA(]4ATMg !@q?d)ATMq~>)) == <~GA(]4ATMg !@q?d)ATMq~>n>> Truen

額,很簡單...

因為難的地方稍微沒那麼好看,因為出現了不只是lambda的東西。要是能用flowpython該多好(逃

這幾天做的題=>

thautwarm/My-Blog

(當然這些方面只是業餘興趣。


推薦閱讀:

Python 遠程訪問系列之分散式RPC(上)
Python數據分析學習路徑
ruby和python該學那一個?
Python數據分析及可視化實例之爬蟲源碼(01)
為什麼Pypy沒有被推廣以及取代CPython?

TAG:Python | Parser |