Kakscript has loop, catch this, head/tail/shift, int>=0, streq ... etc

Note: (entire demo downloadable in one script).

Friends, let’s talk about scripting in kakscript, not other languages. Unexpectedly, a lot of things are possible — a lot more than you might think at first. I’ve been using these tricks in sel-editor and the up-and-coming kak-bundle-plug.

I’m curious what experienced users (and @mawww ) think as far as safety and performance — are there any problems with this approach? There was a discussion about hook re-entrancy a while ago.

Without further ado, let’s start by looping (either a specific number of times, or ad-infinitum stopping on errors):

def loop-2 -params .. %{
  %arg{@}
  %arg{@}
} -override
def loop-4 -params .. %{
  loop-2 loop-2 %arg{@}
} -override
def loop-16  -params .. %{ loop-4  loop-4  %arg{@} } -override
def loop-256 -params .. %{ loop-16 loop-16 %arg{@} } -override
def loop-65536 -params .. %{ loop-256 loop-256 %arg{@} } -override
def loop-inf -params .. -docstring %{
  Loop many times; use fail to exit
  DO NOT CALL with code that never fails
} %{
  loop-65536 loop-65536 %arg{@}
} -override

loop-4 echo -debug Hello

Yes, kak executes str-lists; you don’t even need eval to execute %arg{@}, %reg{x} or %opt{list}. An empty list executes as a nop. One wrinkle:

set global empty  # empty str-list, no elements
reg e             # not empty; single '' element

Loop with counter

We can use the %arg{N} expansion to test for positive int’s (note: %arg{0} fails, but by prefixing with 1, you can test >= 0). Break out of loop-inf by throwing:

def int-gt-0 -params 1 -docstring %{
  Fail unless %arg{1} > 0
} %{
  eval "nop -- %%arg{%arg{1}}"
} -override
def int-ge-0 -params 1 %{
  eval "nop -- %%arg{1%arg{1}}"
} -override


decl int cnt
try %{
  set global cnt 5
  loop-inf eval %{
    int-ge-0 %opt{cnt}  # stop
    echo -debug Going down in %opt{cnt}
    set -remove global cnt 1
  }
}

List head / tail, shift

set -remove does so from a list’s head, so:

decl str-list args
decl str arg
def list-shift -params .. %{
  set         global args %arg{@}
  set -remove global args %arg{1}
} -override
def list-head -params .. %{
  set global args %arg{@}
  set global arg %arg{1}
} -override

list-head  1 2 3; echo -debug %opt{arg}
list-shift 1 2 3; echo -debug %opt{args}

Process list 1 by 1; list-empty; streq

We can check list lengths by defining a custom function with a specific -params. String equality: using shift above.

def list-len-eq0 -params 0   nop -override
def list-len-gt0 -params 1.. nop -override

decl str-list str_test
def streq -params .. %{
  set         global str_test %arg{1}
  set -remove global str_test %arg{2}
  list-len-eq0 %opt{str_test}
} -override

def status-ok -params .. %{
  decl str-list status_slist
  set global status_slist %arg{@}
  try %{
  loop-inf eval -- %{
    list-len-gt0 %opt{status_slist}
    list-head %opt{status_slist}
    try %{
      streq %opt{arg} *status*
      echo -debug ok
    } catch %{
      echo -debug -- %opt{arg}
    }
    list-shift %opt{status_slist}
    set global status_slist %opt{args}
  }
  }
} -override

status-ok Status: *status* for now

Error handling

You may have noticed that we stop looping by throwing errors. However, what if the looped-over code throws errors itself? Well, we can check for specific errors by shifting %val{error} itself out of a list, and then executing that list:

decl str-list test_err_slist
def chk-err -params .. -docstring %{
  Check for specific error by shifting
} %{
  set         global test_err_slist %arg{@}
  set -remove global test_err_slist %val{error}
  %opt{test_err_slist}
} -override

# custrom error
def myerr -params .. %{ fail %val{error} } -override

try %{
  echo -debug try1; fail myerr  # will be ignored
} catch %{ chk-err myerr }
try %{
  echo -debug try2; fail 'some error'  # will fail
} catch %{ chk-err myerr }
6 Likes