- Always put code into external procedure files stored directly on disk
- Filenames are built from the characters
[A-Za-z_-]
and end with.ipf
- For code used exclusively with Igor Pro 7 or higher use UTF-8 as text
encoding and specify
#pragma TextEncoding = "UTF-8"
. For lower versions the file encoding is OS-dependent, but the used charset should always be restricted to ASCII characters. - The beginning of each procedure file has
#pragma rtGlobals=3
with optional comment - For new code
#pragma rtFunctionErrors=1
is mandatory - Always use UNIX (LF) end-of-line style
Use doxygen for documenting files, functions, macros and constants. There is an AWK script available to use Igor Pro Files with Doxygen.
Always add a space before a trailing comment as in
if(a < 0) b = 1 else // positive numbers (including zero) b = 4711 endif
Prefer comments on separate lines instead of trailing comments
Use
///
to start a doxygen comment and///<
for documentation after the definitionAlign multiple
@param
arguments and document them in the same order as in the function signature:/// @param pressure Pressure of the cell /// @param temperature Outdoor temperature /// @param length Length of a soccer field Function PerformCalculation(variable pressure, variable temperature, variable length) // code End
Use
in/out
specifiers for@param
if the function uses call-by-value and call-by-reference parameters./// @param[in] name Name of the device /// @param[out] type Device type /// @param[out] number Device number Function ParseString(string name, variable &type, variable &number) // code End
Optional parameters are documented as
/// @param verbose [optional, default = 0] Verbosely output /// the steps of the performed calculations Function DoCalculation([variable verbose]) // code End
For documenting multiple return values use
@retval
as in/// @param key key to look for /// /// @retval result one on error, zero otherwise /// @retval unit unit of the result [empty if not found] Function [variable result, string unit] GetProperty(string key) // code End
Every function should be separated by exactly one newline from other code
Indentation is done with tabs, a tab consists of four spaces (in case you are coding not in Igor Pro).
Comments on separate lines have the same indentation level as the surrounding code
Separate function parameters from local variables and local variables from the rest of the function body by a newline
Function CalculatePressure(variable weight, variable size) variable i, numEntries // code End
If you are targeting Igor Pro 7 or higher prefer inline parameter declarations as in
Function CalculatePressure(variable weight, variable size) variable i, numEntries // code End
as that is easier to grasp for newcomers. And also works with multiple-return-value syntax.
Add a space around mathematical/binary/comparison/logical operators and assignments, and add a space after a comma or semicolon
a = b + c * (d + 1) / 5 if(a < b) c = a^2 + b^2 end Make/O/N={1, 2} data for(i = 0; i < numWaves; i += 1) a = i^2 endfor if(myStatus && myClock) e = f endif h = i | j
Always avoid trailing whitespace, that is whitespace directly before the end-of-line character. Here
␣
denotes a space, and→
a tab.
Good:
Function␣DoStuff() →print␣"Hi" →if(a␣<␣b) →→c␣=␣a^2␣+␣b^2 →endif →Make/O/N={1,␣2}␣data End
Bad:
Function␣DoStuff()␣␣␣␣ →print␣"Hi"␣␣ →if(a␣<␣b)→ →→c␣=␣a^2␣+␣b^2 →endif→→␣ →Make/O/N={1,␣2}␣data End
Line continuation, available with Igor Pro 7 or higher, requires indentation with tabs as always, but alignment with spaces and a space in front of
\\
. Multiple line continuation characters should be vertically aligned.␣
denotes again a space, and→
a tab.Function␣DoStuff() →string␣str␣ =␣ "abcd"␣␣␣\ →␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣+␣"efgh"␣\ →␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣+␣"ijk"␣␣\ →if(A && B \ →␣␣␣&& C) →→// code End
Surround blocks like
if/endif
,for/endfor, ``do/while
,switch/endswitch
,strswitch/endswitch
with a newline if what they express is a logical group of its ownfor(i = 0; i < numEntries; i += 1) // code endfor if(a > b) c = d elseif(a == b) c = e else c = 0 endif switch(mode) case MODE1: a = "myString" break case MODE2: a = "someOtherString" break default: Abort "unknown mode" endswitch
According to that reasoning the following snippet has no newline before
for
andif
numEntries = ItemsInList(list) for(i = 0; i < numEntries; i += 1) // code endfor NVAR num = root:fancyNumber if(num < 5) // code endif
When mutiple end statements match
for(i = 0; i < numEntries; i += 1) // code if(i < 5) // code endif endfor
you should not add a trailing newline.
There is no whitespace between different flags of an operation and no whitespace around
=
if used in a flag assignment.Good:
Wave/Z/T/SDFR=dfr wv = myWave Function/S DoStuff() // code End
Bad:
Wave /Z /T /SDFR = dfr wv = myWave
The
&
in a call-by-reference parameter is attached to the nameGood:
Function DoStuff(variable &length, variable &height, variable &weight) // code End
Bad:
Function DoStuff(variable& length, variable & height, variable & weight) // code End
Line length should not exceed 120 characters
Use
camelCase
for variable/string/wave/dfref names andPascalCase
for functions and structuresPrefer structure-based GUI control procedures over old-style functions
The variables
i, j, k, l
are reserved for loop counters, from outer to inner loopsUse free waves for temporary waves as they are faster to work with
Prefer generic builtin functions like
IndexToScale
,DimSize
over their 1D counterpartspnt2x
,numpnts
.Avoid generic functions like
exists
and preferWaveExists
,NVAR_Exists
,SVAR_Exists
, etc.Write your code as much as possible without
SetDataFolder
. Properly document if your function expects a certain folder to be the current data folder at the time of the function call. Always restore the previously active current data folder before returning from the function.Although Igor Pro code is case-insensitive use the offical upper/lower case as shown in the Igor Pro Help files for better readability
Make/N=(10) data AppendToGraph/W=$graph data WAVE/Z wv SVAR sv = abcd STRUCT Rectangular rect print ItemsInList(list)
except for the following two cases:
variable storageCount string name
Variable and function definitions and references to them must also never vary in case
Don’t use variables for storing the result which is then returned
Good:
if(someCondition) // code return 0 else // code return 1 endif // if it is important to know that the returned value // is a status, name the function something like GetStatusForFoo // and/or use the @return doxygen comment for explaining its meaning
Bad:
variable status // code if(someCondition) // code status = 0 else // code status = 1 endif return status
Avoid commented out code
Don’t initialize variables and strings if not required and always initialize them on their own line.
Good:
variable i = 1 variable numEntries, maxLength string list
Bad:
variable i = 0, numEntries = ItemsInList(list), maxLength string list = ""
Don’t use the default value for an optional argument
Good:
StringFromList(0, list)
Bad:
StringFromList(0, list, ";")
Use parentheses sparingly
Good:
variable a = b * (1 + 2) if(a < b || a < c) // code endif
Bad:
variable a = (b * (1 + 2)) if((a < b) || (a < c)) // code endif
Use parentheses when combining operators with the same precedence
Good:
if((A || B) && C) // code endif if(A == (B >= C)) // code endif
Bad:
if(A || B && C) // same as above as these are left to right // code endif if(A == B >= C) // same as above as these are right to left // code endif
The reason is that remembering the exact associativity is too error-prone. See also
DisplayHelpTopic "Operators"
.Always add a space after
;
when multiple statements are written in one line. But in general this should be avoided if possible.With try/catch always clear runtime errors twice
try err = GetRTError(1) WAVE wv = I_DONT_EXIST; AbortOnRTE catch err = GetRTError(1) // handle error endtry
If you don’t clear it after
try
any still lingering runtime error will trigger an abort viaAbortOnRTE
and that results in difficult to diagnose bugs.Don’t mix
$
with other expressions as it makes the code too hard to readBad:
WAVE/Z wv = root:$(str + "_suffix")
Good:
string folder = str + "_suffix" WAVE/Z wv = root:$folder
See
DisplayHelpTopic "$ Precedence Issues In Commands"
for the details how$
works in complex expressions.Always add
break
statements in each branch ofswitch/strswitch
statements. If you intentionally fallthrough mark that by an explicit comment. No break should be added after control flow statements likereturn
,continue
,Abort
,AbortOnValue
.switch(state) case STATE_A: // do something break case STATE_B: // something else break case STATE_C: // fallthrough-by-design case STATE_D: // another thing break case STATE_E: return NaN default: // do nothing break endswitch
Don't exploit index clipping when indexing strings. Instead always check that the used index is valid before:
Good:
if(strlen(str) >= 2 && !CmpStr(str[1], "a")) // ... endif
Bad:
if(!CmpStr(str[1], "a")) // ... endif
In multidimensional wave assignments always specify the exact dimension for each value:
Make/N=(1,1,2) data = NaN data[0][0][] = 0
In this example data will be set to
0
for both values. Each dimension is specified: p and q are fixed to0
and both values in dimension r are set to0
.Make/N=(1,1,2) data = NaN data[0][0] = 0
In this example the output will be
0
andNaN
when using Igor Pro 7 or higher. In Igor Pro 6 the assignement will result in0
for both values. The deprecated behaviour can be enabled in later versions by setting an Igor Option:SetIgorOption FuncOptimize, WaveEqn = 1
Therefore to avoid confusing code always specify what value should go in which dimension (row, column, layer, chunk).
In large projects with a lot of wave getter functions it can be difficult to ensure that the numeric types for waves are always correct. Igor Pro does also not really help here, as the default type is
FP32
(single precision float) and there is no way to require wave type specifications by the compiler.Nevertheless it can be useful to specify the exact types for
Make
andWAVE
statements so that readers can verify that the correct types are used:Function/WAVE GetWave() Make/FREE/D someWave return someWave End WAVE/D wv = GetWave()
See also
DisplayHelpTopic "WAVE Reference Type Flags"
for a list of all possible wave type flags.
Use
SNAKE_CASE
for constantsStatic constants, which are required only in one file, should be defined at the top of the file
Global constants are collated in a single file
Explain magic numbers in a comment
static Constant DEFAULT_WAVE_SIZE = 128 // equals 2^8 which is // the width of the DAC signal
- Use Macros only for window recreation macros
- Try to avoid changing window recreation macros by hand. Write instead
a function to reset the panel to the default state and let Igor Pro
rewrite the macro by
DoWindow/R
. - Don't mix machine generated code with developer maintained code. It is therefore advisable to put window recreation macros into separate procedure files.
Try to keep their length below 50 lines (or half the screen height)
Use
PascalCase
for function names (optionally prefixed by aSNAKE_CASE
string ending in_
denoting the filename)Make them
static
if they are only required inside the same procedure fileDefine all variables at the top of the function body as in
Function CalculatePressure(variable weight, variable size) variable i, numEntries // code End
The reason for this rule is that there is no block-scope in Igor Pro, i. e.
if(someCondition) variable a = 4711 endif print a
is valid code. And that certainly will confuse people coming from C/C++.
Please also note that in the example above a blank line separates the function declaration from general variable definitions. This will improve readability.
Optional arguments should have defined default values:
Function DoCalculation(variable input, [variable verbose]) if(ParamIsDefault(verbose)) verbose = 0 endif // code End
Function Call with optional arguments:
DoCalculation(41, verbose = 1)
When calling a function, each argument is separated by a comma followed by a whitespace. Optional arguments are set with surrounding white spaces before and after the equal sign.
Boolean optional arguments should be forced to (0,1)
Function DoCalculation([variable overwrite]) overwrite = ParamIsDefault(overwrite) ? 0 : !!overwrite if(overwrite) // Some Code endif if(!overwrite) // Negation will work as expected endif End
Without the double negation statement none of the above
if
statements would get triggered ifoverwrite = NaN
as bothNaN
and!NaN
are false.This is also demonstrated in the following example
Function NaNisNotBool() if(NaN) print 0 elseif(!NaN) print 1 else print 2 endif End
If you don’t care about a function result, return
NaN
/""
/$""
Function Dostuff() if(!IsSomethingToDo()) return NaN endif // code End
The reason for this rule is that it makes the code easier to understand as these are the default return values (without multiple-return-value syntax) used by Igor Pro.
Set pass-by-reference parameters to a save default value immediately at the beginning of the function and after the variable declarations
Function Dostuff(variable ¶m) string str param = NaN if(!isSomethingToDo()) return NaN endif // code End
The reason is that all function return paths should return well-defined values in the returned pass-by-reference parameters. If the passed parameter is a structure, write a structure initialization function to handle setting it to a safe default and call that.
Be aware of the different initial values for return values when using multiple-return-value (MRS) syntax.
Function [variable var] New() // code End Function Old() // code End
The function
New()
returns0.0
whereasOld()
returnsNaN
.
When a functions that returns a free wave is called in an operation call or user-defined function call directly as parameter the free wave is not freed and thus causes a memory leak. To avoid these cases, free waves that are returned from functions must always be assigned to a wave reference variable.
Function/WAVE ReturnsFreeWave() Make/FREE wv return wv End Function LeakExample() Duplicate ReturnsFreeWave(), targetWave // Bad! Causes leak, DO NOT USE max = WaveMax(ReturnsFreeWave()) // Bad! Causes leak, DO NOT USE WAVE w = ReturnsFreeWave() // Good Duplicate w, targetWave WAVE w = ReturnsFreeWave() // Good max = WaveMax(w) End
Sometimes there are situations where the use of logical and bitwise operators gives technically the same result. However, if a function returns a true/false value, which in principal could be some abstract object then logical operators have to be used. This applies also for variables that use a value as flag to indicate a true/false state. This leaves bitwise operators for bit calculations that commonly include two or more bits.
Function IsFreeWave(WAVE wv) return WaveType(wv, 2) == 2 End Function DoStuff() variable truthValue = 1 variable bitValue = 0x10 Make/FREE wv // use logical operation if(IsFreeWave(wv) && truthValue) // do something endif bitValue = bitValue << 2 // Use bitwise operation print bitValue & 0x40 End
For historircal reasons developers can use unnamed user data, background functions and window hooks. But as these are limited to one instance, it is always more appropriate, and future-proof, to use the named variants instead.
Good:
SetWindow panel0 hook(cleanup)=DoPanelCleanup SetWindow panel0 userData(key)=abcd CtrlNamedBackground start, proc=DoStuffInBackground
Bad:
SetWindow panel0 hook=DoPanelCleanup SetWindow panel0 userData=abcd SetBackground DoStuffInBackground() CtrlBackground start
GUI control procedures are called for all Igor Pro events. Some of these events, like the mouse-over event, can be fired very often. To keep the GUI responsive, it is therefore much more performant to avoid executing any code outside the switch statement.
Good:
Function ButtonProcStart(STRUCT WMButtonAction &ba) : ButtonControl string method switch(ba.eventCode) case 2: // mouse up method = "fast" // ... break endswitch End
Bad:
Function ButtonProcStart(STRUCT WMButtonAction &ba) : ButtonControl string method = "slow" switch(ba.eventCode) case 2: // mouse up // ... break endswitch End
Functions can in expected situations raise runtime errors and the error condition is evaluated properly afterwards. When the Debugger is enabled and options are set to "Debug On Error", the Debugger will popup on the line where such functions calls take place. This is inconvenient for debugging because the error is intended and properly handled.
To prevent the debugger one can use the following pattern:
AssertOnAndClearRTError()
CriticalFunction(); err = GetRTError(1)
The second part that clears the RTE must be on the same line and can not be
moved to a separate function. This coding convention is only valid, if the
function is expected to raise a runtime error. The function
AssertOnAndClearRTError()
bugs out when a lingering RTE is present. Without
it, the call to GetRTError()
would silently clear any RTE, not just that
from CriticalFunction()
.
A try
/catch
/endtry
construct catches by specification either
- Runtime errors when
AbortOnRTE
is used - or Aborts
The code must take into account the possibility of runtime errors generated by bad code. These unexpected RTEs must not be silently cleared.
For the case, where an RTE is expected from CriticalFunction()
:
AssertOnAndClearRTError()
try
CriticalFunction(); AbortOnRTE
catch
err = GetRTError(1)
// ...
endtry
Here lingering RTEs are handled before try
. By convention AbortOnRTE
must be
placed in the same function as the try
/catch
/endtry
construct.
The code between try
/catch
should only include critical function calls and be
kept minimal. The expected RTE condition should be cleared directly after catch.
For the case, where an Abort is expected from CriticalFunction()
:
try
CriticalFunction()
catch
...
endtry
As Abort
/AbortOnValue
do not generate an RTE condition the
try
/catch
/endtry
construct leaves any possibly unexpected RTE
condition pending and no RTE condition is cleared. The developer can evaluate
V_AbortCode
in the catch
statement to check the type of abort.
It is mandatory to add a comment in the code before try
what the construct is
intended to handle (RTE, Abort or both).