2010-07-14 12 views
6

Sono in una situazione in cui è necessario analizzare gli argomenti da una stringa nello stesso modo in cui verrebbero analizzati se forniti nella riga di comando di un'applicazione Java/Clojure .Analizzare gli argomenti della riga di comando da STRING in Clojure

Ad esempio, ho bisogno di trasformare "foo \"bar baz\" 'fooy barish' foo" in ("foo" "bar baz" "fooy barish" "foo").

Sono curioso di sapere se esiste un modo per utilizzare il parser utilizzato da Java o Clojure per eseguire questa operazione. Non sono contrario all'utilizzo di una regex, ma faccio schifo alle regex e fallirei molto se tentassi di scriverne uno per questo.

Qualche idea?

+0

Penso che la shell abbia il compito di dividere gli argomenti della riga di comando, non Java. –

+1

Indipendentemente da ciò, sto ancora cercando un modo decente per farlo. – Rayne

risposta

4

aggiornato con una nuova, versione ancora più contorto. Questo è ufficialmente ridicolo; la prossima iterazione utilizzerà un parser appropriato (o c.c.monads e un po 'di logica simile a Parsec). Vedi la cronologia delle revisioni su questa risposta per l'originale.

Questo gruppo contorto di funzioni sembra fare il trucco (non il mio più secco con questo, scusate!):

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

Un po 'incoraggiante, le seguenti rese true:

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

Quindi:

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

Speriamo che s dovrebbe regolare gli argomenti regolari, ma non quelli racchiusi tra virgolette, gestire virgolette doppie e singole, tra virgolette doppie racchiuse tra virgolette doppie senza virgolette (si noti che attualmente tratta citazioni singole citate all'interno di virgolette singole senza virgolette nello stesso modo, che è apparentemente alla varianza con la * nix shell way ... argh) ecc. Si noti che si tratta fondamentalmente di un calcolo in una monade di stato ad-hoc, appena scritta in un modo particolarmente brutto e in un disperato bisogno di DRY up. :-P

+0

Gesù. Sono terrorizzato di dover mettere quella cosa nella mia codice. Questo dovrebbe essere molto più semplice di quello che è in realtà.: \ Grazie mille! D – Rayne

+1

Sai, potresti prendere in considerazione l'idea di metterlo in contrib o una piccola libreria o qualcosa del genere. Seriamente, questo potrebbe essere utile – Rayne

+0

Non dovrebbe essere vero? '(= (split-args" foo bar baz ") '(" foo "" bar "" baz ")) false' – Rayne

0

ho finito per fare questo:

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

Ho paura che questo si interrompa, diciamo, "asdf" asdf'. –

+0

Inoltre, un backslash può essere esso stesso sfuggito ... Indica semplicemente le cose nel caso in cui vuoi correggerle, se trovo una soluzione alternativa Invierò la risposta come risposta: –

+0

Infatti, sapevo che non era giusto, ma stavo prendendo quello che potevo ottenere a quel punto – Rayne

2

Questo mi ha infastidito, quindi l'ho fatto funzionare in ANTLR. La grammatica di seguito dovrebbe darti un'idea di come farlo. Include un supporto rudimentale per le sequenze di escape backslash.

Ottenere ANTLR in Clojure è troppo da scrivere in questa casella di testo. Ho scritto un blog entry su di esso però.

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'); 
Problemi correlati