40 · Metaprogramação
1 exercícios: .
python3 exercicios/run_all.py 40Os exercícios#
| # | Título | Enunciado |
|---|---|---|
| 258 | comptime, macros, DSL e plugin do check |
258 · comptime, macros, DSL e plugin do check#
// A linguagem ja tinha decorador, metaclasse e augment — tudo em
// EXECUCAO. Esta e a outra metade: o que acontece antes de o programa
// rodar, e o que permite gerar codigo a partir de dado.
adopt Arcane.Macro as M
adopt Arcane.Dsl as D
// ── comptime: a conta feita uma vez, na carga ──
comptime QUADRADOS := [i * i cycle i in range(0, 6)]
comptime steady MAXIMO := 2 ** 10
assert QUADRADOS is [0, 1, 4, 9, 16, 25]
assert MAXIMO is 1024
comptime:
action fatorial(n):
yield 1 given n smaller_eq 1 otherwise n * fatorial(n - 1)
TABELA := [fatorial(i) cycle i in range(0, 6)]
assert TABELA is [1, 1, 2, 6, 24, 120]
// a validacao estatica: se a tabela mudar, o check acusa antes de rodar
comptime:
assert len(TABELA) is 6
// 'comptime' continua sendo nome comum fora da declaracao
comptime_qualquer := 3
assert comptime_qualquer is 3
// ── macro: a arvore como dado ──
action somar(a, b):
yield a + b
arvore := M.arvore(somar)
assert arvore["tipo"] is "ActionDeclaration"
assert arvore["parametros"] is ["a", "b"]
assert arvore["corpo"][0]["tipo"] is "YieldStatement"
// citar: texto vira arvore; texto: arvore vira texto
citada := M.citar("x * 2 + 1")
assert citada["tipo"] is "BinaryOp"
assert M.texto(citada) is "x * 2 + 1"
// percorrer: cada no, uma vez
tipos := []
action anotar(nodo):
tipos.append(nodo["tipo"])
M.percorrer(arvore, anotar)
assert len(tipos) bigger 3
assert "BinaryOp" in tipos
// reescrever: o corpo, trocado
action dobro(x):
yield x * 2
action virar_soma(nodo):
given nodo["tipo"] is "BinaryOp" and nodo["op"] is "*":
yield M.citar("x + x")
yield nodo
trocada := M.reescrever(dobro, virar_soma)
assert dobro(5) is 10
assert trocada(5) is 10 // corpo diferente, resultado igual
// gerar: uma acao a partir de texto
gerada := M.compilar("a * 10", "dez_vezes", ["a"])
assert gerada(4) is 40
// higiene: o nome gerado nao captura o de quem chamou
temporario := "do usuario"
corpo := M.citar("temporario + 1")
limpo := M.renomear(corpo, "temporario", M.nome_fresco("temporario"))
assert M.texto(limpo) isnt M.texto(corpo)
// macro de atributo: os campos geram os metodos
mark @M.derivar("texto", "igualdade", "vault")
blueprint Ponto:
x := 1
y := 2
a := spawn Ponto()
b := spawn Ponto()
assert $"{a}" is "Ponto(x=1, y=2)"
assert a is b
assert a.para_vault() is {"x": 1, "y": 2}
// ── DSL: uma linguagem pequena, propria ──
numero := D.mapear(D.numero(), lambda t => cast t as Float)
operador := D.ou([D.texto("+"), D.texto("-"), D.texto("*")])
action aplicar(partes):
esquerda := partes[0]
cycle par in partes[1]:
given par[0] is "+":
esquerda := esquerda + par[1]
orif par[0] is "-":
esquerda := esquerda - par[1]
otherwise:
esquerda := esquerda * par[1]
yield esquerda
expressao := D.mapear(D.seq([numero, D.muitos(D.seq([operador, numero]))]), aplicar)
assert D.analisar(expressao, "2+3*4").valor() is 20.0
assert D.analisar(expressao, "10-4").valor() is 6.0
// a falha diz ONDE, e o que era esperado
ruim := D.analisar(D.numero(), "abc")
assert ruim.falhou()
assert ruim.erro()["posicao"] is 0
assert "numero" in ruim.erro()["esperado"]
// combinadores: alternativa, repeticao, opcional, separado
palavra := D.ou([D.texto("sim"), D.texto("nao")])
lista := D.muitos(D.seq([palavra, D.opcional(D.texto(","))]))
assert len(D.analisar(lista, "sim,nao,sim").valor()) is 3
nomes := D.separado_por(D.nome(), D.texto(","))
assert D.analisar(nomes, "ana,bia,caio").valor() is ["ana", "bia", "caio"]
out "258 ok"A linguagem já tinha decorador, metaclasse e augment — tudo em execução. Esta é a outra metade: o que acontece antes de o programa rodar, e o que permite gerar código a partir de dado.
comptime: a conta feita uma vez#
comptime roda na carga, antes da primeira linha do programa, numa caixa sem E/S. O resultado vira constante.
Se ele pudesse fazer E/S, "tempo de compilação" seria só "mais cedo" — por isso out, adopt, thread e parallel são recusados ali, com o motivo na mensagem.
Três usos que pagam o recurso:
- tabela de consulta calculada uma vez;
- constante derivada (
2 ** 16) escrita como conta, e não como
número mágico;
- validação estática: um
assertdentro decomptimeé uma trava de
build, e o dataforge check a executa (comptime-falhou).
Macro: a árvore como dado#
O decorador troca o valor; a macro troca o corpo.
M.arvore(acao) devolve um vault comum — que percorre com cycle, casa com match e serializa em JSON, sem que nada disso conheça a classe do nó. M.citar(texto) faz o caminho inverso, e é a mesma árvore do arquivo: é isso que permite gerar código que roda.
M.transformar devolve uma árvore nova. Uma macro que mutasse o que recebeu mudaria a ação de quem chamou.
Higiene é explícita
Uma macro que gera um temporário chamado temp quebra quem já tinha um temp. M.nome_fresco e M.renomear existem para isso — e são explícitos, porque fazer higiene sozinho exigiria saber o que é "de dentro", e essa decisão é de quem escreve a macro.
Derivar
mark @M.derivar("texto", "igualdade", "vault") gera __str__, __eq__ e para_vault() a partir dos campos que já existem. É a macro derivada, e roda na carga — não a cada chamada.
DSL externa#
Arcane.Dsl são combinadores: texto, numero, nome, seq, ou, muitos, opcional, separado_por, mapear. D.analisar devolve um Resultado — texto de fora falha o tempo todo, e obrigar monitor em volta faria o caminho normal ser o do erro.
A falha diz posição, o que era esperado e o trecho em volta: "não deu certo" não ajuda a consertar a linha 3 de um arquivo de configuração.
Plugin do check#
Um .df que exporta verificar(arvore, arquivo) e devolve achados com linha, coluna, codigo, mensagem, sugestao e severidade. Rode com --plugin=regras.df, ou declare em forge.toml:
[check]
plugins = ["regras.df"]
O código aparece na mensagem, e // df: permitir <codigo> silencia a regra na linha — sem isso, quem discordasse de uma regra teria de desligar o plugin inteiro.
Um plugin quebrado vira diagnóstico, e não traceback: quem roda o check quer o relatório do código dele.
Rode um isolado com dataforge run exercicios/40-metaprogramacao/258_comptime_e_macros.df.