I am coding a generic LIFO (Last In First Out) in awk
where a LIFO is an indexed array with index 1 corresponding to the first-in position and index length(LIFO)
corresponding to the last-in position:
# push value `val` in LIFO `lifo`
function push(lifo, val) {
lifo[length(lifo)+1]=val
}
# pop and return last input value from LIFO `lifo`
function pop(lifo, val) {
val=lifo[length(lifo)]
delete lifo[length(lifo)]
return val
}
My issue is with push
that I cannot use to initialize a not yet declared LIFO. If I call:
push(foo, 42)
and foo
has not been initialized, it is passed by value as a scalar and GNU Awk 5.3.0 raises an error:
awk: bug.awk:3: fatal: attempt to use scalar parameter `lifo' as an array
Of course, I could test before calling push
:
if(isarray(foo)) {
push(foo, 42)
} else {
foo[1]=42
}
And I could add a safety test to the function:
function push(lifo, val) {
if(isarray(lifo)) {
lifo[length(lifo)+1]=val
} else {
debug("ERROR: push in unitialized LIFO")
exit(1)
}
}
It is not very convenient. I'd prefer a function that handles all cases. Is there a way to code (or call) the push
function such that it supports uninitialized actual parameters?
It's not that foo
is passed as a scalar, it's passed as an uninitialized variable so when the function is called no determination of scalar vs array or anything else has been made. The problem happens inside the function where length()
sets the type of an uninitialized variable like lifo
to be a scalar string (to provide POSIX-compatible functionality* by default) so you need to tell awk that lifo
is an array before calling length()
by doing some operation on it that only applies to arrays, e.g. use in
:
function push(lifo, val, i) {
for (i in lifo) break;
lifo[length(lifo)+1]=val
}
* Note that length(array)
is non-POSIX behavior. Per POSIX length()
works on strings, not arrays, hence any awk that supports length(array)
treats length()s
argument as a scalar string by default and once awk has decided that a variable is a scalar or array it remains that.
You could alternatively do:
function push(lifo, val, i, lgth) {
for (i in lifo) ++lgth;
lifo[lgth+1]=val
}
or:
function push(lifo, val, lgth) {
for (lgth=1; lgth in lifo; lgth++);
lifo[lgth]=val
}
either of which is portable to all awks or, more efficiently and also portable to all awks (and so it's what I'd do, FWIW), just store the size of the stack in an unused array location such as lifo[0]
:
# push value `val` in LIFO `lifo`
function push(lifo, val) {
lifo[++lifo[0]]=val
}
# pop and return last input value from LIFO `lifo`
function pop(lifo, val) {
val=lifo[lifo[0]]
delete lifo[lifo[0]--]
return val
}
By the way, use of isarray(lifo)
would also make your script non-portable as POSIX has no isarray()
function, that's a gawk extension.