Verificando e Gerando Expressões
Tags: javascript, programminglanguage, languagedesign, compilers
Published: 2024-01-06 13:58:37

### Type Checker

Continuando nosso [capítulo de expressões na nossa linguagem](https://dev.to/lucas_ac_am/expressoes-encadeadas-e-agrupamento-14g6), neste post vamos focar na parte de verificação das expressões, alterando nosso _type checker_ e também ajustando nosso _generator_ para criar transformar as ASTs em javascript válido.

Atualmente nossa função `check_binary_expression` avalia as partes _left_ e _right_ do node são numéricas, porém agora nossas expressões podem conter outras expressões inteiras uma dentro da outra de forma arbitrária.
Nossa função `check_program` já muda a verificação dependendo do tipo do node, portanto podemos simplesmente usar recursividade e essa nossa função para resolver esse problema.
Nossa função `check_binary_expression` ficará assim agora:
```typecheck
function check_binary_expression(node) {
  const { left, right } = node
  return check_program(left) && check_program(right)
}
```

Perceba que essa mudança é bem significativa pois antes estávamos usando a função `check_numeric` para verificar os componentes _left_ e _right_ do node, assim tínhamos certeza que os operadores aritméticos estavam sendo usado apenas com números, porém isso não faz mais sentido, primeiro porque temos mais tipos diferentes de expressões binárias e não só aritméticas e segundo que um operador é tecnicamente uma função e ainda não temos um mecanismo definido para verificar "assinaturas" de funções comparado com os argumentos da função, só conseguiremos fazer uma verificação mais profunda quando tivermos esse mecanismo criado.
Por enquanto vamos fazer verificação estrutural apenas, seguindo a lógica vamos alterar nossa função `check_unary_expression` também:
```typecheck.js
function check_unary_expression(node) {
  const { argument } = node
  return check_program(argument)
}
```

**IMPORTANTE**: Ao fazer um teste com a função `check_program` atual achei um erro que tinha passado despercebido, no meio da função temos a verificação `if (type === 'literal') {` porém não existem nodes do tipo `literal`, na verdade `literal` representa todos os tipos de node de valores como `int`, `float`, `string`, etc. Por isso vamos alterar nossa função para corrigir esse erro:
```typecheck.js
function check_program(ast) {
  const { type } = ast
  if (check_literal(ast)) {
    return true
  } else if (type === 'binary_expression') {
    return check_binary_expression(ast)
  } else if (type === 'unary_expression') {
    return check_unary_expression(ast)
  } else {
    console.log(`Invalid AST has type = ${type}`)
    return false
  }
}
```
Nesta nova versão utilizamos a função já previamente definida `check_literal` para "pular" o caso do node to "tipo" `literal`.

Agora com os novos ajustes vamos altera nosso arquivo exemplo para `2 * 3 + 4 || 3 - (3 * 1 + 3) == 1` rodar o comando `node parser ex.ln0` e depois o comando `node typecheck ast.json`
Na tela é _printado_ `true` indicando que a AST está "correta", isso ainda não significa muita coisa, mas em breve vamos trabalhar mais nisso e implementar o sistema de validação de "assinatura" de funções, por enquanto isso já está bom o suficiente.

### Generator
Vamos focar agora no generator e transformar nossa AST em javascript válido contendo as várias expressões que já conseguimos _parsear_.

No nosso _generator_ temos os mesmos problemas para resolver, precisamos usar recursão e arrumar nossa função principal que também contêm a verificação do tipo `literal` que não faz sentido.

Primeiro vamos alterar nossas funções geradoras `gen_binary_expression` e `gen_unary_expression` usando recursão para gerar os parâmetros:
```generator.js
function gen_binary_expression(node) {
  const { operator, left, right } = node
  return `${gen_program(left)} ${operator.value} ${gen_program(right)}`
}

function gen_unary_expression(node) {
  const { operator, argument } = node
  return `${operator.value}${gen_program(argument)}`
}
```

E depois alteramos nossa função principal, pra isso vamos "emprestar" nossa função `check_literal` do módulo `typecheck`: 
```typecheck.js
module.exports.check_literal = check_literal
```

```generator.js
const { check_literal } = require('./typecheck')
//...
function gen_program(ast) {
  const { type } = ast
  if (check_literal(ast)) {
    return gen_literal(ast)
  } else if (type === 'binary_expression') {
    return gen_binary_expression(ast)
  } else if (type === 'unary_expression') {
    return gen_unary_expression(ast)
  } else {
    console.log(`Invalid AST has type = ${type}`)
    return ''
  }
}
```

Agora com todas essas modificações, basta rodarmos o comando `node generator ast.json` o nosso arquivo `output.js` fica dessa forma: `2 * 3 + 4 || 3 - 3 * 1 + 3 == 1`. Está quase correto, o único problema é a falta dos parênteses que estavam no arquivo original, eles sumiram porque atualmente nossa definição gramática ignora os parênteses, para arrumarmos isso precisamos alterar nossa gramática primeiro:
```lang0.ne
primary_expression
  -> literal {% id %}
  | %lparen term_expression %rparen {% data => ({
    type: 'parenthesized_expression',
    expression: data[1],
  }) %}
```

Agora introduzimos um novo _type_ de node, o `parenthesized_expression`, com essa diferenciação conseguimos preservar a estrutura original, mas antes de continuar vamos _buidlar_ nossa gramática rodando o comando `pnpm nc`.

Agora precisamos adicionar os _handlers_ do tipo `parenthesized_expression` no `typecheck.js` e no `generator.js`, começando com o `typecheck.js`:
```typecheck.js
function check_program(ast) {
  const { type } = ast
  if (check_literal(ast)) {
    return true
  } else if (type === 'binary_expression') {
    return check_binary_expression(ast)
  } else if (type === 'unary_expression') {
    return check_unary_expression(ast)
  } else if (type === 'parenthesized_expression') {
    const { expression } = ast
    return check_program(expression)
  } else {
    console.log(`Invalid AST has type = ${type}`)
    return false
  }
}
```

Por ser algo bem simples de se fazer vou deixar no meio da função mesmo, vamos fazer agora o do `generator.js`:
```generator.js
function gen_program(ast) {
  const { type } = ast
  if (check_literal(ast)) {
    return gen_literal(ast)
  } else if (type === 'binary_expression') {
    return gen_binary_expression(ast)
  } else if (type === 'unary_expression') {
    return gen_unary_expression(ast)
  } else if (type === 'parenthesized_expression') {
    return gen_parenthesized_expression(ast)
  } else {
    console.log(`Invalid AST has type = ${type}`)
    return ''
  }
}
```

Neste caso vamos criar uma função separada mesmo, a função `gen_parenthesized_expression`:
```generator.js
function gen_parenthesized_expression(node) {
  const { expression } = node
  return `(${gen_program(expression)})`
}
```

Agora, ao rodar o comando `node typecheck ast.json` temos o resultado `true`, indicando que tudo está funcionando bem e ao rodar o comando `node generator ast.json` o resultado é: `2 * 3 + 4 || 3 - (3 * 1 + 3) == 1` agora com os parêntesis.

### Finalização
Nossa linguagem agora consegue "enxergar" vários tipos de expressões com vários níveis de profundidade e depois consegue transformar tudo em javascript. Por enquanto não há nenhuma diferença pois estamos tratando apenas de expressões básicas e ainda não implementamos nosso sistema de _tipagem_ nem de funções, nem de verificação de "assinatura" de funções e é isso que vamos focar no próximo post. Até mais!