#!/bin/sh # SPDX-License-Identifier: BSD-2-Clause LOG_LEVEL=1 # vvv https://github.com/dnmfarrell/tap.sh/ TAP_TEST_COUNT=0 TAP_FAIL_COUNT=0 tap_pass() { TAP_TEST_COUNT=$((TAP_TEST_COUNT + 1)) echo "ok $TAP_TEST_COUNT $1" } tap_fail() { TAP_TEST_COUNT=$((TAP_TEST_COUNT + 1)) TAP_FAIL_COUNT=$((TAP_FAIL_COUNT + 1)) echo "not ok $TAP_TEST_COUNT $1" } tap_end() { num_tests="$1" [ -z "$num_tests" ] && num_tests="$TAP_TEST_COUNT" # echo "1..$num_tests" [ "$num_tests" = "$TAP_TEST_COUNT" ] || exit 1 exit $((TAP_FAIL_COUNT > 0)) # C semantics } tap_ok() { if [ "$1" -eq 0 ]; then tap_pass "$2" else tap_fail "$2" fi } tap_cmp() { if [ "$1" = "$2" ]; then tap_pass "$3" else tap_fail "$3 - expected '$2' but got '$1'" fi } # ^^^ # in all cases, awk has to be the last thing in the pipeline so we can # see its exitcode lisp_with_input () { ${TEST_AWK:-awk} -v PROMPT= -v LOG_LEVEL=$LOG_LEVEL -f glotawk } lisp_with_string () { echo "$@" | lisp_with_input } lisp_eval_should_be () { [ $LOG_LEVEL -ge 2 ] && echo "INF ---- test $(($TAP_TEST_COUNT + 1)) ---------" local output="$(lisp_with_string "$1")" local exitcode=$? if [ "$exitcode" -ne 0 ] ; then tap_fail "$3 - nonzero exit code" else tap_cmp "$output" "$2" "$3" fi } lisp_eval_should_be_tricky_tricky () { [ $LOG_LEVEL -ge 2 ] && echo "INF ---- test $(($TAP_TEST_COUNT + 1)) ---------" echo "$1" | base64 -d | lisp_with_input >/dev/null 2>&1 local exitcode=$? if [ "$exitcode" -ne 0 ] ; then tap_fail "$3 - nonzero exit code" else local output="$(echo "$1" | base64 -d | lisp_with_input | base64)" tap_cmp "$output" "$2" "$3" fi } if [ "$#" -gt 0 ]; then while [ "$#" -gt 0 ]; do if [ "$1" = "-v" ]; then if [ $LOG_LEVEL -lt 3 ]; then LOG_LEVEL=$(( $LOG_LEVEL + 1 )) fi elif [ "$1" = "-vv" ]; then if [ $LOG_LEVEL -lt 2 ]; then LOG_LEVEL=$(( $LOG_LEVEL + 2 )) fi fi shift done fi TEST_COUNT=0 lisp_eval_should_be '(quote 5)' '5' 'basic quote' lisp_eval_should_be '5' '5' 'numbers are literal' lisp_eval_should_be '((lambda (x) 3) 5)' '3' 'lambda evaluation' lisp_eval_should_be '(label ((foo 3)) foo)' '3' 'label evaluation' lisp_eval_should_be '(cond (false 5) (true 3))' '3' 'cond evaluation' lisp_eval_should_be '(atom 5)' 'true' 'atom: number' lisp_eval_should_be '(atom (quote "foo"))' 'true' 'atom: string' lisp_eval_should_be '(atom (quote (1 2 3)))' 'false' 'atom: list' lisp_eval_should_be '(atom (quote (1 . 2)))' 'false' 'atom: pair' lisp_eval_should_be '(atom (quote socrates))' 'true' 'atom: symbol' lisp_eval_should_be '(cons (quote foo) (quote bar))' '(foo . bar)' 'cons: pair' lisp_eval_should_be '(cons 1 (cons 2 (cons 3 nil)))' '(1 2 3)' 'cons: list' lisp_eval_should_be '(car (quote (foo bar)))' 'foo' 'car' lisp_eval_should_be '(cdr (quote (foo bar)))' '(bar)' 'cdr' lisp_eval_should_be '(eq "foo" "bar")' 'false' 'eq: unequal strings' lisp_eval_should_be '(eq "foo" "foo")' 'false' 'eq: strings are not interned (non-normative)' lisp_eval_should_be '(equal "foo" "foo")' 'true' 'equal tests for string equality irrespective of identity' lisp_eval_should_be '(equal 42 42)' 'true' 'equal: numbers' lisp_eval_should_be '(equal '\''foo '\''foo)' 'true' 'equal: symbols' lisp_eval_should_be '(equal '\''(1 2 "foo") '\''(1 2 "foo"))' 'true' 'equal: lists' lisp_eval_should_be '(equal '\''(1 foo "foo") '\''(1 foo "bar"))' 'false' 'equal: unequal things' lisp_eval_should_be '(list (not true) (not false) (not nil) (not 0) (not "") (not "0"))' '(false true true false false false)' 'not' lisp_eval_should_be '(list (null true) (null false) (null nil) (null '\''(1)))' '(false false true false)' 'null' lisp_eval_should_be '((lambda xs (car xs)) 1 2 3)' '1' 'lexpr' # the first result 3 is the return value of setq; the second is the # result of evaluating symbol three lisp_eval_should_be '(setq three 3) three' \ '3 3' \ 'setq global' lisp_eval_should_be '(setq fst (lambda (a b) a)) (fst 9 8)' \ '(*lambda (a b) a) 9' \ 'setq global tailable lambda' lisp_eval_should_be '(setq a (quote (1 2 3))) (setq b (quote (4 5))) (nconc a b) a b' '(1 2 3) (4 5) (1 2 3 4 5) (1 2 3 4 5) (4 5)' 'nconc' # the macro function returns the name of the macro; that's what the q # in the output is lisp_eval_should_be '(macro q (lambda (x) (list (quote quote) (car x)))) (q foo)' \ 'q foo' 'macro' lisp_eval_should_be '(quote (1 2 3))' '(1 2 3)' 'quote double-check' lisp_eval_should_be \''(1 2 3)' '(1 2 3)' 'reader syntax: apostrophe short for quote' lisp_eval_should_be '(quasiquote (1 (unquote (+ 2 3)) 4))' '(1 5 4)' 'quasiquote with words' lisp_eval_should_be '`(1 ,(+ 2 3) 4)' '(1 5 4)' 'quasiquote with reader syntax' lisp_eval_should_be '(+ 1 2 3)' '6' 'multiarg +' lisp_eval_should_be '(* 2 3 5)' '30' 'multiarg *' lisp_eval_should_be '(/ 3 5)' '0.6' '/' lisp_eval_should_be '(// 6 5)' '1' 'quotient' lisp_eval_should_be '(% 6 5)' '1' 'modulo' lisp_eval_should_be '(** 3 4)' '81' 'power' lisp_eval_should_be '(atan2 0 -1)' '3.14159' 'atan2' lisp_eval_should_be '(sin 0)' '0' 'sin' lisp_eval_should_be '(cos 0)' '1' 'cos' lisp_eval_should_be '(sqrt 9)' '3' 'sqrt' lisp_eval_should_be '(tolower "Title Case")' '"title case"' 'tolower' lisp_eval_should_be '(toupper "Title Case")' '"TITLE CASE"' 'toupper' lisp_eval_should_be '(substr "Foo" 2)' '"oo"' 'substr 2' lisp_eval_should_be '(substr "Foo" 1 2)' '"Fo"' 'substr 3' lisp_eval_should_be '(list-length '\''(1 2 3 4 5))' '5' 'list-length' lisp_eval_should_be '(string-length "1234567890")' '10' 'string-length' lisp_eval_should_be '(split "a b c d e" " ")' '("a" "b" "c" "d" "e")' 'split' lisp_eval_should_be '(sprintf "foo %d bar %d baz %s" 3 5 "bletch")' \ '"foo 3 bar 5 baz bletch"' 'sprintf' lisp_eval_should_be '(strcat "foo" "bar" "baz")' '"foobarbaz"' 'strcat' lisp_eval_should_be '(sprintf "%03d%%03d%s" 5 "foo")' \ '"005%03dfoo"' 'sprintf % escape' lisp_eval_should_be '(sub "o" "e" "foo quux blotch")' \ '"feo quux blotch"' 'sub' lisp_eval_should_be '(gsub "o" "e" "foo quux blotch")' \ '"fee quux bletch"' 'gsub' lisp_eval_should_be '(with-ors "" (print "foo"))' \ '"foo"()' 'with-ors' lisp_eval_should_be '(printf "%03d%%03d%s" 5 "foo")' \ '005%03dfoo()' 'printf' lisp_eval_should_be '(with-output-to ">>" "/dev/null" (printf "%03d%%03d%s" 5 "foo"))' \ '()' 'with-output-to' lisp_eval_should_be '(let ((seconds '\''(4 5 6))) `(1 2 3 ,@(mapcar (lambda (x) (+ 2 x)) seconds) 1 ,(+ 5 7) 3))' '(1 2 3 6 7 8 1 12 3)' 'quasiquote with unquote and unquote-splicing' f=$(mktemp) lisp_eval_should_be "(progn (with-output-to \">\" \"${f}\" (print \"foo\") (print 3) (fflush)) (close \"${f}\"))" '()' 'with-output-to print close' lisp_eval_should_be "(with-input-from \"<\" \"${f}\" (print (getline)) (print (getline)))" '("\"foo\"" 1) ("3" 1) ()' 'with-input-from getline' # a double-quoted string, with a single quote, the word foo, and a single quote lisp_eval_should_be '(shellquote "foo")' '"'\''foo'\''"' 'shellquote, trivial' lisp_eval_should_be '(shellquote "foo'\''s")' '"'\''foo'\'\\\\\'\''s'\''"' 'shellquote, single single quote' lisp_eval_should_be '(shellquote "c d")' '"'\''c d'\''"' 'shellquote, space' lisp_eval_should_be '(shellquote "$foo")' '"'\''$foo'\''"' 'shellquote, dollarsign' lisp_eval_should_be '(shellquote "a\nb")' \"\'a\\nb\'\" 'shellquote, newline' lisp_eval_should_be '(unsafe-system "echo hi; echo ho")' 'hi ho 0' 'unsafe-system vulnerable to command injection' lisp_eval_should_be '(system "echo" "hi;" "echo" "ho")' 'hi; echo ho 0' 'system properly escapes arguments' lisp_eval_should_be '(let ((a '\''(1 2 3))) (apply + 6 a))' '12' 'apply' lisp_eval_should_be '(reduce only2+ 1)' '1' 'reduce with atom does not call f' lisp_eval_should_be '(reduce only2+ '\''(1))' '1' 'reduce without enough in list does not call f' lisp_eval_should_be '(reduce only2+ '\''(1 2))' '3' 'reduce with enough items calls f' lisp_eval_should_be '`()' '()' 'quasiquote nil' lisp_eval_should_be '`foo' '()' 'quasiquoting atom makes error' lisp_eval_should_be '`(1)' '(1)' 'quasiquote single item list' lisp_eval_should_be '`(foo (bar) baz)' '(foo (bar) baz)' 'quasiquote with list inside' lisp_eval_should_be '`(foo (bar ,(+ 1 2)) baz)' '(foo (bar 3) baz)' 'quasiquote with unquote inside list' lisp_eval_should_be '(let ((xs '\''(1 2))) `(foo ,@xs baz))' \ '(foo 1 2 baz)' 'quasiquote splicing unquote' lisp_eval_should_be '(let ((xs '\''(1 2))) `(foo (bar ,@xs bletch) baz))' \ '(foo (bar 1 2 bletch) baz)' \ 'quasiquote splicing unquote inside list' lisp_eval_should_be '(let* () 5)' '5' 'let* with nothing' lisp_eval_should_be '(let* ((x 1)) x)' '1' 'let* with one thing, not special' lisp_eval_should_be '(let* ((x 1) (y (+ 1 x))) y)' '2' 'let* with two things where order matters' lisp_eval_should_be '(prog1 5 (print "foo"))' '"foo" 5' 'prog1' lisp_eval_should_be '(let ((foo '\''bar) (baz 3)) (list (symbolp '\''a) (symbolp foo) (symbolp 3) (symbolp baz) (symbolp "baz")))' '(true true false false false)' 'symbolp' lisp_eval_should_be '(progn (and (setq fnord 42) false (setq fnord 86)) fnord)' '42' 'and macro short circuit' lisp_eval_should_be '(progn (or (setq fnord 42) false (setq fnord 86)) fnord)' '42' 'or macro short circuit' lisp_eval_should_be '(and true true true true false)' 'false' 'and macro logical result' lisp_eval_should_be '(and true true true true true 42)' 'true' 'and macro returns true when all truthy' lisp_eval_should_be '(and true nil true (print "fnord") true true)' 'false' 'and macro returns false for first falsy result' lisp_eval_should_be '(or false false false 42 false)' '42' 'or macro returns first non-false' lisp_eval_should_be '(progn (setenv "FNORD" "blat") (getenv "FNORD"))' '"blat"' 'setenv' export FNORD=zart lisp_eval_should_be '(getenv "FNORD")' '"zart"' 'getenv' unset FNORD lisp_eval_should_be '(as-number "42")' '42' 'as-number' lisp_eval_should_be '(as-number "xlerb")' '()' 'as-number not number makes error' lisp_eval_should_be '(as-number true)' '()' 'as-number not string makes error' # woo, tricky quoting inside this very script, before it gets to glotawk lisp_eval_should_be_tricky_tricky 'KHN0cmluZy1sZW5ndGggIi5cYS5cYi5cZi5cbi5cci5cdC5cdi5cXC5cIi4iKQ==' 'MTkK' 'string read backslash escapes' #lisp_eval_should_be '(mapcar (lambda (x) (substr ".\a.\b.\f.\n.\r.\t.\v.\\.\"." x 1)) '\''(2 4 6 8 10 12 14 16 18))' '("\a" "\b" "\f" "\n" "\r" "\t" "\v" "\\" "\"")' lisp_eval_should_be_tricky_tricky 'KG1hcGNhciAobGFtYmRhICh4KSAoc3Vic3RyICIuXGEuXGIuXGYuXG4uXHIuXHQuXHYuXFwuXCIu IiB4IDEpKSAocXVvdGUgKDIgNCA2IDggMTAgMTIgMTQgMTYgMTgpKSk=' \ 'KCJcYSIgIlxiIiAiXGYiICJcbiIgIlxyIiAiXHQiICJcdiIgIlxcIiAiXCIiKQo=' \ 'printing backslash escapes' #lisp_eval_should_be '"\\.\\\\.\\\\\\\\.\n"' '"\\.\\\\.\\\\\\\\.\n" # ' 'backslash-escaped backslashes' lisp_eval_should_be_tricky_tricky 'IlxcLlxcXFwuXFxcXFxcXFwuXG4i' 'IlxcLlxcXFwuXFxcXFxcXFwuXG4iCg==' 'backslash-escaped backslashes' #lisp_eval_should_be '(string-length "\\.\\\\.\\\\\\\\.\n")' '11' 'backslash-escaped backslashes 2' lisp_eval_should_be_tricky_tricky 'KHN0cmluZy1sZW5ndGggIlxcLlxcXFwuXFxcXFxcXFwuXG4iKQ==' 'MTEK' 'backslash-escaped backslashes 2' tap_end