Criando DSLs (Domain Specific Languages) com Python

Em sistemas complexos, muitas vezes precisamos de linguagens personalizadas para resolver problemas específicos de forma mais expressiva e eficiente. Essas linguagens são chamadas de Domain Specific Languages (DSLs). Python, com sua sintaxe flexível e recursos avançados, é uma linguagem ideal para criar DSLs de forma prática e poderosa.

Neste post, vamos explorar como criar DSLs com Python, exemplos de DSLs internas e externas, técnicas de parsing, execução de comandos e boas práticas.


1. O que são DSLs?

Domain Specific Languages são linguagens criadas para um domínio específico, diferente de linguagens gerais (como Python ou Java). Características:

  • Focadas em um problema específico

  • Expressivas e legíveis para especialistas do domínio

  • Reduzem complexidade de tarefas recorrentes

Exemplos de DSLs conhecidas:

DSLDomínio
SQLManipulação de bancos de dados
RegexExpressões regulares
CSSEstilização de páginas web
Terraform HCLInfraestrutura como código

Python permite criar DSLs internas (embedded) usando a própria sintaxe Python, ou DSLs externas, com parser e executor próprios.


2. Tipos de DSLs

2.1 DSL interna (Embedded DSL)

  • Usa a linguagem host (Python) para criar abstrações que parecem uma linguagem específica do domínio.

  • Não precisa de parser complexo.

  • Exemplo clássico: SQLAlchemy para consultas SQL em Python.

Exemplo simples: DSL de consulta matemática

class CalculatorDSL:
    def __init__(self):
        self.result = 0

    def add(self, x):
        self.result += x
        return self

    def multiply(self, x):
        self.result *= x
        return self

    def subtract(self, x):
        self.result -= x
        return self

calc = CalculatorDSL()
result = calc.add(5).multiply(2).subtract(3).result
print(result)  # Saída: 7

Vantagens:

  • Simples de implementar

  • Aproveita todas as ferramentas da linguagem Python

  • Fácil de integrar em sistemas existentes


2.2 DSL externa (External DSL)

  • Cria uma nova linguagem com sua própria sintaxe.

  • Requer parser e executor.

  • Útil quando se quer uma linguagem totalmente customizada independente do Python.

Exemplo: DSL para configurar tarefas

Arquivo tasks.dsl:

task backup:
    source: "/data"
    destination: "/backup"
    schedule: "daily"

task cleanup:
    path: "/tmp"
    schedule: "hourly"

Python pode parsear e executar essas tarefas.


3. Técnicas para criar DSLs em Python

3.1 Funções e fluent interfaces

  • Criam sintaxe legível usando encadeamento de métodos (method chaining).

  • Excelente para DSLs internas.

Exemplo de DSL para fluxo de dados:

class DataPipeline:
    def __init__(self, data):
        self.data = data

    def filter(self, func):
        self.data = list(filter(func, self.data))
        return self

    def map(self, func):
        self.data = list(map(func, self.data))
        return self

    def reduce(self, func):
        from functools import reduce
        self.data = reduce(func, self.data)
        return self

pipeline = DataPipeline([1,2,3,4,5])
result = pipeline.filter(lambda x: x%2==0).map(lambda x: x*10).data
print(result)  # Saída: [20, 40]

3.2 Decorators

  • Úteis para criar DSLs de configuração ou registro de funções.

registry = {}

def task(name):
    def decorator(func):
        registry[name] = func
        return func
    return decorator

@task("backup")
def backup_task():
    print("Executando backup...")

registry["backup"]()

Vantagens:

  • Sintaxe limpa

  • Fácil de estender

  • Ótimo para DSLs de workflows e automação


3.3 Parsers para DSLs externas

Python oferece bibliotecas para parser de linguagens externas:

BibliotecaFunção
larkConstrução de grammars EBNF completas
pyparsingParser combinator, ótimo para DSLs
ANTLRGerador de parser para múltiplas linguagens
PLYImplementação de Lex/Yacc em Python

Exemplo com lark:

from lark import Lark, Transformer

dsl_grammar = """
start: command+
command: "PRINT" STRING
%import common.STRING
%import common.WS
%ignore WS
"""

class DslTransformer(Transformer):
    def command(self, items):
        print(items[0][1:-1])  # remove aspas

parser = Lark(dsl_grammar, parser="lalr", transformer=DslTransformer())
parser.parse('PRINT "Olá mundo!" PRINT "DSL em Python"')

3.4 Execução dinâmica

  • Use eval ou exec para interpretar DSLs simples em runtime.

  • Deve ser feito com cuidado, apenas para DSLs internas confiáveis.

dsl_code = "x = 5 + 3"
exec(dsl_code)
print(x)  # Saída: 8

Dica: combine parsing seguro com execução limitada (exec em locals restritos).


4. Exemplos práticos de DSLs em Python

4.1 DSL para consultas em dados (estilo SQL)

class QueryDSL:
    def __init__(self, data):
        self.data = data
        self.filters = []

    def where(self, func):
        self.filters.append(func)
        return self

    def execute(self):
        result = self.data
        for f in self.filters:
            result = list(filter(f, result))
        return result

data = [{"nome":"Alice","idade":25},{"nome":"Bob","idade":20}]
query = QueryDSL(data).where(lambda x: x["idade"]>21).execute()
print(query)  # Saída: [{'nome': 'Alice', 'idade': 25}]

4.2 DSL para pipelines de tarefas

class Workflow:
    def __init__(self):
        self.tasks = []

    def add_task(self, func):
        self.tasks.append(func)
        return self

    def run(self):
        for task in self.tasks:
            task()

workflow = Workflow()
workflow.add_task(lambda: print("Backup")).add_task(lambda: print("Limpeza")).run()

5. Boas práticas ao criar DSLs

  1. Clareza e legibilidade: DSLs devem ser intuitivas para especialistas do domínio.

  2. Evite sobrecarregar a sintaxe: simplicidade é mais importante que poder máximo.

  3. Validação de entrada: sempre verifique comandos e parâmetros.

  4. Documentação: exemplos de uso são essenciais.

  5. Extensibilidade: permita adicionar comandos ou operadores facilmente.

  6. Separação do domínio: lógica de negócios separada do parser ou execução da DSL.


6. Vantagens de criar DSLs com Python

  • Produtividade: abstrai complexidade do código Python puro

  • Expressividade: especialistas do domínio entendem rapidamente

  • Testabilidade: DSLs internas aproveitam ferramentas Python

  • Flexibilidade: integração com APIs, frameworks e pipelines existentes

  • Prototipagem rápida: Python permite iterar DSLs rapidamente


7. Conclusão

Criar DSLs com Python é uma estratégia poderosa para resolver problemas específicos do domínio de maneira expressiva e eficiente. Seja usando DSLs internas com fluent interfaces, decorators e classes, ou DSLs externas com parser e executor próprios, Python oferece ferramentas robustas para desenvolver linguagens customizadas.

Com boas práticas de design, validação, documentação e execução segura, é possível:

  • Reduzir complexidade do código

  • Tornar pipelines e workflows mais intuitivos

  • Integrar especialistas do domínio diretamente no desenvolvimento de sistemas

Comentários

Postagens mais visitadas deste blog

Gerando Relatórios em PDF com Python (ReportLab e FPDF)

Python para Computação Quântica: Introdução com Qiskit

Estrutura Básica de um Programa C# com exemplos