【问题标题】:Syntax-aware substring replacement语法感知的子字符串替换
【发布时间】:2010-08-12 21:52:28
【问题描述】:

我有一个包含有效 Clojure 表单的字符串。我想替换它的一部分,就像 assoc-in 一样,但将整个字符串处理为标记。

=> (assoc-in [:a [:b :c]] [1 0] :new)
[:a [:new :c]]
=> (assoc-in [:a 
                [:b,, :c]] [1 0] :new)
[:a [:new :c]]
=> (string-assoc-in "[:a 
                       [:b,, :c]]" [1 0] ":new")
"[:a 
   [:new,, :c]]"

我想写string-assoc-in。请注意,它的第一个和最后一个参数是字符串,并且保留了换行符和逗号。它在 Clojure 中可行吗?我找到的最接近的是read,它调用clojure.lang.LispReader,但我不知道它是如何工作的。

我想用它来读取一个 Clojure 源文件并显示它并进行一些修改,同时保持文件的结构。

【问题讨论】:

  • 如果不编写您自己的阅读器,我想不出任何可靠的方法。
  • 中听起来像是一个defmacro
  • @Paul Nathan:实际上,Lisp 宏具有与常规函数相同的字符串操作操作。正如保罗格雷厄姆所说,“整个语言始终存在”。 :-)
  • @Michal:是的,我知道。这个问题似乎有“lisp宏”的自然答案。但我对特定于 clojure 的 Lisp 了解不多,无法正确回答。
  • @Paul Nathan:嗯,Lisp 宏无法帮助您处理空格(在 Clojure 中,这包括逗号)。

标签: string syntax clojure replace tokenize


【解决方案1】:

或者另一种选择是将ANTLRparse the Clojure 代码转换为 AST,然后转换 AST,然后导出回字符串。

【讨论】:

  • 啊,这可能是最好的方法...... CCW 的语法可能是全面的并且维护良好(并且随着时间的推移保持这种状态!)。但是,我的 ANTLR-fu 仍然太弱,以至于我不知道如何提取放置在“隐藏通道”上的内容。我以为词法分析器看到了,但解析器没有……?
  • 我不知道 ANTLR 有 Clojure 语法文件,谢谢指点。不过,我更喜欢纯 Clojure 解决方案。
【解决方案2】:

我认为这应该可行,完全通用,不需要自己的阅读器/解析器:

(defn is-clojure-whitespace? [c]
  (or (Character/isSpace c)
      (= \, c)))

(defn whitespace-split
  "Returns a map of true -> (maximal contiguous substrings of s
  consisting of Clojure whitespace), false -> (as above, non-whitespace),
  :starts-on-whitespace? -> (whether s starts on whitespace)."
  [s]
  (if (empty? s)
    {}
    (assoc (group-by (comp is-clojure-whitespace? first)
                     (map (partial apply str)
                          (partition-by is-clojure-whitespace? s)))
      :starts-on-whitespace?
      (if (is-clojure-whitespace? (first s)) true false))))

(defn string-assoc-in [s coords subst]
  (let [{space-blocks true
         starts-on-whitespace? :starts-on-whitespace?}
        (whitespace-split s)
        s-obj (assoc-in (binding [*read-eval* false] (read-string s))
                        coords
                        (binding [*read-eval* false] (read-string subst)))
        {non-space-blocks false}
        (whitespace-split (pr-str s-obj))]
    (apply str
           (if starts-on-whitespace?
             (interleave space-blocks (concat non-space-blocks [nil]))
             (interleave non-space-blocks (concat space-blocks [nil]))))))

例子:

user> (string-assoc-in "[:a [:b,, :c]]" [1 0] ":new")
"[:a [:new,, :c]]"

更新:哎呀,发现了一个错误:

user> (string-assoc-in "[:a [:b,, :c\n]]" [1 0] ":new")
"[:a [:new,, :c]]\n"

如果没关系我会喜欢的,但我想我必须尝试做点什么...... 叹息

【讨论】:

  • 我喜欢这个技巧,在空白处分割,然后再次交错。它向我展示了一种无需编写阅读器即可做到这一点的方法。
  • 我不想写阅读器。具有讽刺意味的是,考虑到你的答案让我写了一个。
【解决方案3】:

您可以结合使用 (read-string) 和一些字符串操作:

(defn string-assoc-in
  [a b c]
  (.replaceAll
    (str
     (assoc-in (read-string (.replaceAll a ",," ",_,")) b (read-string c)))
    " _ " ",, "))

user> (string-assoc-in "[:a [:b,, :c]]" [1 0] ":new")
"[:a [:new,, :c]]"

请注意,我们需要一个保留的占位符(在本例中为 _),您不希望在关键字中使用它。诀窍是在读者阅读矢量字符串时将它们移开,然后将它们放回原处。

此示例没有处理换行符,但我认为您可以用相同的方式处理这些问题。

【讨论】:

  • 我不关注 - (let [s "[:a [:b,, :c]]"] (string-assoc-in s [1 0] ":new")) 工作正常吗?但是,我确实同意宏是不必要的,并且函数也可以正常工作(宏是我搞砸解决方案的产物),所以我将编辑答案以使用 defn。
  • @all:Greg 正在回复我错误地声称上述方法不起作用的评论。我打算用修改后的版本替换它——通过发布稍长的评论并删除原来的评论——但是,我犯了一个漂亮的错误,我点击了删除first。抱歉,评论发布了几分钟后,这不是要走的路。 叹息 @Greg:你是对的,无论如何,很抱歉造成混乱。
  • 赞成这个,因为它给了我解决方案的想法,但是现在我看到它表现出与我在代码中发现的相同/非常相似的错误(尝试例如 (string-assoc-in "[:a [:b,, :c,,]]" [1 0] ":new") 或 @987654324 @ 或 [:b,,:c]...)。似乎没有避免为此使用解析器/专用阅读器。
  • 我喜欢你的简单技巧,它给了我一个想法。但是,在某些情况下,实际实现无法正常工作,请参阅 Michal 的反例。
【解决方案4】:

我假设您不想实际阅读表格并对其进行评估? fnparse 有一个Clojure parser(使用 fnparse 用 Clojure 编写)。您也许可以使用它来将您从字符串转换为形式,然后进行操作,然后将其放回字符串?

【讨论】:

    猜你喜欢
    • 2021-10-29
    • 2013-10-31
    • 2011-10-14
    • 1970-01-01
    • 2019-04-15
    • 1970-01-01
    • 2012-04-03
    • 2013-07-23
    相关资源
    最近更新 更多