2010-08-12 11 views
6

Tengo una cadena que contiene un formulario Clojure válido. Quiero reemplazar una parte, al igual que con assoc-in, pero procesar toda la cadena como tokens.Reemplazo de subcadena sensible a la sintaxis

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

Quiero escribir string-assoc-in. Tenga en cuenta que su primer y último argumento son cadenas, y mantiene el salto de línea y las comas. ¿Es factible en Clojure? Lo más parecido que encontré es read que llama a clojure.lang.LispReader, pero no sé cómo funciona.

Quiero usarlo para leer un archivo fuente Clojure y mostrarlo con algunas modificaciones, manteniendo la estructura del archivo.

+2

No se me ocurre ninguna manera de hacer esto de manera confiable sin escribir tu propio lector. –

+0

Suena como un defmacro para * me * –

+0

@Paul Nathan: En realidad, una macro Lisp tiene las mismas operaciones de manipulación de cadenas a su disposición que una función normal. "Todo el lenguaje siempre está allí", como lo expresó Paul Graham. :-) –

Respuesta

2

creo que esto debería funcionar, ya sea en su totalidad en general y no requiere su propio lector/analizador:

(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])))))) 

Ejemplo:

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

Actualización: Ay, cogió un error:

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

Me encantaría que no importara, pero supongo que lo haré e intentar y hacer algo al respecto ... suspiro

+0

Me gusta este truco con la división en espacios en blanco, luego intercalando de nuevo. Me muestra una forma de hacerlo sin tener que escribir un lector. –

+0

No quería escribir un lector. Irónicamente, pensar en tu respuesta me ha llevado a escribir una. –

1

Supongo que no quiere leer realmente un formulario y evaluarlo? fnparse tiene un Clojure parser (escrito en Clojure usando fnparse). ¿Podrías usar eso para llevarte de una cuerda a otra, luego manipularlo y volver a ponerlo en una cuerda?

2

Usted puede hacer esto con una combinación de (cadena lectura) y un poco de manipulación de cadenas:

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

Tenga en cuenta que se requiere un marcador de posición de carácter reservado (en este caso, _), que usted no quiere en tus palabras clave El truco consiste en sacarlos del camino cuando el lector está haciendo un crujido en la cadena del vector y luego volverlos a poner.

Este ejemplo no aborda las nuevas líneas, pero creo que podría manejarlas de la misma manera.

+0

No sigo - '(let [s" [: a [: b ,,: c]] "] (string-assoc-in s [1 0]": new "))' ¿funciona bien? Sin embargo, estoy de acuerdo en que la macro es innecesaria y que una función funcionará igual de bien (la macro fue un artefacto de mi atormentar con las soluciones), así que editaré la respuesta para usar defn. – Greg

+1

@all: Greg está respondiendo a un comentario en el que afirmé erróneamente que lo anterior no funcionaría. Iba a reemplazarlo con una versión modificada, publicando un comentario un poco más largo y eliminando el original, pero, en un hermoso error, hice clic en eliminar * primero *. Lo siento, no es el camino a seguir después de que el comentario haya estado en marcha durante un par de minutos. * suspiro * @Greg: tienes razón, en cualquier caso, perdón por la confusión. –

+1

Volví a subir esta por darme la idea de mi solución, sin embargo, ahora veo que muestra el mismo error/muy similar que he descubierto en mi código (pruebe, por ejemplo, '(string-assoc-in" [: a [: b ,,: c ,,]] "[1 0]": nuevo ")' o '[: b ,,]' o '[: b ,,: c]' ...). No es necesario evitar un analizador sintáctico/de propósito especial para este, parece. –

4

O bien, otra opción sería utilizar ANTLR en el código parse the Clojure en un AST, luego transformar el AST y exportar de nuevo a una cadena.

+0

Ah, este podría ser el mejor enfoque ... La gramática de CCW es probable que sea exhaustiva y bien mantenida (y que se mantenga así a lo largo del tiempo). Sin embargo, mi ANTLR-fu todavía es demasiado débil para saber cómo extraer cosas que se han colocado en el "canal oculto". Pensé que el lexer lo veía, ¿pero el analizador no ...? –

+0

No sabía que hay un archivo de gramática Clojure para ANTLR, gracias por el puntero. Sin embargo, preferiría una solución Clojure pura. –

Cuestiones relacionadas