Kakscript: boolean / string / error equality testing

Here’s a way to check boolean, string and error equality in pure kakscript. These techniques are used, for example, in my Forth implementation in pure kakscript.

Disclaimers

  • def's are prefixed with mypkg- (sometimes implied) so as not to pollute the namespace
  • I have written this post manually; examples may contain typos/errors (though the showcased techniques are proven in the above-mentioned Forth implementation and other pieces of code)
  • using such techniques is, sadly, not really a copy-paste job at this time (though the Forth implementation is UNLINCESE’d, so one can copy-paste from there).

Without further ado:

Testing booleans

… reduces to testing expansions for string equality to literal true / false.

For “well-behaved” strings like “true” / “false”, this is doable via command existence/in-existence (otherwise, I do not have a solution).

We can define multiple str-literal-STR-STR commands, with zero args (yes, zero; the argument needs to be incorporated in a double-quoted string, see below). These checkers will succeed IF AND ONLY IF called as "literal-STR-%expansion{}" when the expansion (%arg, %opt, or even %sh) is EXACTLY, literally STR; and conversely fail (due to non-existence) otherwise.

# Use these by calling the basename with an appended expansion, inside double quotes:
#   "mypkg-literal-true-%arg{1}"
#   # ... in a 'try/catch', inside a `def`.
# or
#   "mypkg-literal-false-%opt{some_bool}"
#   # ... in a 'try/catch', anywhere

The call will either be a nop (and thus succeed); or fail due to inexistence. Here are the auxiliary defs and examples:

def mypkg-str-literal-true-true   %{}
def mypkg-str-literal-false-false %{}

decl bool bb  # test variable

# a testing command
def chk-bool -params 1 %{
  try %{
    "mypkg-str-literal-true-%arg{1}"
    echo -debug checked true
  } catch %{
    "mypkg-str-literal-false-%arg{1}"
    echo -debug checked false
  } catch %{
    echo -debug -quoting kakoune \
      'bool parsing error in kak:' %arg{1} 'should not be assignable to a bool'
  }
} -override -hidden

# use chk-bool
set global bb true
chk-bool %opt{bb}
set global bb false
chk-bool %opt{bb}

try %{
  set global bb unparsable-bool  # fails, next statement shouldn't execute
  chk-bool %opt{bb}
} catch %{ echo -debug Expected error: %val{error} }

Compare strings

We can generalize this idea to any other “well-behaved” string constants (anything that can be a part of a command name; inclusing the empty string), using a def that in turn… defines a zero-arg checker:

  def mypkg-str-def -params 1 -docstring %{
    This defines a checker command:
      mypkg-str-literal-%arg{1}-%arg{1}
    Call with any expansion (reg, opt) as:
      try %{ "%mypkg-str-literal-%expansion{}-%expansion"
        # IF-EQUAL   commands
      } catch %{
        # IF-UNEQUAL commands
      }
    The checker will fail if arg != initial arg
  } %{
    def "mypkg-str-literal-%arg{1}-%arg{1}" %{} -override -hidden
  }
  mypkg-str-def 42  # tested via `mypkg-str-literal-42-%expansion{}`
  mypkg-str-def ''  # tested via `mypkg-str-literal--%expansion{}`

… and these can be checked via

set global mypgk_s 42
try %{ "mypkg-str-literal-42-%opt{mypgk_s}"
  echo -debug 42: lets go
} catch %{
  echo -debug mypkg: not 42
}

set global mypgk_s 43
try %{ "mypkg-str-literal-42-%opt{mypgk_s}"
  echo -debug 42: lets go
} catch %{
  echo -debug mypkg: not 42
}

Catch specific errors

Let’s use this to define and catch specific exceptions, while ignoring others.

It’s a bit more complicated because any try's erase the current %val{error}. Therefore, the current error needs to be saved first — in a str (errors are simple strings), or in a fail %value{error} str-list trampoline. Using a trampoline (executed as %opt{trampoline}) is simpler, because it can be either an empty str-list (which is a nop), or the fail action.

def mypkg-err-def -docstring %{
  Define a string equality test, to be used next as an exception checker
} %{
  define -override -hidden mypkg-err-def -params 1 %{
    def "mypkg-str-literal-%arg{1}-%arg{1}" %{} -override -hidden
  }
}

decl -hidden str-list mypkg_err_slist         # variable holding current error
define -override -hidden mypkg-err-eq -params 1 -docstring %{
  Defines an error checker
} %{
  # save current error; would be overwritten by subsequent try's
  set global mypkg_err_slist fail %val{error} # save in revolver
  try %{
    "mypkg-str-literal-%arg{1}-%val{error}"   # test str=
  } catch %{
    %opt{mypkg_err_slist}                     # unequal; fire revolver
  }
}
2 Likes

A quick note: I think this mostly supersedes @andreyorst 's if-then-else technique, because

def some-function-using-if_then_else -params 3 %{
try %{
  "mypkg-str-literal-true-%arg{1}"
  # IF PART
  echo -debug %arg{2}
} catch %{
  # ELSE PART
  echo -debug %arg{3}
}
}

can evaluate its arguments. If- and else- strings, OTOH, if passed to an if function, would be evaluated in that function’s context, so %arg's would be nonsense — and access to the original args would need to be arranged via saving them in an option.

FYI I’m no longer using that method, and have migrated to pure kakscript one:

https://github.com/andreyorst/dotfiles/blob/master/.config/kak/commands.kak#L244-L248

That’s the one I was referring. The problem is that such versions of-if, when, and -unless take string arguments. Those strings are expanded (or not expanded) inside another function (those conditionals), not in the context of the original callers. Most expansions are context-free and thus work, but not %arg; if these string arguments try to use a %arg expansion, it will be nonsense. So, for example,

def myfunc -params 1 %{
  functional-if true %{
    echo -debug %arg{1}  # improper expansion passed as a string
  } %{ echo -debug ELSE-CLAUSE }  # no problem here
}

myfunc true
myfunc false

The basic problem is that you can’t produce a closure (to pass on to those conditionals).

Edit: sorry for the post edits; I always end up trying to clarify

1 Like

yes, you’re right. This didn’t occur to me as I write very few functions that require alteration but don’t use sh already. This if is primary for conditional configuration.