Pular para o conteúdo

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#

dataforge
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 5

O 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#

dataforge
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#

GrupoSímbolos
básicostexto, numero, nome, entre_aspas, espaco, simbolo, qualquer_de, ate
combinarseq, ou, muitos, opcional, separado_por
transformarmapear, exigir (troca a mensagem de falha)
recursãoadiado(lambda => regra), gramatica(regras, inicial)
rodaranalisar(regra, texto)Resultado
dataforge
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 3

Uma calculadora inteira#

dataforge
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.0

Qual DSL usar#

QuandoA forma
a linguagem é o DataForge, com vocabulário próprioblueprint com operadores, pipeline (>>) e ação de alta ordem
o texto vem de fora e tem gramática suaArcane.Dsl — esta página
você quer palavra nova na linguagempalavra contextual no parser (é como o Kiln e o Quadro fazem) — e isso é mexer no DataForge
gerar código a partir de dado`Arcane.Macro`