Precedenza della funzione nell'algoritmo Shunting-yard

7

Sto lavorando attraverso l'algoritmo Shunting-yard , come descritto da wikipedia.

La descrizione dell'algoritmo quando si tratta di operatori è la seguente:

If the token is an operator, o1, then:

while there is an operator token, o2, at the top of the operator stack, and either

o1 is left-associative and its precedence is less than or equal to
that of o2, or

o1 is right associative, and has precedence less than that of o2,

then pop o2 off the operator stack, onto the output queue;

push o1 onto the operator stack.

Tuttavia, danno il seguente esempio:

Input: sin max 2 3 / 3 * 3.1415

Quando l'algoritmo colpisce il token / , la descrizione di cosa dovrebbe accadere è la seguente:

Token |        Action       |   Output (in RPN) |   Operator Stack
...
/     | Pop token to output | 2 3 max           | / sin 
...

Stanno scoppiando il token di funzione max di stack e inserendo queue . Secondo il loro algoritmo, questo sembrerebbe significare che la funzione token è sia un operatore che ha una precedenza inferiore a quella dell'operatore / .

Non vi è alcuna spiegazione sul fatto se questo sia il caso. Quindi, per l'algoritmo Shunting-yard , qual è la precedenza di una funzione? Le funzioni sono associate a destra o a sinistra? O è wikipedia solo incompleto / inaccurato?

    
posta MirroredFate 17.07.2015 - 20:06
fonte

2 risposte

5

Credo che la risposta diretta sia semplicemente che le funzioni non sono operatori. Dalla pagina che hai collegato:

If the token is a function token, then push it onto the stack.

Questo è tutto ciò che è necessario dire, dal momento che la funzione case (prefisso al postfix) è molto più semplice del caso dell'operatore (infisso per postfix).

Per le domande di follow-up: le nozioni di precedenza e associatività sono necessarie solo a causa dell'ambiguità ereditaria in qualsiasi espressione con più operatori di infissi. I token funzione stanno già usando la notazione del prefisso, quindi semplicemente non hanno questo problema. Non è necessario sapere se sin o max ha "precedenza più alta" per capire che max deve essere valutato per primo; è già chiaro dall'ordine dei token. Ecco perché i computer preferiscono la notazione pre / postfix per cominciare, e perché abbiamo questo algoritmo per convertire infix in pre / postfix.

Devi avere un qualche tipo di regola per dove gli argomenti di una funzione iniziano e finiscono quando non ci sono parentesi, quindi puoi dire che le funzioni "hanno la precedenza" sugli operatori o viceversa. Ma a differenza degli operatori infissi, una singola regola coerente per tutte le funzioni è sufficiente per rendere le loro composizioni completamente non ambigue.

    
risposta data 17.07.2015 - 22:45
fonte
1

Ci sono due casi diversi da considerare, a seconda della sintassi della tua lingua. Se la tua lingua usa la parentesi per indicare l'applicazione della funzione (ad esempio f(2+1) ), la precedenza è irrilevante. La funzione dovrebbe essere inserita nello stack e rilasciata dopo (per l'esempio sopra, il risultato è 2 1 + f ). In alternativa puoi considerare la funzione come un valore e inviarla immediatamente, ed eseguire un'operazione di chiamata di funzione dopo la parentesi chiusa (che dovrebbe altrimenti essere trattata come qualsiasi altra parentesi), ad esempio f 2 1 + $ , dove $ è la funzione operazione di chiamata.

Se la tua lingua, tuttavia, non usa la parentesi per indicare la funzione di invocazione, ma pone l'argomento direttamente dopo la funzione senza alcuna punteggiatura speciale (es. f 2 + 1 ), come evidentemente è il caso dell'esempio di Wikipedia, quindi le cose sono un po 'più complicato. Nota che l'espressione che ho appena dato ad un esempio è ambigua: è f applicato a 2 e 1 aggiunto al risultato, oppure aggiungiamo 2 e 1 insieme e poi chiamiamo f con il risultato?

Ancora una volta, ci sono due approcci. Puoi semplicemente spingere la funzione sulla pila dell'operatore quando la incontri e assegnarla a qualunque precedenza tu voglia. Questo è l'approccio più semplice ed è apparentemente quello che l'esempio citato ha fatto. Ci sono comunque problemi pratici. In primo luogo, come si identifica una funzione? Se si dispone di un set finito è facile, ma se si dispone di funzioni definite dall'utente, ciò significa che il parser ha bisogno di essere reindirizzato nel proprio ambiente, che può diventare disordinato rapidamente. E come gestisci le funzioni con più argomenti?

La mia sensazione è che per questo stile di sintassi, usare le funzioni come valori più utili da un operatore di applicazioni di funzione ha molto più senso. Quindi, puoi semplicemente iniettare l'operatore dell'applicazione ogni volta che leggi un valore e anche l'ultima cosa che hai letto era un valore, quindi non hai bisogno di alcun modo speciale per dire quali identificatori sono funzioni. Puoi anche lavorare con espressioni che restituiscono funzioni (che è difficile o impossibile con lo stile di funzione-come-operazione). Questo significa che puoi usare currying per gestire più funzioni di argomenti, il che è una grande semplificazione nel tentativo di gestirli direttamente.

L'unica cosa che devi decidere è quale sia la precedenza dell'applicazione della funzione. La scelta spetta a te, ma in tutte le lingue che ho usato funziona così, è stato l'operatore più vincolante nella lingua e ha avuto ragione associativa. (L'unica variazione interessante è Haskell, che oltre ad avere la versione strongmente vincolante descritta, ha anche un sinonimo per esso con il simbolo $ , che è l'operatore più debole nella lingua, permettendo espressioni come f 2 + 1 da applicare f a 2 e f $ 2 + 1 per applicarlo all'intero resto dell'espressione)

    
risposta data 04.06.2017 - 20:45
fonte

Leggi altre domande sui tag