Regex.compile / 2的不区分大小写的选项

时间:2018-04-04 23:56:03

标签: regex elixir

我尝试使用Regex.compile/2构建无壳正则表达式二进制文件,但似乎无法找到有关如何设置该选项的示例。

Regex.compile("^(foo):?", :caseless)
** (FunctionClauseError) no function clause matching in Regex.compile/3

The following arguments were given to Regex.compile/3:

    # 1
    "^(foo):?"

    # 2
    :caseless

    # 3
    "8.41 2017-07-05"

(elixir) lib/regex.ex:140: Regex.compile/3

1 个答案:

答案 0 :(得分:5)

简而言之

根据您提供的链接,options需要配置为list,因为您可以提供多种选项。以下应该有效:

Regex.compile("^(foo):?", [:caseless])

更详细

类型规范如下:

compile(source, options \\ "") 
compile(binary(), binary() | [term()]) :: {:ok, t()} | {:error, any()}

第二行是dialyzer中的类型规范,基本上表明function compile接受两个参数:

  1. 第一个是二进制文件,对应于您的"^(foo):?"
  2. 第二个是二进制文件,或者是包含多个terms的列表。
  3. 如果成功,返回值将为{:ok, t()},如果出现错误,则返回t() is a %Regex{} struct{:error, any()}

    回到第二个参数的讨论,如果是列表,则需要利用上面提到的各种选项here

    对于binary,您可以将第二个参数作为单个字母缩写提供。因此,以下内容将失败:

    • Regex.compile("^(foo):?", "caseless")

    另一方面,以下成功:

    • Regex.compile("^(foo):?", "i")

    您可以从the table of the various module modifiers I linked to above获取映射。

    这些方法之间的主要区别在于,由Erlang驱动的Regex :re建立在PCRE标准之上。根据该标准,各种module modifiers由单个小写字母处理,例如iu等。因此,您可以将这两个选项与binary相结合如下:

    • Regex.compile("^(foo):?", "iu")

    从技术上讲,应该给你相当于:

    • Regex.compile("^(foo):?", [:caseless, :unicode])

    这样,您就可以通过语言规范或Regex规范在Erlang和Elixir中与PCRE进行沟通。

    非常高级的详细信息

    正如OP在评论中正确指出的那样,对于为什么Regex以两种不同的方式产生(例如通过options作为list vs,存在一些混淆options binary视为不同。

    要更详细地解释这种差异,请考虑以下情况:

    • r0 = Regex.compile!("(foo):?") ---> ~r/(foo):?/
    • r1 = Regex.compile!("(foo):?", "i") ---> ~r/(foo):?/i
    • ---> ~r /(foo):?/ # ?????? WHERE IS THE i` ?????

    遇到这种情况时,可能会产生Elixir Regex被打破的印象。 r0r2r1相同且不同。

    但是,功能明智,r2的行为与r1相似,而不像r0,请考虑以下示例,这些示例受到OP评论的无耻启发:

    • Regex.replace(r0, "Foo: bar", "") ---> "Foo: bar"
    • Regex.replace(r1, "Foo: bar", "") ---> " bar"
    • Regex.replace(r2, "Foo: bar", "") ---> " bar"

    那怎么可能呢?

    如果你从上面回忆起来,例如与t()类型的解释有关,Regex中的Elixir只不过是struct

    Regex可以通过以下方式精美呈现:~r/(foo):?/,但实际上它只不过是这样的: %Regex{ opts: opts, re_pattern: re_pattern, re_version: re_version, source: source }

    现在,在所有这些struct字段中,在一天结束时唯一重要的是:re_pattern。这将包含具有所有选项的完全编译的Regex。所以我们相应地找到了:

    • r1.re_pattern == r2.re_pattern
    • r0.re_pattern != r2.re_pattern

    opts字段而言,这是一个仅为binary格式的选项保留的容器。所以你会发现:   - r0.opts == r2.opts == "" 鉴于:   - r1.opts == "i"

    这些相同的opts字段用于在Regex结尾处精美地显示选项,因此您将看到:

    • ~r/(foo):?/同时为r0以及r2 但你会看到:
    • 两个~r/(foo):?/i
    • r1 由于opts字段彼此不同。 出于这个原因,您可以手动更新Regex,如果您希望它看起来更加一致,例如:
    • %{r2 | opts: "i"} ---> ~r/(foo):?/i

    除了字段re_pattern之外,其他任何字段都不会对实际Regex产生任何影响。那些其他领域仅用于文档目的。

    接下来,根据source code,您可以看到binary个选项已转换为选项的list版本because that is how Erlang regex engine, :re expects them to be.

    尽管Elixir核心团队本身并不困难,但他们选择不提供相反的翻译,例如从实际的module modifier原子列表到等效的PCRE binary选项,最终导致opts字段保持为空并且丧失了{{1}中相应的选项}} PCRE格式因此,您最终会得到binary的有缺陷的渲染,如上面的差异所示。

    上面我只研究了解释这种差异的机制,然而,这种差异是否合理本身就是另一个问题。如果有一个比我更有洞察力的人能够澄清是否有任何方法来捍卫这种差异,我将非常感激。

    结论

    • Regex ---> r0 = Regex.compile!("(foo):?")
    • ~r/(foo):?/ ---> r1 = Regex.compile!("(foo):?", "i")
    • ~r/(foo):?/i ---> r2 = Regex.compile!("(foo):?", [:caseless])

    ~r/(foo):?/r1可能看起来不一样,但它们的行为完全相同。