├── CREDITS ├── .gitignore ├── HEADER ├── examples ├── test.esh ├── test2.esh ├── prompt.esh ├── test3.esh ├── test4.esh ├── bold.esh ├── test7.esh ├── prepend.esh ├── test5.esh ├── test6.esh ├── gaudy.esh └── pipes.esh ├── TODO ├── GC_README ├── common.h ├── READLINE-HACKS ├── builtins.h ├── format.h ├── README.md ├── job.h ├── gc.h ├── PATCH ├── format.c ├── read.h ├── INSTALL ├── read-stdio.c ├── esh.h ├── Makefile ├── list.h ├── bold.c ├── hash.h ├── gc.c ├── CHANGELOG ├── read-rl.c ├── list.c ├── hash.c ├── emacs └── esh-mode.el ├── LICENSE ├── esh.c ├── doc └── esh.texi └── builtins.c /CREDITS: -------------------------------------------------------------------------------- 1 | Ivan Tkatchev, ivantk@yahoo.com, http://esh.netpedia.net 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw[op] 2 | *.bak 3 | *.elc 4 | *.o 5 | *.so 6 | bold 7 | esh 8 | -------------------------------------------------------------------------------- /HEADER: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | -------------------------------------------------------------------------------- /examples/test.esh: -------------------------------------------------------------------------------- 1 | (run-simple ~(echo 'File list:')) 2 | (run-simple ~(ls --color=yes)) 3 | (print (nl) (nl)) 4 | (alias ls ls --color=yes) 5 | (run-simple ~(ls)) 6 | (run-simple ~(less LICENSE)) 7 | (copy oops this does nothing) 8 | (bg) 9 | (jobs) 10 | (cd ..) 11 | 12 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * Better tab-completion. 2 | * String handling: chopping off bits, substrings. 3 | * Better reporting of memory leaks. 4 | * Rewrite "alive?" in a more portable way. 5 | * Regexps. 6 | 7 | ? libdl support. Is it portable enough? 8 | * Fix the broken globber. 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/test2.esh: -------------------------------------------------------------------------------- 1 | 2 | (define stacker 3 | ~(if ~(not-null? (top)) 4 | ~(begin (pop) (stacker (stack))) 5 | ())) 6 | 7 | 8 | (print (stacker hello world this is not an 9 | exhaustive test of the memory leakiness of esh) (nl)) 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /GC_README: -------------------------------------------------------------------------------- 1 | 2 | The much-improved refcounter code in esh 0.5 can be used as a drop-in 3 | replacement for malloc/free. It has been written in such a way as to be 4 | imdependent of esh itself. Simply compile and link gc.c along with the 5 | rest of your program, and replace calls to "malloc" with "gc_alloc", 6 | and "free" with "gc_free". 7 | 8 | -------------------------------------------------------------------------------- /common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | #ifndef __common_h__ 8 | #define __common_h__ 9 | 10 | #define VERSION_MAJOR 0 11 | #define VERSION_MINOR 8 12 | #define VERSION_PATCH 5 13 | 14 | #endif /* !__common_h__ */ 15 | -------------------------------------------------------------------------------- /READLINE-HACKS: -------------------------------------------------------------------------------- 1 | Only one readline hack so far: 2 | 3 | You can use control-j to insert a literal newline. (i.e. when you 4 | want to type long definitions interactively.) 5 | 6 | One non-readline hack: typing Control-C while the shell is evaluating 7 | commands will take you back to the prompt. This is useful when you 8 | have inadvertently entered an infinite loop. 9 | 10 | -------------------------------------------------------------------------------- /builtins.h: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | #ifndef __builtins_h__ 8 | #define __builtins_h__ 9 | 10 | extern hash_entry builtins_array[]; 11 | 12 | extern list* eval(list* arg); 13 | extern void register_chdir(void); 14 | 15 | #endif /* !__builtins_h__ */ 16 | -------------------------------------------------------------------------------- /examples/prompt.esh: -------------------------------------------------------------------------------- 1 | (define setxtermtitle 2 | ~(print "XTerm title: " (stack) (nl))) 3 | 4 | (if ~(= (get TERM) xterm) 5 | ~(prompt ~(get USER) 6 | ":" 7 | ~(get TTY) 8 | ":" 9 | ~(get PWD) 10 | "$ " 11 | ~(setxtermtitle (squish "xterm:[" (get TTY) "]:" (get PWD)))) 12 | ()) 13 | 14 | -------------------------------------------------------------------------------- /format.h: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | #ifndef __format_h__ 8 | #define __format_h__ 9 | 10 | extern void signoff(const char* fmt, ...); 11 | extern void error(const char* fmt, ...); 12 | extern void error_simple(const char* fmt, ...); 13 | 14 | #endif /* !__format_h__ */ 15 | -------------------------------------------------------------------------------- /examples/test3.esh: -------------------------------------------------------------------------------- 1 | 2 | # Note: if you are operating on large lists and you don't care about 3 | # return values, you're better off using "while" instead. 4 | 5 | (define for-each 6 | ~(if ~(not-null? (rot)) 7 | ~(begin ((rot) (rot)) 8 | (for-each (cdr (l-stack)))) 9 | ())) 10 | 11 | 12 | (define starrify 13 | ~(squish * (top) *)) 14 | 15 | (print (for-each starrify foo bar baz) (nl)) 16 | 17 | -------------------------------------------------------------------------------- /examples/test4.esh: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Place these lines into your ".eshrc". 4 | # 5 | 6 | (define dir-names (hash-make)) 7 | 8 | (hash-put (dir-names) "/home/ivan/src/esh" "Esh source code") 9 | 10 | (prompt ~(push (get PWD)) 11 | ~(push (hash-get (dir-names) (top))) 12 | "[" 13 | ~(rot) 14 | ~(if ~(rot) 15 | ~(squish " => " (top)) 16 | "") 17 | "]$ " 18 | ~(null (pop)) 19 | ~(null (pop))) 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/bold.esh: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # This file demonstrates what the point of bold.c is, and some very 4 | # handy tricks with files. 5 | # 6 | # Namely, this will cause the standard error to be printed in bold red 7 | # on the terminal. 8 | # 9 | # The only problem with this is that it causes `less' to behave very 10 | # strangely, but this seems to be a bug in `less'. 11 | # 12 | 13 | (push (file-open string '')) 14 | (run (true) (top) (stderr) ~(bold)) 15 | (stderr-handler (pop)) 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/test7.esh: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # This is a sample .eshrc that implements a restricted shell. 4 | # 5 | 6 | (define restricted 7 | ~(print "Sorry, but this function has been restricted." (nl))) 8 | 9 | (define for-each 10 | ~(if ~(not-null? (rot)) 11 | ~(begin ((rot) (rot)) 12 | (for-each (cdr (l-stack)))) 13 | ())) 14 | 15 | (define restrict-command 16 | ~(if ~(not-null? (top)) 17 | ~(define (pop) (list restricted)) 18 | ())) 19 | 20 | (for-each restrict-command cd fg bg) 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The `esh` shell 2 | 3 | [![Build Status](https://drone.io/github.com/aperezdc/esh/status.png)](https://drone.io/github.com/aperezdc/esh/latest) 4 | 5 | This is `esh`, an Unix shell with LISP-like syntax created originally by 6 | Ivan Tkatchev. It could originally be found at http://slon.ttk.ru/esh/ 7 | but the site has been down for a long while. 8 | 9 | This repository contains an updated version with extra patches that will 10 | compile cleanly in recent Unix systems—with a focus on GNU/Linux. Pull 11 | requests are welcome! 12 | -------------------------------------------------------------------------------- /examples/prepend.esh: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # This script was used to prepend the header notice to every 4 | # source code file. 5 | # 6 | 7 | (define prepend 8 | ~(push (file-read (file-open file (pop)))) 9 | ~(push (squish (pop) 10 | (file-read (file-open file (top))))) 11 | ~(file-write (file-open truncate (rot)) (rot))) 12 | 13 | 14 | (define prepend-for-each 15 | ~(if ~(not-null? (rot)) 16 | ~(begin (prepend (rot) (rot)) 17 | (prepend-for-each (cdr (l-stack)))) 18 | ())) 19 | 20 | (prepend-for-each (stack)) 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/test5.esh: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Regex globber. This script accepts a regular expresion and finds all 4 | # filenames in the current directory that match it. The given command is then 5 | # run with the matching filenames as arguments. 6 | # 7 | 8 | (define converter 9 | ~(run-simple (l-stack))) 10 | 11 | (if ~(typecheck "ss" (stack)) 12 | ~(print "Usage: (convert )" (nl)) 13 | ~(converter (rot) (split 14 | (gobble (standard) 15 | ~(/bin/ls -1) 16 | (list grep (rot)))))) 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/test6.esh: -------------------------------------------------------------------------------- 1 | 2 | (eval ~(if ~(false) 3 | ~(print "Yes, lev. 1" (nl)) 4 | ~(if ~(false) 5 | ~(print "Yes, lev. 2" (nl)) 6 | ~(if ~(false) 7 | ~(print "Yes, lev. 3" (nl)) 8 | ~(if ~(false) 9 | ~(print "Yes, lev. 4" (nl)) 10 | ~(print "No." (nl))))))) 11 | 12 | 13 | (eval ~(if ~(false) 14 | ~(print "Yes, lev. 1" (nl)) 15 | ~(begin 16 | (if ~(false) 17 | ~(print "Yes, lev. 2" (nl)) 18 | ~(print "No." (nl))) 19 | (if ~(false) 20 | ~(print "Yes, lev. 2 <2>" (nl)) 21 | ~(print "No. <2>" (nl)))))) 22 | -------------------------------------------------------------------------------- /job.h: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | #ifndef __job_h__ 8 | #define __job_h__ 9 | 10 | #include 11 | 12 | #define JOB_RUNNING 0 13 | #define JOB_STOPPED 1 14 | #define JOB_DEAD 2 15 | 16 | typedef struct job_t job_t; 17 | 18 | struct job_t { 19 | pid_t pgid; 20 | pid_t last_pid; 21 | char* name; 22 | 23 | char status; 24 | char value; 25 | struct termios terminal_modes; 26 | }; 27 | 28 | #endif /* !__job_h__ */ 29 | -------------------------------------------------------------------------------- /gc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | #ifndef __gc_h__ 8 | #define __gc_h__ 9 | 10 | typedef struct gc_node gc_node; 11 | 12 | struct gc_node { 13 | void* data; 14 | char* where; 15 | gc_node* next; 16 | }; 17 | 18 | 19 | extern void* gc_alloc(size_t size, char* where); 20 | extern void gc_inc_ref(void* ptr); 21 | extern void gc_add_ref(void* ptr, int add); 22 | extern int gc_refs(void* ptr); 23 | extern void gc_free(void* ptr); 24 | extern void gc_diagnostics(void); 25 | 26 | #endif /* !__gc_h__ */ 27 | -------------------------------------------------------------------------------- /PATCH: -------------------------------------------------------------------------------- 1 | 2 | Hello, 3 | 4 | I believe you need the following patch to esh-0.8: 5 | 6 | --- esh.c.orig Thu May 20 14:13:25 1999 7 | +++ esh.c Thu May 20 14:22:34 1999 8 | @@ -657,7 +656,9 @@ 9 | 10 | void job_wait(job_t* job) { 11 | int tmp; 12 | + sig_t oldsig; 13 | 14 | + oldsig = signal(SIGCHLD, SIG_DFL); 15 | if (interactive) { 16 | waitpid(job->last_pid, &tmp, WUNTRACED); 17 | 18 | @@ -676,6 +677,7 @@ 19 | } else { 20 | waitpid(job->last_pid, &tmp, WUNTRACED); 21 | } 22 | + signal(SIGCHLD, oldsig); 23 | } 24 | 25 | 26 | Otherwise, show_status() is likely to be called twice for 27 | each process run in the foreground. 28 | 29 | Jacques Vidrine / n@nectar.cc / nectar@FreeBSD.org 30 | 31 | 32 | -------------------------------------------------------------------------------- /format.c: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | void signoff(const char* fmt, ...) { 13 | va_list args; 14 | 15 | va_start(args, fmt); 16 | vprintf(fmt, args); 17 | va_end(args); 18 | 19 | printf("\n"); 20 | exit(EXIT_SUCCESS); 21 | } 22 | 23 | 24 | void error_simple(const char* fmt, ...) { 25 | va_list args; 26 | 27 | va_start(args, fmt); 28 | vfprintf(stderr, fmt, args); 29 | va_end(args); 30 | } 31 | 32 | 33 | void error(const char* fmt, ...) { 34 | va_list args; 35 | 36 | va_start(args, fmt); 37 | vfprintf(stderr, fmt, args); 38 | va_end(args); 39 | 40 | fprintf(stderr, "\n"); 41 | } 42 | -------------------------------------------------------------------------------- /read.h: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | #ifndef __read_h__ 8 | #define __read_h__ 9 | 10 | extern hash_table* defines; 11 | extern hash_table* builtins; 12 | 13 | extern int blank(char); 14 | extern int openparen(char); 15 | extern int closeparen(char); 16 | extern int separator(char); 17 | extern int redirect_in(char); 18 | extern int redirect_out(char); 19 | extern int quote(char); 20 | extern int literal(char); 21 | extern int delaysym(char); 22 | extern int comment(char); 23 | extern int special(char); 24 | 25 | extern char* dynamic_strcpy(char*); 26 | 27 | extern void read_init(void); 28 | extern char* read_read(char* prompt); 29 | extern void read_done(void); 30 | 31 | #endif /* !__read_h__ */ 32 | -------------------------------------------------------------------------------- /examples/gaudy.esh: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 4 | # Run the two commands in the background, in parallel. 5 | # 6 | 7 | (define print-color-piped 8 | ~(run (true) 9 | (file-open string (squish (top) (nl))) 10 | (standard) 11 | (list 'bold' 'none' (top))) 12 | 13 | ~(run (true) 14 | (file-open string (squish 'bold-' (top) (nl))) 15 | (standard) 16 | (list 'bold' 'bold' (top)))) 17 | 18 | # 19 | # Run the two commands in the foreground, sequentially. 20 | # 21 | 22 | (define print-color-simple 23 | ~(run-simple (list 'echo' (top)) 24 | (list 'bold' 'none' (top))) 25 | 26 | ~(run-simple (list 'echo' (squish 'bold-' (top))) 27 | (list 'bold' 'bold' (top)))) 28 | 29 | 30 | 31 | (print (nl) 'Sequential execution: ' (nl)) 32 | (for-each print-color-simple ~(black red green yellow blue magenta cyan white)) 33 | 34 | (print (nl) 'Parallel execution: ' (nl)) 35 | (for-each print-color-piped ~(black red green yellow blue magenta cyan white)) 36 | 37 | (run-simple ~(sleep 100)) 38 | -------------------------------------------------------------------------------- /examples/pipes.esh: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Here's a simple demonstration of interprocess communication. 4 | # It is a double-ended pipe, with 'sort' in between as a filter. 5 | # 6 | # Note that the pipe is asynchronous! Unless you have some sort 7 | # of protocol to guide you, you don't know for sure if the subprocess 8 | # is done writing or merely taking a long time to finish. 9 | # 10 | # That's why we need to wait a bit before reading back from the pipe -- 11 | # 'sort' needs a bit of time to finish it's job. 12 | # 13 | # Also, this script would have been much easier and safer had we used 14 | # "gobble" instead of "run". 15 | 16 | (push " 17 | Foo 18 | Bar 19 | baz 20 | Zoop 21 | Goop 22 | whee 23 | you 24 | we 25 | arg 26 | Grunt 27 | abc") 28 | 29 | (push (file-open s '')) 30 | 31 | # Alternatively, simply use this instead of "run": 32 | # (push (gobble (file-open s (rot)) ~(sort))) 33 | 34 | (run (true) (file-open s (rot)) (rot) ~(sort)) 35 | (wait 1) 36 | (push (file-read (pop))) 37 | 38 | (print 'Sorted output:' (nl) (top) (nl)) 39 | 40 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | (Note: you can download a binary RPM for RedHat from 2 | ftp://ftp.falsehope.com/pub/esh/ 3 | Thanks to Ryan Weaver for it.) 4 | 5 | 6 | 1. Edit the Makefile to set compilation flags and header locations. 7 | Pay attention to the "INC=..." and "LIB=..." lines; that's where you 8 | put the location of your readline library. 9 | 10 | Note: If you get linking errors, something along the lines of 11 | "tputs" and "tgetflag" missing, try compiling with "-ltermcap" in the 12 | "LIB=" line. This seems to be an idiosyncracy of readline -- if someone 13 | can clue me in why some compilations of readline need this, I'll be 14 | greatful. 15 | 16 | Note that the size of the executable triples on my Linux/glibc 17 | system when compiled with debugging, so you might want to turn it off. 18 | 19 | 2. Type "make". 20 | 21 | 3. Copy "esh" to anywhere you want to install the shell. The shell does 22 | not depend on any other files. 23 | 24 | 4. The directory "emacs" contains an emacs editing mode for shell scripts. 25 | The directory "examples" contains some example shell scripts. 26 | The directory "doc" has the info and html documentation. 27 | 28 | -------------------------------------------------------------------------------- /read-stdio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "common.h" 14 | #include "gc.h" 15 | #include "list.h" 16 | #include "hash.h" 17 | #include "read.h" 18 | 19 | 20 | void read_init(void) { 21 | /* Nothing. */ 22 | } 23 | 24 | char* read_read(char* prompt) { 25 | static int interactive = -1; 26 | 27 | int len = 80; 28 | int i = 0; 29 | char* ret = (char*)gc_alloc(sizeof(char) * len, "read_read"); 30 | char foo = 0; 31 | 32 | if (interactive < 0) { 33 | interactive = isatty(STDIN_FILENO); 34 | } 35 | 36 | if (interactive) { 37 | printf(prompt); 38 | fflush(stdout); 39 | } 40 | 41 | while (foo != '\n') { 42 | if (read(STDIN_FILENO, &foo, 1) <= 0) { 43 | gc_free(ret); 44 | return NULL; 45 | } 46 | 47 | if (i >= len-2) { 48 | char* tmp = (char*)gc_alloc(sizeof(char) * len * 2, "read_read"); 49 | 50 | len *= 2; 51 | 52 | strcpy(tmp, ret); 53 | gc_free(ret); 54 | ret = tmp; 55 | } 56 | 57 | ret[i] = foo; 58 | i++; 59 | } 60 | 61 | ret[i] = '\0'; 62 | 63 | return ret; 64 | 65 | } 66 | 67 | void read_done(void) { 68 | /* Nothing. */ 69 | } 70 | -------------------------------------------------------------------------------- /esh.h: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | #ifndef __esh_h__ 8 | #define __esh_h__ 9 | 10 | extern int interactive; 11 | extern int exception_flag; 12 | extern int stderr_handler_fd; 13 | 14 | extern hash_table* aliases; 15 | extern hash_table* defines; 16 | extern hash_table* builtins; 17 | extern list* jobs; 18 | extern list* prompt; 19 | extern list* stack; 20 | extern list* ls_true; 21 | extern list* ls_false; 22 | extern list* ls_stdio; 23 | extern list* ls_stderr; 24 | extern list* ls_void; 25 | extern char** environ; 26 | 27 | extern char* syntax_blank; 28 | 29 | extern char* dynamic_strcpy(char* chr); 30 | 31 | extern char* file_read(int fd); 32 | extern void file_write(int fd, char* data); 33 | 34 | extern char next_token(char* input, int* i, char** value, int* len); 35 | extern list* parse_builtin(char* input, int* len, int liter, int delay); 36 | extern list* parse_split(char* input); 37 | 38 | extern pid_t do_pipe(int f_src, int f_out, list* ls, int bg, int destruc); 39 | extern list* do_builtin(list* ls); 40 | extern void do_file(char* file, int do_error); 41 | 42 | extern void ls_print(list* ls); 43 | extern char* ls_strcat(list* ls); 44 | 45 | extern void job_foreground(job_t* job); 46 | extern void job_background(job_t* job); 47 | 48 | #endif /* !__esh_h__ */ 49 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Flags to the compiler: 3 | # 4 | # MEM_DEBUG Check for memory leaks. 5 | # 6 | #DEFINES += MEM_DEBUG 7 | 8 | # Where your readline library is. 9 | # You can compile with a "gets()" replacement instead. 10 | 11 | #READ = read-stdio 12 | READ ?= read-rl 13 | 14 | INCLUDES += /usr/include/readline 15 | LIB += -lncurses -lreadline 16 | 17 | # No need to change things from this point onwards 18 | 19 | CFLAGS += -Wall 20 | DEFINES := $(patsubst %,-D%,$(DEFINES)) 21 | INCLUDES := $(patsubst %,-I%,$(INCLUDES)) 22 | CPPFLAGS += $(DEFINES) $(INCLUDES) 23 | 24 | OBJS := list.o hash.o builtins.o esh.o format.o gc.o $(READ).o 25 | VERS := 0.8.5 26 | 27 | all: esh 28 | 29 | bold: bold.o 30 | 31 | esh: $(OBJS) 32 | $(CC) $(CFLAGS) $(LDFLAGS) $^ $(LIB) -o esh 33 | 34 | clean: 35 | $(RM) $(OBJS) bold.o esh bold 36 | 37 | dist: 38 | git archive --prefix=esh-$(VERS)/ v$(VERS) | xz -9c > esh-$(VERS).tar.xz 39 | 40 | .PHONY: dist 41 | 42 | depend: 43 | makedepend -Y $(OBJS:.o=.c) read*.c 44 | 45 | .PHONY: depend 46 | 47 | 48 | doc: doc/esh.info doc/esh.html 49 | doc/esh.html: doc/esh.texi 50 | texi2html --subdir=doc/ $< 51 | 52 | clean-doc: 53 | $(RM) doc/esh.html doc/esh_toc.html doc/esh.info 54 | 55 | .PHONY: doc clean-doc 56 | 57 | mrproper: clean clean-doc 58 | 59 | # DO NOT DELETE 60 | 61 | list.o: gc.h list.h hash.h 62 | hash.o: gc.h list.h hash.h 63 | builtins.o: common.h format.h list.h gc.h hash.h job.h esh.h builtins.h 64 | builtins.o: read.h 65 | esh.o: common.h format.h list.h gc.h hash.h job.h builtins.h read.h 66 | gc.o: gc.h format.h 67 | read-stdio.o: common.h gc.h list.h hash.h read.h 68 | read-rl.o: common.h gc.h list.h hash.h read.h 69 | read-stdio.o: common.h gc.h list.h hash.h read.h 70 | -------------------------------------------------------------------------------- /list.h: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | #ifndef __list_h__ 8 | #define __list_h__ 9 | 10 | /* 11 | * A very simple linked-list implementation. 12 | * 13 | * Pitfalls: 14 | * 15 | * + You cannot delete elements from the list. 16 | * + The list functions are lisp-like, i.e. "ls_cons" returns a list. 17 | * + Please don't use the internals of the "list" structure. 18 | * + You can accidentaly reverse the arguments to "ls_cons" 19 | * and not get a warning from the compiler! 20 | * + The list does not do any memory management. Nothing is copied before 21 | * insertion, and "ls_free" does not free the data in the list. 22 | * "ls_free_all" is provided as a convinience -- it will free 23 | * the data before deleting the list node. 24 | * + "ls_copy" and "ls_free_all" make lots of assumptions about type 25 | * information. 26 | */ 27 | 28 | #define TYPE_STRING 0 29 | #define TYPE_LIST 1 30 | #define TYPE_HASH 2 31 | #define TYPE_BOOL 3 32 | #define TYPE_FD 4 33 | #define TYPE_PROC 5 34 | #define TYPE_VOID 6 35 | 36 | #define FLAG_NONE 0 37 | 38 | typedef struct list list; 39 | 40 | struct list { 41 | void* data; 42 | list* next; 43 | char type; 44 | char flag; 45 | }; 46 | 47 | extern void ls_free(list* ls); 48 | extern void ls_free_all(list* ls); 49 | extern void ls_free_shallow(list* ls); 50 | extern list* ls_reverse(list* ls); 51 | extern list* ls_cons(void* data, list* ls); 52 | extern list* ls_next(list* ls); 53 | extern void* ls_data(list* ls); 54 | extern void ls_type_set(list* ls, char type); 55 | extern char ls_type(list* ls); 56 | extern void ls_flag_set(list* ls, char flag); 57 | extern char ls_flag(list* ls); 58 | extern list* ls_copy(list* ls); 59 | 60 | #endif /* !__list_h__ */ 61 | -------------------------------------------------------------------------------- /bold.c: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | 8 | #include 9 | #include 10 | 11 | char colorspec[10]; 12 | 13 | char* colors[] = { 14 | "black", "30", 15 | "red", "31", 16 | "green", "32", 17 | "yellow", "33", 18 | "blue", "34", 19 | "magenta", "35", 20 | "cyan", "36", 21 | "white", "37", 22 | NULL, NULL 23 | }; 24 | 25 | char* flags[] = { 26 | "none", "00", 27 | "bold", "01", 28 | "underscore", "04", 29 | "blink", "05", 30 | "reverse", "07", 31 | "concealed", "08", 32 | NULL, NULL 33 | }; 34 | 35 | int main(int argc, char** argv) { 36 | char* ss = "\033[%s;%sm"; 37 | char* flag = NULL; 38 | char* color = NULL; 39 | char* se = "\033[00m"; 40 | int chr; 41 | int i = 0; 42 | 43 | if (argc != 3) { 44 | printf("Usage: %s \n" 45 | "Where is one of:\n" 46 | "black red green yellow blue magenta cyan white\n" 47 | "And is one of:\n" 48 | "none bold underscore blink reverse concealed\n", 49 | argv[0]); 50 | exit(EXIT_FAILURE); 51 | } 52 | 53 | while (flags[i]) { 54 | if (strcmp(flags[i], argv[1]) == 0) { 55 | flag = flags[i+1]; 56 | } 57 | 58 | i += 2; 59 | } 60 | 61 | i = 0; 62 | 63 | while (colors[i]) { 64 | if (strcmp(colors[i], argv[2]) == 0) { 65 | color = colors[i+1]; 66 | } 67 | 68 | i += 2; 69 | } 70 | 71 | if (!flag || !color) { 72 | fprintf(stderr, "bold: either color or flag argument is wrong.\n"); 73 | return EXIT_FAILURE; 74 | } 75 | 76 | sprintf(colorspec, ss, flag, color); 77 | 78 | 79 | while (1) { 80 | chr = getchar(); 81 | 82 | if (chr == EOF) break; 83 | 84 | printf("%s%c%s", colorspec, chr, se); 85 | fflush(stdout); 86 | } 87 | 88 | return EXIT_SUCCESS; 89 | } 90 | -------------------------------------------------------------------------------- /hash.h: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | #ifndef __hash_h__ 8 | #define __hash_h__ 9 | 10 | /* 11 | * A very simple hash table implementation, using "buckets". 12 | * 13 | * Pitfalls: 14 | * 15 | * + You cannot delete elements from the cache. 16 | * + Calling "hash_put" or "hash_get" before "hash_init" likely 17 | * means a segfault. 18 | * + "hash_init" should always be called. Pass a NULL as the second 19 | * argument if you don't want any initial data. 20 | * + Calling"hash_init" more than once on the same hash table 21 | * constitutes a giant memory leak! 22 | * + The argument to "hash_init" is terminated with a { NULL, NULL } 23 | * + The hash table does not do any memory management -- i.e. 24 | * the arguments to "hash_put" are not copied before they are inserted 25 | * into the hash table. 26 | * + However, there is an ugly loophole in the code around the above 27 | * pitfall, but only if the hash data is lists! 28 | * + "hash_put" returns the previous data with the same key, if any. 29 | * It is your responsibility to free this data, if necessary. 30 | * + "hash_inc_ref" and "hash_put_inc_ref" assume that the hash table 31 | * holds only lists. 32 | */ 33 | 34 | typedef struct hash_entry hash_entry; 35 | typedef list** hash_table; 36 | 37 | struct hash_entry { 38 | char* key; 39 | void* data; 40 | }; 41 | 42 | extern void* hash_put(hash_table* t, char* key, void* data); 43 | extern void* hash_put_inc_ref(hash_table* t, char* key, void* data); 44 | extern void hash_init(hash_table* t, hash_entry data[]); 45 | extern void* hash_get(hash_table* t, char* key); 46 | 47 | extern void hash_free(hash_table* t, 48 | void (*func)()); 49 | 50 | extern void hash_inc_ref(hash_table* t); 51 | extern list* hash_keys(hash_table* t); 52 | 53 | #endif /* !__hash_h__ */ 54 | -------------------------------------------------------------------------------- /gc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | 8 | #include 9 | #include 10 | 11 | #include "gc.h" 12 | #include "format.h" 13 | 14 | /* 15 | * A simplistic garbage collection mechanism. It requires that the C 16 | * program call gc_free whenever an object should be deleted. As such, 17 | * this cannot really be called garbage collection, though it does serve 18 | * a purpose as a central repository of all allocated memory. 19 | */ 20 | 21 | 22 | int __gc_alloc = 0; 23 | 24 | 25 | void* gc_alloc(size_t size, char* where) { 26 | void* ret = malloc(size + sizeof(int)); 27 | 28 | if (!ret) { 29 | error("esh: could not allocate memory."); 30 | exit(EXIT_FAILURE); 31 | } 32 | 33 | ((int*)ret)[0] = 1; 34 | 35 | __gc_alloc++; 36 | 37 | return ret + sizeof(int); 38 | } 39 | 40 | 41 | inline void gc_inc_ref(void* ptr) { 42 | int* ref = (int*)(ptr - sizeof(int)); 43 | 44 | if ((*ref) <= 0) { 45 | error("esh: refcount is corrupted in gc_inc_ref."); 46 | exit(EXIT_FAILURE); 47 | } 48 | 49 | (*ref)++; 50 | __gc_alloc++; 51 | } 52 | 53 | void gc_add_ref(void* ptr, int add) { 54 | int* ref = (int*)(ptr - sizeof(int)); 55 | 56 | if ((*ref) <= 0) { 57 | error("esh: refcount is corrupted in gc_add_ref"); 58 | exit(EXIT_FAILURE); 59 | } 60 | 61 | (*ref) += add; 62 | __gc_alloc += add; 63 | 64 | if ((*ref) <= 0) { 65 | error("esh: tried to set an invalid ref count."); 66 | exit(EXIT_FAILURE); 67 | } 68 | } 69 | 70 | 71 | inline int gc_refs(void* ptr) { 72 | int* ref = (int*)(ptr - sizeof(int)); 73 | 74 | if ((*ref) <= 0) { 75 | error("esh: refcount is corrupted in gc_refs."); 76 | exit(EXIT_FAILURE); 77 | } 78 | 79 | return (*ref); 80 | } 81 | 82 | 83 | inline void gc_free(void* ptr) { 84 | int* ref = (int*)(ptr - sizeof(int)); 85 | 86 | if ((*ref) <= 0) { 87 | error("esh: refcount is corrupted in gc_free."); 88 | exit(EXIT_FAILURE); 89 | } 90 | 91 | (*ref)--; 92 | __gc_alloc--; 93 | 94 | if (!(*ref)) { 95 | free(ptr - sizeof(int)); 96 | } 97 | } 98 | 99 | 100 | void gc_diagnostics(void) { 101 | printf("\nAllocated chunks: %d\n", __gc_alloc); 102 | } 103 | 104 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 0.1.0: First public release. 2 | 0.2.0: Never released. 3 | 0.3.0: Created explicit "boolean" type. 4 | Changed all predicates to use the boolean type. 5 | Created a "file" type. 6 | Changed the file I/O functions as well as "run" to 7 | use file objects. 8 | Added some convenience commands. 9 | Fixed bugs. 10 | Wrote an emacs mode for editing shell scripts. 11 | Rewrote the Makefile. 12 | 0.3.5: Added the "stderr" and "stderr-handler" commands. 13 | Fixed bugs. 14 | 0.3.6: Bugfixes. 15 | 0.4.0: More bugfixes. 16 | 0.4.5: Never released. 17 | 0.5.0: "run" now returns an object of type "process." 18 | Added the "alive?" command. 19 | Changed the refcounter from the disgusting O(n) list 20 | implementation to an O(1) one. 21 | Added the "while" command as an efficiency hack. 22 | 0.6.0: The backslash quote has been removed. 23 | Use the tilde quote instead. 24 | Now includes a very simple replacement for readline. 25 | (This means that readline is no longer required.) 26 | Bugfixes and usability features. 27 | Added the "alias-hash" command and the "car-l" command. 28 | Expanded the manual. 29 | Added the "chop!", "chop-nl!", "match", and "reverse" commands. 30 | 0.6.5: Bugfixes. 31 | Added the "chars", "filter", "clone", and "substring?" commands. 32 | 0.7.0: "run" and "run-simple" now return the exit status of the 33 | pipeline if the job was launched in the foreground. 34 | "chop!" and "chop-nl!" now return their arguments. 35 | Added "begin-last", which is like the "begin" in Scheme. 36 | Added the "<" and ">" commands. 37 | 38 | 0.7.5: Bugfixes. 39 | The "split" command can now split on any arbitrary set of field 40 | separators. 41 | Added the "void" command and type. 42 | Added the "repeat" command. 43 | 44 | 0.8: Several small tweaks. 45 | 46 | 0.8.5: Implemented tab-completion for commands. 47 | Fixed the readline dependency in a sane way. 48 | "run" and "run-simple" now work in a more sane way -- they now evaluate nested lists that 49 | are part of the argument list. That is, something like 50 | "(run-simple ~(echo (top))) will work as expected. 51 | 52 | -------------------------------------------------------------------------------- /read-rl.c: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "common.h" 15 | #include "gc.h" 16 | #include "list.h" 17 | #include "hash.h" 18 | #include "read.h" 19 | 20 | 21 | static int rl_literal_newline(int count, int key) { 22 | rl_insert_text("\n"); 23 | 24 | return 0; 25 | } 26 | 27 | static char* dynamic_strcpy_malloc(char* str) { 28 | char* tmp = (char*)malloc(sizeof(char) * (strlen(str) + 1)); 29 | strcpy(tmp, str); 30 | 31 | return tmp; 32 | } 33 | 34 | static char* rl_find_builtin(const char* word, int state) { 35 | static int len = 0; 36 | static list* hash_ls1 = NULL; 37 | static list* hash_ls2 = NULL; 38 | 39 | static int which_hash = 0; 40 | static list* iter = NULL; 41 | 42 | 43 | if (state == -1) { 44 | ls_free_all(hash_ls1); 45 | ls_free_all(hash_ls2); 46 | 47 | return NULL; 48 | 49 | } else if (state == 0) { 50 | len = strlen(word); 51 | 52 | ls_free_all(hash_ls1); 53 | ls_free_all(hash_ls2); 54 | 55 | hash_ls1 = hash_keys(builtins); 56 | hash_ls2 = hash_keys(defines); 57 | 58 | which_hash = 0; 59 | iter = hash_ls1; 60 | } 61 | 62 | while (1) { 63 | if (!iter) { 64 | if (!which_hash) { 65 | which_hash = 1; 66 | iter = hash_ls2; 67 | 68 | } else { 69 | return NULL; 70 | } 71 | } 72 | 73 | if (strncmp(ls_data(iter), word, len) == 0) { 74 | char* ret = dynamic_strcpy_malloc(ls_data(iter)); 75 | 76 | iter = ls_next(iter); 77 | 78 | return ret; 79 | } 80 | 81 | iter = ls_next(iter); 82 | 83 | } 84 | } 85 | 86 | static char** rl_esh_completion(const char* word, int start, int end) { 87 | 88 | /* If the first non-whitespace character before the word is an 89 | * open parentheses, then complete a command. Otherwise, fallback to 90 | * the default completer. */ 91 | 92 | if (start) { 93 | start--; 94 | 95 | while (start && blank(rl_line_buffer[start])) { 96 | start--; 97 | } 98 | } 99 | 100 | if (openparen(rl_line_buffer[start])) { 101 | return rl_completion_matches(word, (rl_compentry_func_t *)rl_find_builtin); 102 | 103 | } else { 104 | return NULL; 105 | } 106 | } 107 | 108 | void read_init(void) { 109 | rl_bind_key('\012', rl_literal_newline); 110 | 111 | /* rl_catch_signals = 0; */ 112 | rl_attempted_completion_function = (rl_completion_func_t *)rl_esh_completion; 113 | } 114 | 115 | char* read_read(char* prompt) { 116 | char* line = readline(prompt); 117 | char* ret; 118 | 119 | if (!line) return NULL; 120 | 121 | if (*line) { 122 | add_history(line); 123 | } 124 | 125 | ret = dynamic_strcpy(line); 126 | 127 | free(line); 128 | 129 | return ret; 130 | } 131 | 132 | void read_done(void) { 133 | 134 | /* Free the completion buffers. */ 135 | rl_find_builtin("", -1); 136 | } 137 | -------------------------------------------------------------------------------- /list.c: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | 8 | #include 9 | #include 10 | 11 | #include "gc.h" 12 | #include "list.h" 13 | #include "hash.h" 14 | 15 | extern int stderr_handler_fd; 16 | 17 | list* ls_cons(void* data, list* ls) { 18 | list* nw = (list*)gc_alloc(sizeof(list), "ls_cons"); 19 | 20 | nw->data = data; 21 | nw->next = ls; 22 | nw->type = TYPE_STRING; 23 | nw->flag = FLAG_NONE; 24 | return nw; 25 | } 26 | 27 | void ls_type_set(list* ls, char type) { 28 | ls->type = type; 29 | } 30 | 31 | inline char ls_type(list* ls) { 32 | return ls->type; 33 | } 34 | 35 | void ls_flag_set(list* ls, char flag) { 36 | ls->flag = flag; 37 | } 38 | 39 | inline char ls_flag(list* ls) { 40 | return ls->flag; 41 | } 42 | 43 | void ls_free(list* ls) { 44 | if (!ls) return; 45 | 46 | if (ls->next) { 47 | ls_free(ls->next); 48 | } 49 | 50 | if (ls->type == TYPE_LIST) { 51 | ls_free(ls->data); 52 | } 53 | 54 | gc_free(ls); 55 | } 56 | 57 | void ls_free_all(list* ls) { 58 | if (!ls) return; 59 | 60 | if (ls->next) { 61 | ls_free_all(ls->next); 62 | } 63 | 64 | switch (ls->type) { 65 | case TYPE_LIST: 66 | ls_free_all(ls->data); 67 | break; 68 | 69 | case TYPE_STRING: 70 | case TYPE_PROC: 71 | if (ls->data) 72 | gc_free(ls->data); 73 | break; 74 | 75 | case TYPE_FD: 76 | if (gc_refs(ls->data) == 1) { 77 | int* fd = ls->data; 78 | 79 | if (fd[0] != STDIN_FILENO && 80 | fd[0] != stderr_handler_fd) { 81 | 82 | close(fd[0]); 83 | } 84 | 85 | if (fd[1] != STDOUT_FILENO && 86 | fd[1] != STDERR_FILENO && 87 | fd[1] != fd[0] && 88 | fd[1] != stderr_handler_fd) { 89 | 90 | close(fd[1]); 91 | } 92 | } 93 | 94 | gc_free(ls->data); 95 | break; 96 | 97 | case TYPE_HASH: 98 | hash_free(ls->data, ls_free_all); 99 | gc_free(ls->data); 100 | break; 101 | 102 | case TYPE_VOID: 103 | case TYPE_BOOL: 104 | break; 105 | } 106 | 107 | gc_free(ls); 108 | } 109 | 110 | void ls_free_shallow(list* ls) { 111 | if (!ls) return; 112 | 113 | if (ls->next) { 114 | ls_free_shallow(ls->next); 115 | } 116 | 117 | gc_free(ls); 118 | } 119 | 120 | list* ls_reverse(list* ls) { 121 | list* ret = NULL; 122 | list* i = ls; 123 | 124 | while (i) { 125 | ret = ls_cons(i->data, ret); 126 | ret->type = i->type; 127 | ret->flag = i->flag; 128 | i = i->next; 129 | } 130 | 131 | ls_free_shallow(ls); 132 | 133 | return ret; 134 | } 135 | 136 | inline list* ls_next(list* ls) { 137 | return ls->next; 138 | } 139 | 140 | inline void* ls_data(list* ls) { 141 | return ls->data; 142 | } 143 | 144 | 145 | list* ls_copy(list* arg) { 146 | list* ret = arg; 147 | 148 | for (; arg != NULL; arg = ls_next(arg)) { 149 | 150 | gc_inc_ref(arg); 151 | 152 | switch (ls_type(arg)) { 153 | case TYPE_LIST: 154 | ls_copy(ls_data(arg)); 155 | break; 156 | 157 | case TYPE_STRING: 158 | case TYPE_FD: 159 | case TYPE_PROC: 160 | gc_inc_ref(ls_data(arg)); 161 | break; 162 | 163 | case TYPE_HASH: 164 | hash_inc_ref(ls_data(arg)); 165 | gc_inc_ref(ls_data(arg)); 166 | break; 167 | 168 | case TYPE_VOID: 169 | case TYPE_BOOL: 170 | break; 171 | } 172 | } 173 | 174 | return ret; 175 | } 176 | -------------------------------------------------------------------------------- /hash.c: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | 8 | #include 9 | #include 10 | 11 | #include "gc.h" 12 | #include "list.h" 13 | #include "hash.h" 14 | 15 | #define HASH_SIZE 1024 16 | 17 | 18 | /* 19 | * A simple but braindead hasher function. 20 | */ 21 | static int hash(char* key) { 22 | int i = 0, j = 0; 23 | 24 | if (!key) return 0; 25 | 26 | while (key[j]) { 27 | i += key[j++]; 28 | } 29 | 30 | return i % HASH_SIZE; 31 | } 32 | 33 | 34 | /* 35 | * Warning: If "doinc" is true, this function assumes that the hash data is 36 | * lists! 37 | */ 38 | 39 | static void* hash_put_aux(hash_table* _hash_array, char* key, void* data, 40 | int doinc) { 41 | hash_entry* hs_ent; 42 | void* ret; 43 | 44 | int idx = hash(key); 45 | 46 | list* bucket = (*_hash_array)[idx]; 47 | list* iter; 48 | 49 | int refs = gc_refs((*_hash_array)); 50 | int i; 51 | 52 | for (iter = bucket; iter != NULL; iter = ls_next(iter)) { 53 | if (strcmp(((hash_entry*)ls_data(iter))->key, key) == 0) { 54 | 55 | hs_ent = (hash_entry*)ls_data(iter); 56 | 57 | ret = hs_ent->data; 58 | 59 | hs_ent->data = data; 60 | 61 | if (doinc) { 62 | for (i = 1; i < refs; i++) { 63 | hs_ent->data = ls_copy(hs_ent->data); 64 | ls_free_all(ret); 65 | } 66 | } 67 | 68 | return ret; 69 | } 70 | } 71 | 72 | hs_ent = (hash_entry*)gc_alloc(sizeof(hash_entry), "hash_put"); 73 | 74 | hs_ent->key = key; 75 | hs_ent->data = data; 76 | 77 | (*_hash_array)[idx] = ls_cons(hs_ent, bucket); 78 | 79 | if (doinc) { 80 | gc_add_ref((*_hash_array)[idx], refs-1); 81 | gc_add_ref(hs_ent, refs-1); 82 | 83 | gc_add_ref(hs_ent->key, refs-1); 84 | 85 | for (i = 1; i < refs; i++) { 86 | hs_ent->data = ls_copy(hs_ent->data); 87 | } 88 | } 89 | 90 | return NULL; 91 | } 92 | 93 | 94 | inline void* hash_put(hash_table* tab, char* key, void* data) { 95 | return hash_put_aux(tab, key, data, 0); 96 | } 97 | 98 | inline void* hash_put_inc_ref(hash_table* tab, char* key, void* data) { 99 | return hash_put_aux(tab, key, data, 1); 100 | } 101 | 102 | 103 | void* hash_get(hash_table* _hash_array, char* key) { 104 | int idx = hash(key); 105 | 106 | list* bucket = (*_hash_array)[idx]; 107 | list* iter; 108 | 109 | for (iter = bucket; iter != NULL; iter = ls_next(iter)) { 110 | if (strcmp(((hash_entry*)ls_data(iter))->key, key) == 0) 111 | return ((hash_entry*)ls_data(iter))->data; 112 | } 113 | 114 | return NULL; 115 | } 116 | 117 | /* 118 | * Uglification alert. 119 | */ 120 | static char* dynamic_strcpy(char* str) { 121 | char* tmp = (char*)gc_alloc(sizeof(char) * (strlen(str) + 1), 122 | "hash.c:dynamic_strcpy"); 123 | 124 | strcpy(tmp, str); 125 | 126 | return tmp; 127 | } 128 | 129 | 130 | void hash_init(hash_table* _hash_array, hash_entry data[]) { 131 | int i; 132 | 133 | (*_hash_array) = (list**)gc_alloc(sizeof(list*) * HASH_SIZE, 134 | "hash_init"); 135 | 136 | for (i = 0; i < HASH_SIZE; i++) 137 | (*_hash_array)[i] = NULL; 138 | 139 | i = 0; 140 | 141 | if (!data) return; 142 | 143 | while (data[i].key != NULL) { 144 | hash_put(_hash_array, dynamic_strcpy(data[i].key), data[i].data); 145 | i++; 146 | } 147 | } 148 | 149 | void hash_free(hash_table* tab, 150 | void (*func)(void* data)) { 151 | int i; 152 | list* iter; 153 | 154 | for (i = 0; i < HASH_SIZE; i++) { 155 | for (iter = (*tab)[i]; iter != NULL; iter = ls_next(iter)) { 156 | hash_entry* he = (hash_entry*)(ls_data(iter)); 157 | 158 | gc_free(he->key); 159 | 160 | if (func) 161 | func(he->data); 162 | 163 | gc_free(he); 164 | } 165 | 166 | ls_free((*tab)[i]); 167 | 168 | } 169 | 170 | gc_free((*tab)); 171 | } 172 | 173 | 174 | /* 175 | * Warning: This function assumes that hash values are lists! 176 | */ 177 | 178 | void hash_inc_ref(hash_table* tab) { 179 | int i; 180 | list* iter; 181 | 182 | for (i = 0; i < HASH_SIZE; i++) { 183 | for (iter = (*tab)[i]; iter != NULL; iter = ls_next(iter)) { 184 | hash_entry* he = (hash_entry*)(ls_data(iter)); 185 | 186 | gc_inc_ref(iter); 187 | gc_inc_ref(he); 188 | 189 | gc_inc_ref(he->key); 190 | he->data = ls_copy(he->data); 191 | } 192 | } 193 | 194 | gc_inc_ref((*tab)); 195 | } 196 | 197 | 198 | 199 | list* hash_keys(hash_table* tab) { 200 | int i; 201 | list* iter; 202 | 203 | list* ret = NULL; 204 | 205 | for (i = 0; i < HASH_SIZE; i++) { 206 | for (iter = (*tab)[i]; iter != NULL; iter = ls_next(iter)) { 207 | hash_entry* he = (hash_entry*)(ls_data(iter)); 208 | 209 | gc_inc_ref(he->key); 210 | 211 | ret = ls_cons(he->key, ret); 212 | } 213 | } 214 | 215 | return ret; 216 | } 217 | -------------------------------------------------------------------------------- /emacs/esh-mode.el: -------------------------------------------------------------------------------- 1 | ;;; esh-mode.el --- esh script editing mode. 2 | ;;; 3 | ;;; This is a crude hack of scheme.el from the GNU emacs distribution. 4 | ;;; 5 | ;;; The new bits were added by Ivan Tkatchev. 6 | ;;; 7 | ;;; Code: 8 | 9 | (require 'lisp-mode) 10 | 11 | (defvar esh-mode-syntax-table nil "") 12 | 13 | (if (not esh-mode-syntax-table) 14 | (let ((i 0)) 15 | (setq esh-mode-syntax-table (make-syntax-table)) 16 | (set-syntax-table esh-mode-syntax-table) 17 | 18 | ;; Default is atom-constituent. 19 | (while (< i 256) 20 | (modify-syntax-entry i "_ ") 21 | (setq i (1+ i))) 22 | 23 | ;; Word components. 24 | (setq i ?0) 25 | (while (<= i ?9) 26 | (modify-syntax-entry i "w ") 27 | (setq i (1+ i))) 28 | (setq i ?A) 29 | (while (<= i ?Z) 30 | (modify-syntax-entry i "w ") 31 | (setq i (1+ i))) 32 | (setq i ?a) 33 | (while (<= i ?z) 34 | (modify-syntax-entry i "w ") 35 | (setq i (1+ i))) 36 | 37 | ;; Whitespace 38 | (modify-syntax-entry ?\t " ") 39 | (modify-syntax-entry ?\n "> ") 40 | (modify-syntax-entry ?\f " ") 41 | (modify-syntax-entry ?\r " ") 42 | (modify-syntax-entry ? " ") 43 | 44 | ;; These characters are delimiters but otherwise undefined. 45 | ;; Brackets and braces balance for editing convenience. 46 | (modify-syntax-entry ?\[ "(] ") 47 | (modify-syntax-entry ?\] ")[ ") 48 | (modify-syntax-entry ?{ "(} ") 49 | (modify-syntax-entry ?} "){ ") 50 | 51 | ;; Other atom delimiters 52 | (modify-syntax-entry ?\( "() ") 53 | (modify-syntax-entry ?\) ")( ") 54 | (modify-syntax-entry ?\# "< ") 55 | (modify-syntax-entry ?\" "\" ") 56 | (modify-syntax-entry ?\' "\" ") 57 | (modify-syntax-entry ?~ " p") 58 | (modify-syntax-entry ?$ " p"))) 59 | 60 | 61 | (defvar esh-mode-abbrev-table nil "") 62 | (define-abbrev-table 'esh-mode-abbrev-table ()) 63 | 64 | (defvar esh-imenu-generic-expression 65 | '((nil 66 | "^(define[ \n\t][ \n\t]*\\(.*\\)[ \n\t]" 1))) 67 | 68 | (defvar esh-font-lock-keywords 69 | '(("([ \t\n]*\\(define\\|eval\\|begin\\|if\\|eval!\\|and\\|or\\|not\\)[ \n\t)]" 70 | 1 font-lock-variable-name-face) 71 | ("([ \t\n]*\\(top\\|pop\\|push\\|stack\\|rot\\|l-stack\\)[ \t\n)]" 72 | 1 font-lock-keyword-face) 73 | ("(define[ \n\y][ \n\t]*\\(.*\\)[ \n\t]" 74 | 1 font-lock-warning-face) 75 | ("~\\|\\$" 76 | 0 font-lock-warning-face)) 77 | "Default expressions to highlight in esh mode.") 78 | 79 | 80 | (defun esh-mode-variables () 81 | (set-syntax-table esh-mode-syntax-table) 82 | (setq local-abbrev-table esh-mode-abbrev-table) 83 | (make-local-variable 'paragraph-start) 84 | (setq paragraph-start (concat "$\\|" page-delimiter)) 85 | (make-local-variable 'paragraph-separate) 86 | (setq paragraph-separate paragraph-start) 87 | (make-local-variable 'paragraph-ignore-fill-prefix) 88 | (setq paragraph-ignore-fill-prefix t) 89 | (make-local-variable 'fill-paragraph-function) 90 | (setq fill-paragraph-function 'lisp-fill-paragraph) 91 | ;; Adaptive fill mode gets in the way of auto-fill, 92 | ;; and should make no difference for explicit fill 93 | ;; because lisp-fill-paragraph should do the job. 94 | (make-local-variable 'adaptive-fill-mode) 95 | (setq adaptive-fill-mode nil) 96 | (make-local-variable 'indent-line-function) 97 | (setq indent-line-function 'lisp-indent-line) 98 | (make-local-variable 'parse-sexp-ignore-comments) 99 | (setq parse-sexp-ignore-comments t) 100 | (make-local-variable 'outline-regexp) 101 | (setq outline-regexp "### \\|(....") 102 | (make-local-variable 'comment-start) 103 | (setq comment-start "#") 104 | (make-local-variable 'comment-start-skip) 105 | ;; Look within the line for a # following an even number of backslashes 106 | ;; after either a non-backslash or the line beginning. 107 | (setq comment-start-skip "\\(\\(^\\|[^\\\\\n]\\)\\(\\\\\\\\\\)*\\)#+[ \t]*") 108 | (make-local-variable 'comment-column) 109 | (setq comment-column 40) 110 | (make-local-variable 'comment-indent-function) 111 | (setq comment-indent-function 'lisp-comment-indent) 112 | (make-local-variable 'parse-sexp-ignore-comments) 113 | (setq parse-sexp-ignore-comments t) 114 | (make-local-variable 'lisp-indent-function) 115 | (set lisp-indent-function 'esh-indent-function) 116 | (setq mode-line-process '("" esh-mode-line-process)) 117 | (make-local-variable 'imenu-generic-expression) 118 | (setq imenu-generic-expression esh-imenu-generic-expression) 119 | (make-local-variable 'font-lock-defaults) 120 | (setq font-lock-defaults 121 | '(esh-font-lock-keywords 122 | nil nil 123 | ((?/ . "w"))))) 124 | 125 | 126 | (defvar esh-mode-line-process "") 127 | 128 | (defvar esh-mode-map nil 129 | "Keymap for esh mode. 130 | All commands in `shared-lisp-mode-map' are inherited by this map.") 131 | 132 | (if esh-mode-map 133 | () 134 | (let ((map (make-sparse-keymap "esh"))) 135 | (setq esh-mode-map 136 | (nconc (make-sparse-keymap) shared-lisp-mode-map)) 137 | (define-key esh-mode-map "\e\t" 'lisp-complete-symbol) 138 | (define-key esh-mode-map [menu-bar] (make-sparse-keymap)) 139 | (define-key esh-mode-map [menu-bar esh] 140 | (cons "esh" map)) 141 | (define-key map [comment-region] '("Comment Out Region" . comment-region)) 142 | (define-key map [indent-region] '("Indent Region" . indent-region)) 143 | (define-key map [indent-line] '("Indent Line" . lisp-indent-line)) 144 | (put 'comment-region 'menu-enable 'mark-active) 145 | (put 'indent-region 'menu-enable 'mark-active))) 146 | 147 | 148 | ;;;###autoload 149 | (defun esh-mode () 150 | "Major mode for editing esh code. 151 | Editing commands are similar to those of lisp-mode. 152 | 153 | Commands: 154 | Delete converts tabs to spaces as it moves back. 155 | Blank lines separate paragraphs. Semicolons start comments. 156 | \\{esh-mode-map} 157 | Entry to this mode calls the value of esh-mode-hook 158 | if that value is non-nil." 159 | (interactive) 160 | (kill-all-local-variables) 161 | (esh-mode-initialize) 162 | (esh-mode-variables) 163 | (run-hooks 'esh-mode-hook)) 164 | 165 | (defun esh-mode-initialize () 166 | (use-local-map esh-mode-map) 167 | (setq major-mode 'esh-mode) 168 | (setq mode-name "esh")) 169 | 170 | (defgroup esh nil 171 | "Editing esh code" 172 | :group 'lisp) 173 | 174 | (defvar calculate-lisp-indent-last-sexp) 175 | 176 | ;; Copied from lisp-indent-function, but with gets of 177 | ;; esh-indent-{function,hook}. 178 | (defun esh-indent-function (indent-point state) 179 | (let ((normal-indent (current-column))) 180 | (goto-char (1+ (elt state 1))) 181 | (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t) 182 | (if (and (elt state 2) 183 | (not (looking-at "\\sw\\|\\s_"))) 184 | ;; car of form doesn't seem to be a a symbol 185 | (progn 186 | (if (not (> (save-excursion (forward-line 1) (point)) 187 | calculate-lisp-indent-last-sexp)) 188 | (progn (goto-char calculate-lisp-indent-last-sexp) 189 | (beginning-of-line) 190 | (parse-partial-sexp (point) 191 | calculate-lisp-indent-last-sexp 0 t))) 192 | ;; Indent under the list or under the first sexp on the same 193 | ;; line as calculate-lisp-indent-last-sexp. Note that first 194 | ;; thing on that line has to be complete sexp since we are 195 | ;; inside the innermost containing sexp. 196 | (backward-prefix-chars) 197 | (current-column)) 198 | (let ((function (buffer-substring (point) 199 | (progn (forward-sexp 1) (point)))) 200 | method) 201 | (setq method (or (get (intern-soft function) 'esh-indent-function) 202 | (get (intern-soft function) 'esh-indent-hook))) 203 | (cond ((or (eq method 'defun) 204 | (and (null method) 205 | (> (length function) 3) 206 | (string-match "\\`def" function))) 207 | (lisp-indent-defform state indent-point)) 208 | ((integerp method) 209 | (lisp-indent-specform method state 210 | indent-point normal-indent)) 211 | (method 212 | (funcall method state indent-point))))))) 213 | 214 | ;; (put 'begin 'esh-indent-function 0), say, causes begin to be indented 215 | ;; like defun if the first form is placed on the next line, otherwise 216 | ;; it is indented like any other form (i.e. forms line up under first). 217 | 218 | (put 'begin 'esh-indent-function 0) 219 | (put 'eval 'esh-indent-function 0) 220 | (put 'eval! 'esh-indent-function 0) 221 | (put 'if 'esh-indent-function 0) 222 | 223 | 224 | (provide 'esh) 225 | 226 | ;;; esh-mode.el ends here 227 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | This program is free software; you can redistribute it and/or modify 3 | it under the terms of the GNU General Public License as published by 4 | the Free Software Foundation; either version 2 of the License, or 5 | (at your option) any later version. 6 | 7 | 8 | 9 | GNU GENERAL PUBLIC LICENSE 10 | Version 2, June 1991 11 | 12 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 13 | 675 Mass Ave, Cambridge, MA 02139, USA 14 | Everyone is permitted to copy and distribute verbatim copies 15 | of this license document, but changing it is not allowed. 16 | 17 | Preamble 18 | 19 | The licenses for most software are designed to take away your 20 | freedom to share and change it. By contrast, the GNU General Public 21 | License is intended to guarantee your freedom to share and change free 22 | software--to make sure the software is free for all its users. This 23 | General Public License applies to most of the Free Software 24 | Foundation's software and to any other program whose authors commit to 25 | using it. (Some other Free Software Foundation software is covered by 26 | the GNU Library General Public License instead.) You can apply it to 27 | your programs, too. 28 | 29 | When we speak of free software, we are referring to freedom, not 30 | price. Our General Public Licenses are designed to make sure that you 31 | have the freedom to distribute copies of free software (and charge for 32 | this service if you wish), that you receive source code or can get it 33 | if you want it, that you can change the software or use pieces of it 34 | in new free programs; and that you know you can do these things. 35 | 36 | To protect your rights, we need to make restrictions that forbid 37 | anyone to deny you these rights or to ask you to surrender the rights. 38 | These restrictions translate to certain responsibilities for you if you 39 | distribute copies of the software, or if you modify it. 40 | 41 | For example, if you distribute copies of such a program, whether 42 | gratis or for a fee, you must give the recipients all the rights that 43 | you have. You must make sure that they, too, receive or can get the 44 | source code. And you must show them these terms so they know their 45 | rights. 46 | 47 | We protect your rights with two steps: (1) copyright the software, and 48 | (2) offer you this license which gives you legal permission to copy, 49 | distribute and/or modify the software. 50 | 51 | Also, for each author's protection and ours, we want to make certain 52 | that everyone understands that there is no warranty for this free 53 | software. If the software is modified by someone else and passed on, we 54 | want its recipients to know that what they have is not the original, so 55 | that any problems introduced by others will not reflect on the original 56 | authors' reputations. 57 | 58 | Finally, any free program is threatened constantly by software 59 | patents. We wish to avoid the danger that redistributors of a free 60 | program will individually obtain patent licenses, in effect making the 61 | program proprietary. To prevent this, we have made it clear that any 62 | patent must be licensed for everyone's free use or not licensed at all. 63 | 64 | The precise terms and conditions for copying, distribution and 65 | modification follow. 66 | 67 | GNU GENERAL PUBLIC LICENSE 68 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 69 | 70 | 0. This License applies to any program or other work which contains 71 | a notice placed by the copyright holder saying it may be distributed 72 | under the terms of this General Public License. The "Program", below, 73 | refers to any such program or work, and a "work based on the Program" 74 | means either the Program or any derivative work under copyright law: 75 | that is to say, a work containing the Program or a portion of it, 76 | either verbatim or with modifications and/or translated into another 77 | language. (Hereinafter, translation is included without limitation in 78 | the term "modification".) Each licensee is addressed as "you". 79 | 80 | Activities other than copying, distribution and modification are not 81 | covered by this License; they are outside its scope. The act of 82 | running the Program is not restricted, and the output from the Program 83 | is covered only if its contents constitute a work based on the 84 | Program (independent of having been made by running the Program). 85 | Whether that is true depends on what the Program does. 86 | 87 | 1. You may copy and distribute verbatim copies of the Program's 88 | source code as you receive it, in any medium, provided that you 89 | conspicuously and appropriately publish on each copy an appropriate 90 | copyright notice and disclaimer of warranty; keep intact all the 91 | notices that refer to this License and to the absence of any warranty; 92 | and give any other recipients of the Program a copy of this License 93 | along with the Program. 94 | 95 | You may charge a fee for the physical act of transferring a copy, and 96 | you may at your option offer warranty protection in exchange for a fee. 97 | 98 | 2. You may modify your copy or copies of the Program or any portion 99 | of it, thus forming a work based on the Program, and copy and 100 | distribute such modifications or work under the terms of Section 1 101 | above, provided that you also meet all of these conditions: 102 | 103 | a) You must cause the modified files to carry prominent notices 104 | stating that you changed the files and the date of any change. 105 | 106 | b) You must cause any work that you distribute or publish, that in 107 | whole or in part contains or is derived from the Program or any 108 | part thereof, to be licensed as a whole at no charge to all third 109 | parties under the terms of this License. 110 | 111 | c) If the modified program normally reads commands interactively 112 | when run, you must cause it, when started running for such 113 | interactive use in the most ordinary way, to print or display an 114 | announcement including an appropriate copyright notice and a 115 | notice that there is no warranty (or else, saying that you provide 116 | a warranty) and that users may redistribute the program under 117 | these conditions, and telling the user how to view a copy of this 118 | License. (Exception: if the Program itself is interactive but 119 | does not normally print such an announcement, your work based on 120 | the Program is not required to print an announcement.) 121 | 122 | These requirements apply to the modified work as a whole. If 123 | identifiable sections of that work are not derived from the Program, 124 | and can be reasonably considered independent and separate works in 125 | themselves, then this License, and its terms, do not apply to those 126 | sections when you distribute them as separate works. But when you 127 | distribute the same sections as part of a whole which is a work based 128 | on the Program, the distribution of the whole must be on the terms of 129 | this License, whose permissions for other licensees extend to the 130 | entire whole, and thus to each and every part regardless of who wrote it. 131 | 132 | Thus, it is not the intent of this section to claim rights or contest 133 | your rights to work written entirely by you; rather, the intent is to 134 | exercise the right to control the distribution of derivative or 135 | collective works based on the Program. 136 | 137 | In addition, mere aggregation of another work not based on the Program 138 | with the Program (or with a work based on the Program) on a volume of 139 | a storage or distribution medium does not bring the other work under 140 | the scope of this License. 141 | 142 | 3. You may copy and distribute the Program (or a work based on it, 143 | under Section 2) in object code or executable form under the terms of 144 | Sections 1 and 2 above provided that you also do one of the following: 145 | 146 | a) Accompany it with the complete corresponding machine-readable 147 | source code, which must be distributed under the terms of Sections 148 | 1 and 2 above on a medium customarily used for software interchange; or, 149 | 150 | b) Accompany it with a written offer, valid for at least three 151 | years, to give any third party, for a charge no more than your 152 | cost of physically performing source distribution, a complete 153 | machine-readable copy of the corresponding source code, to be 154 | distributed under the terms of Sections 1 and 2 above on a medium 155 | customarily used for software interchange; or, 156 | 157 | c) Accompany it with the information you received as to the offer 158 | to distribute corresponding source code. (This alternative is 159 | allowed only for noncommercial distribution and only if you 160 | received the program in object code or executable form with such 161 | an offer, in accord with Subsection b above.) 162 | 163 | The source code for a work means the preferred form of the work for 164 | making modifications to it. For an executable work, complete source 165 | code means all the source code for all modules it contains, plus any 166 | associated interface definition files, plus the scripts used to 167 | control compilation and installation of the executable. However, as a 168 | special exception, the source code distributed need not include 169 | anything that is normally distributed (in either source or binary 170 | form) with the major components (compiler, kernel, and so on) of the 171 | operating system on which the executable runs, unless that component 172 | itself accompanies the executable. 173 | 174 | If distribution of executable or object code is made by offering 175 | access to copy from a designated place, then offering equivalent 176 | access to copy the source code from the same place counts as 177 | distribution of the source code, even though third parties are not 178 | compelled to copy the source along with the object code. 179 | 180 | 4. You may not copy, modify, sublicense, or distribute the Program 181 | except as expressly provided under this License. Any attempt 182 | otherwise to copy, modify, sublicense or distribute the Program is 183 | void, and will automatically terminate your rights under this License. 184 | However, parties who have received copies, or rights, from you under 185 | this License will not have their licenses terminated so long as such 186 | parties remain in full compliance. 187 | 188 | 5. You are not required to accept this License, since you have not 189 | signed it. However, nothing else grants you permission to modify or 190 | distribute the Program or its derivative works. These actions are 191 | prohibited by law if you do not accept this License. Therefore, by 192 | modifying or distributing the Program (or any work based on the 193 | Program), you indicate your acceptance of this License to do so, and 194 | all its terms and conditions for copying, distributing or modifying 195 | the Program or works based on it. 196 | 197 | 6. Each time you redistribute the Program (or any work based on the 198 | Program), the recipient automatically receives a license from the 199 | original licensor to copy, distribute or modify the Program subject to 200 | these terms and conditions. You may not impose any further 201 | restrictions on the recipients' exercise of the rights granted herein. 202 | You are not responsible for enforcing compliance by third parties to 203 | this License. 204 | 205 | 7. If, as a consequence of a court judgment or allegation of patent 206 | infringement or for any other reason (not limited to patent issues), 207 | conditions are imposed on you (whether by court order, agreement or 208 | otherwise) that contradict the conditions of this License, they do not 209 | excuse you from the conditions of this License. If you cannot 210 | distribute so as to satisfy simultaneously your obligations under this 211 | License and any other pertinent obligations, then as a consequence you 212 | may not distribute the Program at all. For example, if a patent 213 | license would not permit royalty-free redistribution of the Program by 214 | all those who receive copies directly or indirectly through you, then 215 | the only way you could satisfy both it and this License would be to 216 | refrain entirely from distribution of the Program. 217 | 218 | If any portion of this section is held invalid or unenforceable under 219 | any particular circumstance, the balance of the section is intended to 220 | apply and the section as a whole is intended to apply in other 221 | circumstances. 222 | 223 | It is not the purpose of this section to induce you to infringe any 224 | patents or other property right claims or to contest validity of any 225 | such claims; this section has the sole purpose of protecting the 226 | integrity of the free software distribution system, which is 227 | implemented by public license practices. Many people have made 228 | generous contributions to the wide range of software distributed 229 | through that system in reliance on consistent application of that 230 | system; it is up to the author/donor to decide if he or she is willing 231 | to distribute software through any other system and a licensee cannot 232 | impose that choice. 233 | 234 | This section is intended to make thoroughly clear what is believed to 235 | be a consequence of the rest of this License. 236 | 237 | 8. If the distribution and/or use of the Program is restricted in 238 | certain countries either by patents or by copyrighted interfaces, the 239 | original copyright holder who places the Program under this License 240 | may add an explicit geographical distribution limitation excluding 241 | those countries, so that distribution is permitted only in or among 242 | countries not thus excluded. In such case, this License incorporates 243 | the limitation as if written in the body of this License. 244 | 245 | 9. The Free Software Foundation may publish revised and/or new versions 246 | of the General Public License from time to time. Such new versions will 247 | be similar in spirit to the present version, but may differ in detail to 248 | address new problems or concerns. 249 | 250 | Each version is given a distinguishing version number. If the Program 251 | specifies a version number of this License which applies to it and "any 252 | later version", you have the option of following the terms and conditions 253 | either of that version or of any later version published by the Free 254 | Software Foundation. If the Program does not specify a version number of 255 | this License, you may choose any version ever published by the Free Software 256 | Foundation. 257 | 258 | 10. If you wish to incorporate parts of the Program into other free 259 | programs whose distribution conditions are different, write to the author 260 | to ask for permission. For software which is copyrighted by the Free 261 | Software Foundation, write to the Free Software Foundation; we sometimes 262 | make exceptions for this. Our decision will be guided by the two goals 263 | of preserving the free status of all derivatives of our free software and 264 | of promoting the sharing and reuse of software generally. 265 | 266 | NO WARRANTY 267 | 268 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 269 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 270 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 271 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 272 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 273 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 274 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 275 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 276 | REPAIR OR CORRECTION. 277 | 278 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 279 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 280 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 281 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 282 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 283 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 284 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 285 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 286 | POSSIBILITY OF SUCH DAMAGES. 287 | 288 | END OF TERMS AND CONDITIONS 289 | 290 | Appendix: How to Apply These Terms to Your New Programs 291 | 292 | If you develop a new program, and you want it to be of the greatest 293 | possible use to the public, the best way to achieve this is to make it 294 | free software which everyone can redistribute and change under these terms. 295 | 296 | To do so, attach the following notices to the program. It is safest 297 | to attach them to the start of each source file to most effectively 298 | convey the exclusion of warranty; and each file should have at least 299 | the "copyright" line and a pointer to where the full notice is found. 300 | 301 | 302 | Copyright (C) 19yy 303 | 304 | This program is free software; you can redistribute it and/or modify 305 | it under the terms of the GNU General Public License as published by 306 | the Free Software Foundation; either version 2 of the License, or 307 | (at your option) any later version. 308 | 309 | This program is distributed in the hope that it will be useful, 310 | but WITHOUT ANY WARRANTY; without even the implied warranty of 311 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 312 | GNU General Public License for more details. 313 | 314 | You should have received a copy of the GNU General Public License 315 | along with this program; if not, write to the Free Software 316 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 317 | 318 | Also add information on how to contact you by electronic and paper mail. 319 | 320 | If the program is interactive, make it output a short notice like this 321 | when it starts in an interactive mode: 322 | 323 | Gnomovision version 69, Copyright (C) 19yy name of author 324 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 325 | This is free software, and you are welcome to redistribute it 326 | under certain conditions; type `show c' for details. 327 | 328 | The hypothetical commands `show w' and `show c' should show the appropriate 329 | parts of the General Public License. Of course, the commands you use may 330 | be called something other than `show w' and `show c'; they could even be 331 | mouse-clicks or menu items--whatever suits your program. 332 | 333 | You should also get your employer (if you work as a programmer) or your 334 | school, if any, to sign a "copyright disclaimer" for the program, if 335 | necessary. Here is a sample; alter the names: 336 | 337 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 338 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 339 | 340 | , 1 April 1989 341 | Ty Coon, President of Vice 342 | 343 | This General Public License does not permit incorporating your program into 344 | proprietary programs. If your program is a subroutine library, you may 345 | consider it more useful to permit linking proprietary applications with the 346 | library. If this is what you want to do, use the GNU Library General 347 | Public License instead of this License. 348 | 349 | -------------------------------------------------------------------------------- /esh.c: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | #include "common.h" 26 | #include "format.h" 27 | #include "list.h" 28 | #include "gc.h" 29 | #include "hash.h" 30 | #include "job.h" 31 | #include "builtins.h" 32 | #include "read.h" 33 | 34 | 35 | 36 | int shell_terminal_fd = STDIN_FILENO; 37 | int stderr_handler_fd = STDERR_FILENO; 38 | 39 | int interactive = 0; 40 | int exception_flag = 0; 41 | 42 | struct termios shell_terminal_modes; 43 | 44 | hash_table* builtins; 45 | hash_table* aliases; 46 | hash_table* defines; 47 | 48 | list* jobs = NULL; 49 | list* prompt = NULL; 50 | list* stack = NULL; 51 | list* ls_true = NULL; 52 | list* ls_false = NULL; 53 | list* ls_stdio = NULL; 54 | list* ls_stderr = NULL; 55 | list* ls_void = NULL; 56 | 57 | char* syntax_blank = NULL; 58 | char* syntax_special = NULL; 59 | int syntax_fancy = 1; 60 | 61 | char** environ; 62 | 63 | int blank(char c) { 64 | if (c && strchr((syntax_blank ? syntax_blank : " \t\n"), c)) return 1; 65 | return 0; 66 | } 67 | 68 | int openparen(char c) { 69 | if (c && strchr("(", c)) return 1; 70 | return 0; 71 | } 72 | 73 | int closeparen(char c) { 74 | if (c && strchr(")", c)) return 1; 75 | return 0; 76 | } 77 | 78 | int separator(char c) { 79 | if (syntax_fancy && c && strchr(",|", c)) return 1; 80 | return 0; 81 | } 82 | 83 | int redirect_in(char c) { 84 | if (syntax_fancy && c && strchr("<", c)) return 1; 85 | return 0; 86 | } 87 | 88 | int redirect_out(char c) { 89 | if (syntax_fancy && c && strchr(">", c)) return 1; 90 | return 0; 91 | } 92 | 93 | int quote(char c) { 94 | if (c && strchr("\"", c)) return 2; 95 | if (c && strchr("'", c)) return 1; 96 | return 0; 97 | } 98 | 99 | int literal(char c) { 100 | if (c && strchr("`\\", c)) return 1; 101 | return 0; 102 | } 103 | 104 | int delaysym(char c) { 105 | if (c && strchr("$~", c)) return 1; 106 | return 0; 107 | } 108 | 109 | int comment(char c) { 110 | if (c && strchr("#", c)) return 1; 111 | return 0; 112 | } 113 | 114 | int special(char c) { 115 | if (syntax_special) { 116 | if (strchr(syntax_special, c)) 117 | return 1; 118 | 119 | return 0; 120 | 121 | } else if (openparen(c) || closeparen(c) || separator(c) || 122 | redirect_in(c) || redirect_out(c) || quote(c) || 123 | literal(c) || delaysym(c) || comment(c) || 124 | c == '\0') { 125 | return 1; 126 | } 127 | 128 | return 0; 129 | } 130 | 131 | char* dynamic_strcpy(char* str) { 132 | char* tmp = (char*)gc_alloc(sizeof(char) * (strlen(str) + 1), 133 | "dynamic_strcpy"); 134 | 135 | strcpy(tmp, str); 136 | 137 | return tmp; 138 | } 139 | 140 | 141 | /* 142 | * The tokenizer. Useful, but quite limited. It does not detect numerals 143 | * or list literals. In fact, it only detects strings and special 144 | * symbols. 145 | * 146 | * List literals are created by the parser, since they could well 147 | * have special syntax. 148 | * 149 | * Detecting numerals is the job of the individual commands. This is 150 | * done because numerals are needed so infrequently. 151 | */ 152 | 153 | char next_token(char* input, int* i, char** token_value, int* len) { 154 | char ret, foo; 155 | int currquote = 0; 156 | int do_write = 0; 157 | int ignore = 0; 158 | int j = 0; 159 | 160 | if (!input || !input[*i]) { 161 | return '\0'; 162 | } 163 | 164 | while (1) { 165 | foo = input[*i]; 166 | 167 | if (ignore) { 168 | if (foo == '\n' || !foo) { 169 | ignore = 0; 170 | } 171 | } 172 | 173 | /* Are we currently in a quote? */ 174 | if (!ignore && quote(foo)) { 175 | if (do_write) { 176 | ret = 'a'; 177 | break; 178 | 179 | } else if (!currquote) { 180 | currquote = quote(foo); 181 | 182 | } else if (currquote == quote(foo)) { 183 | ret = 'a'; 184 | (*i)++; 185 | break; 186 | } 187 | } 188 | 189 | /* End of input. */ 190 | if (!foo) { 191 | if (do_write) { 192 | ret = 'a'; 193 | } else { 194 | ret = '\0'; 195 | } 196 | 197 | break; 198 | } 199 | 200 | /* Handle comments. */ 201 | if (!ignore && comment(foo) && !currquote) { 202 | if (do_write) { 203 | ret = 'a'; 204 | break; 205 | 206 | } else { 207 | ignore = 1; 208 | } 209 | } 210 | 211 | /* Stop at special syntax. */ 212 | if (!ignore && special(foo) && !quote(foo) && !currquote && !do_write) { 213 | (*i)++; 214 | ret = foo; 215 | break; 216 | } 217 | 218 | /* Find a word beginning. */ 219 | if (!ignore && !currquote) { 220 | 221 | /* It's a letter. */ 222 | if (!blank(foo) && !special(foo)) { 223 | char bar; 224 | 225 | /* Already in a word. */ 226 | if (do_write) { 227 | /* Nothing. */ 228 | 229 | /* Beginning of string. */ 230 | } else if (!*i) { 231 | do_write = 1; 232 | 233 | /* Beginning of word. */ 234 | } else { 235 | bar = input[(*i)-1]; 236 | 237 | if (blank(bar) || special(bar)) do_write = 1; 238 | } 239 | 240 | } else if (do_write) { 241 | ret = 'a'; 242 | break; 243 | } 244 | } 245 | 246 | (*i)++; 247 | 248 | if (do_write || (currquote && currquote != quote(foo))) { 249 | if (j == (*len)-2) { 250 | char* tmp = (char*)gc_alloc(sizeof(char) * (*len) * 2, 251 | "next_token"); 252 | 253 | (*token_value)[(*len)-1] = '\0'; 254 | strcpy(tmp, (*token_value)); 255 | 256 | gc_free(*token_value); 257 | (*token_value) = tmp; 258 | (*len) *= 2; 259 | } 260 | 261 | (*token_value)[j++] = foo; 262 | } 263 | } 264 | 265 | (*token_value)[j] = '\0'; 266 | 267 | if (!ret && currquote) { 268 | error("esh: parse error: end of input while looking for " 269 | "a closing quote."); 270 | } 271 | 272 | return ret; 273 | } 274 | 275 | 276 | 277 | static void ls_strcat_aux(list* ls, char** buff, int* i, int* len) { 278 | list* iter; 279 | int foo; 280 | 281 | for (iter = ls; iter != NULL; iter = ls_next(iter)) { 282 | 283 | if (ls_type(iter) == TYPE_LIST) { 284 | ls_strcat_aux(ls_data(iter), buff, i, len); 285 | 286 | } else if (ls_type(iter) == TYPE_STRING) { 287 | foo = strlen(ls_data(iter)); 288 | 289 | if ((*i) + foo >= ((*len)-1)) { 290 | char* tmp = (char*)gc_alloc(sizeof(char) * (*len + foo) * 2, 291 | "ls_strcat_aux"); 292 | 293 | (*len) = (*len + foo) * 2; 294 | 295 | strcpy(tmp, (*buff)); 296 | gc_free((*buff)); 297 | (*buff) = tmp; 298 | } 299 | 300 | strcat((*buff), ls_data(iter)); 301 | (*i) += foo; 302 | } 303 | } 304 | } 305 | 306 | 307 | char* ls_strcat(list* ls) { 308 | int i = 0; 309 | int len = 128; 310 | char* buff = (char*)gc_alloc(sizeof(char) * len, "ls_strcat"); 311 | 312 | buff[0] = '\0'; 313 | 314 | ls_strcat_aux(ls, &buff, &i, &len); 315 | 316 | return buff; 317 | } 318 | 319 | 320 | glob_t* globbify(list* command) { 321 | glob_t* ret = (glob_t*)gc_alloc(sizeof(glob_t), "globbify"); 322 | 323 | int flags = GLOB_NOCHECK; 324 | list* iter; 325 | list* alias; 326 | 327 | list* junk = NULL; 328 | 329 | if (!command) { 330 | gc_free(ret); 331 | return NULL; 332 | } 333 | 334 | alias = hash_get(aliases, ls_data(command)); 335 | 336 | if (alias) { 337 | if (ls_type(alias) == TYPE_LIST) { 338 | alias = NULL; 339 | 340 | } else { 341 | command = ls_next(command); 342 | } 343 | } 344 | 345 | for (iter = alias; iter != NULL; iter = ls_next(iter)) { 346 | 347 | glob(ls_data(iter), flags, NULL, ret); 348 | 349 | flags |= GLOB_APPEND; 350 | } 351 | 352 | for (iter = command; iter != NULL; iter = ls_next(iter)) { 353 | 354 | if (ls_type(iter) == TYPE_LIST) { 355 | iter = eval(iter); 356 | junk = iter; 357 | } 358 | 359 | if (ls_type(iter) != TYPE_STRING) { 360 | error("esh: disk commands should be given as lists of strings."); 361 | 362 | globfree(ret); 363 | gc_free(ret); 364 | ls_free_all(junk); 365 | return NULL; 366 | } 367 | 368 | glob(ls_data(iter), flags, NULL, ret); 369 | 370 | flags |= GLOB_APPEND; 371 | } 372 | 373 | ls_free_all(junk); 374 | 375 | return ret; 376 | } 377 | 378 | void close_aux(int fd) { 379 | if (fd != STDIN_FILENO && 380 | fd != STDOUT_FILENO && 381 | fd != STDERR_FILENO) { 382 | 383 | close(fd); 384 | } 385 | } 386 | 387 | void dup2_aux(int old, int new) { 388 | if (old != new) { 389 | if (dup2(old, new) < 0) { 390 | error("esh: I/O redirection failed."); 391 | exit(EXIT_FAILURE); 392 | } 393 | 394 | close_aux(old); 395 | } 396 | } 397 | 398 | pid_t fork_aux(void) { 399 | pid_t ret = fork(); 400 | 401 | if (ret < 0) { 402 | error("esh: forking failed."); 403 | exit(EXIT_FAILURE); 404 | } 405 | 406 | return ret; 407 | } 408 | 409 | int exec_aux(char** comm, pid_t pgid, int in_fd, int out_fd, int err_fd) { 410 | pid_t pid; 411 | 412 | if (comm == NULL) { 413 | error("esh: tried to execute a null command."); 414 | exit(EXIT_FAILURE); 415 | } 416 | 417 | pid = getpid(); 418 | 419 | if (!pgid) pgid = pid; 420 | 421 | setpgid(pid, pgid); 422 | 423 | if (interactive) { 424 | signal(SIGINT, SIG_DFL); 425 | signal(SIGQUIT, SIG_DFL); 426 | signal(SIGTSTP, SIG_DFL); 427 | signal(SIGTTIN, SIG_DFL); 428 | signal(SIGTTOU, SIG_DFL); 429 | signal(SIGCHLD, SIG_DFL); 430 | } 431 | 432 | dup2_aux(in_fd, STDIN_FILENO); 433 | dup2_aux(out_fd, STDOUT_FILENO); 434 | dup2_aux(err_fd, STDERR_FILENO); 435 | 436 | execvp(comm[0], comm); 437 | 438 | error("esh: cannot execute \"%s\".", comm[0]); 439 | exit(EXIT_FAILURE); 440 | } 441 | 442 | void pipe_aux(int pipes[2]) { 443 | if (pipe(pipes)) { 444 | error("esh: pipe creation failed."); 445 | exit(EXIT_FAILURE); 446 | } 447 | } 448 | 449 | 450 | int open_read_aux(char* file) { 451 | int fd = open(file, O_RDONLY); 452 | 453 | if (fd < 0) { 454 | error("esh: cannot open \"%s\" for reading.", file); 455 | return -1; 456 | } 457 | 458 | fcntl(fd, F_SETFD, 1); 459 | 460 | return fd; 461 | } 462 | 463 | int open_read_error_aux(char* file) { 464 | int fd = open(file, O_RDONLY); 465 | 466 | fcntl(fd, F_SETFD, 1); 467 | 468 | return fd; 469 | } 470 | 471 | int open_write_aux(char* file) { 472 | int fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0644); 473 | 474 | if (fd < 0) { 475 | error("esh: cannot open \"%s\" for writing.", file); 476 | return -1; 477 | } 478 | 479 | fcntl(fd, F_SETFD, 1); 480 | 481 | return fd; 482 | } 483 | 484 | 485 | char* file_read(int fd) { 486 | int i = 0; 487 | int len = 128; 488 | char* buff = (char*)gc_alloc(sizeof(char) * len, "file_read"); 489 | char foo; 490 | 491 | while (1) { 492 | if (read(fd, &foo, 1) <= 0) { 493 | break; 494 | } 495 | 496 | if (i >= len-2) { 497 | char* tmp = (char*)gc_alloc(sizeof(char) * len * 2, "file_read"); 498 | 499 | len *= 2; 500 | 501 | strcpy(tmp, buff); 502 | gc_free(buff); 503 | buff = tmp; 504 | } 505 | 506 | buff[i] = foo; 507 | i++; 508 | } 509 | 510 | buff[i] = '\0'; 511 | 512 | return buff; 513 | } 514 | 515 | void file_write(int fd, char* data) { 516 | int i = 0; 517 | 518 | while (1) { 519 | if (!data[i]) break; 520 | 521 | if (write(fd, &data[i], 1) <= 0) { 522 | break; 523 | } 524 | 525 | i++; 526 | } 527 | } 528 | 529 | void show_status(job_t* job, int stat) { 530 | 531 | if (WIFEXITED(stat)) { 532 | job->status = JOB_DEAD; 533 | job->value = WEXITSTATUS(stat); 534 | 535 | } else if (WIFSTOPPED(stat)) { 536 | job->status = JOB_STOPPED; 537 | 538 | error_simple("esh: %s: signalled to stop. (", job->name); 539 | 540 | switch (WSTOPSIG(stat)) { 541 | case SIGSTOP: 542 | error_simple("Stopped"); 543 | break; 544 | 545 | case SIGTSTP: 546 | error_simple("Stopped by user"); 547 | break; 548 | 549 | case SIGTTIN: 550 | error_simple("Tried to access terminal input illegaly"); 551 | break; 552 | 553 | case SIGTTOU: 554 | error_simple("Tried to access terminal output illegaly"); 555 | break; 556 | 557 | default: 558 | error_simple("Unknown signal %d", WSTOPSIG(stat)); 559 | break; 560 | } 561 | 562 | error(")"); 563 | 564 | } else if (WIFSIGNALED(stat)) { 565 | job->status = JOB_DEAD; 566 | 567 | error_simple("esh: %s: signalled to exit. (", job->name); 568 | 569 | switch (WTERMSIG(stat)) { 570 | case SIGHUP: 571 | error_simple("Terminal hung up"); 572 | break; 573 | 574 | case SIGINT: 575 | error_simple("Interrupt"); 576 | break; 577 | 578 | case SIGQUIT: 579 | error_simple("Quit"); 580 | break; 581 | 582 | case SIGILL: 583 | error_simple("Illegal instruction"); 584 | break; 585 | 586 | case SIGABRT: 587 | error_simple("Aborted"); 588 | break; 589 | 590 | case SIGFPE: 591 | error_simple("Floating point exception"); 592 | break; 593 | 594 | case SIGKILL: 595 | error_simple("Killed"); 596 | break; 597 | 598 | case SIGSEGV: 599 | error_simple("Segfaulted"); 600 | break; 601 | 602 | case SIGPIPE: 603 | error_simple("Broken pipe"); 604 | break; 605 | 606 | case SIGALRM: 607 | error_simple("Timer went off"); 608 | break; 609 | 610 | case SIGTERM: 611 | error_simple("Terminated"); 612 | break; 613 | 614 | case SIGBUS: 615 | error_simple("Bus error"); 616 | break; 617 | 618 | default: 619 | error_simple("Unknown signal %d", WTERMSIG(stat)); 620 | break; 621 | } 622 | 623 | error(")"); 624 | } 625 | 626 | if (WCOREDUMP(stat)) { 627 | error("Core was dumped."); 628 | } 629 | } 630 | 631 | 632 | /* 633 | * The argument is a dummy value so that this function can be used 634 | * as a signal handler. 635 | */ 636 | 637 | void babysit(int signum) { 638 | list* iter; 639 | job_t* job; 640 | int stat; 641 | int tmp; 642 | 643 | for (iter = jobs; iter != NULL; iter = ls_next(iter)) { 644 | job = ls_data(iter); 645 | 646 | while (1) { 647 | tmp = waitpid(-job->pgid, &stat, WUNTRACED | WNOHANG); 648 | 649 | if (tmp <= 0) break; 650 | 651 | if (tmp == job->last_pid) { 652 | show_status(job, stat); 653 | } 654 | 655 | if (job->status == JOB_DEAD) { 656 | kill(-job->pgid, SIGPIPE); 657 | job->status = JOB_DEAD; 658 | } 659 | } 660 | } 661 | 662 | signal(SIGCHLD, babysit); 663 | } 664 | 665 | 666 | void job_wait(job_t* job) { 667 | int tmp; 668 | sig_t oldsig; 669 | 670 | oldsig = signal(SIGCHLD, SIG_DFL); 671 | if (interactive) { 672 | waitpid(job->last_pid, &tmp, WUNTRACED); 673 | 674 | if (job->name) { 675 | show_status(job, tmp); 676 | } 677 | 678 | if (job->status == JOB_DEAD) { 679 | kill(-job->pgid, SIGPIPE); 680 | 681 | while (waitpid(-job->pgid, &tmp, WUNTRACED) > 0) { 682 | /* Nothing. */ 683 | } 684 | } 685 | 686 | } else { 687 | waitpid(job->last_pid, &tmp, WUNTRACED); 688 | } 689 | signal(SIGCHLD, oldsig); 690 | } 691 | 692 | 693 | void job_foreground(job_t* job) { 694 | 695 | if (interactive) { 696 | tcsetpgrp(shell_terminal_fd, job->pgid); 697 | 698 | if (job->status == JOB_STOPPED) { 699 | tcsetattr(shell_terminal_fd, TCSADRAIN, &job->terminal_modes); 700 | 701 | if (kill(-job->pgid, SIGCONT) < 0) { 702 | error("esh: could not continue PGID %d!", job->pgid); 703 | return; 704 | } 705 | 706 | } else if (job->status == JOB_DEAD) { 707 | error("esh: tried to bring a dead job into the foreground!"); 708 | return; 709 | } 710 | } 711 | 712 | job->status = JOB_RUNNING; 713 | 714 | job_wait(job); 715 | 716 | if (interactive) { 717 | tcsetpgrp(shell_terminal_fd, getpid()); 718 | tcgetattr(shell_terminal_fd, &job->terminal_modes); 719 | tcsetattr(shell_terminal_fd, TCSADRAIN, &shell_terminal_modes); 720 | } 721 | } 722 | 723 | 724 | void job_background(job_t* job) { 725 | 726 | if (!interactive) return; 727 | 728 | if (job->status == JOB_STOPPED) { 729 | if (kill(-job->pgid, SIGCONT) < 0) { 730 | error("esh: could not continue PGID %d!", job->pgid); 731 | return; 732 | } 733 | 734 | } else if (job->status == JOB_DEAD) { 735 | error("esh: tried to bring a dead job into the background!"); 736 | return; 737 | } 738 | 739 | job->status = JOB_RUNNING; 740 | } 741 | 742 | 743 | void arrange_funeral(void) { 744 | list* jnew = NULL; 745 | list* iter; 746 | job_t* job; 747 | 748 | int foo = 0; 749 | 750 | if (!interactive) { 751 | while (waitpid(0, NULL, WUNTRACED | WNOHANG) > 0) { 752 | /* Nothing. */ 753 | } 754 | 755 | return; 756 | } 757 | 758 | if (!jobs) return; 759 | 760 | for (iter = jobs; iter != NULL; iter = ls_next(iter)) { 761 | job = ls_data(iter); 762 | 763 | if (job->status == JOB_DEAD) foo = 1; 764 | } 765 | 766 | if (!foo) return; 767 | 768 | for (iter = jobs; iter != NULL; iter = ls_next(iter)) { 769 | job = ls_data(iter); 770 | 771 | if (job->status == JOB_DEAD) { 772 | gc_free(job->name); 773 | gc_free(job); 774 | 775 | } else { 776 | jnew = ls_cons(job, jnew); 777 | } 778 | } 779 | 780 | ls_free(jobs); 781 | 782 | jobs = ls_reverse(jnew); 783 | } 784 | 785 | 786 | 787 | pid_t do_pipe(int f_src, int f_out, list* ls, int bg, int destructive) { 788 | pid_t pid, pgid = 0, last_pid = 0, ret = 0; 789 | int pipes[2]; 790 | int input_src = STDIN_FILENO; 791 | int output_sink; 792 | 793 | glob_t* comm = NULL; 794 | 795 | job_t* job = NULL; 796 | 797 | int i; 798 | 799 | job = (job_t*)gc_alloc(sizeof(job_t), "do_pipe"); 800 | job->name = NULL; 801 | 802 | if (!interactive) { 803 | pgid = getpid(); 804 | } 805 | 806 | input_src = f_src; 807 | 808 | if (ls == NULL) goto done; 809 | 810 | 811 | for (i = 0; ls != NULL; ls = ls_next(ls), i++) { 812 | 813 | if (ls_type(ls) != TYPE_LIST) { 814 | error("esh: disk commands should be only passed in the form of lists."); 815 | goto done; 816 | } 817 | 818 | comm = globbify(ls_data(ls)); 819 | 820 | if (!comm) { 821 | error("esh: parse error: tried to execute a null command."); 822 | goto done; 823 | 824 | } 825 | 826 | if (!job->name) { 827 | job->name = (char*)gc_alloc(sizeof(char) * 828 | (strlen(comm->gl_pathv[0])+1), 829 | "do_pipe"); 830 | 831 | strcpy(job->name, comm->gl_pathv[0]); 832 | } 833 | 834 | if (ls_next(ls)) { 835 | pipe_aux(pipes); 836 | output_sink = pipes[1]; 837 | 838 | } else { 839 | output_sink = f_out; 840 | } 841 | 842 | 843 | pid = fork_aux(); 844 | 845 | if (pid == 0) { 846 | 847 | exec_aux(comm->gl_pathv, pgid, input_src, output_sink, 848 | stderr_handler_fd); 849 | 850 | } else { 851 | if (!pgid) pgid = pid; 852 | 853 | last_pid = pid; 854 | 855 | setpgid(pid, pgid); 856 | } 857 | 858 | globfree(comm); 859 | gc_free(comm); 860 | comm = NULL; 861 | 862 | if (input_src != f_src) { 863 | close_aux(input_src); 864 | } 865 | 866 | if (output_sink != f_out || destructive) { 867 | close_aux(output_sink); 868 | } 869 | 870 | input_src = pipes[0]; 871 | 872 | } 873 | 874 | job->pgid = pgid; 875 | job->last_pid = last_pid; 876 | job->status = JOB_RUNNING; 877 | job->value = 0; 878 | 879 | if (interactive) { 880 | jobs = ls_cons(job, jobs); 881 | } 882 | 883 | if (!bg) { 884 | job_foreground(job); 885 | ret = job->value; 886 | 887 | } else { 888 | ret = last_pid; 889 | } 890 | 891 | if (!interactive) { 892 | gc_free(job->name); 893 | gc_free(job); 894 | } 895 | 896 | return ret; 897 | 898 | done: 899 | 900 | if (job) { 901 | if (job->name) { 902 | gc_free(job->name); 903 | } 904 | 905 | job->name = NULL; 906 | 907 | if (!bg) { 908 | job_foreground(job); 909 | } 910 | 911 | gc_free(job); 912 | } 913 | 914 | if (comm) { 915 | globfree(comm); 916 | gc_free(comm); 917 | } 918 | 919 | return -1; 920 | } 921 | 922 | 923 | list* do_builtin(list* ls) { 924 | list* (*func)(list*); 925 | list* foo; 926 | 927 | if (ls == NULL || exception_flag) return NULL; 928 | 929 | if (ls_type(ls) != TYPE_STRING) { 930 | error("esh: command names are always strings."); 931 | return NULL; 932 | } 933 | 934 | func = hash_get(builtins, ls_data(ls)); 935 | foo = hash_get(defines, ls_data(ls)); 936 | 937 | if (!func && !foo) { 938 | error("esh: %s is not a command.", ls_data(ls)); 939 | return NULL; 940 | } 941 | 942 | if (foo) { 943 | list* ret; 944 | list* oldstack = stack; 945 | 946 | stack = ls_copy(ls_next(ls)); 947 | ret = eval(foo); 948 | 949 | ls_free_all(stack); 950 | stack = oldstack; 951 | 952 | return ret; 953 | 954 | } else { 955 | return func(ls_next(ls)); 956 | } 957 | } 958 | 959 | list* parse_builtin(char* input, int* i, int liter, int delay) { 960 | list* ls = NULL; 961 | 962 | char token; 963 | int len = 128; 964 | char* value = (char*)gc_alloc(sizeof(char)*len, "parse_builtin"); 965 | 966 | int did_pass = 0; 967 | int pass_liter = 0; 968 | int pass_delay = 0; 969 | list* passthru = NULL; 970 | list* ret = NULL; 971 | 972 | token = next_token(input, i, &value, &len); 973 | 974 | if (!openparen(token)) { 975 | error("esh: parse error: commands should always use " 976 | "parentheses."); 977 | goto done; 978 | } 979 | 980 | while (1) { 981 | did_pass = 0; 982 | pass_liter = 0; 983 | pass_delay = 0; 984 | token = next_token(input, i, &value, &len); 985 | 986 | if (!token) { 987 | error("esh: parse error: no closing parentheses."); 988 | goto done; 989 | } 990 | 991 | /* Recursively parse a subcommand. */ 992 | if (openparen(token)) { 993 | did_pass = 1; 994 | pass_liter = liter; 995 | pass_delay = delay; 996 | 997 | (*i)--; 998 | passthru = parse_builtin(input, i, liter, delay); 999 | 1000 | } else if (literal(token)) { 1001 | error("esh: parse error: the backslash quote is deprecated."); 1002 | goto done; 1003 | 1004 | } else if (delaysym(token)) { 1005 | did_pass = 1; 1006 | pass_liter = 1; 1007 | pass_delay = delay+1; 1008 | 1009 | passthru = parse_builtin(input, i, 1, delay+1); 1010 | 1011 | } else if (closeparen(token)) { 1012 | break; 1013 | 1014 | } else if (special(token)) { 1015 | error("esh: weird syntax found while looking for plain text."); 1016 | goto done; 1017 | } 1018 | 1019 | 1020 | 1021 | if (did_pass) { 1022 | if (pass_liter) { 1023 | ls = ls_cons(passthru, ls); 1024 | 1025 | ls_type_set(ls, TYPE_LIST); 1026 | 1027 | if (pass_delay) { 1028 | ls_flag_set(ls, pass_delay); 1029 | } 1030 | 1031 | } else { 1032 | list* iter; 1033 | 1034 | if (passthru) { 1035 | for (iter = passthru; iter != NULL; iter = ls_next(iter)) { 1036 | 1037 | if (ls_type(iter) == TYPE_VOID) continue; 1038 | 1039 | ls = ls_cons(ls_data(iter), ls); 1040 | ls_type_set(ls, ls_type(iter)); 1041 | ls_flag_set(ls, ls_flag(iter)); 1042 | } 1043 | 1044 | ls_free_shallow(passthru); 1045 | 1046 | } else { 1047 | ls = ls_cons(NULL, ls); 1048 | ls_type_set(ls, TYPE_LIST); 1049 | } 1050 | } 1051 | } else { 1052 | ls = ls_cons(value, ls); 1053 | value = (char*)gc_alloc(sizeof(char) * len, "parse_builtin"); 1054 | } 1055 | } 1056 | 1057 | gc_free(value); 1058 | 1059 | ls = ls_reverse(ls); 1060 | 1061 | if (liter) { 1062 | return ls; 1063 | 1064 | } else { 1065 | ret = do_builtin(ls); 1066 | ls_free_all(ls); 1067 | 1068 | return ret; 1069 | } 1070 | 1071 | done: 1072 | ls_free_all(ls); 1073 | gc_free(value); 1074 | return NULL; 1075 | 1076 | } 1077 | 1078 | 1079 | list* parse_sequence(list* ret, char* input, int* i, char* tok) { 1080 | int len = 127; 1081 | char* value = (char*)gc_alloc(sizeof(char) * len, 1082 | "parse_sequence"); 1083 | while (1) { 1084 | (*tok) = next_token(input, i, &value, &len); 1085 | 1086 | if (special(*tok)) { 1087 | break; 1088 | 1089 | } else { 1090 | ret = ls_cons(value, ret); 1091 | 1092 | value = (char*)gc_alloc(sizeof(char) * len, 1093 | "parse_sequence"); 1094 | } 1095 | } 1096 | 1097 | gc_free(value); 1098 | 1099 | return ret; 1100 | } 1101 | 1102 | 1103 | list* parse_split(char* input) { 1104 | list* ls = NULL; 1105 | 1106 | int i = 0; 1107 | char token; 1108 | 1109 | char* tmp; 1110 | 1111 | syntax_special = ""; 1112 | 1113 | while (1) { 1114 | ls = parse_sequence(ls, input, &i, &token); 1115 | 1116 | if (!token) break; 1117 | 1118 | if (special(token)) { 1119 | tmp = (char*)gc_alloc(sizeof(char) * 2, "parse_split"); 1120 | 1121 | tmp[0] = token; 1122 | tmp[1] = '\0'; 1123 | 1124 | ls = ls_cons(tmp, ls); 1125 | } 1126 | } 1127 | 1128 | syntax_special = NULL; 1129 | 1130 | return ls_reverse(ls); 1131 | } 1132 | 1133 | 1134 | void parse_pipe(char* input) { 1135 | list* ls = NULL; 1136 | 1137 | char* f_in = NULL; 1138 | char* f_out = NULL; 1139 | 1140 | int fd_in = STDIN_FILENO; 1141 | int fd_out = STDOUT_FILENO; 1142 | 1143 | char token, old, foo = -1; 1144 | int i = 0, bar = 0; 1145 | int len = 128; 1146 | char* value = (char*)gc_alloc(sizeof(char) * len, "parse_pipe"); 1147 | 1148 | list* catter; 1149 | 1150 | 1151 | while (1) { 1152 | catter = ls_reverse(parse_sequence(NULL, input, &i, &foo)); 1153 | 1154 | ls = ls_cons(catter, ls); 1155 | ls_type_set(ls, TYPE_LIST); 1156 | 1157 | if (!separator(foo)) 1158 | break; 1159 | } 1160 | 1161 | old = foo; 1162 | 1163 | for (bar = 0; bar < 2; bar++) { 1164 | if (old) { 1165 | token = next_token(input, &i, &value, &len); 1166 | 1167 | if (redirect_in(old) && !special(token) && !f_in) { 1168 | f_in = value; 1169 | foo = -1; 1170 | value = (char*)gc_alloc(sizeof(char) * len, "parse_pipe"); 1171 | 1172 | } else if (redirect_out(old) && !special(token) && !f_out) { 1173 | f_out = value; 1174 | foo = -1; 1175 | value = (char*)gc_alloc(sizeof(char) * len, "parse_pipe"); 1176 | 1177 | } else { 1178 | error("esh: parse error: special syntax where redirection should be."); 1179 | goto done; 1180 | } 1181 | 1182 | old = next_token(input, &i, &value, &len); 1183 | } 1184 | } 1185 | 1186 | if (old) { 1187 | error("esh: parse error: extraneous characters after redirection " 1188 | "operators."); 1189 | goto done; 1190 | } 1191 | 1192 | ls = ls_reverse(ls); 1193 | 1194 | if (f_in) { 1195 | fd_in = open_read_aux(f_in); 1196 | } 1197 | 1198 | if (f_out) { 1199 | fd_out = open_write_aux(f_out); 1200 | } 1201 | 1202 | /* Mondo-hack: special-case parsing of "list"ed aliases. */ 1203 | 1204 | if (!f_in && !f_out && !ls_next(ls)) { 1205 | list* tmp = ls_data(ls); 1206 | list* alias = hash_get(aliases, ls_data(tmp)); 1207 | 1208 | if (alias && ls_type(alias) == TYPE_LIST) { 1209 | list* oldstack = stack; 1210 | list* tmp2; 1211 | 1212 | stack = ls_copy(ls_next(tmp)); 1213 | tmp2 = eval(alias); 1214 | ls_free_all(stack); 1215 | ls_free_all(tmp2); 1216 | 1217 | stack = oldstack; 1218 | 1219 | goto done; 1220 | } 1221 | } 1222 | 1223 | 1224 | do_pipe(fd_in, fd_out, ls, 0, 0); 1225 | 1226 | close_aux(fd_in); 1227 | close_aux(fd_out); 1228 | 1229 | done: 1230 | 1231 | ls_free_all(ls); 1232 | gc_free(value); 1233 | 1234 | if (f_in) gc_free(f_in); 1235 | if (f_out) gc_free(f_out); 1236 | } 1237 | 1238 | 1239 | static void ls_print_aux(list* ls, int i, int delay) { 1240 | list* iter; 1241 | 1242 | if (i) 1243 | printf("("); 1244 | 1245 | for (iter = ls; iter != NULL; iter = ls_next(iter)) { 1246 | 1247 | switch (ls_type(iter)) { 1248 | case TYPE_LIST: 1249 | if (iter && ls_flag(iter) > delay) { 1250 | printf("~"); 1251 | } 1252 | 1253 | ls_print_aux(ls_data(iter), 1, ls_flag(iter)); 1254 | break; 1255 | 1256 | case TYPE_STRING: 1257 | printf("%s", (char*)ls_data(iter)); 1258 | break; 1259 | 1260 | case TYPE_HASH: 1261 | printf("", ls_data(iter)); 1262 | break; 1263 | 1264 | case TYPE_BOOL: 1265 | printf("", ls_data(iter) ? "t" : "f"); 1266 | break; 1267 | 1268 | case TYPE_FD: 1269 | { 1270 | int* fd = ls_data(iter); 1271 | printf("", fd[0], fd[1]); 1272 | break; 1273 | } 1274 | 1275 | case TYPE_PROC: 1276 | printf("", *(pid_t*)(ls_data(iter))); 1277 | break; 1278 | } 1279 | 1280 | if (ls_next(iter)) { 1281 | printf(" "); 1282 | } 1283 | } 1284 | 1285 | if (i) 1286 | printf(")"); 1287 | } 1288 | 1289 | 1290 | void ls_print(list* ls) { 1291 | ls_print_aux(ls, 0, 0); 1292 | } 1293 | 1294 | 1295 | void parse_command(char* input) { 1296 | int i = 0; 1297 | char token; 1298 | int len = 128; 1299 | char* value = (char*)gc_alloc(sizeof(char) * len, "parse_command"); 1300 | 1301 | syntax_fancy = 1; 1302 | 1303 | token = next_token(input, &i, &value, &len); 1304 | 1305 | if (openparen(token)) { 1306 | list* ret; 1307 | 1308 | syntax_fancy = 0; 1309 | 1310 | i = 0; 1311 | ret = parse_builtin(input, &i, 0, 0); 1312 | 1313 | token = next_token(input, &i, &value, &len); 1314 | 1315 | if (token) { 1316 | error("esh: extraneous characters after command."); 1317 | 1318 | gc_free(value); 1319 | ls_free_all(ret); 1320 | return; 1321 | } 1322 | 1323 | if (ret) { 1324 | printf("=>\n"); 1325 | 1326 | ls_print(ret); 1327 | 1328 | printf("\n"); 1329 | } 1330 | 1331 | ls_free_all(ret); 1332 | 1333 | } else if (token == '\0') { 1334 | /* Nothing. */ 1335 | 1336 | } else if (special(token)) { 1337 | error("esh: parse error: unexpected special syntax at the beginning of " 1338 | "the command."); 1339 | 1340 | } else { 1341 | parse_pipe(input); 1342 | } 1343 | 1344 | gc_free(value); 1345 | } 1346 | 1347 | 1348 | int parse_file(int file, char** buff, int* len) { 1349 | int i = 0; 1350 | char chr; 1351 | int parencount = 0; 1352 | int did_one = 0; 1353 | int inquote = 0; 1354 | 1355 | int junk = 0; 1356 | list* ret; 1357 | 1358 | syntax_fancy = 0; 1359 | 1360 | while (1) { 1361 | 1362 | if (read(file, &chr, 1) <= 0) { 1363 | 1364 | if (parencount || inquote) { 1365 | error("esh: premature end of file while reading a script."); 1366 | } 1367 | 1368 | return 0; 1369 | } 1370 | 1371 | if (!inquote && comment(chr)) { 1372 | while (1) { 1373 | 1374 | if (read(file, &chr, 1) <= 0) { 1375 | break; 1376 | } 1377 | 1378 | if (chr == '\n') break; 1379 | } 1380 | } 1381 | 1382 | if (quote(chr)) { 1383 | if (inquote == quote(chr)) { 1384 | inquote = 0; 1385 | 1386 | } else if (!inquote) { 1387 | inquote = quote(chr); 1388 | } 1389 | 1390 | } else if (!inquote && openparen(chr)) { 1391 | parencount++; 1392 | 1393 | } else if (!inquote && closeparen(chr)) { 1394 | did_one = 1; 1395 | parencount--; 1396 | 1397 | } 1398 | 1399 | if (i >= (*len)-2) { 1400 | char* tmp = (char*)gc_alloc(sizeof(char) * (*len) * 2, "parse_file"); 1401 | 1402 | (*len) *= 2; 1403 | 1404 | strcpy(tmp, (*buff)); 1405 | gc_free((*buff)); 1406 | (*buff) = tmp; 1407 | } 1408 | 1409 | (*buff)[i] = chr; 1410 | 1411 | i++; 1412 | 1413 | if (!parencount && did_one) break; 1414 | } 1415 | 1416 | (*buff)[i] = '\0'; 1417 | 1418 | ret = parse_builtin((*buff), &junk, 0, 0); 1419 | 1420 | ls_free_all(ret); 1421 | 1422 | return 1; 1423 | } 1424 | 1425 | 1426 | char* get_prompt(void) { 1427 | char* ret; 1428 | list* prompt_ev; 1429 | 1430 | if (prompt) { 1431 | prompt_ev = eval(prompt); 1432 | 1433 | ret = ls_strcat(prompt_ev); 1434 | 1435 | ls_free_all(prompt_ev); 1436 | 1437 | } else { 1438 | ret = (char*)gc_alloc(sizeof(char) * 3, "get_prompt"); 1439 | strcpy(ret, "$ "); 1440 | } 1441 | 1442 | return ret; 1443 | } 1444 | 1445 | 1446 | void do_file(char* fname, int do_error) { 1447 | int file = STDIN_FILENO; 1448 | 1449 | int len = 256; 1450 | char* buff = (char*)gc_alloc(sizeof(char) * len, "do_file"); 1451 | 1452 | if (fname) { 1453 | file = open_read_error_aux(fname); 1454 | } 1455 | 1456 | if (file < 0) { 1457 | if (do_error) { 1458 | if (fname) { 1459 | error("esh: could not load script file \"%s\".", fname); 1460 | } else { 1461 | error("esh: could not load script file from stdin."); 1462 | } 1463 | } 1464 | 1465 | gc_free(buff); 1466 | return; 1467 | } 1468 | 1469 | while (parse_file(file, &buff, &len)) { 1470 | arrange_funeral(); 1471 | } 1472 | 1473 | close_aux(file); 1474 | 1475 | gc_free(buff); 1476 | } 1477 | 1478 | 1479 | 1480 | static void exception(int signum) { 1481 | exception_flag = 1; 1482 | 1483 | signal(SIGINT, exception); 1484 | } 1485 | 1486 | 1487 | void init_shell(int argc, char** argv) { 1488 | char buff[256]; 1489 | char* homedir; 1490 | int i; 1491 | 1492 | pid_t pgid, self_pid; 1493 | 1494 | int* tmp1; 1495 | int* tmp2; 1496 | 1497 | tmp1 = (int*)gc_alloc(sizeof(int) * 2, "init_shell"); 1498 | tmp2 = (int*)gc_alloc(sizeof(int) * 2, "init_shell"); 1499 | 1500 | builtins = (hash_table*)gc_alloc(sizeof(hash_table), "init_shell"); 1501 | aliases = (hash_table*)gc_alloc(sizeof(hash_table), "init_shell"); 1502 | defines = (hash_table*)gc_alloc(sizeof(hash_table), "init_shell"); 1503 | 1504 | hash_init(builtins, builtins_array); 1505 | hash_init(aliases, NULL); 1506 | hash_init(defines, NULL); 1507 | 1508 | interactive = isatty(shell_terminal_fd); 1509 | 1510 | ls_true = ls_cons((void*)1, NULL); 1511 | ls_type_set(ls_true, TYPE_BOOL); 1512 | 1513 | ls_false = ls_cons((void*)0, NULL); 1514 | ls_type_set(ls_false, TYPE_BOOL); 1515 | 1516 | tmp1[0] = STDIN_FILENO; 1517 | tmp1[1] = STDOUT_FILENO; 1518 | 1519 | tmp2[0] = STDIN_FILENO; 1520 | tmp2[1] = STDERR_FILENO; 1521 | 1522 | ls_stdio = ls_cons(tmp1, NULL); 1523 | ls_type_set(ls_stdio, TYPE_FD); 1524 | 1525 | ls_stderr = ls_cons(tmp2, NULL); 1526 | ls_type_set(ls_stderr, TYPE_FD); 1527 | 1528 | ls_void = ls_cons(NULL, NULL); 1529 | ls_type_set(ls_void, TYPE_VOID); 1530 | 1531 | if (interactive) { 1532 | 1533 | while (1) { 1534 | pgid = getpgrp(); 1535 | 1536 | if (tcgetpgrp(shell_terminal_fd) == pgid) break; 1537 | 1538 | kill(-pgid, SIGTTIN); 1539 | } 1540 | 1541 | /* Ignore interactive and job-control signals. */ 1542 | signal(SIGINT, exception); 1543 | signal(SIGQUIT, SIG_IGN); 1544 | signal(SIGTSTP, SIG_IGN); 1545 | signal(SIGTTIN, SIG_IGN); 1546 | signal(SIGTTOU, SIG_IGN); 1547 | 1548 | signal(SIGCHLD, babysit); 1549 | 1550 | self_pid = getpid(); 1551 | 1552 | if (setpgid(0, self_pid) < 0) { 1553 | error("esh: could not put myself in my own process group."); 1554 | error("esh: %s.", 1555 | (errno == EINVAL ? "pgid < 0" : 1556 | (errno == EPERM ? "permission denied" : 1557 | (errno == ESRCH ? "no such PID" : 1558 | ("unknown error"))))); 1559 | } 1560 | 1561 | tcgetattr(shell_terminal_fd, &shell_terminal_modes); 1562 | 1563 | read_init(); 1564 | } 1565 | 1566 | for (i = argc-1; i > 0; i--) { 1567 | stack = ls_cons(dynamic_strcpy(argv[i]), stack); 1568 | } 1569 | 1570 | register_chdir(); 1571 | 1572 | do_file("/etc/eshrc", 0); 1573 | 1574 | homedir = getenv("HOME"); 1575 | 1576 | if (homedir) { 1577 | strncpy(buff, homedir, 255); 1578 | strncat(buff, "/.eshrc", 255); 1579 | 1580 | buff[255] = '\0'; 1581 | 1582 | do_file(buff, 0); 1583 | } 1584 | } 1585 | 1586 | 1587 | int main(int argc, char** argv, char** env) { 1588 | char* pmt; 1589 | char* line = NULL; 1590 | 1591 | environ = env; 1592 | init_shell(argc, argv); 1593 | 1594 | if (interactive) { 1595 | while (1) { 1596 | exception_flag = 0; 1597 | arrange_funeral(); 1598 | 1599 | pmt = get_prompt(); 1600 | 1601 | line = read_read(pmt); 1602 | 1603 | gc_free(pmt); 1604 | 1605 | if (!line) break; 1606 | 1607 | parse_command(line); 1608 | 1609 | gc_free(line); 1610 | } 1611 | 1612 | } else { 1613 | do_file(NULL, 1); 1614 | } 1615 | 1616 | 1617 | close_aux(stderr_handler_fd); 1618 | 1619 | #ifdef MEM_DEBUG 1620 | 1621 | for (tmp = jobs; tmp != NULL; tmp = ls_next(tmp)) { 1622 | gc_free(((job_t*)ls_data(tmp))->name); 1623 | } 1624 | 1625 | ls_free_all(jobs); 1626 | 1627 | jobs = NULL; 1628 | 1629 | ls_free_all(ls_void); 1630 | ls_free_all(ls_true); 1631 | ls_free_all(ls_false); 1632 | ls_free_all(ls_stdio); 1633 | ls_free_all(ls_stderr); 1634 | ls_free_all(prompt); 1635 | ls_free_all(stack); 1636 | 1637 | hash_free(aliases, ls_free_all); 1638 | hash_free(defines, ls_free_all); 1639 | hash_free(builtins, NULL); 1640 | 1641 | gc_free(aliases); 1642 | gc_free(defines); 1643 | gc_free(builtins); 1644 | 1645 | if (syntax_blank) { 1646 | gc_free(syntax_blank); 1647 | } 1648 | 1649 | if (syntax_special) { 1650 | gc_free(syntax_special); 1651 | } 1652 | 1653 | read_done(); 1654 | 1655 | gc_diagnostics(); 1656 | 1657 | #endif 1658 | 1659 | return EXIT_SUCCESS; 1660 | } 1661 | 1662 | -------------------------------------------------------------------------------- /doc/esh.texi: -------------------------------------------------------------------------------- 1 | \input texinfo @c -*-texinfo-*- 2 | @c %**start of header 3 | @setfilename esh.info 4 | @settitle esh, the easy shell. 5 | @c %**end of header 6 | 7 | @ifinfo 8 | @dircategory Shells 9 | @direntry 10 | * esh: (esh). 11 | @end direntry 12 | @end ifinfo 13 | 14 | @titlepage 15 | @sp 10 16 | @center @titlefont{esh, the easy shell.} 17 | @end titlepage 18 | 19 | @ifinfo 20 | @node Top, Rationale, (dir), (dir) 21 | @top esh, the easy shell. 22 | 23 | @code{esh} is a new, lightweight Unix shell designed for maximum simplicity and 24 | generality. 25 | 26 | @end ifinfo 27 | 28 | @menu 29 | * Rationale:: Why write it? 30 | * Overview:: What is it? 31 | * Details:: In-depth information on programming the shell. 32 | * Differences from Scheme:: How this shell differs from Scheme. 33 | * Differences from sh:: How this shell differs from sh-based shells. 34 | * Command Index:: All the builtin commands. 35 | * Concept Index:: Concepts referred to in this manual. 36 | @end menu 37 | 38 | @node Rationale, Overview, Top, Top 39 | @cindex Rationale 40 | @chapter Rationale 41 | 42 | @code{esh} was primarily written out of a need for a simple and lightweight shell 43 | for Unix. As such, it deviates completely from all of the traditional 44 | shells, opting instead for a Lisp-like syntax. This allows exceptionally 45 | small size, both in terms of lines of code and memory consumption, while 46 | retaining remarkable flexibility and programmability. 47 | 48 | @node Overview, Details, Rationale, Top 49 | @cindex Overview 50 | @cindex Introduction 51 | @cindex Getting started 52 | @chapter Overview 53 | 54 | @ifinfo 55 | An overview of the basic information about the shell. 56 | @end ifinfo 57 | 58 | @menu 59 | * Starting esh:: How to launch it. 60 | * Interaction:: Simple interaction with the shell. 61 | * Simple Programming:: Writing simple programs. 62 | * Non-interactive:: Running the shell non-interactively. 63 | * Job Control:: How to use job control. 64 | * Basic Usage:: Some essential commands. 65 | @end menu 66 | 67 | @node Starting esh, Interaction, , Overview 68 | @cindex Starting @code{esh} 69 | @cindex Command line parameters 70 | @cindex Launching @code{esh} 71 | @cindex .eshrc 72 | @cindex eshrc 73 | @section Starting @code{esh} 74 | 75 | To start the shell, simply type @code{esh}. There are no command-line 76 | parameters. However, since all the command-line arguments are available 77 | to the shell programmer, it is a simple matter to write your own argument 78 | processing routine and place it in your @code{.eshrc} file. 79 | 80 | Both @code{/etc/eshrc} and the @code{.eshrc} in your home directory will 81 | be run by the shell on startup, if they exist. 82 | 83 | @node Interaction, Simple Programming, Starting esh, Overview 84 | @cindex Interaction 85 | @cindex Running commands 86 | @cindex Executing files 87 | @cindex Pipes and redirection 88 | @section Interaction 89 | 90 | If the shell is running interactively, you will get a prompt, @code{$ } by 91 | default. Since the shell uses the readline library, you can use line-editing 92 | keystrokes, the history buffer, filename completion, and the rest of the 93 | goodies provided by the readline library. 94 | 95 | To run a single executable, simply type it in as you would in any other 96 | shell. Example: @code{/usr/games/fortune -m Unix}. 97 | 98 | To run a pipeline, type several commands separated by commas or vertical 99 | lines. Example: 100 | @code{cat /var/log/messages, grep pppd, grep "IP address", xmessage -file -}. 101 | 102 | To run a pipeline with file redirection, place the redirection symbols and the 103 | filenames after the pipeline. Example: 104 | @code{sort, uniq < names.raw > names.sorted}. In this case, the contents of 105 | @code{names.raw} are used as the standard input of @code{sort}, and the output 106 | of the whole pipeline is saved to @code{names.sorted}. The order in which 107 | the redirection symbols are given does not matter, but a valid filename 108 | should always follow a redirection symbol. 109 | 110 | Note one major pitfall: @code{cd}, @code{bg}, @code{fg}, etc., are all 111 | builtin shell commands! For info on running shell commands, see the 112 | next section. (@pxref{Simple Programming}) 113 | 114 | @node Simple Programming, Non-interactive, Interaction, Overview 115 | @cindex Simple programming 116 | @cindex Syntax, basic 117 | @cindex Basics of shell programming 118 | @section Simple Programming 119 | 120 | All shell commands are specified using a parenthetical syntax 121 | very similar to that of Lisp or Scheme. Example: Use @code{(cd /var)} to change 122 | the current directory. In this case, @code{cd} is the name of the command, 123 | and @code{/var} is the argument. Multiple arguments to the same 124 | command are separated by white space. Example: @code{(set HOME /home/ivan)}. 125 | Builtin commands never accept options in the 126 | style of compiled programs. Also note that quotes around string values are 127 | not required, though they are certainly allowed at any time if you wish 128 | to escape special characters. (In fact, using quotes is the only way to 129 | do this. Backslash escape sequences are not recognized.) Example: 130 | @code{(cd "funny,directory name()")}. In this case, the whole string inside 131 | the quotes is used as the argument verbatim. Single quotes and double 132 | quotes are equivalent. 133 | 134 | If a command returns anything, the returned data will be printed 135 | by the shell. Example: @code{(get HOME)} @result{} @code{/home/ivan}. 136 | 137 | Note that you can certainly use the return values of one command as input 138 | to another. Example: @code{(cd (get HOME))} 139 | 140 | If you want to pass a list instead of a string as an argument to a command, 141 | quote the list with a tilde. Example: 142 | @code{(run-simple ~(ls --color=yes))}. Here, the @code{run} command is given 143 | a list as an argument. 144 | 145 | Note that a sublist of a quoted list is also quoted; that is, 146 | @code{~(1.1 (2.1 2.2) 1.2)} is a list of three elements, the first and last 147 | are strings and the middle element is a list. 148 | 149 | @xref{Basic Usage}, for an explanation of what @code{cd}, @code{get}, 150 | @code{set}, and @code{run} do. 151 | 152 | @node Non-interactive, Job Control, Simple Programming, Overview 153 | @cindex Non-interactive 154 | @cindex Files, as shell scripts 155 | @section Non-interactive 156 | 157 | If the shell is started non-interactively -- for example, when the standard 158 | input to the shell is a file -- the shell will only accept commands. 159 | To run executables and construct pipes, simply use the @code{run} 160 | command. 161 | (@pxref{Basic Usage}) 162 | 163 | @node Job Control, Basic Usage, Non-interactive, Overview 164 | @cindex Job control 165 | @cindex Foreground 166 | @cindex Background 167 | @cindex Stopping jobs 168 | @section Job Control 169 | 170 | If the shell is running interactively, you will have access to job control 171 | primitives. 172 | 173 | To stop a job in the foreground, simply type @kbd{C-z}, as in other shells. 174 | 175 | To bring the last known job into foreground or background, issue the @code{fg} 176 | or @code{bg} command, respectively, without arguments. 177 | (@pxref{Simple Programming}) 178 | 179 | To get a listing of all the currently known jobs, issue the @code{jobs} 180 | command, also without arguments. 181 | 182 | If the @code{fg} or @code{bg} command is given a single numeric argument, it 183 | will act on the job number specified by that argument. To find out the job 184 | number for a command, simply issue the @code{jobs} command. 185 | 186 | Note: the job number is @emph{not} the PID! 187 | 188 | 189 | @node Basic Usage, Syntax, Job Control, Overview 190 | @cindex Command list 191 | @cindex Basic commands 192 | @cindex Builtin command list, basic 193 | @findex jobs 194 | @findex cd 195 | @findex fg 196 | @findex bg 197 | @findex run 198 | @findex run-simple 199 | @findex get 200 | @findex set 201 | @findex env 202 | @findex print 203 | @findex + 204 | @findex - 205 | @findex * 206 | @findex / 207 | @findex if 208 | @findex list 209 | @findex eval 210 | @findex = 211 | @section Basic Usage 212 | 213 | @itemize @bullet 214 | @item 215 | @code{(cd [string])} Change the current directory to the given 216 | argument. If the directory name is a single dash, the previous directory 217 | will be used. (i.e. the value of the environment variable @code{OLDPWD}) 218 | If the command is called without any arguments, change to the home directory. 219 | 220 | @item 221 | @code{(jobs)} List the current jobs. 222 | 223 | @item 224 | @code{(fg [number])} If the optional numeric argument is given, bring 225 | the job number given by the argument into the foreground. If this argument 226 | is omitted, bring the first job into the foreground. 227 | 228 | @item 229 | @code{(bg [number])} Same as @code{fg}, but puts a job into the background. 230 | 231 | @item 232 | @code{(run ...)} Run a pipeline. 233 | The first argument should be @code{false} if you want the command to be 234 | brought into the foreground, or @code{true} if otherwise. 235 | The next two arguments are the input and output redirection files, 236 | respectively. You can use @code{(standard)} for standard input/output. 237 | The rest of the arguments are the commands you want to run, 238 | given as lists. 239 | If the command was run in the background, then the return value 240 | is the PID of the last executable in the pipeline. Otherwise, the return 241 | value is the exit status of the pipeline. 242 | 243 | Examples: 244 | @code{(run (false) (standard) (standard) ~(/usr/games/fortune -m Unix))} 245 | is equivalent to 246 | @code{/usr/games/fortune -m Unix}. 247 | @code{(run (false) (file-open file "names.raw") (file-open file "names.sorted") ~(sort) ~(uniq))} is equivalent to 248 | @code{sort, uniq < names.raw > names.sorted}. 249 | 250 | @item 251 | @code{(run-simple ...)} Equivalent to 252 | @code{(run (false) (standard) (standard) ...)}. 253 | This command returns the exit status of the pipeline. 254 | 255 | @item 256 | @code{(get )} Return the environment variable named by the given 257 | argument. 258 | 259 | @item 260 | @code{(set )} Set the environment variable named by the first 261 | string to be the second string. 262 | 263 | @item 264 | @code{(env)} List all environment variables. 265 | 266 | @item 267 | @code{(print ...)} Print the given arguments on the standard output in 268 | a human-readable form. 269 | 270 | @item 271 | @code{(+ ...)} Add all the arguments. 272 | 273 | @item 274 | @code{(- ...)} Subtract the arguments following the first argument 275 | from the first argument. 276 | 277 | @item 278 | @code{(* ...)} Multiply all the arguments. 279 | 280 | @item 281 | @code{(/ ...)} Divide the first argument by the rest of the arguments. 282 | 283 | @item 284 | @code{(< )} Return @code{true} if the first number is 285 | less than the second; otherwise, return @code{false}. 286 | 287 | @item 288 | @code{(> )} Return @code{true} if the first number is 289 | less than the second; otherwise, return @code{false}. 290 | 291 | @item 292 | @code{(if )} If the \"eval\" of the 293 | first argument is @code{false}, execute @code{eval} on the second argument. 294 | Otherwise, execute @code{eval} on the third argument. Note that the 295 | arguments must be quoted right! (@pxref{Quoting Trickery} for 296 | more info on that.) 297 | 298 | @item 299 | @code{(list ...)} Compose a list made of the given arguments and return it. 300 | 301 | @item 302 | @code{(eval ...)} If an argument is a string or a hash table, simply return it. 303 | Otherwise, execute the given list as if it was a command. 304 | For example, typing in 305 | @code{(eval ~(print Hello World!))} is equivalent to 306 | @code{(print Hello World!)}. However, you must make sure that the list given 307 | to @code{eval} is quoted properly in order to delay evaluation 308 | until the right moment. (@pxref{Quoting Trickery} for more info.) 309 | 310 | @item 311 | @code{(= )} Compare the two strings and return an empty 312 | list if they are not equal. Otherwise, simply return the first string. 313 | 314 | @end itemize 315 | 316 | 317 | 318 | @node Details, Differences from Scheme, Overview, Top 319 | @cindex Details 320 | @cindex Programmer's Manual 321 | @cindex Tutorial 322 | @chapter Details 323 | 324 | A programmer's manual and a tutorial. 325 | 326 | @menu 327 | * Syntax:: Syntax of the shell. 328 | * Semantics:: Writing commands, control structures, data storage. 329 | * Quoting Trickery:: Controlling @code{eval} and using data as code. 330 | * Command List:: More commands. 331 | * Tutorial:: Learn by example. 332 | @end menu 333 | 334 | 335 | @node Syntax, Semantics, Basic Usage, Details 336 | @cindex Syntax 337 | @section Syntax 338 | 339 | Here is a description of the syntax of @code{esh}. Note, however, that 340 | this grammar is only a descriptive tool, since the actual parser in the 341 | shell is written by hand. 342 | 343 | Also note that using the interactive-command syntax is not allowed when 344 | the shell is not started interactively. 345 | 346 | @example 347 | openparen-symbol = '(' 348 | closeparen-symbol = ')' 349 | delay-symbol = '~' | '$' 350 | pipe-symbol = ',' | '|' 351 | redirection-symbol = '<' | '>' 352 | 353 | script = /* Nothing */ | 354 | list script 355 | 356 | list = openparen-symbol list-elements closeparen-symbol 357 | 358 | list-elements = /* Nothing */ | 359 | string list-elements | 360 | delay-symbol list list-elements | 361 | list list-elements 362 | 363 | interactive-command = list | 364 | interactive-pipeline 365 | 366 | interactive-pipeline = /* Nothing */ | 367 | interactive-pipe redirection redirection 368 | 369 | interactive-pipe = /* Nothing */ | 370 | single-command pipe-symbol interactive-pipe 371 | 372 | single-command = /* Nothing */ | 373 | string single-command 374 | 375 | redirection = /* Nothing */ | 376 | redirection-symbol string 377 | @end example 378 | 379 | @node Semantics, Quoting Trickery, Syntax, Details 380 | @cindex Commands 381 | @cindex Recursion 382 | @cindex Control structures 383 | @cindex Loops 384 | @cindex Variables 385 | @cindex Stack, local variable 386 | @cindex Arguments, to commands 387 | @section Semantics 388 | 389 | One major deviation of @code{esh} from other programming languages is 390 | that commands are not functions. Any command can return any number of 391 | values, without explicit unpacking syntax. (e.g. @code{Python}) 392 | 393 | For example, @code{(rest ~(foo bar baz))} @result{} @code{bar baz}. 394 | These returned elements are not a list! Therefore, this code 395 | will produce an error: @code{(rest (rest ~(foo bar baz)))}. Instead, you 396 | should write: @code{(rest (list (rest ~(foo bar baz))))}. 397 | 398 | The first, most important concept is the @code{define} command. This command 399 | allows you to define new commands, which are no different from builtin 400 | commands as far as the programmer is concerned. 401 | 402 | The syntax of @code{define} is simple: @code{(define ...)}. 403 | The first argument is simply the name of the command you are defining, and 404 | the rest of the arguments will simply be passed to @code{eval} whenever 405 | your new command is called. 406 | 407 | It's important to realize that this code snippet 408 | @code{(define foo ~(print bar) bar) (foo)} is identical to 409 | this one: @code{(eval ~(print bar) bar)}. Therefore, anything that 410 | applies to @code{eval} also applies to commands defined by @code{define}. 411 | (@pxref{Quoting Trickery} for more on that.) 412 | 413 | If you are familiar with Scheme or Lisp, you'll notice the lack of 414 | argument passing information in the syntax of @code{define}. The reason for 415 | that is that the argument passing convention is radically different in 416 | @code{esh} from other Lisp-like languages. 417 | 418 | Every command in @code{esh} has a stack all to itself for scratch space 419 | and local variables. When a user-defined command is run, all the arguments 420 | are simply placed on the local-variable stack, the first argument at the 421 | top. 422 | 423 | To access the local-variable stack, several commands can be used. 424 | @code{(push )} will push a value onto the stack, @code{(pop)} will 425 | remove the top element of the stack and return it, @code{(top)} will return 426 | a copy of the top element, @code{(stack)} will return a copy of the whole 427 | stack, and finally @code{(rot)} will switch the top and the next-to-top 428 | elements of the stack and return a copy of the element that just became 429 | the top one. 430 | 431 | The top-level of the interpreter also has a stack; that is, typing the 432 | stack manipulation commands directly into the interpreter will produce the 433 | expected results. 434 | 435 | Note that commands called recursively do not inherit the stack of the 436 | calling command. 437 | 438 | Also note that @code{(stack)} does not return a list, it returns an 439 | arbitrary number of elements. If you find typing @code{(list (stack))} 440 | frequently, you can instead use the @code{(l-stack)} command, since they are 441 | identical. 442 | 443 | The shell does not support looping constructs in the traditional sense -- 444 | like in Scheme, looping is accomplished by using recursion. Here is 445 | a concrete example: 446 | 447 | @example 448 | (define print-squish 449 | ~(if ~(not-null? (top)) 450 | ~(begin 451 | (print (squish (pop))) 452 | (print-squish (stack))) 453 | ())) 454 | @end example 455 | 456 | The command @code{begin} simply returns the given arguments; its purpose 457 | is to allow the use of several commands where one is asked for. 458 | (@pxref{Command List} for the syntax of @code{if}) 459 | 460 | Since @code{esh} 0.2, there is an explicit @code{true} and @code{false} 461 | value; these values are different from all other possible values. Commands 462 | that operate on predicates (@code{if}, @code{and}, @code{or}, @code{=}, etc.) 463 | use these values extensively. You can use them in your own scripts with 464 | the @code{true} and @code{false} commands. Note that @code{if}, @code{and}, 465 | and @code{or} do not explicitly check for @code{true}, so to these commands 466 | any value that isn't @code{false} is "true", so to say. 467 | 468 | @node Quoting Trickery, Command List, Semantics, Details 469 | @cindex Quoting 470 | @cindex @code{eval}, quoting for 471 | @cindex Code-as-data 472 | @cindex Tilde, as a quote character 473 | @section Quoting Trickery 474 | 475 | (Note: Since @code{esh} 0.6, the backslash quote has been removed. 476 | Use the tilde quote instead.) 477 | 478 | Note that the meaning of the quote syntax is much different in @code{esh} 479 | than in Lisp -- in Lisp, the single quote is a syntactic trick to 480 | allow inputting lists and symbols as literals. In @code{esh}, the tilde 481 | quote has a very clear semantic meaning -- whenever a list is marked with 482 | a tilde, it is marked as such throughout it's life. Commands such as 483 | @code{eval} can then use this information to see which lists were supposed 484 | to be evaluated, and which ones were meant to be left alone. 485 | 486 | In other words, any list marked with a tilde has a numeric flag set on it, 487 | which is preserved on the list until the list is deleted. 488 | 489 | Also, nested tildes increase the "strength" of the tilde, so to say. 490 | @code{eval} will not evaluate lists which have a "strength" greater than 491 | itself, and calling @code{eval} recursively will increase it's strength. 492 | 493 | This may sound confusing, but it has a simple intuitive meaning: 494 | 495 | @example 496 | (eval ~(foo ~(bar baz))) 497 | @end example 498 | 499 | Here, as you would expect, @code{eval} will only evaluate @code{foo}, and 500 | pass @code{(bar baz)} as a list to @code{foo}. 501 | 502 | Note that unlike in Lisp, @code{esh} has no "special forms"! A command 503 | that works on lists is indistinguishable from a command that works 504 | on code. That's why there is no @code{lambda} command, and also why 505 | arguments to @code{if} and firends need to be quoted with a tilde. 506 | 507 | 508 | @node Command List, Tutorial, Quoting Trickery, Details 509 | @cindex Command list 510 | @cindex Builtin command list, detailed 511 | @findex alias 512 | @findex alias-hash 513 | @findex alive? 514 | @findex and 515 | @findex begin 516 | @findex begin-last 517 | @findex car 518 | @findex car-l 519 | @findex cdr 520 | @findex chars 521 | @findex chop! 522 | @findex chop-nl! 523 | @findex clone 524 | @findex copy 525 | @findex define 526 | @findex defined? 527 | @findex exec 528 | @findex exit 529 | @findex false 530 | @findex file-open 531 | @findex file-read 532 | @findex file-type 533 | @findex file-write 534 | @findex filter 535 | @findex first 536 | @findex first-l 537 | @findex gobble 538 | @findex hash-get 539 | @findex hash-keys 540 | @findex hash-make 541 | @findex hash-put 542 | @findex help 543 | @findex interactive? 544 | @findex l-stack 545 | @findex newline 546 | @findex not 547 | @findex not-null? 548 | @findex nl 549 | @findex null 550 | @findex null? 551 | @findex match 552 | @findex or 553 | @findex parse 554 | @findex pop 555 | @findex prompt 556 | @findex push 557 | @findex read 558 | @findex repeat 559 | @findex rest 560 | @findex reverse 561 | @findex rot 562 | @findex script 563 | @findex split 564 | @findex squish 565 | @findex stack 566 | @findex standard 567 | @findex stderr 568 | @findex stderr-handler 569 | @findex substring? 570 | @findex top 571 | @findex true 572 | @findex typecheck 573 | @findex unlist 574 | @findex version 575 | @findex void 576 | @findex while 577 | @section Command List 578 | 579 | @itemize @bullet 580 | 581 | @item 582 | @code{(alias ...)} 583 | Create an alias. The first argument is the name of the alias, and the 584 | rest of the arguments is the expansion. For example, @code{(alias rm rm -i)} 585 | will create an alias called @code{rm}, so that any time @code{rm} is run, 586 | @code{rm -i} gets executed in actuality. 587 | 588 | @item 589 | @code{(alias ...)} 590 | Mimic an executable with a command. Whenever an executable named by 591 | the first string is run, the lists following get evaluated, and 592 | their output is thrown away. This process will only happen during the 593 | parsing stage, though. That is, this type of alias will only be enabled 594 | when typing commands interactively into te shell. 595 | Example: @code{(alias cd ~(cd (stack)))} to mimic the 596 | traditional syntax of @code{cd}. 597 | 598 | @item 599 | @code{(alias-hash)} 600 | Return the alias hash table. Modifying this table also modifies the 601 | actual aliases. 602 | 603 | @item 604 | @code{(alive? )} 605 | Return @code{true} if the given process is alive, and @code{false} otherwise. 606 | 607 | @item 608 | @code{(and ...)} Return @code{false} if any of the arguments evaluate to 609 | @code{false}. Warning: the inputs need to be quoted with a tilde, since this 610 | command implements a short-circuited @code{"and"}! 611 | For example, @code{(and ~(under-attack) ~(launch-nukes))} is safe to use. 612 | If the @code{"eval"} of @code{~(under-attack)} evaluates to @code{false} 613 | @code{~(launch-nukes)} will never be evaluated. 614 | 615 | @item 616 | @code{(begin ...)} 617 | Simply copy the inputs to the outputs. This command is useful when you want 618 | to use several commands as if they were a single command. Example: 619 | 620 | @example 621 | (if ~(check-me) 622 | ~(begin (print-message) 623 | (set-environment-magic)) 624 | ()) 625 | @end example 626 | 627 | @item 628 | @code{(begin-last ...)} Evaluate the given arguments sequentially, and 629 | return the value of last argument. 630 | 631 | @example 632 | (if ~(not-null? (top)) 633 | ~(begin (push foo) 634 | (print (stack)) 635 | (pop)) 636 | ()) 637 | @end example 638 | 639 | @item 640 | @code{(builtin ...)} Execute the builtin command named by the 641 | first argument, even if this name has already been @code{define}d. 642 | For example, here's how to write a custom replacement for @code{cd}: 643 | 644 | @example 645 | (define cd 646 | ~(push (squish (get HOME) "/.cdlog")) 647 | ~(file-write (file-open file (top)) 648 | (squish (file-read (file-open file (pop))) 649 | (stack) (nl))) 650 | ~(builtin cd (stack))) 651 | @end example 652 | 653 | @item 654 | @code{(car )} Simply return the first element of the list. 655 | 656 | @item 657 | @code{(car-l ...)} Return the first element. 658 | 659 | @item 660 | @code{(cdr )} Return the elements of the list after the first one. 661 | 662 | @item 663 | @code{(chars )} Return the charcters in the given string. 664 | @example 665 | (chars "foo bar") => 'f' 'o' 'o' ' ' 'b' 'a' 'r' 666 | @end example 667 | 668 | @item 669 | @code{(chop! )} Delete the last character in the given string, 670 | and return the given string. 671 | Note: this function modifies the string in-place! 672 | 673 | @item 674 | @code{(chop-nl! )} Like @code{chop!}, except that it only deletes 675 | the last character if it is a newline. 676 | 677 | @item 678 | @code{(clone )} Return the first argument X number of times, 679 | where X is the numeric value of the second argument. 680 | 681 | @item 682 | @code{(copy)} Equivalent to @code{begin}. 683 | 684 | @item 685 | @code{(define ...)} Define a new command, named by the 686 | first argument. The rest of the arguments will be passed to @code{eval} 687 | when the newly defined command is called. Example: 688 | 689 | @example 690 | (define print-nl 691 | ~(print (stack) (nl))) 692 | @end example 693 | 694 | @item 695 | @code{(defined? )} Return @code{false} if the given string has not 696 | been defined as a command. 697 | 698 | @item 699 | @code{(exec ..)} Equivalent to @code{eval}, except that the stack will 700 | be set to the first argument for the duration of evaluation. 701 | 702 | @item 703 | @code{(exit [number])} Exit with the given exit status, or zero if the 704 | command is called with no arguments. 705 | 706 | @item 707 | @code{(false ...)} Return @code{false}. 708 | 709 | @item 710 | @code{(file-open )} Open a file. The first argument specifies 711 | the type of the file, and the second the name of the file you want to open. 712 | Allowed values for the first argument: 713 | 714 | @itemize @bullet 715 | @item @code{"file"} Open a regular file for reading/writing. 716 | @item @code{"truncate"} Open a regular file for reading/writing, but truncate 717 | it first. 718 | @item @code{"append"} Open a regular file for reading/appending. 719 | @item @code{"string"} Simulate a file with a string variable. In this case, 720 | the name is used as the initial contents of the buffer. 721 | (Note: "string" files are implemented as pipes internally.) 722 | @end itemize 723 | 724 | This command return the file, or an empty list on error. 725 | 726 | @item 727 | @code{(file-read )} Read the entire file given by the argument 728 | into a single string, and return the string. @emph{Warning:} when reading 729 | the output of a pipeline, you may have to call this command several times. 730 | 731 | 732 | @item 733 | @code{(file-type )} Return a string describing what type of file 734 | is named by the given string. If such a file does not exist, return an empty 735 | list. Otherwise, one of these strings is returned: 736 | 737 | @itemize @bullet 738 | @item @code{"link"} File is a symbolic link. 739 | @item @code{"regular"} File is a regular file. 740 | @item @code{"directory"} File is a directory. 741 | @item @code{"character"} File is a character device. 742 | @item @code{"block"} File is a block device. 743 | @item @code{"pipe"} File is a FIFO pipe. 744 | @item @code{"socket"} File is a socket. 745 | @end itemize 746 | 747 | @item 748 | @code{(file-write )} Write the second argument into the 749 | first one. 750 | 751 | @item 752 | @code{(filter )} Apply the second argument to every character 753 | of the first argument. This command does not change the original string. 754 | 755 | @example 756 | (filter "string with spaces" 757 | ~(if ~(= (top) ' ') 758 | '_' 759 | ~(top))) 760 | @end example 761 | 762 | returns "string_with_spaces". 763 | 764 | 765 | @item 766 | @code{(first )} Equivalent to @code{car}. 767 | 768 | @item 769 | @code{(first-l ...)} Equivalent to @code{car-l}. 770 | 771 | @item 772 | @code{(gobble ...)} Equivalent to @code{run}, except that the 773 | output of the pipeline will be returned, as a string. 774 | 775 | 776 | @item 777 | @code{(hash-get )} Return the element in the given hash table 778 | corresponding to the given key. 779 | 780 | @item 781 | @code{(hash-keys )} Return all the keys in the given hash table. 782 | 783 | @item 784 | @code{(hash-make)} Return a new, empty hash table. 785 | 786 | @item 787 | @code{(hash-put ...)} Associate the arguments after the 788 | second one with the given key in the given hash table. 789 | 790 | @item 791 | @code{(help)} Show version number and all builtin commands. 792 | 793 | @item 794 | @code{(interactive?)} Return @code{false} if the shell has @emph{not} been 795 | started interactively. 796 | 797 | @item 798 | @code{(l-cdr )} Equivalent to @code{(list (cdr ...))}. 799 | 800 | @item 801 | @code{(l-rest )} Equivalent to @code{l-cdr}. 802 | 803 | @item 804 | @code{(l-stack)} Equivalent to @code{(list (stack))}. 805 | 806 | @item 807 | @code{(newline)} Return a newline character. 808 | 809 | @item 810 | @code{(nl)} Equivalent to @code{newline}. 811 | 812 | @item 813 | @code{(not )} Return @code{false} if the argument is @code{true}. 814 | 815 | @item 816 | @code{(not-null? )} Return @code{false} if the argument is an empty list. 817 | 818 | @item 819 | @code{(null ...)} Return an empty list. 820 | 821 | @item 822 | @code{(null? )} Return @code{true} if the argument is an empty list. 823 | 824 | @item 825 | @code{(match )} Return @code{true} if the second argument 826 | matches the first. The first argument is a regular expression. 827 | 828 | @item 829 | @code{(or ...)} Return @code{false} if all the argument evaluate to 830 | @code{false}. As with the command @code{and}, this command is also 831 | short-circuited. See the description of @code{and} for an explanation of why 832 | arguments must be quoted with a tilde. 833 | 834 | @item 835 | @code{(parse )} Parse the given string as if it was typed into the 836 | shell. 837 | 838 | @item 839 | @code{(pop)} Excise the top element from the stack, and return it. 840 | 841 | @item 842 | @code{(prompt ...)} Run the given arguments through @code{(squish (eval ...))} 843 | to produce the prompt. This command need only be run once. 844 | 845 | @item 846 | @code{(push )} Push the argument onto the stack. 847 | 848 | @item 849 | @code{(read )} Read a line of input from the user, using the given 850 | string as a prompt. Warning: this command only works if the shell is running 851 | interactively. 852 | 853 | @item 854 | @code{(repeat ...)} Repeat the arguments after the first argument 855 | @code{n} number of times, where @code{n} is the numeric value of the first 856 | argument. 857 | 858 | @item 859 | @code{(rest )} Equivalent to @code{cdr}. 860 | 861 | @item 862 | @code{(reverse ...)} Return the given arguments in reverse order. 863 | 864 | @item 865 | @code{(rot)} Switch the top and next-to-top elements of the stack, and return 866 | the element that just became the top one. 867 | 868 | @item 869 | @code{(script )} Execute the given filename as a shell script. 870 | 871 | @item 872 | @code{(split ...)} Separate the given string into words. If there are 873 | more than one arguments given, then use the arguments after the first one as 874 | field separators. Otherwise, assume that fields are separated by whitespace. 875 | Note that splitting is more complex than you'd think at first glance -- 876 | @code{(split ":.:.foo:.:bar.:baz." ":" ".")} @result{} @code{foo bar baz}. 877 | In other words, extra field separators are ignored. 878 | 879 | @item 880 | @code{(squish ...)} Concatenate any string values into a single string. List 881 | structures will be ignored. For example: 882 | @code{(squish ~(foo (bar (baz))))} @result{} @code{foobarbaz}. 883 | 884 | @item 885 | @code{(stack)} Return all the stack elements, top one first. 886 | 887 | @item 888 | @code{(standard)} Return the standard input/output. 889 | 890 | @item 891 | @code{(stderr)} Return the standard input/error. 892 | 893 | @item 894 | @code{(stderr-handler )} Define a standard error handler for 895 | all new subprocesses. This means that from now on, all executables run 896 | from the shell will send their standard error to the given file. 897 | 898 | @item 899 | @code{(substring? )} Return @code{true} if the first argument 900 | is a substring of the second. 901 | 902 | @item 903 | @code{(top)} Return a copy of the top element of the stack. 904 | 905 | @item 906 | @code{(true ...)} Return @code{true}. 907 | 908 | @item 909 | @code{(typecheck ...)} Make sure that the given arguments match the 910 | given type specification string. (@pxref{Tutorial} for a description of this 911 | type specification string.) 912 | 913 | @item 914 | @code{(unlist )} Return the elements of the given list. 915 | 916 | @item 917 | @code{(version)} Return the version of the shell, as three numbers. 918 | 919 | @item 920 | @code{(void ...)} Return nothing at all. Note: This is different from 921 | @code{null}! For example: 922 | 923 | @example 924 | (if (null (magic-sequence)) 925 | ~(my-predicate) 926 | true 927 | false) 928 | @end example 929 | 930 | is an error, since in this case @code{if} is given four arguments. 931 | (@code{null} returns an empty list.) 932 | 933 | On the other hand, 934 | 935 | @example 936 | (if (void (magic-sequence)) 937 | ~(my-predicate) 938 | true 939 | false) 940 | @end example 941 | 942 | is perfectly fine since @code{void} returns nothing at all whatsoever! 943 | 944 | @item 945 | @code{(while ...)} @code{eval} the second argument as long 946 | as the @code{eval} of the first argument is not @code{false}. The rest of 947 | the arguments are used as the stack for the duration of this command's 948 | execution. @emph{Warning:} this command is an efficiency hack. It saves the 949 | waste of creating a huge return value which would be inherent in a recursive 950 | command. Use @code{while} only if you don't care about the return value of 951 | the given commands. 952 | 953 | Example: 954 | 955 | @example 956 | (define while-rec 957 | ~(if ~(condition) 958 | ~(begin (action) 959 | (while-rec)) 960 | ())) 961 | @end example 962 | 963 | will return a value equivalent to @code{(action) (action) (action) ...}. 964 | This value could be a large memory and time waste. Therefore, when you are 965 | looping over large lists and you don't care about return values, use 966 | @code{while}. 967 | 968 | 969 | @end itemize 970 | 971 | @node Tutorial, , Command List, Details 972 | @cindex Tutorial 973 | @cindex Examples 974 | @cindex Sample code 975 | @cindex Typechecking 976 | @findex typecheck 977 | @findex prompt 978 | @findex for-each 979 | @findex file-read 980 | @findex file-write 981 | @section Tutorial 982 | 983 | For starters, let us write a simple command to iterate through a list: 984 | 985 | @example 986 | (define for-each 987 | ~(if ~(not-null? (rot)) 988 | ~(begin ((rot) (rot)) 989 | (for-each (cdr (list (stack))))) 990 | ())) 991 | @end example 992 | 993 | Using this command is simple: 994 | 995 | @example 996 | (define starrify 997 | ~(squish '*' (top) '*')) 998 | 999 | (for-each starrify foo bar baz) 1000 | @end example 1001 | 1002 | results in @code{*foo* *bar* *baz*}. 1003 | 1004 | Several notes to keep in mind: notice the tricky use of @code{rot} to 1005 | shuffle stack elements. If you don't remember, @code{rot} switches the top 1006 | and the next-to-top elements of the stack, and returns a copy of the element 1007 | that just became the top one. 1008 | 1009 | Think of it as such: the first call to @code{rot} brings the second argument 1010 | to the front and checks that the stack is not empty, at the same time. 1011 | 1012 | The second call to @code{rot} returns the first argument, and the third 1013 | call to @code{rot} returns the second element and undoes the previous call 1014 | of @code{rot} at the same time. At this point, the second argument is still 1015 | on top of the stack. 1016 | 1017 | Finally, when @code{for-each} is recursively called, it is given only 1018 | below the top stack element as arguments. Effectively, the second element 1019 | was excised from the stack. 1020 | 1021 | Also note that @code{((rot) (rot))} is calling a command, even though the 1022 | name of the command is not explicit. 1023 | 1024 | Now for another example. Suppose that you want to remind yourself which 1025 | directory serves which purpose, and you'd like a descriptive string to 1026 | be listed along with a directory name in the prompt. 1027 | (i.e. @code{[/home/ivan/src/xcf => Backup of GIMP artwork]$ }) 1028 | 1029 | @example 1030 | (prompt ~(push (get PWD)) 1031 | ~(push (hash-get (dir-names) (top))) 1032 | "[" 1033 | ~(rot) 1034 | ~(if ~(not-null? (rot)) 1035 | ~(squish " => " (top)) 1036 | "") 1037 | "]$ " 1038 | ~(null (pop)) 1039 | ~(null (pop))) 1040 | @end example 1041 | 1042 | The meaning of @code{prompt} is simple -- whatever arguments are given to 1043 | it are passed through @code{eval} and @code{squish} to become the string 1044 | specifying the prompt. Again, as far as quoting is concerned, @code{prompt} 1045 | is identical to @code{eval}. 1046 | 1047 | Also, the command @code{null} simply returns an empty list. 1048 | 1049 | To finish the job, you should insert the following code into your 1050 | @code{.eshrc}: 1051 | 1052 | @example 1053 | (define dir-names (hash-make)) 1054 | 1055 | (hash-put (dir-names) "/home/ivan/src/xcf" "Backup of GIMP artwork") 1056 | @end example 1057 | 1058 | 1059 | This is a simple script that illustrates simple file I/O: 1060 | 1061 | @example 1062 | (define prepend 1063 | ~(push (file-read (file-open file (pop)))) 1064 | ~(push (squish (pop) 1065 | (file-read (file-open file (top))))) 1066 | ~(file-write (file-open truncate (rot)) (rot))) 1067 | @end example 1068 | 1069 | A script such as this was used to append a header to every source code file 1070 | in the @code{esh} distribution. Note the @code{typecheck} command; it is 1071 | very useful for insuring the consistency of arguments to commands. 1072 | 1073 | The only tricky part about using @code{typecheck} is the syntax of the 1074 | first argument. Here is an explanation: 1075 | 1076 | @itemize @bullet 1077 | @item @code{s} Make sure that the next argument is a single string. 1078 | @item @code{l} Make sure that the next argument is a single list. 1079 | @item @code{h} Make sure that the next argument is a single hash table. 1080 | @item @code{b} Make sure that the next argument is a single boolean. 1081 | @item @code{f} Make sure that the next argument is a single file. 1082 | @item @code{p} Make sure that the next argument is a PID. 1083 | @item @code{S} Match any number of strings. 1084 | @item @code{L} Match any number of lists. 1085 | @item @code{H} Match any number of hash tables. 1086 | @item @code{B} Match any number of booleans. 1087 | @item @code{F} Match any number of files. 1088 | @item @code{P} Match any number of PID's. 1089 | @item @code{?} Match any one element. 1090 | @item @code{*} Match any number of any elements. 1091 | @item @code{(} Match a list only if the sublist passes typechecking on the 1092 | string after the parentheses. 1093 | @item @code{)} Match end-of-list. 1094 | @end itemize 1095 | 1096 | For example, @code{"H(ss)s"} will match any list of arguments that begin 1097 | with an arbitrary number of hash tables, followed by a list of two strings, 1098 | and ends with a single string. 1099 | 1100 | 1101 | @node Differences from Scheme, Differences from sh, Details, Top 1102 | @cindex Differences from Scheme 1103 | @cindex Scheme 1104 | @cindex Lisp 1105 | @cindex Differences 1106 | @chapter Differences from Scheme 1107 | 1108 | 1109 | Note that in @code{esh} there is no numeric or "symbol" type -- all scalars are 1110 | either strings, booleans, files, or process ID's. Most importantly, 1111 | there is no "procedure" type -- hence, no @code{lambda} command. From the point 1112 | of view of the interpreter, a command and a list are indistinguishable. 1113 | 1114 | Don't be mislead by the naming of @code{define} -- it is actually equivalent to 1115 | a Lisp-like macro definition. 1116 | 1117 | Also, @code{car} and friends are more complicated because commands in 1118 | @code{esh} 1119 | can return any number of arguments. This is why @code{(car (split "foo bar"))} 1120 | doesn't work -- @code{car} expects a list, while @code{split} returns 1121 | an arbitrary number of elements. In a case like this, it is more convenient 1122 | to write @code{(car-l (split "foo bar"))} instead, which is equivalent 1123 | to @code{(car (list (split "foo bar")))}. 1124 | 1125 | @node Differences from sh, Command Index, Differences from Scheme, Top 1126 | @cindex Differences from sh 1127 | @cindex Differences 1128 | @cindex bash 1129 | @cindex csh 1130 | @cindex sh 1131 | @cindex tcsh 1132 | @chapter Differences from sh 1133 | 1134 | The most obvious difference is the syntax -- other shells normally don't 1135 | make much of a distinction between commands from a disk executable and 1136 | the shell interpreter's builtin commands. Not so in @code{esh}. 1137 | While other shell normally operate by complicated string substitution 1138 | and matching rules, @code{esh} mainly uses command definitions and 1139 | recursion. In that sense, it is more similar to a "real" programming 1140 | language. @code{esh} also supports more complicated data structures -- 1141 | lists and hash tables, for example. However, string operations are 1142 | lacking in comparison to other shells. 1143 | 1144 | @code{esh} is also more verbose and formal, though this could be beneficial 1145 | when you're trying to compose large libraries of useful routines. 1146 | (It's possible to write shell "modes", much as in @code{emacs}, when using 1147 | @code{esh}.) 1148 | 1149 | @code{esh} has a more generalized support for files. Instead of limiting 1150 | file I/O to redirection only, the same commands can be used either on pipes 1151 | or on files directly. (e.g. in the future, it may be possible to use a 1152 | network socket as the output of a pipeline.) 1153 | 1154 | Also, traditional shells have "exported" and "non-exported" environment 1155 | variables, whereas in @code{esh} all variables are exported. That is, 1156 | there's no @code{setenv} command, there's just @code{set}. 1157 | 1158 | @node Command Index, Concept Index, Differences from sh, Top 1159 | @unnumbered Command Index 1160 | 1161 | @printindex fn 1162 | 1163 | @node Concept Index, , Command Index, Top 1164 | @unnumbered Concept Index 1165 | 1166 | @printindex cp 1167 | 1168 | @contents 1169 | @bye 1170 | 1171 | -------------------------------------------------------------------------------- /builtins.c: -------------------------------------------------------------------------------- 1 | /* 2 | * esh, the Unix shell with Lisp-like syntax. 3 | * Copyright (C) 1999 Ivan Tkatchev 4 | * This source code is under the GPL. 5 | */ 6 | 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include "common.h" 20 | #include "format.h" 21 | #include "list.h" 22 | #include "gc.h" 23 | #include "hash.h" 24 | #include "job.h" 25 | #include "esh.h" 26 | #include "builtins.h" 27 | #include "read.h" 28 | 29 | 30 | 31 | static int typecheck_aux(char* tspec, list* data, int* i, int quiet) { 32 | int len = strlen(tspec); 33 | int err = 0; 34 | int stoploop = 0; 35 | 36 | for (; (*i) <= len; (*i)++) { 37 | 38 | if (stoploop || err) break; 39 | 40 | if (!data) { 41 | if (tspec[(*i)] && tspec[(*i)] != ')') { 42 | err = 3; 43 | } 44 | 45 | break; 46 | } 47 | 48 | switch (tspec[(*i)]) { 49 | case 's': 50 | case 'l': 51 | case 'h': 52 | case 'b': 53 | case 'f': 54 | case 'p': 55 | { 56 | int type = TYPE_STRING; 57 | 58 | switch (tspec[(*i)]) { 59 | case 's': type = TYPE_STRING; break; 60 | case 'l': type = TYPE_LIST; break; 61 | case 'h': type = TYPE_HASH; break; 62 | case 'b': type = TYPE_BOOL; break; 63 | case 'f': type = TYPE_FD; break; 64 | case 'p': type = TYPE_PROC; break; 65 | } 66 | 67 | if (ls_type(data) != type) err = 1; 68 | 69 | data = ls_next(data); 70 | break; 71 | } 72 | 73 | case '?': 74 | data = ls_next(data); 75 | break; 76 | 77 | case ')': 78 | case '\0': 79 | err = 2; 80 | stoploop = 1; 81 | break; 82 | 83 | case '*': 84 | while (data) { 85 | data = ls_next(data); 86 | } 87 | 88 | break; 89 | 90 | case '(': 91 | if (ls_type(data) != TYPE_LIST) { 92 | err = 1; 93 | 94 | } else { 95 | (*i)++; 96 | err = typecheck_aux(tspec, ls_data(data), i, quiet); 97 | } 98 | 99 | data = ls_next(data); 100 | break; 101 | 102 | case 'S': 103 | case 'L': 104 | case 'H': 105 | case 'B': 106 | case 'F': 107 | case 'P': 108 | { 109 | int type = TYPE_STRING; 110 | 111 | switch (tspec[(*i)]) { 112 | case 'S': type = TYPE_STRING; break; 113 | case 'L': type = TYPE_LIST; break; 114 | case 'H': type = TYPE_HASH; break; 115 | case 'B': type = TYPE_BOOL; break; 116 | case 'F': type = TYPE_FD; break; 117 | case 'P': type = TYPE_PROC; break; 118 | } 119 | 120 | if (ls_type(data) != type) { 121 | err = 1; 122 | break; 123 | } 124 | 125 | while (data && ls_type(data) == type) { 126 | data = ls_next(data); 127 | } 128 | 129 | break; 130 | } 131 | 132 | default: 133 | err = 4; 134 | stoploop = 1; 135 | break; 136 | 137 | } 138 | } 139 | 140 | if (data && !err) err = 2; 141 | 142 | if (!quiet) { 143 | switch (err) { 144 | case 1: 145 | error("esh: type of the given arguments did not match " 146 | "the required type."); 147 | break; 148 | 149 | case 2: 150 | error("esh: extraneous arguments given."); 151 | break; 152 | 153 | case 3: 154 | error("esh: not enough arguments given."); 155 | break; 156 | 157 | case 4: 158 | error("esh: format for type specification string is incorrect."); 159 | break; 160 | } 161 | } 162 | 163 | return err; 164 | } 165 | 166 | 167 | static inline int typecheck(char* tspec, list* arg) { 168 | int i = 0; 169 | 170 | return typecheck_aux(tspec, arg, &i, 0); 171 | } 172 | 173 | 174 | static inline int quiet_typecheck(char* tspec, list* arg) { 175 | int i = 0; 176 | 177 | return typecheck_aux(tspec, arg, &i, 1); 178 | } 179 | 180 | 181 | static int fancy_typecheck(char* tspec, list* arg, 182 | char* cmdname, char* cmddesc) { 183 | 184 | int ret = typecheck(tspec, arg); 185 | 186 | if (ret) { 187 | int len = strlen(tspec); 188 | int i; 189 | 190 | printf("\nUsage: (%s", cmdname); 191 | 192 | for (i = 0; i < len; i++) { 193 | printf(" "); 194 | 195 | switch (tspec[i]) { 196 | case 's': 197 | printf(""); 198 | break; 199 | 200 | case 'l': 201 | printf(""); 202 | break; 203 | 204 | case 'h': 205 | printf(""); 206 | break; 207 | 208 | case 'b': 209 | printf(""); 210 | break; 211 | 212 | case 'f': 213 | printf(""); 214 | break; 215 | 216 | case 'p': 217 | printf(""); 218 | break; 219 | 220 | case '?': 221 | printf(""); 222 | break; 223 | 224 | case 'S': 225 | printf("..."); 226 | break; 227 | 228 | case 'L': 229 | printf("..."); 230 | break; 231 | 232 | case 'H': 233 | printf("..."); 234 | break; 235 | 236 | case 'B': 237 | printf("..."); 238 | break; 239 | 240 | case 'F': 241 | printf("..."); 242 | break; 243 | 244 | case 'P': 245 | printf("..."); 246 | break; 247 | 248 | case '*': 249 | printf("..."); 250 | break; 251 | 252 | case '(': 253 | printf("("); 254 | break; 255 | 256 | case ')': 257 | printf(")"); 258 | break; 259 | } 260 | } 261 | 262 | printf(")\n%s\n\n", cmddesc); 263 | } 264 | 265 | return ret; 266 | } 267 | 268 | 269 | static long do_atoi(char* dat, int* err, long def) { 270 | char* merr; 271 | long ret; 272 | 273 | if (!dat) return def; 274 | 275 | ret = strtol(dat, &merr, 0); 276 | *err = 0; 277 | 278 | if (*dat == '\0' || *merr != '\0') { 279 | *err = 1; 280 | return def; 281 | } 282 | 283 | return ret; 284 | } 285 | 286 | list* car(list* arg) { 287 | list* ret = NULL; 288 | list* foo; 289 | 290 | if (!arg) return NULL; 291 | 292 | switch (ls_type(arg)) { 293 | case TYPE_LIST: 294 | foo = ls_copy(ls_data(arg)); 295 | ret = ls_cons(foo, NULL); 296 | break; 297 | 298 | case TYPE_STRING: 299 | case TYPE_FD: 300 | case TYPE_PROC: 301 | gc_inc_ref(ls_data(arg)); 302 | ret = ls_cons(ls_data(arg), NULL); 303 | break; 304 | 305 | case TYPE_HASH: 306 | hash_inc_ref(ls_data(arg)); 307 | gc_inc_ref(ls_data(arg)); 308 | 309 | ret = ls_cons(ls_data(arg), NULL); 310 | break; 311 | 312 | case TYPE_BOOL: 313 | ret = ls_cons(ls_data(arg), NULL); 314 | break; 315 | } 316 | 317 | ls_type_set(ret, ls_type(arg)); 318 | ls_flag_set(ret, ls_flag(arg)); 319 | 320 | return ret; 321 | } 322 | 323 | 324 | 325 | void register_chdir(void) { 326 | char* buff1; 327 | char* buff2; 328 | char* foo = getenv("PWD"); 329 | char bar[256]; 330 | 331 | if (!getcwd(bar, 256)) { 332 | error("esh: cd: cannot read current directory."); 333 | return; 334 | } 335 | 336 | /* Hack -- this will memory leak slightly, but will work on 337 | * systems that expect a mallocated string in "putenv". */ 338 | 339 | buff2 = (char*)malloc(sizeof(char) * (strlen(bar) + 6)); 340 | 341 | sprintf(buff2, "PWD=%s", bar); 342 | 343 | if (foo) { 344 | buff1 = (char*)malloc(sizeof(char) * (strlen(foo) + 9)); 345 | 346 | sprintf(buff1, "OLDPWD=%s", foo); 347 | putenv(buff1); 348 | } 349 | 350 | putenv(buff2); 351 | } 352 | 353 | 354 | static list* cd(list* arg) { 355 | char* dirnm; 356 | 357 | if (arg && ls_data(arg) && 358 | fancy_typecheck("s", arg, "cd", 359 | "This command changes the current directory.")) { 360 | return NULL; 361 | } 362 | 363 | if (!arg) { 364 | dirnm = getenv("HOME"); 365 | } else { 366 | dirnm = ls_data(arg); 367 | } 368 | 369 | if (!dirnm) { 370 | dirnm = "/"; 371 | } 372 | 373 | if (dirnm[0] == '-' && dirnm[1] == '\0') { 374 | char* ge = getenv("OLDPWD"); 375 | 376 | if (ge) { 377 | dirnm = ge; 378 | 379 | } else { 380 | error("esh: there is no previous directory."); 381 | return NULL; 382 | } 383 | } 384 | 385 | if (chdir(dirnm) < 0) { 386 | error("esh: cd: could not change to directory \"%s\".", dirnm); 387 | 388 | } else { 389 | register_chdir(); 390 | } 391 | 392 | return NULL; 393 | } 394 | 395 | 396 | 397 | static list* help(list* arg) { 398 | int i = 0; 399 | 400 | printf("esh version %d.%d.%d, builtin command list.\n" 401 | "To get help on an individual command, try running the command\n" 402 | "without any arguments.\n\n", VERSION_MAJOR, VERSION_MINOR, 403 | VERSION_PATCH); 404 | 405 | while (builtins_array[i].key) { 406 | printf("%s\n", builtins_array[i].key); 407 | i++; 408 | } 409 | 410 | return NULL; 411 | } 412 | 413 | 414 | list* eval_aux(list* arg, int mode, int strength) { 415 | list* iter; 416 | list* ret = NULL; 417 | list* tmp; 418 | list* tmp2; 419 | 420 | for (iter = arg; iter != NULL; iter = ls_next(iter)) { 421 | 422 | switch (ls_type(iter)) { 423 | case TYPE_LIST: 424 | { 425 | list* rec = ls_data(iter); 426 | 427 | if (!mode && strength < ls_flag(iter)) { 428 | strength = ls_flag(iter); 429 | } 430 | 431 | if (strength < ls_flag(iter)) { 432 | 433 | ret = ls_cons(ls_copy(rec), ret); 434 | ls_type_set(ret, TYPE_LIST); 435 | ls_flag_set(ret, ls_flag(iter)); 436 | 437 | } else { 438 | 439 | tmp = eval_aux(rec, 1, strength); 440 | 441 | if (tmp) { 442 | for (tmp2 = tmp; tmp2 != NULL; tmp2 = ls_next(tmp2)) { 443 | 444 | if (ls_type(tmp2) == TYPE_VOID) continue; 445 | 446 | ret = ls_cons(ls_data(tmp2), ret); 447 | ls_type_set(ret, ls_type(tmp2)); 448 | ls_flag_set(ret, ls_flag(tmp2)); 449 | } 450 | 451 | ls_free_shallow(tmp); 452 | 453 | } else { 454 | ret = ls_cons(NULL, ret); 455 | ls_type_set(ret, TYPE_LIST); 456 | } 457 | } 458 | } 459 | 460 | break; 461 | 462 | case TYPE_STRING: 463 | case TYPE_FD: 464 | case TYPE_PROC: 465 | gc_inc_ref(ls_data(iter)); 466 | ret = ls_cons(ls_data(iter), ret); 467 | 468 | ls_type_set(ret, ls_type(iter)); 469 | ls_flag_set(ret, ls_flag(iter)); 470 | break; 471 | 472 | case TYPE_HASH: 473 | hash_inc_ref(ls_data(iter)); 474 | gc_inc_ref(ls_data(iter)); 475 | 476 | ret = ls_cons(ls_data(iter), ret); 477 | 478 | ls_type_set(ret, TYPE_HASH); 479 | ls_flag_set(ret, ls_flag(iter)); 480 | break; 481 | 482 | case TYPE_BOOL: 483 | ret = ls_cons(ls_data(iter), ret); 484 | 485 | ls_type_set(ret, TYPE_BOOL); 486 | ls_flag_set(ret, ls_flag(iter)); 487 | break; 488 | } 489 | } 490 | 491 | ret = ls_reverse(ret); 492 | 493 | if (mode) { 494 | tmp = do_builtin(ret); 495 | 496 | ls_free_all(ret); 497 | 498 | return tmp; 499 | 500 | } else { 501 | return ret; 502 | } 503 | } 504 | 505 | 506 | inline list* eval(list* arg) { 507 | list* ret; 508 | 509 | ret = eval_aux(arg, 0, 0); 510 | 511 | return ret; 512 | } 513 | 514 | 515 | static list* set(list* arg) { 516 | int len; 517 | char* str; 518 | 519 | char* key; 520 | char* val; 521 | 522 | if (fancy_typecheck("ss", arg, "set", 523 | "This command manipulates the environment.")) { 524 | return NULL; 525 | } 526 | 527 | key = ls_data(arg); 528 | val = ls_data(ls_next(arg)); 529 | 530 | len = strlen(key) + strlen(val) + 3; 531 | str = (char*)malloc(sizeof(char) * len); 532 | 533 | sprintf(str, "%s=%s", key, val); 534 | 535 | putenv(str); 536 | 537 | return NULL; 538 | } 539 | 540 | static list* get(list* arg) { 541 | char* tmp; 542 | char* key; 543 | 544 | if (fancy_typecheck("s", arg, "get", 545 | "This command examines the environment.")) { 546 | return NULL; 547 | } 548 | 549 | key = ls_data(arg); 550 | 551 | tmp = getenv(key); 552 | 553 | if (!tmp) { 554 | return NULL; 555 | } else { 556 | return ls_cons(dynamic_strcpy(tmp), NULL); 557 | } 558 | } 559 | 560 | 561 | static list* env(list* arg) { 562 | int i; 563 | 564 | if (fancy_typecheck("", arg, "env", 565 | "This command prints the environment " 566 | "on the standard output.")) { 567 | return NULL; 568 | } 569 | 570 | for (i = 0; environ[i]; i++) { 571 | printf("%s\n", environ[i]); 572 | } 573 | 574 | return NULL; 575 | } 576 | 577 | static list* run(list* arg) { 578 | int* fd1; 579 | int* fd2; 580 | int ret; 581 | 582 | if (fancy_typecheck("bffL", arg, "run", 583 | "This command runs the specified executables, " 584 | "with a pipeline in between\neach command.\n" 585 | "The first argument specifies whether the command " 586 | "should be run\nin the background or not.\n" 587 | "The next two arguments are the input and output " 588 | "redirection files,\nrespectively. " 589 | "You can pass \"(standard\") " 590 | "to use the standard input/output.\n" 591 | "This command is equivalent to typing " 592 | "\"cmd1, cmd2, ...\" " 593 | "while running the shell\ninteractively.\n" 594 | "This command returns the PID of the pipeline, " 595 | "if the command\nis in the background.")) { 596 | return NULL; 597 | } 598 | 599 | fd1 = ls_data(ls_next(arg)); 600 | fd2 = ls_data(ls_next(ls_next(arg))); 601 | 602 | ret = do_pipe(fd1[0], fd2[1], ls_next(ls_next(ls_next(arg))), 603 | (int)ls_data(arg), 0); 604 | 605 | if ((int)ls_data(arg)) { 606 | if (ret > 0) { 607 | pid_t* foo; 608 | list* bar; 609 | 610 | foo = (pid_t*)gc_alloc(sizeof(pid_t), "run"); 611 | (*foo) = ret; 612 | bar = ls_cons(foo, NULL); 613 | ls_type_set(bar, TYPE_PROC); 614 | 615 | return bar; 616 | } 617 | 618 | } else { 619 | char* decchar = (char*)gc_alloc(sizeof(char) * 5, "run"); 620 | 621 | sprintf(decchar, "%d", ret); 622 | 623 | return ls_cons(decchar, NULL); 624 | } 625 | 626 | return NULL; 627 | } 628 | 629 | static list* run_simple(list* arg) { 630 | int ret; 631 | char* decchar; 632 | 633 | if (fancy_typecheck("L", arg, "run-simple", 634 | "This command is equivalent to " 635 | "\"(run (false) (standard) (standard) ...)\"")) { 636 | return NULL; 637 | } 638 | 639 | ret = do_pipe(STDIN_FILENO, STDOUT_FILENO, arg, 0, 0); 640 | 641 | decchar = (char*)gc_alloc(sizeof(char) * 5, "run"); 642 | 643 | sprintf(decchar, "%d", ret); 644 | 645 | return ls_cons(decchar, NULL); 646 | } 647 | 648 | static list* gobble(list* arg) { 649 | int pfd[2]; 650 | int* fd; 651 | list* ret; 652 | pid_t foo; 653 | 654 | if (fancy_typecheck("fL", arg, "gobble", 655 | "This command is equivalent to \"run\", except " 656 | "that it will\nreturn the output of the pipeline, " 657 | "as a string.\n" 658 | "The first argument specifies the " 659 | "pipe's input file.")) { 660 | return NULL; 661 | } 662 | 663 | if (pipe(pfd)) { 664 | error("esh: gobble: could not create a pipe."); 665 | return NULL; 666 | } 667 | 668 | 669 | fd = ls_data(arg); 670 | 671 | fcntl(pfd[0], F_SETFD, 1); 672 | fcntl(pfd[1], F_SETFD, 1); 673 | 674 | foo = do_pipe(fd[0], pfd[1], ls_next(arg), 1, 1); 675 | 676 | if (foo < 0) return NULL; 677 | 678 | ret = ls_cons(file_read(pfd[0]), NULL); 679 | 680 | close(pfd[0]); 681 | close(pfd[1]); 682 | 683 | return ret; 684 | } 685 | 686 | 687 | static list* my_exit(list* arg) { 688 | int stat = EXIT_SUCCESS; 689 | int err = 0; 690 | char* tmp = "0"; 691 | 692 | if (arg && ls_data(arg) && 693 | fancy_typecheck("s", arg, "exit", 694 | "This command exits with the given exit status.")) { 695 | return NULL; 696 | } 697 | 698 | if (arg && ls_data(arg)) { 699 | tmp = ls_data(arg); 700 | } 701 | 702 | if (tmp) { 703 | stat = do_atoi(tmp, &err, stat); 704 | 705 | if (err) { 706 | error("esh: exit: \"exit\" takes a numeric exit status."); 707 | return NULL; 708 | } 709 | } 710 | 711 | exit(stat); 712 | } 713 | 714 | 715 | static list* alias(list* arg) { 716 | list* copy = NULL; 717 | 718 | list* old = NULL; 719 | 720 | if (quiet_typecheck("sL", arg) && 721 | fancy_typecheck("sS", arg, "alias", 722 | "This command will create an alias with the given name " 723 | "and expansion.\nNote that the arguments have to be " 724 | "defined as a list, not as a string.\n" 725 | "i.e. (alias ls ls -l) not (alias ls 'ls -l').\n\n" 726 | "(alias ...) will instead evaluate the " 727 | "given\nlists as commands whenever the alias is run.\n" 728 | "i.e. (alias cd ~(cd (top)) to mimic the traditional " 729 | "syntax\nof \"cd\".")) { 730 | return NULL; 731 | } 732 | 733 | copy = ls_copy(arg); 734 | 735 | old = hash_put_inc_ref(aliases, ls_data(copy), ls_next(copy)); 736 | 737 | if (old) { 738 | gc_free(ls_data(copy)); 739 | ls_free_all(old); 740 | } 741 | 742 | gc_free(copy); 743 | 744 | return NULL; 745 | } 746 | 747 | 748 | static list* plus(list* arg) { 749 | int tot = 0; 750 | int err; 751 | char* ret = (char*)gc_alloc(sizeof(char) * 80, "plus"); 752 | char* tmp; 753 | 754 | if (fancy_typecheck("S", arg, "+", "This command adds its arguments.")) { 755 | gc_free(ret); 756 | return NULL; 757 | } 758 | 759 | for (; arg != NULL; arg = ls_next(arg)) { 760 | tmp = ls_data(arg); 761 | 762 | if (!tmp) continue; 763 | 764 | tot += do_atoi(tmp, &err, 0); 765 | 766 | if (err) { 767 | error("esh: +: \"+\" only accepts numeric arguments."); 768 | gc_free(ret); 769 | return NULL; 770 | } 771 | } 772 | 773 | sprintf(ret, "%d", tot); 774 | 775 | return ls_cons(ret, NULL); 776 | } 777 | 778 | 779 | 780 | 781 | static list* times(list* arg) { 782 | int tot = 1; 783 | int err; 784 | char* ret = (char*)gc_alloc(sizeof(char) * 80, "times"); 785 | char* tmp; 786 | 787 | if (fancy_typecheck("S", arg, "*", "This command multiplies its " 788 | "arguments.")) { 789 | gc_free(ret); 790 | return NULL; 791 | } 792 | 793 | for (; arg != NULL; arg = ls_next(arg)) { 794 | tmp = ls_data(arg); 795 | 796 | if (!tmp) continue; 797 | 798 | tot *= do_atoi(tmp, &err, 1); 799 | 800 | if (err) { 801 | error("esh: *: \"*\" only accepts numeric arguments."); 802 | gc_free(ret); 803 | return NULL; 804 | } 805 | } 806 | 807 | sprintf(ret, "%d", tot); 808 | 809 | return ls_cons(ret, NULL); 810 | } 811 | 812 | static list* minus(list* arg) { 813 | int tot = 0; 814 | int err; 815 | char* ret = (char*)gc_alloc(sizeof(char) * 80, "minus"); 816 | char* tmp; 817 | 818 | if (fancy_typecheck("sS", arg, "-", "This command subtracts " 819 | "its arguments.")) { 820 | gc_free(ret); 821 | return NULL; 822 | } 823 | 824 | 825 | tmp = ls_data(arg); 826 | 827 | tot = do_atoi(tmp, &err, 0); 828 | 829 | if (err) { 830 | error("esh: -: \"-\" only accepts numeric arguments."); 831 | gc_free(ret); 832 | return NULL; 833 | } 834 | 835 | for (arg = ls_next(arg); arg != NULL; arg = ls_next(arg)) { 836 | tmp = ls_data(arg); 837 | 838 | if (!tmp) continue; 839 | 840 | tot -= do_atoi(tmp, &err, 0); 841 | 842 | if (err) { 843 | error("esh: -: \"-\" only accepts numeric arguments."); 844 | gc_free(ret); 845 | return NULL; 846 | } 847 | } 848 | 849 | sprintf(ret, "%d", tot); 850 | 851 | return ls_cons(ret, NULL); 852 | } 853 | 854 | 855 | static list* over(list* arg) { 856 | int tot = 1; 857 | int err; 858 | char* ret = (char*)gc_alloc(sizeof(char) * 80, "over"); 859 | char* tmp; 860 | 861 | 862 | if (fancy_typecheck("sS", arg, "/", "This command divides " 863 | "its arguments.")) { 864 | gc_free(ret); 865 | return NULL; 866 | } 867 | 868 | tmp = ls_data(arg); 869 | 870 | tot = do_atoi(tmp, &err, 0); 871 | 872 | if (err) { 873 | error("esh: /: \"/\" only accepts numeric arguments."); 874 | gc_free(ret); 875 | return NULL; 876 | } 877 | 878 | for (arg = ls_next(arg); arg != NULL; arg = ls_next(arg)) { 879 | tmp = ls_data(arg); 880 | 881 | if (!tmp) continue; 882 | 883 | tot /= do_atoi(tmp, &err, 1); 884 | 885 | if (err) { 886 | error("esh: /: \"/\" only accepts numeric arguments."); 887 | gc_free(ret); 888 | return NULL; 889 | } 890 | } 891 | 892 | sprintf(ret, "%d", tot); 893 | 894 | return ls_cons(ret, NULL); 895 | } 896 | 897 | 898 | static list* my_eval(list* arg) { 899 | if (fancy_typecheck("*", arg, "eval", 900 | "This command will evaluate each given list as if " 901 | "each was a command.\nString values will be simply " 902 | "copied and returned.\n")) { 903 | return NULL; 904 | } 905 | 906 | return eval(arg); 907 | } 908 | 909 | 910 | static job_t* nth_job(int i) { 911 | int j; 912 | list* iter = jobs; 913 | 914 | for (j = 0; j < i; j++) { 915 | if (!iter) return NULL; 916 | 917 | iter = ls_next(iter); 918 | } 919 | 920 | return ls_data(iter); 921 | } 922 | 923 | 924 | static list* fg(list* arg) { 925 | if (arg && 926 | fancy_typecheck("s", arg, "fg", 927 | "This command brings a job into the foreground.\n" 928 | "The optional argument specifies which job number " 929 | "to use, as given by (jobs).\nIf without arguments, " 930 | "the first job will be used.")) { 931 | return NULL; 932 | } 933 | 934 | if (!jobs) { 935 | error("esh: fg: no jobs are running."); 936 | 937 | } else { 938 | job_t* job; 939 | int i = 0, err = 0; 940 | 941 | if (arg) { 942 | i = do_atoi(ls_data(arg), &err, i); 943 | 944 | if (err) { 945 | error("esh: fg: \"fg\" accepts only a numeric argument."); 946 | return NULL; 947 | } 948 | } 949 | 950 | job = nth_job(i); 951 | 952 | if (!job) { 953 | error("esh: fg: invalid job number."); 954 | return NULL; 955 | } 956 | 957 | job_foreground(job); 958 | } 959 | 960 | return NULL; 961 | } 962 | 963 | 964 | 965 | 966 | static list* bg(list* arg) { 967 | if (arg && 968 | fancy_typecheck("s", arg, "bg", 969 | "This command brings a job into the background.\n" 970 | "The optional argument specifies which job number " 971 | "to use, as given by (jobs).\nIf without arguments, " 972 | "the first job will be used.")) { 973 | return NULL; 974 | } 975 | 976 | if (!jobs) { 977 | error("esh: bg: no jobs are running."); 978 | 979 | } else { 980 | job_t* job; 981 | int i = 0, err = 0; 982 | 983 | if (arg) { 984 | i = do_atoi(ls_data(arg), &err, i); 985 | 986 | if (err) { 987 | error("esh: bg: \"bg\" accepts only a numeric argument."); 988 | return NULL; 989 | } 990 | } 991 | 992 | job = nth_job(i); 993 | 994 | if (!job) { 995 | error("esh: bg: invalid job number."); 996 | return NULL; 997 | } 998 | 999 | job_background(job); 1000 | } 1001 | 1002 | return NULL; 1003 | } 1004 | 1005 | 1006 | 1007 | static list* list_jobs(list* arg) { 1008 | list* iter; 1009 | job_t* job; 1010 | int i = 0; 1011 | 1012 | if (fancy_typecheck("", arg, "jobs", 1013 | "This command will list all running jobs.")) { 1014 | return NULL; 1015 | } 1016 | 1017 | printf("No. %-35s %-6s %-6s %-8s\n", "Name", "PID", "PGID", "Status"); 1018 | 1019 | for (iter = jobs; iter != NULL; iter = ls_next(iter), i++) { 1020 | job = ls_data(iter); 1021 | 1022 | printf("%-3d %-35s %-6d %-6d %-8s\n", i, job->name, job->last_pid, 1023 | job->pgid, 1024 | (job->status == JOB_STOPPED ? "Stopped" : 1025 | (job->status == JOB_DEAD ? "Dead" : 1026 | "Running"))); 1027 | } 1028 | 1029 | return NULL; 1030 | } 1031 | 1032 | 1033 | static list* defined_p(list* arg) { 1034 | list* ret; 1035 | 1036 | if (fancy_typecheck("s", arg, "defined?", 1037 | "This command will return \"true\" if the string " 1038 | "has\n been defined as a command using " 1039 | "\"define\". Otherwise, return \"true\".")) { 1040 | return NULL; 1041 | } 1042 | 1043 | ret = hash_get(defines, ls_data(arg)); 1044 | 1045 | if (ret) { 1046 | return ls_copy(ls_true); 1047 | 1048 | } else { 1049 | return ls_copy(ls_false); 1050 | } 1051 | } 1052 | 1053 | 1054 | static list* my_interactive(list* arg) { 1055 | if (fancy_typecheck("", arg, "interactive?", 1056 | "This command returns \"true\" if the shell has " 1057 | "been\nstarted in interactive mode.")) { 1058 | return NULL; 1059 | } 1060 | 1061 | if (interactive) { 1062 | return ls_copy(ls_true); 1063 | 1064 | } else { 1065 | return ls_copy(ls_false); 1066 | } 1067 | } 1068 | 1069 | 1070 | 1071 | static list* define(list* arg) { 1072 | list* copy = NULL; 1073 | 1074 | list* old; 1075 | 1076 | if (fancy_typecheck("s*", arg, "define", 1077 | "This command will create a new command.\n" 1078 | "The first argument is the name, and the rest are " 1079 | "arguments that will be\nautomatically passed to " 1080 | "\"eval\" whenever the new command gets run.")) { 1081 | return NULL; 1082 | } 1083 | 1084 | copy = ls_copy(arg); 1085 | 1086 | old = hash_put(defines, ls_data(copy), ls_next(copy)); 1087 | 1088 | if (old) { 1089 | gc_free(ls_data(copy)); 1090 | ls_free_all(old); 1091 | } 1092 | 1093 | gc_free(copy); 1094 | 1095 | return NULL; 1096 | } 1097 | 1098 | 1099 | static list* set_prompt(list* arg) { 1100 | 1101 | if (fancy_typecheck("*", arg, "prompt", 1102 | "This command will set the prompt to the " 1103 | "concatenation of the\n\"eval\" of each argument.")) { 1104 | return NULL; 1105 | } 1106 | 1107 | if (prompt) { 1108 | ls_free_all(prompt); 1109 | } 1110 | 1111 | prompt = ls_copy(arg); 1112 | 1113 | return NULL; 1114 | } 1115 | 1116 | 1117 | static list* my_if(list* arg) { 1118 | list* iter; 1119 | list* tmp; 1120 | list* arg_split[3]; 1121 | int i; 1122 | 1123 | if (fancy_typecheck("???", arg, "if", 1124 | "If the \"eval\" of the first argument is a " 1125 | "\"true\", this command " 1126 | "returns the\n\"eval\" of the second argument; " 1127 | "otherwise, the \"eval\"\nof the third argument is " 1128 | "returned.")) { 1129 | return NULL; 1130 | } 1131 | 1132 | arg = ls_copy(arg); 1133 | 1134 | for (i = 0, iter = arg; iter != NULL; iter = ls_next(iter), i++) { 1135 | 1136 | arg_split[i] = ls_cons(ls_data(iter), NULL); 1137 | ls_type_set(arg_split[i], ls_type(iter)); 1138 | ls_flag_set(arg_split[i], ls_flag(iter)); 1139 | } 1140 | 1141 | ls_free_shallow(arg); 1142 | 1143 | iter = eval(arg_split[0]); 1144 | 1145 | if (iter && ls_type(iter) == TYPE_BOOL && !ls_data(iter)) { 1146 | tmp = eval(arg_split[2]); 1147 | 1148 | } else { 1149 | tmp = eval(arg_split[1]); 1150 | } 1151 | 1152 | ls_free_all(iter); 1153 | ls_free_all(arg_split[0]); 1154 | ls_free_all(arg_split[1]); 1155 | ls_free_all(arg_split[2]); 1156 | 1157 | return tmp; 1158 | } 1159 | 1160 | 1161 | static list* equal_p(list* arg) { 1162 | if (fancy_typecheck("ss", arg, "=", 1163 | "This comand checks is two strings are equal.\n" 1164 | "If yes, return \"true\".\n" 1165 | "Otherwise, return \"false\".")) { 1166 | return NULL; 1167 | } 1168 | 1169 | if (strcmp(ls_data(arg), ls_data(ls_next(arg))) == 0) { 1170 | return ls_copy(ls_true); 1171 | 1172 | } else { 1173 | return ls_copy(ls_false); 1174 | } 1175 | } 1176 | 1177 | 1178 | static list* pop(list* arg) { 1179 | list* foo; 1180 | list* ret; 1181 | 1182 | if (fancy_typecheck("", arg, "pop", 1183 | "This command will pop off a value from the local " 1184 | "variable stack.")) { 1185 | return NULL; 1186 | } 1187 | 1188 | if (!stack) return NULL; 1189 | 1190 | foo = stack; 1191 | stack = ls_next(stack); 1192 | 1193 | ret = ls_cons(ls_data(foo), NULL); 1194 | ls_type_set(ret, ls_type(foo)); 1195 | ls_flag_set(ret, ls_flag(foo)); 1196 | 1197 | gc_free(foo); 1198 | 1199 | return ret; 1200 | } 1201 | 1202 | 1203 | static list* push(list* arg) { 1204 | if (fancy_typecheck("?", arg, "push", 1205 | "This command will push on a value to the local " 1206 | "variable stack.")) { 1207 | return NULL; 1208 | } 1209 | 1210 | arg = ls_copy(arg); 1211 | 1212 | stack = ls_cons(ls_data(arg), stack); 1213 | ls_type_set(stack, ls_type(arg)); 1214 | ls_flag_set(stack, ls_flag(arg)); 1215 | 1216 | gc_free(arg); 1217 | 1218 | return NULL; 1219 | } 1220 | 1221 | 1222 | static list* top(list* arg) { 1223 | if (fancy_typecheck("", arg, "top", 1224 | "This command will return the top value on the " 1225 | "local variable\nstack, without popping it off.")) { 1226 | return NULL; 1227 | } 1228 | 1229 | return car(stack); 1230 | } 1231 | 1232 | 1233 | static list* my_list(list* arg) { 1234 | list* ret; 1235 | list* tmp; 1236 | 1237 | if (fancy_typecheck("*", arg, "list", 1238 | "This command simply returns a list composed of the " 1239 | "given arguments.")) { 1240 | return NULL; 1241 | } 1242 | 1243 | tmp = ls_copy(arg); 1244 | ret = ls_cons(tmp, NULL); 1245 | ls_type_set(ret, TYPE_LIST); 1246 | 1247 | return ret; 1248 | } 1249 | 1250 | 1251 | static list* reverse(list* arg) { 1252 | if (fancy_typecheck("*", arg, "reverse", 1253 | "This command returns the arguments in reverse " 1254 | "order.")) { 1255 | return NULL; 1256 | } 1257 | 1258 | return ls_reverse(ls_copy(arg)); 1259 | } 1260 | 1261 | 1262 | static list* my_stack(list* arg) { 1263 | if (fancy_typecheck("", arg, "stack", 1264 | "This command will return the local variable " 1265 | "stack,\ntop values first.")) { 1266 | return NULL; 1267 | } 1268 | 1269 | return ls_copy(stack); 1270 | } 1271 | 1272 | 1273 | static list* my_print(list* arg) { 1274 | if (fancy_typecheck("*", arg, "print", 1275 | "This command prints the given arguments in a\n" 1276 | "human-readable format.")) { 1277 | return NULL; 1278 | } 1279 | 1280 | ls_print(arg); 1281 | return NULL; 1282 | } 1283 | 1284 | 1285 | static list* my_hash_make(list* arg) { 1286 | hash_table* ntab; 1287 | list* ret; 1288 | 1289 | if (fancy_typecheck("", arg, "hash-make", 1290 | "This command will return a new hash table.")) { 1291 | return NULL; 1292 | } 1293 | 1294 | ntab = (hash_table*)gc_alloc(sizeof(hash_table), "my_hash_make"); 1295 | 1296 | hash_init(ntab, NULL); 1297 | 1298 | ret = ls_cons(ntab, NULL); 1299 | ls_type_set(ret, TYPE_HASH); 1300 | 1301 | return ret; 1302 | } 1303 | 1304 | 1305 | 1306 | static list* my_hash_get(list* arg) { 1307 | if (fancy_typecheck("hs", arg, "hash-get", 1308 | "This command will return the value associated with the " 1309 | "given\nkey in the given hash table.")) { 1310 | return NULL; 1311 | } 1312 | 1313 | return ls_copy(hash_get(ls_data(arg), ls_data(ls_next(arg)))); 1314 | } 1315 | 1316 | static list* my_hash_put(list* arg) { 1317 | list* copy; 1318 | list* old; 1319 | hash_table* tab; 1320 | char* key; 1321 | 1322 | if (fancy_typecheck("hs*", arg, "hash-put", 1323 | "Associate the given data to the given key in " 1324 | "the\ngiven hash table.")) { 1325 | return NULL; 1326 | } 1327 | 1328 | /* 1329 | * Note the meaning of "hash_put_inc_ref": it will set the reference 1330 | * count of the newly allocated data equal to the reference count of 1331 | * the rest of the table. 1332 | * 1333 | * For tables that always have a refcount of 1, this is unnecessary, 1334 | * but for tables that get passed back and forth between commands, this 1335 | * is vital to keep the table from imploding. 1336 | */ 1337 | 1338 | tab = ls_data(arg); 1339 | key = ls_data(ls_next(arg)); 1340 | copy = ls_copy(ls_next(ls_next(arg))); 1341 | 1342 | gc_inc_ref(key); 1343 | 1344 | old = hash_put_inc_ref(tab, key, copy); 1345 | 1346 | if (old) { 1347 | gc_free(key); 1348 | ls_free_all(old); 1349 | } 1350 | 1351 | return NULL; 1352 | } 1353 | 1354 | 1355 | static list* my_hash_keys(list* arg) { 1356 | if (fancy_typecheck("h", arg, "hash-keys", 1357 | "Return all the keys in the given hash table.")) { 1358 | return NULL; 1359 | } 1360 | 1361 | return hash_keys(ls_data(arg)); 1362 | } 1363 | 1364 | static list* alias_hash(list* arg) { 1365 | list* ret; 1366 | 1367 | if (fancy_typecheck("", arg, "alias-hash", 1368 | "Return all the aliases as a hash table.")) { 1369 | return NULL; 1370 | } 1371 | 1372 | gc_inc_ref(aliases); 1373 | hash_inc_ref(aliases); 1374 | 1375 | ret = ls_cons(aliases, NULL); 1376 | ls_type_set(ret, TYPE_HASH); 1377 | 1378 | return ret; 1379 | } 1380 | 1381 | 1382 | static list* my_car(list* arg) { 1383 | if (fancy_typecheck("l", arg, "car", 1384 | "Simply return the first element of the given list.")) { 1385 | return NULL; 1386 | } 1387 | 1388 | return car(ls_data(arg)); 1389 | } 1390 | 1391 | static list* my_car_l(list* arg) { 1392 | if (fancy_typecheck("*", arg, "car-l", 1393 | "Simply return the first argument.")) { 1394 | return NULL; 1395 | } 1396 | 1397 | return car(arg); 1398 | } 1399 | 1400 | static list* cdr(list* arg) { 1401 | if (fancy_typecheck("l", arg, "cdr", 1402 | "Simply return the elements after the first one " 1403 | "in the given list.")) { 1404 | return NULL; 1405 | } 1406 | 1407 | if (!ls_data(arg)) return NULL; 1408 | 1409 | return ls_copy(ls_next(ls_data(arg))); 1410 | } 1411 | 1412 | static list* script(list* arg) { 1413 | if (fancy_typecheck("s", arg, "script", 1414 | "Read the contents of the file named by the " 1415 | "given string,\nand execute them as a script.")) { 1416 | return NULL; 1417 | } 1418 | 1419 | do_file(ls_data(arg), 1); 1420 | 1421 | return NULL; 1422 | } 1423 | 1424 | static list* my_read(list* arg) { 1425 | char* rl_out; 1426 | 1427 | if (fancy_typecheck("s", arg, "read", 1428 | "Read a line of input from the user.\n" 1429 | "The first argument is the prompt to show the user.")) { 1430 | return NULL; 1431 | } 1432 | 1433 | if (!interactive) return NULL; 1434 | 1435 | rl_out = read_read(ls_data(arg)); 1436 | 1437 | if (!rl_out) return NULL; 1438 | 1439 | return ls_cons(rl_out, NULL); 1440 | } 1441 | 1442 | static list* squish(list* arg) { 1443 | char* buff; 1444 | 1445 | if (fancy_typecheck("*", arg, "squish", 1446 | "Concatenate all the given arguments (whether strings " 1447 | "or lists)\nand combine all the string values into one " 1448 | "long string.\nList structures have no effect " 1449 | "on the final output.\n" 1450 | "Example: (squish foo ~(bar (baz)) => \"foobarbaz\"")) { 1451 | return NULL; 1452 | } 1453 | 1454 | buff = ls_strcat(arg); 1455 | 1456 | return ls_cons(buff, NULL); 1457 | } 1458 | 1459 | 1460 | static list* my_parse(list* arg) { 1461 | list* ret; 1462 | int i = 0; 1463 | char* input; 1464 | 1465 | int len = 128; 1466 | char* value = (char*)gc_alloc(sizeof(char) * len, "my_parse"); 1467 | 1468 | 1469 | if (fancy_typecheck("s", arg, "parse", 1470 | "Parse the given string as if it were typed into " 1471 | "the shell.")) { 1472 | return NULL; 1473 | } 1474 | 1475 | input = ls_data(arg); 1476 | 1477 | ret = parse_builtin(input, &i, 0, 0); 1478 | 1479 | if (next_token(input, &i, &value, &len)) { 1480 | error("esh: extraneous characters after command."); 1481 | 1482 | ls_free_all(ret); 1483 | ret = NULL; 1484 | } 1485 | 1486 | gc_free(value); 1487 | 1488 | return ret; 1489 | } 1490 | 1491 | 1492 | static list* newline(list* arg) { 1493 | if (fancy_typecheck("", arg, "newline", 1494 | "Simply return the newline character.")) { 1495 | return NULL; 1496 | } 1497 | 1498 | return ls_cons(dynamic_strcpy("\n"), NULL); 1499 | } 1500 | 1501 | 1502 | static list* my_typecheck(list* arg) { 1503 | int ret; 1504 | 1505 | if (fancy_typecheck("s*", arg, "typecheck", 1506 | "This command checks that the types of the given " 1507 | "arguments are\nwhat you want them to be. " 1508 | "The first argument is the type specification " 1509 | "string;\nit must be in the right format.\n" 1510 | "Please see the manual for a detailed explanation.\n" 1511 | "The rest of the arguments are to be checked against " 1512 | "the first one.\n" 1513 | "This command returns \"false\" if the types of " 1514 | "the arguments\nmatch.")) { 1515 | return NULL; 1516 | } 1517 | 1518 | ret = quiet_typecheck(ls_data(arg), ls_next(arg)); 1519 | 1520 | if (!ret) { 1521 | return ls_copy(ls_false); 1522 | 1523 | } else { 1524 | return ls_copy(ls_true); 1525 | } 1526 | } 1527 | 1528 | 1529 | static list* split(list* arg) { 1530 | list* ret; 1531 | 1532 | if (fancy_typecheck("S", arg, "split", 1533 | "This command takes a single string, and returns " 1534 | "the parts of\nthe original string " 1535 | "that are separated by the given field separators.\n" 1536 | "(i.e. the arguments after the first.)\n" 1537 | "If no field separators are given, split on whitespace." 1538 | "\n" 1539 | "Example: (split 'foo bar baz') => foo bar baz")) { 1540 | return NULL; 1541 | } 1542 | 1543 | if (ls_next(arg)) { 1544 | syntax_blank = ls_strcat(ls_next(arg)); 1545 | } 1546 | 1547 | ret = parse_split(ls_data(arg)); 1548 | 1549 | if (syntax_blank) { 1550 | gc_free(syntax_blank); 1551 | syntax_blank = NULL; 1552 | } 1553 | 1554 | return ret; 1555 | } 1556 | 1557 | 1558 | static list* unlist(list* arg) { 1559 | if (fancy_typecheck("l", arg, "unlist", 1560 | "This command simply returns the elements of the " 1561 | "given list.")) { 1562 | return NULL; 1563 | } 1564 | 1565 | return ls_copy(ls_data(arg)); 1566 | } 1567 | 1568 | static list* exec(list* arg) { 1569 | list* oldstack; 1570 | list* ret; 1571 | 1572 | if (fancy_typecheck("l*", arg, "exec", 1573 | "This command is equivalent to \"eval\" except " 1574 | "that the\nstack will be set to the first " 1575 | "argument while\n\"eval\" is running.")) { 1576 | return NULL; 1577 | } 1578 | 1579 | oldstack = stack; 1580 | 1581 | stack = ls_copy(ls_data(arg)); 1582 | ret = eval(ls_next(arg)); 1583 | ls_free_all(stack); 1584 | stack = oldstack; 1585 | 1586 | return ret; 1587 | } 1588 | 1589 | 1590 | static list* rot(list* arg) { 1591 | list* foo; 1592 | 1593 | if (fancy_typecheck("", arg, "rot", 1594 | "This command switches the top and the next-to-top " 1595 | "elements of the stack.\nThe element that just " 1596 | "became the top element is returned.")) { 1597 | return NULL; 1598 | } 1599 | 1600 | if (quiet_typecheck("?*", stack)) return NULL; 1601 | 1602 | foo = stack; 1603 | stack = ls_next(ls_next(stack)); 1604 | 1605 | stack = ls_cons(ls_data(foo), stack); 1606 | ls_type_set(stack, ls_type(foo)); 1607 | ls_flag_set(stack, ls_flag(foo)); 1608 | 1609 | stack = ls_cons(ls_data(ls_next(foo)), stack); 1610 | ls_type_set(stack, ls_type(ls_next(foo))); 1611 | ls_flag_set(stack, ls_flag(ls_next(foo))); 1612 | 1613 | gc_free(ls_next(foo)); 1614 | gc_free(foo); 1615 | 1616 | return car(stack); 1617 | } 1618 | 1619 | 1620 | static list* list_stack(list* arg) { 1621 | list* ret; 1622 | 1623 | if (fancy_typecheck("", arg, "l-stack", 1624 | "This command will return the local variable " 1625 | "stack,\nas a list, top values first.")) { 1626 | return NULL; 1627 | } 1628 | 1629 | ret = ls_cons(ls_copy(stack), NULL); 1630 | ls_type_set(ret, TYPE_LIST); 1631 | 1632 | return ret; 1633 | } 1634 | 1635 | static list* list_cdr(list* arg) { 1636 | list* ret; 1637 | 1638 | if (fancy_typecheck("l", arg, "l-cdr", 1639 | "Simply return the elements after the first one " 1640 | "in the given list,\nas a list.\n" 1641 | "This command is equivalent to (list (cdr ...)).")) { 1642 | return NULL; 1643 | } 1644 | 1645 | if (!ls_data(arg)) return NULL; 1646 | 1647 | ret = ls_cons(ls_copy(ls_next(ls_data(arg))), NULL); 1648 | ls_type_set(ret, TYPE_LIST); 1649 | 1650 | return ret; 1651 | } 1652 | 1653 | static list* my_null(list* arg) { 1654 | return NULL; 1655 | } 1656 | 1657 | static list* my_null_p(list* arg) { 1658 | if (fancy_typecheck("?", arg, "null?", 1659 | "This command returns \"true\" if the argument " 1660 | "is an empty list.")) { 1661 | return NULL; 1662 | } 1663 | 1664 | if (!ls_data(arg)) { 1665 | return ls_copy(ls_true); 1666 | } else { 1667 | return ls_copy(ls_false); 1668 | } 1669 | } 1670 | 1671 | static list* my_not_null_p(list* arg) { 1672 | if (fancy_typecheck("?", arg, "not-null?", 1673 | "This command returns \"true\" if the argument " 1674 | "is NOT an empty list.")) { 1675 | return NULL; 1676 | } 1677 | 1678 | if (!ls_data(arg)) { 1679 | return ls_copy(ls_false); 1680 | } else { 1681 | return ls_copy(ls_true); 1682 | } 1683 | } 1684 | 1685 | static list* my_true(list* arg) { 1686 | return ls_copy(ls_true); 1687 | } 1688 | 1689 | static list* my_false(list* arg) { 1690 | return ls_copy(ls_false); 1691 | } 1692 | 1693 | static list* my_file_open(list* arg) { 1694 | int* ret = (int*)gc_alloc(sizeof(int) * 2, "my_file_open"); 1695 | 1696 | char* first; 1697 | char* name; 1698 | 1699 | list* foo = NULL; 1700 | 1701 | if (fancy_typecheck("ss", arg, "file-open", 1702 | "This command opens a file.\n" 1703 | "The first argument describes what type of " 1704 | "file to open.\n" 1705 | "Possible values are:\n" 1706 | "\"file\" -- Open a regular file for " 1707 | "reading/writing.\n" 1708 | "\"truncate\" -- Open a regular file for " 1709 | "reading/writing, truncating it first.\n" 1710 | "\"append\" -- Open a regular file for " 1711 | "reading/appending.\n" 1712 | "\"string\" -- Simulate a file with a string " 1713 | "variable.\n\n" 1714 | "The second argument is either a filename or the " 1715 | "initial value of the\nstring buffer.\n" 1716 | "This command returns a file, or an empty list " 1717 | "on error.")) { 1718 | gc_free(ret); 1719 | return NULL; 1720 | } 1721 | 1722 | /* Note: The first fd is for reading, the second is for writing. 1723 | * in other words, stdin is [0], stdout is [1]. */ 1724 | 1725 | ret[0] = -1; 1726 | ret[1] = -1; 1727 | 1728 | first = ls_data(arg); 1729 | name = ls_data(ls_next(arg)); 1730 | 1731 | switch (first[0]) { 1732 | case 'f': 1733 | ret[0] = open(name, O_RDWR | O_CREAT, 0644); 1734 | ret[1] = ret[0]; 1735 | break; 1736 | 1737 | case 't': 1738 | ret[0] = open(name, O_RDWR | O_CREAT | O_TRUNC, 0644); 1739 | ret[1] = ret[0]; 1740 | break; 1741 | 1742 | case 'a': 1743 | ret[0] = open(name, O_RDWR | O_CREAT | O_APPEND, 0644); 1744 | ret[1] = ret[0]; 1745 | break; 1746 | 1747 | case 's': 1748 | if (pipe(ret) < 0) { 1749 | ret[0] = -1; 1750 | ret[1] = -1; 1751 | 1752 | } else { 1753 | file_write(ret[1], name); 1754 | } 1755 | break; 1756 | 1757 | default: 1758 | error("esh: file-open: don't know how to open a file using \"%s\".", 1759 | first); 1760 | gc_free(ret); 1761 | return NULL; 1762 | break; 1763 | } 1764 | 1765 | if (ret[0] < 0 || ret[1] < 0) { 1766 | error("esh: file-open: couldn't open \"%s\" with mode \"%s\".", 1767 | name, first); 1768 | gc_free(ret); 1769 | return NULL; 1770 | } 1771 | 1772 | fcntl(ret[0], F_SETFD, 1); 1773 | fcntl(ret[1], F_SETFD, 1); 1774 | 1775 | foo = ls_cons(ret, NULL); 1776 | ls_type_set(foo, TYPE_FD); 1777 | return foo; 1778 | } 1779 | 1780 | 1781 | static list* my_file_read(list* arg) { 1782 | int* fd; 1783 | list* ret; 1784 | 1785 | int flags; 1786 | 1787 | if (fancy_typecheck("f", arg, "file-read", 1788 | "This command returns the entire contents of the " 1789 | "given file,\nas a single string.")) { 1790 | return NULL; 1791 | } 1792 | 1793 | fd = ls_data(arg); 1794 | 1795 | flags = fcntl(fd[0], F_GETFL); 1796 | 1797 | fcntl(fd[0], F_SETFL, flags | O_NONBLOCK); 1798 | 1799 | ret = ls_cons(file_read(fd[0]), NULL); 1800 | 1801 | fcntl(fd[0], F_SETFL, flags); 1802 | 1803 | return ret; 1804 | } 1805 | 1806 | static list* my_file_read_block(list* arg) { 1807 | int* fd; 1808 | list* ret; 1809 | 1810 | if (fancy_typecheck("f", arg, "file-read-block", 1811 | "This command returns the entire contents of the " 1812 | "given file,\nas a single string.\n" 1813 | "This command differs from \"file-read\" " 1814 | "in that it will\nwait until the whole " 1815 | "file is read.\n" 1816 | "Use with caution, as this could cause the " 1817 | "shell to enter\nan infinite loop.")) { 1818 | return NULL; 1819 | } 1820 | 1821 | fd = ls_data(arg); 1822 | 1823 | ret = ls_cons(file_read(fd[0]), NULL); 1824 | 1825 | return ret; 1826 | } 1827 | 1828 | static list* my_file_write(list* arg) { 1829 | int* fd; 1830 | int flags; 1831 | 1832 | if (fancy_typecheck("fs", arg, "file-write", 1833 | "This command writes the second argument into the " 1834 | "first argument.")) { 1835 | return NULL; 1836 | } 1837 | 1838 | fd = ls_data(arg); 1839 | 1840 | flags = fcntl(fd[0], F_GETFL); 1841 | 1842 | fcntl(fd[1], F_SETFL, flags | O_NONBLOCK); 1843 | 1844 | file_write(fd[1], ls_data(ls_next(arg))); 1845 | 1846 | fcntl(fd[1], F_SETFL, flags); 1847 | 1848 | return NULL; 1849 | } 1850 | 1851 | static list* my_file_type(list* arg) { 1852 | struct stat sbuff; 1853 | 1854 | if (fancy_typecheck("s", arg, "file-type", 1855 | "This command returns a string describing what " 1856 | "the given file is.\n" 1857 | "If the file does not exist, this command returns " 1858 | "\"false\".\n" 1859 | "Otherwise, one of these strings is returned:\n" 1860 | "\"link\" - File is a symbolic link.\n" 1861 | "\"regular\" - File is a regular file.\n" 1862 | "\"directory\" - File is a directory.\n" 1863 | "\"character\" - File is a character device.\n" 1864 | "\"block\" - File is a block device.\n" 1865 | "\"pipe\" - File is a FIFO pipe.\n" 1866 | "\"socket\" - File is a socket.\n")) { 1867 | return NULL; 1868 | } 1869 | 1870 | if (lstat(ls_data(arg), &sbuff)) { 1871 | return ls_copy(ls_false); 1872 | } 1873 | 1874 | if (S_ISLNK(sbuff.st_mode)) { 1875 | return ls_cons(dynamic_strcpy("link"), NULL); 1876 | 1877 | } else if (S_ISREG(sbuff.st_mode)) { 1878 | return ls_cons(dynamic_strcpy("regular"), NULL); 1879 | 1880 | } else if (S_ISDIR(sbuff.st_mode)) { 1881 | return ls_cons(dynamic_strcpy("directory"), NULL); 1882 | 1883 | } else if (S_ISCHR(sbuff.st_mode)) { 1884 | return ls_cons(dynamic_strcpy("character"), NULL); 1885 | 1886 | } else if (S_ISBLK(sbuff.st_mode)) { 1887 | return ls_cons(dynamic_strcpy("block"), NULL); 1888 | 1889 | } else if (S_ISFIFO(sbuff.st_mode)) { 1890 | return ls_cons(dynamic_strcpy("pipe"), NULL); 1891 | 1892 | } else if (S_ISSOCK(sbuff.st_mode)) { 1893 | return ls_cons(dynamic_strcpy("socket"), NULL); 1894 | 1895 | } else { 1896 | return NULL; 1897 | } 1898 | } 1899 | 1900 | static list* standard(list* arg) { 1901 | if (fancy_typecheck("", arg, "standard", 1902 | "This command returns the standard input/standard " 1903 | "output file.")) { 1904 | return NULL; 1905 | } 1906 | 1907 | return ls_copy(ls_stdio); 1908 | } 1909 | 1910 | 1911 | static list* and(list* arg) { 1912 | list* iter; 1913 | list* tmp1 = NULL; 1914 | list* tmp2 = NULL; 1915 | 1916 | if (fancy_typecheck("*", arg, "and", 1917 | "This command returns \"false\" if any argument " 1918 | "is \"false\".\n" 1919 | "Note: arguments should be quoted with a tilde!")) { 1920 | return NULL; 1921 | } 1922 | 1923 | for (iter = arg; iter != NULL; iter = ls_next(iter)) { 1924 | ls_free_all(tmp1); 1925 | ls_free_all(tmp2); 1926 | 1927 | tmp1 = car(iter); 1928 | tmp2 = eval(tmp1); 1929 | 1930 | if (tmp2 && ls_type(tmp2) == TYPE_BOOL && !ls_data(tmp2)) { 1931 | ls_free_all(tmp1); 1932 | ls_free_all(tmp2); 1933 | return ls_copy(ls_false); 1934 | } 1935 | } 1936 | 1937 | ls_free_all(tmp1); 1938 | return tmp2; 1939 | } 1940 | 1941 | 1942 | 1943 | static list* or(list* arg) { 1944 | list* iter; 1945 | list* tmp1; 1946 | list* tmp2; 1947 | 1948 | if (fancy_typecheck("*", arg, "or", 1949 | "This command returns \"false\" if all arguments " 1950 | "are \"false\".\n" 1951 | "Note: arguments should be quoted with a tilde!")) { 1952 | return NULL; 1953 | } 1954 | 1955 | for (iter = arg; iter != NULL; iter = ls_next(iter)) { 1956 | tmp1 = car(iter); 1957 | tmp2 = eval(tmp1); 1958 | 1959 | if (!tmp2 || ls_type(tmp2) != TYPE_BOOL || ls_data(tmp2)) { 1960 | ls_free_all(tmp1); 1961 | return tmp2; 1962 | } 1963 | 1964 | ls_free_all(tmp1); 1965 | ls_free_all(tmp2); 1966 | } 1967 | 1968 | return ls_copy(ls_false); 1969 | } 1970 | 1971 | static list* not(list* arg) { 1972 | if (fancy_typecheck("b", arg, "not", 1973 | "This command returns \"false\" if the argument is " 1974 | "\"true\".")) { 1975 | return NULL; 1976 | } 1977 | 1978 | if (ls_data(arg)) { 1979 | return ls_copy(ls_false); 1980 | 1981 | } else { 1982 | return ls_copy(ls_true); 1983 | } 1984 | } 1985 | 1986 | 1987 | 1988 | static list* begin_last(list* arg) { 1989 | list* iter; 1990 | list* tmp1 = NULL; 1991 | list* tmp2 = NULL; 1992 | 1993 | if (fancy_typecheck("*", arg, "begin-last", 1994 | "This command evaluates the given argument, one " 1995 | "by one,\nand returns the value of the last " 1996 | "argument.")) { 1997 | return NULL; 1998 | } 1999 | 2000 | for (iter = arg; iter != NULL; iter = ls_next(iter)) { 2001 | tmp1 = car(iter); 2002 | tmp2 = eval(tmp1); 2003 | 2004 | if (!ls_next(iter)) { 2005 | ls_free_all(tmp1); 2006 | return tmp2; 2007 | } 2008 | 2009 | ls_free_all(tmp1); 2010 | ls_free_all(tmp2); 2011 | } 2012 | 2013 | return NULL; 2014 | } 2015 | 2016 | 2017 | static list* version(list* arg) { 2018 | list* ret = NULL; 2019 | char* tmp; 2020 | 2021 | if (fancy_typecheck("", arg, "version", 2022 | "This command returns the version of the shell, " 2023 | "as three numbers.")) { 2024 | return NULL; 2025 | } 2026 | 2027 | tmp = (char*)gc_alloc(sizeof(char) * 5, "version"); 2028 | sprintf(tmp, "%d", VERSION_MAJOR); 2029 | ret = ls_cons(tmp, ret); 2030 | 2031 | tmp = (char*)gc_alloc(sizeof(char) * 5, "version"); 2032 | sprintf(tmp, "%d", VERSION_MINOR); 2033 | ret = ls_cons(tmp, ret); 2034 | 2035 | tmp = (char*)gc_alloc(sizeof(char) * 5, "version"); 2036 | sprintf(tmp, "%d", VERSION_PATCH); 2037 | ret = ls_cons(tmp, ret); 2038 | 2039 | return ls_reverse(ret); 2040 | } 2041 | 2042 | 2043 | static list* builtin(list* arg) { 2044 | list* (*func)(list*); 2045 | 2046 | if (fancy_typecheck("s*", arg, "builtin", 2047 | "This command executes the first argument as if it " 2048 | "was a\nbuiltin command, regardless of whether it has " 2049 | "been overriden or not.\nIt is useful for writing your " 2050 | "own replacements for builtin commands.")) { 2051 | return NULL; 2052 | } 2053 | 2054 | func = hash_get(builtins, ls_data(arg)); 2055 | 2056 | if (!func) { 2057 | error("esh: builtin: %s is not a command.", ls_data(arg)); 2058 | return NULL; 2059 | } 2060 | 2061 | return func(ls_next(arg)); 2062 | } 2063 | 2064 | 2065 | static list* begin(list* arg) { 2066 | return ls_copy(arg); 2067 | } 2068 | 2069 | 2070 | static list* stderr_handler(list* arg) { 2071 | int* fd; 2072 | 2073 | if (fancy_typecheck("f", arg, "stderr-handler", 2074 | "This command will set the standard error handler.\n" 2075 | "When the standard error handler is set, all new " 2076 | "subprocesses\nwill use the given file as the " 2077 | "standard error.\n" 2078 | "For example, to save all standard error in a file " 2079 | "called\n\"stderr.log\" in your home directory, " 2080 | "run this command:\n" 2081 | "\"(stderr-handler \n" 2082 | " (file-open file (squish (get HOME) " 2083 | "sterr.log)))\"")) { 2084 | return NULL; 2085 | } 2086 | 2087 | fd = ls_data(arg); 2088 | 2089 | if (stderr_handler_fd != STDERR_FILENO && 2090 | stderr_handler_fd != STDOUT_FILENO) { 2091 | close(stderr_handler_fd); 2092 | } 2093 | 2094 | stderr_handler_fd = fd[1]; 2095 | return NULL; 2096 | } 2097 | 2098 | 2099 | static list* my_stderr(list* arg) { 2100 | if (fancy_typecheck("", arg, "stderr", 2101 | "This command returns the standard input/standard error " 2102 | "file.")) { 2103 | return NULL; 2104 | } 2105 | 2106 | return ls_copy(ls_stderr); 2107 | } 2108 | 2109 | 2110 | static list* my_wait(list* arg) { 2111 | int err; 2112 | long val = 0; 2113 | 2114 | if (fancy_typecheck("s", arg, "wait", 2115 | "This command will pause for the given number of " 2116 | "seconds.")) { 2117 | return NULL; 2118 | } 2119 | 2120 | val = do_atoi(ls_data(arg), &err, val); 2121 | 2122 | if (err) { 2123 | error("esh: wait: \"wait\" takes a numeric value."); 2124 | return NULL; 2125 | } 2126 | 2127 | sleep(val); 2128 | return NULL; 2129 | } 2130 | 2131 | 2132 | static list* alive_p(list* arg) { 2133 | pid_t foo; 2134 | char buff[255]; 2135 | struct stat dummy; 2136 | 2137 | if (fancy_typecheck("p", arg, "alive?", 2138 | "This command returns \"true\" if the given process " 2139 | "is still running,\nor \"false\" otherwise.")) { 2140 | return NULL; 2141 | } 2142 | 2143 | foo = *(pid_t*)(ls_data(arg)); 2144 | 2145 | sprintf(buff, "/proc/%d", foo); 2146 | 2147 | /* Mondo-hack: check the /proc filesystem to see if the process exists. */ 2148 | 2149 | if (stat(buff, &dummy)) { 2150 | return ls_copy(ls_false); 2151 | 2152 | } else { 2153 | return ls_copy(ls_true); 2154 | } 2155 | } 2156 | 2157 | 2158 | static list* my_while(list* arg) { 2159 | list* cond; 2160 | list* act; 2161 | list* foo; 2162 | list* oldstack; 2163 | 2164 | if (fancy_typecheck("ll*", arg, "while", 2165 | "This command will iteratively \"eval\" the second " 2166 | "argument\nas long as the \"eval\" of the first " 2167 | "argument is not \"false\".\n" 2168 | "The rest of the arguments define the initial " 2169 | "stack for\nthe duration of execution of \"while\".\n" 2170 | "This command is only suitable when you don't " 2171 | "care about\nthe return value of the second " 2172 | "argument.")) { 2173 | return NULL; 2174 | } 2175 | 2176 | oldstack = stack; 2177 | 2178 | stack = ls_copy(ls_next(ls_next(arg))); 2179 | 2180 | cond = car(arg); 2181 | act = car(ls_next(arg)); 2182 | 2183 | while (1) { 2184 | foo = eval(cond); 2185 | 2186 | if (exception_flag || 2187 | (foo && ls_type(foo) == TYPE_BOOL && !ls_data(foo))) { 2188 | 2189 | ls_free_all(foo); 2190 | break; 2191 | } 2192 | 2193 | ls_free_all(foo); 2194 | ls_free_all(eval(act)); 2195 | } 2196 | 2197 | ls_free_all(stack); 2198 | ls_free_all(cond); 2199 | ls_free_all(act); 2200 | stack = oldstack; 2201 | 2202 | return NULL; 2203 | } 2204 | 2205 | 2206 | static list* chop(list* arg) { 2207 | char* foo; 2208 | int len, i; 2209 | 2210 | if (fancy_typecheck("s", arg, "chop!", 2211 | "Get rid of the last character in the given string.\n" 2212 | "Warning: the given string is modified in-place!")) { 2213 | return NULL; 2214 | } 2215 | 2216 | foo = ls_data(arg); 2217 | len = strlen(foo); 2218 | 2219 | for (i = len-1; i >= 0; i--) { 2220 | if (foo[i] != '\0') { 2221 | foo[i] = '\0'; 2222 | return NULL; 2223 | } 2224 | } 2225 | 2226 | return ls_copy(arg); 2227 | } 2228 | 2229 | 2230 | static list* chop_nl(list* arg) { 2231 | char* foo; 2232 | int len, i; 2233 | 2234 | if (fancy_typecheck("s", arg, "chop-nl!", 2235 | "Get rid of the last character in the given string, " 2236 | "but only if\nit is a newline.\n" 2237 | "Warning: the given string is modified in-place!")) { 2238 | return NULL; 2239 | } 2240 | 2241 | foo = ls_data(arg); 2242 | len = strlen(foo); 2243 | 2244 | for (i = len-1; i >= 0; i--) { 2245 | if (foo[i] == '\0') continue; 2246 | 2247 | if (foo[i] != '\n') break; 2248 | 2249 | foo[i] = '\0'; 2250 | break; 2251 | } 2252 | 2253 | return ls_copy(arg); 2254 | } 2255 | 2256 | 2257 | static list* match(list* arg) { 2258 | regex_t reg; 2259 | int err; 2260 | 2261 | if (fancy_typecheck("ss", arg, "match", 2262 | "Match the second argument with the first.\n" 2263 | "The first argument is a regular expression.\n" 2264 | "This command returns \"true\" if there is a match, " 2265 | "or \"false\" otherwise.")) { 2266 | return NULL; 2267 | } 2268 | 2269 | err = regcomp(®, ls_data(arg), REG_EXTENDED | REG_NOSUB); 2270 | 2271 | if (!err) { 2272 | char* tomatch = ls_data(ls_next(arg)); 2273 | 2274 | err = regexec(®, tomatch, 0, NULL, 0); 2275 | 2276 | if (err == REG_NOMATCH) { 2277 | regfree(®); 2278 | return ls_copy(ls_false); 2279 | } 2280 | } 2281 | 2282 | if (err) { 2283 | int len = regerror(err, ®, NULL, 0); 2284 | char* buff = (char*)gc_alloc(sizeof(char) * len, "match"); 2285 | 2286 | regerror(err, ®, buff, len); 2287 | 2288 | error("esh: match: %s", buff); 2289 | 2290 | gc_free(buff); 2291 | regfree(®); 2292 | return NULL; 2293 | } 2294 | 2295 | regfree(®); 2296 | return ls_copy(ls_true); 2297 | } 2298 | 2299 | 2300 | static list* chars(list* arg) { 2301 | char* foo; 2302 | char* bar; 2303 | int len, i; 2304 | list* ret = NULL; 2305 | 2306 | if (fancy_typecheck("s", arg, "chars", 2307 | "Return a list of the characters in the given " 2308 | "string.")) { 2309 | 2310 | return NULL; 2311 | } 2312 | 2313 | foo = ls_data(arg); 2314 | len = strlen(foo); 2315 | 2316 | for (i = len-1; i >= 0; i--) { 2317 | bar = (char*)gc_alloc(sizeof(char) * 2, "chars"); 2318 | 2319 | bar[0] = foo[i]; 2320 | bar[1] = '\0'; 2321 | 2322 | ret = ls_cons(bar, ret); 2323 | } 2324 | 2325 | return ret; 2326 | } 2327 | 2328 | 2329 | 2330 | static list* filter(list* arg) { 2331 | char* foo; 2332 | char* bar; 2333 | int len, i; 2334 | list* ret = NULL; 2335 | list* tmp; 2336 | list* code; 2337 | list* oldstack = stack; 2338 | 2339 | if (fancy_typecheck("sl", arg, "filter", 2340 | "Filter the first argument with the second one.\n" 2341 | "The characters of the first argument are passed " 2342 | "to the second one,\ncharacter by character, and then " 2343 | "the outputs of the second argument\nare passed to " 2344 | "\"squish\" to form the\nreturn value of this " 2345 | "command.")) { 2346 | return NULL; 2347 | } 2348 | 2349 | code = car(ls_next(arg)); 2350 | 2351 | foo = ls_data(arg); 2352 | len = strlen(foo); 2353 | 2354 | for (i = len-1; i >= 0; i--) { 2355 | bar = (char*)gc_alloc(sizeof(char) * 2, "filter"); 2356 | 2357 | bar[0] = foo[i]; 2358 | bar[1] = '\0'; 2359 | 2360 | stack = ls_cons(bar, NULL); 2361 | 2362 | tmp = eval(code); 2363 | 2364 | ret = ls_cons(tmp, ret); 2365 | ls_type_set(ret, TYPE_LIST); 2366 | 2367 | ls_free_all(stack); 2368 | } 2369 | 2370 | stack = oldstack; 2371 | 2372 | bar = ls_strcat(ret); 2373 | 2374 | ls_free_all(ret); 2375 | ls_free_all(code); 2376 | 2377 | return ls_cons(bar, NULL); 2378 | } 2379 | 2380 | 2381 | static list* my_clone(list* arg) { 2382 | list* ret = NULL; 2383 | char* foo; 2384 | int i, len, err; 2385 | 2386 | if (fancy_typecheck("ss", arg, "clone", 2387 | "This command will return the first argument " 2388 | "X number of times,\nwhere X is equal to the second " 2389 | "argument.")) { 2390 | return NULL; 2391 | } 2392 | 2393 | foo = ls_data(arg); 2394 | 2395 | len = do_atoi(ls_data(ls_next(arg)), &err, 0); 2396 | 2397 | if (err) { 2398 | error("esh: clone: \"clone\" takes a numeric value."); 2399 | return NULL; 2400 | } 2401 | 2402 | for (i = 0; i < len; i++) { 2403 | gc_inc_ref(foo); 2404 | ret = ls_cons(foo, ret); 2405 | } 2406 | 2407 | return ret; 2408 | } 2409 | 2410 | 2411 | static list* substring_p(list* arg) { 2412 | if (fancy_typecheck("ss", arg, "substring?", 2413 | "This command returns \"true\" if the first argument " 2414 | "is a\nsubstring of the second.")) { 2415 | return NULL; 2416 | } 2417 | 2418 | if (strstr(ls_data(ls_next(arg)), ls_data(arg))) { 2419 | return ls_copy(ls_true); 2420 | } else { 2421 | return ls_copy(ls_false); 2422 | } 2423 | } 2424 | 2425 | 2426 | static list* less_than(list* arg) { 2427 | int arg1, arg2; 2428 | int err1, err2; 2429 | 2430 | if (fancy_typecheck("ss", arg, "<", 2431 | "This command returns true if the first argument is " 2432 | "less than\nthe second.")) { 2433 | return NULL; 2434 | } 2435 | 2436 | arg1 = do_atoi(ls_data(arg), &err1, 0); 2437 | arg2 = do_atoi(ls_data(ls_next(arg)), &err2, 0); 2438 | 2439 | if (err1 || err2) { 2440 | error("esh: <: \"<\" only accepts numeric arguments."); 2441 | return NULL; 2442 | } 2443 | 2444 | if (arg1 < arg2) { 2445 | return ls_copy(ls_true); 2446 | } else { 2447 | return ls_copy(ls_false); 2448 | } 2449 | } 2450 | 2451 | 2452 | static list* greater_than(list* arg) { 2453 | int arg1, arg2; 2454 | int err1, err2; 2455 | 2456 | if (fancy_typecheck("ss", arg, ">", 2457 | "This command returns true if the first argument is " 2458 | "greater than\nthe second.")) { 2459 | return NULL; 2460 | } 2461 | 2462 | arg1 = do_atoi(ls_data(arg), &err1, 0); 2463 | arg2 = do_atoi(ls_data(ls_next(arg)), &err2, 0); 2464 | 2465 | if (err1 || err2) { 2466 | error("esh: >: \">\" only accepts numeric arguments."); 2467 | return NULL; 2468 | } 2469 | 2470 | if (arg1 > arg2) { 2471 | return ls_copy(ls_true); 2472 | } else { 2473 | return ls_copy(ls_false); 2474 | } 2475 | } 2476 | 2477 | 2478 | static list* my_void(list* arg) { 2479 | return ls_copy(ls_void); 2480 | } 2481 | 2482 | 2483 | static list* repeat(list* arg) { 2484 | int arg1, err1, i; 2485 | 2486 | if (fancy_typecheck("s*", arg, "repeat", 2487 | "This command evaluates the given arguments some number " 2488 | "of times and\nreturns nothing. The first argument " 2489 | "specifies the number\nof times the rest of the " 2490 | "arguments should be evaluated.")) { 2491 | return NULL; 2492 | } 2493 | 2494 | arg1 = do_atoi(ls_data(arg), &err1, 0); 2495 | 2496 | if (err1) { 2497 | error("esh: repeat: expected a number as first argment."); 2498 | return NULL; 2499 | } 2500 | 2501 | for (i = 0; i < arg1; i++) { 2502 | ls_free_all(eval(ls_next(arg))); 2503 | } 2504 | 2505 | return NULL; 2506 | } 2507 | 2508 | 2509 | 2510 | hash_entry builtins_array[] = { 2511 | { "cd", cd }, 2512 | { "help", help }, 2513 | { "copy", ls_copy }, 2514 | { "set", set }, 2515 | { "get", get }, 2516 | { "env", env }, 2517 | { "run", run }, 2518 | { "run-simple", run_simple }, 2519 | { "gobble", gobble }, 2520 | { "exit", my_exit }, 2521 | { "alias", alias }, 2522 | { "+", plus }, 2523 | { "*", times }, 2524 | { "-", minus }, 2525 | { "/", over }, 2526 | { "eval", my_eval }, 2527 | { "fg", fg }, 2528 | { "bg", bg }, 2529 | { "jobs", list_jobs }, 2530 | { "define", define }, 2531 | { "prompt", set_prompt }, 2532 | { "if", my_if }, 2533 | { "=", equal_p }, 2534 | { "pop", pop }, 2535 | { "push", push }, 2536 | { "top", top }, 2537 | { "list", my_list }, 2538 | { "print", my_print }, 2539 | { "stack", my_stack }, 2540 | { "hash-make", my_hash_make }, 2541 | { "hash-get", my_hash_get }, 2542 | { "hash-put", my_hash_put }, 2543 | { "hash-keys", my_hash_keys }, 2544 | { "car", my_car }, 2545 | { "first", my_car }, 2546 | { "cdr", cdr }, 2547 | { "rest", cdr }, 2548 | { "script", script }, 2549 | { "read", my_read }, 2550 | { "squish", squish }, 2551 | { "parse", my_parse }, 2552 | { "newline", newline }, 2553 | { "nl", newline }, 2554 | { "typecheck", my_typecheck }, 2555 | { "split", split }, 2556 | { "unlist", unlist }, 2557 | { "exec", exec }, 2558 | { "rot", rot }, 2559 | { "l-stack", list_stack }, 2560 | { "begin", begin }, 2561 | { "defined?", defined_p }, 2562 | { "null", my_null }, 2563 | { "file-open", my_file_open }, 2564 | { "file-read", my_file_read }, 2565 | { "file-read-block", my_file_read_block }, 2566 | { "file-write", my_file_write }, 2567 | { "file-type", my_file_type }, 2568 | { "standard", standard }, 2569 | { "stderr", my_stderr }, 2570 | { "interactive?", my_interactive }, 2571 | { "and", and }, 2572 | { "or", or }, 2573 | { "not", not }, 2574 | { "version", version }, 2575 | { "builtin", builtin }, 2576 | { "true", my_true }, 2577 | { "false", my_false }, 2578 | { "null?", my_null_p }, 2579 | { "not-null?", my_not_null_p }, 2580 | { "l-cdr", list_cdr }, 2581 | { "l-rest", list_cdr }, 2582 | { "stderr-handler", stderr_handler }, 2583 | { "wait", my_wait }, 2584 | { "alive?", alive_p }, 2585 | { "while", my_while }, 2586 | { "alias-hash", alias_hash }, 2587 | { "car-l", my_car_l }, 2588 | { "first-l", my_car_l }, 2589 | { "chop!", chop }, 2590 | { "chop-nl!", chop_nl }, 2591 | { "match", match }, 2592 | { "reverse", reverse }, 2593 | { "chars", chars }, 2594 | { "filter", filter }, 2595 | { "clone", my_clone }, 2596 | { "substring?", substring_p }, 2597 | { "begin-last", begin_last }, 2598 | { "<", less_than }, 2599 | { ">", greater_than }, 2600 | { "void", my_void }, 2601 | { "repeat", repeat }, 2602 | { NULL, NULL } 2603 | }; 2604 | 2605 | --------------------------------------------------------------------------------