Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/SquircleSpace/shcl

SHell in Common Lisp
https://github.com/SquircleSpace/shcl

common-lisp shell

Last synced: 2 months ago
JSON representation

SHell in Common Lisp

Awesome Lists containing this project

README

        

#+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
1. a very customizable shell made with secret alien technology, and
2. 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 #) :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 # #) :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.