String 语法感知子字符串替换

String 语法感知子字符串替换,string,syntax,clojure,replace,tokenize,String,Syntax,Clojure,Replace,Tokenize,我有一个包含有效Clojure表单的字符串。我想替换它的一部分,就像在中使用assoc一样,但将整个字符串作为令牌处理 => (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,, :

我有一个包含有效Clojure表单的字符串。我想替换它的一部分,就像在中使用
assoc一样,但将整个字符串作为令牌处理

=> (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]]"
我想在
中写入
字符串关联。注意,它的第一个和最后一个参数是字符串,它保留了换行符和逗号。这在Clojure可行吗?我找到的最接近的东西是
read
,它调用
clojure.lang.LispReader
,但我不知道如何工作


我想用它来读取Clojure源文件,并对其进行一些修改,以保持文件的结构。

我假设您不想实际读取表单并对其进行评估?fnparse有一个(使用fnparse在Clojure中编写)。您可能可以使用它从一个字符串转换到另一个表单,然后进行操作,然后将其放回字符串?

您可以通过(读取字符串)和一些字符串操作的组合来完成此操作:

(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]]"
请注意,我们需要一个保留的占位符字符(在本例中为x),这是您不希望在关键字中使用的。诀窍是,当读者在处理向量字符串时,将这些,,移开,然后将它们放回原处


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

或者另一种方法是使用代码将其转换为AST,然后将AST转换并导出回字符串。

我认为这应该可以工作,完全是通用的,不需要自己的读取器/解析器:

(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"

如果不要紧的话,我会喜欢的,但我想我必须试着做点什么。。。唉

我想不出任何可靠的方法来做到这一点,除非你自己写读者。听起来像是一个很好的例子me@PaulNathan:实际上Lisp宏与常规函数具有相同的字符串操作。正如保罗·格雷厄姆(Paul Graham)所说,“整个语言总是在那里。”@迈克尔:是的,我知道。这个问题似乎有一个“lisp宏”的自然答案。但是我对clojure特有的Lisp不太了解,无法正确回答。@Paul Nathan:好吧,Lisp宏不能帮助你处理空白(在clojure中,这包括逗号)。我不懂-
(让[s][:a[:b,:c]“](s[10]:new中的字符串assoc))
可以吗?不过,我确实同意宏是不必要的,函数也可以正常工作(宏是我在解决方案中胡乱操作的产物),因此我将编辑答案以使用defn。@all:Greg正在回复一条评论,我在评论中错误地声称上述方法不起作用。我本来打算用一个修改过的版本来替换它——发布一条稍长的评论并删除原始内容——但是,在一个漂亮的错误中,我首先点击了删除。抱歉,在评论发表了几分钟后,这不是一个好办法。叹气@Greg:无论如何,你是对的,很抱歉给我带来了困惑。我对这一条投了赞成票,因为它给了我解决方案的想法,但是现在我看到它展示了我在代码中发现的相同/非常相似的错误(例如尝试
(字符串assoc in“[:a[:b,:c,,]]”[10]:new”)
[:b,,]
[:b,,:c]
. 看来,这本书不能避免使用解析器/专用阅读器。我喜欢你的技巧的简单性,这给了我一个想法。但是,在某些情况下,实际实现无法工作,请参见Michal的反例。啊,这可能是最好的方法。。。《特定常规武器公约》的语法很可能是全面的,并得到很好的维护(并且随着时间的推移会保持这种状态!)。然而,我的ANTLR fu仍然太弱,我不知道如何提取放置在“隐藏通道”上的内容。我以为lexer看到了,但解析器没有…?我不知道ANTLR有Clojure语法文件,谢谢你的指针。不过,我更喜欢纯Clojure解决方案。我喜欢这种在空格上拆分,然后再次交错的技巧。它向我展示了一种不用写读者就能做到这一点的方法。我不想写读者。讽刺的是,想到你的答案,我就写了一封信。