Question Michael Akselrod · Oct 28

*.inc file For loop

How to define a For loop in *.inc file?

Comments

DC AI Bot · Oct 28 Bot

To define a For loop in an *.inc file, you can use ObjectScript syntax in the following ways:

  1. Define the loop with numeric increments: You can use a counter variable, specifying the start, increment, and stop values:

    FOR var=start:increment:stop {
        // Code block content
    }
    

    In this syntax:

    • start is the initial value assigned to the loop counter.
    • increment specifies how much the counter increases on every iteration.
    • stop is the end condition of the loop. The loop will terminate when the counter exceeds this value [1][2].
  2. Iterate through a comma-separated list: You can iterate over predefined values directly:

    FOR var="val1","val2","val3" {
        // Code block content
    }
    

    This executes the code block for each value in the list [2].

  3. Dynamic iteration with delimiters: If you have a string of comma-separated values, you can split and iterate:

    SET customString = "1,3,9"
    FOR index=1:1:$LENGTH(customString, ",") {
        SET val = $PIECE(customString, ",", index)
        // Do something with val
    }
    

    The $LENGTH function obtains the count of items in the list, and $PIECE extracts each element [3].

  4. Infinite loops with FOR and break conditions: A loop can also work without a definite stop value:

    SET x=8
    FOR { 
        WRITE "Running loop x=",x,!
        SET x = x-1
        QUIT:x=3
    }
    

    This continues until a QUIT, RETURN, or GOTO command exits the loop [1][2].

  5. *Custom Macros in an .inc file: An INC file can also define new For-like macros to simplify global or variable iteration. For example:

    #define ForAll(%in, %gn) SET gn%in=$NAME(%gn) SET %in="" FOR { SET %in=$ORDER(@gn%in@(%in)) QUIT:%in="" }
    

    This allows iteration over a global or list defined in the INC file [4].

References: [5][1][2][3].

Sources:

0
Michael Akselrod · Oct 28

Thank  you for the line: #define ForAll(%in, %gn) SET gn%in=$NAME(%gn) SET %in="" FOR { SET %in=$ORDER(@gn%in@(%in)) QUIT:%in="" }

Please, explain what the arguments "%in" and "%gn" mean/define. 

0
Michael Akselrod · Oct 28

I’d specifically like to use a *.inc file to define a for loop—assuming that’s even possible.

Also, is there a way to bypass the AI bot and direct my question exclusively to a human community?

0
Robert Cemper  Oct 28 to Michael Akselrod

The AI example is misleading anyhow, as it just does a $O loop without any action to be done 
*.inc is typically kind of text generation code during compilation. 
What is your expectation for using the *.inc ?

%in and %gn are just text replacement parameters:
%gn is used as globalname, %in as first level subscript

0
Michael Akselrod · Oct 28

@Robert Cemper 

I realized that AI-generated answer looked "unreal".

I would like to have *inc file entry to convert an array into a string and string into an array. ObjectScript code to do it is not an issue at all - I just want to have it as a macro in *.inc file. There is a chance I have no enough knowledge regarding  *.inc macros because the simple "if" statement doesn't work as well (error on compiling within a class) like: #define TestIf(%arr)        if (%arr > 0) { QUIT 5 }. What am I missing? Thank you.

0
Robert Cemper  Oct 28 to Michael Akselrod

(error on compiling within a class) like: 
#define TestIf(%arr)        if (%arr > 0) { QUIT 5 }    ; no final dot.

#define is an element of ObjectScript
so it has to be embedded in a [Class]Method 
and it is only available within that method

It can't be flying free inside a class definition.

If you need your $$$TestIf(...)  in more than 1 method, you can deposit
it in some TestIf.INC  and include it BEFORE the Class statement !!
Then it is visible to ALL methods.

Attention: You can INCLUDE just 1 single  *.INC in a class definition.
If you need more than 1, you have to cascade it with #include  in  the first *.inc

Include TestIf

