2014-01-24 11:21:49 +08:00
|
|
|
# An experimental Unix shell
|
2013-06-16 16:45:22 +08:00
|
|
|
|
2013-09-21 21:05:27 +08:00
|
|
|
This is a work in progress. Things may change and/or break without notice. You
|
|
|
|
have been warned...
|
|
|
|
|
2014-01-24 11:21:49 +08:00
|
|
|
## Obligatory screenshots
|
2014-01-08 19:07:34 +08:00
|
|
|
> All software websites should have screenshots.
|
2014-01-08 19:09:34 +08:00
|
|
|
> -- Someone on the Internet
|
2014-01-08 19:07:34 +08:00
|
|
|
|
|
|
|
Syntax highlighting (also showcasing right-hand-side prompt):
|
|
|
|
|
|
|
|
![syntax highlighting](./screenshots/syntax.png)
|
|
|
|
|
|
|
|
Tab completion:
|
|
|
|
|
|
|
|
![tab completion](./screenshots/completion.png)
|
|
|
|
|
2014-01-28 14:44:16 +08:00
|
|
|
## Building
|
|
|
|
|
2014-01-28 20:23:50 +08:00
|
|
|
Go >= 1.1.1 is required. This repository is a go-getable package.
|
|
|
|
|
|
|
|
In case you are new to Go, you are advised to read [How To Write Go
|
|
|
|
Code](http://golang.org/doc/code.html), but here is a quick snippet:
|
|
|
|
|
|
|
|
```
|
|
|
|
export GOPATH=$HOME/go
|
|
|
|
export PATH=$PATH:$GOPATH/bin
|
|
|
|
go get github.com/xiaq/das
|
|
|
|
das
|
|
|
|
```
|
|
|
|
|
|
|
|
To update and rebuild:
|
|
|
|
|
|
|
|
```
|
|
|
|
go get -u github.com/xiaq/das
|
|
|
|
```
|
|
|
|
|
|
|
|
Remember to put the two `export`s above into your `bashrc` or `zshrc` (or
|
|
|
|
whatever).
|
2014-01-08 19:07:34 +08:00
|
|
|
|
2014-01-24 11:21:49 +08:00
|
|
|
## The Editor
|
2013-12-29 15:04:37 +08:00
|
|
|
|
2014-01-08 19:07:34 +08:00
|
|
|
Those marked with ✔ are implemented (but could be broken from time to
|
2014-01-08 18:52:09 +08:00
|
|
|
time).
|
2013-12-29 15:04:37 +08:00
|
|
|
|
|
|
|
Like fish:
|
|
|
|
|
2014-01-08 18:56:55 +08:00
|
|
|
* Syntax highlighting ✔
|
2013-12-29 15:04:37 +08:00
|
|
|
* Auto-suggestion
|
|
|
|
|
|
|
|
Like zsh:
|
|
|
|
|
2014-01-08 18:56:55 +08:00
|
|
|
* Right-hand-side prompt ✔
|
|
|
|
* Dropdown menu completion ✔
|
2013-12-29 15:04:37 +08:00
|
|
|
* Programmable line editor
|
|
|
|
|
|
|
|
And:
|
|
|
|
|
|
|
|
* A vi keybinding that makes sense
|
2014-01-08 19:07:34 +08:00
|
|
|
* More intuitive multiline editing
|
|
|
|
* Some method to save typed snippets into a script
|
|
|
|
* A "navigation mode" integrating functionalities of
|
2014-01-08 19:10:49 +08:00
|
|
|
[ranger](http://ranger.nongnu.org/)
|
2013-12-29 15:04:37 +08:00
|
|
|
|
2014-01-24 11:21:49 +08:00
|
|
|
## The Language
|
2013-12-29 15:04:37 +08:00
|
|
|
|
2014-01-08 18:56:55 +08:00
|
|
|
(Like the previous section, only those marked with ✔ have been implemented.)
|
2013-12-29 15:20:49 +08:00
|
|
|
|
2014-01-24 20:10:37 +08:00
|
|
|
* Running external programs and pipelines, of course (`>` represents the
|
|
|
|
prompt): ✔
|
2013-12-29 15:12:01 +08:00
|
|
|
```
|
|
|
|
> vim README.md
|
2013-12-29 15:20:49 +08:00
|
|
|
...
|
2013-12-29 15:12:01 +08:00
|
|
|
> cat -v /dev/random
|
2013-12-29 15:20:49 +08:00
|
|
|
...
|
|
|
|
> dmesg | grep bar
|
|
|
|
...
|
2013-12-29 15:12:01 +08:00
|
|
|
```
|
|
|
|
|
2014-01-24 20:10:37 +08:00
|
|
|
* Some constructs look like lisp without the outermost pair of parentheses: ✔
|
2013-12-29 15:07:29 +08:00
|
|
|
```
|
|
|
|
> + 1 2
|
|
|
|
3
|
|
|
|
> * (+ 1 2) 3
|
|
|
|
9
|
|
|
|
```
|
2013-12-29 15:04:37 +08:00
|
|
|
|
|
|
|
* Use backquote for literal string (so that you can write both single and
|
2014-01-08 18:56:55 +08:00
|
|
|
double quotes inside), double backquotes for a literal backquote: ✔
|
2013-12-29 15:07:29 +08:00
|
|
|
```
|
|
|
|
> echo `"He's dead, Jim."`
|
|
|
|
"He's dead, Jim."
|
|
|
|
> echo ```He's dead, Jim."`
|
|
|
|
``He's dead, Jim."
|
|
|
|
```
|
2013-12-29 15:04:37 +08:00
|
|
|
|
|
|
|
* Barewords are string literals:
|
2013-12-29 15:07:29 +08:00
|
|
|
```
|
|
|
|
> = a `a`
|
|
|
|
true
|
|
|
|
```
|
2013-12-29 15:04:37 +08:00
|
|
|
|
|
|
|
* Tables are a hybrid of array and hash (a la Lua); tables are first-class
|
2014-01-08 18:56:55 +08:00
|
|
|
values: ✔
|
2013-12-29 15:07:29 +08:00
|
|
|
```
|
2014-01-08 18:52:09 +08:00
|
|
|
> println [a b c &key value]
|
2013-12-29 15:07:29 +08:00
|
|
|
[a b c &key value]
|
2014-01-08 18:52:09 +08:00
|
|
|
> println [a b c &key value][0]
|
2013-12-29 15:07:29 +08:00
|
|
|
a
|
2014-01-08 18:52:09 +08:00
|
|
|
> println [a b c &key value][key]
|
2013-12-29 15:07:29 +08:00
|
|
|
value
|
|
|
|
```
|
2013-12-29 15:04:37 +08:00
|
|
|
|
2014-01-08 18:52:09 +08:00
|
|
|
* Declare variable with `var`, set value with `set`; `var` also serve as a
|
2014-01-08 18:56:55 +08:00
|
|
|
shorthand of var-set combo: ✔
|
2013-12-29 15:07:29 +08:00
|
|
|
```
|
|
|
|
> var v
|
|
|
|
> set v = [foo bar]
|
2014-01-08 18:52:09 +08:00
|
|
|
> var u = [foo bar] # equivalent
|
2013-12-29 15:07:29 +08:00
|
|
|
```
|
2013-12-29 15:04:37 +08:00
|
|
|
|
|
|
|
* First-class closures, lisp-like functional programming:
|
2013-12-29 15:07:29 +08:00
|
|
|
```
|
|
|
|
> map {|x| * 2 $x} [1 2 3]
|
|
|
|
[2 4 6]
|
2013-12-30 16:23:22 +08:00
|
|
|
> filter {|x| > $x 2} [1 2 3 4 5]
|
2013-12-29 15:07:29 +08:00
|
|
|
[3 4 5]
|
2013-12-30 16:23:22 +08:00
|
|
|
> map {|x| * 2 $x} (filter {|x| > $x 2} [1 2 3 4 5])
|
2013-12-29 15:07:29 +08:00
|
|
|
```
|
2013-12-29 15:04:37 +08:00
|
|
|
|
2014-01-08 18:52:09 +08:00
|
|
|
* Get rid of lots of irritating superfluous parentheses with pipelines (`put`
|
|
|
|
is the builtin for outputting compound data):
|
2013-12-29 15:07:29 +08:00
|
|
|
```
|
2013-12-30 16:23:22 +08:00
|
|
|
> put [1 2 3 4 5] | filter {|x| > $x 2} | map {|x| * 2 $x}
|
2013-12-29 15:07:29 +08:00
|
|
|
[6 8 10]
|
|
|
|
```
|
2013-12-29 15:04:37 +08:00
|
|
|
|
|
|
|
* Use the table `$env` for environmental variables:
|
2013-12-29 15:07:29 +08:00
|
|
|
```
|
|
|
|
> put $env[HOME]
|
|
|
|
/home/xiaq
|
|
|
|
> set env[PATH] = $env[PATH]:/bin
|
|
|
|
```
|
2013-12-29 15:04:37 +08:00
|
|
|
|
|
|
|
There are many parts of the language that is not yet decided. See TODO.md for
|
|
|
|
a list of things I'm currently thinking about.
|
|
|
|
|
2014-01-24 11:21:49 +08:00
|
|
|
## Motivation
|
2013-09-22 18:49:07 +08:00
|
|
|
|
2014-01-24 11:21:49 +08:00
|
|
|
This experiment has a number of motivations. Some of them:
|
2013-09-22 18:49:07 +08:00
|
|
|
|
2014-01-24 11:21:49 +08:00
|
|
|
* It attempts to prove that a shell language can be a handy interface to the
|
|
|
|
operating system **and** a decent programming language at the same time; Many
|
|
|
|
existing shells recognize the former but blatantly ignore the latter.
|
2013-09-22 18:49:07 +08:00
|
|
|
|
2014-01-24 11:21:49 +08:00
|
|
|
* It attempts to build a **better interface** to the operating system, trying
|
|
|
|
to strike the right balance between the tool philosophy of Unix and the
|
|
|
|
tremendous usefulness of a more integrated system.
|
|
|
|
|
|
|
|
* It also attempts to build a **better language**, learning from the success
|
|
|
|
and failure of programming language designs.
|
|
|
|
|
2014-01-24 20:08:52 +08:00
|
|
|
* It attempts to exploit a facility Shell programmers are very familiar with,
|
|
|
|
but virtually unknown to other programmers - the pipeline. That leads us to
|
2014-01-24 11:21:49 +08:00
|
|
|
the topic of the next few sections.
|
|
|
|
|
|
|
|
## Pipeline, the Good
|
|
|
|
|
|
|
|
### A Concatenative Programming Facility
|
|
|
|
|
|
|
|
Pipelines make for a natural notation of concatenative programming.
|
|
|
|
|
|
|
|
So what's concatenative programming? In some of its most common use cases, we
|
|
|
|
can say it's just functional programming without [lots of irritating
|
|
|
|
superfluous parentheses](http://xkcd.com/297/). Consider this fictional piece
|
|
|
|
of lisp to find in `strs`, a list of strings, all members containing "LOL",
|
|
|
|
transform them into upper case, sort them, and store them in another list
|
|
|
|
`lols`:
|
2013-09-22 18:49:07 +08:00
|
|
|
|
|
|
|
```
|
2014-01-24 11:21:49 +08:00
|
|
|
(def lols (sort (map upper-case
|
|
|
|
(filter (lambda (x) (contains? x "LOL")) strs))))
|
2013-09-22 18:49:07 +08:00
|
|
|
```
|
|
|
|
|
2014-01-24 20:08:52 +08:00
|
|
|
(See [Appendix A](#appendix-a) for this piece of code in real lisps.)
|
|
|
|
|
2014-01-24 11:21:49 +08:00
|
|
|
It looks OK until you try to read the code aloud:
|
|
|
|
|
|
|
|
> Put in `lols` what results from sorting what results from turning into upper
|
|
|
|
> case what results from filtering the strings that contain "LOL" in `strs`.
|
|
|
|
|
|
|
|
An deep hierarchy of parentheses map into a deep hierarchy of clauses. Worse,
|
|
|
|
this reads *backwards*.
|
|
|
|
|
|
|
|
What would you do it in shell, with pipelines? Assuming that the strings are
|
|
|
|
stored in the file `strs`, it is just:
|
2013-09-22 18:49:07 +08:00
|
|
|
|
|
|
|
```
|
2014-01-24 11:21:49 +08:00
|
|
|
lols=`cat strs | grep LOL | tr a-z A-Z | sort`
|
2013-09-22 18:49:07 +08:00
|
|
|
```
|
|
|
|
|
2014-01-24 20:08:52 +08:00
|
|
|
The historically weird names aside, it reads perfectly natural: assign to
|
|
|
|
`lols` the result of the following: take the lines in `strs`, find those
|
|
|
|
having "LOL", change them to upper case, and sort them. This matches our
|
|
|
|
description of the procedure except for the assignment. There is an obvious
|
|
|
|
restriction with this shell pipeline approach, but that will be the topic of
|
|
|
|
the next section.
|
|
|
|
|
|
|
|
Concatenative programming is the notion of building programs by connecting
|
|
|
|
data-transforming constructs together. In our case, the constructs are `cat
|
|
|
|
strs`, `grep LOL`, `tr a-z A-Z` and `sort`; the pipe symbol is the
|
|
|
|
connector. The interesting thing is that each construct itself is actually a
|
|
|
|
valid program; thus it could be said that a more complex program is formed by
|
|
|
|
*concatenating* simpler programs, hence the term "concatenative programming".
|
|
|
|
Compare this to the functional approach, where constructs are *nested* instead
|
|
|
|
of connected one after another.
|
2014-01-24 11:21:49 +08:00
|
|
|
|
|
|
|
### A Concurrency Construct
|
2013-09-22 18:49:07 +08:00
|
|
|
|
2014-01-24 11:21:49 +08:00
|
|
|
(TO BE WRITTEN)
|
2013-10-06 15:35:27 +08:00
|
|
|
|
2014-01-24 20:08:52 +08:00
|
|
|
## Pipeline, the Bad and the Ugly
|
2013-10-06 15:35:27 +08:00
|
|
|
|
2014-01-24 11:21:49 +08:00
|
|
|
(TO BE WRITTEN)
|
2013-09-22 18:49:07 +08:00
|
|
|
|
2014-01-24 11:32:52 +08:00
|
|
|
## Name
|
2013-09-21 21:05:27 +08:00
|
|
|
|
2013-09-21 21:08:54 +08:00
|
|
|
Indeed, **das** is not a very good name for a Unix shell. The name is actually
|
|
|
|
a corrupted form of **dash**, which also happens to be the German definite
|
|
|
|
neuter article.
|
|
|
|
|
2013-11-16 19:30:04 +08:00
|
|
|
I have some other ideas in mind. One of them is **elv**, since I found
|
|
|
|
"elvish" to be a great adjective - I can't use "elf" though, since it's
|
|
|
|
already [taken](http://www.cs.cmu.edu/~fp/elf.html) and may be confused with
|
|
|
|
the well known [file
|
2013-09-21 21:05:27 +08:00
|
|
|
format](http://en.wikipedia.org/wiki/Executable_and_Linkable_Format).
|
|
|
|
|
|
|
|
Another possible source of names is the names of actual seashells; but my
|
|
|
|
English vocabulary is too small for me to recall any beyond "nautilus", which
|
|
|
|
is both too long and already taken.
|
|
|
|
|
|
|
|
I'm not avoiding names ending in "sh" though; but I do find "bash" to be a
|
2013-11-16 19:30:04 +08:00
|
|
|
terrible name. "fish" is clever, but it has a quite [unpleasant
|
|
|
|
adjective](https://en.wiktionary.org/wiki/fishy). I find "dash" really good
|
|
|
|
though, which is why it came to my mind :).
|
2013-09-18 16:45:18 +08:00
|
|
|
|
2014-01-24 11:32:52 +08:00
|
|
|
## License
|
2013-07-26 14:46:24 +08:00
|
|
|
|
|
|
|
BSD 2-clause license. See LICENSE for a copy.
|
2014-01-24 20:08:52 +08:00
|
|
|
|
|
|
|
## Appendix A
|
|
|
|
|
|
|
|
This fictional lisp code:
|
|
|
|
|
|
|
|
```
|
|
|
|
(def lols (sort (map upper-case
|
|
|
|
(filter (lambda (x) (contains? x "LOL")) strs))))
|
|
|
|
```
|
|
|
|
|
|
|
|
written in Clojure:
|
|
|
|
|
|
|
|
```
|
|
|
|
(require ['clojure.string :refer '(upper-case)])
|
|
|
|
(def strs '("aha" "LOLaha" "hahaLOL" "hum?"))
|
|
|
|
(def lols (sort (map upper-case
|
|
|
|
(filter #(re-find #"LOL" %) strs))))
|
|
|
|
```
|
|
|
|
|
|
|
|
written in Racket:
|
|
|
|
|
|
|
|
```
|
|
|
|
(define strs '("aha" "LOLaha" "hahaLOL" "hum?"))
|
|
|
|
(define lols (sort (map string-upcase
|
|
|
|
(filter (lambda (x) (regexp-match? #rx"LOL" x)) strs))
|
|
|
|
string<?))
|
|
|
|
```
|
|
|
|
|
|
|
|
I'm by no means a Lisp hacker, so feel free to fire an issue if my code is not
|
|
|
|
idiomatic.
|