Mais níveis de precedência e mais operadores
Tags: programminglanguage, javascript, series, languagedesign
Published: 2023-12-23 13:10:25

[No último post](https://dev.to/lucas_ac_am/operadores-numericos-sao-mais-complexos-do-que-parece-2f16) adicionamos os operadores binários `+` e `-` em nossa linguagem, nesse post vamos adicionar os operadores binários `*` e `/` além dos unários `!` e `-`, repare que o sinal de menos poder tanto um operador binário onde realmente seria uma operação de subtração ou pode ser um operador unário indicando que o número é negativo.

Olhando nas nossas definições de tokens iniciais, podemos já identificar um problema, os token incluem sinalização, teremos que alterar isso para não causar confusão no parser:
Atualmente temos essas duas definições:
```tokenizer.js
//...
  float: /[-+]?(?:\d+\.\d*|\.\d+)(?:[eE][-+]?\d+)?/,
  int: /0|[-+]?[1-9][0-9]*/,
//...
```

Vamos altera-las para:
```tokenizer.js
//...
  float: /(?:\d+\.\d*|\.\d+)(?:[eE][-+]?\d+)?/,
  int: /0|[1-9][0-9]*/,
//...
```

Dessa forma temos controle total da nossa gramática e o _tokenizer_ não vai nos atrapalhar.

---

Com isso resolvido vamos continuar com nossa modificação, primeiramente implementando os operadores `*` e `/`

### Adicionando os operadores `*` e `/`
Vamos adicionar os operadores à gramática:
```lang0.ne
factor_operator
  -> %star {% id %}
  | %slash {% id %}
```

E a regra de expressão única do tipo _factor_:
```lang0.ne
factor_expression
  -> literal __ factor_operator __ literal {% data => ({
    type: 'binary_expression',
    operator: data[2],
    left: data[0],
    right: data[4],
  }) %}
```

vamos também adicionar a regra `factor_expression` como opção para definição de um `program` válido:
```lang0.ne
program
  -> literal {% id %}
  | term_expression {% id %}
  | factor_expression {% id %}
```

Lembrando mais uma vez que por enquanto a linguagem suporta apenas uma expressão por vez. Uma multi-expressão como essa `2 + 3 * 4` ainda não é suportada pela nossa linguagem, trabalharemos nisso mais pra frente.

Para prosseguirmos vamos compilar nossa gramática com o comando `pnpm nc`.
Também vou alterar nosso programa exemplo dessa forma:
```ex.ln0
2 * 2
```

E compilar o programa: `node parser ex.ln0`
O resultado final é correto e fica dessa forma (omiti algumas informações por efeitos de concisão):
```ast.json
{
  "type": "binary_expression",
  "operator": {
    "type": "star",
    "value": "*",
    //...
  },
  "left": {
    "type": "int",
    "value": "2",
    //...
  },
  "right": {
    "type": "int",
    "value": "2",
    //...
  }
}
```

Como os novos operadores geram _nodes_ do tipo _binary_expression_ não precisamos alterar nosso arquivo `typecheck.js`. Da mesma forma nossa função `gen_binary_expression` do nosso arquivo `generator.js` já funcionará corretamente.

Para verificar vou continuar com o processo rodando o comando `node typecheck ast.json`, o resultado é _true_.
E rodando o comando `node generator ast.json`, o resultado é o arquivo `output.js` contendo o texto `2 * 2`, ou seja, tudo funcionando perfeitamente.

### Adicionando operadores unários
Operadores unário são operadores que recebem apenas um operando, os principais são o operador de negação lógica `!` e o operador de negação aritmética `-` repare que o o símbolo `-` pode ser tanto o operador binário de subtração aritmética quanto a outra versão, o operador unários.

Para evitar confusão vamos definir uma regra geral para a linguagem onde os operadores unário dever estar localizados imediatamente ao lado do operando, por exemplo, esse seria um código inválido `- 2`, por causa do espaço, o correto seria `-2` sem espaço.

Para isso precisamos alterar nossa gramática e nosso tokenizer mais uma vez.

Começando com alterações dos tokens, vamos adicionar o símbolo de negação lógica `!`:
```tokenizer.js
//...
  bang: '!',
//...
```

Na nossa gramática vamos criar as regras para operadores e expressões unárias:
```lang0.ne
unary_operator
  -> %bang {% id %}
  | %dash {% id %}

#...

unary_expression
  -> unary_operator literal {% data => ({
    type: 'unary_expression',
    operator: data[0],
    argument: data[1],
  }) %}
```

Perceba que na definição de `unary_expression` não há nenhuma regra de espaçamento (`_` ou `__`) para indicarmos que espaço entre o operador e o operando é proibido.

Precisamos incluir a nova regra na definição de `program`:
```lang0.ne
program
  -> literal {% id %}
  | term_expression {% id %}
  | factor_expression {% id %}
  | unary_expression {% id %}
```

Agora podemos compilar o arquivo de gramática usando `pnpm nc`

Vamos alterar nosso exemplo agora para fazer o teste de compilação:
```ex.ln0
-3
```

rodando o comando `node parser ex.ln0` temos como resultado a seguinte AST:
```ast.json
{
  "type": "unary_expression",
  "operator": {
    "type": "dash",
    "value": "-",
    "text": "-",
    "offset": 0,
    "lineBreaks": 0,
    "line": 1,
    "col": 1
  },
  "argument": {
    "type": "int",
    "value": "3",
    "text": "3",
    "offset": 1,
    "lineBreaks": 0,
    "line": 1,
    "col": 2
  }
}
```

O que indica que tudo está funcionando corretamente.

Para finalizar basta adicionar novas funções para expressões unárias nos arquivos `typecheck.js` e `generator.js`

Começando com o `typecheck.js` precisamos criar uma função `check_unary_expression` e alterar nossa lógica principal.
Primeiro criamos a função:
```typecheck.js
function check_unary_expression(node) {
  const { argument } = node
  return check_number(argument)
}
```

E agora alteramos a lógica principal adicionando a _branch_ de `unary_expression`:
```typecheck.js
function check_program(ast) {
  const { type } = ast
  if (type === 'literal') {
    return check_literal(ast)
  } 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
  }
}
```

Rodando o comando `node typecheck ast.json` o resultado no console é `true` indicando sucesso.

Por último vamos criar a função no arquivo `generator.js`:
```generator.js
function gen_unary_expression(node) {
  const { operator, argument } = node
  return `${operator.value}${argument.value}`
}
```

E agora basta alterar a lógica principal também:
```generator.js
function gen_program(ast) {
  const { type } = ast
  if (type === 'literal') {
    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 ''
  }
}
```

Rodando o comando `node generator ast.json` o arquivo `output.js` possuiu o texto `-3` indicando que tudo está funcionando corretamente

### Próximos passos
Por enquanto nossa expressões são "únicas", ou seja, expressões encadeadas como essa `2 + 3 * 4` simplesmente não são suportadas pela nossa linguagem ainda e é nisso que vamos trabalhar no [próximo capítulo](https://dev.to/lucas_ac_am/expressoes-encadeadas-e-agrupamento-14g6).