#!/usr/bin/env bash # # Usage: # ./osh-to-oil-test.sh set -o nounset set -o pipefail set -o errexit source test/common.sh proc osh-to-oil { $OSH --fix @Argv } # Compare osh code on stdin (fd 0) and expected oil code on fd 3. proc osh0-oil3 { osh-to-oil @Argv | diff -u /dev/fd/3 - || fail } proc args-vars { osh0-oil3 << ''' 3<< 'OIL' echo one "$@" two ''' << ''' echo one "$@" two OSH echo one @Argv two ''' # These are all the same. Join by first char of IFS. osh0-oil3 << ''' 3<< 'OIL' echo one $* "__$*__" $@ two ''' << ''' echo one $* "__$*__" $@ two OSH echo one $ifsjoin(Argv) "__$ifsjoin(Argv)__" $ifsjoin(Argv) two ''' osh0-oil3 << ''' 3<< 'OIL' echo $? $# ''' << ''' echo $? $# OSH echo $Status $Argc ''' } proc unquote-subs { osh0-oil3 << ''' 3<< 'OIL' echo "$1" "$foo" ''' << ''' echo "$1" "$foo" OSH echo $1 $foo ''' osh0-oil3 << ''' 3<< 'OIL' echo "${foo}" ''' << ''' echo "${foo}" OSH echo $(foo) ''' osh0-oil3 << ''' 3<< 'OIL' echo "$(echo hi)" ''' << ''' echo "$(echo hi)" OSH echo $[echo hi] ''' } proc special-vars { # TODO: Some ops don't require $(), like $foo[1] and $foo[1:1+3] # We don't want $(foo[1]). osh0-oil3 << ''' 3<< 'OIL' echo ${?} ${#} ${@} ''' << ''' echo ${?} ${#} ${@} OSH echo $(Status) $(len(Argv)) @Argv ''' # How to do $1 and then 0? # $join([$1 0]) osh0-oil3 << ''' 3<< 'OIL' echo $9 $10 ${10} ${11} ''' << ''' echo $9 $10 ${10} ${11} OSH echo $9 $10 $10 $11 # Rule is changed ''' } proc arg-array { # Only "$@" goes to @Argv # "__$@__" goes "$join(Argv)__" # Yeah the rest go to $join(Argv) # does join respect IFS though? Have to work that out. # or maybe $ifsjoin(Argv) -- make explicit the global variable. osh0-oil3 << ''' 3<< 'OIL' echo $@ $* "$@" "$*" "__$@__" "__$*__" ''' << ''' echo $@ $* "$@" "$*" "__$@__" "__$*__" OSH echo $Status $len(Argv) @Argv ''' } proc bracket-ops { osh0-oil3 << ''' 3<< 'OIL' echo ${a[1]} ${a[@]} ${PIPESTATUS[@]} ${BASH_REMATCH[@]} ''' << ''' echo ${a[1]} ${a[@]} ${PIPESTATUS[@]} ${BASH_REMATCH[@]} OSH echo $a[1] @a $PipeStatus @Match ''' } # I think ! can raise an exception. No named references or keys yet. proc prefix-ops { osh0-oil3 << ''' 3<< 'OIL' echo ${#s} ${#a[@]} ''' << ''' echo ${#s} ${#a[@]} OSH echo $len(s) $len(a) ''' } proc suffix-ops { osh0-oil3 << ''' 3<< 'OIL' echo ${s:-3} ${s##suffix} ''' << ''' echo ${s:-3} ${s##suffix} OSH echo $(s or 3) $s.trimRight('suffix') ''' } proc slice { osh0-oil3 << ''' 3<< 'OIL' echo ${a:1:2} ${a[@]:1:2} ''' << ''' echo ${a:1:2} ${a[@]:1:2} OSH echo $a[1:3] @a[1:3] ''' } # Replace is Python syntax for constants, and JavaScript syntax for # constant/regex. proc patsub { osh0-oil3 << ''' 3<< 'OIL' echo ${s/foo/bar} ${s//foo/bar} ''' << ''' echo ${s/foo/bar} ${s//foo/bar} OSH echo $s.replace('foo', 'bar') $s.replace('foo', 'bar', :ALL) ''' } proc simple-command { osh0-oil3 << ''' 3<< 'OIL' echo hi ''' << ''' echo hi OSH echo hi ''' } proc line-breaks { osh0-oil3 << ''' 3<< 'OIL' echo one \ two three \ four ''' << ''' echo one \ two three \ four OSH echo one \ two three \ four ''' } proc bracket-builtin { osh0-oil3 << ''' 3<< 'OIL' [ ! -z "$foo" ] || die ''' << ''' [ ! -z "$foo" ] || die OSH test ! -z $foo || die ''' osh0-oil3 << ''' 3<< 'OIL' if [ "$foo" -eq 3 ]; then echo yes fi ''' << ''' if [ "$foo" -eq 3 ]; then echo yes fi OSH if test $foo -eq 3 { echo yes } ''' } proc builtins { osh0-oil3 << ''' 3<< 'OIL' . lib.sh ''' << ''' . lib.sh OSH source lib.sh ''' osh0-oil3 << ''' 3<< 'OIL' [ -f lib.sh ] && . lib.sh ''' << ''' [ -f lib.sh ] && . lib.sh OSH test -f lib.sh && source lib.sh ''' # Runtime option is setoption. Compile time is at top of file, with :option # +errexit. This aids compilation. # could also be "bashoption" for deprecated stuff. # Hm but this is the DEFAULT. osh0-oil3 << ''' 3<< 'OIL' set -o errexit ''' << ''' set -o errexit OSH setoption +errexit ''' osh0-oil3 << ''' 3<< 'OIL' echo '\n' echo -e '\n' echo -e -n '\n' ''' << ''' echo '\n' echo -e '\n' echo -e -n '\n' OSH echo '\\n' echo '\n' write '\n' ''' osh0-oil3 << ''' 3<< 'OIL' eval 'echo $?' ''' << ''' eval 'echo $?' OSH oshEval('echo $?') # call into osh! ''' osh0-oil3 << ''' 3<< 'OIL' exec 1>&2 # stdout to stderr from now on ''' << ''' exec 1>&2 # stdout to stderr from now on OSH redir !1 > !2 ''' # TODO: Statically parseable [ invocations can be built in? # But not dynamic ones like [ foo $op bar ]. } proc export-readonly { # Separate definition and attribute? osh0-oil3 << ''' 3<< 'OIL' export FOO export BAR=bar ''' << ''' export FOO export BAR=bar OSH setenv FOO BAR = 'bar' setenv BAR ''' osh0-oil3 << ''' 3<< 'OIL' readonly FOO readonly BAR=bar ''' << ''' readonly FOO readonly BAR=bar OSH freeze FOO BAR = 'bar' ''' } proc redirect { osh0-oil3 << ''' 3<< 'OIL' cat >out.txt out.txt out.txt out.txt 2> err.txt ''' << ''' cat >out.txt 2> err.txt OSH cat >out.txt !2 > err.txt ''' osh0-oil3 << ''' 3<< 'OIL' echo "error message" >& 2 ''' << ''' echo "error message" >& 2 OSH echo "error message" > !2 ''' osh0-oil3 << ''' 3<< 'OIL' echo "error message" 1>&2 ''' << ''' echo "error message" 1>&2 OSH echo "error message" !1 > !2 ''' osh0-oil3 << ''' 3<< 'OIL' cat >${out} <${in} ''' << ''' cat >${out} <${in} OSH cat >$(out) <$(in) ''' } proc here-doc { osh0-oil3 << ''' 3<< 'OIL' cat < not -- # PROBLEM: It applies to the entire pipeline? Gah. # I guess you can keep it as a word? It can't be a descriptor. # Or maybe !! ? # if !echo hi | wc { # # } # # Oh yeah this seems fine. # if not echo hi { # } # if not { echo hi | wc } { # } # PipeStatus return osh0-oil3 << ''' 3<< 'OIL' ! echo hi | wc ''' << ''' ! echo hi | wc OSH not -- echo hi | wc ''' } proc and-or { osh0-oil3 << ''' 3<< 'OIL' ls && echo "$@" || die "foo" ''' << ''' ls && echo "$@" || die "foo" OSH ls && echo @Argv || die "foo" ''' } proc posix-func { osh0-oil3 << ''' 3<< 'OIL' func1() { echo func1 func2() { echo func2 } } ''' << ''' func1() { echo func1 func2() { echo func2 } } OSH proc func1 { echo func1 proc func2 { echo func2 } } ''' } proc subshell-func { osh0-oil3 << ''' 3<< 'OIL' subshell-func() ( echo subshell ) ''' << ''' subshell-func() ( echo subshell ) OSH proc subshell-func { shell { echo subshell } } ''' } proc ksh-func { osh0-oil3 << ''' 3<< 'OIL' function func1 { # no parens echo func1 function func2() { echo func2 } } ''' << ''' function func1 { # no parens echo func1 function func2() { echo func2 } } OSH proc func1 { # no parens echo func1 proc func2 { echo func2 } } ''' } proc for-loop { osh0-oil3 << ''' 3<< 'OIL' for x in a b c \ d e f; do echo $x done ''' << ''' for x in a b c \ d e f; do echo $x done OSH for x in [a b c \ d e f] { echo $x } ''' osh0-oil3 << ''' 3<< 'OIL' for x in a b c \ d e f do echo $x done ''' << ''' for x in a b c \ d e f do echo $x done OSH for x in [a b c \ d e f] { echo $x } ''' } proc empty-for-loop { osh0-oil3 << ''' 3<< 'OIL' for x in do echo $x done ''' << ''' for x in do echo $x done OSH for x in [] { echo $x } ''' } proc args-for-loop { osh0-oil3 << ''' 3<< 'OIL' for x; do echo $x done ''' << ''' for x; do echo $x done OSH for x in @Argv { echo $x } ''' # NOTE: we don't have the detailed spid info to preserve the brace style. # Leave it to the reformatter? return #set -- 1 2 3 #setargv -- 1 2 3 osh0-oil3 << ''' 3<< 'OIL' for x do echo $x done ''' << ''' for x do echo $x done OSH for x in @Argv { echo $x } ''' } proc while-loop { osh0-oil3 << ''' 3<< 'OIL' while read line; do echo $line done ''' << ''' while read line; do echo $line done OSH while read line { echo $line } ''' osh0-oil3 << ''' 3<< 'OIL' while read \ line; do echo $line done ''' << ''' while read \ line; do echo $line done OSH while read \ line { echo $line } ''' } proc if_ { osh0-oil3 << ''' 3<< 'OIL' if true; then echo yes fi ''' << ''' if true; then echo yes fi OSH if true { echo yes } ''' osh0-oil3 << ''' 3<< 'OIL' if true then echo yes fi ''' << ''' if true then echo yes fi OSH if true { echo yes } ''' osh0-oil3 << ''' 3<< 'OIL' if true; then echo yes elif false; then echo elif elif spam; then echo elif else echo no fi ''' << ''' if true; then echo yes elif false; then echo elif elif spam; then echo elif else echo no fi OSH if true { echo yes } elif false { echo elif } elif spam { echo elif } else { echo no } ''' } proc case_ { osh0-oil3 << ''' 3<< 'OIL' case $var in foo|bar) [ -f foo ] && echo file ;; *) echo default ;; esac ''' << ''' case $var in foo|bar) [ -f foo ] && echo file ;; *) echo default ;; esac OSH matchstr $var { foo|bar { test -f foo && echo file } * { echo default } } ''' osh0-oil3 << ''' 3<< 'OIL' case "$var" in *) echo foo echo bar # no dsemi esac ''' << ''' case "$var" in *) echo foo echo bar # no dsemi esac OSH matchstr $var { * { echo foo echo bar # no dsemi } } ''' } proc subshell { osh0-oil3 << ''' 3<< 'OIL' (echo hi;) ''' << ''' (echo hi;) OSH shell {echo hi;} ''' osh0-oil3 << ''' 3<< 'OIL' (echo hi) ''' << ''' (echo hi) OSH shell {echo hi} ''' osh0-oil3 << ''' 3<< 'OIL' (echo hi; echo bye) ''' << ''' (echo hi; echo bye) OSH shell {echo hi; echo bye} ''' osh0-oil3 << ''' 3<< 'OIL' ( (echo hi; echo bye ) ) ''' << ''' ( (echo hi; echo bye ) ) OSH shell { shell {echo hi; echo bye } } ''' } proc brace-group { osh0-oil3 << ''' 3<< 'OIL' { echo hi; } ''' << ''' { echo hi; } OSH do { echo hi; } ''' osh0-oil3 << ''' 3<< 'OIL' { echo hi; echo bye; } ''' << ''' { echo hi; echo bye; } OSH do { echo hi; echo bye; } ''' } proc fork { osh0-oil3 << ''' 3<< 'OIL' sleep 1& ''' << ''' sleep 1& OSH fork sleep 1 ''' } proc var-sub { osh0-oil3 << ''' 3<< 'OIL' echo $foo ''' << ''' echo $foo OSH echo $foo ''' osh0-oil3 << ''' 3<< 'OIL' echo $foo ${bar} "__${bar}__" ''' << ''' echo $foo ${bar} "__${bar}__" OSH echo $foo $(bar) "__$(bar)__" ''' osh0-oil3 << ''' 3<< 'OIL' echo ${foo:-default} ''' << ''' echo ${foo:-default} OSH echo $(foo or 'default') ''' } proc command-sub { osh0-oil3 << ''' 3<< 'OIL' echo $(echo hi) echo `echo hi` ''' << ''' echo $(echo hi) echo `echo hi` OSH echo $[echo hi] echo $[echo hi] ''' osh0-oil3 << ''' 3<< 'OIL' echo "__$(echo hi)__" ''' << ''' echo "__$(echo hi)__" OSH echo "__$[echo hi]__" ''' } proc proc-sub { osh0-oil3 << ''' 3<< 'OIL' echo <(echo hi) >(echo hi) ''' << ''' echo <(echo hi) >(echo hi) OSH echo $<[echo hi] $>[echo hi] ''' } proc arith-sub { osh0-oil3 << ''' 3<< 'OIL' echo __$(( 1+ 2 ))__ ''' << ''' echo __$(( 1+ 2 ))__ OSH echo __$( 1+ 2 )__ ''' return # Non-standard Bash style osh0-oil3 << ''' 3<< 'OIL' echo $[ 1+ 2 ] ''' << ''' echo $[ 1+ 2 ] OSH echo $( 1+ 2 ) ''' } proc arith-ops { # Operations that change: # - ** to ^ # - ternary to Python style # - / % to div mod # - bitwise operators: .& .| .^ . I think complement is still ~. # Precedence also changes, like bitwise operators # Not sure about ++i and i++. Maybe support them only in bash mode. osh0-oil3 << ''' 3<< 'OIL' echo $(( a > 0 ? 2**3 : 3/2 )) ''' << ''' echo $(( a > 0 ? 2**3 : 3/2 )) OSH echo $(( 2^3 if a > 0 else 3 div 2 )) ''' osh0-oil3 << ''' 3<< 'OIL' echo $(( a << 1 | b & 1 )) ''' << ''' echo $(( a << 1 | b & 1 )) OSH echo $(( a << 1 .| b .& 1 )) ''' } # newerThan, olderThan, etc. proc dbracket { osh0-oil3 << ''' 3<< 'OIL' [[ -d / ]] && echo "is dir" ''' << ''' [[ -d / ]] && echo "is dir" OSH isDir('/') && echo "is dir" ''' } proc escaped-literal { osh0-oil3 << ''' 3<< 'OIL' echo \$ \ \n "\$" "\n" ''' << ''' echo \$ \ \n "\$" "\n" OSH echo '$' ' ' 'n' "\$" "\n" ''' return # TODO: Have to combine adjacent escaped literals osh0-oil3 << ''' 3<< 'OIL' echo \$\ \$ ''' << ''' echo \$\ \$ OSH echo '$ $' ''' # Make sure we don't mess up the backslash osh0-oil3 << ''' 3<< 'OIL' echo \ hi ''' << ''' echo \ hi OSH echo \ hi ''' } proc words { # I probably should drive this with specific cases, rather than making it # super general. Most scripts don't use WordPart juxtaposition. osh0-oil3 << ''' 3<< 'OIL' echo foo'bar' echo foo'bar'$baz ''' << ''' echo foo'bar' echo foo'bar'$baz OSH echo 'foobar' echo "foobar$baz" ''' # Avoiding WordPart joining. # Need specific use cases too osh0-oil3 << ''' 3<< 'OIL' echo ~/'name with spaces' ''' << ''' echo ~/'name with spaces' OSH echo "$HOME/name with spaces" ''' } proc time-block { osh0-oil3 << ''' 3<< 'OIL' time ls ''' << ''' time ls OSH time ls ''' osh0-oil3 << ''' 3<< 'OIL' time while false; do echo $i done ''' << ''' time while false; do echo $i done OSH time while false { echo $i } ''' return # TODO: The "do" has to be removed osh0-oil3 << ''' 3<< 'OIL' time { echo one echo two } ''' << ''' time { echo one echo two } OSH time { echo one echo two } ''' } readonly -a PASSING=( simple-command more-env line-breaks redirect pipeline and-or # Word stuff escaped-literal args-vars unquote-subs # Substitutions command-sub arith-sub unquote-subs posix-func ksh-func # Compound commands brace-group subshell while-loop if_ case_ for-loop empty-for-loop args-for-loop time-block # Builtins bracket-builtin ) proc all-passing { run-all $(PASSING[@]) } proc run-for-release { local out_dir=_tmp/osh2oil mkdir -p $out_dir all-passing | tee $out_dir/log.txt echo "Wrote $out_dir/log.txt" } @Argv