Enumerating Fields in a GROUP or QUEUE - How do you know you've reached the end?

I think I figured out a pretty decent way to know when you’ve reached the end of a GROUP/QUEUE when processing for WHO/WHAT.

If you use WHO(), you can’t rely on a label existing.

If you test for MyANY &= NULL, you don’t know if you’re just getting a NULL because of a NULL reference.

Using the TYPE() method of the TUFO interface, however, you can tell if there’s a field there.

  PROGRAM

   Include('TUFO.inc')

MAX_LOOP EQUATE(250) !Just a randomly big number

MyGroup GROUP
         BYTE
         BYTE
B        &BYTE
         STRING(20)
        END

DataType LONG

  MAP
    GetAnyDataType(*? pWhat),LONG
    ENUMGroup(*GROUP pGROUP)
  END

  CODE

  ENUMGroup(MyGroup)
  
ENUMGroup  PROCEDURE(*GROUP pGROUP)
Ndx      LONG
  CODE

  LOOP Ndx = 1 TO MAX_LOOP 
    DataType = GetAnyDataType(WHAT(MyGroup,Ndx))
    If NOT DataType
       Message(Ndx - 1 & ' fields')
       BREAK
    END
  END


GetAnyDataType  PROCEDURE(*? pWhat)!,LONG
UFO &iUfo

   CODE

  UFO &= ADDRESS(pWhat)
  IF UFO &= NULL
    RETURN 0
  END
  Return UFO._Type(Address(pWhat))

Here’s TUFO TUFO.inc (3.5 KB)

This is not true, try following procedure against a group with NULL references:

NumFieldsInGroup              PROCEDURE(*GROUP grp)
ndx                             LONG, AUTO
fldRef                          ANY
  CODE
  LOOP ndx = 1 TO 99999
    fldRef &= WHAT(grp, ndx)
    IF fldRef &= NULL
      !end of group
      ndx -= 1
      BREAK
    END
  END
  RETURN ndx
1 Like

Hi Mike - I agree that your method does not crash.

Perhaps I got to 2+2=5 too soon for the sake of this example, but the reason I went down this road was that I needed to know if my reference was good. Not just to know that a field existed. So I tried to make as simple of an example as possible (which then lost the point I was trying to convey and then I made a poor assumption for the sake of keeping the example brief).

Here’s what my issue was that got me going in this direction:

What does fldRef point to if the original reference is NULL, and how would you KNOW if it is NULL (if you needed to know)?

When/If you have write to fldRef, it will be like writing to a standard non-ref’d ANY, that goes into oblivion. I agree that oblivion is better than crashing, but how does one know that this is what happened (if they needed to know).

And if the field is an object reference (such as &PopupClass), what happens when what() happens? You still have an ANY that’s not “null”, but it’s still not useful. (For your json stuff, I reckon that’s a GOOD thing because it protects you from damaging objects, but I wanted to know what I had)

The way that I am testing for that now is to see if GetAnyDataType() returns a 31 (which I assume means reference but it’s not documented). If it does test at 31, you can then test the first 4 characters of the return value of WHAT() for ‘<0,0,0,0,>’. You can cast those 4 characters to a LONG to get the actual address of the object, as well.

e.g. here’s some pseudo code

S STRING(4)
L LONG,OVER(S)

  CODE

IF GetAnyDataType(WHAT(pGroup,Ndx))=31
   S = WHAT(pGroup,Ndx)
  !Implicitly setting L because it's OVER(S)
ELSE
   L = GetAnyDataAddress(WHAT(pGroup,Ndx))
END
RETURN L

Jeff - Interesting conversation.
What does WHAT( Grp, N) return when the Nth field is a reference?
Does the returned value vary when the reference is NULL or not-NULL ?
Have you found some useful way to interpret the returned value ?

Howdy Mark -

As far as I can tell, if it’s a reference, WHAT() will return a 4 byte string that can be cast to a LONG to get the ADDRESS(). If it’s NULL, you get ‘<0,0,0,0>’, AKA zero. This WHAT() thing was discussed in the comments of my/Dave’s 2007 article here, but I can’t find the comments now. It was one of my accidental discoveries https://clarionmag.jira.com/wiki/spaces/archive/pages/399450?preview=%2F399450%2F401956%2Fcmag-2007-01.pdf

As far as determining the underlying data type, (whether it’s a queue, object, or simple data type). I’m still unclear about that. The BaseDataType() method was not producing something I found useful.

Regarding my article about PRIVATE data, they kind of “fixed” that, but not completely. You can still cast a &ClassRef to a GROUP and get at the stuff with WHAT(). I sent an email to SV yesterday to let them know.

What they FIXED was that you can’t cast a fully declared class.

e.g.

!THIS won’t cast to a &GROUP (or get passed as a *GROUP)
MyPopup CLASS(PopupClass)
END

