不會 tokenizer 和 parser 是種怎樣的體驗?
winter:所以其實你更應該關心不會tokenizer和parser是什麼水平。
不會 tokenizer 和 parser 是種怎樣的體驗?
之前的公司一個項目需要把一種build definition換成另一種build definition,大概有一百多個項目吧,老外給了我們五個人大致兩個月的時間,然後我自己寫了一個語義解析和替換程序把我的部分兩天就搞完了,後來因為每天裝忙實在太痛苦,就share出來了,加上調試一共一周就搞定了,老外都驚呆了,完全沒有心理準備,緩了一個月才給我們找到下一個任務。你問不會寫是什麼體驗?可能就是對面老外後來對我的評價吧:寫的程序可讀性太差,不好維護。
以前開發bbsmax的時候還不會寫tokenizer和parser,但是又需要一個靈活的模板系統,於是手寫了一大堆正則表達式,有的正則表達式單個有幾十行,然後一個個調用下去直到解析出所需的數據,其實就是用正則替代了parser做的事情,到後來要加或者改點東西都很難,提心弔膽的怕哪一天加不下去了就跪了。
後來因為有了語法設計的經驗和解析器的經驗了,就開竅能設計DSL和寫簡單的tokenizer、parser了。
野生程序員的黑歷史,僅供參考。
為了震撼一下各位,我找了個貼出來,它還不是最長的,這個正則是用來匹配模板中的各種調用的:
using System;
using System.Text;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace MaxLabs.bbsMax.RegExp
{
public class TemplateMemberInvokeRegex : Regex
{
private const string PATTERN = @"G
(?&>
(?&>
s*.s*(?&
(?&>
(?&>
&<(?&>[a-z_]+[a-z_d]*)(?&>s*,s*[a-z_]+[a-z_d]*)*&>
){0,1}
(
(?&
(?&>
""[^""\
]*(?:\.[^""\
]*)*""
|
"[^"\
]*(?:\.[^"\
]*)*"
|
((?&
|
)(?&<-n&>)
|
(?!(|)).
)*
)
(?(n)(?!))
)
){0,1}
)
|
(?&>
(?&>
[
(?&
(?&>
""[^""\
]*(?:\.[^""\
]*)*""
|
"[^"\
]*(?:\.[^"\
]*)*"
|
[(?&
|
](?&<-n&>)
|
(?![|]).
)+
)
(?(n)(?!))
]
)
|
(?&>
(?&>
&<(?&>[a-z_]+[a-z_d]*)(?&>s*,s*[a-z_]+[a-z_d]*)*&>
){0,1}
(
(?&
(?&>
""[^""\
]*(?:\.[^""\
]*)*""
|
"[^"\
]*(?:\.[^"\
]*)*"
|
((?&
|
)(?&<-n&>)
|
(?!(|)).
)*
)
(?(n)(?!))
)
)
)
)
(?&
(?&>
(?&>
s*.s*
(?&>
(?&>[a-z_]+[a-z_d]*)
(?&>
(?&>
&<(?&>[a-z_]+[a-z_d]*)(?&>s*,s*[a-z_]+[a-z_d]*)*&>
)
(?&>
(
(?&>
(?&>
""[^""\
]*(?:\.[^""\
]*)*""
|
"[^"\
]*(?:\.[^"\
]*)*"
|
((?&
|
)(?&<-n&>)
|
(?!(|)).
)*
)
(?(n)(?!))
)
)
){0,1}
(?&>
(
(?&>
(?&>
""[^""\
]*(?:\.[^""\
]*)*""
|
"[^"\
]*(?:\.[^"\
]*)*"
|
((?&
|
)(?&<-n&>)
|
(?!(|)).
)*
)
(?(n)(?!))
)
)*
)
)
|
(?&>
(?&>
(
(?&>
(?&>
""[^""\
]*(?:\.[^""\
]*)*""
|
"[^"\
]*(?:\.[^"\
]*)*"
|
((?&
|
)(?&<-n&>)
|
(?!(|)).
)*
)
(?(n)(?!))
)
)
|
(?&>
[
(?&>
(?&>
""[^""\
]*(?:\.[^""\
]*)*""
|
"[^"\
]*(?:\.[^"\
]*)*"
|
[(?&
|
](?&<-n&>)
|
(?![|]).
)+
)
(?(n)(?!))
]
)
)
)*
)";
private const RegexOptions OPTIONS = RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase | RegexOptions.Compiled;
public TemplateMemberInvokeRegex()
: base(PATTERN, OPTIONS)
{
}
}
}
由於我讀大學的時候沒學過編譯理論(我的本科是認知科學??額??我也沒上過計算機圖形學),所以至今也不能理解一些相關的術語、理論(什麼LR、LALR那些)。
有時候可能寫了一些相關的東西,但不能它用了那些理論,也不確定該實現是否有更優解。
對於一些簡單的語法,似乎無須編譯理論的知識也可以寫 parser,例如我之前做的 RapidJSON,以及最近寫了個 JSON Schema 用的簡單 Regex 實現(NFA引擎)。但再複雜一點的似乎會較麻煩,可能需要使用(或重造)一些工具,而不是隨便手寫,當然理論的支持也變得重要。
不是所有的工作都能用上編譯理論中的東西,但我會有興趣去補回來,計算機專業本科程度的東西自學應該難度不大,剛剛也買了書回來。
我的體驗如上。水平就不談了。
--補充:我覺得這個問題與「不會寫rasterizer和raytracer是什麼體驗」差不多,問不懂的技能是什麼體驗好像意義不大。問會寫之後什麼體驗,可能更有參考意義。不會就不會唄不會能死啊該吃吃該睡睡寫碼又不是生命的全部世界這麼大好玩的事兒多了去了每個鮮活的個體又不同別跟生命里無聊的就會寫碼的人計較
之前給某大型手游升級引擎,大概是30-40萬行的代碼量吧。因為新版本的API有變化,所以弄了一套腳本利用正則表達式來做自動匹配和替換,大概能自動完成70%-80%的工作。
同樣某大型手游,因為跨平台移植的關係代碼需要從OC翻譯成C++,使用腳本替換搞定了接近80%的工作量。
還有像利用配置文件自動生成代碼這類的工作 @達達 已經說過了,我手頭也有幾個工程就是類似的做法。
當然......這一切都是我在不會tokenizer和parser的前提下土法鍊鋼完成的......
所以現在想想,如果當初我就會的話,估計替換的轉換率可以提升到95%以上.....渣渣程序員,說一下自己的經歷。
之前有個項目,做實時計算,就是想對從消息隊列中讀取的對象做一些統計,你們可以理解成對一個流式的寬表,進行一些SQL的計算。當時的技術老大寫了一個引擎,對於可以寫一個sql語句,對於 List這種數據結構進行SQL計算。但是他parser是用的https://github.com/JSQLParser/JSqlParser,所以只支持SQL語句,如果拿到了計算結果了,還是需要寫代碼把結果拿落地的。我當時就想,如果落地的這部分代碼,也能用SQL語句完成,比如insert into redis **該多好,於是我就琢磨著琢磨重寫。
翻看了很多書,包括 Manning: DSLs in ActionDomain Specific LanguagesLanguage Implementation Patterns (豆瓣) 之後學習scala用它的parser combinator,自己實現了一個SQL引擎,jadetang/scala_sql · GitHub 功能和之前項目中用到的差不多。但是從parser到解析都是自己寫的,沒有依賴第三方類庫。
但是,這還不是手寫tokenlizer和parser,直到有天我看到了一本神書:
《兩周自製腳本語言》 千葉滋, 陳筱煙【摘要 書評 試讀】圖書這本書從 語法分析,語義分析,執行,代碼優化從頭到位實現了一門語言,而且全是用java寫的。看完這本書的前半部分,在結合之前看的那些書裡面的LL(1),LL(K)演算法什麼的,我手寫了一個4則運算器 jadetang/calculator · GitHub,至此我也可以說我是會寫 tokenlizer 和 parser的人了。(雖然我的計算器只支持整數)
其實會寫tokenlizer 和 parser的感覺還是挺好的,因為一般的DSL都是為了完成特定的任務而實現的,不會有現成的輪子,所以我的體驗是,會寫tokenlizer 和 parser,除了能夠體會到自製一門語言的爽快感以外,也給我們光明正大重新造輪子的借口。也許因為本人非cs專業出身,所以孤陋,並不知道你說的 tokeniser 和 parser 值得是什麼東西。所以,答案就是沒有感覺。
你在問什麼?我看不懂。
真實的情況是知乎上大部分猿,裸寫冒泡排序都費勁。所以你問的對大部分猿而言並不care
吃的香, 睡得棒.
我曾經做過一個項目,高亮自己公司某腳本語言的語法,並標記錯誤。
完全不懂tokenizer和parser,因為的確沒學過(土木驢)
誰痛苦誰知道!曾經實現了一個模板系統用來在網頁里生成各種各樣的格式(大概類似於wiki代碼那種),第一版用了正則,寫了很多的正則表達式。完美實現了解析和替換。
然後版本迭代,慢慢就變的不對了。。。。模板系統越來越複雜,正則式越來越多。甚至需要另一些正則式先處理一遍模板,然後用正則式再處理第一遍的處理結果。就算這樣系統的bug越來越多,調試越來越複雜。
每個正則式的順序都是非常重要的。調試bug特別的痛苦。。。。。然而我比現在最高票答案慘多了,有一天我發現我再也沒法維護這份代碼了。我發現了一個bug,是在我們的模板處理模板代碼保持原樣輸出的時候出現的,然後我跪了。這個不應該替換的地方出現的替換,我怎麼改正則表達式都不能讓它消失。。。。我用了一整天來整理我正則替換的步驟和思路,嘗試修復這個問題。。。。。
第二天,我開始著手設計Tokenizer和Parser了。。。。
當我Tokenizer寫好之後,我的代碼是原來的五分之一。而且非常清爽,邏輯非常自然,工作非常穩定。比原來的複雜的正則不知道高到哪裡去了。等等,這好像是會寫Tokenizer和Parser是一種怎樣的體驗。。。
應用層怎麼就不能用Tokenizer和Parser了,這又不是什麼高大上的東西。這是什麼卵問題?!
我就想吐句槽:會了你又能增加多少優越感?不會難道你就沒有優越感?
真正的牛逼不是成天嚷嚷自己會什麼別人不會什麼,而是你用你會的做了什麼、幹了多少貢獻、是不是單純只是浪費電?許多在應用層上混飯吃的代碼猴因為(大部分時候)不需要考慮 tokenizer 和 parser ,大學一讀完工作中用不到的知識年久失修,所以不會也沒什麼,軟體照樣做工資照樣拿。
何況編譯器前端本身可挖的東西已經不多,基本功不錯的代碼猴都可以很快上手。人總喜歡拿自己擅長的東西來博取優越感。不過如果是專門做編譯器的不會這個,那體驗應該是餓,因為沒錢買飯吃。完全沒聽說過,這兩個是什麼?
人家winter讓你問「是什麼水平」,不是「什麼體驗」
省了不少時間健身、陪妹子玩耍!
會flex和bison就可以了
給出文法規則的話,這倆不都是體力活嗎?還是他們說的是在不告訴問法規則的前提下寫?那就比較難了
推薦閱讀:
※如何看待CC++使用大量的宏定義函數?
※關於自己的知識庫的困惑?
※對於一個新手來說,在使用Visual Studio新建項目時,win32控制台應用程序和win32項目、空項目、MFC應用程序有哪些區別?