Branching on a boolean option -- without calling the shell

In Kakoune scripts, branching on the value of a boolean option, myvar, may be done by calling the shell inside a try ... catch construction:

try %{
    evaluate-commands %sh{
        if [ "$kak_opt_myvar" = false ]; then
            printf %s\\n fail
        fi
    }
    commands_1
} \
catch %{
    commands_2
}

which looks bloated and contorted to me. (One could use a wrapper to make the code look simpler, but then the wrapper needs to be defined elsewhere.)

Recently I have been using a trick to avoid calling the shell and inflating the code. The trick is to change the type of the myvar option to str instead of boolean, setting myvar to nop when I want it to be true and to nay when I want it to be false.

Now the branching looks like this:

try %{
    eval %opt(myvar)
    commands_1
} \
catch %{
    commands_2
}

This is Kakoune’s eval, not the shell’s. When myvar is “true”, eval %opt(myvar) goes through and we execute commands_1. When myvar is “false”, eval %opt(myvar) fails (because there is no such command as nay) and we execute commands_2.

But perhaps most of you, professional coders, are already using this sort of hack? In any case, I like the look of the resulting code. You are free to decide whether this is more clever than stupid or vice-versa :slight_smile:!

3 Likes

No, at leas I’m not thought about it! Nice trick!

I was using this custom command:

require-module kak
add-highlighter shared/kakrc/code/if_else regex \b(if|else)\b 0:keyword

define-command -docstring "if <condition> <expression> [else [if <condition>] <expression>]: if statement that accepts shell-valid condition string" \
if -params 2.. %{ evaluate-commands %sh{
    while [ true ]; do
        condition="[ $1 ]"
        if [ -n "$3" ] && [ "$3" != "else" ]; then
            printf "%s\n" "fail %{if: unknown operator '$3'}"
        elif [ $# -eq 3 ]; then
            printf "%s\n" "fail %{if: wrong argument count}"
        elif eval $condition; then
            [ -n "${2##*&*}" ] && arg="$2" || arg="$(printf '%s' "$2" | sed 's/&/&&/g')"
            printf "%s\n" "evaluate-commands %& $arg &"
        elif [ $# -eq 4 ]; then
            [ -n "${4##*&*}" ] && arg="$4" || arg="$(printf '%s' "$4" | sed 's/&/&&/g')"
            printf "%s\n" "evaluate-commands %& $arg &"
        elif [ $# -gt 4 ]; then
            if [ "$4" = "if" ]; then
                shift 4
                continue
            else
                printf "%s\n" "fail %{if: wrong argument count}"
            fi
        fi
        exit
    done
}}

That allows me to chain if else ifelse blocks as in C-like languages, but I mostly use it for single if else, like this, to branch my configuration depending on where I run Kakoune:

plug "andreyorst/base16-gruvbox.kak" domain gitlab.com theme %{
    if %[ -n "${PATH##*termux*}" ] %{
        colorscheme base16-gruvbox-dark-soft
    } else %{
        colorscheme base16-gruvbox-dark-hard
    }
}

The problem is that I mostly want to use shell conditionals as those are far more versatile compared to Kakoune ones, but your example can be used to make some code that uses try catch approach more appealing. I wonder if this can be turned into function, as in my example

I forgot to say that a similar trick can be used to define a procedure (= command) with two variants. Instead of declaring two different commands, declare a single command along these lines:

define-command -params 1 my-procedure %{
    try %{
       eval %arg(1)
       commands_1
    } \
    catch %{
        commands_2
    }
    shared_commands ...
}

Calling the procedure with either nop or nay as an argument value will execute one variant or the other.

One thing that I don’t get with your trick is how do you set the option to the desired value, don’t you need a branch at that point? Let’s say, how would you check if your current selection is 5 characters long?

Not necessarily. Setting the option to “true” or “false” (nop or nay) may be done in a separate command that you may call during your editing session (e.g., “do you want the script to use feature X [yes|no]?”). The option-setting command may also be called by a hook that fires when opening a buffer of a specific filetype.

Even when setting the option to “true” or “false” requires a branch, this branch (A) may occur earlier in the script, in a part separate from the later branch or command (B). For example, you may set the option to “nop” or “nay” depending on the result of an <a-k>... test done on the current selection. Then you may need to call other commands before finally calling (B) with the value of the previously set option.

Of course, my trick is superflous if you just need to do some test and execute some action immediately, depending on the result of the test. The trick is useful only when option-setting is done separately from the final branch – and especially useful if several procedures depend on the previously set option at different points in time.

Nice trick, hadn’t thought of this!

You can allow the variable to have the value true and false by defining true:

def true nop

Now eval true does not fail, as desired.

2 Likes

Nice! I’ve done something similar with setting option values as commands and running eval on the option.

With how many posts of this sort that have popped up, I’m becoming more and more convinced that conditional control flow should be properly included in kakscript. Though, I guess the percentage of kak files that use things like this is still pretty small.

That or, once mrsh is ready, have a compile option to integrate the shell into Kakoune itself.