Il modo più duro
Vuoi un parser di discesa ricorsivo .
Per avere la precedenza devi pensare in modo ricorsivo, ad esempio, usando la tua stringa di esempio,
1+11*5
per farlo manualmente, dovresti leggere il 1
, quindi vedere il plus e avviare una nuova "sessione" di analisi ricorsiva iniziando con 11
... e assicurarti di analizzare il 11 * 5
in un proprio fattore, producendo un albero di analisi con 1 + (11 * 5)
.
Tutto ciò sembra così doloroso persino da tentare di spiegare, specialmente con l'ulteriore impotenza di C.Vedi, dopo aver analizzato l'11, se * fosse effettivamente un + invece, dovresti abbandonare il tentativo di creare un termine e invece analizzare il 11
stesso come fattore. La mia testa sta già esplodendo. È possibile con la strategia decente ricorsiva, ma c'è un modo migliore ...
Il modo facile (giusto)
Se utilizzi uno strumento GPL come Bison, probabilmente non devi preoccuparti dei problemi di licenza poiché il codice C generato da bison non è coperto dalla GPL (IANAL ma sono abbastanza sicuro che gli strumenti GPL non forzino la GPL codice generato / binari; per esempio Apple compila codice come ad esempio Aperture con GCC e lo vendono senza dover GPL detto codice).
Scarica Bison (o qualcosa di equivalente, ANTLR, ecc.).
Di solito c'è un codice di esempio su cui puoi semplicemente eseguire bison e ottenere il codice C desiderato che dimostra questo calcolatore a quattro funzioni:
http://www.gnu.org/software/bison/manual/html_node/Infix-Calc.html
Guarda il codice generato e vedi che non è così facile come sembra. Inoltre, i vantaggi dell'utilizzo di uno strumento come Bison sono 1) impari qualcosa (soprattutto se leggi il libro del drago e impari la grammatica), 2) eviti che NIH cerchi di reinventare la ruota. Con un vero strumento generatore di parser, hai effettivamente una speranza di ridimensionare in seguito, mostrando ad altre persone che sai che i parser sono il dominio degli strumenti di analisi.
Aggiornare:
Le persone qui hanno offerto molti buoni consigli. Il mio unico avvertimento contro il saltare gli strumenti di analisi o semplicemente usare l'algoritmo di Shunting Yard o un parser decente ricorsivo a mano è che i linguaggi giocattolo 1 un giorno potrebbero trasformarsi in grandi linguaggi reali con funzioni (sin, cos, log) e variabili, condizioni e per loop.
Flex / Bison può benissimo essere eccessivo per un piccolo, semplice interprete, ma un parser + valutatore una tantum può causare problemi su tutta la linea quando è necessario apportare modifiche o è necessario aggiungere funzionalità. La tua situazione varierà e dovrai usare il tuo giudizio; semplicemente non punire altre persone per i tuoi peccati [2] e costruisci uno strumento meno che adeguato.
Il mio strumento preferito per l'analisi
Il miglior strumento al mondo per il lavoro è la libreria Parsec (per parser decenti ricorsivi) che viene fornita con il linguaggio di programmazione Haskell. Assomiglia molto a BNF , o come uno strumento specializzato o un linguaggio specifico per il dominio per l'analisi (codice di esempio [3]), ma in realtà è solo una normale libreria in Haskell, il che significa che si compila nella stessa fase di compilazione del resto del tuo codice Haskell e puoi scrivere codice Haskell arbitrario e chiamarlo all'interno del tuo parser, e puoi mescolare e abbinare altre librerie tutte nello stesso codice . (Incorporare un linguaggio di analisi come questo in un linguaggio diverso da Haskell si traduce in un sacco di cruft sintattico, a proposito. L'ho fatto in C # e funziona abbastanza bene ma non è così carino e succinto.)
Appunti:
1 Richard Stallman dice, in Perché non dovresti usare Tcl
La lezione principale di Emacs è che un linguaggio per estensioni non dovrebbe essere un semplice "linguaggio di estensione". Dovrebbe essere un vero linguaggio di programmazione, progettato per scrivere e mantenere programmi sostanziali. Perché le persone vorranno farlo!
[2] Sì, sono per sempre sfregiato dall'usare quel "linguaggio".
Nota anche che quando ho inviato questa voce, l'anteprima era corretta, ma il parser meno che adeguato di SO ha mangiato il mio tag di ancoraggio di chiusura nel primo paragrafo , dimostrando che i parser non sono qualcosa con cui scherzare perché se usi espressioni regolari e una tantum ti hackera probabilmente otterrà qualcosa di sottile e piccolo di sbagliato .
[3] Frammento di un parser Haskell che utilizza Parsec: un calcolatore a quattro funzioni esteso con esponenti, parentesi, spazi bianchi per la moltiplicazione e costanti (come pi ed e).
aexpr = expr `chainl1` toOp
expr = optChainl1 term addop (toScalar 0)
term = factor `chainl1` mulop
factor = sexpr `chainr1` powop
sexpr = parens aexpr
<|> scalar
<|> ident
powop = sym "^" >>= return . (B Pow)
<|> sym "^-" >>= return . (\x y -> B Pow x (B Sub (toScalar 0) y))
toOp = sym "->" >>= return . (B To)
mulop = sym "*" >>= return . (B Mul)
<|> sym "/" >>= return . (B Div)
<|> sym "%" >>= return . (B Mod)
<|> return . (B Mul)
addop = sym "+" >>= return . (B Add)
<|> sym "-" >>= return . (B Sub)
scalar = number >>= return . toScalar
ident = literal >>= return . Lit
parens p = do
lparen
result <- p
rparen
return result