DEV Community

CrabPascal
CrabPascal

Posted on

Recursive Unit Loading Without Hardcode | Carregamento recursivo de units sem hardcode

Bilingual post · Post bilíngue

Jump to: English · Português


English {#english}

Recursive Unit Loading Without Hardcode

Early Pascal runtimes often cheat: a giant match unit_name { "SysUtils" => ..., "Classes" => ... } table baked into the engine. That works for demos and fails for real Delphi projects with dozens of interdependent units. CrabPascal v2.0.1 introduced recursive unit loading — discover files from search paths, parse them, follow uses, and cache results. No hardcoded library list.

Goals

  • Load units from .dproj / project search paths
  • Resolve dependencies recursively
  • Detect circular uses via cache
  • Preprocess when directives are present
  • Parse and execute real source — not stubs

Architecture sketch

The runtime keeps a map of already-loaded units:

pub struct CompletePascalRuntime {
    loaded_units: HashMap<String, Unit>,
    // ...
}
Enter fullscreen mode Exit fullscreen mode

Before parsing, check the cache:

if self.loaded_units.contains_key(unit) {
    return Ok(()); // already loaded — breaks cycles
}
Enter fullscreen mode Exit fullscreen mode

Recursive load flow

  1. Resolve pathProjectConfig::find_unit_file searches configured directories.
  2. Read and preprocess — if {$IFDEF} appears, run the preprocessor first.
  3. Parse — build AST; tolerate some parse errors by caching empty shells to continue.
  4. Insert into cache before dependencies — prevents infinite recursion on circular uses.
  5. Walk interface uses — call load_unit for each dependency not yet cached.
unit DataLayer;

interface

uses
  System.SysUtils,
  System.Classes,
  Models;

implementation

initialization
  // ...
end.
Enter fullscreen mode Exit fullscreen mode

Each name in uses triggers the same pipeline — Models loads before DataLayer body runs.

Zero hardcode philosophy

The old approach:

match unit {
    "System.SysUtils" => inject_builtin_sysutils(),
    "Horse" => inject_horse_stub(),
    _ => ignore,
}
Enter fullscreen mode Exit fullscreen mode

The new approach: find a .pas file or treat as built-in intrinsics, never maintain a hand-curated list of every third-party framework.

Built-ins still exist for core intrinsics (WriteLn, etc.), but framework units like Horse ship as real .pas files under rtl/ or your project tree.

Preprocessor integration

Units with conditional compilation preprocess before parse:

{$IFDEF DEBUG}
  WriteLn('Debug build');
{$ELSE}
  WriteLn('Release build');
{$ENDIF}
Enter fullscreen mode Exit fullscreen mode

This matches how Delphi developers structure cross-platform code.

Failure modes (honest)

  • Missing unit on disk → logged, may skip (if optional) or fail at reference site
  • Parse error in dependency → unit cached as empty; downstream may error clearly
  • Wrong search path → fix .dproj or crab-pascal project config

Try it

crab-pascal run examples/crud/crud.dpr
Enter fullscreen mode Exit fullscreen mode

crud.dpr pulls ProdutoService, Horse, System.JSON — all resolved through search paths, not magic names.

Why this matters for CrabPascal

Delphi compatibility is not only grammar — it is project topology. Recursive loading lets the same runtime execute small scripts and multi-unit REST services. Later sprints improved namespaces (rtl/sys) and resolver rules; v2.0.1 laid the graph-walking foundation everything else depends on.

Deep dive: architecture/carregamento-recursivo-v2-0-1 in project docs.


Português {#portugus}

Carregamento recursivo de units sem hardcode

Runtimes Pascal antigos costumam trapacear: uma tabela gigante match unit_name { "SysUtils" => ..., "Classes" => ... } no motor. Funciona para demos e falha em projetos Delphi reais com dezenas de units interdependentes. O CrabPascal v2.0.1 introduziu carregamento recursivo de units — descobrir arquivos nos search paths, parsear, seguir uses e cachear. Sem lista hardcoded de bibliotecas.

Objetivos

  • Carregar units de search paths (via .dproj)
  • Resolver dependências recursivamente
  • Detectar uses circular via cache
  • Preprocessar quando há diretivas
  • Parsear e executar código real — não stubs

Esboço de arquitetura

O runtime mantém mapa de units já carregadas:

pub struct CompletePascalRuntime {
    loaded_units: HashMap<String, Unit>,
    // ...
}
Enter fullscreen mode Exit fullscreen mode

Antes de parsear, consulta o cache:

if self.loaded_units.contains_key(unit) {
    return Ok(()); // já carregada — quebra ciclos
}
Enter fullscreen mode Exit fullscreen mode

Fluxo recursivo

  1. Resolver pathProjectConfig::find_unit_file busca nos diretórios configurados.
  2. Ler e preprocessar — se aparecer {$IFDEF}, roda o preprocessor antes.
  3. Parsear — monta AST; tolera alguns erros cacheando shells vazios para continuar.
  4. Inserir no cache antes das dependências — evita recursão infinita em uses circular.
  5. Percorrer uses da interface — chama load_unit para cada dependência ainda não cacheada.
unit DataLayer;

interface

uses
  System.SysUtils,
  System.Classes,
  Models;

implementation

initialization
  // ...
end.
Enter fullscreen mode Exit fullscreen mode

Cada nome em uses dispara o mesmo pipeline — Models carrega antes do body de DataLayer.

Filosofia zero hardcode

Abordagem antiga:

match unit {
    "System.SysUtils" => inject_builtin_sysutils(),
    "Horse" => inject_horse_stub(),
    _ => ignore,
}
Enter fullscreen mode Exit fullscreen mode

Abordagem nova: achar arquivo .pas ou tratar como intrinsics built-in, nunca manter lista manual de cada framework terceiro.

Built-ins continuam para intrinsics core (WriteLn, etc.), mas units como Horse vivem como .pas reais em rtl/ ou na árvore do projeto.

Integração com preprocessor

Units com compilação condicional preprocessam antes do parse:

{$IFDEF DEBUG}
  WriteLn('Build debug');
{$ELSE}
  WriteLn('Build release');
{$ENDIF}
Enter fullscreen mode Exit fullscreen mode

Igual ao que desenvolvedores Delphi usam em código multiplataforma.

Modos de falha (honestos)

  • Unit ausente no disco → log, pode ignorar (se opcional) ou falhar no uso
  • Erro de parse na dependência → unit cacheada vazia; downstream pode erro claro
  • Search path errado → corrigir .dproj ou config do crab-pascal

Experimente

crab-pascal run examples/crud/crud.dpr
Enter fullscreen mode Exit fullscreen mode

crud.dpr puxa ProdutoService, Horse, System.JSON — tudo resolvido por search paths, não por nomes mágicos.

Por que importa

Compatibilidade Delphi não é só gramática — é topologia de projeto. Carregamento recursivo permite o mesmo runtime executar scripts pequenos e serviços REST multi-unit. Sprints posteriores melhoraram namespaces (rtl/sys) e regras do resolver; o v2.0.1 pôs a base de caminhar no grafo que tudo mais usa.

Detalhes: architecture/carregamento-recursivo-v2-0-1 na documentação.


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

Top comments (0)