(require 'stdlib) (require 'readline) ;; TODO (define-command name closure) ;; enter name+closure into command table, allow for shortest prefixes (define command-table ()) (defmacro define-command (name #!rest forms) `(setq command-table (nconc command-table (list (cons ',name (lambda () ,@forms)))))) (define-command help ;; TODO build this from the commands table, add command descriptions ;; to define-command (puts "lsh 0.1") (puts ",help for this help.") (puts ",quit to quit.")) (define-command quit (puts "byebye.") (throw 'quit 0)) (define-command ls (puts (mapconcat identity (sort (directory-files (getenv "PWD"))) ", "))) (define (get-command-closure name) (rest (assoc name command-table))) ;; (or .. (lambda () (printf ":: unknown command `%s'\n" name))) (define (prompt) (let ((prompt (concat "lsh:" (getenv "PWD") "> "))) prompt)) (define (read-loop) (let (command) (while (setq command (readline (prompt))) (setq command (read-from-string (string-replace "\n" "" command))) (printf "lsh: %s\n" command) (condition-case descr (handler command) (error (printf "lsh: oops: %s\n" descr)))))) ;; TODO unify all command types: a command is always a list. Commands ;; like a bare 'ls args' is translated by the reader into (ls ;; args). The handler first checks if the car is on command-table ;; (funcall it), is backquote-unquote (special command), and otherwise ;; it calls system. The reader can either dispatch to different ;; handlers based on the type of command received, or canonicalize all ;; read commands, and then call a dispatcher that determines the kind ;; of action take. (define (handler command) (cond ((atom command) (let ((internal (get-command-closure command))) (if internal (funcall internal) ;; TODO start a bash once and pipe commands to it? (system (symbol-name command))))) ((eq (first command) 'backquote-unquote) (funcall (or (get-command-closure (second command)) (lambda () (puts ":: unknown meta command"))))) ((listp command) (puts (eval command))) (t (error "Errr, let's say... 42.\n")))) (read-loop)