Bug in #For(%Symbol) when using #Delete(%Symbol) skips some

Found a bug in the #For() template command which intermittently doesnt delete all instances.

Its like the internal counter gets messed up after the delete, ie record 2, delete record 2, record 3 becomes record 2 and then the #For fetches record 3, skipping the replacement record 2.

As #For is mainly used with builtin symbols which cant be deleted, this might explain why it exists.

#For(%MultiSymbol)
#Delete(%MultiSymbol)
#EndFor

Wont delete all instances of the %MultiSymbol.

I wouldn’t call that a ā€œbugā€, but rather a typical quirk of looping logic. That’s why so many of my loops are written to go backwards, which you could do with the Reverse attribute:

#For(%MultiSymbol),Reverse
  #Delete(%MultiSymbol)
#EndFor

2 Likes

The docs dont allude to this quirk so I rehashed the code with a few more lines.

#Declare( %Found, Long ) 
#Loop 
#Set( %Found, 0 ) 
#For( %MultiSymbol ) 
#IF( %MultiSymbol ) 
#Delete( %MultiSymbol ) 
#Set( %Found, 1 ) 
#EndIF 
#EndFor 
#IF( NOT %Found ) 
#Break 
#EndIF 
#EndLoop 

I’ll give the reverse a go as I can see how that was probably invented for this situation. :grin:

Yeah, the REVERSE attribute is a bit more elegant. :wink:

BTW, here are a couple other less straightforward ways to skin the cat:

#LOOP
  #SELECT(%MultiSymbol, 1)
  #IF(INSTANCE(%MultiSymbol)=0)
    #BREAK
  #ENDIF
  #DELETE(%MultiSymbol)
#ENDLOOP
#LOOP,WHILE(ITEMS(%MultiSymbol))
  #FOR(%MultiSymbol)
    #DELETE(%MultiSymbol)
  #ENDFOR
#ENDLOOP

The template language is wonderfully flexible, and of course flexibility breeds complexity.

I havent added a lot of commands to my template builder yet, but there’s enough there for it to write my class writing template, so its very bare bones still without any ā€œcompoundā€ templates, ie templates which do things like the above code examples with minimal prompt input and I’m only writing classes to remove huge chunks of code from the embeds of an app, to keep things simple.

1 Like

Seems good to also REVERSE in that code so that it is unlikely the While(Items()) will be True on the 2nd loop:

#LOOP,WHILE(ITEMS(%MultiSymbol))
  #FOR(%MultiSymbol)           ,REVERSE
    #DELETE(%MultiSymbol)
  #ENDFOR
#ENDLOOP

Well should have this Class Writing template finished by the weekend ready for me to test its code with the compiler.

I think it cover’s all the bases, with scope for future data types, if any…

Cant decide whether to hook some of these Ai’ LLM’s into it to make writing the class code even easier. Any recommendations? :grin:

1 Like

Agreed. My point with those two additional examples was that they were less efficient/elegant ways to get the job done. The simple #FOR(...),REVERSE approach is absolutely the best choice.

However, if you just want to delete all instances of the multivalued symbol, then obviously #PURGE(%MultiSymbol) is even better. The #FOR + #DELETE loop is necessary only if you need to do something with each entry before deleting it.

That brings up another interesting point: I’ve always been confused about the difference between #PURGE and #FREE. The (non-template) Clarion language uses FREE(Queue), so one could expect that #FREE(%MultiSymbol) is the way to go. However, the docs indicate that #FREE clears all instances, whereas #PURGE deletes them. Therefore, I’ve always used #PURGE, just in case. I suspect they do the same thing, but I’ve never gotten around to testing it. :roll_eyes:

2 Likes

They are like #Insert and #Invoke…

The only difference I can see, is the #Invoke help suggests it can bring in #group code from different #template’s, at least thats how it reads to me, but like you I’ve not tested either and I cant find any example’s in the shipping templates to back that up, but I’ve only done a cursory check.

