M .gitignore => .gitignore +4 -0
@@ 53,3 53,7 @@ dkms.conf
/build
/build-*
+
+.build
+highlight
+mrsh
A Makefile => Makefile +88 -0
@@ 0,0 1,88 @@
+.POSIX:
+.SUFFIXES:
+OUTDIR=.build
+include $(OUTDIR)/config.mk
+
+INCLUDE=-Iinclude
+
+public_includes=\
+ include/mrsh/arithm.h \
+ include/mrsh/array.h \
+ include/mrsh/ast.h \
+ include/mrsh/buffer.h \
+ include/mrsh/builtin.h \
+ include/mrsh/entry.h \
+ include/mrsh/getopt.h \
+ include/mrsh/hashtable.h \
+ include/mrsh/parser.h \
+ include/mrsh/shell.h
+
+tests=\
+ test/conformance/if.sh \
+ test/case.sh \
+ test/command.sh \
+ test/for.sh \
+ test/function.sh \
+ test/loop.sh \
+ test/pipeline.sh \
+ test/subshell.sh \
+ test/syntax.sh \
+ test/ulimit.sh \
+ test/word.sh
+
+include $(OUTDIR)/cppcache
+
+.SUFFIXES: .c .o
+
+.c.o:
+ @mkdir -p $$(dirname "$@")
+ @printf 'CC\t$@\n'
+ @touch $(OUTDIR)/cppcache
+ @grep $< $(OUTDIR)/cppcache >/dev/null || \
+ $(CPP) $(INCLUDE) -MM -MT $@ $< >> $(OUTDIR)/cppcache
+ @$(CC) -c $(CFLAGS) $(INCLUDE) -o $@ $<
+
+$(OUTDIR)/libmrsh.a: $(libmrsh_objects)
+ @printf 'AR\t$@\n'
+ @$(AR) -csr $@ $(libmrsh_objects)
+
+libmrsh.so.$(SOVERSION): $(OUTDIR)/libmrsh.a
+ @printf 'LD\t$@\n'
+ @$(CC) -shared $(LDFLAGS) -o $@ $(OUTDIR)/libmrsh.a
+
+$(OUTDIR)/mrsh.pc:
+ @printf 'MKPC\t$@\n'
+ @PREFIX=$(PREFIX) ./mkpc $@
+
+mrsh: $(OUTDIR)/libmrsh.a $(mrsh_objects)
+ @printf 'CCLD\t$@\n'
+ @$(CC) -o $@ $(LIBS) $(mrsh_objects) -L$(OUTDIR) -lmrsh
+
+highlight: $(OUTDIR)/libmrsh.a $(highlight_objects)
+ @printf 'CCLD\t$@\n'
+ @$(CC) -o $@ $(LIBS) $(highlight_objects) -L$(OUTDIR) -lmrsh
+
+check: mrsh $(tests)
+ @for t in $(tests); do \
+ printf '%-30s... ' "$$t" && \
+ MRSH=./mrsh REF_SH=$${REF_SH:-sh} ./test/harness.sh $$t >/dev/null && \
+ echo OK || echo FAIL; \
+ done
+
+install: mrsh libmrsh.so.$(SOVERSION) $(OUTDIR)/mrsh.pc
+ mkdir -p $(BINDIR) $(LIBDIR) $(INCDIR)/mrsh $(PCDIR)
+ install -m755 mrsh $(BINDIR)/mrsh
+ install -m755 libmrsh.so.$(SOVERSION) $(LIBDIR)/libmrsh.so.$(SOVERSION)
+ for inc in $(public_includes); do \
+ install -m644 $$inc $(INCDIR)/mrsh/$$(basename $$inc); \
+ done
+ install -m644 $(OUTDIR)/mrsh.pc $(PCDIR)/mrsh.pc
+
+clean:
+ rm -rf \
+ $(libmrsh_objects) \
+ $(mrsh_objects) \
+ $(highlight_objects) \
+ mrsh highlight libmrsh.so.$(SOVERSION) $(OUTDIR)/mrsh.pc
+
+.PHONY: all install clean check
A configure => configure +286 -0
@@ 0,0 1,286 @@
+#!/bin/sh -e
+SOVERSION=0.0.0
+
+pkg_config=${PKG_CONFIG:-pkg-config}
+outdir=${OUTDIR:-.build}
+srcdir=${SRCDIR:-$(dirname "$0")}
+CC=${CC:-cc}
+LIBS=
+
+use_readline=-1
+readline=readline
+
+for arg
+do
+ case "$arg" in
+ --prefix=*)
+ PREFIX=${arg#*=}
+ ;;
+ --without-readline)
+ use_readline=0
+ ;;
+ --with-readline=*)
+ use_readline=1
+ readline=${arg#*=}
+ ;;
+ esac
+done
+
+libmrsh() {
+ genrules libmrsh \
+ 'arithm.c' \
+ 'array.c' \
+ 'ast_print.c' \
+ 'ast.c' \
+ 'buffer.c' \
+ 'builtin/alias.c' \
+ 'builtin/bg.c' \
+ 'builtin/break.c' \
+ 'builtin/builtin.c' \
+ 'builtin/cd.c' \
+ 'builtin/colon.c' \
+ 'builtin/command.c' \
+ 'builtin/dot.c' \
+ 'builtin/eval.c' \
+ 'builtin/exit.c' \
+ 'builtin/export.c' \
+ 'builtin/false.c' \
+ 'builtin/fg.c' \
+ 'builtin/getopts.c' \
+ 'builtin/pwd.c' \
+ 'builtin/read.c' \
+ 'builtin/set.c' \
+ 'builtin/shift.c' \
+ 'builtin/times.c' \
+ 'builtin/true.c' \
+ 'builtin/type.c' \
+ 'builtin/ulimit.c' \
+ 'builtin/umask.c' \
+ 'builtin/unalias.c' \
+ 'builtin/unset.c' \
+ 'builtin/unspecified.c' \
+ 'builtin/wait.c' \
+ 'getopt.c' \
+ 'hashtable.c' \
+ 'parser/arithm.c' \
+ 'parser/parser.c' \
+ 'parser/program.c' \
+ 'parser/word.c' \
+ 'shell/arithm.c' \
+ 'shell/entry.c' \
+ 'shell/job.c' \
+ 'shell/path.c' \
+ 'shell/process.c' \
+ 'shell/redir.c' \
+ 'shell/shell.c' \
+ 'shell/task/pipeline.c' \
+ 'shell/task/simple_command.c' \
+ 'shell/task/task.c' \
+ 'shell/task/word.c' \
+ 'shell/word.c'
+}
+
+mrsh() {
+ if [ $use_readline -eq 1 ]
+ then
+ genrules mrsh \
+ 'main.c' \
+ 'frontend/readline.c'
+ else
+ genrules mrsh \
+ 'main.c' \
+ 'frontend/basic.c'
+ fi
+}
+
+highlight() {
+ genrules highlight example/highlight.c
+}
+
+genrules() {
+ target="$1"
+ shift
+ printf '# Begin generated rules for %s\n' "$target"
+ for file in "$@"
+ do
+ file="${file%.*}"
+ printf '%s.o: %s.c\n' "$file" "$file"
+ done
+ printf '%s_objects=\\\n' "$target"
+ n=0
+ for file in "$@"
+ do
+ file="${file%.*}"
+ n=$((n+1))
+ if [ $n -eq $# ]
+ then
+ printf '\t%s.o\n' "$file"
+ else
+ printf '\t%s.o \\\n' "$file"
+ fi
+ done
+ printf '# End generated rules for %s\n' "$target"
+}
+
+append_cflags() {
+ for flag
+ do
+ CFLAGS="$(printf '%s \\\n\t%s' "$CFLAGS" "$flag")"
+ done
+}
+
+append_ldflags() {
+ for flag
+ do
+ LDFLAGS="$(printf '%s \\\n\t%s' "$LDFLAGS" "$flag")"
+ done
+}
+
+append_libs() {
+ for flag
+ do
+ LIBS="$(printf '%s \\\n\t%s' "$LIBS" "$flag")"
+ done
+}
+
+test_cflags() {
+ [ ! -e "$outdir"/check.c ] && cat <<-EOF > "$outdir"/check.c
+ int main(void) { return 0; }
+ EOF
+ werror=""
+ case "$CFLAGS" in
+ *-Werror*)
+ werror="-Werror"
+ ;;
+ esac
+ if $CC $werror "$@" -o /dev/null "$outdir"/check.c >/dev/null 2>&1
+ then
+ append_cflags "$@"
+ else
+ return 1
+ fi
+}
+
+test_ldflags() {
+ [ ! -e "$outdir"/check.c ] && cat <<-EOF > "$outdir"/check.c
+ int main(void) { return 0; }
+ EOF
+ if $CC "$@" -o /dev/null "$outdir"/check.c >/dev/null 2>&1
+ then
+ append_ldflags "$@"
+ else
+ return 1
+ fi
+}
+
+mkdir -p $outdir
+
+for flag in \
+ -g -std=c99 -pedantic -Werror -Wundef -Wlogical-op \
+ -Wmissing-include-dirs -Wold-style-definition -Wpointer-arith -Winit-self \
+ -Wfloat-equal -Wstrict-prototypes -Wredundant-decls \
+ -Wimplicit-fallthrough=2 -Wendif-labels -Wstrict-aliasing=2 -Woverflow \
+ -Wformat=2 -Wno-missing-braces -Wno-missing-field-initializers \
+ -Wno-unused-parameter
+do
+ printf "Checking for $flag... "
+ if test_cflags "$flag"
+ then
+ echo yes
+ else
+ echo no
+ fi
+done
+
+for flag in -fPIC -Wl,--no-undefined -Wl,--as-needed
+do
+ test_ldflags "$flag"
+done
+
+soname=libmrsh.so.$(echo "$SOVERSION" | cut -d. -f1)
+printf "Checking for specifying soname for shared lib... "
+if ! \
+ test_ldflags -Wl,-soname,$soname || \
+ test_ldflags -Wl,-install_name,$soname
+then
+ echo no
+ echo "Unable to specify soname (is $(uname) supported?)" >&2
+ exit 1
+else
+ echo yes
+fi
+
+printf "Checking for exported symbol restrictions... "
+if ! \
+ test_ldflags -Wl,--version-script="libmrsh.gnu.sym" || \
+ test_ldflags -Wl,-exported_symbols_list,"libmrsh.darwin.sym"
+then
+ echo no
+ echo "Unable to specify exported symbols (is $(uname) supported?)" >&2
+ exit 1
+else
+ echo yes
+fi
+
+if [ $use_readline -eq -1 ]
+then
+ printf "Checking for readline... "
+ if $pkg_config readline
+ then
+ readline=readline
+ use_readline=1
+ append_cflags -DHAVE_READLINE
+ echo yes
+ else
+ echo no
+ fi
+fi
+if [ $use_readline -eq -1 ]
+then
+ printf "Checking for libedit... "
+ if $pkg_config libedit
+ then
+ echo yes
+ readline=libedit
+ use_readline=1
+ append_cflags -DHAVE_EDITLINE
+ else
+ echo no
+ fi
+fi
+
+if [ $use_readline -eq 1 ]
+then
+ append_cflags $($pkg_config --cflags $readline)
+ append_libs $($pkg_config --libs $readline)
+ if [ "$readline" = "readline" ]
+ then
+ # Undo GNU_SOURCE
+ append_cflags -U_GNU_SOURCE
+ fi
+fi
+
+printf "Creating $outdir/config.mk... "
+cat <<EOF > "$outdir"/config.mk
+SOVERSION=$SOVERSION
+CC=$CC
+PREFIX=${PREFIX:-/usr/local}
+_INSTDIR=\$(DESTDIR)\$(PREFIX)
+BINDIR?=${BINDIR:-\$(_INSTDIR)/bin}
+LIBDIR?=${LIBDIR:-\$(_INSTDIR)/lib}
+INCDIR?=${INCDIR:-\$(_INSTDIR)/include}
+MANDIR?=${MANDIR:-\$(_INSTDIR)/share/man}
+PCDIR?=${PCDIR:-\$(_INSTDIR)/lib/pkgconfig}
+CFLAGS=${CFLAGS}
+LDFLAGS=${LDFLAGS}
+LIBS=${LIBS}
+SRCDIR=${srcdir}
+
+all: mrsh highlight libmrsh.so.\$(SOVERSION) \$(OUTDIR)/mrsh.pc
+EOF
+libmrsh >>"$outdir"/config.mk
+mrsh >>"$outdir"/config.mk
+highlight >>"$outdir"/config.mk
+echo done
+
+touch $outdir/cppcache
A mkpc => mkpc +12 -0
@@ 0,0 1,12 @@
+#!/bin/sh
+cat <<EOF > $1
+prefix=$PREFIX
+libdir=\${prefix}/lib
+includedir=\${prefix}/include
+
+Name: mrsh
+Description: POSIX shell library
+Version: 0.0.0
+Libs: -L\${libdir} -lmrsh
+Cflags: -I\${includedir}
+EOF
M test/conformance/if.sh => test/conformance/if.sh +8 -8
@@ 7,20 7,20 @@
# pass
# pass
-echo >&2 "-> if..fi with true condition"
+echo "-> if..fi with true condition"
if true
then
echo pass
fi
-echo >&2 "-> if..fi with false condition"
+echo "-> if..fi with false condition"
if false
then
echo >&2 "fail: This branch should not have run" && exit 1
fi
echo pass
-echo >&2 "-> if..else..fi with true condition"
+echo "-> if..else..fi with true condition"
if true
then
echo pass
@@ 28,7 28,7 @@ else
echo >&2 "fail: This branch should not have run" && exit 1
fi
-echo >&2 "-> if..else..fi with false condition"
+echo "-> if..else..fi with false condition"
if false
then
echo >&2 "fail: This branch should not have run" && exit 1
@@ 36,7 36,7 @@ else
echo pass
fi
-echo >&2 "-> if..else..fi with true condition"
+echo "-> if..else..fi with true condition"
if true
then
echo pass
@@ 44,7 44,7 @@ else
echo >&2 "fail: This branch should not have run" && exit 1
fi
-echo >&2 "-> if..else..elif..fi with false condition"
+echo "-> if..else..elif..fi with false condition"
if false
then
echo >&2 "fail: This branch should not have run" && exit 1
@@ 55,7 55,7 @@ else
echo >&2 "fail: This branch should not have run" && exit 1
fi
-echo >&2 "-> test exit status"
+echo "-> test exit status"
if true
then
( exit 10 )
@@ 71,7 71,7 @@ else
fi
[ $# -eq 20 ] || { echo >&2 "fail: Expected status code = 20" && exit 1; }
-echo >&2 "-> test alternate syntax"
+echo "-> test alternate syntax"
# These tests are only expected to parse, they do not make assertions
if true; then true; fi
if true; then true; else true; fi
M test/harness.sh => test/harness.sh +3 -3
@@ 1,11 1,11 @@
#!/bin/sh
dir=$(dirname "$0")
-testcase="$dir/$1"
+testcase="$1"
-echo >&2 "Running with mrsh"
+echo "Running with mrsh"
mrsh_out=$("$MRSH" "$testcase")
mrsh_ret=$?
-echo >&2 "Running with reference shell"
+echo "Running with reference shell"
ref_out=$("$REF_SH" "$testcase")
ref_ret=$?
if [ $mrsh_ret -ne $ref_ret ] || [ "$mrsh_out" != "$ref_out" ]
M test/meson.build => test/meson.build +1 -1
@@ 19,7 19,7 @@ test_files = [
foreach test_file : test_files
test(
- test_file,
+ join_paths(meson.current_source_dir(), test_file),
harness,
env: [
'MRSH=@0@'.format(mrsh_exe.full_path()),
M test/pipeline.sh => test/pipeline.sh +3 -3
@@ 1,12 1,12 @@
#!/bin/sh
-echo >&2 "Pipeline with 1 command"
+echo "Pipeline with 1 command"
echo "a b c d"
-echo >&2 "Pipeline with 2 commands"
+echo "Pipeline with 2 commands"
echo "a b c d" | sed s/b/B/
-echo >&2 "Pipeline with 3 commands"
+echo "Pipeline with 3 commands"
echo "a b c d" | sed s/b/B/ | sed s/c/C/
echo >&2 "Pipeline with bang"