Discussion (32)4
Log in or sign up to continue

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:

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

@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.

(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
}

 
   

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

@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-st...

Thanks again for your help!

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

@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

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>0 QUIT 5

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)

@Robert Cemper

I've created separate post for *.inc "If" issue (https://community.intersystems.com/post/failure-compile-inc-simple-if-st...), hoping somebody could bring an idea for resolving the issue. 
If no positive resolution I would try to create WRC ticket based on your note that everything works on your environment.

But getting back to the initial issue regarding "For" loop in *.inc file.
Can you post any example of using "For" loop in a macro?
Thank you.

TestFor.inc
 

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

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

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)

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

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

@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