That’s not quite the same, as the purpose of #INVOKE is rather special.

Let’s start with #INSERT and #CALL, which are more similar to each other:

#INSERT is the traditional command, and indicates to generate the named group with its code left-justified at the column position of the #INSERT. For example, if the #INSERT is position in column 5, then the #GROUP code will appear in column 5. If the code in the #GROUP itself is indented, then that will be added to the #INSERT column position. You can disable this indentation with the NOINDENT attribute.

#CALL was added later. It can call a #GROUP like #INSERT, but the generated group code will ignore the column position of #CALL, like #INSERT(...),NOINDENT.

Generally speaking, I use #INSERT to call a #GROUP that I expect to generate code, while I use #CALL to execute a #GROUP that contains pure logic (no generated code). I break that rule occasionally, mostly when I use #INSERT in place of #CALL (because it was always OK to use #INSERT).

With both #INSERT and #CALL, the name of the #GROUP must be hard-coded when the template is written:

  #INSERT(%SomeGroup)  #!Generates code starting at column 3
  #CALL(%SomeGroup)    #!Generates code started at column 1

#CALL can also be used to call a #GROUP that returns a value (although I believe they added that ability to #INSERT as well):

  #DECLARE(%CapturedReturnValue)
  #CALL(%SomeGroup), %CapturedReturnValue

#INVOKE is different in a very important way. It’s used to call a #GROUP, where the name isn’t known until the code is generated (long after the template was written). You have to assign the name of the #GROUP to a runtime symbol, then pass that symbol to the #INVOKE command:

  #EQUATE(%SomeGroupName, '%SomeGroup(ABC)')
  #INVOKE(%SomeGroupName)

#INVOKE can also specify a return value be captured.

I believe the chain name is optional with all of them. Parameters are passed the same for all three commands.

Bottom line, here’s how I choose to use them:

  • If the #GROUP I’m calling will generate code, I use #INSERT.
  • If if’s pure logic with no generated code, I use #CALL.
  • If I don’t know the name of the #GROUP until generation time (very special), then I use #INVOKE.

FYI, I have only two instances of #INVOKE in all of my template sets. It’s typically used to generate virtual methods based upon reading of classes at generation time.

Yeah I did note that the symbol used in #Invoke could be used like a variable to call another #group symbol, but I havent had to use #Invoke yet.

There’s alot of template commands I’ve not written template’s for in my Template Builder, I’ve only used 64 commands to write my template builder. And those 64 commands are also capable of writing my Class Writer template using only the AppGen and no embed source code.

As the other screenshots have shown, the prompt and control positioning is functional as is the various PROP:XYZ which works with PROP() as seen with the tooltip below.

Its so slow going getting the right wording for tooltips…

Edit.
This is all the Class Writer template looks like inside the AppGen.

The good thing about this exercise is, I’m smoothing out the template builder so I can then use it to generate code in other languages. I’m thinking a Dot Net Maui template soon… :grin:

The #RunDLL means I can call compilers and other CLI stuff now.

Edit.

Would this be a better Tooltip/TLDR description for Implements and Interfaces?

In particular I’m trying to use a real world relatable example of what Interface’s can be used for.

So I’ve used this example which I think is relatable.

For example, a FileManager Interface, can force a SQL DB Class, ISAM Files Class, and Cloud Data Class to all provide the same methods (procedure’s) to provide identical CRUD functionality.

So the tooltip now read’s

Tick this option if you want this class to inherit the methods defined in one or more Interfaces.
An Interface is a Type definition that dictates the methods this class will/must have and respond to.
Think of an Interface as a predefined list of Methods (procedureā€™ā€˜s) that this class must have and respond to along with this classā€™ā€˜s other unique method (procedure) name’'s.
For example, a FileManager Interface, can force a SQL DB Class, ISAM Files Class, and Cloud Data Class to all provide the same methods (procedure’s) to provide identical CRUD functionality.

See what I mean about trying to get the tooltip wording right… :roll_eyes:

1 Like