O parser
Descida recursiva: uma função por nível de precedência, a recuperação que junta vários erros, e o fim de linha exigido.
O parser é descendente recursivo: uma função por construção da gramática, e uma por nível de precedência. 1 + 2 * 3 vira + na raiz porque a função da soma chama a da multiplicação para ler cada lado — a precedência está na ordem das chamadas, e não numa tabela.
adopt Arcane.Compilador as Comp
raiz := Comp.arvore("x := 1 + 2 * 3")["corpo"][0]["valor"]
assert raiz["op"] is "+"
assert raiz["direita"]["op"] is "*" // o * ficou mais fundo: liga mais forte| Do mais fraco | ao mais forte |
|---|---|
>> (pipeline) | a mais fraca de todas |
a given c otherwise b | o ternário |
?? | abaixo de or — x ?? 0 is 0 é x ?? (0 is 0): o check avisa |
or · and · not | |
is, bigger, smaller… | encadeiam: 1 smaller x smaller 9 |
+ - · * / ~/ % · ** | ** associa à direita |
| unário, chamada, índice, membro | o mais forte |
Vários erros de uma vez#
Um parser que para no primeiro erro obriga a corrigir, compilar e corrigir uma vez por erro. Este registra o erro, ressincroniza no começo da próxima instrução e continua: quem lê recebe os quatro erros do arquivo de uma vez. O erro que viaja continua sendo um só, e os demais vão em outros.
Uma instrução por linha#
Depois de uma instrução completa, a linha tem de acabar. x := 1 vazios era aceito como duas instruções — x := 1 e vazios —, e o caso que custou caro foi o comentário começando com número, que virava uma divisão seguida de uma instrução solta. Dezessete blocos desta documentação só "compilavam" por isso; hoje todos compilam de verdade.
adopt Arcane.Compilador as Comp
recusado := no
monitor:
Comp.arvore("x := 1 vazios")
handle Error as e:
recusado := e.message.contains("same line")
assert recusado