我正在尝试从this Javascript implementation在Golang中实现removeComments
函数。我希望删除文中的任何评论。例如:
/* this is comments, and should be removed */
However, "/* this is quoted, so it should not be removed*/"
在Javascript实现中,引用匹配不会在组中捕获,因此我可以轻松地将其过滤掉。然而,在Golang中,似乎不容易判断匹配的部分是否在一个组中被捕获。那么如何在Golang中实现与Javascript版本相同的removeComments
逻辑呢?
答案 0 :(得分:3)
执行任务的正确方法是匹配和捕获引用的字符串(记住可以在内部转义实体),然后匹配多行注释。
以下是处理该问题的代码:
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"))
}
我建议的正则表达式是用Best Regex Trick Ever概念编写的,包含两个选项:
("[^"\\]*(?:\\.[^"\\]*)*")
- 双引号字符串文字正则表达式 - 第1组(请参阅由外部非转义括号组成的capturing group,稍后可通过replacement backreferences访问)匹配可以包含转义序列的双引号字符串文字。这部分匹配:
"
- 领先的双引号[^"\\]*
- 除"
和\
之外的0 +个字符(因为[^...]
构造是negated character class,它匹配任何字符,但在其中定义的字符除外) (*
是零个或多个匹配quantifier的匹配项(?:\\.[^"\\]*)*"
- 0+序列(请参阅最后*
和non-capturing group仅用于组子模式而不形成捕获)转义序列( \\.
匹配文字\
后跟任何字符),后跟0+
"
和\
|
- 或/\*[^*]*\*+(?:[^/*][^*]*\*+)*/
- 多行注释正则表达式部分匹配*而不形成捕获组(因此,通过反向引用从替换模式中无法使用)和匹配
/
- /
文字斜杠\*
- 文字星号[^*]*
- 除星号外的零个或多个字符\*+
- 1个或更多(+
是一个或多个匹配量词的出现次数)asterisks (?:[^/*][^*]*\*+)*
- 除了/
或*
(参见[^/*]
)之外的任何字符的0+序列(非捕获,我们以后不再使用),除了星号外有0+个字符(请参阅[^*]*
),然后是1 +星号(参见\*+
)。/
- 一个字面(尾随,结束)斜杠。 注意 :此多行注释正则表达式是我测试过的最快的。同样适用于双引号文字正则表达式{{1在编写时考虑到unroll-the-loop technique:没有替换,只有具有"[^"\\]*(?:\\.[^"\\]*)*"
和*
量词的字符类按特定顺序使用才能实现最快的匹配。
如果您计划扩展到匹配单引号文字,那么没有什么比这更容易了,只需通过重复使用双引号字符串文字正则表达式并将双引号替换为单引号,将另一个备选项添加到第一个捕获组:
+
以下是single- and double-quoted literal supporting regex demo removing the miltiline comments
添加单行注释支持类似:只需添加reg := regexp.MustCompile(`("[^"\\]*(?:\\.[^"\\]*)*"|'[^'\\]*(?:\\.[^'\\]*)*')|/\*[^*]*\*+(?:[^/*][^*]*\*+)*/`)
^-------------------------^
替代结尾:
//[^\n\r]*
答案 1 :(得分:2)
我从未在Go中读过/写过任何内容,所以请耐心等待。幸运的是,我知道正则表达式。我对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给出了一个很好的解释:
^和$ anchors现在分别在每行的开头/结尾匹配,而不是整个字符串的开头/结尾。
需要将其锚定到每一行的开头(使用^
)以确保报价尚未开始。
第一个正则表达式有:[^"\n]*
。从本质上讲,它匹配的所有内容都不是"
或\n
。我添加了括号,因为这些东西不是评论,所以需要放回。
第二个正则表达式有:(([^"\n]*|("[^"\n]*"))*)
。具有此语句的正则表达式可以匹配[^"\n]*
(就像第一个正则表达式那样),或者|
)它可以匹配一对引号(以及它们之间的内容)与"[^"\n]*"
。它是重复的,所以当有多个报价对时,它可以正常工作。请注意,与更简单的正则表达式一样,正在捕获此非注释内容。
两个正则表达式都使用此:/\*([^*]+|(\*+[^/]))*\*+/
。它匹配/*
后跟任意数量的任何一个:
[^*]+
非*
字符或
\*+[^/]
一个或多个*
未被/
跟踪。然后它与结束*/
在替换过程中,${1}
会引用已捕获的非评论内容,因此会将其重新插入字符串中。
答案 2 :(得分:1)
(输出每个阶段的工作,通过向下滚动可以看到最终结果。)
一些"技巧"习惯于使用Golang的somewhat limited regex syntax:
/*
),其前面是未终止的起始引号,其中包含一个或多个字符的唯一序列。*/
)替换为之前没有开头引号的结束引号,并使用一个或多个字符的不同唯一序列/*...*/
个评论序列。目前的演示并未解决评论中出现双引号的可能性,例如: /* Not expected: " */
。 注意:我的感觉是这可以处理 - 只是还没有付出努力 - 所以,如果你认为这可能是一个问题,请告诉我,我会调查它。
答案 3 :(得分:1)
只是为了另一种方法,将最小的词法分析器作为状态机实现,受到Rob Pike talk http://cuddle.googlecode.com/hg/talk/lex.html的启发和描述。代码更冗长,但更具可读性,可理解性和可破解性,然后是regexp。它也可以与任何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
}
答案 4 :(得分: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)
答案 5 :(得分:0)
试试这个例子..