Projetando uma DSL
Video da aula estara disponivel em breve
O que e uma DSL?
Uma DSL (Domain-Specific Language) e uma linguagem de programacao projetada para um dominio especifico, em contraste com linguagens de proposito geral (GPLs) como Python, Java ou C. DSLs sacrificam generalidade em troca de expressividade e concisao dentro do seu dominio.
GPL (General Purpose Language) DSL (Domain-Specific Language)
+-------------------------------+ +-------------------------------+
| Turing-completa | | Pode ou nao ser Turing-completa|
| Dominio: qualquer problema | | Dominio: problema especifico |
| Sintaxe generica | | Sintaxe otimizada para dominio|
| Curva de aprendizado longa | | Curva curta (para o dominio) |
| Exemplos: Python, Java, C | | Exemplos: |
+-------------------------------+ | SQL (banco de dados) |
| HTML (marcacao) |
| CSS (estilos) |
| regex (padroes de texto) |
| Make (build) |
| Terraform (infraestrutura) |
| GraphQL (queries de API) |
+-------------------------------+
Por que criar uma DSL?
1. Expressividade: dizer mais com menos (SQL vs. loops em Python)
2. Validacao: restringir o que e possivel (erros impossveis)
3. Comunicacao: nao-programadores podem ler/escrever
4. Otimizacao: o compilador/runtime entende o dominio
DSL Interna vs. DSL Externa
Existem duas abordagens fundamentais para criar uma DSL:
DSL INTERNA (embedded DSL)
Usa a sintaxe da linguagem hospedeira (Python, Ruby, Kotlin)
Nao precisa de lexer/parser proprios
Herda ferramentas da linguagem hospedeira (IDE, debugger)
Exemplo: SQLAlchemy (Python)
users.select().where(users.c.age > 18).order_by(users.c.name)
Exemplo: pytest fixtures
@pytest.fixture
def database():
db = create_test_db()
yield db
db.cleanup()
Vantagens: rapida de criar, usa tooling existente
Desvantagens: limitada pela sintaxe da linguagem hospedeira
---------------------------------------------------------
DSL EXTERNA (standalone DSL)
Tem sua propria sintaxe, lexer, parser, semantica
Precisa de tooling proprio (editor support, error messages)
Pode ter sintaxe perfeita para o dominio
Exemplo: SQL
SELECT name, age FROM users WHERE age > 18 ORDER BY name
Exemplo: Terraform
resource "aws_instance" "web" {
ami = "ami-0c55b159"
instance_type = "t2.micro"
}
Vantagens: sintaxe ideal, melhor validacao
Desvantagens: mais trabalho para criar e manter
Design de DSL: Principios
Principios para projetar uma boa DSL:
1. MINIMALISMO
Menos construcoes = menos para aprender
Cada construcao deve "pagar" sua complexidade
"Se voce pode dizer em 3 keywords, nao use 10"
2. LEITURA > ESCRITA
Codigo e lido 10x mais que escrito
A DSL deve ser legivel para quem nao a criou
Favor English-like syntax quando possivel
3. FECHAMENTO DE DOMINIO
A DSL deve cobrir 80%+ dos casos do dominio
Os 20% restantes podem usar escape hatches para a GPL
4. ERROS CLAROS
Mensagens de erro em termos do dominio, nao da implementacao
"Regra 'commit-hygiene' referencia concern 'xyz' que nao existe"
NAO: "KeyError: 'xyz' at line 42 in rule_evaluator.py"
5. COMPOSICAO
Construcoes devem combinar naturalmente
rule + condition = regra condicional
workflow + step = workflow completo
6. IDEMPOTENCIA
Executar duas vezes produz o mesmo resultado
Importante para configuracao e infraestrutura
Projetando a DSL para harness.os
Vamos projetar uma DSL para definir regras (rules) do harness.os. Atualmente, regras sao armazenadas como texto livre no banco de dados. Uma DSL permitiria validacao, composicao e execucao automatica.
Passo 1: Levantar os Requisitos do Dominio
Conceitos do dominio (harness.os rules):
RULE: unidade de governanca
- tem nome, descricao, contexto
- pode ser ativa ou inativa
- pode ter condicoes de ativacao
CONDITION: quando a regra se aplica
- contexto: "testing", "deployment", "all"
- projeto: "way2fly", "cortex.ai", "*"
- dominio: "build", "product", "operations", "domain"
ACTION: o que a regra faz quando ativada
- "block": impede a acao
- "require": exige uma acao adicional
- "warn": emite aviso
- "log": registra no harness
PATTERN: o que a regra detecta
- presenca de texto (ex: "Co-Authored-By")
- tipo de operacao (ex: "git commit")
- fase da sessao (ex: "session_end")
Operacoes:
- Definir regra
- Ativar/desativar regra
- Listar regras por contexto
- Avaliar regras contra uma acao
Passo 2: Projetar a Sintaxe
# Sintaxe proposta para a DSL de regras do harness.os
rule "commit-hygiene" {
description "Nunca adicionar Co-Authored-By: Claude a commits"
context all
domain build
priority high
when git_commit {
contains "Co-Authored-By: Claude"
}
then block {
message "Remova o trailer Co-Authored-By: Claude do commit"
}
}
rule "session-lifecycle" {
description "Toda sessao deve comecar com start_session"
context all
domain build
priority critical
when session_start {
missing "start_session"
}
then require {
action "start_session"
message "Chame start_session() antes de iniciar qualquer trabalho"
}
}
rule "publish-improvements" {
description "Publicar blog post ao melhorar o harness"
context development
domain build
priority medium
when harness_change {
type "schema_migration" or "new_workflow" or "new_knowledge"
}
then require {
action "write_blog_post"
message "Escreva um blog post documentando a melhoria"
}
}
# Regra com multiplas condicoes
rule "token-budget" {
description "Alertar quando contexto excede 80% do budget"
context all
domain operations
priority medium
when context_load {
utilization > 0.8
}
then warn {
message "Contexto em {utilization}% do budget. Considere otimizar."
}
}
Passo 3: Definir a Gramatica Formal
# Gramatica da DSL de regras (BNF simplificado)
program ::= rule_def*
rule_def ::= "rule" STRING "{" rule_body "}"
rule_body ::= property* when_clause then_clause
property ::= IDENTIFIER value
# description STRING
# context IDENTIFIER
# domain IDENTIFIER
# priority IDENTIFIER
value ::= STRING | IDENTIFIER | NUMBER
when_clause ::= "when" IDENTIFIER "{" condition+ "}"
condition ::= IDENTIFIER STRING
| IDENTIFIER comparison_op value
| condition "or" condition
| condition "and" condition
comparison_op ::= ">" | "<" | ">=" | "<=" | "==" | "!="
then_clause ::= "then" action_type "{" action_body "}"
action_type ::= "block" | "require" | "warn" | "log"
action_body ::= property+
# Tokens:
# STRING: "..." (aspas duplas)
# IDENTIFIER: [a-zA-Z_][a-zA-Z0-9_-]*
# NUMBER: [0-9]+(\.[0-9]+)?
# Comentarios: # ate fim de linha
# Whitespace: ignorado (exceto dentro de strings)
Analise da DSL Proposta
Checklist de design da DSL:
[OK] Minimalismo
6 keywords: rule, when, then, and, or, not
4 action types: block, require, warn, log
Propriedades sao pares chave-valor
[OK] Leitura > Escrita
"when git_commit { contains '...' }" e legivel
Nao-programadores entendem a intencao
[OK] Fechamento de dominio
Cobre: definicao de regras, condicoes, acoes
Nao cobre: logica arbitraria (precisa GPL para isso)
[OK] Erros claros
Parser pode dizer: "Linha 5: action type 'blok' nao existe.
Voce quis dizer 'block'?"
[OK] Composicao
Condicoes combinam com and/or
Regras sao independentes e composiveis
[??] Extensibilidade
Como adicionar novos tipos de condicao?
Opcao: plugin system para condition handlers
Comparacao com alternativas:
JSON/YAML: verboso, sem validacao de dominio
Python dict: poderoso demais, erros genericos
DSL propria: concisa, validavel, legivel
DSL Interna em Python (Alternativa)
Antes de construir o lexer e parser (proxima aula), vejamos como seria uma DSL interna equivalente em Python, usando decorators e method chaining:
from dataclasses import dataclass, field
from typing import List, Callable, Optional
@dataclass
class Rule:
name: str
description: str = ""
context: str = "all"
domain: str = "build"
priority: str = "medium"
conditions: List[Callable] = field(default_factory=list)
action_type: str = "warn"
action_message: str = ""
action_handler: Optional[Callable] = None
class RuleBuilder:
"""DSL interna em Python usando builder pattern."""
def __init__(self, name: str):
self.rule = Rule(name=name)
def desc(self, text: str):
self.rule.description = text
return self
def for_context(self, ctx: str):
self.rule.context = ctx
return self
def for_domain(self, domain: str):
self.rule.domain = domain
return self
def with_priority(self, p: str):
self.rule.priority = p
return self
def when(self, condition: Callable):
self.rule.conditions.append(condition)
return self
def then_block(self, message: str):
self.rule.action_type = "block"
self.rule.action_message = message
return self
def then_require(self, message: str, handler: Callable = None):
self.rule.action_type = "require"
self.rule.action_message = message
self.rule.action_handler = handler
return self
def then_warn(self, message: str):
self.rule.action_type = "warn"
self.rule.action_message = message
return self
def build(self) -> Rule:
return self.rule
# Uso da DSL interna
commit_hygiene = (
RuleBuilder("commit-hygiene")
.desc("Nunca adicionar Co-Authored-By: Claude")
.for_context("all")
.for_domain("build")
.with_priority("high")
.when(lambda event: "Co-Authored-By: Claude" in event.get("message", ""))
.then_block("Remova o trailer Co-Authored-By do commit")
.build()
)
print(f"Rule: {commit_hygiene.name}")
print(f"Action: {commit_hygiene.action_type}")
# Testar a regra
event = {"type": "git_commit", "message": "Add feature\n\nCo-Authored-By: Claude"}
for cond in commit_hygiene.conditions:
if cond(event):
print(f"BLOCKED: {commit_hygiene.action_message}")
Comparando as Duas Abordagens
DSL Externa (proposta): DSL Interna (Python):
+----------------------------------+ +----------------------------------+
| rule "commit-hygiene" { | | RuleBuilder("commit-hygiene") |
| when git_commit { | | .when(lambda e: |
| contains "Co-Authored-By" | | "Co-Authored-By" in |
| } | | e.get("message","")) |
| then block { | | .then_block("Remova...") |
| message "Remova..." | | .build() |
| } | | |
| } | | |
+----------------------------------+ +----------------------------------+
Pro: Sintaxe perfeita Pro: Zero infra (sem parser)
Pro: Nao-devs podem editar Pro: Full power de Python
Pro: Validacao rica Pro: IDE support gratis
Con: Precisa de lexer + parser Con: Sintaxe limitada
Con: Tooling proprio Con: Erros genericos
Con: Mais trabalho inicial Con: Precisa saber Python
Para harness.os:
DSL externa para regras publicadas (legibilidade)
DSL interna para prototipagem e regras complexas
Resumo
- Uma DSL e uma linguagem projetada para um dominio especifico, priorizando expressividade sobre generalidade
- DSLs internas usam a sintaxe da linguagem hospedeira; externas tem sintaxe propria
- Bons principios de design: minimalismo, leitura sobre escrita, erros claros, composicao
- O processo de design comeca pelo dominio (requisitos), depois sintaxe, depois gramatica formal
- Para harness.os, projetamos uma DSL de regras com keywords rule, when, then e quatro tipos de acao
- Na proxima aula, implementaremos o lexer e parser para essa DSL
Exercicio
Projete uma DSL para um dominio de sua escolha (configuracao de CI/CD, definicao de workflows, queries de knowledge base, etc.). Defina: (1) pelo menos 5 conceitos do dominio, (2) a sintaxe proposta com 3+ exemplos, (3) a gramatica BNF completa, (4) uma versao interna equivalente em Python. Justifique suas decisoes de design usando os 6 principios discutidos.
Verifique seu entendimento
Qual e a principal diferenca entre uma DSL interna e uma DSL externa?