DSL em TypeScript: criando sua própria linguagem de programação em uma noite

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Por que seu projeto merece sua própria linguagem?

\n\n

Imagine: você escreve código para calcular prêmios de seguro e, em vez de dezenas de if-else aninhados e números mágicos, vê uma construção concisa:

\n\n
const rule = new InsuranceRule()\n  .when(age > 25)\n  .and(experience > 2)\n  .then(premium = base * 0.8);
\n\n

Isso não é mágica. Isso é um DSL (Domain Specific Language) — uma linguagem específica de domínio, incorporada ao TypeScript. Neste artigo, vamos explorar como criar essas linguagens para que seu código fale a linguagem do negócio, e não a linguagem das máquinas.

\n\n

O que é DSL e por que você precisa dela?

\n\n

DSL é uma linguagem criada para resolver problemas de um domínio específico. Ao contrário das linguagens de propósito geral (Java, C#, TypeScript), a DSL não tenta ser universal. Ela sacrifica flexibilidade em prol da expressividade.

\n\n

Quando usar DSL:

\n
    \n
  • A lógica de negócios muda com frequência e precisa ser editada por analistas
  • \n
  • O projeto tem muitos padrões de configuração repetitivos
  • \n
  • É necessário isolar algoritmos complexos do restante do código
  • \n
  • É necessária validação de dados no nível da gramática
  • \n
\n\n

TypeScript é a escolha ideal para criar DSLs. Tipagem estática, um sistema de tipos poderoso e a capacidade de compilar para JavaScript o tornam uma ferramenta flexível para construir linguagens internas e externas.

\n\n

DSLs internas: fluent API e encadeamento de chamadas

\n\n

A maneira mais simples de criar uma DSL em TypeScript é usar uma Fluent Interface (interface fluente). Isso ocorre quando cada método retorna o próprio objeto, permitindo construir cadeias de chamadas.

\n\n

Exemplo: construtor de consultas SQL

\n\n
class QueryBuilder {\n  private table: string = '';\n  private conditions: string[] = [];\n  private selectedFields: string[] = ['*'];\n\n  select(...fields: string[]): this {\n    this.selectedFields = fields.length ? fields : ['*'];\n    return this;\n  }\n\n  from(table: string): this {\n    this.table = table;\n    return this;\n  }\n\n  where(condition: string): this {\n    this.conditions.push(condition);\n    return this;\n  }\n\n  and(condition: string): this {\n    return this.where(condition);\n  }\n\n  build(): string {\n    let sql = `SELECT ${this.selectedFields.join(', ')} FROM ${this.table}`;\n    if (this.conditions.length) {\n      sql += ` WHERE ${this.conditions.join(' AND ')}`;\n    }\n    return sql;\n  }\n}\n\n// Uso\nconst query = new QueryBuilder()\n  .select('name', 'age')\n  .from('users')\n  .where('age > 18')\n  .and('status = "active"')\n  .build();\n\nconsole.log(query);\n// SELECT name, age FROM users WHERE age > 18 AND status = "active"
\n\n

Este é um DSL interno clássico em TypeScript. Ele não requer um parser separado e funciona dentro do código TypeScript. A principal vantagem é o suporte completo a autocompletar e tipagem.

\n\n

Complexificando: DSL tipado para regras de negócio

\n\n

Vamos adicionar tipagem estrita com genéricos e tipos union:

\n\n
type ComparisonOperator = '>' | '<' | '=' | '!=';\n\nclass RuleBuilder<T extends Record<string, any>> {\n  private conditions: ((data: T) => boolean)[] = [];\n\n  when<K extends keyof T>(field: K, op: ComparisonOperator, value: T[K]): this {\n    this.conditions.push((data: T) => {\n      switch (op) {\n        case '>': return data[field] > value;\n        case '<': return data[field] < value;\n        case '=': return data[field] === value;\n        case '!=': return data[field] !== value;\n      }\n    });\n    return this;\n  }\n\n  and<K extends keyof T>(field: K, op: ComparisonOperator, value: T[K]): this {\n    return this.when(field, op, value);\n  }\n\n  evaluate(data: T): boolean {\n    return this.conditions.every(c => c(data));\n  }\n}\n\n// Tipo de dados para verificação\ninterface UserData                

Blogs

Book Recommendations