2010-07-14 21 views
6

Estoy en una situación en la que necesito analizar argumentos de una cadena de la misma manera que se analizarían si se proporcionan en la línea de comandos a una aplicación Java/Clojure .Analizando argumentos de línea de comando desde STRING en Clojure

Por ejemplo, necesito convertir "foo \"bar baz\" 'fooy barish' foo" en ("foo" "bar baz" "fooy barish" "foo").

Tengo curiosidad por saber si hay una forma de usar el analizador que Java o Clojure usa para hacer esto. No me opongo a utilizar una expresión regular, pero me encantan las expresiones regulares, y fracasaría si intentara escribir una para esto.

¿Alguna idea?

+0

Creo que su shell está a cargo de dividir args de línea de comandos, no Java. –

+1

De todos modos, todavía estoy buscando una forma decente para hacer esto. – Rayne

Respuesta

4

actualizado con una nueva versión, aún más enrevesado. Esto es oficialmente ridículo; la próxima iteración usará un analizador apropiado (o c.c.monads y un poco de lógica parecida a Parsec además de eso). Ver el historial de revisión en esta respuesta para el original.

Este grupo complicado de funciones parece hacer el truco (no en mi DRYest con éste, lo siento!):

(defn initial-state [input] 
    {:expecting nil 
    :blocks (mapcat #(str/split % #"(?<=\s)|(?=\s)") 
        (str/split input #"(?<=(?:'|\"|\\))|(?=(?:'|\"|\\))")) 
    :arg-blocks []}) 

(defn arg-parser-step [s] 
    (if-let [bs (seq (:blocks s))] 
    (if-let [d (:expecting s)] 
     (loop [bs bs] 
     (cond (= (first bs) d) 
       [nil (-> s 
         (assoc-in [:expecting] nil) 
         (update-in [:blocks] next))] 
       (= (first bs) "\\") 
       [nil (-> s 
         (update-in [:blocks] nnext) 
         (update-in [:arg-blocks] 
            #(conj (pop %) 
             (conj (peek %) (second bs)))))] 
       :else 
       [nil (-> s 
         (update-in [:blocks] next) 
         (update-in [:arg-blocks] 
            #(conj (pop %) (conj (peek %) (first bs)))))])) 
     (cond (#{"\"" "'"} (first bs)) 
      [nil (-> s 
        (assoc-in [:expecting] (first bs)) 
        (update-in [:blocks] next) 
        (update-in [:arg-blocks] conj []))] 
      (str/blank? (first bs)) 
      [nil (-> s (update-in [:blocks] next))] 
      :else 
      [nil (-> s 
        (update-in [:blocks] next) 
        (update-in [:arg-blocks] conj [(.trim (first bs))]))])) 
    [(->> (:arg-blocks s) 
      (map (partial apply str))) 
    nil])) 

(defn split-args [input] 
    (loop [s (initial-state input)] 
    (let [[result new-s] (arg-parser-step s)] 
     (if result result (recur new-s))))) 

Algo alentador, los siguientes rendimientos true:

(= (split-args "asdf 'asdf \" asdf' \"asdf ' asdf\" asdf") 
    '("asdf" "asdf \" asdf" "asdf ' asdf" "asdf")) 

Lo mismo ocurre con:

(= (split-args "asdf asdf ' asdf \" asdf ' \" foo bar ' baz \" \" foo bar \\\" baz \"") 
    '("asdf" "asdf" " asdf \" asdf " " foo bar ' baz " " foo bar \" baz ")) 

Espero que s debe recortar argumentos regulares, pero no rodeados de comillas, manejar comillas dobles e individuales, incluidas comillas dobles entre comillas dobles sin comillas (tenga en cuenta que actualmente trata las comillas simples entre comillas simples sin comillas de la misma manera, que aparentemente está en desacuerdo) con la forma de caparazón * nix ... argh) etc. Tenga en cuenta que se trata básicamente de un cálculo en una mónada de estado ad-hoc, que se acaba de escribir de una manera particularmente fea y con una necesidad urgente de DRYing up. :-P

+0

Jesús. Estoy horrorizado de tener que poner eso en mi mente. código. Esto debería ser mucho más fácil de lo que realmente es.: \ ¡Muchas gracias !: D – Rayne

+1

Ya sabes, quizás quieras considerar poner esto en contrib o en una pequeña biblioteca o algo. En serio, esto podría ser útil para más que solo yo. – Rayne

+0

¿No debería ser esto cierto? '(= (split-args" foo bar baz ") '(" foo "" bar "" baz ")) falso' – Rayne

0

que terminé haciendo esto:

(filter seq 
     (flatten 
     (map #(%1 %2) 
       (cycle [#(s/split % #" ") identity]) 
       (s/split (read-line) #"(?<!\\)(?:'|\")")))) 
+0

Me temo que esto se rompe con, digamos, ''asdf '' asdf''. –

+0

Además, una barra invertida puede ser escapada ... Simplemente señalando cosas en caso de que quiera arreglarlas, si descubro una solución alternativa , Lo publicaré como una respuesta. –

+0

De hecho, sabía que no estaba del todo bien, pero estaba tomando todo lo que podía obtener en ese momento. – Rayne

2

Esto me molestó, así que lo conseguí trabajando en ANTLR. La siguiente gramática debería darte una idea de cómo hacerlo. Incluye soporte rudimentario para secuencias de escape de barra invertida.

Obtener ANTLR trabajando en Clojure es demasiado para escribir en este cuadro de texto. Sin embargo, escribí un blog entry al respecto.

grammar Cmd; 

options { 
    output=AST; 
    ASTLabelType=CommonTree; 
} 

tokens { 
    DQ = '"'; 
    SQ = '\''; 
    BS = '\\'; 
} 

@lexer::members { 
    String strip(String s) { 
     return s.substring(1, s.length() - 1); 
    } 
} 

args: arg (sep! arg)* ; 
arg : BAREARG 
    | DQARG 
    | SQARG 
    ; 
sep : WS+ ; 

DQARG : DQ (BS . | ~(BS | DQ))+ DQ 
     {setText(strip(getText()));}; 
SQARG : SQ (BS . | ~(BS | SQ))+ SQ 
     {setText(strip(getText()));} ; 
BAREARG: (BS . | ~(BS | WS | DQ | SQ))+ ; 

WS : (' ' | '\t' | '\r' | '\n'); 
Cuestiones relacionadas