English {#english}
Real Diagnostic Spans in CrabPascal v2.9.9
If you have ever fixed a typo in Delphi and watched the IDE underline line 1 while the real mistake sits on line 47, you know why source locations matter. CrabPascal Sprint 1 (release v2.9.9) focused entirely on that problem: making check report real line and column numbers for parse errors.
Before this release, a missing begin could show up as 1:1. After v2.9.9, the same file reports the exact token — for example 3:1 — which is what IDEs, CI logs, and humans actually need.
What changed under the hood
The fix lives in three layers:
-
span.rs— converts byte offsets (UTF-8) into 1-based line/column pairs. -
Lexer — stores
last_token_spanon the token already emitted, not on a peeked lookahead. -
Parser — propagates
ParseError { line, column }and respectsquiet_diagnosticssocheckstays silent on success.
The peek-vs-current distinction sounds subtle, but it is the difference between "error near end" and "error at the end you just typed."
Try it yourself
Create a file with a deliberate syntax error:
program BadBegin;
WriteLn('hello');
end.
Notice the missing begin. Run:
crab-pascal check BadBegin.pas
Expected output shape:
BadBegin.pas:2:3: error: expected 'begin' but found identifier 'WriteLn'
The column points at WriteLn, not at the top of the file.
IDE integration
The VS Code / Cursor extension uses the same format. Problem matchers parse file:line:column: error: so squiggles land on the right character. That makes check usable as a pre-commit hook or CI gate without reformatting compiler output.
What Sprint 1 did not cover
Parse diagnostics are solid; semantic errors (undefined symbols, type mismatches) still pointed to 1:1 in early sprints. That was intentional scope control — Sprint 1 shipped spans for syntax, and later sprints extended the same machinery to semantic analysis.
Golden tests in tests/check_diagnostics.rs lock ten edge cases, including Windows paths like C:\projects\unit.pas where naive split(':') parsing would break.
Why Rust helps here
CrabPascal is written in Rust. Spans are plain structs carried through the lexer and parser without garbage-collection surprises. The check command exits non-zero on failure, which plays nicely with shell scripts:
crab-pascal check src/*.pas && echo "clean"
Takeaway
Good compiler UX starts before codegen — it starts at honest locations. v2.9.9 is a small version bump with a large daily impact: faster debugging, cleaner CI, and trust in the toolchain.
Next up: Sprint 2 brings System.* namespaces so your uses clauses look like modern Delphi again.
Português {#portugus}
Diagnósticos reais no CrabPascal v2.9.9
Quem já corrigiu um typo no Delphi e viu o IDE sublinhar a linha 1 enquanto o erro real está na linha 47 sabe por que localização no código-fonte importa. A Sprint 1 do CrabPascal (release v2.9.9) focou exatamente nisso: fazer o check reportar linha e coluna reais em erros de parse.
Antes desta release, um begin faltando podia aparecer como 1:1. Depois da v2.9.9, o mesmo arquivo aponta o token exato — por exemplo 3:1 — que é o que IDEs, logs de CI e humanos precisam.
O que mudou por baixo dos panos
A correção vive em três camadas:
-
span.rs— converte offsets de byte (UTF-8) em pares linha/coluna base 1. -
Lexer — guarda
last_token_spanno token já emitido, não no lookahead de peek. -
Parser — propaga
ParseError { line, column }e respeitaquiet_diagnosticspara ocheckficar silencioso em sucesso.
A distinção peek vs current parece sutil, mas é a diferença entre "erro perto de end" e "erro no end que você acabou de digitar".
Experimente
Crie um arquivo com erro de sintaxe proposital:
program BadBegin;
WriteLn('hello');
end.
Repare no begin ausente. Execute:
crab-pascal check BadBegin.pas
Formato esperado:
BadBegin.pas:2:3: error: expected 'begin' but found identifier 'WriteLn'
A coluna aponta para WriteLn, não para o topo do arquivo.
Integração com IDE
A extensão VS Code / Cursor usa o mesmo formato. Problem matchers interpretam arquivo:linha:coluna: error: para posicionar os squiggles no caractere certo. Isso torna o check utilizável como hook de pre-commit ou gate de CI sem reformatar a saída do compilador.
O que a Sprint 1 não cobriu
Diagnósticos de parse estão sólidos; erros semânticos (símbolos indefinidos, incompatibilidade de tipos) ainda apontavam para 1:1 nas sprints iniciais. Foi controle de escopo intencional — a Sprint 1 entregou spans para sintaxe, e sprints posteriores estenderam a mesma maquinaria à análise semântica.
Testes golden em tests/check_diagnostics.rs travam dez casos de borda, incluindo paths Windows como C:\projects\unit.pas, onde parsing ingênuo com split(':') quebraria.
Por que Rust ajuda
O CrabPascal é escrito em Rust. Spans são structs simples carregadas pelo lexer e parser sem surpresas de garbage collection. O comando check sai com código não zero em falha, o que combina bem com scripts:
crab-pascal check src/*.pas && echo "limpo"
Conclusão
Boa UX de compilador começa antes do codegen — começa em localizações honestas. A v2.9.9 é um bump de versão pequeno com impacto diário grande: debug mais rápido, CI mais limpo e confiança na toolchain.
A seguir: a Sprint 2 traz namespaces System.* para seus uses parecerem Delphi moderno de novo.
Published on dev.to/@crabpascal · Código em CrabPascal
Top comments (0)