DEV Community

CrabPascal
CrabPascal

Posted on

CrabPascal Lexer: How Source Becomes Tokens | Lexer: do fonte aos tokens

Bilingual post · Post bilíngue

Jump to: English · Português


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;
Enter fullscreen mode Exit fullscreen mode

The lexer produces something like:

VAR  IDENT("count")  COLON  IDENT("Integer")  ASSIGN  INTEGER(42)  SEMICOLON
Enter fullscreen mode Exit fullscreen mode

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 ':='
Enter fullscreen mode Exit fullscreen mode

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 *)
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

Define CRABPASCAL in config:

[compiler]
defines = ["CRABPASCAL"]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

O lexer produz algo como:

VAR  IDENT("count")  COLON  IDENT("Integer")  ASSIGN  INTEGER(42)  SEMICOLON
Enter fullscreen mode Exit fullscreen mode

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 ':='
Enter fullscreen mode Exit fullscreen mode

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 *)
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

Defina CRABPASCAL na config:

[compiler]
defines = ["CRABPASCAL"]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)