DEV Community

CrabPascal
CrabPascal

Posted on

Preprocessor: IFDEF and Conditional Compilation | Preprocessor: IFDEF e compilação condicional

Bilingual post · Post bilíngue

Jump to: English · Português


English {#english}

Preprocessor: IFDEF and Conditional Compilation

Delphi projects rarely compile as a single flat source file. {$IFDEF}, {$DEFINE}, {$INCLUDE}, and friends select platform code, toggle debug logging, and stitch units together. CrabPascal ships a line-oriented preprocessor (since v1.5.2, refined through v2.x) integrated into check, run, and unit loading.

Why it matters

Without preprocessing, this compiles on Windows in Delphi but fails elsewhere — or worse, includes dead code:

{$DEFINE DEBUG}
{$IFDEF MSWINDOWS}
  uses Winapi.Windows;
{$ELSE}
  uses Posix.Unistd;
{$ENDIF}

procedure Log(const Msg: string);
begin
{$IFDEF DEBUG}
  WriteLn('[DEBUG] ', Msg);
{$ENDIF}
end;
Enter fullscreen mode Exit fullscreen mode

CrabPascal evaluates directives before lexing the main program, matching Delphi's compilation model.

Supported directives (high level)

From project changelog and src/preprocessor/:

  • {$DEFINE} / {$UNDEF} — symbol table
  • {$IFDEF} / {$IFNDEF} / {$ELSE} / {$ENDIF} — conditional blocks
  • {$IF} — numeric/symbolic conditions (basic support)
  • {$INCLUDE} — text inclusion
  • {$REGION} / {$ENDREGION} — editor folding (preserved/stripped)

Nested blocks work for typical patterns; deeply nested {$ELSEIF} chains remain an improvement area noted in architecture docs.

CLI usage

Inspect preprocessor output without running:

crab-pascal preproc MyUnit.pas
Enter fullscreen mode Exit fullscreen mode

Symbols can come from source or config. Project TOML supports a [preprocessor] section:

[preprocessor]
enabled = true
# symbols = ["DEBUG", "MSWINDOWS"]
Enter fullscreen mode Exit fullscreen mode

Disable when debugging parser issues in isolation:

[preprocessor]
enabled = false
Enter fullscreen mode Exit fullscreen mode

Integration with unit loading

When the recursive loader reads a .pas file containing {$, it runs the preprocessor and parses processed source. That keeps conditional uses clauses consistent:

uses
  System.SysUtils,
{$IFDEF USE_JSON}
  System.JSON,
{$ENDIF}
  Horse;
Enter fullscreen mode Exit fullscreen mode

Practical tips

  1. Prefer explicit {$DEFINE} in project config for CI — same flags on Linux agents and Windows dev machines.
  2. Use preproc to diff output when porting a large Delphi unit; surprises usually mean a symbol mismatch.
  3. Combine with check — semantic errors after preprocessing are real errors, not lexer ghosts.

Limitations (honest)

CrabPascal is not FPC nor DCC; exotic directives ({$MESSAGE}, compiler-specific pragmas) may pass through or warn. Report minimal repros — preprocessor bugs are fixed with golden file tests.

Real-world pattern: feature flags

Libraries often gate experimental APIs behind symbols defined in the project file or CI:

{$DEFINE CRABPASCAL_EXPERIMENTAL}
{$IFDEF CRABPASCAL_EXPERIMENTAL}
  procedure FastPath(const Data: TBytes);
{$ENDIF}
Enter fullscreen mode Exit fullscreen mode

Define CRABPASCAL_EXPERIMENTAL in TOML for nightly builds; omit it for stable runs. The same source compiles both ways — exactly how Delphi teams ship gradual rollouts.

Debugging workflow

When a unit fails only on CI:

  1. Run crab-pascal preproc FailingUnit.pas > local.out
  2. Compare with developer machine output
  3. Diff symbol tables — missing MSWINDOWS or DEBUG is the usual culprit

The preprocessor runs in src/preprocessor/mod.rs with line-by-line evaluation (rewritten in v2.x for predictability). Errors reference the original line numbers when possible, so IDE jump-to-error still works after expansion.

Next steps

Read usage/configuracao for full TOML keys. Follow @crabpascal for sprint updates when {$ELSEIF} nesting and FPC parity improve. The preprocessor is the door most Delphi code walks through first; getting it right multiplies everything downstream.


Português {#portugus}

Preprocessor: IFDEF e compilação condicional

Projetos Delphi raramente compilam como arquivo único. {$IFDEF}, {$DEFINE}, {$INCLUDE} e similares escolhem código por plataforma, ligam log de debug e montam units. O CrabPascal traz preprocessor linha a linha (desde v1.5.2, refinado na v2.x) integrado em check, run e carregamento de units.

Por que importa

Sem preprocessamento, isto compila no Windows em Delphi mas falha em outro lugar — ou pior, inclui código morto:

{$DEFINE DEBUG}
{$IFDEF MSWINDOWS}
  uses Winapi.Windows;
{$ELSE}
  uses Posix.Unistd;
{$ENDIF}

procedure Log(const Msg: string);
begin
{$IFDEF DEBUG}
  WriteLn('[DEBUG] ', Msg);
{$ENDIF}
end;
Enter fullscreen mode Exit fullscreen mode

O CrabPascal avalia diretivas antes de lexar o programa principal, alinhado ao modelo Delphi.

Diretivas suportadas (visão geral)

Do changelog e src/preprocessor/:

  • {$DEFINE} / {$UNDEF} — tabela de símbolos
  • {$IFDEF} / {$IFNDEF} / {$ELSE} / {$ENDIF} — blocos condicionais
  • {$IF} — condições numéricas/simbólicas (suporte básico)
  • {$INCLUDE} — inclusão de texto
  • {$REGION} / {$ENDREGION} — folding de editor (preservado/removido)

Blocos aninhados funcionam em padrões típicos; cadeias profundas de {$ELSEIF} seguem como área de melhoria na documentação de arquitetura.

Uso na CLI

Inspecione saída do preprocessor sem executar:

crab-pascal preproc MyUnit.pas
Enter fullscreen mode Exit fullscreen mode

Símbolos vêm do fonte ou config. TOML do projeto tem seção [preprocessor]:

[preprocessor]
enabled = true
# symbols = ["DEBUG", "MSWINDOWS"]
Enter fullscreen mode Exit fullscreen mode

Desabilite ao debugar parser isolado:

[preprocessor]
enabled = false
Enter fullscreen mode Exit fullscreen mode

Integração com carregamento de units

Quando o loader recursivo lê .pas com {$, roda preprocessor e parseia fonte processada. Mantém cláusulas uses condicionais consistentes:

uses
  System.SysUtils,
{$IFDEF USE_JSON}
  System.JSON,
{$ENDIF}
  Horse;
Enter fullscreen mode Exit fullscreen mode

Dicas práticas

  1. Prefira {$DEFINE} explícito na config para CI — mesmas flags em agentes Linux e máquinas Windows.
  2. Use preproc para diff ao portar unit Delphi grande; surpresas costumam ser símbolo faltando.
  3. Combine com check — erros semânticos pós-preprocessamento são erros reais, não fantasmas do lexer.

Limitações (honestas)

CrabPascal não é FPC nem DCC; diretivas exóticas ({$MESSAGE}, pragmas específicos) podem passar ou avisar. Reporte repro mínimo — bugs de preprocessor ganham testes golden.

Padrão real: feature flags

Bibliotecas costumam isolar APIs experimentais atrás de símbolos no projeto ou CI:

{$DEFINE CRABPASCAL_EXPERIMENTAL}
{$IFDEF CRABPASCAL_EXPERIMENTAL}
  procedure FastPath(const Data: TBytes);
{$ENDIF}
Enter fullscreen mode Exit fullscreen mode

Defina CRABPASCAL_EXPERIMENTAL no TOML para builds nightly; omita em runs estáveis. O mesmo fonte compila dos dois jeitos — como times Delphi fazem rollouts graduais.

Fluxo de debug

Quando uma unit falha só no CI:

  1. Rode crab-pascal preproc UnitComFalha.pas > local.out
  2. Compare com saída da máquina do dev
  3. Diff tabelas de símbolos — falta de MSWINDOWS ou DEBUG é culpado usual

O preprocessor roda em src/preprocessor/mod.rs com avaliação linha a linha (reescrito na v2.x para previsibilidade). Erros referenciam linhas originais quando possível, então jump-to-error na IDE continua após expansão.

Próximos passos

Leia usage/configuracao para chaves TOML completas. Siga @crabpascal para updates de sprint em aninhamento {$ELSEIF} e paridade FPC. O preprocessor é a porta por onde a maior parte do código Delphi entra; acertar aqui multiplica tudo downstream.


Published on dev.to/@crabpascal · Código em CrabPascal

Top comments (0)