# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: base.eval base_ "Fonctions de base: évaluation d'expressions"
require: base.str base.arr

################################################################################
# Chaines

function: base_evals "Appliquer à une chaine de caractères une suite de traitements, e.g:
~~~
base_evals var deref +suffix
~~~
est équivalent à
~~~
echo \"\${var}suffix\"
~~~

En commençant avec la valeur initiale \$1, les arguments \$2..* sont des
opérations à appliquer dans l'ordre.

Les opérations suivantes considèrent que la valeur courante est un nom de
variable:
~~~
:- := :? :+ deref dcount
~~~

Toutes les autres opérations travaillent directement avec la valeur
courante. Les opérations suivantes appliquent une transformation:
~~~
# % / : ^ , +# -# +% -% + - mid repl
~~~
IMPORTANT: aucune de ces fonctions ne met en échappement les valeur des
patterns. Ainsi, si un pattern contient des caractères interdits comme \\ ou \$,
il faut d'abord le traiter avec _qval()

Les opérations suivantes font un test sur la valeur et retournent immédiatement:
~~~
= == != < > -eq -ne -lt -le -gt -ge -n -z
~~~

La syntaxe des opérateurs standards de bash est reprise autant que possible, i.e
si on a l'habitude d'écrire ${varOP} en bash, alors la syntaxe à utiliser à
priori est 'base_evals var OP' ou 'base_evals var deref OP' suivant les
opérateurs.

Autres opérateurs:
~~~
deref          indirection
dcount         nombre d'éléments du tableau
+#STR          ajouter un préfixe
-#STR          supprimer un préfixe
+%STR ou +STR  ajouter un suffixe
-%STR ou -STR  supprimer un suffixe
mid RANGE      traiter la chaine avec base_strmid()
repl FROM TO   traiter la chaine avec base_strrepl()
~~~

Tout autre opérateur est traité comme un appel à une fonction qui prend un seul
argument, la valeur courante, et qui affiche le résultat."
function base_evals() {
    local -a es__tmp
    local es__value="$1"; shift
    while [ $# -gt 0 ]; do
        case "$1" in
        # l'argument est le nom de la variable
        :-*|:=*|:\?*|:+*) eval 'es__value="${'"${es__value}$1"'}"';;
        d|deref) es__value="${!es__value}";;
        dc|dcount|ds|dsize)
            es__value="${es__value}[@]"
            es__tmp=("${!es__value}")
            es__value="${#es__tmp[@]}"
            ;;
        # l'argument est la valeur de la variable
        \#*|%*|/*|:*|^*|,*) eval 'es__value="${es__value'"$1"'}"';;
        l|length) es__value="${#es__value}";;
        =|==|!=|\<|\>|-eq|-ne|-lt|-le|-gt|-ge)
            es__tmp=(\[ "$es__value" "$@" ]); "${es__tmp[@]}"; return $?;;
        -n|-z) es__tmp=(\[ "$1" "$es__value" ]); "${es__tmp[@]}"; return $?;;
        +#*) eval 'es__value="'"${1#+#}"'$es__value"';;
        -#*) eval 'es__value="${es__value'"${1#-}"'}"';;
        +%*) eval 'es__value="$es__value"'"${1#+%}";;
        +*) eval 'es__value="$es__value"'"${1#+}";;
        -%*) eval 'es__value="${es__value'"${1#-}"'}"';;
        -*) eval 'es__value="${es__value%'"${1#-}"'}"';;
        mid|strmid|base_strmid) eval 'es__value="$(base_strmid "$2" "$es__value")"'; shift;;
        repl|strrepl|base_strrepl) eval 'es__value="$(base_strrepl "$2" "$3" "$es__value")"'; shift; shift;;
        *) es__value="$("$1" "$es__value")";;
        esac
        shift
    done
    echo "$es__value"
}

function: base_setxs "équivalent à setx \$1 evals \$2..@"
function base_setxs() {
    local -a ss__args
    if [ "$1" == -a ]; then ss__args=(-a); shift; fi
    local ss__var="$1"; shift
    if [[ "$ss__var" == *=* ]]; then
        set -- "${ss__var#*=}" "$@"
        ss__var="${ss__var%%=*}"
    fi
    ss__args=("${ss__args[@]}" "$ss__var")
    setx "${ss__args[@]}" base_evals "$@"
}

