Language enhancement adds new collection type:
MLIST(varstr)
MLIST(varx)
MLIST (short for "Multi-level LIST") is a bidirectional linked list, with the added feature that each element may contain a link to a child MLIST. Aside from the links, each element may contain a variable length string (varstr) or blob (varx). The MLIST is effectively a kind of tree structure, useful for representing a variety of real-world data such as disk directories and XML/JSON documents, where the ability to maintain a particular order and to efficiently insert, delete, and splice elements or branches at arbitrary points is required.
Unlike the ORDMAP and ORDMAPM collections, there is no direct access to an element by its key; there is only direct access to the first and last elements in the list, with the ability to iterate forwards, backwards, or down from there. The order of the elements in the list is preserved based on the order in which you build it.
As with other collection types, an MLIST is created with the DIMX statement:
DIMX $M, MLIST(VARSTR)
DIMX $M, MLIST(VARX)
The syntax for assigning and accessing elements is similar to that for ORDMAP, modeled on single-dimension array syntax. But instead of using string keys for the array index as in ORDMAP, the MLIST uses various special dot-variables which act as pseudo-keys. In addition, there are several new dot-statements and dot-functions and dot-members to perform operations particular to MLISTs that cannot be easily represented with existing statements and functions. These are summarized below, following by descriptions and syntax for the various operations.
Dot-Variable |
Description |
---|---|
.FRONT |
first element, e.g. ? $M(.FRONT) |
.BACK |
last element, e.g. ? M(.BACK) |
.PUSHFRONT |
add new element to front, e.g. $M(.PUSHFRONT) = X |
.PUSHBACK |
add new element to end, e.g. $M(.PUSHBACK) = X |
Dot-Statement |
Description |
.POPFRONT |
remove first element from list, e.g. .POPFRONT $M() |
.POPBACK |
remove last element from list, e.g. .POPBACK $M() |
.SPLICE |
splice elements from one list to other |
.SORT |
sort elements in list by value, e.g. .SORT $M() |
Dot-Function |
Description |
.REF($$i) |
returns a reference to iterator location, used when modifying an element by iterator, e.g. $M(.REF($$i)) = X |
.BEFORE($$i) |
specifies insertion position, e.g $M(.BEFORE($$i) = X |
Dot-Member |
Description |
.SUBLIST |
specifies sublist of element, e.g. $$i.SUBLIST, or $M(.BACK).SUBLIST |
.KEY |
$$i.KEY is equivalent to .KEY($$i) |
All of the following descriptions and examples will assume the prior definition of these two MLISTs:
dimx $m, MLIST(varstr)
dimx $m2, MLIST(varstr)
Adding Elements
New elements may be added to the front or back of a list using array assignment syntax equivalent to that used for the ORDMAP collection, except instead of string keys, you must use one of the special dot-variable pseudo keys:
$m(.PUSHFRONT) = X ! add new element to front; assign value X
$m(.PUSHBACK) = X ! add new element at back; assign value x
You may also insert an element into the middle of the list using the special dot-function .BEFORE($$i). In the following example, we scan the list until locating an element whose value is "something", and then insert a new element before it:
foreach $$i in $m()
if $$i = "something" then
$m(.BEFORE($$i)) = x ! insert new element before $$i position
exit
endif
next $$i
Modifying existing elements uses the same syntax as adding or inserting new elements, except with a different set of dot variables and dot functions:
$m(.FRONT) = y ! change value of first element to y
$m(.BACK) = y ! change value of last element to y
To update the value of an element by iterator, you must use the special dot function .REF($$i) to return to updateable reference to the iterator (since $$i by itself is a read-only copy of the value of the element).
foreach $$i in $m()
$m(.REF($$i)) = $$i + "y" ! update value of each element
next $$i
Deleting Elements
Elements can be deleted by assigning the special value .NULL to them, using the element modification syntax given above. For example:
$m(.FRONT) = .NULL ! delete the first element
$m(.BACK) = .NULL ! delete the last element
foreach $$i in $m()
if $$i = "something" then
$m(.REF($$i)) = .NULL ! delete the element
exit ! always exit foreach loop
endif ! after removing an element, as it
next $$i ! may invalidate the iterator
In addition, you can use the following dot-statements to delete:
.POPFRONT $m() ! delete first element
.POPBACK $m() ! delete last element
Retrieving elements
X = $m(.FRONT) ! retrieve value of first element into X
X = $m(.BACK) ! retrieve value of last element into X
foreach $$i in $m() ! access by iterator
X = $$i ! X receives value of iterated element
next $$i
Splicing lists
One or more elements from one list can be spliced on to another using the dot-statement .SPLICE. Note no elements are actually allocated or deleted in this operation; they are just moved from the source list to the destination list. The general syntax for the .SPLICE statement is:
.SPLICE dst-list-ref, src-list-ref {, count}
If count (number of elements to move) is not specified, the operation moves all of the elements starting from the src-list-ref position. The list-ref parameters may be list base references, e.g. $m(), or they may be references to a particular element by iterator reference, e.g. .REF($$i). For example:
.SPLICE $m(), $m2()
The above case splices the entire $m2() list to the end of the $m() list. Afterward, the $m2() list will be empty (but still valid).
.SPLICE $m(), $m2(), 1
Same as the previous case except only the first element of $m2() is spliced to the end of $m().
To splice to some position other than the end of the destination, specify a position via an iterator reference. For example, to move append the 3rd and 4th items from $m2() the end of $m1()...
count = 0
foreach $$i in $m2()
count += 1
if count = 3 then ! 3rd iterated item
.SPLICE $m(), .REF($$i), 2 ! splice 3rd & 4th items
exit ! iterator corrupted after splice! must exit!
endif
next $$i
The .REF($$i) iterator technique also works for the destination, i.e. to insert at a destination other than the end of the target list. To splice from an arbitrary Xth position in the source to the Yth position in the destination, use a double-nested foreach loop:
! splice count elements from position srcpos in m1$() to dstpos in m2$
idst = 0 ! counter for dest position
foreach $$i in $m2()
idst += 1
if idst >= dstpos then ! $$i is the dst splice-to position
isrc = 0
foreach $$j in $m1()
isrc += 1
if isrc >= srcpos ! $$j is the src splice-from position
.splice .ref($$i), .ref($$j), count
exit ! must exit after splice!
endif
next $$j
exit ! must exit after splice!
endif
next $$i
Note: as with other modifications to a collection, the use of .SPLICE corrupts the iterator; thus you must exit from the foreach loop after the .SPLICE operation. (In the above example, we have to exit from both loops.)
Multi-level list operations: The sublists in an MLIST structure are themselves MLISTs, so all of the above operations work the same way on sublists. However, you do need a special dot-member, .SUBLIST, to specify when you are referring to the sublist for an element, rather than to the element as a member of the parent list.
~~~~======= !=== <this first method doesn't yet work> === ~~~~=======
There are two ways to create a sublist, both involving the use of the special .SUBLIST qualifier. The first is to splice elements from another list on to the .SUBLIST member of an element in the destination list, for example:
.SPLICE $m(.BACK).SUBLIST, $m2()
The above statement splices the list $m2() as a sublist of the last element in $m(). Note that without the .SUBLIST modifier, the $m2() list would have been spliced on to the $m() list, just before the last element.
foreach $$i in $m()
if $$i = "something" then
.SPLICE $$i.SUBLIST, $m2, 3
exit
endif
next $$i
In the above example, we scan the $m() list for an element with value "something", and then splice the first 3 elements of the $m2() list as a sublist of that target element.
~~~~~~~~~======= !=== <this method works> === ~~~~~~~~~=======
The second way to create a sublist involves passing it as a parameter to a function or procedure, which is dealt with in a separate section below, after the discussion of deleting sublists.
To delete a sublist, assign .NULL to the target element, either using one of the dot-variable pseudo-keys, or using an iterator to target the element:
$m(.BACK) = .NULL ! delete sublist from last element of $m()
foreach $$i in $m()
if $$i = "something" then
$$i.SUBLIST = .NULL
exit
endif
next $$
The above examples delete the sublist without deleting the element itself. Deleting the element will also delete any sublist(s) attached to it.
Parameter Passing
MLISTs can be passed by reference to functions and procedures. This is similar to the way it works with ORDMAPS (see 1520.0) except that with MLISTs there is also a variation for passing sublists.
To pass a regular MLIST to a function or procedure, use the following syntax (nearly identical to that for ORDMAPs except for the "AS" clause):
call Proc( $m() ) ! passing the entire $m list by reference
Procedure Proc( $mloc() as MLIST(varstr) )
$mloc(.PUSHBACK) = "new element"
EndProcedure
The above example adds the element "new element" to the MLIST $m(). Note that the local list $mloc() is effectively just an alias for the $m() list. As with passing ORDMAP arrays and other DIMX arrays by reference, you have to initialize the array first in the calling routine.
The pre-initialization requirement does not apply to sublists though, which are automatically initalized when passing them to another routine:
call Proc( $m(.back).SUBLIST )
In the above example, the SUBLIST for the last element of the $m() list is passed to the Proc procedure (given in the previous example). Note that the procedure does not know, or care, that the caller passed a sublist rather than an MLIST base reference; they are indistinguishable from the perspective of the procedure. Also note that since the sublist gets auto-initialized if necessary, the same syntax works regardless of whether the sublist initially exists.
You can also pass a sublist of an iterator, e.g.
foreach $$i in $m()
if $$i = "something" then
call Proc($$i.sublist)
endif
next $$i
In the above example, for each element in the MLIST $m() whose value is "something", we pass the sublist to the Proc procedure, which adds a "new element" to the end of that sublist.