Help please: or function

13 views
Skip to first unread message

timc

unread,
Mar 9, 2009, 11:48:39 AM3/9/09
to Clojure
Can someone please see what's wrong with this.

(defn getArg [arg]
"Return a [keyword value] depending on the form of the string arg:
1. arg is of the form +x ==> [:x true].
2. arg is of the form -x ==> [:x false].
3. arg is of the form x=v ==> [:x v].
4. else ==> [:files arg]."
(let [x (or
(re-seq #"([+])([a-zA-Z_0-9]+)" arg)
(re-seq #"([-])([a-zA-Z_0-9]+)" arg)
(re-seq #"([a-zA-Z_0-9]+)=([^ ]+)" arg))]
(if (nil? x) [:files arg]
(let [x (first x)
y (nth x 1)
z (nth x 2)]
(cond
(= y "+") [(keyword z) true]
(= y "-") [(keyword z) false]
:else [(keyword y) z])))))

This is part of a command-line-arg processing function. I want to end
up with a map containing things like this:

{:verbose true :fast false :configFile "/tom/dick" :files ["foo" "bar"
"zot"]}

as a result of processing the command line args:

+verbose -fast configFile=/tom/dick foo bar zot

Now...

(getArg "+foo") correctly returns [:foo true]

however

(getArg "-foo") throws a NullPointerException.

Am I wrong in my understanding of the or function? That is, that (or a
b c) should return the first of a,b,c that returns true (i.e. non-nil)
or nil if they are all nil?

Or is something happening to the function 'arg' inside the let?


Thanks in advance.

---------------------------------------
In case it's interesting to anyone, the other part of this (that calls
getArg) is the following.

(defn getArgs [args & xs]
"This processes args (a seq of strings) and returns a map.
The parameter xs is a seq of two-element vectors: [keyword value].
The returned map contains (keyword,value) pairs produced by
processing the args, as follows.
1. An arg of the form +x creates the pair (:x true).
2. An arg of the form -x creates the pair (:x false).
3. An arg of the form x=v creates the pair (:x v), where v is a
string.
4. Any other args will be placed in the pair (:files
vectorOfStrings).
5. Any [x v] in xs, not found in the args, will create the pair (:x
v)."
(let [res {:files []}]
; First process the args (if any).
(when args (doall (for [arg args]
(let [ga (getArg arg) [key val] ga]
(cond
(= key :files) (conj (:files res) val)
:else (conj res ga))))))
; Now process the xs (if any).
(when xs (doall (for [x xs]
(let [key (first x)]
(when (not (key res)) (conj res x))))))
res))

so that (for example)

(def cmdArgs (getArgs *command-line-args* [[:verbose false] [:fast
true] [:configFile "/tom/harry"] [:something "someValue"]]))

both gets command line params into the map cmdArgs and/or creates
entries with default values. What is does NOT do is validate the
parameters.

David Nolen

unread,
Mar 9, 2009, 12:22:05 PM3/9/09
to clo...@googlegroups.com
The first regex is returning an empty list not nil.

David

timc

unread,
Mar 9, 2009, 12:34:36 PM3/9/09
to Clojure
Thanks David.

On Mar 9, 4:22 pm, David Nolen <dnolen.li...@gmail.com> wrote:
> The first regex is returning an empty list not nil.
> David
>

Steve Fisher

unread,
Mar 9, 2009, 1:22:35 PM3/9/09
to Clojure


On Mar 9, 10:48 am, timc <TimGCl...@gmail.com> wrote:
> Can someone please see what's wrong with this.
>
> (defn getArg [arg]
>         "Return a [keyword value] depending on the form of the string arg:
>         1. arg is of the form +x ==> [:x true].
>         2. arg is of the form -x ==> [:x false].
>         3. arg is of the form x=v ==> [:x v].
>         4. else ==> [:files arg]."
>         (let [x (or
>                    (re-seq #"([+])([a-zA-Z_0-9]+)" arg)
>                    (re-seq #"([-])([a-zA-Z_0-9]+)" arg)
>                    (re-seq #"([a-zA-Z_0-9]+)=([^ ]+)" arg))]
>                 (if (nil? x) [:files arg]
>                         (let [x (first x)
>                                 y (nth x 1)
>                                 z (nth x 2)]
>                                 (cond
>                                         (= y "+")     [(keyword z) true]
>                                         (= y "-")     [(keyword z) false]
>                                         :else           [(keyword y) z])))))
>


(defn get-arg [arg]
(let [[_ prefix word] (re-find #"^([+-])(.*)$" arg)]
(if word
[(keyword word) (= prefix "+")]
(let [[_ left right] (re-find #"^(\w*)=(\S+)$" arg)]
(if right
[(keyword left) right]
[:files arg])))))


(prn (get-arg "+foo"))
(prn (get-arg "-foo"))
(prn (get-arg "foo=bar"))
(prn (get-arg "foo-bar"))

David Sletten

unread,
Mar 9, 2009, 1:24:11 PM3/9/09
to clo...@googlegroups.com

On Mar 9, 2009, at 5:48 AM, timc wrote:

  (re-seq #"([a-zA-Z_0-9]+)=([^ ]+)" arg))]


Two small suggestions--it's easier, and more legible to use \S to match non-whitespace characters:
(re-seq #"([a-zA-Z_0-9]+)=(\S+)" arg)

And you've basically defined the "word character" class \w verbatim in your example:
(re-seq #"(\w+)=(\S+)"

Aloha,
David Sletten

Meikel Brandmeyer

unread,
Mar 9, 2009, 3:32:39 PM3/9/09
to clo...@googlegroups.com
Hi Tim,

your getArg function is actually a nice use case
of a not very often used of condp: :>>.

Please note in the following example: as David
Nolen said, we have to use seq after re-seq since
re-seq doesn't return a nil but the empty list.
Hence I used comp to chain the two together.
Also incorporated the suggestions of David Sletten
to use \w and \S.

Another thing I noticed: you put the docstring
after the argument vector. That's wrong. In Clojure
the docstring has to be in front of the argument
vector.

(defn get-arg
"Docstring goes here!"
[arg]
(condp (comp seq re-seq) arg
#"[+](\w+)" :>> #(vector (-> % first (nth 1) keyword) true)
#"[-](\w+)" :>> #(vector (-> % first (nth 1) keyword) false)
#"(\w+)=(\S+)" :>> #(let [x (first %)]
[(keyword (nth x 1)) (nth x 2)])
[:files arg]))

Maybe one could also merge the first two cases, but well...

Hope this helps.

Sincerely
Meikel

Reply all
Reply to author
Forward
0 new messages