function: base_cmds "lancer une commande avec comme argument le résultat de evals

Par exemple, les deux commandes suivantes sont équivalentes:
~~~
base_cmds CMD ARGS... // EVALARGS
CMD ARGS... \"\$(evals EVALARGS)\"
~~~"
function base_cmds() {
    local cs__arg
    local -a cs__cmd
    while [ $# -gt 0 ]; do
        cs__arg="$1"; shift
        [ "$cs__arg" == // ] && break
        cs__cmd=("${cs__cmd[@]}" "$cs__arg")
    done
    "${cs__cmd[@]}" "$(base_evals "$@")"
}

function: base_evalm "construire une chaine en mixant chaines statiques et évaluations de commandes

Par exemple, les deux commandes suivantes sont équivalentes:
~~~
evalm //\"string\" cmd args // cmd args //\"string\"
echo \"string\$(cmd args)\$(cmd args)string\"
~~~"
function base_evalm() {
    local em__val em__arg
    local -a em__cmd
    while [ $# -gt 0 ]; do
        em__arg="$1"
        if [ "${em__arg#//}" != "$em__arg" ]; then
            em__val="$em__val${em__arg#//}"
            shift
            continue
        fi
        em__cmd=()
        while [ $# -gt 0 ]; do
            em__arg="$1"
            [ "${em__arg#//}" != "$em__arg" ] && break
            shift
            if [ "${em__arg%//}" != "$em__arg" ]; then
                local em__tmp="${em__arg%//}"
                if [ -z "${em__tmp//\\/}" ]; then
                    em__arg="${em__arg#\\}"
                    em__cmd=("${em__cmd[@]}" "$em__arg")
                    continue
                fi
            fi
            em__cmd=("${em__cmd[@]}" "$em__arg")
        done
        [ ${#em__cmd[*]} -gt 0 ] && em__val="$em__val$("${em__cmd[@]}")"
    done
    echo "$em__val"
}

function: base_setxm "équivalent à setx \$1 evalm \$2..@"
function base_setxm() {
    local -a sm__args
    if [ "$1" == -a ]; then sm__args=(-a); shift; fi
    local sm__var="$1"; shift
    if [[ "$sm__var" == *=* ]]; then
        set -- "${sm__var#*=}" "$@"
        sm__var="${sm__var%%=*}"
    fi
    sm__args=("${sm__args[@]}" "$sm__var")
    setx "${sm__args[@]}" base_evalm "$@"
}

function: base_cmdm "lancer une commande avec comme argument le résultat de evalm

Par exemple, les deux commandes suivantes sont équivalentes:
~~~
base_cmdm CMD ARGS... // EVALARGS
CMD ARGS... \"\$(evalm EVALARGS)\"
~~~"
function base_cmdm() {
    local cm__arg
    local -a cm__cmd
    while [ $# -gt 0 ]; do
        cm__arg="$1"; shift
        [ "$cm__arg" == // ] && break
        cm__cmd=("${cm__cmd[@]}" "$cm__arg")
    done
    "${cm__cmd[@]}" "$(base_evalm "$@")"
}

################################################################################
# Nombres

function: base_evali "Evaluer une expression numérique"
function base_evali() {
    echo "$(($*))"
}

################################################################################
# Tableaux

################################################################################
# Composition

function: base_evalc "Implémenter une syntaxe lisible et naturelle permettant d'enchainer des traitements sur une valeur.

Par exemple, la commande
~~~
evalc cmd1... // cmd2... // cmd3...
~~~
est équivalente à la commande
~~~
cmd3... \"\$(cmd2... \"\$(cmd1...)\")\"
~~~"
function base_evalc() {
    local ec__arg ec__cmd ec__finalcmd

    while [ $# -gt 0 ]; do
        ec__arg="$1"; shift
        if [ "$ec__arg" == // ]; then
            if [ ${#ec__cmd} -gt 0 ]; then
                if [ ${#ec__finalcmd} -eq 0 ]; then ec__finalcmd="$ec__cmd"
                else ec__finalcmd="$ec__cmd \$($ec__finalcmd)"
                fi
            fi
            ec__cmd=
            continue
        elif [ "${ec__arg%//}" != "$ec__arg" ]; then
            local tmp="${ec__arg%//}"
            [ -z "${tmp//\\/}" ] && ec__arg="${ec__arg#\\}"
        fi
        ec__cmd="$ec__cmd \"$(_qval "$ec__arg")\""
    done
    if [ ${#ec__cmd} -gt 0 ]; then
        if [ ${#ec__finalcmd} -eq 0 ]; then ec__finalcmd="$ec__cmd"
        else ec__finalcmd="$ec__cmd \$($ec__finalcmd)"
        fi
    fi
    eval "$ec__finalcmd"
}

function: base_setxc "équivalent à setx \$1 evalc \$2..@"
function base_setxc() {
    local -a sx__args
    if [ "$1" == -a ]; then sx__args=(-a); shift; fi
    local sx__var="$1"; shift
    if [[ "$sx__var" == *=* ]]; then
        set -- "${sx__var#*=}" "$@"
        sx__var="${sx__var%%=*}"
    fi
    sx__args=("${sx__args[@]}" "$sx__var")
    setx "${sx__args[@]}" base_evalc "$@"
}

################################################################################
# Chainage

function: base_evalp "Implémenter une syntaxe alternative permettant d'enchainer des traitements sur un flux de données.

Par exemple, la commande
~~~
evalp cmd1... // cmd2... // cmd3...
~~~
affiche le résultat de la commande
~~~
cmd1... | cmd2... | cmd3...
~~~

Typiquement, cette fonction permet de faciliter la *construction* d'un
enchainement de commandes par programme, ou de faciliter l'utilisation de la
fonction setx() pour récupérer le résultat d'un enchainement. Dans les autres
cas, il est plus simple et naturel d'écrire les enchainements avec la syntaxe de
bash."
function base_evalp() {
    local ep__arg ep__cmd

    while [ $# -gt 0 ]; do
        ep__arg="$1"; shift
        if [ "$ep__arg" == // ]; then
            ep__cmd="$ep__cmd |"
            continue
        elif [ "${ep__arg%//}" != "$ep__arg" ]; then
            local ep__tmp="${ep__arg%//}"
            if [ -z "${ep__tmp//\\/}" ]; then
                ep__arg="${ep__arg#\\}"
            fi
        fi
        ep__cmd="${ep__cmd:+$ep__cmd }\"$(_qval "$ep__arg")\""
    done
    eval "$ep__cmd"
}

function: base_setxp "équivalent à setx \$1 evalp \$2..@"
function base_setxp() {
    local -a sp__args
    if [ "$1" == -a ]; then sp__args=(-a); shift; fi
    local sp__var="$1"; shift
    if [[ "$sp__var" == *=* ]]; then
        set -- "${sp__var#*=}" "$@"
        sp__var="${sp__var%%=*}"
    fi
    sp__args=("${sp__args[@]}" "$sp__var")
    setx "${sp__args[@]}" base_evalp "$@"
}

function: base_cmdp "lancer une commande avec comme argument le résultat de evalp

Par exemple, les deux commandes suivantes sont équivalentes:
~~~
base_cmdp CMD ARGS... // EVALARGS
CMD ARGS... \"\$(evalp EVALARGS)\"
~~~"
function base_cmdp() {
    local cp__arg
    local -a cp__cmd
    while [ $# -gt 0 ]; do
        cp__arg="$1"; shift
        [ "$cp__arg" == // ] && break
        cp__cmd=("${cp__cmd[@]}" "$cp__arg")
    done
    "${cp__cmd[@]}" "$(base_evalp "$@")"
}

################################################################################
# Générique

function: base_evalx ""
function base_evalx() {
    :
}

function: base_setxx "équivalent à setx \$1 evalx \$2..@"
function base_setxx() {
    local -a sx__args
    if [ "$1" == -a ]; then sx__args=(-a); shift; fi
    local sx__var="$1"; shift
    if [[ "$sx__var" == *=* ]]; then
        set -- "${sx__var#*=}" "$@"
        sx__var="${sx__var%%=*}"
    fi
    sx__args=("${sx__args[@]}" "$sx__var")
    setx "${sx__args[@]}" base_evalx "$@"
}

function: base_cmdx "lancer une commande avec comme argument le résultat de evalx

Par exemple, les deux commandes suivantes sont équivalentes:
~~~
base_cmdx CMD ARGS... // EVALARGS
CMD ARGS... \"\$(evalx EVALARGS)\"
~~~"
function base_cmdx() {
    local cx__arg
    local -a cx__cmd
    while [ $# -gt 0 ]; do
        cx__arg="$1"; shift
        [ "$cx__arg" == // ] && break
        cx__cmd=("${cx__cmd[@]}" "$cx__arg")
    done
    "${cx__cmd[@]}" "$(base_evalx "$@")"
}

function: base_cmdsplitf "\
Cette fonction doit être appelée avec N arguments (avec N>1). Elle analyse et
découpe l'argument \$N comme avec une ligne de commande du shell. Ensuite, elle
appelle la fonction \$1 avec les arguments de \$2 à \${N-1}, suivi des arguments
obtenus lors de l'analyse de l'argument \$N. Par exemple, la commande suivante:
~~~
strsplitf cmd arg1 \"long arg2\" \"arg3 'long arg4'\"
~~~
est équivalente à:
~~~
cmd arg1 \"long arg2\" arg3 \"long arg4\"
~~~

Retourner le code 127 si la fonction à appeler n'est pas spécifiée. Retourner le
code 126 si une erreur s'est produite lors de l'analyse de l'argument \$N"
function base_cmdsplitf() {
    [ $# -gt 0 ] || return 127
    local func count
    func="$1"; shift
    count=$#
    if [ $count -gt 0 ]; then
        eval 'set -- "${@:1:$(($count-1))}" '"${!count}" || return 126
    fi
    "$func" "$@"
}

################################################################################
# Tests

function: testx "Faire un test unaire avec la commande [ sur une valeur calculée avec evalx.

Utiliser la syntaxe 'testx op cmds...' e.g.
~~~
testx -z cmd1 // cmd2
~~~"
function testx() {
    local t__op="$1"; shift
    local t__val="$(evalx "$@")"
    [ $t__op "$t__val" ]
}

function: test2x "Faire une test binaire avec la commande [ entre une valeur spécifiée et une valeur calculée avec evalx.

Utiliser la syntaxe 'test2x value op cmds...' e.g.
~~~
test2x value == cmd1 // cmd2
~~~"
function test2x() {
    local t__val1="$1"; shift
    local t__op="$1"; shift
    local t__val2="$(evalx "$@")"
    [ "$t__val1" $t__op "$t__val2" ]
}

function: testrx "Faire une test binaire avec la commande [[ entre une valeur spécifiée et une valeur calculée avec evalx.

Utiliser la syntaxe 'testrx value op cmds...' e.g.
~~~
testrx value == cmd1 // cmd2
~~~"
function testrx() {
    local t__val1="$1"; shift
    local t__op="$1"; shift
    local t__val2="$(evalx "$@")"
    eval '[[ "$t__val1" '"$t__op"' "$t__val2" ]]'
}

function: testp "Faire un test unaire avec la commande [ sur une valeur calculée avec evalp.

Utiliser la syntaxe 'testp op cmds...' e.g.
~~~
testp -z cmd1 // cmd2
~~~"
function testp() {
    local t__op="$1"; shift
    local t__val="$(evalp "$@")"
    [ $t__op "$t__val" ]
}

function: test2p "Faire une test binaire avec la commande [ entre une valeur spécifiée et une valeur calculée avec evalp.

Utiliser la syntaxe 'test2p value op cmds...' e.g.
~~~
test2p value == cmd1 // cmd2
~~~"
function test2p() {
    local t__val1="$1"; shift
    local t__op="$1"; shift
    local t__val2="$(evalp "$@")"
    [ "$t__val1" $t__op "$t__val2" ]
}

function: testrp "Faire une test binaire avec la commande [[ entre une valeur spécifiée et une valeur calculée avec evalp.

Utiliser la syntaxe 'testrp value op cmds...' e.g.
~~~
testrp value == cmd1 // cmd2
~~~"
function testrp() {
    local t__val1="$1"; shift
    local t__op="$1"; shift
    local t__val2="$(evalp "$@")"
    eval '[[ "$t__val1" '"$t__op"' "$t__val2" ]]'
}