Pular para o conteúdo

O livro · 15

Tooling na prática

book.md · 84 linhas · 2 min de leitura

Objetivo: conhecer o CLI mk no dia a dia, as ferramentas de runtime (observer/profiler/ debugger), e como o package manager resolve versões.

A decisão fundacional: o compilador não é um batch que parseia, checa e morre. É uma biblioteca incremental de queries (modelo rust-analyzer). Quase toda ferramenta é um consumidor do entendimento que o compilador tem do código (o LSP pergunta “qual o tipo de X?”, o linter pede a info semântica, o doc gen as assinaturas). Por isso há um binário único, nada de gopls/zls à parte. A incrementalidade é por declaração: editou uma função, re-checa aquela declaração e o que depende dela.

Tudo num binário (mk é alias de makoto):

Janela do terminal
mk run app.mko // compila e roda
mk build // compila o projeto → binário
mk test // roda os testes (@test) e benchmarks (@bench)
mk fmt // formatter zero-config (um formato canônico; sem bikeshedding)
mk lint // linter fechado (regras fixas; acaba a format-war)
mk get <repo> // adiciona uma dep
mk tidy // sincroniza deps/lockfile
mk doc // gera docs (de /// e dos .mkoui)
mk new / mk init // novo projeto / inicializa no diretório
mk update / mk version

Dois modos do compilador: portátil (um arquivo no PATH, default) e instalado (mk install host explode em N estágios versionáveis, pra atualização parcial, como trocar só o parser de TS em vez de tudo).

Testes moram junto do código (@test) ou em arquivo_test.mko/tests/. A matriz de execução @testmode(order, state) cobre (sequential|parallel) × (isolated|shared); o default (sequential, isolated) é o seguro. Abra a matriz pra caçar uma race:

@test
fn parse_aceita_valido() {
r := parse("42")
test.expect_eq(test.expect_ok(r), 42)
}

Benchmarks são @bench; o runner é configurável (esforço), mas o comparador é fixo (a estatística que decide regressão-vs-ruído, anti-p-hacking).

Apoiam-se na observabilidade do runtime (pull, default-off; você não paga pelo que não liga):

  • observer: o frontend de runtime.processes()/trace(), com árvore de supervisão, processos vivos, channels e o |e| das mortes (o :observer do BEAM).
  • profiler: sampling/tracing opt-in, com saída textual estruturada (um LLM lê o profile como texto e raciocina sobre ele) além de flamegraph.
  • step-debugger: feito pro modelo de atores, com breakpoints process-only (pausa só aquele) ou everything, e breakpoints linkados (ABC disparam juntos) pra pegar a interação entre processos no momento exato.
ev := runtime.trace(.Named("worker"), [.send, .receive], sink: .channel, sample: 100)
loop e in ev { match e { Send(m) => ...; Receive(m) => ... } }

Resolução de versão por MVS (Minimal Version Selection) mais lockfile mk.sum (checksums auto-gerados, build reproduzível). O registry é híbrido: libs oficiais por nome (first-party), terceiros por URL (estilo Go). O wire-format do registry é a serialização nativa da linguagem.

Codegen tem dois lares: extensões de compilador (§20, in-process, como a UI) para o que toca o pipeline, e o task runner para ferramentas externas (um protoc, um minificador), declaradas com suas capabilities no mk.project. Sem REPL (decisão explícita).


Você chegou ao fim do passeio. Daqui, os docs canônicos (language-design.md, language-grammar.md, official_libraries.md, extensions.md, tooling.md) são a referência completa; este livro foi a porta de entrada. Bom Makoto.