Trilha 12 · Linguagens e compiladores

O léxico: tokens e lexers

A primeira etapa de todo compilador: transformar uma sequência de caracteres em uma lista de tokens com significado.

① Intuição

Ler código como palavras, não caracteres

Para um computador, seu código fonte é apenas uma string de caracteres. A primeira tarefa do compilador é agrupar esses caracteres em unidades com significado: tokens. let é um token keyword. 42 é um token number. + é um token operator. Espaços e comentários são descartados.

Esse processo se chama análise léxica (ou tokenização), feita pelo lexer (também chamado de scanner ou tokenizer). O lexer usa expressões regulares para reconhecer cada categoria de token. A saída é uma lista plana de tokens que o parser vai consumir.

Por que separar lexer do parser? Separação de responsabilidades: o lexer lida com caracteres e regex simples. O parser lida com estrutura e gramática. Lexers são muito mais rápidos que parsers — processar um arquivo de 10.000 linhas em tokens leva microssegundos.
② Visualização interativa

Tokenize código em tempo real

Edite o código ou escolha um exemplo. Cada token aparece colorido por tipo. Passe o mouse para inspecionar.

CÓDIGO FONTE
KEYWORD (3)
IDENT (7)
NUMBER (2)
OP (2)
PUNC (10)
TOKENS (24)
function
soma
(
a
,
b
)
{
return
a
+
b
;
}
const
resultado
=
soma
(
3
,
7
)
;
③ Explicação técnica

Implementando um lexer

// Estrutura de um lexer simples em JavaScript

const RULES = [
  { type: "KEYWORD", pattern: /^(if|else|while|return|let|const)/ },
  { type: "NUMBER",  pattern: /^d+(.d+)?/               },
  { type: "STRING",  pattern: /^"[^"]*"/                    },
  { type: "IDENT",   pattern: /^[a-zA-Z_]w*/               },
  { type: "OP",      pattern: /^[+-*/<>=!]+/              },
  { type: "PUNC",    pattern: /^[(){}[];,.]/               },
  { type: "SPACE",   pattern: /^s+/                        },
];

function lex(source) {
  const tokens = [];
  let i = 0;
  while (i < source.length) {
    let matched = false;
    for (const rule of RULES) {
      const m = source.slice(i).match(rule.pattern);
      if (m) {
        if (rule.type !== "SPACE") tokens.push({ type: rule.type, value: m[0] });
        i += m[0].length; matched = true; break;
      }
    }
    if (!matched) throw new Error(`Char inesperado: ${source[i]}`);
  }
  return tokens;
}

Saída de tokens

// Saída de lex("let x = 10 + y;")

[
  { type: "KEYWORD", value: "let"  },
  { type: "IDENT",   value: "x"    },
  { type: "OP",      value: "="    },
  { type: "NUMBER",  value: "10"   },
  { type: "OP",      value: "+"    },
  { type: "IDENT",   value: "y"    },
  { type: "PUNC",    value: ";"    },
]

// Whitespace e comentários: descartados (não viram tokens)
// Cada token carrega: tipo + valor + posição (linha, coluna) na prática
Lexer vs. Regex: em produção, lexers gerados por ferramentas como Flex (C), ANTLR (múltiplas linguagens) ou Moo (JS) são mais eficientes que regex aplicadas sequencialmente. Eles constroem um DFA (autômato finito determinístico) que processa o input em O(n) com um único pass — sem backtracking.
④ Projeto para programar

Construa seu próprio lexer

Mini projeto: implemente um lexer que tokeniza expressões matemáticas: números (inteiros e decimais), operadores (+, -, *, /, ^), parênteses e variáveis. Ignore espaços. Retorne um array de tokens.

Projeto principal: implemente um lexer completo para um subconjunto de Python: keywords (if, else, while, def, return), identifiers, numbers, strings, operadores, e indentação (INDENT/DEDENT tokens — o grande desafio de Python). Teste com 20 programas de exemplo.

Desafio extra: implemente um lexer usando um DFA gerado manualmente para reconhecer floats, inteiros, strings com escape (\"), comentários de linha e bloco, e identificadores Unicode. Meça o throughput em linhas/segundo.

⑤ Exercícios rápidos

Teste sua intuição

O que um lexer (tokenizador) faz?
Qual categoria tipicamente não gera tokens na saída do lexer?
Qual é a saída de um lexer?
⑥ Aplicações no mundo real

Onde você encontra isso

🎨

Syntax highlighting

VS Code, Vim e todos os editores usam lexers para colorir código. O popular pacote shiki (usado neste site) tokeniza código com gramáticas TextMate e aplica cores por tipo de token.

🔍

Linters

ESLint, Pylint e Rubocop tokenizam antes de analisar. Algumas regras são puramente léxicas: no-trailing-spaces, max-line-length — não precisam do AST, só dos tokens.

🛡️

WAF e SQL injection

Web Application Firewalls detectam SQL injection tokenizando o input: se tokens SQL (SELECT, DROP, UNION) aparecem em contexto inesperado, a request é bloqueada — sem precisar parsear SQL completo.

📝

Markdown parsers

Remark, marked e commonmark usam lexers para tokenizar Markdown: detectam headings (# ), listas (- ), code fences (```), e então o parser constrói a AST que vira HTML.

← Trilha: Linguagens e compiladores Próxima: A Árvore Sintática →