diff --git a/.version b/.version index 6b4950e..f9fe6b4 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.4 +2.5b diff --git a/LICENSE b/LICENSE index 4779b34..cf8e84f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,6 @@ MIT License +Copyright (c) 2020 Brandon Rozek Copyright (c) 2019 Sławomir Śledź Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/README.md b/README.md index 30e5395..15673cd 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ # Introduction -[Introduction to fun.sh library](http://ssledz.github.io/presentations/bash-fun.html#/) +This is a fork of [ssledz's fun.sh library](https://github.com/ssledz/bash-fun). +This is mainly for my own personal use cases. So I would recommend using ssledz's version instead. +I mainly worked towards getting this library to mostly pass shellcheck, removed some functionality, +and name deconflicted some of the functions with what I had in my system. # Quick start ```bash #!/bin/bash -. <(test -e fun.sh || curl -Ls https://raw.githubusercontent.com/ssledz/bash-fun/master/src/fun.sh > fun.sh; cat fun.sh) +. <(test -e fun.sh || curl -Ls https://raw.githubusercontent.com/brandon-rozek/bash-fun/master/src/fun.sh > fun.sh; cat fun.sh) seq 1 4 | sum ``` @@ -14,17 +17,17 @@ seq 1 4 | sum # Functions overview ||||||| |------|------|------|------|------|------| -|**append**|**buff**|**call**|**catch**|**curry**|**div**| -|**drop**|**dropw**|**factorial**|**filter**|**foldl**|**foldr**| +|**list_append**|**divide**|**take_while**| +|**list_drop**|**drop_while**|**factorial**|**filter**|**foldl**| |**isint**|**isempty**|**isfile**|**isnonzerofile**|**isreadable**|**iswritable**| -|**isdir**|**join**|**lambda**|**last**|**lhead**|**list**| -|**ltail**|**lzip**|**map**|**maybe**|**maybemap**|**maybevalue**| -|**mod**|**mul**|**not**|**ntup**|**ntupl**|**ntupr**| -|**ntupx**|**peek**|**plus**|**prepend**|**product**|**ret**| -|**res**|**revers**|**revers_str**|**scanl**|**splitc**|**strip**| -|**stripl**|**stripr**|**sub**|**sum**|**take**|**try**| -|**tup**|**tupl**|**tupr**|**tupx**|**unlist**|**λ**| -|**with_trampoline**| +|**isdir**|**list_join**|**lambda**|**list_last**|**list_head**|**list**| +|**list_tail**|**list_zip**|**list_map**| +|**mod**|**multiply**|**not**| +|**add**|**list_prepend**|**product**|**ret**| +|**revers**|**revers_str**|**scanl**|**splitc**|**strip**| +|**stripl**|**stripr**|**subtract**|**sum**|**take**| +|**tup**|**unlist**|**λ**| + ## *list/unlist* @@ -38,25 +41,25 @@ $ list 1 2 3 4 5 | unlist 1 2 3 4 5 ``` -## *take/drop/ltail/lhead/last* +## *list_take/list_drop/list_tail/list_head/list_last* ```bash -$ list 1 2 3 4 | drop 2 +$ list 1 2 3 4 | list_drop 2 3 4 -$ list 1 2 3 4 5 | lhead +$ list 1 2 3 4 5 | list_head 1 -$ list 1 2 3 4 | ltail +$ list 1 2 3 4 | list_tail 2 3 4 -$ list 1 2 3 4 5 | last +$ list 1 2 3 4 5 | list_last 5 -$ list 1 2 3 4 5 | take 2 +$ list 1 2 3 4 5 | list_take 2 1 2 ``` @@ -64,61 +67,36 @@ $ list 1 2 3 4 5 | take 2 ## *join* ```bash -$ list 1 2 3 4 5 | join , +$ list 1 2 3 4 5 | list_join , 1,2,3,4,5 - -$ list 1 2 3 4 5 | join , [ ] -[1,2,3,4,5] ``` ## *map* ```bash -$ seq 1 5 | map λ a . 'echo $((a + 5))' +$ seq 1 5 | list_map λ a . 'echo $((a + 5))' 6 7 8 9 10 -$ list a b s d e | map λ a . 'echo $a$(echo $a | tr a-z A-Z)' +$ list a b s d e | list_map λ a . 'echo $a$(echo $a | tr a-z A-Z)' aA bB sS dD eE -$ list 1 2 3 | map echo +$ list 1 2 3 | list_map tee 1 2 3 - -$ list 1 2 3 | map 'echo $ is a number' -1 is a number -2 is a number -3 is a number - -$ list 1 2 3 4 | map 'echo \($,$\) is a point' -(1,1) is a point -(2,2) is a point -(3,3) is a point -(4,4) is a point ``` - -## *flat map* - -```bash -$ seq 2 3 | map λ a . 'seq 1 $a' | join , [ ] -[1,2,1,2,3] - -$ list a b c | map λ a . 'echo $a; echo $a | tr a-z A-z' | join , [ ] -[a,A,b,B,c,C] -``` - ## *filter* ```bash -$ seq 1 10 | filter λ a . '[[ $(mod $a 2) -eq 0 ]] && ret true || ret false' +$ seq 1 10 | filter even 2 4 6 @@ -131,10 +109,6 @@ $ seq 1 10 | filter λ a . '[[ $(mod $a 2) -eq 0 ]] && ret true || ret false' ```bash $ list a b c d | foldl λ acc el . 'echo -n $acc-$el' a-b-c-d - -$ list '' a b c d | foldr λ acc el .\ - 'if [[ ! -z $acc ]]; then echo -n $acc-$el; else echo -n $el; fi' -d-c-b-a ``` ```bash @@ -143,11 +117,8 @@ $ seq 1 4 | foldl λ acc el . 'echo $(($acc + $el))' ``` ```bash -$ seq 1 4 | foldl λ acc el . 'echo $(mul $(($acc + 1)) $el)' +$ seq 1 4 | foldl λ acc el . 'echo $(multiply $(($acc + 1)) $el)' 64 # 1 + (1 + 1) * 2 + (4 + 1) * 3 + (15 + 1) * 4 = 64 - -$ seq 1 4 | foldr λ acc el . 'echo $(mul $(($acc + 1)) $el)' -56 # 1 + (1 + 1) * 4 + (8 + 1) * 3 + (27 + 1) * 2 = 56 ``` ## *tup/tupx/tupl/tupr* @@ -160,19 +131,16 @@ $ tup 'foo bar' 1 'one' 2 (foo bar,1,one,2) $ tup , 1 3 -(u002c,1,3) +(,,1,3) ``` ```bash -$ tupl $(tup a 1) +$ echo tup a 1 | tupl a -$ tupr $(tup a 1) +$ echo tup a 1 | tupr 1 -$ tup , 1 3 | tupl -, - $ tup 'foo bar' 1 'one' 2 | tupl foo bar @@ -180,63 +148,10 @@ $ tup 'foo bar' 1 'one' 2 | tupr 2 ``` -```bash -$ tup 'foo bar' 1 'one' 2 | tupx 2 -1 - -$ tup 'foo bar' 1 'one' 2 | tupx 1,3 -foo bar -one - -$ tup 'foo bar' 1 'one' 2 | tupx 2-4 -1 -one -2 -``` - -## *ntup/ntupx/ntupl/ntupr* +## *list_zip* ```bash -$ ntup tuples that $(ntup safely nest) -(dHVwbGVzCg==,dGhhdAo=,KGMyRm1aV3g1Q2c9PSxibVZ6ZEFvPSkK) - -echo '(dHVwbGVzCg==,dGhhdAo=,KGMyRm1aV3g1Q2c9PSxibVZ6ZEFvPSkK)' | ntupx 3 | ntupr -nest - -$ ntup 'foo,bar' 1 one 1 -(Zm9vLGJhcgo=,MQo=,b25lCg==,MQo=) - -$ echo '(Zm9vLGJhcgo=,MQo=,b25lCg==,MQo=)' | ntupx 1 -foo,bar -``` - -```bash -$ ntupl $(ntup 'foo bar' 1 one 2) -foo bar - -$ ntupr $(ntup 'foo bar' 1 one 2) -2 -``` - -## *buff* - -```bash -$ seq 1 10 | buff λ a b . 'echo $(($a + $b))' -3 -7 -11 -15 -19 - -$ seq 1 10 | buff λ a b c d e . 'echo $(($a + $b + $c + $d + $e))' -15 -40 -``` - -## *lzip* - -```bash -$ list a b c d e f | lzip $(seq 1 10) +$ list a b c d e f | list_zip $(seq 1 10) (a,1) (b,2) (c,3) @@ -246,86 +161,10 @@ $ list a b c d e f | lzip $(seq 1 10) ``` ```bash -$ list a b c d e f | lzip $(seq 1 10) | last | tupr +$ list a b c d e f | list_zip $(seq 1 10) | list_last | tupr 6 ``` -## *curry* - -```bash -add2() { - echo $(($1 + $2)) -} -``` - -```bash -$ curry inc add2 1 -``` - -```bash -$ inc 2 -3 - -$ seq 1 3 | map λ a . 'inc $a' -2 -3 -4 -``` - -## *peek* - -```bash -$ list 1 2 3 \ - | peek lambda a . echo 'dbg a : $a' \ - | map lambda a . 'mul $a 2' \ - | peek lambda a . echo 'dbg b : $a' \ - | sum - -dbg a : 1 -dbg a : 2 -dbg a : 3 -dbg b : 2 -dbg b : 4 -dbg b : 6 -12 -``` - -```bash -$ a=$(seq 1 4 | peek lambda a . echo 'dbg: $a' | sum) - -dbg: 1 -dbg: 2 -dbg: 3 -dbg: 4 - -$ echo $a - -10 -``` - -## *maybe/maybemap/maybevalue* - -```bash -$ list Hello | maybe -(Just,Hello) - -$ list " " | maybe -(Nothing) - -$ list Hello | maybe | maybemap λ a . 'tr oH Oh <<<$a' -(Just,hellO) - -$ list " " | maybe | maybemap λ a . 'tr oH Oh <<<$a' -(Nothing) - -$ echo bash-fun rocks | maybe | maybevalue DEFAULT -bash-fun rocks - -$ echo | maybe | maybevalue DEFAULT -DEFAULT - -``` - ## *not/isint/isempty* ```bash @@ -338,7 +177,7 @@ false $ not true false -$ not isint 777 +$ not "isint 777" false $ list 1 2 "" c d 6 | filter λ a . 'isint $a' @@ -346,7 +185,7 @@ $ list 1 2 "" c d 6 | filter λ a . 'isint $a' 2 6 -$ list 1 2 "" c d 6 | filter λ a . 'not isempty $a' +$ list 1 2 "" c d 6 | filter λ a . 'not "isempty $a"' 1 2 c @@ -393,35 +232,6 @@ $ list $files | filter λ a . 'not isfile $a' /tmp /no_such_file ``` - -## *try/catch* - -```bash -$ echo 'expr 2 / 0' | try λ _ . 'echo 0' -0 - -$ echo 'expr 2 / 0' | try λ status . 'echo $status' -2 - -$ echo 'expr 2 / 2' | try λ _ . 'echo 0' -1 -``` - -```bash -try λ _ . 'echo some errors during pull; exit 1' < <(echo git pull) -``` - -```bash -$ echo 'expr 2 / 0' \ - | LANG=en catch λ cmd status val . 'echo cmd=$cmd,status=$status,val=$val' -cmd=expr 2 / 0,status=2,val=(expr:,division,by,zero) -``` - -```bash -$ echo 'expr 2 / 2' | catch λ _ _ val . 'tupl $val' -1 -``` - ## *scanl* ```bash @@ -434,58 +244,10 @@ $ seq 1 5 | scanl lambda acc el . 'echo $(($acc + $el))' ``` ```bash -$ seq 1 5 | scanl lambda a b . 'echo $(($a + $b))' | last +$ seq 1 5 | scanl lambda a b . 'echo $(($a + $b))' | list_last 15 ``` -## *with_trampoline/res/call* - -```bash -factorial() { - fact_iter() { - local product=$1 - local counter=$2 - local max_count=$3 - if [[ $counter -gt $max_count ]]; then - res $product - else - call fact_iter $(echo $counter\*$product | bc) $(($counter + 1)) $max_count - fi - } - - with_trampoline fact_iter 1 1 $1 -} -``` - -```bash -$ time factorial 30 | fold -w 70 -265252859812191058636308480000000 - -real 0m1.854s -user 0m0.072s -sys 0m0.368s -``` - -```bash -time factorial 60 | fold -w 70 -8320987112741390144276341183223364380754172606361245952449277696409600 -000000000000 - -real 0m3.635s -user 0m0.148s -sys 0m0.692s -``` - -```bash -$ time factorial 90 | fold -w 70 -1485715964481761497309522733620825737885569961284688766942216863704985 -393094065876545992131370884059645617234469978112000000000000000000000 - -real 0m4.371s -user 0m0.108s -sys 0m0.436s -``` - # Examples ```bash @@ -498,7 +260,7 @@ processNames() { list $@ \ | filter λ name . '[[ ${#name} -gt 1 ]] && ret true || ret false' \ - | map λ name . 'uppercase $name' \ + | list_map λ name . 'uppercase $name' \ | foldl λ acc el . 'echo $acc,$el' } @@ -511,7 +273,7 @@ Adam,Monika,Slawek,Daniel,Bartek ``` # Running tests - +TODO: Need to change the tests here ```bash cd test ./test_runner diff --git a/src/fun.sh b/src/fun.sh old mode 100755 new mode 100644 index 08f3bc1..704ef28 --- a/src/fun.sh +++ b/src/fun.sh @@ -1,437 +1,548 @@ -#!/bin/bash - -drop() { - command tail -n +$(($1 + 1)) -} - -take() { - command head -n ${1} -} - -ltail() { - drop 1 -} - -lhead() { - take 1 -} - -last() { - command tail -n 1 -} +#!/bin/sh +############################################### +## List Functions +############################################### list() { - for i in "$@"; do - echo "$i" - done + for i in "$@"; do + echo "$i" + done } unlist() { - cat - | xargs + xargs } -append() { - cat - - list "$@" +# Drop the first n items of a list. +list_drop() { + command tail -n +$(($1 + 1)) } -prepend() { - list "$@" - cat - +# Take the first n items of a list. +list_take() { + command head -n "$1" } +# Take the 'tail' of a list. +# Otherwise known as dropping the first element. +list_tail() { + list_drop 1 +} +# Take only the first element of the list. +list_head() { + list_take 1 +} + +# Take the last element of the list. +list_last() { + command tail -n 1 +} + +# Add the contents of standard input +# to the end of the list. +list_append() { + cat - + list "$@" +} + +# Add the contents of standard input +# to the beginning of the list. +list_prepend() { + list "$@" + cat - +} + +############################################### +## Lambdas and Lists +############################################### +# Defines an anonymous function. lambda() { + # shellcheck disable=2039 + local expression + lam() { + # shellcheck disable=2039 + local arg + while [ $# -gt 0 ]; do + arg="$1" + shift + if [ "$arg" = '.' ]; then + echo "$@" + return + else + echo "read $arg;" + fi + done + } - lam() { - local arg - while [[ $# -gt 0 ]]; do - arg="$1" - shift - if [[ $arg = '.' ]]; then - echo "$@" - return - else - echo "read $arg;" - fi - done - } - - eval $(lam "$@") - + expression=$(lam "$@") + eval "$expression" } +# Same as lambda. +# shellcheck disable=2039 λ() { - lambda "$@" + lambda "$@" } -map() { - if [[ $1 != "λ" ]] && [[ $1 != "lambda" ]]; then +# Print the number of arguments a lambda takes. +# shellcheck disable=2039 +λ_num_args() { + # Calculates the number of arguments a lambda takes + minus "$#" 3 +} - local has_dollar=$(list $@ | grep '\$' | wc -l) - - if [[ $has_dollar -ne 0 ]]; then - args=$(echo $@ | sed -e 's/\$/\$a/g') - map λ a . $args - else - map λ a . "$@"' $a' - fi - else +# Perform an operation to each +# element(s) of a list provided +# through standard input. +list_map() { + # shellcheck disable=2039 local x - while read x; do - echo "$x" | "$@" - done - fi -} - -foldl() { - local f="$@" - local acc - read acc - while read elem; do - acc="$({ echo $acc; echo $elem; } | $f )" - done - echo "$acc" -} - -foldr() { - local f="$@" - local acc - local zero - read zero - foldrr() { - local elem - - if read elem; then - acc=$(foldrr) -# [[ -z $acc ]] && echo $elem && return - else - echo $zero && return + # shellcheck disable=2039 + local i + # shellcheck disable=2039 + local arguments + # shellcheck disable=2039 + local num_args + if [ "$1" = "λ" ] || [ "$1" = "lambda" ]; then + num_args=$(λ_num_args "$@") + while read -r x; do + arguments="$x" + i=2 + while [ $i -le "$num_args" ] ; do + read -r x + arguments="$arguments $x" + i=$(add $i 1) + done + # We want to word split arguments, so no quotes + eval "list $arguments" | "$@" + done + else # Do not know the arity, assume 1 + while read -r x; do + echo "$x" | "$@" + done fi - - acc="$({ echo $acc; echo $elem; } | $f )" - echo "$acc" - } - - foldrr } +# Perform a binary operation on a list +# where one element is the accumulation +# of the results so far. +# Ex: seq 3 | foldl lambda a b . 'minus $a $b' +# First is (1 - 2 = -1) then (-1 - 3 = -4). +foldl() { + # shellcheck disable=2039 + local acc + read -r acc + while read -r elem; do + acc=$({ echo "$acc"; echo "$elem"; } | "$@" ) + done + echo "$acc" +} + +# Constructs a list where each element +# is the foldl of the 0th-ith elements of +# the list. scanl() { - local f="$@" - local acc - read acc - echo $acc - while read elem; do - acc="$({ echo $acc; echo $elem; } | $f )" + # shellcheck disable=2039 + local acc + read -r acc echo "$acc" - done + while read -r elem; do + acc=$({ echo "$acc"; echo "$elem"; } | "$@" ) + echo "$acc" + done } -mul() { - ( set -f; echo $(($1 * $2)) ) +# Drops any elements of the list where the +# function performed on it evaluates to false. +filter() { + # shellcheck disable=2039 + local x + while read -r x; do + ret=$(echo "$x" | "$@") + if_then "$ret" "echo $x" + done } -plus() { - echo $(($1 + $2)) +# Keep taking elements until a certain condition +# is false. +take_while() { + # shellcheck disable=2039 + local x + # shellcheck disable=2039 + local condition + while read -r x; do + condition="$(echo "$x" | "$@")" + if_then_else "$condition" "echo $x" "break" + done } -sub() { - echo $(($1 - $2)) +# Keep dropping elements until a certain condition +# is false. +drop_while() { + # shellcheck disable=2039 + local x + while read -r x; do + condition="$(echo "$x" | "$@")" + if_then_else "$condition" 'do_nothing' 'break' + done + if_then "[ -n $x ]" "{ echo $x; cat -; }" } -div() { - echo $(($1 / $2)) + +############################################### +## Arithmetic Functions +############################################### +multiply() { + # shellcheck disable=2039 + local a + # shellcheck disable=2039 + local b + a=$1 + if [ $# -lt 2 ] ; then + read -r b + else + b=$2 + fi + isint "$a" > /dev/null && \ + isint "$b" > /dev/null && \ + echo $((a * b)) +} + +add() { + # shellcheck disable=2039 + local a + # shellcheck disable=2039 + local b + a=$1 + if [ $# -lt 2 ] ; then + read -r b + else + b=$2 + fi + isint "$a" > /dev/null && \ + isint "$b" > /dev/null && \ + echo $((a + b)) +} + +minus() { + # shellcheck disable=2039 + local a + # shellcheck disable=2039 + local b + a=$1 + if [ $# -lt 2 ] ; then + b=$1 + read -r a + else + b=$2 + fi + isint "$a" > /dev/null && \ + isint "$b" > /dev/null && \ + echo $((a - b)) +} + +divide() { + # shellcheck disable=2039 + local a + # shellcheck disable=2039 + local b + a=$1 + if [ $# -lt 2 ] ; then + b=$1 + read -r a + else + b=$2 + fi + isint "$a" > /dev/null && \ + isint "$b" > /dev/null && \ + echo $((a / b)) } mod() { - echo $(($1 % $2)) + # shellcheck disable=2039 + local a + # shellcheck disable=2039 + local b + a=$1 + if [ $# -lt 2 ] ; then + b=$1 + read -r a + else + b=$2 + fi + isint "$a" > /dev/null && \ + isint "$b" > /dev/null && \ + echo $((a % b)) } +even() { + # shellcheck disable=2039 + local n + # shellcheck disable=2039 + local result + # shellcheck disable=2039 + local result_code + if [ $# -lt 1 ] ; then + read -r n + else + n=$1 + fi + result=$(mod "$n" 2) + result_code=$? + if [ $result_code -ne 0 ] ; then + ret false + else + result_to_bool "[ $result = 0 ]" + fi +} + +odd() { + not even +} + +less_than() { + # shellcheck disable=2039 + local n + read -r n + if isint "$n" > /dev/null && \ + [ "$n" -lt "$1" ] ; then + ret true + else + ret false + fi +} sum() { - foldl lambda a b . 'echo $(($a + $b))' + foldl lambda a b . "add \$a \$b" } product() { - foldl lambda a b . 'echo $(mul $a $b)' + foldl lambda a b . "multiply \$a \$b" } factorial() { - seq 1 $1 | product + seq 1 "$1" | product } +############################################### +## String Operations +############################################### +# Splits a string into a list where each element +# is one character. splitc() { - cat - | sed 's/./&\n/g' + sed 's/./\n&/g' | list_tail } -join() { - local delim=$1 - local pref=$2 - local suff=$3 - echo $pref$(cat - | foldl lambda a b . 'echo $a$delim$b')$suff +# Takes a list and creates a string where +# each element is seperated by a delimiter. +list_join() { + # shellcheck disable=2039 + local delim + delim=$1 + foldl lambda a b . "echo \$a$delim\$b" } +# Split a string into a list +# by a specified delimeter +str_split() { + sed "s/$1/\n/g" +} + +# Reverses a list. revers() { - foldl lambda a b . 'append $b $a' + # shellcheck disable=2039 + local result + # shellcheck disable=2039 + local n + while read -r n; do + result="$n\n$result" + done + echo "$result" } -revers_str() { - cat - | splitc | revers | join +# Removes multiple occurences of +# a single character from the beginning +# of the list. +lstrip() { + # shellcheck disable=2039 + local c + if [ $# -eq 0 ] ; then + c=" " + else + c="$1" + fi + sed "s/^$c*//g" } -catch() { - local f="$@" - local cmd=$(cat -) - local val=$(2>&1 eval "$cmd"; echo $?) - local cnt=$(list $val | wc -l) - local status=$(list $val | last) - $f < <(list "$cmd" $status $(list $val | take $((cnt - 1)) | unlist | tup)) +# Removes multiple occurences of +# a single character from the end +# of the list. +rstrip() { + # shellcheck disable=2039 + local c + if [ $# -eq 0 ] ; then + c=" " + else + c="$1" + fi + sed "s/$c*$//g" } -try() { - local f="$@" - catch lambda cmd status val . '[[ $status -eq 0 ]] && tupx 1- $val | unlist || { '"$f"' < <(list $status); }' +# Removes multiple occurences of +# a single character from the beginning +# and end of the list. +strip() { + lstrip "$@" | rstrip "$@" +} + +############################################### +## Tuple Functions +############################################### + +# Creates a tuple, which is a string with +# multiple elements seperated by a comma, +# and it begins with a ( and ends with a ). +tup() { + # shellcheck disable=2039 + local args + # shellcheck disable=2039 + local result + if [ $# -eq 0 ]; then + args=$(unlist) + eval "tup $args" + else + result=$(list "$@" | list_join ,) + echo "($result)" + fi +} + +# Takes a tuple and outputs it as a list +tup_to_list() { + local li + local f + local la + li=$(str_split ",") + + # Remove '(' from the first element + f=$(echo "$li" | list_head) + f=$(echo "$f" | sed 's/^(//') + + la=$(echo "$li" | list_last) + # If there is only one element in the list + # Remove ')' from the only element + if [ "$(echo "$la" | cut -c1)" = "(" ]; then + f=$(echo "$f" | sed "s/)$//") + echo "$f" + # If there is more than one element in the list + # Remove ')' from the last element + else + la=$(echo "$la" | sed "s/)$//") + # Remove the first and last element from li + li=$(echo "$li" | list_tail | sed '$d') + # Print the list + { echo "$f"; echo "$li"; echo "$la"; } + fi +} + +# Takes the first element of the tuple +tupl() { + tup_to_list | list_head +} + +# Takes the last element of the tuple +tupr() { + tup_to_list | list_last +} + + +# Takes each element from a list in standard +# input and matches it with a list provided +# as the argument to this function. +# The result is a list of 2-tuples. +list_zip() { + # shellcheck disable=2039 + local l + l=$(list "$@") + while read -r x; do + y=$(echo "$l" | list_take 1) + tup "$x" "$y" + l=$(echo "$l" | list_drop 1) + done +} + +############################################### +## Logic Based Functions +############################################### + +if_then() { + # shellcheck disable=2039 + local result + eval "$1" + result=$? + if [ $result -eq 0 ] ; then + eval "$2" + fi +} + +if_then_else() { + # shellcheck disable=2039 + local result + eval "$1" + result=$? + if [ $result -eq 0 ] ; then + eval "$2" + else + eval "$3" + fi +} + +result_to_bool() { + if_then_else "$1" 'ret true' 'ret false' +} + +not() { + if_then_else "$1 > /dev/null" "ret false" "ret true" } ret() { - echo $@ + echo "$@" + "$@" } -filter() { - local x - while read x; do - ret=$(echo "$x" | "$@") - $ret && echo $x - done -} - -pass() { - echo > /dev/null -} - -dropw() { - local x - while read x && $(echo "$x" | "$@"); do - pass - done - [[ ! -z $x ]] && { echo $x; cat -; } -} - -peek() { - local x - while read x; do - ([ $# -eq 0 ] && 1>&2 echo $x || 1>&2 "$@" < <(echo $x)) - echo $x - done -} - -stripl() { - local arg=$1 - cat - | map lambda l . 'ret ${l##'$arg'}' -} - -stripr() { - local arg=$1 - cat - | map lambda l . 'ret ${l%%'$arg'}' -} - -strip() { - local arg=$1 - cat - | stripl "$arg" | stripr "$arg" -} - -buff() { - local cnt=-1 - for x in $@; do - [[ $x = '.' ]] && break - cnt=$(plus $cnt 1) - done - local args='' - local i=$cnt - while read arg; do - [[ $i -eq 0 ]] && list $args | "$@" && i=$cnt && args='' - args="$args $arg" - i=$(sub $i 1) - done - [[ ! -z $args ]] && list $args | "$@" -} - -tup() { - if [[ $# -eq 0 ]]; then - local arg - read arg - tup $arg - else - list "$@" | map lambda x . 'echo ${x//,/u002c}' | join , '(' ')' - fi -} - -tupx() { - if [[ $# -eq 1 ]]; then - local arg - read arg - tupx "$1" "$arg" - else - local n=$1 - shift - echo "$@" | stripl '(' | stripr ')' | cut -d',' -f${n} | tr ',' '\n' | map lambda x . 'echo ${x//u002c/,}' - fi -} - -tupl() { - tupx 1 "$@" -} - -tupr() { - tupx 1- "$@" | last -} - -ntup() { - if [[ $# -eq 0 ]]; then - local arg - read arg - ntup $arg - else - list "$@" | map lambda x . 'echo "$x" | base64 --wrap=0 ; echo' | join , '(' ')' - fi -} - -ntupx() { - if [[ $# -eq 1 ]]; then - local arg - read arg - ntupx "$1" "$arg" - else - local n=$1 - shift - echo "$@" | stripl '(' | stripr ')' | cut -d',' -f${n} | tr , '\n' | map lambda x . 'echo "$x" | base64 -d' - fi -} - -ntupl() { - ntupx 1 "$@" -} - -ntupr() { - ntupx 1- "$@" | last -} - -lzip() { - local list=$* - cat - | while read x; do - y=$(list $list | take 1) - tup $x $y - list=$(list $list | drop 1) - done -} - -curry() { - exportfun=$1; shift - fun=$1; shift - params=$* - cmd=$"function $exportfun() { - more_params=\$*; - $fun $params \$more_params; - }" - eval $cmd -} - -with_trampoline() { - local f=$1; shift - local args=$@ - while [[ $f != 'None' ]]; do - ret=$($f $args) -# echo $ret - f=$(tupl $ret) - args=$(echo $ret | tupx 2- | tr ',' ' ') - done - echo $args -} - -res() { - local value=$1 - tup "None" $value -} - -call() { - local f=$1; shift - local args=$@ - tup $f $args -} - -maybe() { - if [[ $# -eq 0 ]]; then - local arg - read arg - maybe "$arg" - else - local x="$*" - local value=$(echo $x | strip) - if [[ ${#value} -eq 0 ]]; then - tup Nothing - else - tup Just "$value" - fi - fi -} - -maybemap() { - local x - read x - if [[ $(tupl $x) = "Nothing" ]]; then - echo $x - else - local y=$(tupr "$x") - local r=$(echo "$y" | map "$@") - maybe "$r" - fi -} - -maybevalue() { - local default="$*" - local x - read x - if [[ $(tupl $x) = "Nothing" ]]; then - echo "$default" - else - echo $(tupr $x) - fi +do_nothing() { + echo > /dev/null } -# commonly used predicates for filter -# e.g. list 1 a 2 b 3 c | filter lambda x . 'isint $x' - -# inverse another test, e.g. "not isint $x" -not() { - local r=$("$@" 2>/dev/null) - $r && ret false || ret true -} +############################################### +## Useful utility functions +############################################### isint() { - [ "$1" -eq "$1" ] 2>/dev/null && ret true || ret false + result_to_bool "echo \"$1\" | grep -Eq '^-?[0-9]+$'" } isempty() { - [ -z "$1" ] && ret true || ret false + result_to_bool "[ -z \"$1\" ]" } isfile() { - [ -f "$1" ] && ret true || ret false + result_to_bool "[ -f \"$1\" ]" } isnonzerofile() { - [ -s "$1" ] && ret true || ret false + result_to_bool "[ -s \"$1\" ]" } isreadable() { - [ -r "$1" ] && ret true || ret false + result_to_bool "[ -r \"$1\" ]" } iswritable() { - [ -w "$1" ] && ret true || ret false + result_to_bool "[ -w \"$1\" ]" } isdir() { - [ -d "$1" ] && ret true || ret false + result_to_bool "[ -d \"$1\" ]" } diff --git a/test/append_test.sh b/test/append_test.sh index e6ceb99..2ee28fc 100755 --- a/test/append_test.sh +++ b/test/append_test.sh @@ -1,15 +1,15 @@ #! /bin/bash testAppendToEmptyList() { - assertEquals 4 "$(list | append 4)" + assertEquals 4 "$(list | list_append 4)" } testAppendToOneElementList() { - assertEquals "1 4" "$(list 1 | append 4 | unlist)" + assertEquals "1 4" "$(list 1 | list_append 4 | unlist)" } testAppendToList() { - assertEquals "1 2 3 4 5 4" "$(list 1 2 3 4 5 | append 4 | unlist)" + assertEquals "1 2 3 4 5 4" "$(list 1 2 3 4 5 | list_append 4 | unlist)" } . ./shunit2-init.sh \ No newline at end of file diff --git a/test/catch_test.sh b/test/catch_test.sh deleted file mode 100755 index b3f0917..0000000 --- a/test/catch_test.sh +++ /dev/null @@ -1,19 +0,0 @@ -#! /bin/bash - -testCatchIfSuccess() { - assertEquals 1 "$(echo 'expr 2 / 2' | catch lambda cmd status val . '[[ $status -eq 0 ]] && tupl $val || echo 0')" -} - -testCatchIfError() { - assertEquals 0 $(echo 'expr 2 / 0' | catch lambda cmd status val . '[[ $status -eq 0 ]] && tupl $val || echo 0') - assertEquals 'cmd=expr 2 / 0,status=2,val=(expr:,division,by,zero)' "$(echo 'expr 2 / 0' | echo 'expr 2 / 0' | LANG=en catch lambda cmd status val . 'echo cmd=$cmd,status=$status,val=$val')" -} - -testCatchEdgeCases() { - assertEquals 1 "$(echo 'expr 2 / 2' | catch lambda _ _ val . 'tupl $val')" - assertEquals 'expr 2 / 2' "$(echo 'expr 2 / 2' | catch lambda cmd . 'ret $cmd')" - assertEquals 'expr 2 / 2,0' "$(echo 'expr 2 / 2' | catch lambda cmd status . 'ret $cmd,$status')" - assertEquals 'expr 2 / 0,2' "$(echo 'expr 2 / 0' | catch lambda cmd status . 'ret $cmd,$status')" -} - -. ./shunit2-init.sh \ No newline at end of file diff --git a/test/drop_test.sh b/test/drop_test.sh index 5aef523..7195d48 100755 --- a/test/drop_test.sh +++ b/test/drop_test.sh @@ -1,23 +1,23 @@ #! /bin/bash testDrop9From10() { - assertEquals 10 $(list {1..10} | drop 9) + assertEquals 10 $(list {1..10} | list_drop 9) } testDrop8From10() { - assertEquals "9 10" "$(list {1..10} | drop 8 | unlist)" + assertEquals "9 10" "$(list {1..10} | list_drop 8 | unlist)" } testDropAll() { - assertEquals "" "$(list {1..10} | drop 10)" + assertEquals "" "$(list {1..10} | list_drop 10)" } testDropMoreThanAvailable() { - assertEquals "" "$(list {1..10} | drop 15)" + assertEquals "" "$(list {1..10} | list_drop 15)" } testDropZero() { - assertEquals "1 2 3 4 5 6 7 8 9 10" "$(list {1..10} | drop 0 | unlist)" + assertEquals "1 2 3 4 5 6 7 8 9 10" "$(list {1..10} | list_drop 0 | unlist)" } . ./shunit2-init.sh \ No newline at end of file diff --git a/test/head_test.sh b/test/head_test.sh index 90ea201..0293890 100755 --- a/test/head_test.sh +++ b/test/head_test.sh @@ -1,16 +1,16 @@ #! /bin/bash testLHeadFromList() { - assertEquals 1 $(list {1..10} | lhead) - assertEquals 5 $(list 5 6 7 | lhead) + assertEquals 1 $(list {1..10} | list_head) + assertEquals 5 $(list 5 6 7 | list_head) } testLHeadFromOneElementList() { - assertEquals 1 $(list 1 | lhead) + assertEquals 1 $(list 1 | list_head) } testLHeadFromEmptyList() { - assertEquals "" "$(list | lhead)" + assertEquals "" "$(list | list_head)" } . ./shunit2-init.sh diff --git a/test/last_test.sh b/test/last_test.sh index e76a694..e10ec36 100755 --- a/test/last_test.sh +++ b/test/last_test.sh @@ -1,16 +1,16 @@ #! /bin/bash testLastFromList() { - assertEquals 10 $(list {1..10} | last) - assertEquals 7 $(list 5 6 7 | last) + assertEquals 10 $(list {1..10} | list_last) + assertEquals 7 $(list 5 6 7 | list_last) } testLastFromOneElementList() { - assertEquals 1 $(list 1 | last) + assertEquals 1 $(list 1 | list_last) } testLastFromEmptyList() { - assertEquals "" "$(list | last)" + assertEquals "" "$(list | list_last)" } . ./shunit2-init.sh \ No newline at end of file diff --git a/test/map_test.sh b/test/map_test.sh index 5371349..383a8a8 100755 --- a/test/map_test.sh +++ b/test/map_test.sh @@ -1,38 +1,33 @@ #!/bin/bash testMapEmptyList() { - assertEquals "" "$(list | map lambda x . 'echo $(($x + 1))')" + assertEquals "" "$(list | list_map lambda x . 'echo $(($x + 1))')" } testMapEmptyList_ifNoArgumentsInLambda() { - assertEquals "" "$(list | map lambda . 'echo 3')" + assertEquals "" "$(list | list_map lambda . 'echo 3')" } testMapOneElementList() { - assertEquals "3" "$(list 2 | map lambda x . 'echo $(($x + 1))')" + assertEquals "3" "$(list 2 | list_map lambda x . 'echo $(($x + 1))')" } testMapList() { - assertEquals "2 3 4 5 6" "$(list {1..5} | map lambda x . 'echo $(($x + 1))' | unlist)" + assertEquals "2 3 4 5 6" "$(list {1..5} | list_map lambda x . 'echo $(($x + 1))' | unlist)" } testMapList_ifNoArgumentsInLambda() { - assertEquals "9 9 9 9 9" "$(list {1..5} | map lambda . 'echo 9' | unlist)" + assertEquals "9 9 9 9 9" "$(list {1..5} | list_map lambda . 'echo 9' | unlist)" } testMapList_ifManyArgumentsInLambda() { - list {1..5} | map lambda x y . 'echo $(($x + $y))' 2> /dev/null \ + list {1..5} | list_map lambda x y . 'echo $(($x + $y))' 2> /dev/null \ && fail "There should be syntax error, because map is an one argument operation" } testFlatMap() { - assertEquals "1 2 3 2 3 3" "$(list {1..3} | map lambda x . 'seq $x 3' | unlist)" - assertEquals "d e h l l l o o r w" "$(list hello world | map lambda x . 'command fold -w 1 <<< $x' | sort | unlist)" -} - -testMapNoLambdaSyntax() { - assertEquals "1 2 3" "$(list 1 2 3 | map echo | unlist)" - assertEquals "1 is a number 2 is a number 3 is a number" "$(list 1 2 3 | map 'echo $ is a number' | unlist)" + assertEquals "1 2 3 2 3 3" "$(list {1..3} | list_map lambda x . 'seq $x 3' | unlist)" + assertEquals "d e h l l l o o r w" "$(list hello world | list_map lambda x . 'command fold -w 1 <<< $x' | sort | unlist)" } . ./shunit2-init.sh \ No newline at end of file diff --git a/test/maybe_test.sh b/test/maybe_test.sh deleted file mode 100755 index 1003179..0000000 --- a/test/maybe_test.sh +++ /dev/null @@ -1,29 +0,0 @@ -#! /bin/bash - -testMaybe() { - assertEquals '(Just,1)' "$(maybe 1)" - assertEquals '(Just,1)' "$(echo 1 | maybe)" - assertEquals '(Nothing)' "$(maybe '')" - assertEquals '(Nothing)' "$(maybe ' ')" - assertEquals '(Nothing)' "$(maybe ' ' ' ' ' ')" - assertEquals '(Nothing)' "$(echo | maybe)" - assertEquals '(Just,1 2 3)' "$(maybe 1 2 3)" - assertEquals '(Just,1 2 3)' "$(echo 1 2 3 | maybe)" -} - -testMaybemap() { - assertEquals '(Just,3)' "$(echo 1 | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo $(( a + 1 ))')" - assertEquals '(Nothing)' "$(echo | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo $(( a + 1 ))')" - - assertEquals '(Nothing)' "$(echo 1 | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo')" - assertEquals '(Nothing)' "$(echo 1 | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo' | maybemap lambda a . 'echo $(( a + 1 ))')" -} - -testMaybevalue() { - assertEquals 3 "$(echo 1 | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo $(( a + 1 ))' | maybevalue 0)" - assertEquals 0 "$(echo | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo $(( a + 1 ))' | maybevalue 0)" - assertEquals 'a b c' "$(echo | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo $(( a + 1 ))' | maybevalue a b c)" -} - - -. ./shunit2-init.sh diff --git a/test/predicates_test.sh b/test/predicates_test.sh index 79bff20..5e96337 100755 --- a/test/predicates_test.sh +++ b/test/predicates_test.sh @@ -8,16 +8,16 @@ testIsint() { assertEquals '1 2 3 4 5' "$(list 1 a 2 b 3 c 4 d 5 e | filter lambda x . 'isint $x' | unlist )" assertEquals '1 2' "$(list 1 a 2 b 3 c 4 d 5 e | filter lambda x . '($(isint $x) && [[ $x -le 2 ]] && ret true) || ret false ' | unlist )" - assertEquals 'false' $(not isint 1) - assertEquals 'true' $(not isint a) + assertEquals 'false' $(not "isint 1") + assertEquals 'true' $(not "isint a") } testIsempty() { assertEquals 'true' $(isempty "") assertEquals 'false' $(isempty a) - assertEquals 'true' $(not isempty a) - assertEquals 'false' $(not isempty "") + assertEquals 'true' $(not "isempty a") + assertEquals 'false' $(not "isempty \"\"") } testIsfile() { @@ -26,7 +26,7 @@ testIsfile() { assertEquals 'true' $(isfile $f) assertEquals 'false' $(isfile $f.xxx) assertEquals 'false' $(isfile "") - assertEquals 'true' $(not isfile $f.xxx) + assertEquals 'true' $(not "isfile $f.xxx") assertEquals 'false' $(isnonzerofile $f) echo hello world >$f diff --git a/test/prepend_test.sh b/test/prepend_test.sh index 8e9e3a0..491a56e 100755 --- a/test/prepend_test.sh +++ b/test/prepend_test.sh @@ -1,15 +1,15 @@ #! /bin/bash testPrependToEmptyList() { - assertEquals 4 "$(list | prepend 4)" + assertEquals 4 "$(list | list_prepend 4)" } testPrependToOneElementList() { - assertEquals "4 1" "$(list 1 | prepend 4 | unlist)" + assertEquals "4 1" "$(list 1 | list_prepend 4 | unlist)" } testPrependToList() { - assertEquals "4 1 2 3 4 5" "$(list 1 2 3 4 5 | prepend 4 | unlist)" + assertEquals "4 1 2 3 4 5" "$(list 1 2 3 4 5 | list_prepend 4 | unlist)" } . ./shunit2-init.sh \ No newline at end of file diff --git a/test/tail_test.sh b/test/tail_test.sh index b2b19f5..f6c11e6 100755 --- a/test/tail_test.sh +++ b/test/tail_test.sh @@ -1,15 +1,15 @@ #! /bin/bash testLTailFrom10() { - assertEquals "2 3 4 5 6 7 8 9 10" "$(list {1..10} | ltail | unlist)" + assertEquals "2 3 4 5 6 7 8 9 10" "$(list {1..10} | list_tail | unlist)" } testLTailFromOneElementList() { - assertEquals "" "$(list 1 | ltail)" + assertEquals "" "$(list 1 | list_tail)" } testLTailFromEmptyList() { - assertEquals "" "$(list | ltail)" + assertEquals "" "$(list | list_tail)" } . ./shunit2-init.sh diff --git a/test/take_test.sh b/test/take_test.sh index efd48ef..5b0cb49 100755 --- a/test/take_test.sh +++ b/test/take_test.sh @@ -1,23 +1,23 @@ #! /bin/bash testTake9From10() { - assertEquals "1 2 3 4 5 6 7 8 9" "$(list {1..10} | take 9 | unlist)" + assertEquals "1 2 3 4 5 6 7 8 9" "$(list {1..10} | list_take 9 | unlist)" } testTake8From10() { - assertEquals "1 2 3 4 5 6 7 8" "$(list {1..10} | take 8 | unlist)" + assertEquals "1 2 3 4 5 6 7 8" "$(list {1..10} | list_take 8 | unlist)" } testTakeAll() { - assertEquals "1 2 3 4 5 6 7 8 9 10" "$(list {1..10} | take 10 | unlist)" + assertEquals "1 2 3 4 5 6 7 8 9 10" "$(list {1..10} | list_take 10 | unlist)" } testTakeMoreThanAvailable() { - assertEquals "1 2 3 4 5 6 7 8 9 10" "$(list {1..10} | take 15 | unlist)" + assertEquals "1 2 3 4 5 6 7 8 9 10" "$(list {1..10} | list_take 15 | unlist)" } testTakeZero() { - assertEquals "" "$(list {1..10} | take 0 | unlist)" + assertEquals "" "$(list {1..10} | list_take 0 | unlist)" } . ./shunit2-init.sh \ No newline at end of file diff --git a/test/try_test.sh b/test/try_test.sh deleted file mode 100755 index be1aeb3..0000000 --- a/test/try_test.sh +++ /dev/null @@ -1,11 +0,0 @@ -#! /bin/bash - -testTry() { - assertEquals 1 "$(echo 'expr 2 / 2' | try lambda _ . 'ret 0')" - assertEquals 0 "$(echo 'expr 2 / 0' | try lambda _ . 'ret 0')" - assertEquals 2 "$(echo 'expr 2 / 0' | try lambda status . 'ret $status')" - assertEquals 'already up to date' "$(echo 'echo already up to date' | try lambda _ . 'ret error')" - assertEquals 'error exit 1' "$(try λ _ . 'echo "error"; echo exit 1' < <(echo fgit pull) | unlist)" -} - -. ./shunit2-init.sh \ No newline at end of file diff --git a/test/tup_test.sh b/test/tup_test.sh index 25cd8bf..71f1b05 100755 --- a/test/tup_test.sh +++ b/test/tup_test.sh @@ -8,8 +8,8 @@ testTupIfOneElement() { assertEquals '(1)' $(tup 1) assertEquals '(")' $(tup '"') assertEquals "(')" $(tup "'") - assertEquals "(u002c)" $(tup ",") - assertEquals "(u002cu002c)" $(tup ",,") + assertEquals "(,)" $(tup ",") + assertEquals "(,,)" $(tup ",,") assertEquals "(()" $(tup "(") assertEquals "())" $(tup ")") } @@ -20,42 +20,10 @@ testTupHappyPath() { assertEquals '(a b,c d e,f)' "$(tup 'a b' 'c d e' 'f')" } -testTupxHappyPath() { - assertEquals '4' $(tup 4 5 1 4 | tupx 1) - assertEquals '5' $(tup 4 5 1 4 | tupx 2) - assertEquals '1' $(tup 4 5 1 4 | tupx 3) - assertEquals '4' $(tup 4 5 1 4 | tupx 4) - -} - -testTupxIfEmpty() { - assertEquals '' "$(tup '' | tupx 1)" - assertEquals '' "$(tup '' | tupx 5)" -} - testTupxIfZeroIndex() { assertEquals '' "$(tup 1 3 | tupx 0 2>/dev/null)" } -testTupxIfSpecialChars() { - assertEquals ',' "$(tup ',' | tupx 1)" - assertEquals ',,' "$(tup ',,' | tupx 1)" - assertEquals '(' "$(tup '(' | tupx 1)" - assertEquals ')' "$(tup ')' | tupx 1)" - assertEquals '()' "$(tup '()' | tupx 1)" - assertEquals '(' "$(tup '(' ')' | tupx 1)" - assertEquals '(' "$(tup '(' '(' | tupx 1)" - assertEquals ')' "$(tup ')' ')' | tupx 1)" - assertEquals ',' "$(tup 'u002c' | tupx 1)" - assertEquals ',,' "$(tup 'u002cu002c' | tupx 1)" -} - -testTupxRange() { - assertEquals '4 5' "$(tup 4 5 1 4 | tupx 1-2 | unlist)" - assertEquals '4 4' "$(tup 4 5 1 4 | tupx 1,4 | unlist)" - assertEquals '4 5 4' "$(tup 4 5 1 4 | tupx 1,2,4 | unlist)" -} - testTupl() { assertEquals '4' "$(tup 4 5 | tupl)" assertEquals '4' "$(tup 4 5 6 | tupl)" @@ -69,16 +37,4 @@ testTupr() { assertEquals '5' "$(tup 5 | tupr)" } -testNTup() { - assertEquals '(KFlRbz0sWWdvPSkK,Ywo=)' "$(ntup $(ntup a b) c)" - assertEquals '(YQo=,Ygo=)' "$(ntupl '(KFlRbz0sWWdvPSkK,Ywo=)')" - assertEquals 'a' "$(ntupl '(YQo=,Ygo=)')" - assertEquals 'b' "$(ntupr '(YQo=,Ygo=)')" - assertEquals 'c' "$(ntupr '(KFlRbz0sWWdvPSkK,Ywo=)')" - assertEquals 'a' "$(ntup $(ntup a b) c | ntupx 1 | ntupx 1)" - assertEquals 'b' "$(ntup $(ntup a b) c | ntupx 1 | ntupx 2)" - assertEquals 'c' "$(ntup $(ntup a b) c | ntupx 2)" - assertEquals 'a b' "$(ntup $(ntup a b) c | ntupx 1 | ntupx 1,2 | unlist)" -} - . ./shunit2-init.sh