!THIS WILL cast to a &GROUP and CAN be passed as a *GROUP
MyPopup PopupClass
MyPopupRef &MyPopup
MyPopupRef &PopupClass

But maybe they’ll “fix” those too.

I’m much less interested in them trying to secure data marked as private
as a matter of fact I hope they don’t secure it.

That is exactly my position as well.

My reason for reporting that was not because I want to be a hall monitor.

So, I wrote a testbed, to see about using WHAT( Grp, N) with your MyGroup structure above.
I enumerated the fields once with B left Null
and again, with it assigned to something.
In both cases the loop finds all 4 fields.

  PROGRAM
  MAP
  END
  INCLUDE('debuger.inc'),ONCE
dbg        debuger 

MyGroup GROUP
         BYTE
         BYTE
B        &BYTE
         STRING(20)
        END

MyByte BYTE(42)
  CODE
  dbg.mg_init('WhatNull')

  dbg.Debugout('-{42} B &BTYE explicitly set to NULL') 
  MyGroup.B &= NULL
  DO EnumGroup

  dbg.Debugout('-{42} Now with B &BTYE non-null') 
  MyGroup.B &= MyByte
  DO EnumGroup

EnumGroup ROUTINE 
  DATA
FieldNum LONG(1)
_What    ANY 
_Who     ANY
What4Bytes STRING(4) 
RefAddr  LONG,OVER(What4Bytes)
  CODE 

  LOOP 
    _What &= WHAT( MyGroup, FieldNum )
    What4Bytes = _What  
    _Who   = WHO( MyGroup, FieldNum )
    dbg.Debugout(  'FieldNum['& FieldNum &']' |
                 &    '  WHO['& _Who     &']' |
                 & ' RefAddr['& RefAddr  &']' |
                 &  ' IsNull['& CHOOSE( _What &= NULL, 'IsNull', 'OK ['& _What &']')   &']' |
               )
    IF _What &= NULL THEN BREAK END
    FieldNum += 1 
 END 

  dbg.Debugout('Done, Fields Found['& FieldNum - 1 &']')

Output (reformatted to remove uninteresting clock etc.)

   ------------------------------------------ B &BTYE explicitly set to NULL
 FieldNum[1]  WHO[] RefAddr[538976304] IsNull[OK [0]]
 FieldNum[2]  WHO[] RefAddr[538976304] IsNull[OK [0]]
 FieldNum[3]  WHO[B] RefAddr[0] IsNull[OK [
 FieldNum[4]  WHO[] RefAddr[538976288] IsNull[OK [                    ]]
 FieldNum[5]  WHO[] RefAddr[538976288] IsNull[IsNull]
 Done, Fields Found[4]
 ------------------------------------------ Now with B &BTYE non-null
 FieldNum[1]  WHO[] RefAddr[538976304] IsNull[OK [0]]
 FieldNum[2]  WHO[] RefAddr[538976304] IsNull[OK [0]]
 FieldNum[3]  WHO[B] RefAddr[4221364] IsNull[OK [´i@
 FieldNum[4]  WHO[] RefAddr[538976288] IsNull[OK [                    ]]
 FieldNum[5]  WHO[] RefAddr[538976288] IsNull[IsNull]
 Done, Fields Found[4]

So some things to point out:

It shows the content of the &Byte as starting with ‘´i@’
which is why you can’t trust WHAT on references
so the next trick is determining that a field is in fact a reference

_What for the 3rd field is NOT null, even when the reference in the group itself is NULL
which was the assumption behind this thread.

Let me know if you didn’t receive my email

On the What4Bytes, don’t use an &= ANY in between. You’re reading just the straight value.

What4Bytes = WHAT(Yada yada yada)

There is also some interesting reading here (although some stuff has changed since 2006)

https://www.clarionlife.net/?s=tufo_callinterface

thanks for the clarification
It does make sense

My Russian is very weak (as in non-existent)
I do have TUFO.INT in my ClarionCommunity Repo, for use by my Debuger class

Note, there are two versions in there, hence the conditional OMIT/COMPILE pre C7 and C7+

prop:fields. Example at
https://www.litreum.com/?articleId=7348.
(Litreum.com /clarionfoundry/language/data, variables, structures/structures, groups,

That is for FILEs, not independent structures

    i#=1
    loop
      F=who(MyGroup,i#)
      if F=''
        stop('No more Fields')
        break
      end
      i#+=1
    end

If you use WHO(), you can’t rely on a label existing.

How it may be?

Would be nice if HOWMANY(label) simply provided the number?

Hello Rafael -

It’s “legal” to have structures without labels, so WHO() is unreliable for this purpose.

e.g.

MyGroup GROUP
          BYTE
          BYTE
FIELD3    BYTE
        END
1 Like

It would tie the room together, that’s for sure. Just like an ABCBrowse.SetEnabled(InsertRecord) would

It would tie the room together, that’s for sure.

Back in the day, Randy Goodhew had a library with several data structure functions. Just wondering if…