%List
?Determining if a given string qualifies as a Caché %List
is
non-trivial, as Caché itself provides no (documented) function for this
purpose. A undocumented function in ^%occRun
is available:
Write $$islist^%occRun(Variable)
However, this function seems to do minimal checks (it apparently merely
checks whether the list item lengths add up). So, for example, in the
following code, a string is built which, according to
islist^%occRun
, is a valid list, but which in fact returns an
error when trying to retrieve an item from it (i.e., it is not a
%List
).
Set NoList="2"_$J("",49) Write $$islist^%occRun(NoList),! ;Returns 1 Write $ListGet(NoList),! ;Causes an error
Another commonly used way to determine if something is a list is to use
errortrapping in combination with the use of the $ListLength
and/or $ListFind
functions, e.g.:
IsList(String) Set $ZT="NoList" If $ListLength(String) If $ListFind(String,"dummy string") Quit 1 NoList Quit 0
The $ListLength
test is equivalent to islist^%occRun
,
the additional check using $ListFind
adds some safety;
however, a string that can be considered a valid list if only the length
part of the list items is checked, contains an item with the string
searched for (here: "dummy string"), and has a non-list item after
the item that contains the value searched for, will return a false
positive.
An inmproved version of this code is:
IsList(String) New i Set $ZT="NoList" For i=1:1:$ListLength(String) If $ListGet(String,i) Quit 1 NoList Quit 0
I can't think of a situation where this will fail; disadvantages are only the (possibly expensive) use of error trapping, and the performance issues that returning each list item may have on larger strings.
%List
For most purposes, the code described above
should be sufficient. The version below is intended to illustrate how a
%List
can be parsed manually. It implements a more thourough
check than islist^%occRun
; code that passes this test is
accessible using Caché's list-related functions. It only uses error
trapping for numeric types, which could be an advantage if the test is
needed often and will likely fail a significant percentage of the calls.
For other item types, it only examines the item length and type.
Note that it is still possible that a binary string, by coincidence, qualifies as a list, even if it is not intended as such. Normal text can never qualify, as a list always has a type byte which is binary.
Based on the background information described below, more comprehensive testing is possible; specifically, numeric types could be checked for validity.
IsList(List) Public { Set $ZTrap="Error" If List="" Quit 0 Set IsList=0 Set Index=1,ListLength=$Length(List) For Item=1:1 { Set ItemLength=$Ascii($Extract(List,Index)) Set Type=$Ascii($Extract(List,Index+1)) If ItemLength=0 { ;Item of more than 253 characters ;Stored length is one more than the item's data = three ; less than the overall item length Set ItemLength=$Ascii($Extract(List,Index+2))*256+ $Ascii($Extract(List,Index+1))+3 Set Type=$Ascii($Extract(List,Index+3)) If ItemLength=0 Quit } ElseIf ItemLength=1 { ;Null item If Index=ListLength Set IsList=1 Quit Set Index=Index+1 Continue } If Index+ItemLength-1>ListLength Quit ;Check the list type: If "1245678"'[Type Quit ;For numeric types, use an error trap to check validity If Type>2,$ListGet(List,Item) ;If Index plus length of this item adds up to end of string, ; this is a list: If Index+ItemLength-1=ListLength Set IsList=1 Quit Set Index=Index+ItemLength } Quit IsList Error Quit 0 }
Caché lists do not use delimiters to separate items; instead, they store information about the length and type of a list item along with the actual data. Lists of more than one item are therefore equivalent to the concatenation of single-item lists with the same contents.
The basic structure of a list is either:
1 byte | length of item (including overhead) |
---|---|
1 byte | type of item (see below) |
variable | item data |
(for items of 253 characters or less in length), or:
1 byte | binary 0 |
---|---|
1 byte | type of item (see below) |
2 bytes | length of item (including overhead) - 3 |
variable | item data |
List types can be one of those described below. Note that this list is reverse-engineered, more types may be possible; specifically, I wonder if type 3 is perhaps an UTF-16BE encoded string. Also note that the descriptions of the format are based on testing on Intel Pentium (i.e., little-endian) hardware; I don't have access to a big-endian machine with Caché, so I can't test list contents there.
1 | A string |
---|---|
2 | A Unicode string (UTF-16LE encoded); see the Wikipedia on UTF-16. |
4 | A positive integer (stored in binary form, little endian, leading zeroes stripped) |
5 | A negative integer, (stored in binary form, little endian, leading -1 bytes stripped) |
6 | A positive floating point number |
7 | A negative floating point number |
8 | A 64-bit IEEE floating point number, as created by $Double() |
The integer and floating point types store the data in binary format, not as a string. As far as I know, this is the only place where Caché visibly distinguishes between different data types (apart from objects in Caché 5+). I have not examined exactly how floating point values are stored. Note that a list item containing another list has no special type (as far as I could determine), i.e. it is stored as a string (type 1).
A null item is a list item that is present but has no contents. It is defined by a single binary 1, i.e. a list item of length one.
Finding out if a variable contains a %Status is easy once the problem of determining if something is a %List is solved. A %Status is either a "1" (for success), or a string "0 " (zero,space) followed by a list of lists; each inner list item represents an error, and must contain four items. The first of those is the error code, the second through fourth are optional parameters to the error, and are present as null items if not applicable. In code:
IsStatus(Status) New Item,i,Result If Status="1" Quit 1 If $Extract(Status,1,2)'="0 " Quit 0 Set Status=$Extract(Status,3,$Length(Status)) If '$$IsList(Status) Quit 0 Set Result=1 For i=1:1:$ListLength(Status) { Set Item=$List(Status,i) If '$$IsList(Item) Set Result=0 Quit If $ListLength(Item)'=4 Set Result=0 Quit } Quit Result
The code and information presented on this page is written by Gertjan Klein, and placed in the public domain; it can be used freely by anyone for any purpose.