【问题标题】:A regex for Go, that matches content including balanced parenthesesGo 的正则表达式,匹配包括平衡括号在内的内容
【发布时间】:2022-10-25 19:45:51
【问题描述】:

所以我的用例如下:我正在解析一个 SQL 查询,试图获取一个函数名和发送给这个函数的各个参数。这要求我的正则表达式能够找到名称、左括号、内容和右括号。不幸的是,在测试它时发现它有时过于贪婪,抓住额外的括号,而其他时候它错过了最后一个。

这是我在操场上的测试代码:

func getRegex(name string) string {
    return fmt.Sprintf("\\$__%s\\b(?:\\((.*?\\)?)\\))?", name)
}

func main() {
    var rawSQL = "(select min(time) from table where $__timeFilter(time))"
    rgx, err := regexp.Compile(getRegex("timeFilter"))
    if err != nil {
        fmt.Println(err)
    }
    var match = rgx.FindAllStringSubmatch(rawSQL, -1)

    fmt.Println(match)
}

有一个活生生的例子https://go.dev/play/p/4FpZblia7Ks

我正在测试的4个案例如下:

(select min(time) from table where $__timeFilter(time) ) OK
(select min(time) from table where $__timeFilter(time)) NOK
select * from foo where $__timeFilter(cast(sth as timestamp)) OK
select * from foo where $__timeFilter(cast(sth as timestamp) ) NOK

这是一个实时正则表达式版本https://regexr.com/700oh

我来自 javascript 世界,所以从未使用过递归正则表达式,看起来可能是这种情况?

【问题讨论】:

  • 如果您的语言不是正则,则正则表达式可能是错误的工具。

标签: regex go


【解决方案1】:

您的正则表达式似乎有两个主要问题,其中一个比另一个更容易处理:

  1. 正则表达式天生不擅长处理递归匹配,例如分组左括号和右括号,因为它们没有记忆。就您而言,我认为您已尝试通过将自己限制在一些特定情况下来解决此问题,但是正则表达式的贪婪性质在这里对您不利。
  2. 您不匹配右括号前可能有空格的情况。

    这两个问题共同导致您的正则表达式在这两种情况下失败,但也导致您的第一个情况匹配。

    要解决此问题,您必须在将字符串发送到正则表达式之前对字符串进行一些预处理:

    if strings.HasPrefix(rawSql, "(") {
        rawSql = rawSql[1:len(rawSql) - 1]
    }
    

    这将去掉任何外括号,如果没有内存或额外的子句,正则表达式将无法忽略这些括号。

    接下来,您需要修改正则表达式以处理内部函数调用和$__timeFilter 调用之间可能存在空格的情况:

    func getRegex(name string) string {
        return fmt.Sprintf("\$__%s\b(\((.*?\)?)\s*\))?", name)
    }
    

    完成此操作后,您的正则表达式应该可以工作。您可以在 this playground link 上找到完整示例。

【讨论】:

    【解决方案2】:

    尽管我最终不得不走另一条路,但我还是选择了伍迪的答案作为正确的答案。附加的测试用例不包括某些场景,事实证明我还必须能够提取括号内的参数。所以这是我的最终解决方案,我手动解析文本,找到边界括号并提取它们之间的任何内容:

    // getMacroMatches extracts macro strings with their respective arguments from the sql input given
    // It manually parses the string to find the closing parenthesis of the macro (because regex has no memory)
    func getMacroMatches(input string, name string) ([][]string, error) {
        macroName := fmt.Sprintf("\$__%s\b", name)
        matchedMacros := [][]string{}
        rgx, err := regexp.Compile(macroName)
    
        if err != nil {
            return nil, err
        }
    
        // get all matching macro instances
        matched := rgx.FindAllStringIndex(input, -1)
    
        if matched == nil {
            return nil, nil
        }
    
        for matchedIndex := 0; matchedIndex < len(matched); matchedIndex++ {
            var macroEnd = 0
            var argStart = 0
            macroStart := matched[matchedIndex][0]
            inputCopy := input[macroStart:]
            cache := make([]rune, 0)
    
            // find the opening and closing arguments brackets
            for idx, r := range inputCopy {
                if len(cache) == 0 && macroEnd > 0 {
                    break
                }
                switch r {
                case '(':
                    cache = append(cache, r)
                    if argStart == 0 {
                        argStart = idx + 1
                    }
                case ')':
                    l := len(cache)
                    if l == 0 {
                        break
                    }
                    cache = cache[:l-1]
                    macroEnd = idx + 1
                default:
                    continue
                }
            }
    
            // macroEnd equals to 0 means there are no parentheses, so just set it
            // to the end of the regex match
            if macroEnd == 0 {
                macroEnd = matched[matchedIndex][1] - macroStart
            }
            macroString := inputCopy[0:macroEnd]
            macroMatch := []string{macroString}
    
            args := ""
            // if opening parenthesis was found, extract contents as arguments
            if argStart > 0 {
                args = inputCopy[argStart : macroEnd-1]
            }
            macroMatch = append(macroMatch, args)
            matchedMacros = append(matchedMacros, macroMatch)
        }
        return matchedMacros, nil
    }
    

    去游乐场链接:https://go.dev/play/p/-odWKMBLCBv

    【讨论】:

      猜你喜欢
      • 2010-10-07
      • 1970-01-01
      • 2011-07-18
      • 1970-01-01
      • 2016-02-13
      相关资源
      最近更新 更多