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 }