ksh93 / ksh

ksh 93u+m: KornShell lives! | Latest release: https://github.com/ksh93/ksh/releases
Eclipse Public License 2.0
187 stars 31 forks source link

nameref fails with nested associative arrays #594

Open elmehdou opened 1 year ago

elmehdou commented 1 year ago

Setting a nested associative arrays in one go fails on references:

function func_a1 {
    typeset -n ref=$1
    ref=(
        [key1]=(
            [key2]=val2
        )
    )
}

function func_a2 {
    typeset -n ref=$1
    typeset -A ref
    typeset -A ref[key1]=(
        [key2]=val2
    )
}

function func_b {
    typeset map1
    func_a1 map1
    echo "Function a1: $(typeset -p map1)"

    typeset map2
    func_a2 map2
    echo "Function a2: $(typeset -p map2)" 
}

func_b
hyenias commented 1 year ago

Your local scoped map1 variable via func_b function using func_a1 function ends up in the default global namespace as follows.

$ typeset -p map1 map1[0]
typeset -a map1=(([key2]=val2) )
typeset -A map1[0]=([key2]=val2)

It is difficult at this time for ksh to arrive at your desired nested or multidimensional array structure by simply parsing your one liner nested compound assignment. ksh's default assignment for =() is a compound variable [-C] not an indexed array [-a] nor associative array [-A]. ksh's current ambiguity in syntax as well as its current parsing methods has ended up in your map1 variable being an indexed array of 1 element containing an associative array of one element.

In this case as you have done in func_a2 function the multidimensional array structure needs to be defined before use so that ksh does not implicitly determine things. See below for a small adjustment to func_a1 defining the top level of your desired multidimensional array to be an associate array and not the default of a compound variable.

function func_a1 {
    typeset -n ref=$1
        typeset -A ref
    ref[key1]=([key2]=val2)
}

$ func_b
Function a1: typeset -A map1=([key1]=([key2]=val2) )
Function a2: typeset -A map2=([key1]=([key2]=val2) )
$ typeset -p map1 map2
$

Or perhaps, we might set variable map1 to be an associate array from the start.

function func_a1 {
    typeset -n ref=$1
    ref[key1]=([key2]=val2)
}

function func_b {
    typeset -A map1
    func_a1 map1
    echo "Function a1: $(typeset -p map1)"

    typeset map2
    func_a2 map2
    echo "Function a2: $(typeset -p map2)" 
}
$ func_b
Function a1: typeset -A map1=([key1]=([key2]=val2) )
Function a2: typeset -A map2=([key1]=([key2]=val2) )
$ typeset -p map1 map2
$

See issue #148 for more insights.

elmehdou commented 1 year ago

I have to say, it's quite frustrating when such important features are still unstable. The company I work at relies heavily on ksh for complex operations, so having to find workarounds for each feature that's supposed to make my life easier and scripts cleaner is very scary. Thanks for you feedback.

hyenias commented 1 year ago

I can understand your frustrations but I do not share your sentiment that ksh has important unstable features.

Hopefully, as your proficiency with ksh continues to grow; your life will become easier and far less scary. From my viewpoint, most other command line scripting languages lack multidimensional array support so I consider ksh's array support as absolutely beneficial allowing me to program in a more concise and flexible manner than having to create workarounds with one-dimensional arrays only.

McDutchie commented 1 year ago

This does look like a bug. The following version of the reproducer makes it slightly more obvious that the two functions should be equivalent:

function func_a1 {
    typeset -n ref=$1
    typeset -A ref=(
        [key1]=(
            [key2]=val2
        )
    )
}

function func_a2 {
    typeset -n ref=$1
    typeset -A ref
    ref[key1]=(
        [key2]=val2
    )
}

function func_b {
    typeset map1
    func_a1 map1
    echo "Function a1: $(typeset -p map1)"

    typeset map2
    func_a2 map2
    echo "Function a2: $(typeset -p map2)" 
}

func_b

Output:

Function a1: typeset -A map1=()
Function a2: typeset -A map2=([key1]=([key2]=val2) )

Expected output:

Function a1: typeset -A map1=([key1]=([key2]=val2) )
Function a2: typeset -A map2=([key1]=([key2]=val2) )
hyenias commented 1 year ago

Additional testing results based on new reproducer:

Container type func_a1 Test Result Success
nested associative arrays typeset -A ref=([key1]=([key2]=val2) ) typeset -A map1=() No
nested indexed arrays typeset -a ref=( (a b) (c d) ) typeset -a map1 No
compound variables with indexed arrays typeset -C ref=( a=(c d); b=(e f) ) typeset -C map1=(typeset -a a=(c d);typeset -a b=(e f);) Yes
nested compound variables typeset -C ref=( a=(c=e; ); b=(d=f;) ) ksh: func_b[3]: func_a1: line 3: map1.a.c=e: no parent No
2d fixed indexed array typeset -a ref[2][2]; ref=( (a b) (c d) ) Memory fault or Segmentation fault No

An example of pasted code change above:

function func_a1 {
    typeset -n ref=$1
    typeset -a ref[2][2]; ref=( (a b) (c d) )

}
func_b