代码之家  ›  专栏  ›  技术社区  ›  Rayne

从Clojure中的字符串分析命令行参数

  •  6
  • Rayne  · 技术社区  · 14 年前

    我需要在一个字符串中解析参数,如果在命令行上提供了一个Java/Culjure应用程序,它们将被解析。

    例如,我需要转向 "foo \"bar baz\" 'fooy barish' foo" 进入之内 ("foo" "bar baz" "fooy barish" "foo") .

    我很好奇,是否有一种方法可以使用Java或Culjule使用的解析器来实现这一点。我并不反对使用正则表达式,但我在正则表达式方面很差劲,如果我尝试为此编写一个正则表达式,我会很难失败。

    有什么想法吗?

    3 回复  |  直到 14 年前
        1
  •  4
  •   Michał Marczyk    14 年前

    更新了一个新的,更复杂的版本。这在官方上是荒谬的;下一次迭代将使用一个合适的解析器(或C.C.monads,以及在此之上一些类似于解析器的逻辑)。原文见本答案的修订历史。

    这一组复杂的函数似乎起到了作用(抱歉,这一个在我看来不是最简单的!):

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

    令人鼓舞的是,以下是 true :

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

    这样做:

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

    希望这样可以减少常规的参数,但不要使用引号包围的参数,处理双引号和单引号,包括未引号双引号内的引号(请注意,它当前处理未引号单引号内的引号的方式与处理未引号单引号的方式相同,这显然与*nix shell方式不同…argh)等等。请注意,它基本上是一个特殊状态monad的计算,只是用一种特别难看的方式写的,并且非常需要干涸。-P

        2
  •  2
  •   Brian Carper    14 年前

    这让我心烦意乱,所以我让它在安特尔工作。下面的语法应该给你一个怎么做的概念。它包括对反斜杠转义序列的基本支持。

    让Antlr在Clojure中工作太多,无法在此文本框中写入。我写了一篇 blog entry 尽管如此。

    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');
    
        3
  •  0
  •   Rayne    14 年前

    我最终做了这件事:

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