DSLs
Combinadores de análise para escrever uma linguagem pequena: gramática própria, falha com posição e o resultado como Resultado.
A linguagem já tem duas formas de DSL interna: palavra contextual no parser (as onze do Kiln, os seis verbos do Quadro) e objeto com operadores. As duas exigem mexer no DataForge ou desenhar uma API.
O que faltava era a DSL externa: ler um texto que segue uma gramática sua — uma regra de preço, um filtro de busca, um formato de configuração — sem trazer dependência nem escrever um analisador à mão com índice e persist.
Uma gramática em quatro linhas#
adopt Arcane.Dsl as D
numero := D.mapear(D.numero(), lambda t => cast t as Integer)
soma := D.mapear(D.seq([numero, D.texto("+"), numero]),
lambda partes => partes[0] + partes[2])
r := D.analisar(soma, "2+3")
assert r.deu_certo() and r.valor() is 5O resultado é um `Resultado`, e não uma exceção: texto de fora falha o tempo todo, e obrigar monitor em volta de cada análise faria o caminho normal ser o do erro.
A falha diz onde#
adopt Arcane.Dsl as D
r := D.analisar(D.numero(), "abc")
assert r.falhou()
assert r.erro()["posicao"] is 0
assert "numero" in r.erro()["esperado"]"Não deu certo" não ajuda ninguém a consertar a linha 3 de um arquivo de configuração. A falha traz posição, o que era esperado e o trecho em volta.
Os combinadores#
| Grupo | Símbolos |
|---|---|
| básicos | texto, numero, nome, entre_aspas, espaco, simbolo, qualquer_de, ate |
| combinar | seq, ou, muitos, opcional, separado_por |
| transformar | mapear, exigir (troca a mensagem de falha) |
| recursão | adiado(lambda => regra), gramatica(regras, inicial) |
| rodar | analisar(regra, texto) → Resultado |
adopt Arcane.Dsl as D
palavra := D.ou([D.texto("sim"), D.texto("nao")])
lista := D.muitos(D.seq([palavra, D.opcional(D.texto(","))]))
r := D.analisar(lista, "sim,nao,sim")
assert r.deu_certo() and len(r.valor()) is 3Uma calculadora inteira#
adopt Arcane.Dsl as D
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.0Qual DSL usar#
| Quando | A forma |
|---|---|
| a linguagem é o DataForge, com vocabulário próprio | blueprint com operadores, pipeline (>>) e ação de alta ordem |
| o texto vem de fora e tem gramática sua | Arcane.Dsl — esta página |
| você quer palavra nova na linguagem | palavra contextual no parser (é como o Kiln e o Quadro fazem) — e isso é mexer no DataForge |
| gerar código a partir de dado | `Arcane.Macro` |