簡體   English   中英

在 c 樣式注釋中讀取 Python 正則表達式

[英]Python Regex reading in c style comments

我試圖在 ac 文件中找到 c 樣式的注釋,但如果恰好在引號內,我會遇到麻煩。 這是文件:

/*My function
is great.*/
int j = 0//hello world
void foo(){
    //tricky example
    cout << "This // is // not a comment\n";
}

它將與該 cout 匹配。 這就是我到目前為止所擁有的(我已經可以匹配 /**/ 評論)

fp = open(s)

p = re.compile(r'//(.+)')
txt = p.findall(fp.read())
print (txt)

第一步是確定不能將///*解釋為注釋子字符串開頭的情況。 例如,當它們在字符串內時(引號之間) 為了避免引號(或其他東西)之間的內容,訣竅是將它們放在捕獲組中並在替換模式中插入反向引用:

圖案:

(
    "(?:[^"\\]|\\[\s\S])*"
  |
    '(?:[^'\\]|\\[\s\S])*'
)
|
//.*
|
/\*(?:[^*]|\*(?!/))*\*/

替代品:

\1

在線演示

由於引用的部分首先搜索,因此每次找到///*...*/時,您可以確定您不在字符串中。

請注意,該模式是自願低效的(由於(A|B)*子模式)以使其更易於理解。 為了提高效率,您可以像這樣重寫它:

("(?=((?:[^"\\]+|\\[\s\S])*))\2"|'(?=((?:[^'\\]+|\\[\s\S])*))\3')|//.*|/\*(?=((?:[^*]+|\*(?!/))*))\4\*/

(?=(something+))\1只是模擬原子組(?>something+)的一種方式

在線演示

因此,如果您只想查找注釋(而不是刪除它們),最方便的是將模式的注釋部分放在捕獲組中並測試它是否不為空。 以下模式已被修改(在 Jonathan Leffler 評論之后)處理被預處理器解釋為反斜杠字符的三元組??/ (我假設代碼不是為-trigraphs選項編寫的)並處理反斜杠后跟一個換行符,允許在多行中格式化單行:

fp = open(s)

p = re.compile(r'''(?x)
(?=["'/])      # trick to make it faster, a kind of anchor
(?:
    "(?=((?:[^"\\?]+|\?(?!\?/)|(?:\?\?/|\\)[\s\S])*))\1" # double quotes string
  |
    '(?=((?:[^'\\?]+|\?(?!\?/)|(?:\?\?/|\\)[\s\S])*))\2' # single quotes string
  |
    (
        /(?:(?:\?\?/|\\)\n)*/(?:.*(?:\?\?|\\)/\n)*.* # single line comment
      |
        /(?:(?:\?\?/|\\)\n)*\*                       # multiline comment
        (?=((?:[^*]+|\*+(?!(?:(?:\?\?/|\\)\n)*/))*))\4
        \*(?:(?:\?\?/|\\)\n)*/             
    )
)
''')

for m in p.findall(fp.read()):
    if (m[2]):    
        print m[2]

這些更改不會影響模式效率,因為正則表達式引擎的主要工作是查找以引號或斜杠開頭的位置。 此任務通過在模式開始處存在的前瞻(?=["'/])來簡化,它允許內部優化以快速找到第一個字符。

另一個優化是使用模擬原子組,它將回溯減少到最小,並允許在重復組內使用貪婪量詞。

注意:C 中可能沒有heredoc 語法!

Python 的re.findall方法基本上與大多數詞法分析器的工作方式相同:它連續返回從前一個匹配完成的位置開始的最長匹配。 所需要的只是產生所有詞匯模式的析取:

(<pattern 1>)|(<pattern 2>)|...|(<pattern n>)

與大多數詞法分析器不同,它不需要匹配是連續的,但這並不是顯着的區別,因為您總是可以添加(.)作為最后一個模式,以便單獨匹配所有其他不匹配的字符。

re.findall的一個重要特性是,如果正則表達式有任何組,那么只會返回這些組。 因此,您可以通過簡單地省略括號或將它們更改為非捕獲括號來排除替代方案:

(<pattern 1>)|(?:<unimportant pattern 2>)|(<pattern 3)

考慮到這一點,讓我們看看如何將 C 標記化到足以識別評論的程度。 我們需要處理:

  1. 單行注釋: // Comment
  2. 多行注釋: /* Comment */
  3. 雙引號字符串: "Might include escapes like \n"
  4. 單引號字符: '\t'
  5. (請參閱下面的一些更令人惱火的案例)

考慮到這一點,讓我們為上述每個創建正則表達式。

  1. 兩個斜杠后跟除換行符以外的任何內容: //[^\n]*
  2. 這個正則表達式解釋起來很繁瑣: /*[^*]*[*]+(?:[^/*][^*]*[*]+)*/請注意,它使用(?:...)避免捕獲重復的組。
  3. 引號,除引號和反斜杠之外的字符的任何重復,或反斜杠后跟任何字符。 這不是轉義序列的精確定義,但它足以檢測"何時終止字符串,這就是我們所關心的: "(?:[^"\\]|\\.*)"
  4. 與 (3) 相同,但使用單引號: '(?:[^'\\]|\\.)*'

最后,目標是找到 C 風格的注釋文本。 所以我們只需要避免在任何其他組中捕獲。 因此:

p = re.compile('|'.join((r"(//[^\n])*"
                        ,r"/*[^*]*[*]+(?:[^/*][^*]*[*]+)*/"
                        ,'"'+r"""(?:[^"\\]|\\.)*"""+'"'
                        ,r"'(?:[^'\\]|\\.)*'")))
return [c[2:] for c in p.findall(text) if c]

上面,我省略了一些不太可能出現的模糊案例:

  1. #include <...>指令中, <...>本質上是一個字符串。 理論上,它可以包含看起來像注釋的引號或序列,但實際上你永遠不會看到:

     #include </*This looks like a comment but it is a filename*/>
  2. \結尾的行在下一行繼續; \和后面的換行符只是從輸入中刪除。 這發生執行任何詞法掃描之前,因此以下是完全合法的注釋(實際上是兩條注釋):

     /\ **************** Surprise! **************\ //////////////////////////////////////////
  3. 更糟糕的是,三元組??/\相同,並且替換發生在繼續處理之前。

     /************************************//??/ **************** Surprise! ************??/ //////////////////////////////////////////

    在混淆競賽之外,沒有人真正使用三元組。 但它們仍然符合標准。 處理這兩個問題的最簡單方法是預掃描字符串:

     return [c[2:] for c in p.findall(text.replace('//?','\\').replace('\\\n','')) if c]

處理#include <...>問題的唯一方法是,如果您真的關心它,那就是再添加一個模式,例如#define\s*<[^>\n]*>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM