O trabalho do lexer é apenas a separação dos tokens, ele não se importa se a sintaxe estiver quebrada (por exemplo, se tem um parênteses sem fechar, ele não vai se importar). Os erros léxicos são, geralmente, um caractere desconhecido. Se eu rodar 1 % 1, ele não vai ter nenhum conhecimento do que é esse tal %, e aí sim, disparar um erro.Iremos representar um token como um objeto que possui as propriedades type e value (para os literals), dessa forma teremos uma lista de tokens parecida com isso:
  while (current < input.length) { // continua a execução até que o ponteiro esteja no final do input
Para entender melhor o ponteiro, explico: a string tem um comportamento bem parecido à um array no que se refere à indexes e iteração. Conseguimos acessar um caractere da string passando a posição dela. Dessa forma, a cada vez que o ponteiro é incrementado, conseguimos ler o próximo caracter da string e aplicar a lógica que desejamos.
Os whitespaces são essenciais para escrevermos códigos legíveis, e em muitas vezes eles também tem seu papel sintático. No nosso caso, eles vão ser apenas ignorados. Então quando o nosso lexer encontrar um whitespace, ele vai apenas prosseguir:
A maioria dos tokens que utilizaremos são de apenas um caractére (*, +, (, …), então eles são os mais simples de se parsear, pois não é necessário olhar os chars da frente, apenas o atual dentro da iteração:
Existem tokens formados por mais de um caractere. Na maioria das linguagens esse grupo forma maior parte dos tokens, porém, no nosso caso será apenas o token Number
Para cobrir este cenário, a partir do momento que encontrarmos o primeiro caractere de número, iremos observar seus próximos caracteres e ir agrupando enquanto continuarem sendo números, e quando encontrarmos um caracter qualquer que não seja o que procuramos, cancelamos o processo e salvamos o token com o agrupamento final.
Agora que cobrimos os cenários de todos os nossos tokens, qualquer outro caractere que apareça vai ser um que não está sendo tratado. Por isso, disparamos um erro.
No final de tudo, adicionamos um token para indicar o fim da nossa expressão e retornamos todos os tokens.