Skip to content

cesquivias/rash

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Racket Shell (rash)

Rash is command line shell built on top of the Racket programming environment with the entire power of the platform available.

The default language is based on lisp. Language extensions can be loaded dynamically.

Examples

#!/usr/bin/env racket
#lang rash

echo Hello World
echo "Hello in a string!"

(~> (echo Piping Example) (wc -c))

Goals

  • Take advantage of Racket’s language capabilities to create an easy-to-use shell.
  • An intuitive syntax that’s memorable and doesn’t require an 38 chapter guide.
  • Extendable language through Racket’s language creation libraries.

Non Goals

  • Lean, mean memory usage The Racket VM takes more memory than bash or other shells. All things being equal, a process should take as much memory as needed and no more but spending effort to cut down rash’s memory usage isn’t a priority.

Motivation

All current shells have a line when scripts become too complicated, and you need to switch from bash/zsh/fish to a more general purpose scripting language like Perl/Python/Ruby. Current shell languages provide robust programming environment to grow hackish, one-off scripts into large, robust programs.

The switch to “scripting” languages has its own problems. Although you now have a language that provides useful data structures and paradigms for more complex problems, job control and process pipelining takes a backseat. Spawning processes and redirecting output become second-class citizens. If they weren’t, why not use Python as a login shell?

Rash tries to bridge this gap using the Racket programming environment. Racket is uniquely positioned to tackle this problem. The programming environment is built with the purpose of creating domain specific languages (DSLs) to solve specific technical problems while still having the full power of Racket at your disposal. Rash is just a DSL in Racket specifically designed to be a command line shell; that is, it’s emphasis is on spawning jobs and redirecting IO.

Getting Started

Installation

Rash requires the Racket programming environment[fn:1]. You can download the latest version from Racket’s download page.

Custom functions

You can expand Rash’s functionality by writing your own custom functions. Racket’s default syntax for creating functions works perfectly.

#!/usr/bin/env racket
#lang rash

(define (racket-file? fname)
  (string-ref fname ".rkt"))

Under the Hood

Rash is built from three abstraction layers. Each level removes some of Racket’s complexity to focus on tasks done in a shell. From the topmost abstraction layer:

  • Reader
  • Module language
  • Library

The reader and module language sections plugs into Racket’s langauge creation tools. Read the Creating Languages section of the Racket Guide for an introduction.

Reader

The job of the reader is to take a stream of bytes and create an abstract syntax tree. Rash is different from Racket in that it uses newlines as a statement separator while Racket uses parentheses to delimit all expressions.

Rash only has 3 main datatypes the reader handles:

String
Nearly all tokens read by the reader will be converted to a Racket string. For example, a simple line like echo Hello world is of strings.
Symbol
(Not Yet Implemented) When a token begins with $ it is a symbol. Symbols will be resolved later on as either a Racket variable or a system variable.
Expression
When Rash sees an open parenthesis it reads the input as a normal Racket s-expression. All the power of Racket is available here.

Module Language

The module language level specifies how Rash expressions are evaluated and what the builtin functions are provided. Rash’s evaluation is similar typical Racket but contains a sepcial twist: strings as procedures.

From the Reader all top-level expressions that aren’t variables or Racket expressions are read in as strings. Strings are used as paths to external programs. That is, when Rash sees you are trying to evaluate an procedure call with a string as a procedure it will look for an executable file with name in your PATH environment variable and start a process of that executable.[fn:2]

There are some special cases like the pipe operator (~>) that circumvent this.

The other duty of the module language is to provide all the necessary built-in functions that Rash provides to be a useful, convenient shell language. These builtins are functions like cd, export and other common shell builtins written at the library level.[fn:3] These builtins come from either Rash’s library or from Racket’s standard library. Because Rash exposes =require=, any other library or code written for the Racket environment is available in Rash.

Library

At its lowest level, Rash is just a Racket library with convenience functions and macros to spawn a new process, pipe processes together. When using Rash, these functions are considered the builtins that do not spawn a new process.

Because the whole power of Racket is available when using Rash, functionality that other shell environments have to provide are not implemented in Rash’s library; those builtins like conditionals and loops are provided by Racket.

Footnotes

[fn:1] TODO: Determine the minimum version of Racket required.

[fn:2] TODO: Handle absolute and relative paths for executables.

[fn:3] TODO: Provide link to doc page for all builtins.

About

A *nix shell written in Racket

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages