能用 AST 搞明白的正則語(yǔ)法,就不需要看文檔
字符串的處理基本都會(huì)用正則表達(dá)式,用它來做字符串的匹配、提取、替換等很方便。
但是正則表達(dá)式的學(xué)習(xí)還是有些難度的,比如貪婪匹配、非貪婪匹配、捕獲子組、非捕獲子組等概念,不止初學(xué)者難理解,有很多工作幾年的人都不理解。
那正則表達(dá)式怎么學(xué)比較好?怎么快速掌握正則表達(dá)式呢?
推薦一個(gè)我覺得很不錯(cuò)的學(xué)習(xí)正則的方式:通過 AST 來學(xué)習(xí)。
正則表達(dá)式的匹配原理是把模式串 parse 成 AST,然后通過這個(gè) AST 去匹配目標(biāo)字符串。
模式串中的各種信息在 parse 之后都會(huì)保存在 AST 里面。AST 是 abstract syntax tree,抽象語(yǔ)法樹的意思,顧名思義,是按照語(yǔ)法結(jié)構(gòu)組織的一棵樹,那么從 AST 的結(jié)構(gòu)上自然可以輕易的知道正則表達(dá)式支持的語(yǔ)法。
怎么查看正則表達(dá)式的 AST 呢?
可以通過 astexplorer.net 這個(gè)網(wǎng)站來可視化的查看:
切換 parse 的語(yǔ)言為 RegExp,就可以做正則表達(dá)式的 AST 的可視化。
就像前面所說,AST 是按照語(yǔ)法來組織的一棵樹,那么從它的結(jié)構(gòu)上自然能容易地理清各種語(yǔ)法。
那么我們就從 AST 的角度來學(xué)習(xí)下各種語(yǔ)法吧:
/abc/
先從簡(jiǎn)單的開始,/abc/ 這樣一個(gè)正則就可以匹配 'abc' 的字符串,它的 AST 是這樣的:
3 個(gè) Char,值分別是 a、b、c,類型是 simple。那之后的匹配就是遍歷 AST,分別匹配這三個(gè)字符了。
我們用 exec 的 api 測(cè)試了下:
第 0 個(gè)元素是匹配的字符串,index 是匹配字符串的開始下標(biāo)。input 是輸入的字符串。
再來試下特殊的字符:
/\d\d\d/
/\d\d\d/ 是匹配三個(gè)數(shù)字的意思,\d 是正則支持的有特殊含義的元字符(meta char)。
通過 AST 我們也可以看出來,它們雖然也是 Char,但類型確是 meta:
可以通過 \d 的元字符來匹配任意數(shù)字:
哪些是 meta char 哪些是 simple char,通過 AST 來看一目了然。
/[abc]/
正則支持通過 [] 的方式來指定一組字符,也就是說匹配其中任意一種字符都行。
通過 AST 我們也可以看出來,它被包裹了一層 CharacterClass,就是字符類的意思,也就是匹配它包含的任意一種字符都行。
測(cè)試下也確實(shí)是這樣:
/a{1,3}/
正則表達(dá)式支持指定某個(gè)字符重復(fù)多少次,用 {from,to} 的形式,比如 /b{1,3}/ 表示字符 b 重復(fù) 1 到 3 次,/[abc]{1,3}/ 表示這個(gè) a/b/c 這個(gè)字符類重復(fù) 1 到 3 次。
通過 AST 可以看出來,這種語(yǔ)法叫做 Repetition(重復(fù)):
他有個(gè) quantifier 的屬性表示量詞,這里的類型是 range,從 1 到 3。
正則也支持一些量詞的簡(jiǎn)寫,比如 + 表示 1 到無數(shù)次、* 表示 0 到無數(shù)次、? 表示 0 或 1 次。
分別是不同類型的量詞:
有同學(xué)可能會(huì)問,這里的 greedy 屬性是啥意思呢?
greedy 是貪婪的意思,這個(gè)屬性就表示這個(gè) Repetition 是貪婪匹配還是非貪婪匹配。
如果在量詞后加個(gè) ?,你就會(huì)發(fā)現(xiàn) greedy 變成 false 了,也就是切換到了非貪婪匹配:
那貪婪和非貪婪是指啥呢?
我們看個(gè)例子就知道了。
默認(rèn) Repetition 的匹配是貪婪的,只要滿足條件就一直匹配下去,所以這里 acbac 都能匹配到。
量詞后加個(gè) ? 就切換到了非貪婪,就只會(huì)匹配第一個(gè)了:
這就是貪婪匹配和非貪婪匹配,通過 AST 我們能夠清楚的知道貪婪和非貪婪是針對(duì)重復(fù)語(yǔ)法來說的,默認(rèn)是貪婪匹配,在量詞后加個(gè) ? 就可以切換到非貪婪。
(aaa)bbb(ccc)
正則表達(dá)式支持通過()把匹配到的一部分字符串放到子組里返回。
通過 AST 看一下:
對(duì)應(yīng)的 AST 就叫做 Group。
而且你會(huì)發(fā)現(xiàn)它有個(gè) capturing 的屬性,默認(rèn)是 true:
這是啥意思呢?
這就是子組捕獲的語(yǔ)法。
如果不想捕獲子組,可以這樣寫 (?:aaa)。
看,capturing 變?yōu)?false 了。
那捕獲和非捕獲有什么區(qū)別呢?
我們?cè)囈幌拢?/p>
哦,原來 Group 的 capturing 屬性代表的是是否提取的意思啊。
我們通過 AST 可以看出來,捕獲是針對(duì)子組來說的,默認(rèn)是捕獲,也就是提取子組的內(nèi)容,可以通過 ?: 切換到非捕獲,就不會(huì)提取子組的內(nèi)容了。
我們對(duì)用 AST 來了解正則語(yǔ)法已經(jīng)輕車熟路了,那來看點(diǎn)難的!
/bbb(?=ccc)/
正則表達(dá)式支持通過 (?=xxx) 的語(yǔ)法來表示先行斷言,用來判斷某個(gè)字符串是否前面是某個(gè)字符串。
通過 AST 可以看到這種語(yǔ)法叫做 Assertion,并且類型為 lookahead,也就是往前看,只匹配前面的意思:
這是啥意思呢?為啥要這么寫?和 /bbb(ccc)/ 還有 /bbb(?:ccc)/有啥區(qū)別呢?
我們?cè)囈幌拢?/p>
從結(jié)果可以看出來:
/bbb(ccc)/ 匹配了 ccc 的子組并且提取出來了這個(gè)子組,因?yàn)槟J(rèn)子組是捕獲的。
/bbb(?:ccc)/ 匹配了 ccc 的子組但沒有提取出來,因?yàn)槲覀兺ㄟ^ ?: 設(shè)置了子組不捕獲。
/bbb(?=ccc)/ 匹配了 ccc 的子組也沒有提取出子組,說明也是非捕獲的。它和 ?: 的區(qū)別是 ccc 沒有出現(xiàn)在匹配結(jié)果里。
這就是先行斷言(lookahead assertion)的性質(zhì):先行斷言代表某段字符串前面是某段字符串,對(duì)應(yīng)的子組是非捕獲的,而且斷言的字符串不會(huì)出現(xiàn)在匹配結(jié)果中。
如果后面不是跟著那段字符串就不匹配:
/bbb(?!ccc)/
把 ?= 改成 ?! 之后意思就變了,通過 AST 看一下:
雖然還是先行斷言 lookahead assertion,但是多了個(gè) negative 為 true 的屬性。
這個(gè)意思很明顯,本來是前面是某段字符串,否定之后就是前面不是某段字符串。
那匹配結(jié)果正好就反過來了:
現(xiàn)在前面不是某段字符串的話才匹配了,這就是否定先行斷言。
/(?<=aaa)bbb/
有先行斷言自然也有后行斷言,也就是后面是某段字符串才匹配。
同理,也可以否定:
(?<=aaa)對(duì)應(yīng)的 AST 很容易想到,就是 lookbehind assertion:
(?<!aaa)對(duì)應(yīng)的 AST 就是加個(gè) negative 屬性:
先行斷言、后行斷言就是最難理解的正則表達(dá)式語(yǔ)法了,通過 AST 來學(xué)習(xí)是不是就容易理解多了。
總結(jié)
正則表達(dá)式是處理字符串的很方便的工具,但它的學(xué)習(xí)還是有些難度的,像貪婪匹配、非貪婪匹配、捕獲子組、非捕獲子組、先行斷言、后行斷言等語(yǔ)法很多人都搞不清楚。
我推薦通過 AST 來學(xué)習(xí)正則,AST 是按照語(yǔ)法結(jié)構(gòu)來組織的一顆對(duì)象樹,各種語(yǔ)法通過 AST 節(jié)點(diǎn)的名字和屬性可以輕易的理清楚。
比如我們通過 AST 理清楚了:
重復(fù)語(yǔ)法(Repetition)就是字符 + 量詞的形式,默認(rèn)是貪婪匹配(greedy 為 true),代表一直匹配到不匹配為止,量詞后加個(gè) ? 就切換成了非貪婪匹配,匹配到一個(gè)字符就停止。
子組語(yǔ)法(Group)是用于提取某段字符串的,默認(rèn)是捕獲(capturing 為 true),代表需要提取,可以通過 (?:xxx)切換到非捕獲,只匹配不提取。
斷言語(yǔ)法(Assertion)代表前面或后面有某段字符串,分為先行斷言(lookahead assertion)和后行斷言(lookbehind assertion),語(yǔ)法分別是(?=xxx)和 (?<=xxx),可以通過把 = 換成 ! 來表示否定(negative 為 true),意思正好反過來。
是各種文檔對(duì)語(yǔ)法理解的深還是編譯器對(duì)語(yǔ)法理解的深?
那還用問,肯定是編譯器呀!
那么通過它按照語(yǔ)法 parse 出來的語(yǔ)法樹來學(xué)習(xí)語(yǔ)法自然比文檔更好。
正則表達(dá)式是這樣,其他的語(yǔ)法的學(xué)習(xí)也是這樣,能用 AST 學(xué)會(huì)的語(yǔ)法,就不需要看文檔。