Há dois casos diferentes a serem considerados, dependendo da sintaxe do seu idioma. Se o seu idioma usa parênteses para indicar a aplicação da função (por exemplo f(2+1)
), a precedência é irrelevante. A função deve ser empurrada para a pilha e removida depois (no exemplo acima, o resultado é 2 1 + f
). Como alternativa, você pode tratar a função como um valor e produzi-la imediatamente, e gerar uma operação de invocação de função após o parêntese próximo (que, caso contrário, deve ser tratado da mesma forma que qualquer outro parêntese), por exemplo f 2 1 + $
, onde $
está a operação de invocação de função.
Se seu idioma, no entanto, não usa parênteses para indicar a chamada de função, mas coloca o argumento diretamente após a função sem nenhuma pontuação especial (por exemplo f 2 + 1
), como é aparentemente o caso do exemplo da Wikipedia, as coisas ficam um pouco mais complicadas. Observe que a expressão que acabei de dar como exemplo é ambígua: f é aplicado a 2 e 1 adicionado ao resultado ou adicionamos 2 e 1 juntos e depois chamamos f com o resultado?
Novamente, existem duas abordagens. Você pode simplesmente enviar a função para a pilha do operador quando a encontrar e atribuí-la à precedência que desejar. Essa é a abordagem mais simples e, aparentemente, é o que o exemplo citado fez. Existem questões práticas, no entanto. Em primeiro lugar, como você identifica uma função? Se você tem um conjunto finito, é fácil, mas se você possui funções definidas pelo usuário, isso significa que seu analisador precisa também alimentar o seu ambiente, o que pode ficar confuso rapidamente. E como você lida com funções com vários argumentos?
Meu sentimento é que, para esse estilo de sintaxe, o uso de funções como valores mais úteis por um operador de aplicativo de função faz muito mais sentido. Então, você pode simplesmente injetar o operador do aplicativo sempre que ler um valor e a última coisa que ler também for um valor, para que você não precise de nenhuma maneira especial de dizer quais identificadores são funções. Você também pode trabalhar com expressões que retornam funções (o que é difícil ou impossível com o estilo de função como operação). E isso significa que você pode usar currying para lidar com várias funções de argumento, o que é uma simplificação maciça ao tentar lidar com elas diretamente.
A única coisa que você precisa decidir é qual é a precedência do aplicativo de funções. A escolha é sua, mas em todos os idiomas que usei que funcionam assim, ele tem sido o operador mais fortemente vinculante do idioma e tem uma associação associativa. (A única variação interessante é Haskell, que além de ter a versão fortemente vinculativa descrita, também possui um sinônimo com o símbolo $
que é o operador mais fraco na linguagem, permitindo expressões como f 2 + 1
aplicar f a 2 e f $ 2 + 1
aplicar para todo o resto da expressão)
sin( max( 2 3) / 3 * 3.1415)