Conditional expressions and related command-line options (--define, --conditionals)

1. Introduction

Pascal allows for directives in the source code. These look similar to comments and they contain commands for the compiler to do something special, like conditionaly ignore (or not ignore) a piece of following Pascal code.

An example:

type
  TProcess = class
  public
    property CommandLine: TStrings read GetCommandLine;
    procedure Run;
    {$ifdef MSWINDOWS}
    property Handle: TWindowHandle read GetHandle;
    {$endif}
    {$ifdef UNIX}
    property StdIn: TUnixHandle read GetStdIn write SetStdIn;
    property StdOut: TUnixHandle read GetStdOut write SetStdOut;
    property StdErr: TUnixHandle read GetStdErr write SetStdErr;
    {$endif}
  end;

Just like a compiler, PasDoc understands various directives when it parses Pascal code:

  • $ifdef, $if, $ifopt (and friends: $else, $endif, $ifend) - to conditionally use or ignore a piece of code.

  • $define, $undef - to define or undefine a symbol that can be used in $if defined(SYMBOL) / $ifdef expressions.

  • $include (short form $I) to include another file.

In contrast to a Pascal compiler, PasDoc starts with an empty list of conditional directives. For example, we don’t automatically define MSWINDOWS, even when you run PasDoc on Windows. The reason for this is that you usually want to generate one documentation that makes sense for all operating systems, all compiler versions and so on. It’s up to you to decide which symbols should be defined to achieve this.

You can tell PasDoc to have some symbol defined using the --define and --conditionals CommandLine options described below.

2. --define command-line option

--define SYMBOL (short form is -D SYMBOL) adds SYMBOL to the list of defined symbols. In effect, in Pascal code, {$ifdef SYMBOL} and (equivalent) {$if defined(SYMBOL)} will be considered true.

You can specify multiple symbols separated by a comma:

pasdoc --define DEBUG,FPC,MSWINDOWS myunit.pas

This defines three conditionals: DEBUG, FPC and MSWINDOWS.

You can also just use --define SYMBOL multiple times, like this:

pasdoc --define DEBUG --define FPC --define MSWINDOWS myunit.pas

3. --define command-line option with := to define a symbol with a value

You can use the assignment operator to define a symbol with a value, like this:

pasdoc --define FPC_FULLVERSION:=30202 --define LCL_FULLVERSION:=2020401 myunit.pas

This makes the given symbol behave like a macro, that expands to the given value, both during normal Pascal code parsing and during conditional expression evaluation in $if / $elseif.

This is useful to:

  • Define values for useful symbols used in conditional expressions, like:

    • Special FPC FPC_FULLVERSION symbol (available only in conditional expressions in FPC, though in PasDoc it will be also expanded during normal Pascal code parsing)

    • LCL_FULLVERSION defined in Lazarus LCLVersion unit

    • CompilerVersion constant from Delphi.

  • Or to define FPC macros values at command-line.

4. --conditionals command-line option

--conditionals SYMBOLS-FILE option (short form -d SYMBOLS-FILE) adds the symbols specified in a file SYMBOLS-FILE to the list of conditional symbols defined. The file must contain one symbol per line, without any comments.

Examples:

pasdoc --conditionals c:\sources\myconditionals.txt
pasdoc --conditionals /home/me/pascal/myconditionals.txt

where the myconditionals.txt file may contain, for example:

DEBUG
FPC
MSWINDOWS

5. Define symbols to make the code valid for PasDoc

When you use conditional directives in your code, make sure that the combination of symbols you define for PasDoc results in code that can be parsed by PasDoc. For example, if you have something like this:

const NewLine =
  {$ifdef MSWINDOWS} #13#10 {$endif}
  {$ifdef UNIX} #10 {$endif};

By default PasDoc defines neither MSWINDOWS nor UNIX, so it will consider them both undefined. Thus PasDoc will "see" (and fail to parse) a code like this:

const NewLine = ;

You have to make sure that the combination of symbols used by PasDoc makes sense, i.e. results in code that can be parsed. Sometimes the right solution is to introduce a special variant, used only when parsing with PasDoc:

const NewLine =
  {$ifdef PASDOC}
    'The value of this constant depends on the operating system'
  {$else}
    {$ifdef MSWINDOWS} #13#10 {$endif}
    {$ifdef UNIX} #10 {$endif}
  {$endif};

Make sure to execute PasDoc with command-line option -define PASDOC to make it work.

6. Support for $if expressions

The $if directive allows to evaluate an expression, like

{$if defined(MSWINDOWS) and not defined(FPC)}
const CompilerInfo = 'Delphi on Windows';
{$endif}

Most of $if features supported by compilers (like FPC or Delphi) are supported. This includes:

  • Functions defined(SYMBOL), undefined(SYMBOL), option(R+)

  • Constants false, true

  • Composing the expression using and, or, not, xor operations

  • Comparing (Booleans and Integers) using =

  • Addition, multiplication operatoers.

Some expressions remain not supported, ultimately because PasDoc is not a compiler (so we don’t have the knowledge about the units you used, like RTL; and we don’t decide what are OS / CPU parameters). We don’t support:

  • declared(…​)

  • SizeOf(…​)

The solution is to use a special symbol, like PASDOC, to make sure that the expression is valid for PasDoc. For example:

const
  { Integer of the same size as Pointer.
    @deprecated Use NativeUInt (Delphi) or PtrUInt (FPC). }
  MyPointerInt =
    {$if defined(PASDOC)}
      UIntSystemDependent
    {$elseif SizeOf(Pointer) = 8}
      UInt64
    {$else}
      UInt32
    {$endif};