2024 年 Clojure 状态调查中分享您的想法!

欢迎!请参阅关于页面以获取关于如何使用此功能的一些更多信息。

0 投票
ClojureScript

re-matches 函数没有正确的语义:它对字符串进行搜索(而不是匹配),如果字符串和匹配的字符串不相等,则返回 nil。这不同于真正匹配,这就像在模式开头和结尾插入"^"和"$"。

Clojure 中的示例

user=> (re-find #"0|[1-9][0-9]+|0[xX][0-9a-zA-Z]+" "0x1") "0" user=> (re-matches #"0|[1-9][0-9]+|0[xX][0-9a-zA-Z]+" "0x1") "0x1"

比较 ClojureScript

ClojureScript:cljs.user> (re-find #"0|[1-9][0-9]+|0[xX][0-9a-zA-Z]+" "0x1") "0" ClojureScript:cljs.user> (re-matches #"0|[1-9][0-9]+|0[xX][0-9a-zA-Z]+" "0x1") nil

这是 CLJS-775(之一)的原因。

我不太确定这里可以做什么。我的第一个想法是让 re-matches 检查其正则输入的 -source 属性,将字符串用"^$"包裹起来,然后小心翼翼地将所有标志复制到一个新的正则表达式。

问题
1. 有任何有效的模式在这个操作中不安全吗?例如,我们是否不能把"^"放在第一位?"^^abc$$"好吗?
1. 如果"^"和"$"已经是模式的第一和最后一个字符,我们能否避免复制?
1. 多行模式对此有何影响?如果有,如何影响?
1. regexinstance.lastIndex 是正则实例(或旧浏览器上的 RegExp 全局标志)的可变性的一部分,用于对相同字符串调用 exec() 的多个调用中的字符串偏移量。我不知道如果 re-* 在全局标志设置为正则表达式的情况下接收到正则表达式该怎么做。(顺便说一下,这是拒绝 CLJS-150 的一个非常好的理由:允许 clojure 接受全局标志会使正则表达式对象有状态,并完全搞砸 re-seq,例如。)

4 答案

0 投票

评论由:favila 发布

我想提出一个稍微激进的建议,这个建议将:修复此问题以及 CLJS-810,使我们能够在解决 CLJS-485 CLJS-746 CLJS-794(clojure.string/replace 痛点)时处于更好的位置,允许我们在 js(CLJS-67 和 CLJS-68)模式中添加一些正则表达式作为值的便利之处,并通过实施更多 re-* 函数使 ClojureScript 的正则表达式处理更接近 Clojure。

在此 cljsfiddle 中的示例实现(不是补丁):http://cljsfiddle.net/fiddle/favila.regexp

关键要点

  1. 通过 re-pattern 创建一个 Pattern 对象,该对象提供用于搜索(re-find)或精确匹配(re-matches)或重复搜索(re-seq、re-matcher + re-find)的方法。在 JavaScript 中,尽管这些正则表达式字符串相似,但每个都必须是不同的 RegExp 对象。re-find 和 re-matches 模式可以被缓存。所有这些都可以懒加载生成 RegExps。
  2. 正则表达式文字会发出这些 Pattern 对象而不是 RegExp 对象。
  3. 创建一个 Matcher 对象来对应尚未实现的 re-matcher。它包含一个全局标记的 RegExp 对象、一个搜索字符串和一个完成标记。如果它保留最后一个匹配项(类似于 Java),cljs 也可以实现 re-groups。
  4. 让 re-seq 使用 Matcher 对象,并因此使用原生 RegExp 提供的全局匹配的 .lastIndex。(其实现不再需要在每次匹配后进行字符串切片。)
  5. 如果 re-find 给出了一个原生 RegExp 对象而不是模式,则它将直接使用它。这符合当前的行为。
  6. 如果 re-matches 给出了一个原生 RegExp 对象,并且它不适用于精确匹配,则从输入 RegExp 中克隆一个新的 RegExp,并在前面和后面加上 ^ 和 $,并添加全局标志。(这种技术在 clojure.string/replace 中使用,但使用不正确。)

有何看法?

0 投票
by

评论者:dnolen

这听起来很有趣,但我对互操作性故事有所顾虑。我认为人们会期待函数能够接受常规 RegExp 以及 Pattern。您在这里没有提到这个问题吗?

0 投票
by

评论由:favila 发布

如果我的某些想法比较模糊,那是因为我已经有一段时间没有考虑这个问题了。

首先要注意,有一类 RegExp 实际上是“纯净的”:如果它们进行全文匹配(例如以 ^ 开始并以 $ 结束)并且全局标志设置为 false,则它们的 lastIndex 总是看起来是 0。

互操作性可能性

  • 可以从一个创建另一个,因此强制转换总是选项之一。例如,re-pattern 可以接受一个 RegExp,以及一些其他(cljs 特定)可以将 Pattern 或 Matcher 强制转换为 RegExp 的函数。(或者也许 re-matcher 可以返回一个与 RegExp 兼容的对象——见下文。)
  • 传递给 cljs 的 RegExp:"纯净" 的正则表达式可以直接使用,否则我们创建一个 Pattern 和/或 Matcher。(我不记得具体细节,但 fiddle 应该涵盖了这些问题。)
  • 用作 RegExp 的 Pattern:Patterns 可以公开 RegExp 实例的所有属性。如果是纯净模式,则可以实现 .test 和 .exec。.lastIndex 总是 0。不清楚如何处理不纯净模式:抛出异常、仍然表现为纯净,还是返回一个新的对象?
  • Matcher 作为 RegExp 使用:Matcher 可以精确复制 RegExp 实例,甚至可以使用相同的原型。将其用作 RegExp 会改变对象的状态并干扰其内部状态,但只要它始终一致地用作 RegExp 或始终一致地用作 Matcher,这就无关紧要。注意事项
    **** Matcher 在 Java 中持有匹配的字符串。JavaScript 信任你始终提供相同的字符串(例如,在 while 循环中)。
    • Java 的 Matcher 持有最后一次匹配(由正则分组使用)。JavaScript 的 RegExp 不绑定。
      当 lastIndex 达到源字符串的末尾时,JavaScript 的 RegExp 会自动重置。Java 的 Matcher 不会。
      由于这三个额外的状态,Matcher 必须是一个包装器,而不是一个普通的 RegExp。
      re-matcher 的返回值仅由 re-find 和 re-groups 的 1 个参数版本消耗。
      re-seq 可以在内部使用 matcher,但由于它是私有的,因此不必这样做。
    • Matcher 应该实现其他Java方法吗?
  • 传递给 String.prototype.match、.replace、.search 和 .split 的模式:我还没有考虑过这个问题。考虑事项
    **** 问题代码是任何使用通过模式文字或 re-pattern 创建的对象,并将其用作这些 String 方法的参数的 cljs 代码。如果它们使用 clojure.string 方法,则会正常工作。
    • 此类代码在 Java Clojure 中也不可能:只有 (.split s "pattern-str") 在 java/clj 和 js/cljs 中相同,并且它将在两个平台上继续工作(无标志)。我们可能只需要让人们修复这样的代码,因为它是平台特定的,但我需要看看它有多普遍。
      这类代码的修复方法是:
      使用我们将提供的 pattern→regexp 强制转换函数。
      直接使用 js/RegExp 构造那些正则表达式。
      使用 clojure.string 函数而不是 String 方法。这也具有在 clj 和 cljs 之间可移植的优点。
    • 我们可能会修补 RegExp 构造函数或操作 String 原型链以自动执行 pattern→regexp 强制转换,但这是一个暴力解决方案。

如果我在 Clojure (java) 代码中错了,但在极端情况下使用 Pattern 和 Match 方法或直接与 String 方法一起使用是非常罕见的。大部分情况下,它们被视为用于 re-* 和 clojure.string/** 的不透明对象。在 cljs 中以相同方式编写的代码将不会受到影响,并且我们可以将任何其他使用声明为平台特定的,并要求显式创建 RegExp(并且不必担心 Matcher 或 Pattern 是否表现得像 RegExp)。如果没有太多使用 RegExp.prototype.test、.exec 和 String.prototype.match、.replace、.search 和 .split,这是我首选的互操作方法。

0 投票
通过
参考:https://clojure.atlassian.net/browse/CLJS-776(由 favila 报告)
...