DEV Community

CrabPascal
CrabPascal

Posted on

Parser Hardening: Lessons from Sprint 11 | Parser mais robusto: lições do Sprint 11

Bilingual post · Post bilíngue

Jump to: English · Português


English {#english}

Parser Hardening: Lessons from Sprint 11

Real Delphi code rarely looks like textbook Pascal. A single line can declare three fields, implement two interfaces, or chain a generic call into a property access. CrabPascal v2.19.0 (Sprint 11) focused on exactly these patterns — the small syntactic details that make or break compatibility with existing projects.

Why parser hardening matters

When you port a Delphi unit to CrabPascal, the first failure is often not semantics or runtime — it is the parser silently dropping information. If FX, FY: Integer becomes only FX, later semantic errors look random. If TList<T>.Create skips the dot after >, the rest of the statement parses incorrectly.

Sprint 11 treated these as first-class bugs, not edge cases.

Multi-field declarations

Delphi allows comma-separated field lists in both records and classes:

type
  TPoint = record
    X, Y: Integer;
  end;

  TWidget = class
  private
    FWidth, FHeight: Integer;
  public
    property Width: Integer read FWidth write FWidth;
  end;
Enter fullscreen mode Exit fullscreen mode

Before v2.19.0, the AST might keep only the first name. The fix ensures each identifier becomes its own field node. Downstream semantic analysis and codegen then see the full shape Delphi expects.

Interfaces in class declarations

Multiple interface implementation is common in Delphi services and data modules:

type
  TService = class(TInterfacedObject, IAuth, IAudit)
  public
    procedure Login; stdcall;
  end;
Enter fullscreen mode Exit fullscreen mode

The parser now populates an interfaces list on the class AST, separate from the base class. Runtime support for COM-style interfaces is still evolving, but the front end no longer loses this structure at parse time.

The generic dot bug

One critical fix: after parsing Something<T>, the lexer must not consume the following . as part of the generic closing token.

var
  L: TList<Integer>;
begin
  L := TList<Integer>.Create;
  L.Add(42);
end;
Enter fullscreen mode Exit fullscreen mode

This pattern appears constantly in modern Delphi. Losing the dot broke method calls on generic types and produced confusing diagnostics three tokens later.

out parameters

The lexer gained an out token; parameters declared out are treated like var in the current runtime — enough for many existing signatures:

procedure Split(const S: string; out Left, Right: string);
Enter fullscreen mode Exit fullscreen mode

Full Delphi out initialization semantics can come later; parsing them correctly unblocks compilation today.

GenericInstance compatibility

Semantic analysis now treats GenericInstance as compatible with itself, eliminating false type-mismatch errors when the same instantiated generic flows through multiple checks.

Tests as documentation

Sprint 11 added tests/parser_hardening.rs and fixed a race in build parity tests by using unique temporary files. Parser work without regression tests tends to regress — these fixtures encode the lessons above.

Takeaway

Parser hardening is unglamorous but foundational. Every sprint that improves AST fidelity reduces mystery errors downstream. If you hit a parse edge case, open an issue at bitbucket.org/alphatecnologia/crabpascal with a minimal .pas repro — we turn those into permanent tests.


Português {#portugus}

Parser mais robusto: lições do Sprint 11

Código Delphi real raramente parece Pascal de livro didático. Uma linha pode declarar três fields, implementar duas interfaces ou encadear uma chamada genérica com acesso a property. O CrabPascal v2.19.0 (Sprint 11) focou exatamente nesses padrões — detalhes sintáticos pequenos que decidem a compatibilidade com projetos existentes.

Por que fortalecer o parser importa

Ao portar uma unit Delphi para o CrabPascal, a primeira falha costuma não ser semântica ou runtime — é o parser descartando informação silenciosamente. Se FX, FY: Integer vira só FX, erros semânticos depois parecem aleatórios. Se TList<T>.Create pula o ponto após >, o resto da instrução parseia errado.

O Sprint 11 tratou isso como bug de primeira classe, não caso raro.

Declarações multi-field

Delphi permite listas de fields separadas por vírgula em records e classes:

type
  TPoint = record
    X, Y: Integer;
  end;

  TWidget = class
  private
    FWidth, FHeight: Integer;
  public
    property Width: Integer read FWidth write FWidth;
  end;
Enter fullscreen mode Exit fullscreen mode

Antes do v2.19.0, a AST podia guardar só o primeiro nome. A correção garante que cada identificador vire um nó de field. Análise semântica e codegen enxergam a forma completa que o Delphi espera.

Interfaces em declarações de classe

Implementação múltipla de interfaces é comum em serviços e data modules:

type
  TService = class(TInterfacedObject, IAuth, IAudit)
  public
    procedure Login; stdcall;
  end;
Enter fullscreen mode Exit fullscreen mode

O parser agora preenche uma lista interfaces na AST da classe, separada da classe base. Suporte runtime a interfaces estilo COM ainda evolui, mas o front-end não perde mais essa estrutura no parse.

O bug do ponto em genéricos

Correção crítica: após parsear Something<T>, o lexer não pode consumir o . seguinte como parte do fechamento do genérico.

var
  L: TList<Integer>;
begin
  L := TList<Integer>.Create;
  L.Add(42);
end;
Enter fullscreen mode Exit fullscreen mode

Esse padrão aparece o tempo todo em Delphi moderno. Perder o ponto quebrava chamadas de método em tipos genéricos e gerava diagnósticos confusos três tokens depois.

Parâmetros out

O lexer ganhou token out; parâmetros declarados out são tratados como var no runtime atual — suficiente para muitas assinaturas existentes:

procedure Split(const S: string; out Left, Right: string);
Enter fullscreen mode Exit fullscreen mode

Semântica completa de inicialização out do Delphi pode vir depois; parsear corretamente já destrava compilação hoje.

Compatibilidade GenericInstance

A semântica agora trata GenericInstance como compatível consigo mesmo, eliminando falsos type mismatch quando o mesmo genérico instanciado passa por várias checagens.

Testes como documentação

O Sprint 11 adicionou tests/parser_hardening.rs e corrigiu race nos testes de paridade de build com arquivos temporários únicos. Trabalho de parser sem regressão tende a regredir — esses fixtures codificam as lições acima.

Conclusão

Fortalecer o parser não é glamouroso, mas é fundacional. Cada sprint que melhora a fidelidade da AST reduz erros misteriosos downstream. Se encontrar um edge case de parse, abra issue em bitbucket.org/alphatecnologia/crabpascal com um .pas mínimo — transformamos isso em teste permanente.


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

Top comments (0)