shcl
shcl copied to clipboard
SHell in Common Lisp
#+BEGIN_COMMENT Copyright 2017 Bradley Jensen
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. #+END_COMMENT
#+TITLE: SHCL: Shell Meets Common Lisp #+AUTHOR: Brad Jensen
SHCL is
- a very customizable shell made with secret alien technology, and
- an unholy union of POSIX Shell and Common Lisp.
SHCL is more than just a shell. It is a mutual embedding of POSIX Shell and Common Lisp. Behold Common Lisp embedded in POSIX shell embedded in Common Lisp! Notice that the Common Lisp form embedded in the shell expression can access the lexical environment. #+BEGIN_EXAMPLE (let ((rld "rld")) (capture (:stdout) #$ echo Hello ,(concatenate 'string "Wo" rld) #$)) ; => "Hello World" #+END_EXAMPLE
Now lay your eyes on a lisp function participating in a pipeline! #+BEGIN_EXAMPLE shcl> : ,(shcl/core/debug:graph-dependencies) | dot -Tpng > graph.png #+END_EXAMPLE
The =#$= reader macro isn't just some hack that constructs a string to be evaluated by a "real" shell. The =#$= reader macro fully parses the shell expression and constructs an equivalent Common Lisp form. SHCL IS the "real" shell!
#+BEGIN_EXAMPLE SHCL/CORE/LISP-INTERPOLATION> (macroexpand-1 '#$ if true; then echo woo; fi #$) (SHCL/CORE/SHELL-FORM:SHELL-IF (SHCL/CORE/SHELL-FORM:SHELL-RUN (WITH-FD-STREAMS NIL (EXPANSION-FOR-WORDS (LIST #<NAME "true">) :EXPAND-ALIASES T :EXPAND-PATHNAME-WORDS T :SPLIT-FIELDS NIL)) :ENVIRONMENT-CHANGES NIL :FD-CHANGES NIL) (SHCL/CORE/SHELL-FORM:SHELL-RUN (WITH-FD-STREAMS NIL (EXPANSION-FOR-WORDS (LIST #<NAME "echo"> #<NAME "woo">) :EXPAND-ALIASES T :EXPAND-PATHNAME-WORDS T :SPLIT-FIELDS NIL)) :ENVIRONMENT-CHANGES NIL :FD-CHANGES NIL)) T #+END_EXAMPLE
- Building SHCL
SHCL is only really tested against SBCL and CCL, but it should be portable to other lisp compilers. Be aware that ECL is known to be problematic because it tries to reap child processes automatically.
First, you'll need to install some dependencies. To start with, you'll need Clang and libedit. There's also some Common Lisp dependencies that need to be taken care of: SBCL, Quicklisp, and cffi-grovel. If you're new to building Common Lisp projects, you might want to let [[https://github.com/roswell/roswell][Roswell]] set up your lisp environment for you.
#+BEGIN_EXAMPLE
Set up Clang, libedit, and Roswell
make LISP='ros -s cffi-grovel run --' #+END_EXAMPLE
You can skip Roswell if you want. Just make sure that you set ~LISP~ to a command that runs SBCL with Quicklisp and cffi-grovel loaded. For example,
#+BEGIN_EXAMPLE
Set up Clang, libedit, SBCL, and Quicklisp
QUICKLISP_SETUP=~/quicklisp/setup.lisp # or wherever you installed quicklisp make LISP="sbcl --no-userinit --load "$QUICKLISP_SETUP" --eval '(ql:quickload :cffi-grovel)'" #+END_EXAMPLE
If you use the Nix package manager, building SHCL is super easy! SHCL has a =default.nix= file, so you just need to run =nix-build=. #+BEGIN_EXAMPLE nix-build #+END_EXAMPLE
Congratulations! You built SHCL! If you try to run =shcl= you'll probably find that it doesn't work because it can't find =libshcl-support=. As part of the build, SHCL produces a shared library named (you guessed it!) =libshcl-support=. That library needs to be installed somewhere that the dynamic linker can find it. So, go ahead and use =sudo make install= to install SHCL and its support library! Don't forget to set the =PREFIX= to something you're happy with. Alternatively, you can just use the =run-shcl= script included in the repository. =run-shcl= just adds =$(pwd)= to the dynamic linker's search path before invoking =./shcl=.
Note: if you build SHCL using =nix-build=, then you don't have to worry about =libshcl-support=. SHCL will know how to find it!
- Example Usage
I don't know what you're expecting to see here. Its a POSIX-like shell. You can do (almost) all your normal POSIX shell stuff in it.
#+BEGIN_EXAMPLE shcl> echo foobar foobar shcl> { echo foobar ; echo baz ; echo blip ; } | tail -n 1 blip shcl> shcl-enable-lisp-syntax shcl> if [ ,(+ 1 2 3) = ,(* 2 3) ]; then
echo woah wait what fi woah wait what shcl> shcl-repl shcl (lisp)> (define-builtin upcase () (loop :for line = (read-line standard-input nil :eof) :until (eq line :eof) :do (format "~A~%" (string-upcase line))) 0) UPCASE shcl (lisp)> ^D shcl> { echo ahhh ; echo what is going on ; } | upcase AHHH WHAT IS GOING ON #+END_EXAMPLE
Okay, actually, that kind of went off the rails.