No último post 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:
//... float: /[-+]?(?:\d+\.\d*|\.\d+)(?:[eE][-+]?\d+)?/, int: /0|[-+]?[1-9][0-9]*/, //...
Vamos altera-las para:
//... 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:
factor_operator -> %star {% id %} | %slash {% id %}
E a regra de expressão única do tipo factor:
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:
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:
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):
{ "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 !
:
//... bang: '!', //...
Na nossa gramática vamos criar as regras para operadores e expressões unárias:
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
:
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:
-3
rodando o comando node parser ex.ln0
temos como resultado a seguinte AST:
{ "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:
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
:
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
:
function gen_unary_expression(node) { const { operator, argument } = node return `${operator.value}${argument.value}` }
E agora basta alterar a lógica principal também:
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.
Top comments (0)