2016-04-19 78 views
6

我試圖在012.的Golang中實現removeComments函數。我希望刪除文中的任何評論。例如:Golang正則表達式替換不包括帶引號的字符串

/* this is comments, and should be removed */ 

However, "/* this is quoted, so it should not be removed*/" 

在Javascript實現中,引用的匹配不會在組中捕獲,因此我可以輕鬆地將它們濾除。但是,在Golang中,似乎很難判斷匹配的部分是否被捕獲到一個組中。那麼,如何在Golang中實現與Javascript版本中相同的removeComments邏輯?

+1

你會考慮使用[墊片](https://golanglibs.com/top?q=shim) - 即[golang-PKG-PCRE(https://github.com/glenn-brown/golang-PKG-PCRE)? –

回答

1

這些不保留格式


首選方式(生產,如果第1組不匹配,一個NULL)
作品golang遊樂場 -

 # https://play.golang.org/p/yKtPk5QCQV 
    # fmt.Println(reg.ReplaceAllString(txt, "$1")) 
    # (?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/|//[^\n]*(?:\n|$))|("[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'|[\S\s][^/"'\\]*) 

    (?:        # Comments 
      /\*        # Start /* .. */ comment 
      [^*]* \*+ 
      (?: [^/*] [^*]* \*+)* 
     /        # End /* .. */ comment 
     | 
      // [^\n]*      # Start // comment 
      (?: \n | $)      # End // comment 
    ) 
    | 
    (        # (1 start), Non - comments 
      " 
      [^"\\]*       # Double quoted text 
      (?: \\ [\S\s] [^"\\]*)* 
      " 
     | 
      ' 
      [^'\\]*       # Single quoted text 
      (?: \\ [\S\s] [^'\\]*)* 
      ' 
     | [\S\s]       # Any other char 
      [^/"'\\]*      # Chars which doesn't start a comment, string, escape, or line continuation (escape + newline) 
    )        # (1 end) 

替代方式(第1組始終是匹配的,但可能是空的)
作品golang遊樂場 -

# https://play.golang.org/p/7FDGZSmMtP 
# fmt.Println(reg.ReplaceAllString(txt, "$1")) 
# (?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/|//[^\n]*(?:\n|$))?((?:"[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'|[\S\s][^/"'\\]*)?)  

(?:        # Comments 
     /\*        # Start /* .. */ comment 
     [^*]* \*+ 
     (?: [^/*] [^*]* \*+)* 
    /        # End /* .. */ comment 
    | 
     // [^\n]*      # Start // comment 
     (?: \n | $)      # End // comment 
)? 
(        # (1 start), Non - comments 
     (?: 
      " 
      [^"\\]*       # Double quoted text 
      (?: \\ [\S\s] [^"\\]*)* 
      " 
     | 
      ' 
      [^'\\]*       # Single quoted text 
      (?: \\ [\S\s] [^'\\]*)* 
      ' 
     | [\S\s]       # Any other char 
      [^/"'\\]*      # Chars which doesn't start a comment, string, escape, or line continuation (escape + newline) 
    )? 
)        # (1 end) 

的Cadilac - 果脯格式

(不幸的是,這可以」不會在Golang中完成,因爲Golang不能執行斷言)
發佈後,您將移至不同的正則表達式引擎。

 # raw: ((?:(?:^[ \t]*)?(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/(?:[ \t]*\r?\n(?=[ \t]*(?:\r?\n|/\*|//)))?|//(?:[^\\]|\\(?:\r?\n)?)*?(?:\r?\n(?=[ \t]*(?:\r?\n|/\*|//))|(?=\r?\n))))+)|("[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'|(?:\r?\n|[\S\s])[^/"'\\\s]*) 
    # delimited: /((?:(?:^[ \t]*)?(?:\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\/(?:[ \t]*\r?\n(?=[ \t]*(?:\r?\n|\/\*|\/\/)))?|\/\/(?:[^\\]|\\(?:\r?\n)?)*?(?:\r?\n(?=[ \t]*(?:\r?\n|\/\*|\/\/))|(?=\r?\n))))+)|("[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'|(?:\r?\n|[\S\s])[^\/"'\\\s]*)/ 

    (        # (1 start), Comments 
      (?: 
       (?:^[ \t]*)?     # <- To preserve formatting 
       (?: 
        /\*        # Start /* .. */ comment 
        [^*]* \*+ 
        (?: [^/*] [^*]* \*+)* 
        /        # End /* .. */ comment 
        (?:        # <- To preserve formatting 
         [ \t]* \r? \n          
         (?= 
           [ \t]*     
           (?: \r? \n | /\* | //) 
         ) 
        )? 
       | 
        //        # Start // comment 
        (?:        # Possible line-continuation 
         [^\\] 
         | \\ 
         (?: \r? \n)? 
        )*? 
        (?:        # End // comment 
         \r? \n        
         (?=        # <- To preserve formatting 
           [ \t]*       
           (?: \r? \n | /\* | //) 
         ) 
         | (?= \r? \n) 
        ) 
       ) 
     )+        # Grab multiple comment blocks if need be 
    )        # (1 end) 

    |         ## OR 

    (        # (2 start), Non - comments 
      " 
      [^"\\]*       # Double quoted text 
      (?: \\ [\S\s] [^"\\]*)* 
      " 
     | 
      ' 
      [^'\\]*       # Single quoted text 
      (?: \\ [\S\s] [^'\\]*)* 
      ' 
     | 
      (?: \r? \n | [\S\s])   # Linebreak or Any other char 
      [^/"'\\\s]*      # Chars which doesn't start a comment, string, escape, 
              # or line continuation (escape + newline) 
    )        # (2 end) 
+0

謝謝你的偉大答案! –

+0

@ElgsQianChen - 沒問題。這是解析所有C風格語言中的註釋的事實標準答案。原來是從幾年前的Perl新聞組收集的(不是我寫的),然後我做了一些重大修改。信息適用於所有參與此問題/答案的人。 – sln

0

試試這個例子..

play golang

+0

謝謝,但它沒有按照我的預期工作。試試這個例子:https://play.golang.org/p/hYmbMP7ifl –

+0

這項工作:[play.golang.org/p/wl6PZorFGG](https://play.golang.org/p/wl6PZorFGG)。另外我改變了上面的例子 – tisov

+0

不,這不是預期的。當評論被引用時,我希望他們不會被刪除。引用的註釋是引用字符串的一部分。 –

3

我從來沒有讀過圍棋/寫任何東西,如此忍受我。幸運的是,我知道正則表達式。我對Go正則表達式進行了一些研究,似乎他們缺乏大多數現代功能(如引用)。

儘管如此,我開發了一個正則表達式,似乎是你在找什麼。我假設所有字符串都是單行。那就是:

reg := regexp.MustCompile(`(?m)^([^"\n]*)/\*([^*]+|(\*+[^/]))*\*+/`) 

txt := `random text 
     /* removable comment */ 
      "but /* never remove this */ one" 
     more random *text*` 

fmt.Println(reg.ReplaceAllString(txt, "${1}")) 

變化:版本以上將不會刪除引號之後發生的意見。這個版本會,但它可能需要運行多次。

reg := regexp.MustCompile(
    `(?m)^(([^"\n]*|("[^"\n]*"))*)/\*([^*]+|(\*+[^/]))*\*+/` 
) 
txt := ` 
    random text 
    what /* removable comment */ 
    hi "but /* never remove this */ one" then /*whats here*/ i don't know /*what*/ 
    more random *text* 
` 
newtxt := reg.ReplaceAllString(txt, "${1}") 
fmt.Println(newtxt) 
newtxt = reg.ReplaceAllString(newtxt, "${1}") 
fmt.Println(newtxt) 

說明
  • (?m)意味着多行模式。 Regex101給出了一個很好的解釋:

    ^和$錨現在分別匹配每行的開始/結尾,而不是整個字符串的開始/結束。

    需要將其固定到每行的開頭(使用^)以確保報價尚未開始。

  • 第一個正則表達式有:[^"\n]*。本質上,它匹配的不是"\n。我添加了括號,因爲這個東西不是評論,所以需要放回去。

  • 第二個正則表達式有:(([^"\n]*|("[^"\n]*"))*)。正則表達式,這條語句可以匹配[^"\n]*(就像第一個正則表達式那樣),或者(|)它可以匹配一對引號(以及它們之間的內容)與"[^"\n]*"。例如,它重複出現,以便在有多個報價對時有效。請注意,就像更簡單的正則表達式一樣,這個非註釋的東西正在被捕獲。

  • 這兩個正則表達式都使用這個:/\*([^*]+|(\*+[^/]))*\*+/。它匹配/*隨後任任何金額:

    • [^*]+*字符

    未其次/
    • \*+[^/]一個或多個*秒。
  • 然後它關閉*/

  • 在更換相匹配時,${1}是指被抓獲的非註釋的東西,所以他們重新插入到字符串。

+0

謝謝@Laurel。它似乎按預期工作。你能詳細解釋一下正則表達式嗎?讀它真的傷害了我的大腦。 –

+2

@ElgsQianChen我已經添加了一個解釋。還有更多的正則表達式! – Laurel

5

背景

做任務的正確方法是匹配和捕獲引號的字符串(牢記可能有內逃脫的實體),然後匹配多行註釋。

中的regex-CODE DEMO

下面是代碼來處理是:

package main 
import (
    "fmt" 
    "regexp" 
) 
func main() { 
    reg := regexp.MustCompile(`("[^"\\]*(?:\\.[^"\\]*)*")|/\*[^*]*\*+(?:[^/*][^*]*\*+)*/`) 
     txt := `random text 
      /* removable comment */ 
      "but /* never remove this */ one" 
      more random *text*` 
     fmt.Println(reg.ReplaceAllString(txt, "$1")) 
} 

Playground demo

說明

我建議正則表達式與Best Regex Trick Ever寫概念,幷包含2個備選方案:

  • ("[^"\\]*(?:\\.[^"\\]*)*") - 雙引號內的字符串字面正則表達式 - 第1組(參照與外一對未轉義括號的和經由replacement backreferences以後訪問形成capturing group)匹配,它可以包含轉義序列雙引號字符串文字。此部分相匹配:
    • " - 領先雙引號
    • [^"\\]* - 0+字符比"\其他(如[^...]構建體是negated character class匹配任何字符,但那些在其內部定義的)(該*是一個零個或多個出現匹配quantifier
    • (?:\\.[^"\\]*)*" - 0+序列(參見最後*non-capturing group僅用於子模式沒有逃脫序列的形成捕獲)(所述\\.相匹配的文字\接着與任何字符),然後以比其他" 0+ 字符和\
  • | - 或
  • /\*[^*]*\*+(?:[^/*][^*]*\*+)*/ - 多評論正則表達式部分匹配*沒有形成捕獲組(因此,通過反向引用從替換模式中不可用)和匹配
    • / - 所述/字面斜線
    • \* - 字面星號
    • [^*]* - 零個或多個字符比星號
    • \*+其他 - 1以上(+一次或多次出現匹配量詞 )星號
    • (?:[^/*][^*]*\*+)* - 除了/*(參見[^/*])以外的任何字符的0+序列(非捕獲,我們以後不使用它),fo使用0以外的星號(參見[^*]*),然後跟着1個星號(參見\*+)。
    • / - 文字(尾隨,關閉)斜線。

這多行註釋正則表達式是我測試過的最快的。與雙引號文字正則表達式一樣,"[^"\\]*(?:\\.[^"\\]*)*"unroll-the-loop technique一起寫入:沒有變化,只有字符類與*+量詞以特定的順序使用,以允許最快的匹配。

注意事項PATTERN增強

如果您計劃擴展到匹配的單引號的文字,沒有什麼更簡單,只需添加另一種選擇爲第一捕獲組通過重新使用雙引號的字符串字面正則表達式和更換雙引號單的:

reg := regexp.MustCompile(`("[^"\\]*(?:\\.[^"\\]*)*"|'[^'\\]*(?:\\.[^'\\]*)*')|/\*[^*]*\*+(?:[^/*][^*]*\*+)*/`) 
                ^-------------------------^ 

這裏是single- and double-quoted literal supporting regex demo removing the miltiline comments

添加單行註釋的支持是類似的:只需添加//[^\n\r]*替代端:

reg := regexp.MustCompile(`("[^"\\]*(?:\\.[^"\\]*)*"|'[^'\\]*(?:\\.[^'\\]*)*')|/\*[^*]*\*+(?:[^/*][^*]*\*+)*/|//.*[\r\n]*`) 
                               ^-----------^ 

這裏是single- and double-quoted literal supporting regex demo removing the miltiline and singleline comments

+0

我想我已經添加了所有我想要的。如果有什麼遺漏,請告訴我。 –

+0

感謝它的工作原理,甚至可以很好地處理多行註釋。 –

+1

感謝有史以來最好的正則表達式。太棒了! –

1

演示

Play golang demo

(在每個階段的運作是輸出和最終的結果可以通過向下滾動看到。)

方法

一些「招數s」是用來解決Golang的somewhat limitedregex syntax

  1. 具有獨特的性格更換開始報價和最終報價。至關重要的是,用於識別開始和結束引號的字符必須彼此不同,並且極不可能出現在正在處理的文本中。
  2. 替換所有評論起始者(/*)即而不是前面有一個未終止的開始引用,其中包含一個或多個字符的唯一序列。
  3. 同樣,用一個或多個字符的不同唯一序列替換所有不含的註釋(*/),而不是,後面跟着一個沒有開始引號的結束引號。
  4. 刪除所有剩餘的/*...*/評論序列。
  5. 通過顛倒上面第2步和第3步所做的替換,揭開先前「蒙面」的評論初學者/恩德斯。

限制

當前的演示並沒有解決註釋中出現的雙引號,例如可能性/* Not expected: " */注意:我的感覺是可以處理 - 只是還沒有付出努力 - 所以讓我知道如果你認爲這可能是一個問題,我會研究它。

1

只是爲了好玩另一種方法,作爲狀態機實現的最小詞法分析器,靈感來自Rob Pike talk的靈感和描述http://cuddle.googlecode.com/hg/talk/lex.html。代碼更冗長,但更易讀,易於理解,並且可以正確表達。它也可以和任何Reader和Writer一起工作,而不是隻使用字符串,所以不要消耗RAM,甚至更快。

type stateFn func(*lexer) stateFn 

func run(l *lexer) { 
    for state := lexText; state != nil; { 
     state = state(l) 
    } 
} 

type lexer struct { 
    io.RuneReader 
    io.Writer 
} 
func lexText(l *lexer) stateFn { 
    for r, _, err := l.ReadRune(); err != io.EOF; r, _, err = l.ReadRune() { 
     switch r { 
     case '"': 
      l.Write([]byte(string(r))) 
      return lexQuoted 
     case '/': 
      r, _, err = l.ReadRune() 
      if r == '*' { 
       return lexComment 
      } else { 
       l.Write([]byte("/")) 
       l.Write([]byte(string(r))) 
      } 
     default: 
      l.Write([]byte(string(r))) 
     } 
    } 
    return nil 
} 
func lexQuoted(l *lexer) stateFn { 
    for r, _, err := l.ReadRune(); err != io.EOF; r, _, err = l.ReadRune() { 
     if r == '"' { 
      l.Write([]byte(string(r))) 
      return lexText 
     } 
     l.Write([]byte(string(r))) 
    } 

    return nil 
} 

func lexComment(l *lexer) stateFn { 
    for r, _, err := l.ReadRune(); err != io.EOF; r, _, err = l.ReadRune() { 
     if r == '*' { 
      r, _, err = l.ReadRune() 
      if r == '/' { 
       return lexText 
      } 
     } 
    } 

    return nil 
} 

你可以看到它的工作原理http://play.golang.org/p/HyvEeANs1u