2010-08-12 15 views
6

J'ai une chaîne contenant un formulaire Clojure valide. Je veux en remplacer une partie, comme avec assoc-in, mais en traitant toute la chaîne comme des jetons.Remplacement de sous-chaîne compatible avec la syntaxe

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

Je souhaite écrire . Notez que ses premier et dernier arguments sont des chaînes, et qu'il garde le saut de ligne et les virgules. Est-ce faisable à Clojure? La chose la plus proche que j'ai trouvé est read qui appelle clojure.lang.LispReader, mais je ne sais pas comment ça marche.

Je souhaite l'utiliser pour lire un fichier source Clojure et l'afficher avec quelques modifications, en conservant la structure du fichier.

+2

Je ne peux pas penser à un moyen de le faire de manière fiable sans écrire votre propre lecteur. –

+0

Semble comme un defmacro à * moi * –

+0

@ Paul Nathan: En fait, une macro Lisp a les mêmes opérations de manipulation de chaînes à sa disposition comme le fait une fonction régulière. "La langue entière toujours là", comme Paul Graham l'a dit. :-) –

Répondre

2

Je pense que cela devrait fonctionner, être tout à fait générale et ne pas exiger son propre lecteur/analyseur:

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

Exemple:

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

Mise à jour: Aïe, a attrapé un bug:

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

Je l'adorerais si cela n'avait pas d'importance, mais je suppose que je vais avoir e pour essayer de faire quelque chose à ce sujet ... sigh

+0

J'aime cette astuce avec la division sur les espaces, puis l'entrelacement à nouveau. Il me montre un moyen de le faire sans avoir à écrire un lecteur. –

+0

Je ne voulais pas écrire un lecteur. Ironiquement, penser à votre réponse m'a conduit à en écrire un. –

1

Je suppose que vous ne voulez pas lire dans un formulaire et l'évaluer? fnparse a un Clojure parser (écrit dans Clojure en utilisant fnparse). Vous pourriez être en mesure d'utiliser cela pour obtenir de la chaîne pour former, puis manipuler, puis le remettre à une chaîne?

2

Vous pouvez le faire avec une combinaison de (lecture string) et une manipulation de chaînes:

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

Notez que nous avons besoin d'un caractère d'espace réservé réservé (dans ce cas, _) que vous ne voudriez pas dans vos mots-clés. L'astuce consiste à les mettre à l'écart lorsque le lecteur crie sur la chaîne de caractères, puis à les remettre.

Cet exemple n'aborde pas les retours à la ligne, mais je pense que vous pouvez les gérer de la même manière.

+0

Je ne suis pas - '(let [s" [: un [: b ,,: c]] "] (string-assoc-in s [1 0]": new "))' fonctionne bien? Cependant, je suis d'accord sur le fait que la macro n'est pas nécessaire et qu'une fonction fonctionnera aussi bien (la macro était un artefact de mes fouillis avec des solutions), donc je vais éditer la réponse pour utiliser defn. – Greg

+1

@all: Greg répond à un commentaire dans lequel j'ai prétendu par erreur que ce qui précède ne fonctionnerait pas. J'allais le remplacer par une version modifiée - en publiant un commentaire un peu plus long et en supprimant l'original - mais, dans une belle erreur, j'ai cliqué sur delete * first *. Désolé, pas le chemin à suivre après que le commentaire a été levé pendant quelques minutes. * soupir * @Greg: Vous avez raison, en tout cas, désolé pour la confusion. –

+1

Upvoted celui-ci pour me donner l'idée de ma solution, mais maintenant je vois qu'il présente le même/très similaire bug que j'ai depuis découvert dans mon code (essayez par exemple '(string-assoc-in" [: a [: b ,,: c ,,]] "[1 0]": nouveau ")' ou '[: b ,,]' ou '[: b ,,: c]' ...). Pas d'évitement d'un lecteur parser/special-purpose pour celui-ci, semble-t-il. –

4

Ou une autre option consisterait à utiliser le code ANTLR à parse the Clojure dans un AST, puis de transformer l'AST et de réexporter vers une chaîne.

+0

Ah, c'est peut-être la meilleure approche ... La grammaire de CCW est susceptible d'être complète et bien entretenue (et de rester dans le temps!). Cependant, mon ANTLR-fu est encore trop faible pour que je sache comment extraire des choses qui ont été placées sur le "canal caché". Je pensais que le lexer voit ça, mais l'analyseur ne le fait pas ...? –

+0

Je ne savais pas qu'il existe un fichier de grammaire Clojure pour ANTLR, merci pour le pointeur. Je préférerais une solution pure de Clojure, cependant. –