English {#english}
CrabPascal Lexer: How Source Becomes Tokens
Before CrabPascal can parse a program or report a type error, it must read plain text and slice it into meaningful units called tokens. The lexer (scanner) is phase one of the compiler pipeline — fast, deterministic, and surprisingly subtle when Delphi syntax enters the picture.
What is a token?
A token is the smallest meaningful chunk of source code. Consider:
var count: Integer := 42;
The lexer produces something like:
VAR IDENT("count") COLON IDENT("Integer") ASSIGN INTEGER(42) SEMICOLON
Each token carries a kind (keyword, identifier, literal) and often span metadata — start line, start column, end position — used for diagnostics:
Unit1.pas:3:15: error: Expected ';' but found ':='
That precision comes from the lexer tracking positions as it scans.
Token categories in CrabPascal
| Category | Examples |
|---|---|
| Keywords |
program, begin, end, procedure, class
|
| Identifiers |
MyUnit, TProdutoService, WriteLn
|
| Literals |
42, 3.14, 'hello', #13#10
|
| Operators |
:=, +, <>, <=, @
|
| Delimiters |
(, ), [, ], ., ;, ,
|
| Directives |
{$IFDEF}, {$APPTYPE CONSOLE}
|
Delphi is case-insensitive for identifiers but case-sensitive for certain directives — the lexer normalizes where appropriate.
Comments and whitespace
The lexer discards whitespace and skips comments without emitting tokens:
// Line comment — ignored
{ Block comment — ignored }
(* Old-style comment — ignored *)
var x: Integer; (* inline *)
Nested { } comments in Delphi style are handled correctly — a common lexer bug in hobby compilers that CrabPascal avoids.
String literals and escape sequences
Pascal strings use single quotes with doubled quotes for embedding:
msg := 'It''s CrabPascal v2.22.0';
The lexer reads from opening ' to closing ', producing a STRING_LITERAL token with the unescaped value. Control characters via # notation (#13#10 for CRLF) become separate char literals or combined string tokens depending on context.
Numbers
a := 100; // INTEGER
b := 3.14159; // FLOAT
c := $FF; // HEX_INTEGER
d := 1e6; // SCIENTIFIC (where supported)
The lexer distinguishes integer and floating formats early so the parser does not need to re-inspect raw text.
Preprocessor interaction
When preproc runs or preprocessor is enabled in crabpascal.toml, directives like {$IFDEF DEBUG} may be processed before or during lexing depending on configuration. Conditional blocks remove source branches entirely — the parser never sees disabled code.
{$IFDEF CRABPASCAL}
WriteLn('Running on CrabPascal');
{$ENDIF}
Define CRABPASCAL in config:
[compiler]
defines = ["CRABPASCAL"]
Debugging the lexer
Enable verbose output or inspect tokens indirectly:
crab-pascal parse MyUnit.pas # AST implies successful tokenization
crab-pascal check MyUnit.pas # Lexer errors surface first
Common lexer failures:
- Unclosed string or comment
- Invalid character in source (Unicode edge cases)
- Malformed directive syntax
Why Rust for lexing?
CrabPascal's lexer uses Rust iterators and explicit state machines — no unsafe pointer arithmetic. Buffer overreads are impossible by construction. Performance is more than adequate: lexing a 10,000-line unit takes milliseconds.
The lexer looks simple but anchors everything downstream. Bad tokens mean bad ASTs; bad ASTs mean confusing semantic errors. CrabPascal invests in correct scanning so check diagnostics point to the real problem — not a cascade of parser noise.
Next up: what the parser does with these tokens. 🦀
Português {#portugus}
Lexer do CrabPascal: do fonte aos tokens
Antes do CrabPascal parsear um programa ou reportar erro de tipo, precisa ler texto puro e fatiá-lo em unidades significativas chamadas tokens. O lexer (scanner) é a fase um do pipeline — rápido, determinístico e surpreendentemente sutil quando entra sintaxe Delphi.
O que é um token?
Token é o menor pedaço significativo de código-fonte. Considere:
var count: Integer := 42;
O lexer produz algo como:
VAR IDENT("count") COLON IDENT("Integer") ASSIGN INTEGER(42) SEMICOLON
Cada token carrega um tipo (keyword, identificador, literal) e metadados de span — linha, coluna, posição final — usados nos diagnósticos:
Unit1.pas:3:15: error: Expected ';' but found ':='
Essa precisão vem do lexer rastrear posições enquanto escaneia.
Categorias de tokens no CrabPascal
| Categoria | Exemplos |
|---|---|
| Keywords |
program, begin, end, procedure, class
|
| Identificadores |
MyUnit, TProdutoService, WriteLn
|
| Literais |
42, 3.14, 'hello', #13#10
|
| Operadores |
:=, +, <>, <=, @
|
| Delimitadores |
(, ), [, ], ., ;, ,
|
| Diretivas |
{$IFDEF}, {$APPTYPE CONSOLE}
|
Delphi é case-insensitive para identificadores, mas case-sensitive em certas diretivas — o lexer normaliza onde cabe.
Comentários e whitespace
O lexer descarta whitespace e pula comentários sem emitir tokens:
// Comentário de linha — ignorado
{ Comentário de bloco — ignorado }
(* Comentário antigo — ignorado *)
var x: Integer; (* inline *)
Comentários { } aninhados estilo Delphi são tratados corretamente — bug comum em compiladores hobby que o CrabPascal evita.
Literais string e escape
Strings Pascal usam aspas simples com aspas duplicadas para embutir:
msg := 'It''s CrabPascal v2.22.0';
O lexer lê da ' inicial à final, produzindo token STRING_LITERAL com valor desescapado. Caracteres de controle via # (#13#10 para CRLF) viram char literals ou tokens de string combinados conforme contexto.
Números
a := 100; // INTEGER
b := 3.14159; // FLOAT
c := $FF; // HEX_INTEGER
d := 1e6; // SCIENTIFIC (onde suportado)
O lexer distingue inteiros e ponto flutuante cedo — o parser não reinspeciona texto bruto.
Interação com pré-processador
Quando preproc roda ou o pré-processador está habilitado no crabpascal.toml, diretivas como {$IFDEF DEBUG} podem ser processadas antes ou durante a lexação. Blocos condicionais removem ramos do fonte — o parser nunca vê código desabilitado.
{$IFDEF CRABPASCAL}
WriteLn('Rodando no CrabPascal');
{$ENDIF}
Defina CRABPASCAL na config:
[compiler]
defines = ["CRABPASCAL"]
Depurando o lexer
Habilite saída verbose ou inspecione tokens indiretamente:
crab-pascal parse MyUnit.pas # AST implica tokenização ok
crab-pascal check MyUnit.pas # Erros de lexer aparecem primeiro
Falhas comuns:
- String ou comentário não fechado
- Caractere inválido (casos Unicode)
- Sintaxe de diretiva malformada
Por que Rust para lexing?
O lexer do CrabPascal usa iterators Rust e máquinas de estado explícitas — sem aritmética unsafe de ponteiros. Buffer overread é impossível por construção. Performance sobra: lexar unit de 10.000 linhas leva milissegundos.
O lexer parece simples, mas ancora tudo downstream. Tokens ruins geram AST ruim; AST ruim gera erros semânticos confusos. O CrabPascal investe em scanning correto para check apontar o problema real — não cascata de ruído do parser.
A seguir: o que o parser faz com esses tokens. 🦀
Published on dev.to/@crabpascal · Código em CrabPascal
Top comments (0)