最近正在補習編譯原理的相關理論基礎。於是琢磨著寫個簡單的語言解析器。
1. python程式設計(ply庫)
2. 正規表示式
上下文無關文法;1. json裡的字典key必須是字串型,value可以是任意型別;
2. json根基點必須是字典或者陣列;
3. json支援的值包括:
- 數字(整數或浮點數)
- 字串(在雙引號中)
- 邏輯值(true 或 false)
- 陣列(在方括號中)
- 物件(在花括號中)
- null
4. 分割符為逗號",".
避免二義性,或者使用規則解決二義性衝突;
判斷二義性存在的方法:某一段輸入可以用兩棵以上語法樹解釋的,我們認為存在二義性;
優先順序,需要n+1個非終結符號;
下面先祭出自己寫的json的文法–bnf正規化。
ply 是純粹由 python 實現的 lex 和 yacc(流行的編譯器構建工具)。ply 的設計目標是盡可能的沿襲傳統 lex 和 yacc 工具的工作方式,包括支援 lalr(1)分析法、提供豐富的輸入驗證、錯誤報告和診斷。因此,如果你曾經在其他程式語言下使用過 yacc,你應該能夠很容易的遷移到 ply 上。英譯root_block : lsbracket block_item_list rsbracket
| lbrace block_item_list rbrace
| constant
| normstring colon root_block
| normstring
| keywords
block_item_list : block_item_list comma root_block
| root_block
token
標記context free grammar
上下文無關文法
syntax directed translation
語法制導的翻譯
ambiguity
二義性terminals
終結符non-terminals
非終結符
documentation string
文件字串(python中的docstring)
shift-reduce
移進-歸約
empty productions
空產生式
panic mode recovery
panic恢復模式
第一步. 詞法解析(lex)
宣告tokens,所有用到的token;
# list of token names. this is always required
tokens = (
'lsbracket',
'rsbracket',
'lbrace',
'rbrace',
'colon',
'normstring',
'constant',
'variable',
'comma',
'keywords',
)
在賦值表示式或者函式中,使用正則扣取token,並返回這個值。值得注意的是換行或者注釋是不再tokens裡面的,而且沒有返回值。
t_ignore = ' \t\r'
t_lsbracket = r'\['
t_rsbracket = r'\]'
t_lbrace = r'\'
t_colon = r':'
t_comma = r','
keywords = [
r'null',
]keyword = '|'.join(keyword.replace(' ', '\s+') for keyword in keywords)
@lex.token(keyword)
deft_keywords
(t):
# todo: to support false,true
t.value = none
return t
deft_newline
(t):
r'\n+'
t.lexer.lineno +=len(t.value)
錯誤控制代碼,在解析過程中報錯時,會呼叫這個函式。放一些除錯資訊進去會幫助你快速定位問題。
```
def t_error(t):
raise exception('error {} at line {}'.format(t.value[0], t.lineno))
```
解析,扣tokens的方法有了,解析就是給定一段輸入,輸出tokens。
```
# test it out
data = '''
3 + 4 * 10
+ -20 *2
'''# give the lexer some input
lexer.input(data)
# tokenize
while true:
tok = lexer.token()
if not tok: break # no more input
print tok
```
第二步. 語法解析(yacc)
說下大致思路: 使用bnf正規化,構建ast樹。輸出是你想把輸入翻譯成的樣子,比如計算器,你想要的是結果。json我想要一段python版的json資料結構。
第三步. 匯入標頭檔案
看很多博文,給**片段的博主都沒有加標頭檔案,天曉得你用的是什麼庫。
這裡祭出完整的** –>傳送門。
1. 詞法解析時,被處理的token沒有返回值
輸入為:
報錯資訊如下:,
,null,,,
,null, # 這裡是第11行,報錯的位置,可以看到問題出在','前面,,
,null,,,
,,,,
,,
null,,]
}, "a": }
解析:syntax error
in input! parser state:
[0, 1, 6, 10, 1, 8, 11, 6, 10, 3, 9, 11]
lbrace normstring colon lbrace block_item_list comma normstring colon lsbracket block_item_list comma . lextoken(comma,',',11,310)
[2017-02-09
15:30:56,725] file_parser.py:113-error: before: ',' ,line 11 column 13
第二行的列表是詞法解析的狀態跳轉棧,即從第0個狀態跳到第11個狀態。然而,在第11個狀態中,因為異常無法跳到下一狀態。
第三行是token的處理序列。重點是看序列的最後乙個token和lextoken, 最後乙個token代表現在能正確處理的最後乙個位置,而lextoken表示現在處理的是哪個字元,他們中間的位置就是有問題的部分。可以看到第11行逗號前面是null, null在我的邏輯中應該歸為keywords這類token,為什麼沒有出現呢?答案只有乙個,就是在lex扣取null時沒有返回值。
如何寫乙個Stack?
1.棧是陣列 2.先進後出 3.出棧 4.入棧 手寫乙個雙向鍊錶 棧 public class stackpopandpush public stackpopandpush int lens 返回元素個數 public intsize 返回陣列長度,容量,棧資料長 private intcapaci...
如何寫乙個鍊錶
有的時候,處於記憶體中的資料並不是連續的。那麼這時候,我們就需要在 資料結構中新增乙個屬性,這個屬性會記錄下面乙個資料的位址。有了這個位址之後,所有的資料就像一條鍊子一樣串起來了,那麼這個位址屬性就起到了穿線鏈結的作用。相比較普通的線性結構,鍊錶結構的優勢是什麼呢?我們可以總結一下 1 單個節點建立...
如何寫乙個Vue元件
寫的是以.vue結尾的單檔案元件的寫法,是基於webpack構建的專案。template 模板 js 邏輯 css 樣式 每個元件都有屬於自己的模板,js和樣式。如果將乙個頁面比喻成一間房子的話,元件就是房子裡的客廳 臥室 廚房 廁所。如果把廚房單獨拿出來的話,元件又可以是刀 油煙機.等等。就是說頁...