For over a month now I’ve been experimenting with a totally new concept to me: functional programming. I’m not going to go deeper into what it is (that’s for another blogpost) so sorry for those who don’t know it yet.
The language I used to take these first steps is ‘Clojure’ (a language with a lisp-like syntax) which I really start loving. In fact I like it so much that I want to be able to control my unix machine with a similar syntax. And this is exactly what my goal is with this new project. Clojure syntax for using the linux terminal!
What is it exactly what I want to achieve?
First of all,
I want to be able to use commands like
- ls -a | sort
- cat * | grep “Linux” | grep -v “UNIX” | wc -l
into a lisp-like syntax
- (sort (ls -a))
- (wc -l (grep -v “UNIX” (grep “Linux” (cat *))))
which I believe to be an improvement to the pipes. Mostly because of the readability.
So this is like converting nested lists to pipes. For this to work the nested lists must always appear the end of the parent list.
At this point it is no more than a syntax conversion.
I want to do better. I want to be able to put nested lists (nested commands) at any place. What would this look like?
(cmd1 –arg1 (cmd2 … ) –arg2 (cmd3 ….))
I’m still thinking of examples of where this would be useful (dd?). If you can let me know (and everyone) know in the comments below!
The real stuff
As an exercise for myself I challenged myself to create a syntax converter in clojure.
So here is a first tryout:
(def sample-string "(sort (ls -a))") (defn pipe ([a] (clojure.string/join " " a)) ([a b] (str (pipe a) " | " (pipe b)))) (defn stack-to-linux [stack] (reduce pipe stack )) (defn split-from-subcommand [l] (split-with #(not (coll? %)) l)) (defn valid-cmd? [cmd] (not (or (empty? cmd)))) (defn list-to-stack [l stack] (let [[first* r] l] (if (coll? first*) (list-to-stack first* stack) (dosync (let [[cmd1 cmd2] (split-from-subcommand l)] (alter stack conj cmd1) (when (valid-cmd? cmd2) (list-to-stack (first cmd2) stack))))))) (defn string-to-stack [string] (let [stack (ref '())] (list-to-stack (read-string string) stack) @stack)) (defn funix [string] (stack-to-linux (string-to-stack string)))
I’m using a feature of clojure: clojure code is clojure data. Its really really fancy (and helpful)
What currently works:
(funix “(sort (ls -la)” ) –> “ls -la | sort”
I still have problems with strings in the string:
(funix "(grep "a" (ls -la))") ArityException Wrong number of args (3) passed to: user$lilisp clojure.lang.AFn.throwArity (AFn.java:437) #So I tried escaping it (funix "(grep \"test\" (ls -la))") #returns (la -la | grep test) which is not what I want of course (the quotes are missing)
If you have ideas on how to improve this let us know in the comments. Also join me at https://code.google.com/p/funix/
Also if you think my idea is just stupid (or great) let me know as well :-)
Share and enjoy!