Macros: a árvore como dado
Ler o corpo de uma ação, percorrer, transformar, gerar código e derivar métodos — com higiene explícita.
Um decorador troca o valor: recebe a ação pronta e devolve outra coisa. O que ele não alcança é o corpo — e metade da metaprogramação que vale a pena é sobre o corpo: instrumentar cada instrução, derivar um método a partir dos campos, gerar uma ação a partir de um esquema.
A árvore é um vault#
adopt Arcane.Macro as M
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" Ela é um dado comum: percorre com cycle, casa com match, serializa em JSON e atravessa processo — sem que nada disso precise conhecer a classe do nó. É o mesmo raciocínio do Quadro e da ponte para o Python.
citar: texto vira árvore#
adopt Arcane.Macro as M
arvore := M.citar("x * 2 + 1")
assert arvore["tipo"] is "BinaryOp"
assert M.texto(arvore) is "x * 2 + 1" Reescrever o corpo#
adopt Arcane.Macro as M
action dobro(x):
yield x * 2
action trocar(nodo):
given nodo["tipo"] is "BinaryOp" and nodo["op"] is "*":
yield M.citar("x + x")
yield nodo
trocada := M.reescrever(dobro, trocar)
assert dobro(5) is 10
assert trocada(5) is 10 // o corpo é outro, o resultado é o mesmoA árvore original não é tocada: transformar devolve uma nova. Uma macro que mutasse o que recebeu mudaria a ação de quem chamou.
Gerar código#
adopt Arcane.Macro as M
corpo := M.citar("a * 10")
gerada := M.acao("dez_vezes", ["a"], corpo)
assert gerada(4) is 40
assert M.arvore(gerada)["nome"] is "dez_vezes"
// e o atalho, num passo só
pronta := M.compilar("a + b", "soma", ["a", "b"])
assert pronta(2, 3) is 5Higiene: não capturar o nome de quem chamou#
A macro que gera um temporário chamado temp quebra o código de quem já tinha um temp. nome_fresco e renomear existem para isso, e são explícitos: fazer higiene sozinho exigiria saber o que é "de dentro", e essa decisão é de quem escreve a macro.
adopt Arcane.Macro as M
corpo := M.citar("temporario + 1")
limpo := M.renomear(corpo, "temporario", M.nome_fresco("temporario"))
assert M.texto(limpo) isnt M.texto(corpo)
assert "__temporario_" in M.texto(limpo)Macro de atributo: derivar#
Em vez de escrever __str__ e __eq__ à mão em cada blueprint, os campos que já existem geram os dois:
adopt Arcane.Macro as M
mark @M.derivar("texto", "igualdade")
blueprint Ponto:
x := 1
y := 2
a := spawn Ponto()
b := spawn Ponto()
assert $"{a}" is "Ponto(x=1, y=2)"
assert a is b| Derivável | Gera |
|---|---|
texto | __str__ com os campos, no formato Nome(campo=valor, …) |
igualdade | __eq__ comparando todos os campos |
ordem | __lt__ pelo primeiro campo |
vault | para_vault() com os campos |
A superfície#
| Símbolo | O que faz |
|---|---|
M.arvore(acao) | a árvore da ação (ou do blueprint) como dado |
M.citar(texto) | texto vira árvore |
M.texto(arvore) | árvore vira texto (aproximado) |
M.percorrer(arvore, visitante) | chama o visitante em cada nó |
M.transformar(arvore, acao) | árvore nova, com cada nó passado pela ação |
M.substituir · M.renomear | troca por tipo de nó, ou nome de identificador |
M.nome_fresco(base) | um nome que o código de fora não tem |
M.acao(nome, params, corpo) · M.compilar(texto, …) | gera a ação |
M.reescrever(acao, transformador) | ação nova, corpo transformado |
M.derivar(…) | a macro de atributo |