Class A.PERSON1 Extends%Library.Persistent
{
Parameter GlobalName = "^.........!";;/// .......ClassMethod michael(param) as%Integer
{
    $$$TestIf(param)
 .........
    quit$$$OK
}

 
   

0
Vitaliy Serdtsev  Oct 29 to Robert Cemper
Attention: You can INCLUDE just 1 single *.INC in a class definition.
Including Include Files

To include multiple include files at the beginning of a class definition, the syntax is of the form:

Include (MyMacros, YourMacros)
0
Robert Cemper  Oct 29 to Vitaliy Serdtsev

Thanks for the correction.
I had something similar in mind, but didn't find the related doc!

0
Vitaliy Serdtsev  Oct 30 to Michael Akselrod
I would like to have *inc file entry to convert an array into a string and string into an array.
If the array is one-dimensional, then you can do without cycles altogether. I'll give you a small example below:
  #define Array1ToJSONString(%arr,%json)##continue
  s ##unique(new)=##class(%ZEN.proxyObject).%New()##continue
  d ##unique(old).%CopyFromArray(.%arr),##continue
    ##class(%ZEN.Auxiliary.altJSONProvider).%WriteJSONStreamFromObject(.%json,##unique(old))##continue
  s %json=%json.Read(3641144)
  
  #define JSONStringToArray1(%json,%arr)##continue
  d ##class(%ZEN.Auxiliary.altJSONProvider).%ConvertJSONToObject(%json,,.##unique(new)),##continue
    ##unique(old).%CopyToArray(.%arr)

Usage:

  set a("color")=$listbuild("red","blue")
  set a("price")="expensive"
  set a("size")=$listbuild("large","small")
    
  $$$Array1ToJSONString(a,jsonStr)
  zwrite jsonStr
  $$$JSONStringToArray1(jsonStr,b)
  zwrite b
0
Michael Akselrod  Oct 30 to Vitaliy Serdtsev

@Vitaliy Serdtsev

Thank you for the examples. However, replacing a "For" loop isn't quite what I'm looking for.
I'm able to create a simple *.mac routine to achieve the desired functionality, but my issue lies in understanding why the "For" macro fails to compile when used in a .inc file.
What I'm really aiming for is something like Robert's example:
    #define MyLoop(%count) set x="" for i=1:1:%count set x=$order(^%SYS("JOURNAL",x),-1)
(Benjamin's example is more complex, but essentially follows the same idea.)
Also, just to note — I'm encountering a similar issue with IF statements in .inc files. Here's a reference to a related discussion:
https://community.intersystems.com/post/failure-compile-inc-simple-if-s…

Thanks again for your help!

0
Vitaliy Serdtsev  Oct 30 to Michael Akselrod

Show your code with the for loop that doesn't work.

0
Vitaliy Serdtsev  Oct 30 to Michael Akselrod

If the array is multidimensional, then you can't do without loops (the code is without error handling):

  #define ArrayToStr(%arr,%str) set ref=$name(%arr),%str="s " for  {set ref=$query(@ref,1,val) quit:ref=""  set %str=%str_$$FormatName^%qcr(ref,1)_"="_##class(%Utility).FormatString(val)_","} set $extract(%str,*)=""
  #define StrToArray(%str) xecute %str

Usage:

  set a(0)=7
  set a(1,"color")="green"
  set a(1,"color","green")=""
  set a("color",$listbuild($double(3)))=$listbuild("red","blue")

  $$$ArrayToStr(a,str)
  zwrite str
  kill a
  $$$StrToArray(str)
  zwrite a
0
Michael Akselrod · Oct 28

@Robert Cemper

Re: "If" in *.inc

#define TestIf(%arr) if (%arr 0) QUIT } is already included in ...Utility.inc (which contains hundreds of macros successfully used throughout the application). 

Include (....Utility) is already declared within a ...FSLogging class.

set = $$$TestIf(3) is set within a LogArchive classmethod.  Expected "a" value is 5

And compilation error: 

Where is an issue? Thank you

0
Robert Cemper  Oct 28 to Michael Akselrod

Now it's clear   instead of   (%arr 0)  you simply should write (%arr>0)  
The ObjectScript compiler is interpreting the blank after %arr as an argument terminator

0
Michael Akselrod · Oct 28

@Robert Cemper
Same compilation error for all options below:

#define TestIf(%arr) if (%arr>0) {QUIT 5} 

#define TestIf(%arr) if (%arr> 0) {QUIT 5} 

#define TestIf(%arr) if (%arr>0) { QUIT 5 } 

0
Robert Cemper  Oct 28 to Michael Akselrod

Strange.  It should generate   IF (3>0) {QUIT 5} and accept it.
Could you pls simplify the case for testing to 

#define TestIf(%arr) if%arr>0QUIT5

Not yet Halloween, but this looks spooky:
For me, all variants work: 
My versions:
InterSystems Studio Client  2024.1.0 Build 262
Server IRIS for Windows (x86-64) 2024.3 (Build 217U)

0
Michael Akselrod · Oct 28

@Robert Cemper

#define TestIf(%arr) if %arr>0 QUIT  - same error, while no "(" in the "Invalid command text in screenshot.

IRIS for Windows (x86-64) 2022.1.2 (Build 574_0_22407U) Wed Apr 5 2023 11:19:54 EDT

InterSystems Studio Client  2022.1.2 Build 574
Server IRIS for Windows (x86-64) 2022.1.2 (Build 574_0_22407U)

0
Robert Cemper  Oct 28 to Michael Akselrod

So this means that the IF is not applicable in your context.
What commands are before the $$$TestIf(3) and after  ? 
I think of some  WRITE  $$$TestIf(3) .....    
Or you may try to explicitly write the macro-generated code to see any hidden issue
I'm running a bit out of fantas,y what may go on 

0
Robert Cemper  Oct 29 to Michael Akselrod

TestFor.inc
 

#define MyLoop(%count) setx=""for i=1:1:%countsetx=$order(^%SYS("JOURNAL",x),-1) writex,! 

in Test class:
 

ClassMethod Mike()
{
	$$$MyLoop(7) Write ?5,$get(@$ZR," *** "),!
}	

Resulting in

PURGED
      ***
PREFIX
 
MAXSIZE
     1073741824
LIFESPAN
      ***
LAST
     1^C:\InterSystems\IRIS242\mgr\journal\20251029.003
EXPSIZE
     0
CURRENT
     1^C:\InterSystems\IRIS242\mgr\journal\20251029.003
0
Michael Akselrod  Oct 29 to Robert Cemper

@Robert Cemper
While the definition, IMO, seems valid, I got the error on attempt to compile  class with call in it - see below. But the message does not contain annoying "Invalid function" - complain is regarding missing space in definition.

0
Vitaliy Serdtsev  Oct 30 to Michael Akselrod

Example of @Robert Cemper compiles without errors for me (even on Caché 2018.xxx):

Class dc.a Abstract ]
{

ClassMethod Test()
{
  #define MyLoop(%count) set x="" for i=1:1:%count set x=$order(^%SYS("JOURNAL",x),-1) write x,! 
  $$$MyLoop(5)
}

}
0
Michael Akselrod  Oct 30 to Vitaliy Serdtsev

"Your" definition is NOT in *.inc file!

Upon inserting in *.inc as:
    #define MyLoop(%count)         set x="" for {i=1:1:%count set x=$order(^%SYS("JOURNAL",x),-1)} QUIT x
And setting in classmethod as:
    set x = $$$MyLoop(5)
I got the following compilation error: 

0
Vitaliy Serdtsev  Oct 30 to Michael Akselrod

You have two mistakes.

  • Instead
    for {i=1:1:%count set x=
    should be
    for i=1:1:%count {set x=
  • Macros ≠ Function

A working example:

  #define MyLoop(%count,%result) set ref="",%result="" for i=1:1:%count set ref=$order(^%SYS("JOURNAL",ref),-1),%result=%result_$listbuild(ref)

  $$$MyLoop(5,x)
  write $listtostring(x)
0
Michael Akselrod  Oct 30 to Vitaliy Serdtsev

It works! Thank you - let me try to set "real life" code and test.

0
Benjamin De Boe  Oct 29 to Michael Akselrod

Here's an example that combines two bitmaps:

#define AndBits(%dst,%src) ##continue
    set mbChunk=""                                      ##continue
    for {                                               ##continue
        set mbChunk=$o(%dst(mbChunk),1,mbBits)          ##continue
        quit:mbChunk=""                                 ##continue
        set mbBits=$bitlogic(mbBits&%src(mbChunk))      ##continue
        If $bitfind(mbBits,1) {                         ##continue
            Set %dst(mbChunk)=$bitlogic(mbBits)         ##continue
        } Else {                                        ##continue
            Kill %dst(mbChunk)                          ##continue
        }                                               ##continue
    }

Using ##continue helps you avoid cramming everything onto a single line

0
Michael Akselrod  Oct 29 to Benjamin De Boe

@Benjamin De Boe

I am pretty familiar with using "##continue" in macro definition - there are a lot for other macros with ##continue  in "*inc file.

Upon defining the macro per your example the error is:

Upon deleting line "set mbChunk=""     ##continue" (seems like unnecessary) the error is more definitive (pointing to "for" itself"):

0
Benjamin De Boe  Oct 29 to Michael Akselrod

that first line is required to initialize the mbChunk variable used in the loop, else you'd get an UNDEFINED at runtime.

I'm thinking the way how you're using this macro is slightly off, and this has the same root cause as the other thread you opened. This macro is expected to be used on its own:

  // some logic here
  $$$AndBits(^glo1,^glo2)
  // more logic here, and note there's at least a space before all these lines

hope this helps

0
Michael Akselrod  Oct 29 to Benjamin De Boe

@Benjamin De Boe
You are right about "mbChuck" initiation for runtime, but the discussion is about compiling macro reference in a class which is way prior runtime.

I need output from macro "For" - so I can't just use "$$$AndBits(^glo1,^glo2)" (or "do $$$AndBits(^glo1,^glo2)"). I am testing compilation and upon finding a right syntax, "For" macro will be extended to return value I am looking for (for example: argument could be an array and output will be comma-separated sting of array values). ObectScript code, to achieve it, is not an issue - I would like to have it as macro in *inc file. So, any ideas how to transform your example into compilable entry? Thank you

0